@termwright/conformance 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter-conformance.ts","../src/support/probe.ts","../src/support/pty.ts","../../probe-ink/src/instrumentation.ts","../../probe-ink/src/certified-instrumentation.json","../../probe-ink/src/react-commit-bridge.ts","../../probe-ink/src/annotations.ts","../../probe-ink/src/observe.ts","../../probe-ink/src/version.ts","../../probe-ink/src/probe-info.ts","../../probe-ink/src/session.ts","../../pty/src/index.ts","../../pty/src/windows.ts","../../pty/src/write-drain-epoch.ts","../../pty/src/windows-output-normalizer.ts","../../recognizers/src/recognize.ts","../../recognizers/src/naming.ts","../../recognizers/src/ink.ts","../../recognizers/src/opentui.ts","../../probe-ink/src/shim.ts","../../probe-ink/src/runtime.ts","../../probe-ink/src/launch.ts","../src/support/probe-peer-owner.ts","../src/support/probe-process-shutdown.ts","../src/support/probe-startup.ts","../src/support/probe-start-cleanup.ts"],"sourcesContent":["/**\n * The adapter contract suite — the part of this package that is meant to be\n * used from outside it.\n *\n * An adapter is conforming when five things hold, and they are the same five in\n * every language: it stays dormant without an endpoint, it completes the\n * handshake, every snapshot it publishes is valid, it orders each revision as\n * snapshot → commit → marker-after-the-frame, and it survives losing the\n * channel without taking the application with it.\n *\n * The suite drives the adapter as a subprocess and observes only bytes and\n * frames, so a Python, Go or Rust adapter self-certifies exactly like the Ink\n * one — nothing here imports an adapter.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n ADAPTER_CAPABILITIES,\n validateSnapshot,\n DEFAULT_LIMITS,\n PROTOCOL_ID,\n} from '@termwright/protocol';\nimport type { SemanticSnapshot } from '@termwright/protocol';\nimport {\n AdapterProbe,\n MARKER_TEXT_PREFIX,\n type AdapterCommand,\n type ProbeObservation,\n} from './support/probe.js';\nimport { commandAvailable, ptyAvailable } from './support/pty.js';\n\n/** How to start, drive and stop the adapter under test. */\nexport interface AdapterConformanceOptions {\n /** Name of the adapter, used in the test titles. */\n readonly name: string;\n /** Command that starts the instrumented application. */\n spawn(): AdapterCommand;\n /**\n * Optional command rendering the same UI with the adapter compiled out. When\n * given, the dormant run is compared against it byte for byte — the strongest\n * form of the dormant rule. Without it, a dormant run is only checked for\n * silence on the wire.\n */\n baseline?(): AdapterCommand;\n /** Text that proves the first frame reached the terminal. */\n readonly ready: string | RegExp;\n /**\n * An input that changes the screen, and the text that proves it landed.\n *\n * The suite sends it **more than once** — a dormant run, a run that has to\n * produce a second revision, and a run after the channel was cut all need a\n * render. Pick something whose repetition is harmless.\n */\n readonly interaction: { readonly input: string; readonly expect: string | RegExp };\n /**\n * An input that makes the application exit, and the status it exits with.\n *\n * It must work from **any** state repeated `interaction` can reach. A key\n * that quits only while a particular widget has focus is not a quit input:\n * the tview example's documented `q` types into its text field once focus has\n * cycled that far, so its registration uses Ctrl+C instead.\n */\n readonly quit: { readonly input: string; readonly exitCode?: number };\n readonly columns?: number;\n readonly rows?: number;\n /** Assert that intended geometry is authoritative in viewport cells. */\n readonly expectIntendedGeometry?: boolean;\n /**\n * Opt out of the \"publishes a tree before any input\" obligation.\n *\n * By default an adapter must publish a non-empty tree once the handshake\n * completes, with no input sent — an app that is addressable only after the\n * first keystroke is not addressable at all to a driver that has just\n * launched it. Some apps legitimately render nothing until an event arrives;\n * pass a reason, which is printed in the test title so the exemption stays\n * visible rather than becoming folklore.\n */\n readonly treeBeforeInput?: { readonly required: false; readonly reason: string };\n /**\n * How to make the application log, for an adapter that announces the `logs`\n * capability. Without it the log obligations are skipped; with it they are\n * asserted, and an adapter that announces `logs` but never delivers one\n * fails here rather than in a user's test.\n */\n /**\n * How to check the normative adapter conventions (protocol README, \"Adapter\n * semantics conventions\"). Rules 1, 2 and 4 are largely judgement calls from\n * outside; what is listed here is what a subprocess can actually observe.\n *\n * A rule an adapter cannot follow is a *declared deviation*, not a failure:\n * name it in `deviations` and the matching check is skipped with the reason\n * in the test title, so the exemption stays visible instead of becoming\n * folklore.\n */\n readonly conventions?: {\n /** A test id the fixture sets by author annotation (rule 3). */\n readonly annotatedTestId?: string;\n /** A textbox whose field is empty, to prove `value: ''` (rule 5). */\n readonly emptyTextboxTestId?: string;\n /** A container with no name of its own, wrapping text (rule 2). */\n readonly unnamedContainerTestId?: string;\n /**\n * Test ids whose `value` is author-annotated. The role gate in rule 5\n * bounds *derived* values; an annotation may put one on any role, and only\n * the registration knows which is which.\n */\n readonly annotatedValues?: readonly string[];\n /**\n * The adapter's README. Its `## Deviations` section is the single source of\n * truth for what this adapter cannot do (rule 6), so a declared limitation\n * is read from there rather than repeated in the registration — two copies\n * of the same fact disagree eventually, and the README is the one a user\n * reads.\n */\n readonly readmePath?: string;\n };\n readonly logs?: {\n /**\n * Input that makes the application write a record. Omit it for an app that\n * logs on its own (at startup, say) — the obligation then waits for a\n * record rather than provoking one.\n */\n readonly input?: string;\n /** A substring of the logged message, used to prove it stayed off-screen. */\n readonly expect: string;\n };\n /** How long the handshake may take. Default 10 s. */\n readonly timeoutMs?: number;\n /**\n * A cheap command that must succeed before this adapter can be certified here.\n * Compilers and other descendant-producing preparation must run before the\n * native host opens; this probe is collection-time capability validation only.\n * When it fails the whole suite skips and the reason is in the block's name,\n * exactly as a missing pseudo-terminal does.\n */\n readonly requires?: {\n readonly probe: readonly string[];\n readonly label: string;\n readonly cwd?: string;\n readonly timeoutMs?: number;\n readonly env?: Readonly<Record<string, string>>;\n };\n}\n\n/**\n * Directory the convention summaries are written to, and read back by\n * `scripts/conformance.mjs` when it prints the matrix.\n */\nexport const CONVENTION_SUMMARY_DIR = join(tmpdir(), 'termwright-conformance-conventions');\n\n/**\n * Records what each rule concluded, so the matrix can print a per-adapter\n * roll-up of declared deviations.\n *\n * This exists because a hand-maintained table of per-adapter gaps went stale\n * within one round of being written — a generated one cannot. Nothing here is\n * a gate: the file is a report, and the tests already decided pass or fail.\n */\nfunction writeConventionSummary(\n name: string,\n declared: Map<string, string[]>,\n outcomes: readonly ConventionOutcome[],\n): void {\n try {\n mkdirSync(CONVENTION_SUMMARY_DIR, { recursive: true });\n const file = join(CONVENTION_SUMMARY_DIR, `${name.replace(/[^\\w.-]+/gu, '_')}.json`);\n writeFileSync(\n file,\n JSON.stringify(\n {\n adapter: name,\n declared: Object.fromEntries(declared),\n outcomes,\n // A rule declared in the README that no check covers: the suite\n // cannot confirm or refute it, and saying so is more honest than\n // letting it read as verified.\n unverified: [...declared.keys()].filter(\n (rule) => !outcomes.some((outcome) => outcome.rule === rule),\n ),\n },\n null,\n 2,\n ),\n 'utf8',\n );\n } catch {\n // A report that cannot be written must not fail a conformance run.\n }\n}\n\n/**\n * Waits until the child stops writing, so two captures end at comparable\n * points. A stabilisation, not a deadline: an app that never stops writing\n * makes the comparison fail loudly rather than pass by accident.\n */\nasync function settle(probe: AdapterProbe, quietMs = 250, budgetMs = 5_000): Promise<void> {\n const deadline = performance.now() + budgetMs;\n let seen = -1;\n for (;;) {\n const length = probe.observe().stdout.length;\n if (length === seen) return;\n seen = length;\n if (performance.now() >= deadline) return;\n await new Promise((resolve) => {\n setTimeout(resolve, quietMs);\n });\n }\n}\n\n/**\n * Rule numbers declared in an adapter's `## Deviations` section.\n *\n * Three shapes are in use and all are accepted, because the suite should not\n * dictate anyone's prose: `**Rule 2 — …**` (a heading per entry, Ink),\n * `- **…** (rule 3).` (the number at the end of a bullet, the language\n * clients), and a markdown table whose first column is `2 — …` (OpenTUI).\n *\n * Entries that name no rule are kept under `other`: they are still declared\n * limitations, and dropping them would make the roll-up quietly incomplete.\n */\nexport function parseDeclaredDeviations(readme: string): Map<string, string[]> {\n const declared = new Map<string, string[]>();\n const start = readme.indexOf('## Deviations');\n if (start < 0) return declared;\n const rest = readme.slice(start + '## Deviations'.length);\n const end = rest.indexOf('\\n## ');\n const section = end < 0 ? rest : rest.slice(0, end);\n\n for (const line of section.split('\\n')) {\n for (const match of line.matchAll(/\\*\\*Rule (\\d+)\\s*—\\s*([^*]+?)\\.?\\*\\*/gu)) {\n add(declared, match[1] as string, (match[2] as string).trim());\n }\n for (const match of line.matchAll(/\\*\\*(.+?)\\*\\*\\s*\\(rule (\\d+)\\)/gu)) {\n add(declared, match[2] as string, (match[1] as string).trim());\n }\n const trimmed = line.trim();\n if (\n trimmed.startsWith('|') &&\n !/^\\|[\\s:|-]*\\|?$/u.test(trimmed) &&\n !/^\\|\\s*rule\\s*\\|/iu.test(trimmed)\n ) {\n // A table row. The rule number and its title share the first cell\n // (`2 — name sources`); a row that names no rule carries its text in the\n // second cell instead.\n const cells = trimmed\n .split('|')\n .slice(1, -1)\n .map((cell) => cell.trim());\n const numbered = /^(\\d+)\\s*[—-]\\s*(.+)$/u.exec(cells[0] ?? '');\n if (numbered !== null) {\n add(declared, numbered[1] as string, (numbered[2] as string).trim());\n } else if ((cells[1] ?? '').length > 0) {\n add(declared, 'other', cells[1] as string);\n }\n }\n }\n return declared;\n}\n\nfunction add(map: Map<string, string[]>, key: string, value: string): void {\n map.set(key, [...(map.get(key) ?? []), value]);\n}\n\n/** What a convention check concluded, for the run summary. */\ninterface ConventionOutcome {\n readonly rule: string;\n readonly what: string;\n readonly status: 'compliant' | 'documented' | 'checked-despite-declaration' | 'violation';\n readonly detail?: string;\n}\n\n/** Roles that are containers: never named from what they contain (rule 2). */\nconst CONTAINER_ROLES: ReadonlySet<string> = new Set([\n 'region',\n 'dialog',\n 'list',\n 'table',\n 'application',\n 'menu',\n]);\n\nconst snapshotsOf = (observation: ProbeObservation): SemanticSnapshot[] =>\n observation.messages\n .filter((entry) => entry.message.type === 'snapshot')\n .map((entry) => (entry.message as { snapshot: SemanticSnapshot }).snapshot);\n\n/**\n * Registers the adapter contract suite for one adapter.\n *\n * Call it at the top level of a test file; it declares its own `describe`.\n *\n * `vitest` is imported dynamically, so the package stays importable from a\n * plain script that only wants the fixture paths or the probe. That is why the\n * function is async: `await` it at the top level of the test file, which is\n * where vitest collects the suite from.\n *\n * @example\n * ```ts\n * await runAdapterConformance({\n * name: 'my-framework-probe',\n * spawn: () => ({ command: ['node', 'app.mjs'] }),\n * ready: 'Ready',\n * interaction: { input: '\\t', expect: '[Save]' },\n * quit: { input: 'q', exitCode: 0 },\n * });\n * ```\n */\nexport async function runAdapterConformance(options: AdapterConformanceOptions): Promise<void> {\n const { afterAll, afterEach, beforeEach, describe, expect } = await import('vitest');\n const { it: resourceAwareIt } = await import('@termwright/resource-broker/vitest');\n const it = resourceAwareIt.resources({ terminals: 1, traceWriters: 0 });\n const timeout = options.timeoutMs ?? 10_000;\n const toolchain =\n options.requires === undefined ||\n commandAvailable(options.requires.probe, {\n ...(options.requires.cwd === undefined ? {} : { cwd: options.requires.cwd }),\n ...(options.requires.timeoutMs === undefined\n ? {}\n : { timeoutMs: options.requires.timeoutMs }),\n ...(options.requires.env === undefined ? {} : { env: options.requires.env }),\n });\n const probeOptions = {\n ...(options.columns === undefined ? {} : { columns: options.columns }),\n ...(options.rows === undefined ? {} : { rows: options.rows }),\n };\n\n const title = !ptyAvailable()\n ? `adapter conformance: ${options.name} (skipped: no pseudo-terminal here)`\n : toolchain\n ? `adapter conformance: ${options.name}`\n : `adapter conformance: ${options.name} (skipped: ${options.requires?.label ?? 'toolchain'} unavailable)`;\n\n describe.skipIf(!ptyAvailable() || !toolchain)(title, { timeout: timeout * 4 }, () => {\n describe('the dormant rule', () => {\n it('opens nothing and emits no marker without an endpoint', async () => {\n const probe = await AdapterProbe.start(options.spawn(), {\n ...probeOptions,\n instrument: false,\n });\n try {\n await probe.waitForText(options.ready, timeout);\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n\n const observation = probe.observe();\n expect(observation.connections).toBe(0);\n expect(observation.messages).toHaveLength(0);\n expect(observation.text).not.toContain(MARKER_TEXT_PREFIX);\n } finally {\n await probe.stop();\n }\n });\n\n it.skipIf(options.baseline === undefined)(\n 'produces the same bytes as a build without the adapter',\n async () => {\n // Nothing is written to the child during this comparison. A\n // pseudo-terminal echoes the suite's own keystrokes, and whether an\n // echoed byte lands between two frames depends on when the app took\n // raw mode — so a stream containing our input compares the tty's\n // timing, not the adapter's output. Measured on the Ink fixture: 3\n // mismatches in 30 pairs with input (always a stray 0x09, the tab the\n // suite itself sent), 0 in 40 pairs without.\n const startup = async (\n command: AdapterCommand,\n ): Promise<{ readonly stdout: Uint8Array; readonly screen: string }> => {\n const probe = await AdapterProbe.start(command, { ...probeOptions, instrument: false });\n try {\n await probe.waitForText(options.ready, timeout);\n await settle(probe);\n const observation = probe.observe();\n return { stdout: observation.stdout, screen: observation.screen };\n } finally {\n await probe.stop();\n }\n };\n\n const instrumented = await startup(options.spawn());\n const plain = await startup((options.baseline as () => AdapterCommand)());\n if (process.platform === 'win32') {\n // ConPTY itself emits process-lifecycle control sequences. Their\n // chunking/order can differ between two identical child binaries,\n // so the raw host stream is not an application-byte oracle on\n // Windows. Feed both streams through the same terminal emulator\n // and require the complete visible grids to be identical. The\n // adjacent dormant test separately proves zero connections,\n // messages and Termwright markers.\n expect(instrumented.screen).toBe(plain.screen);\n } else {\n // POSIX PTYs expose the child stream directly: one extra escape\n // sequence from instrumentation is a real dormant-rule failure.\n expect(Buffer.from(instrumented.stdout).toString('binary')).toBe(\n Buffer.from(plain.stdout).toString('binary'),\n );\n }\n },\n );\n });\n\n describe('an instrumented session', () => {\n let probe: AdapterProbe;\n /** Everything observed before a single byte was written to the child. */\n let beforeInput: ProbeObservation;\n\n beforeEach(\n async () => {\n probe = await AdapterProbe.start(options.spawn(), probeOptions);\n await probe.waitForText(options.ready, timeout);\n await probe.waitFor(\n (observation) => snapshotsOf(observation).length > 0,\n timeout,\n 'a first snapshot from the adapter',\n );\n beforeInput = probe.observe();\n },\n // Starting the process, waiting for its first frame, and completing the\n // adapter handshake are separate bounded operations. Vitest otherwise\n // applies its 10-second hook default even though the suite has a larger\n // timeout, which makes real adapters flaky under a parallel root run.\n timeout * 4,\n );\n\n afterEach(async () => {\n await probe?.stop();\n });\n\n it('completes the handshake before anything else', async () => {\n const { messages, connections } = probe.observe();\n const first = messages[0];\n\n expect(connections).toBe(1);\n expect(first?.message.type).toBe('hello');\n const hello = first?.message as {\n protocol: string;\n adapter: { name: string; version: string };\n capabilities: readonly string[];\n };\n expect(hello.protocol).toBe(PROTOCOL_ID);\n expect(hello.adapter.name.length).toBeGreaterThan(0);\n expect(hello.adapter.version.length).toBeGreaterThan(0);\n expect(\n hello.capabilities.every((entry) =>\n (ADAPTER_CAPABILITIES as readonly string[]).includes(entry),\n ),\n ).toBe(true);\n expect(hello.capabilities).toContain('tree');\n // A second hello, or a hello after other traffic, is a protocol fault.\n expect(messages.filter((entry) => entry.message.type === 'hello')).toHaveLength(1);\n\n // Nothing may precede the handshake, log records least of all: their\n // budget is granted *in* the reply to this message.\n const firstLog = messages.findIndex((entry) => entry.message.type === 'log');\n expect(firstLog === -1 || firstLog > 0).toBe(true);\n });\n\n it(\n options.treeBeforeInput === undefined\n ? 'publishes a usable tree before any input'\n : `publishes a usable tree before any input (exempt: ${options.treeBeforeInput.reason})`,\n { skip: options.treeBeforeInput !== undefined },\n () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n\n // A driver launches an app and addresses it. An adapter that only\n // publishes once a key has been pressed is not addressable at that\n // moment, and a suite that sends input before looking would never\n // notice — which is exactly how this class of bug reached a shipped\n // adapter.\n expect(latest, 'no snapshot arrived before any input was sent').toBeDefined();\n expect(latest?.nodes.length ?? 0).toBeGreaterThan(0);\n expect(latest?.rootIds.length ?? 0).toBeGreaterThan(0);\n\n // A tree of one anonymous root is empty in every sense that matters:\n // nothing in it can be located by role and name.\n const addressable = (latest?.nodes ?? []).filter(\n (node) => node.name.length > 0 || node.testId !== undefined,\n );\n expect(\n addressable.length,\n 'the tree has no node that a locator could address',\n ).toBeGreaterThan(0);\n },\n );\n\n it('publishes only valid snapshots, bound to this session', async () => {\n const observation = probe.observe();\n const snapshots = snapshotsOf(observation);\n\n expect(observation.faults).toEqual([]);\n expect(snapshots.length).toBeGreaterThan(0);\n for (const snapshot of snapshots) {\n expect(validateSnapshot(snapshot, DEFAULT_LIMITS)).toMatchObject({ ok: true });\n expect(snapshot.sessionId).toBe(probe.sessionId);\n expect(snapshot.v).toBe(2);\n const ids = new Set(snapshot.nodes.map((node) => node.id));\n for (const node of snapshot.nodes) {\n if (node.parentId === undefined) expect(snapshot.rootIds).toContain(node.id);\n else expect(ids.has(node.parentId)).toBe(true);\n }\n }\n\n const revisions = snapshots.map((snapshot) => snapshot.revision);\n expect([...revisions]).toEqual([...new Set(revisions)].sort((left, right) => left - right));\n });\n\n it.skipIf(options.expectIntendedGeometry !== true)(\n 'publishes intended geometry in viewport cells',\n () => {\n const snapshots = snapshotsOf(probe.observe());\n const latest = snapshots[snapshots.length - 1];\n expect(latest).toBeDefined();\n const bounded =\n latest?.nodes.filter((node) => node.geometry.intendedRect.status === 'known') ?? [];\n expect(bounded.length).toBeGreaterThan(0);\n for (const node of bounded) {\n const bounds =\n node.geometry.intendedRect.status === 'known'\n ? node.geometry.intendedRect.value\n : undefined;\n expect(bounds).toBeDefined();\n if (bounds === undefined) continue;\n expect(bounds.row).toBeGreaterThanOrEqual(0);\n expect(bounds.column).toBeGreaterThanOrEqual(0);\n expect(bounds.row).toBeLessThan(latest?.rows ?? 0);\n expect(bounds.column).toBeLessThan(latest?.columns ?? 0);\n }\n },\n );\n\n it('orders every revision as snapshot, then commit, then marker', async () => {\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n await probe.waitFor(\n (observation) => observation.markers.length >= 2,\n timeout,\n 'a second render marker',\n );\n\n // The socket and the terminal are independent streams, so a marker can\n // be read before the frame describing the same revision has been\n // parsed. The contract is that each revision *eventually* has all\n // three parts; waiting for that is not leniency, it is the difference\n // between asserting the contract and asserting arrival order.\n const complete = (observation: ProbeObservation): boolean =>\n observation.markers.every(\n (marker) =>\n observation.messages.some(\n (entry) =>\n entry.message.type === 'snapshot' &&\n (entry.message as { snapshot: SemanticSnapshot }).snapshot.revision ===\n marker.revision,\n ) &&\n observation.messages.some(\n (entry) =>\n entry.message.type === 'revision-commit' &&\n entry.message.revision === marker.revision,\n ),\n );\n await probe.waitFor(complete, timeout, 'every marker paired with a snapshot and a commit');\n\n const observation = probe.observe();\n const markers = observation.markers;\n expect(markers.length).toBeGreaterThan(0);\n expect(markers.map((marker) => marker.revision)).toEqual(\n [...markers.map((marker) => marker.revision)].sort((left, right) => left - right),\n );\n\n for (const marker of markers) {\n const snapshot = observation.messages.find(\n (entry) =>\n entry.message.type === 'snapshot' &&\n (entry.message as { snapshot: SemanticSnapshot }).snapshot.revision ===\n marker.revision,\n );\n const commit = observation.messages.find(\n (entry) =>\n entry.message.type === 'revision-commit' &&\n entry.message.revision === marker.revision,\n );\n expect(snapshot, `no snapshot for revision ${marker.revision}`).toBeDefined();\n expect(commit, `no commit for revision ${marker.revision}`).toBeDefined();\n\n const snapshotIndex = observation.messages.indexOf(snapshot!);\n expect(snapshotIndex).toBeLessThan(observation.messages.indexOf(commit!));\n }\n\n // Each marker commits a frame, so there must be output between one\n // marker and the next, and the first one cannot open the stream.\n //\n // The socket and the terminal are two independent streams, and the\n // event loop may deliver a later chunk of one before an earlier message\n // of the other. Comparing a marker's byte offset against the stdout\n // position recorded when its frame arrived therefore measures delivery\n // scheduling, not adapter ordering, and is deliberately not asserted.\n let previousEnd = 0;\n for (const marker of markers) {\n expect(marker.offset).toBeGreaterThan(previousEnd);\n previousEnd = marker.offset;\n }\n });\n\n const conventions = options.conventions ?? {};\n const readme =\n conventions.readmePath !== undefined && existsSync(conventions.readmePath)\n ? readFileSync(conventions.readmePath, 'utf8')\n : '';\n const declared = parseDeclaredDeviations(readme);\n const outcomes: ConventionOutcome[] = [];\n\n it('reads the deviations its README declares', () => {\n if (conventions.readmePath === undefined) return;\n if (!readme.includes('## Deviations')) return;\n\n // A section the parser cannot read is worse than a missing one: the\n // three-state logic silently collapses to two, and every documented\n // limitation starts reporting as an error against the adapter that\n // took the trouble to declare it.\n //\n // Detectable without understanding any shape: a section that *has\n // structure* — bullets, table rows, bold lead-ins — but yields no\n // entries is a parser gap. A section that is plain prose saying there\n // is nothing to declare is not, and must not be failed for it.\n const start = readme.indexOf('## Deviations');\n const rest = readme.slice(start + '## Deviations'.length);\n const end = rest.indexOf('\\n## ');\n const section = end < 0 ? rest : rest.slice(0, end);\n const structured = /^\\s*[-*|]/mu.test(section) || section.includes('**');\n if (!structured) return;\n\n expect(\n declared.size,\n `${options.name} has a \"## Deviations\" section with entries this suite could not ` +\n 'read; its declarations would be invisible and its documented limitations would ' +\n 'report as errors. Teach `parseDeclaredDeviations` the shape it uses.',\n ).toBeGreaterThan(0);\n });\n\n /**\n * Runs one rule and decides what its result means.\n *\n * Three states, not two. A rule an adapter cannot follow and *says* it\n * cannot follow is a documented limitation, not a failure — failing it\n * would give the first author who honestly describes their framework a\n * red run for doing exactly what rule 6 asks, which is the shortest path\n * to people hiding deviations instead of declaring them.\n *\n * A check that passes while a declaration exists is deliberately *not*\n * called stale. One rule has more aspects than a subprocess can observe:\n * Ink declares a rule 3 limitation about native identifiers while\n * satisfying the annotation half of the same rule, and both are true.\n * The summary records the coincidence so a reader can re-read the\n * README; calling it a defect would be a false signal, and a suite that\n * cries wolf gets ignored.\n */\n const convention = (rule: string, what: string, check: () => string | null): void => {\n const failure = check();\n const titles = declared.get(rule) ?? [];\n if (failure === null) {\n outcomes.push(\n titles.length === 0\n ? { rule, what, status: 'compliant' }\n : { rule, what, status: 'checked-despite-declaration', detail: titles.join('; ') },\n );\n return;\n }\n if (titles.length > 0) {\n outcomes.push({\n rule,\n what,\n status: 'documented',\n detail: `${titles.join('; ')} — ${failure}`,\n });\n return;\n }\n outcomes.push({ rule, what, status: 'violation', detail: failure });\n expect.fail(`convention ${rule} (${what}): ${failure}`);\n };\n\n afterAll(() => {\n writeConventionSummary(options.name, declared, outcomes);\n });\n\n it('convention 3: an author-annotated test id reaches the wire', () => {\n if (conventions.annotatedTestId === undefined) return;\n const wanted = conventions.annotatedTestId;\n convention('3', 'an annotated test id reaches the wire', () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n const node = latest?.nodes.find((entry) => entry.testId === wanted);\n return node === undefined\n ? `no node carries the test id ${JSON.stringify(wanted)}`\n : null;\n });\n });\n\n it('convention 5: an empty textbox publishes an empty value', () => {\n if (conventions.emptyTextboxTestId === undefined) return;\n const wanted = conventions.emptyTextboxTestId;\n convention('5', 'an empty textbox publishes an empty value', () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n const node = latest?.nodes.find((entry) => entry.testId === wanted);\n if (node === undefined) return `no node carries the test id ${JSON.stringify(wanted)}`;\n // `''` means the field is empty; absent means \"not a value-bearing\n // widget\". A wire format that drops empty strings turns the first\n // into the second and makes `toHaveValue('')` unassertable.\n return node.value?.status === 'known' && node.value.value === ''\n ? null\n : `the value is ${JSON.stringify(node.value)}, not an empty known value`;\n });\n });\n\n it('convention 5: value is derived only for value-bearing roles', () => {\n convention('5', 'value is derived only for value-bearing roles', () => {\n const annotated = new Set(conventions.annotatedValues ?? []);\n const nodes = snapshotsOf(beforeInput).at(-1)?.nodes ?? [];\n const offenders = nodes.filter(\n (node) =>\n node.value !== undefined &&\n node.role !== 'textbox' &&\n node.role !== 'progressbar' &&\n !(node.testId !== undefined && annotated.has(node.testId)),\n );\n if (offenders.length > 0) {\n return `derived a value outside {textbox, progressbar}: ${offenders\n .map((node) => `${node.role} ${JSON.stringify(node.name)}`)\n .join(', ')}`;\n }\n // A boolean is a state, not contents: publishing `value: \"true\"`\n // makes a checkbox look like a textbox containing that word.\n const booleans = nodes.filter(\n (node) =>\n node.value?.status === 'known' &&\n (node.value.value === 'true' || node.value.value === 'false'),\n );\n return booleans.length === 0\n ? null\n : `published a boolean as a value on ${booleans.map((node) => node.role).join(', ')}`;\n });\n });\n\n it('convention 2: no container is named from the text it contains', () => {\n convention('2', 'no container is named from the text it contains', () => {\n const nodes = snapshotsOf(beforeInput).at(-1)?.nodes ?? [];\n const children = new Map<string, string[]>();\n for (const node of nodes) {\n if (node.parentId === undefined) continue;\n children.set(node.parentId, [...(children.get(node.parentId) ?? []), node.id]);\n }\n const descendantText = (id: string): string[] => {\n const out: string[] = [];\n const pending = [...(children.get(id) ?? [])];\n while (pending.length > 0) {\n const next = nodes.find((node) => node.id === pending.pop());\n if (next === undefined) continue;\n if (next.name.length > 0) out.push(next.name);\n pending.push(...(children.get(next.id) ?? []));\n }\n return out;\n };\n\n // Naming containers from content is what makes\n // getByRole('region', {name: 'Approve'}) match the dialog *around*\n // the button, so every ancestor of a label becomes a plausible match\n // for it. Both failure shapes are visible from the tree alone.\n const offenders = nodes\n .filter((node) => CONTAINER_ROLES.has(node.role) && node.name.length > 0)\n .filter((node) => {\n const texts = descendantText(node.id);\n const joined = texts.join(' ').replace(/\\s+/gu, ' ').trim();\n return texts.includes(node.name) || (joined.length > 0 && joined === node.name);\n });\n return offenders.length === 0\n ? null\n : `named from content: ${offenders.map((node) => `${node.role} ${JSON.stringify(node.name)}`).join(', ')}`;\n });\n });\n\n it('convention 2: a container with no label of its own has an empty name', () => {\n if (conventions.unnamedContainerTestId === undefined) return;\n const wanted = conventions.unnamedContainerTestId;\n convention('2', 'an unlabelled container has an empty name', () => {\n const node = snapshotsOf(beforeInput)\n .at(-1)\n ?.nodes.find((entry) => entry.testId === wanted);\n if (node === undefined) return `no node carries the test id ${JSON.stringify(wanted)}`;\n return node.name === '' ? null : `the container is named ${JSON.stringify(node.name)}`;\n });\n });\n\n it.skipIf(conventions.readmePath === undefined)(\n 'declares its deviations in its README (advisory)',\n () => {\n // Rules 1, 2 and 4 cannot be judged from outside a subprocess, so the\n // README is the only evidence that a difference was considered rather\n // than overlooked. Advisory on purpose: a missing heading is a\n // documentation gap, not a broken adapter, and failing here would\n // make a conformance run red for something no user can observe.\n const path = conventions.readmePath as string;\n const text = existsSync(path) ? readFileSync(path, 'utf8') : '';\n if (!text.includes('## Deviations')) {\n // Rule 6 follows the adapter, not the package: something that\n // publishes no tree has nothing to declare. Anything reaching this\n // suite does publish one, so the heading is expected here.\n process.stderr.write(\n `conformance: ${options.name} has no \"## Deviations\" section in ${path}; ` +\n 'rules 1, 2 and 4 are unverifiable from outside, so an undeclared difference is invisible\\n',\n );\n }\n expect(true).toBe(true);\n },\n );\n\n it('sends log records only if it negotiated the channel', async () => {\n const hello = beforeInput.messages[0]?.message as { capabilities: readonly string[] };\n if (hello.capabilities.includes('logs')) return;\n\n // An adapter is free not to support logs. What it is not free to do is\n // send records anyway: the budget granted in the handshake is what\n // bounds them, and one that was never granted cannot bound anything.\n // The driver closes the channel over this, so an adapter that does it\n // is broken in production rather than merely untidy.\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n expect(\n probe.observe().logs,\n 'the adapter sent log records without announcing the logs capability',\n ).toEqual([]);\n });\n\n it.skipIf(options.logs === undefined)(\n 'carries a log record without printing it',\n async () => {\n const logs = options.logs as NonNullable<AdapterConformanceOptions['logs']>;\n const hello = probe.observe().messages[0]?.message as { capabilities: readonly string[] };\n expect(\n hello.capabilities.includes('logs'),\n 'the registration declares logs, but the adapter never announced the capability',\n ).toBe(true);\n\n const before = probe.observe().logs.length;\n if (logs.input !== undefined) await probe.write(logs.input);\n // An app that logs on its own may already have; one that logs on demand\n // has just been asked to. Either way the wait is for a record.\n await probe.waitFor(\n (observation) => observation.logs.length > (logs.input === undefined ? 0 : before),\n timeout,\n 'a log record over the negotiated channel',\n );\n\n const observation = probe.observe();\n const record = observation.logs.find((entry) => entry.message.includes(logs.expect));\n expect(record, `no log record matched ${JSON.stringify(logs.expect)}`).toBeDefined();\n // `seq` is a non-negative counter, so the first record of a session is\n // legitimately 0; what matters is the relation between records.\n expect(record?.seq).toBeGreaterThanOrEqual(0);\n\n // Strictly increasing within a session: a consumer counting errors must\n // not be able to count one twice, and a gap must mean a real loss.\n const seqs = observation.logs.map((entry) => entry.seq);\n expect(seqs).toEqual([...seqs].sort((left, right) => left - right));\n expect(new Set(seqs).size).toBe(seqs.length);\n\n // The whole point of the capability: the record reaches the driver and\n // never the terminal. A TUI that printed it would corrupt its render.\n expect(observation.screen).not.toContain(logs.expect);\n expect(observation.text).not.toContain(logs.expect);\n },\n );\n\n it('keeps the application alive when the channel is cut', async () => {\n const before = probe.observe();\n probe.cutChannel();\n\n await probe.write(options.interaction.input);\n // The observed text is cumulative, so `waitForText` would match output\n // the application produced before the cut. Growth is what proves it is\n // still rendering.\n await probe.waitFor(\n (observation) => observation.text.length > before.text.length,\n timeout,\n 'any further output from the child',\n );\n const after = probe.observe();\n\n // The application keeps rendering; the adapter goes quiet and does not\n // reconnect behind the driver's back.\n expect(after.text.length).toBeGreaterThan(before.text.length);\n expect(after.connections).toBe(1);\n expect(probe.exitStatus).toBeNull();\n\n await probe.write(options.quit.input);\n const status = await probe.waitForExit(timeout);\n if (options.quit.exitCode !== undefined) expect(status.code).toBe(options.quit.exitCode);\n });\n });\n });\n}\n","/**\n * A minimal driver, built for one job: watching what an adapter actually puts\n * on the wire.\n *\n * `@termwright/driver` deliberately hides frame ordering behind a settled tree,\n * which is the right API for tests of applications and the wrong one for tests\n * of adapters. The probe therefore speaks the protocol itself — endpoint,\n * handshake, framing, marker verification — and records every message together\n * with how many stdout bytes had been written when it arrived. That is what\n * makes the §4.3 ordering contract (snapshot → commit → marker-after-frame)\n * observable at all.\n *\n * It does emulate a terminal, because it has to: an adapter is free to draw by\n * positioning each run of cells (tview does), so the text a user sees exists\n * only on a rendered grid and never as contiguous bytes on the wire. Waiting\n * for text therefore reads the grid, while marker offsets read the byte stream.\n *\n * It is still not a second driver: it never locates and never acts.\n */\nimport { type Server, type Socket } from 'node:net';\nimport { readFileSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomBytes, randomUUID } from 'node:crypto';\nimport {\n DEFAULT_LIMITS,\n ENV_ENDPOINT,\n ENV_TOKEN,\n MARKER_OSC_CODE,\n MARKER_OSC_PREFIX,\n parseAdapterMessage,\n PROTOCOL_ID,\n createFrameDecoder,\n encodeFrame,\n generateToken,\n verifyMarkerPayload,\n type AdapterToDriverMessage,\n type HelloAckMessage,\n type LogRecord,\n} from '@termwright/protocol';\nimport type { Terminal } from '@xterm/headless';\nimport { createNativePtyBackend, VtScreen, type PtyProcess } from '@termwright/driver/experimental';\nimport { environment } from './pty.js';\nimport { ProbePeerOwner } from './probe-peer-owner.js';\nimport { ProbeProcessShutdown } from './probe-process-shutdown.js';\nimport { ProbeStartupTransaction } from './probe-startup.js';\n\n/** How a fixture is started. Everything else about it is opaque to the probe. */\nexport interface AdapterCommand {\n readonly command: readonly string[];\n readonly env?: Readonly<Record<string, string>>;\n readonly cwd?: string;\n}\n\n/** One message the adapter sent, stamped with the stdout position at arrival. */\nexport interface RecordedMessage {\n readonly message: AdapterToDriverMessage;\n /** Bytes of stdout the probe had received when this frame was parsed. */\n readonly stdoutBytes: number;\n readonly atMs: number;\n}\n\n/** One verified render marker found in stdout. */\nexport interface RecordedMarker {\n readonly revision: number;\n /** Offset of the marker's first byte in the stdout stream. */\n readonly offset: number;\n readonly atMs: number;\n}\n\n/** Log budget the probe grants to an adapter that announces `logs`. */\nconst LOG_BUDGET = Object.freeze({ enabled: true, maxRecordsPerSecond: 200, burst: 400 });\n\n/** A frame the probe refused; a conforming adapter produces none. */\nexport interface RecordedFault {\n readonly code: string;\n readonly detail: string;\n}\n\nexport interface ProbeOptions {\n readonly columns?: number;\n readonly rows?: number;\n /** Set `false` to withhold the instrumentation env — the dormant-run case. */\n readonly instrument?: boolean;\n}\n\n/** Everything the probe observed, readable while the child is still running. */\nexport interface ProbeObservation {\n readonly messages: readonly RecordedMessage[];\n readonly markers: readonly RecordedMarker[];\n readonly faults: readonly RecordedFault[];\n readonly connections: number;\n readonly stdout: Uint8Array;\n /** Raw bytes decoded as UTF-8: what was written, in order, escapes included. */\n readonly text: string;\n /** The visible grid, one row per line — what a user would actually see. */\n readonly screen: string;\n /** Application log records the adapter sent, in arrival order. */\n readonly logs: readonly LogRecord[];\n}\n\n/**\n * A marker on the wire: `OSC 8487 ; twm;<rev>;<mac>` closed by BEL or ST.\n *\n * Both terminators are matched because both are legal — an implementation\n * emits BEL, but a receiver that only understood BEL would reject a\n * conforming adapter, and this probe stands in for a receiver.\n */\nconst MARKER_PATTERN = new RegExp(\n `\\\\x1b\\\\]${MARKER_OSC_CODE};(${MARKER_OSC_PREFIX}[0-9]+;[A-Za-z0-9_-]+)(?:\\\\x07|\\\\x1b\\\\\\\\)`,\n 'gu',\n);\n\n/**\n * Runs one fixture under a pseudo-terminal with a protocol endpoint attached.\n *\n * @example\n * ```ts\n * const probe = await AdapterProbe.start({ command: ['node', 'app.mjs'] }, {});\n * await probe.waitForText('Ready');\n * await probe.write('\\t');\n * const { messages, markers } = probe.observe();\n * await probe.stop();\n * ```\n */\nexport class AdapterProbe {\n readonly sessionId: string;\n readonly token: string;\n\n readonly #server: Server | null;\n readonly #directory: string | null;\n readonly #pty: PtyProcess;\n readonly #vt: VtScreen;\n readonly #terminal: Terminal;\n #detachTerminalResponse: (() => void) | null;\n readonly #startedAt = performance.now();\n readonly #messages: RecordedMessage[] = [];\n readonly #markers: RecordedMarker[] = [];\n readonly #faults: RecordedFault[] = [];\n readonly #logs: LogRecord[] = [];\n #chunks: Uint8Array[] = [];\n #bytes = 0;\n #text = '';\n #markerScanFrom = 0;\n #connections = 0;\n #socket: Socket | null = null;\n readonly #peers: ProbePeerOwner;\n #exit: { code: number | null; signal: string | null } | null = null;\n /** Where the adapter writes its own account of attaching, if it writes one. */\n #debugFile: string | null = null;\n readonly #shutdown: ProbeProcessShutdown;\n readonly #changeWaiters = new Set<() => void>();\n\n private constructor(\n identity: { readonly sessionId: string; readonly token: string },\n server: Server | null,\n directory: string | null,\n pty: PtyProcess,\n size: { readonly columns: number; readonly rows: number },\n peers: ProbePeerOwner,\n ) {\n this.sessionId = identity.sessionId;\n this.token = identity.token;\n this.#server = server;\n this.#directory = directory;\n this.#pty = pty;\n this.#peers = peers;\n this.#vt = new VtScreen({\n columns: size.columns,\n rows: size.rows,\n scrollbackLines: 1_000,\n });\n this.#terminal = this.#vt.terminal;\n // This probe is the terminal emulator, so it owns terminal-generated\n // replies just like TerminalSession does. Without this bridge the pinned\n // Windows host can block a framework's GCSBI on its private cursor query,\n // leaving the first frame visible while every later draw waits forever.\n this.#detachTerminalResponse = this.#vt.onResponse((response) =>\n this.#writeTerminalResponse(response.data),\n );\n this.#shutdown = new ProbeProcessShutdown({\n pty,\n closeAdmission: () =>\n this.#server === null ? Promise.resolve() : this.#peers.close(this.#server),\n closeTerminalResponseAdmission: () => this.#closeTerminalResponseAdmission(),\n drainParser: () => this.#vt.drain(),\n disposeParser: () => {\n this.#notifyChange();\n this.#vt.dispose();\n },\n removeArtifacts: () => this.#removeArtifacts(),\n });\n }\n\n /** Creates the endpoint (unless dormant), then spawns the fixture. */\n static async start(command: AdapterCommand, options: ProbeOptions = {}): Promise<AdapterProbe> {\n const instrument = options.instrument ?? true;\n const sessionId = randomUUID();\n const token = generateToken();\n const startup = new ProbeStartupTransaction();\n try {\n await startup.acquireEndpoint(instrument);\n const { server, directory, endpoint, peers } = startup;\n const env = environment(command.env);\n // A dormant run must not merely lack our endpoint — it must not inherit one\n // from whatever process is running the suite.\n delete env[ENV_ENDPOINT];\n delete env[ENV_TOKEN];\n if (endpoint !== null) {\n env[ENV_ENDPOINT] = endpoint;\n env[ENV_TOKEN] = token;\n }\n\n // The adapter's own account of why it did or did not attach. The probe can\n // only see the outside — no connection arrived — which leaves \"wrong\n // transport\", \"driver not listening\" and \"never started\" indistinguishable.\n // The clients write that distinction here (1bbe0f9), and a failure quotes\n // the file, so the attribution lands in the message rather than in an\n // artifact somebody has to go and find. A path, never `TERMWRIGHT_DEBUG=1`:\n // that means \"log to stderr\", which under a pty lands in the middle of the\n // frame this suite makes assertions about.\n startup.debugFile = join(\n tmpdir(),\n `termwright-adapter-debug-${randomBytes(8).toString('hex')}.log`,\n );\n env['TERMWRIGHT_DEBUG_FILE'] = startup.debugFile;\n\n const size = { columns: options.columns ?? 80, rows: options.rows ?? 24 };\n startup.pty = createNativePtyBackend().spawn({\n command: command.command,\n ...(command.cwd === undefined ? {} : { cwd: command.cwd }),\n env,\n columns: size.columns,\n rows: size.rows,\n // This probe is itself the terminal emulator. Do not inherit a harness\n // shell's `TERM=dumb`: the child is connected to our xterm-compatible\n // parser regardless of which terminal launched Vitest.\n term: 'xterm-256color',\n });\n const pty = startup.pty;\n\n const probe = new AdapterProbe({ sessionId, token }, server, directory, pty, size, peers);\n probe.#debugFile = startup.debugFile;\n\n pty.onData((data) => probe.#onData(data));\n pty.onExit((status) => {\n probe.#exit = status;\n probe.#shutdown.observeExit(status);\n probe.#notifyChange();\n });\n peers.activate((socket) => probe.#onConnection(socket as Socket));\n return probe;\n } catch (error) {\n return startup.rollback(error);\n }\n }\n\n /** Everything observed so far. Safe to call at any point. */\n observe(): ProbeObservation {\n return {\n messages: [...this.#messages],\n markers: [...this.#markers],\n faults: [...this.#faults],\n connections: this.#connections,\n stdout: this.#stdout(),\n text: this.#text,\n screen: this.screenText(),\n logs: this.#logs.map((entry) => entry),\n };\n }\n\n /** The visible grid as text, trailing whitespace trimmed per row. */\n screenText(): string {\n const buffer = this.#terminal.buffer.active;\n const rows: string[] = [];\n for (let row = 0; row < this.#terminal.rows; row += 1) {\n rows.push(buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? '');\n }\n return rows.join('\\n');\n }\n\n /** The child's exit status, or `null` while it is still running. */\n get exitStatus(): { code: number | null; signal: string | null } | null {\n return this.#exit;\n }\n\n /** Writes raw bytes to the child, exactly as a terminal would. */\n async write(input: string): Promise<void> {\n this.#pty.write(new TextEncoder().encode(input));\n await Promise.resolve();\n }\n\n /**\n * Resolves once `needle` appears on the rendered grid.\n *\n * Matching the byte stream instead would only work for adapters that happen\n * to write their text contiguously: a framework that positions each run of\n * cells never emits `focus: reject` as those twelve bytes in a row.\n */\n async waitForText(needle: string | RegExp, timeoutMs = 10_000): Promise<void> {\n const deadline = performance.now() + timeoutMs;\n for (;;) {\n const change = this.#armChange(deadline);\n const screen = this.screenText();\n if (needle instanceof RegExp ? needle.test(screen) : screen.includes(needle)) {\n change.cancel();\n return;\n }\n if (performance.now() >= deadline) {\n change.cancel();\n throw new Error(\n `adapter conformance: ${String(needle)} never appeared on the fixture's screen\\n` +\n `screen was:\\n${screen}`,\n );\n }\n await change.wait();\n }\n }\n\n /**\n * Resolves once `predicate` holds over the current observation.\n *\n * `what` names the thing being waited for, and the failure carries what the\n * probe could see when it gave up. \"Condition never became true\" is not a\n * result anybody can act on: an adapter that never connected, one that\n * connected and published nothing, and one whose binary died all produce it,\n * and only the observation tells them apart.\n */\n async waitFor(\n predicate: (observation: ProbeObservation) => boolean,\n timeoutMs = 10_000,\n what = 'the condition',\n ): Promise<void> {\n const deadline = performance.now() + timeoutMs;\n for (;;) {\n const change = this.#armChange(deadline);\n if (predicate(this.observe())) {\n change.cancel();\n return;\n }\n if (performance.now() >= deadline) {\n change.cancel();\n throw new Error(`adapter conformance: ${what} never happened — ${this.describe()}`);\n }\n await change.wait();\n }\n }\n\n /** What the probe has seen so far, for a failure that has to explain itself. */\n describe(): string {\n const { messages, connections } = this.observe();\n const kinds = new Map<string, number>();\n for (const recorded of messages) {\n const kind = recorded.message.type;\n kinds.set(kind, (kinds.get(kind) ?? 0) + 1);\n }\n const traffic =\n kinds.size === 0 ? 'no messages' : [...kinds].map(([kind, n]) => `${kind}×${n}`).join(', ');\n const screen = this.screenText()\n .trimEnd()\n .split('\\n')\n .filter((line) => line.trim() !== '');\n const exit = this.#exit === null ? 'still running' : `exited ${JSON.stringify(this.#exit)}`;\n return (\n `${connections} connection(s) to the endpoint, ${traffic}; the child is ${exit}; ` +\n `last screen line: ${JSON.stringify(screen.at(-1) ?? '')}\\n${this.#adapterAccount()}`\n );\n }\n\n /**\n * What the adapter says about its own attach, if the client writes it.\n *\n * The outside view cannot tell \"dialled the wrong transport\" from \"the driver\n * was not listening\" from \"the process never started\" — all three look like\n * no connection. The clients write that distinction to `TERMWRIGHT_DEBUG_FILE`,\n * so it is quoted here rather than left in a file nobody opens. An adapter\n * that writes nothing is itself an answer, and says so.\n */\n #adapterAccount(): string {\n if (this.#debugFile === null) return 'adapter debug log: not requested';\n let contents: string;\n try {\n contents = readFileSync(this.#debugFile, 'utf8');\n } catch {\n return `adapter debug log: nothing written to ${this.#debugFile} — the client either predates the log or never ran`;\n }\n const lines = contents.trimEnd().split('\\n').slice(-12);\n return `adapter debug log (last ${lines.length} line(s)):\\n ${lines.join('\\n ')}`;\n }\n\n /** Waits for the child to exit and returns its status. */\n async waitForExit(timeoutMs = 10_000): Promise<{ code: number | null; signal: string | null }> {\n const deadline = performance.now() + timeoutMs;\n while (this.#exit === null) {\n const change = this.#armChange(deadline);\n if (this.#exit !== null) {\n change.cancel();\n break;\n }\n if (performance.now() >= deadline) {\n change.cancel();\n throw new Error('adapter conformance: the fixture never exited');\n }\n await change.wait();\n }\n return this.#exit;\n }\n\n /** Cuts the semantic channel without touching the child: the disconnect case. */\n cutChannel(): void {\n this.#socket?.destroy();\n this.#socket = null;\n }\n\n /** Stops the child and releases the endpoint. Idempotent. */\n stop(): Promise<void> {\n return this.#shutdown.stop();\n }\n\n async #removeArtifacts(): Promise<void> {\n const failures: unknown[] = [];\n if (this.#directory !== null) {\n try {\n await rm(this.#directory, { recursive: true, force: true });\n } catch (error) {\n failures.push(error);\n }\n }\n if (this.#debugFile !== null) {\n try {\n await rm(this.#debugFile, { force: true });\n } catch (error) {\n failures.push(error);\n }\n }\n this.#notifyChange();\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1)\n throw new AggregateError(failures, 'adapter probe artifact cleanup failed');\n }\n\n // -------------------------------------------------------------------------\n\n #stdout(): Uint8Array {\n const out = new Uint8Array(this.#bytes);\n let offset = 0;\n for (const chunk of this.#chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n this.#chunks = [out];\n return out;\n }\n\n #onData(data: Uint8Array): void {\n this.#chunks.push(data);\n this.#bytes += data.length;\n this.#text += Buffer.from(data).toString('utf8');\n void this.#vt.write(data).finally(() => this.#notifyChange());\n this.#scanMarkers();\n this.#notifyChange();\n }\n\n /** Returns emulator-owned replies without presenting them as user input. */\n #writeTerminalResponse(response: string): void {\n const data = Buffer.from(response, 'utf8');\n const write = this.#pty.writeTerminalResponse;\n if (write === undefined) {\n this.#pty.write(data, 'raw');\n return;\n }\n write.call(this.#pty, data);\n }\n\n #closeTerminalResponseAdmission(): void {\n this.#detachTerminalResponse?.();\n this.#detachTerminalResponse = null;\n }\n\n /** Finds render markers in the byte stream and verifies each against the token. */\n #scanMarkers(): void {\n MARKER_PATTERN.lastIndex = this.#markerScanFrom;\n for (;;) {\n const match = MARKER_PATTERN.exec(this.#text);\n if (match === null) break;\n const payload = match[1] ?? '';\n const verified = verifyMarkerPayload(payload, this.token, this.sessionId);\n if (verified === null) {\n this.#faults.push({\n code: 'marker',\n detail: `marker did not verify: ${JSON.stringify(payload)}`,\n });\n } else {\n this.#markers.push({ revision: verified.revision, offset: match.index, atMs: this.#now() });\n }\n this.#markerScanFrom = match.index + match[0].length;\n }\n MARKER_PATTERN.lastIndex = 0;\n }\n\n #onConnection(socket: Socket): void {\n this.#connections += 1;\n this.#notifyChange();\n if (this.#socket !== null) {\n // One adapter per session; a second connection is a conformance failure.\n this.#faults.push({\n code: 'second-connection',\n detail: 'the adapter opened a second channel',\n });\n socket.destroy();\n return;\n }\n this.#socket = socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n socket.on('data', (chunk: Buffer) => {\n let frames: readonly unknown[];\n try {\n frames = decoder.push(chunk);\n } catch (error) {\n this.#faults.push({\n code: 'framing',\n detail: error instanceof Error ? error.message : String(error),\n });\n this.#notifyChange();\n socket.destroy();\n return;\n }\n for (const frame of frames) this.#onFrame(socket, frame);\n });\n socket.on('close', () => {\n if (this.#socket === socket) this.#socket = null;\n this.#notifyChange();\n });\n }\n\n #onFrame(socket: Socket, frame: unknown): void {\n // The probe parses with the real protocol parser: a hand-written check\n // would only prove that the fixture agrees with itself.\n const parsed = parseAdapterMessage(frame, DEFAULT_LIMITS);\n if (!parsed.ok) {\n this.#faults.push({ code: parsed.code, detail: parsed.detail });\n this.#notifyChange();\n return;\n }\n this.#messages.push({ message: parsed.message, stdoutBytes: this.#bytes, atMs: this.#now() });\n if (parsed.message.type === 'log') this.#logs.push(parsed.message.record);\n this.#notifyChange();\n if (parsed.message.type !== 'hello') return;\n\n const ack: HelloAckMessage = {\n type: 'hello-ack',\n protocol: PROTOCOL_ID,\n sessionId: this.sessionId,\n limits: DEFAULT_LIMITS,\n subscribe: 'snapshots',\n marker: { enabled: parsed.message.capabilities.includes('render-revisions') },\n // Granted only to an adapter that asked: an adapter that never announced\n // `logs` must not be handed a budget it can then claim it was given.\n ...(parsed.message.capabilities.includes('logs') ? { logs: LOG_BUDGET } : {}),\n };\n socket.write(encodeFrame(ack, DEFAULT_LIMITS.maxFrameBytes));\n }\n\n #now(): number {\n return performance.now() - this.#startedAt;\n }\n\n #notifyChange(): void {\n for (const resolve of [...this.#changeWaiters]) resolve();\n }\n\n #armChange(deadline: number): { wait(): Promise<void>; cancel(): void } {\n let settled = false;\n let resolvePromise!: () => void;\n const finish = () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n this.#changeWaiters.delete(finish);\n resolvePromise();\n };\n const promise = new Promise<void>((resolve) => {\n resolvePromise = resolve;\n });\n const timer = setTimeout(finish, Math.max(0, deadline - performance.now()));\n timer.unref?.();\n this.#changeWaiters.add(finish);\n return { wait: () => promise, cancel: finish };\n }\n}\n\n/** The marker prefix, re-exported so suites can assert on dormant output. */\nexport const MARKER_TEXT_PREFIX = `\\x1b]${MARKER_OSC_CODE};${MARKER_OSC_PREFIX}`;\n","/**\n * Shared harness plumbing for the conformance suites: fixture resolution,\n * pseudo-terminal availability and session bookkeeping.\n *\n * Every suite in this package drives real child processes. Where no PTY can be\n * opened — a sandboxed CI runner, a missing prebuild — the suites skip rather\n * than fail, because \"this machine cannot open a terminal\" is not a conformance\n * result. Set `TERMWRIGHT_SKIP_PTY=1` to skip them explicitly.\n */\nimport { spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { launchTerminal, type LaunchOptions, type TerminalHarness } from '@termwright/driver';\nimport { nativePtyAvailable } from '@termwright/driver/experimental';\nimport { withProbe } from '@termwright/probe-ink';\n\n/**\n * Absolute path of a fixture that ships with this package.\n *\n * The fixtures are shipped as sources rather than bundled, because they are\n * meant to be launched as `node <path>` by suites in other packages and in\n * other languages' CI. They are therefore located from the package root, which\n * is the one anchor that is the same whether this module was loaded from `src/`\n * during development or from the bundled `dist/`.\n */\nexport function fixturePath(name: string): string {\n return join(packageRoot(), 'src', 'fixtures', name);\n}\n\n/**\n * Absolute path inside the repository, for suites that reach outside this\n * package — the language clients live in `clients/`, not in `packages/`.\n */\nexport function repositoryPath(...segments: readonly string[]): string {\n return join(packageRoot(), '..', '..', ...segments);\n}\n\nlet cachedRoot: string | null = null;\n\nfunction packageRoot(): string {\n if (cachedRoot !== null) return cachedRoot;\n let directory = dirname(fileURLToPath(import.meta.url));\n for (;;) {\n if (existsSync(join(directory, 'package.json'))) {\n cachedRoot = directory;\n return directory;\n }\n const parent = dirname(directory);\n if (parent === directory) {\n throw new Error(\n '@termwright/conformance: could not locate the package root from this module',\n );\n }\n directory = parent;\n }\n}\n\n/** The fixtures published for adapter authors and other packages' suites. */\nexport const CONFORMANCE_FIXTURES = Object.freeze({\n /** Uninstrumented app: proves the generic fallback (§20.1). */\n generic: () => fixturePath('generic-app.mjs'),\n /** Shell-shaped app emitting OSC 133 marks; `--marks=off` suppresses them. */\n prompt: () => fixturePath('prompt-app.mjs'),\n /** Normal-render Ink app used to exercise launch-time probe attachment. */\n inkProbe: () => fixturePath('ink-probe-app.mjs'),\n /** Hostile wire peer; takes a scenario name as its first argument (§20.3). */\n adversarialPeer: () => fixturePath('adversarial-peer.mjs'),\n});\n\nlet cachedPty: boolean | null = null;\n\n/**\n * Whether this machine can open a pseudo-terminal at all.\n *\n * @returns `false` when `TERMWRIGHT_SKIP_PTY=1` or the native binding cannot\n * be loaded and validated. Real child creation remains inside a test attempt.\n */\nexport function ptyAvailable(): boolean {\n if (cachedPty !== null) return cachedPty;\n cachedPty = nativePtyAvailable();\n return cachedPty;\n}\n\n/** `process.env` with the `undefined` values dropped, as PTY spawning requires. */\nexport function environment(extra?: Readonly<Record<string, string>>): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (value !== undefined) env[key] = value;\n }\n return { ...env, ...extra };\n}\n\n/**\n * Absolute path of a Python interpreter that can import the given modules.\n *\n * Two problems at once. The executable is `python3` on POSIX and often only\n * `python` on Windows, and `node-pty` resolves neither reliably — the first\n * Windows run failed with `File not found:` while a `spawnSync` probe of the\n * same name had just succeeded. Asking the interpreter for `sys.executable`\n * turns whichever name works into an absolute path a pty can spawn.\n *\n * @returns the interpreter path, or `null` when no candidate can import them.\n */\nexport function pythonWith(\n modules: readonly string[],\n extraEnv?: Readonly<Record<string, string>>,\n): string | null {\n const script = `import ${modules.join(', ')}, sys; print(sys.executable)`;\n for (const candidate of ['python3', 'python']) {\n if (\n !commandAvailable([candidate, '-c', `import ${modules.join(', ')}`], {\n quiet: true,\n ...(extraEnv === undefined ? {} : { env: extraEnv }),\n })\n )\n continue;\n const resolved = spawnSync(candidate, ['-c', script], {\n encoding: 'utf8',\n env: environment(extraEnv),\n });\n const path = (resolved.stdout ?? '').trim();\n if (resolved.status === 0 && path.length > 0) return path;\n }\n return null;\n}\n\n/** Options every conformance session shares; suites override what they need. */\nexport interface FixtureLaunchOptions extends Partial<Omit<LaunchOptions, 'command'>> {\n /** Extra arguments appended to `node <fixture>`. */\n readonly args?: readonly string[];\n /** Attach the zero-config framework probe to the otherwise normal command. */\n readonly probe?: 'ink';\n /**\n * Text that proves the fixture started drawing. Waiting for it here rather\n * than in each suite is what lets the failure be diagnosed: a fixture that\n * printed nothing at all failed to start, which is a very different problem\n * from one that started and drew the wrong thing.\n */\n readonly ready?: string | RegExp;\n}\n\n/**\n * A set of sessions closed together, so one wedged fixture cannot leak a child\n * process into the next test.\n *\n * @example\n * ```ts\n * const sessions = createSessionPool();\n * afterEach(sessions.closeAll);\n * const terminal = await sessions.launch(CONFORMANCE_FIXTURES.generic());\n * ```\n */\nexport interface SessionPool {\n launch(fixture: string, options?: FixtureLaunchOptions): Promise<TerminalHarness>;\n closeAll(): Promise<void>;\n}\n\nexport function createSessionPool(): SessionPool {\n const open: TerminalHarness[] = [];\n return {\n async launch(fixture, options = {}) {\n const { args = [], probe, ready: _ready, ...launchOptions } = options;\n const base = [process.execPath, fixture, ...args];\n const terminal = await launchTerminal({\n command: probe === 'ink' ? withProbe('node', base).command : base,\n columns: 80,\n rows: 24,\n // No `env` and no `envMode`: the suites run against the secret-safe\n // 'replace' default, which is what a user gets. Forwarding the runner's\n // whole environment here would quietly make every suite an 'inherit'\n // test and leave the default uncovered.\n // Conformance runs start a fresh Node process, a pseudo-terminal and a\n // socket per test, and several suites run beside other builds. The\n // driver's defaults are tight enough that machine load, rather than the\n // implementation, would decide the result — a genuine failure still\n // fails here, just later.\n timeouts: { text: 30_000, action: 30_000, exit: 30_000, idle: 10_000 },\n ...launchOptions,\n });\n open.push(terminal);\n if (options.ready !== undefined) await waitForStart(terminal, options.ready, fixture);\n return terminal;\n },\n async closeAll() {\n while (open.length > 0) {\n const terminal = open.pop();\n await terminal?.close();\n }\n },\n };\n}\n\n/**\n * Waits for a fixture's first output, and says which way it failed.\n *\n * `waitForText` can only report that text never appeared, which reads as a\n * rendering problem. Distinguishing \"the child wrote nothing at all\" from \"the\n * child wrote something else\" is the difference between hunting a fixture bug\n * and hunting a spawn or scheduling one — and on a heavily loaded machine it is\n * always the latter.\n */\nasync function waitForStart(\n terminal: TerminalHarness,\n ready: string | RegExp,\n fixture: string,\n): Promise<void> {\n let bytes = 0;\n const off = terminal.events.on('output', ({ data }) => {\n bytes += data.length;\n });\n try {\n await terminal.waitForText(ready);\n } catch (error) {\n const exit = await Promise.race([terminal.exit, Promise.resolve(null)]);\n const detail =\n bytes === 0\n ? `it produced no output at all${exit === null ? ' and is still running' : `; it exited ${JSON.stringify(exit)}`}`\n : `it produced ${bytes} bytes but never drew ${String(ready)}`;\n throw new Error(\n `conformance: ${fixture.split('/').pop() ?? fixture} did not start — ${detail}`,\n {\n cause: error,\n },\n );\n } finally {\n off();\n }\n}\n\n/**\n * Whether a toolchain command succeeds here.\n *\n * Used to decide, at collection time, whether an adapter written in another\n * language can be certified on this machine at all. A missing interpreter is\n * not a conformance result, so the suite skips and says why — the same rule as\n * a missing pseudo-terminal.\n *\n * @param command - argv to run; exit status 0 counts as available.\n * @returns `false` when the command is missing, fails, or exceeds `timeoutMs`.\n *\n * @example\n * ```ts\n * commandAvailable(['python3', '-c', 'import textual']);\n * ```\n */\nexport function commandAvailable(\n command: readonly string[],\n options: {\n readonly cwd?: string;\n readonly timeoutMs?: number;\n readonly quiet?: boolean;\n readonly env?: Readonly<Record<string, string>>;\n } = {},\n): boolean {\n const [binary, ...args] = command;\n if (binary === undefined) return false;\n const printable = command.join(' ');\n try {\n const result = spawnSync(binary, args, {\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n timeout: options.timeoutMs ?? 120_000,\n encoding: 'utf8',\n env: environment(options.env),\n });\n if (result.status === 0) return true;\n // A skipped suite has to say *why* it skipped, or a probe that broke looks\n // exactly like a toolchain that was never installed.\n if (options.quiet === true) return false;\n const reason =\n result.error?.message ??\n (result.signal === null ? `exit ${String(result.status)}` : `signal ${result.signal}`);\n process.stderr.write(\n `conformance: probe \\`${printable}\\` failed (${reason})\\n` +\n `${(result.stderr ?? '').trim().split('\\n').slice(-3).join('\\n')}\\n`,\n );\n return false;\n } catch (error) {\n if (options.quiet !== true) {\n process.stderr.write(\n `conformance: probe \\`${printable}\\` could not run: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n }\n return false;\n }\n}\n\n/**\n * Turns on the child's mouse reporting and waits for the exact observed mode.\n * Every certified PTY backend, including pinned passthrough ConPTY, carries\n * the child's DECSET to the emulator.\n */\nexport async function enableMouseReporting(\n terminal: TerminalHarness,\n mode: 'click' | 'drag',\n): Promise<void> {\n const expected = mode === 'click' ? 'vt200' : 'drag';\n await terminal.press(mode === 'click' ? 'm' : 'M');\n await waitForTerminal(terminal, () => terminal.screen().modes.mouseTracking === expected);\n}\n\n/** Turns mouse reporting off and waits for the observed DECSET reset. */\nexport async function disableMouseReporting(terminal: TerminalHarness): Promise<void> {\n await terminal.press('m');\n await waitForTerminal(terminal, () => terminal.screen().modes.mouseTracking === 'none');\n}\n\n/**\n * Asks the child to enable focus reporting and waits until DECSET 1004 is\n * observed through owned checkpoint changes.\n */\nexport async function enableFocusReporting(terminal: TerminalHarness): Promise<void> {\n await terminal.press('f');\n await waitForTerminal(terminal, () => terminal.screen().modes.focusReporting === 'on');\n}\n\n/** Turns focus reporting off and waits for the observed DECSET reset. */\nexport async function disableFocusReporting(terminal: TerminalHarness): Promise<void> {\n await terminal.press('f');\n await waitForTerminal(terminal, () => terminal.screen().modes.focusReporting === 'off');\n}\n\n/**\n * Runs a burst out and answers where the published revision came to rest.\n *\n * A wall-clock budget for \"two hundred revisions arrived\" is a bet on the\n * platform's throughput, and it is the wrong question: what a burst settles on\n * depends on how far the terminal falls behind the socket while it is running,\n * which is the platform's business. Measured rather than assumed — the caller\n * then asserts what is true of the answer it got. `target` only bounds the\n * wait; reaching it is not required here.\n */\nexport async function settledRevision(\n terminal: TerminalHarness,\n target: number,\n stallMs = 15_000,\n): Promise<number> {\n let seen = terminal.semanticTree()?.revision ?? 0;\n let progressed = performance.now();\n let checkpoint = terminal.checkpoint();\n for (;;) {\n const current = terminal.semanticTree()?.revision ?? 0;\n if (current >= target) return current;\n if (current > seen) {\n seen = current;\n progressed = performance.now();\n }\n if (performance.now() - progressed > stallMs) return seen;\n const remaining = Math.max(0, stallMs - (performance.now() - progressed));\n try {\n checkpoint = await terminal.waitForCheckpointChange({\n after: checkpoint,\n timeout: remaining,\n });\n } catch {\n return seen;\n }\n }\n}\n\n/** How many times each diagnostic code was recorded — for failure messages. */\nexport function diagnosticTally(terminal: TerminalHarness): string {\n const counts = new Map<string, number>();\n for (const entry of terminal.diagnostics()) {\n counts.set(entry.code, (counts.get(entry.code) ?? 0) + 1);\n }\n return [...counts].map(([code, count]) => `${code}×${count}`).join(', ') || 'none';\n}\n\n/** Waits on the driver's owned observation generation until a predicate holds. */\nasync function waitForTerminal(\n terminal: TerminalHarness,\n predicate: () => boolean,\n timeoutMs = 15_000,\n): Promise<void> {\n const deadline = performance.now() + timeoutMs;\n let checkpoint = terminal.checkpoint();\n for (;;) {\n if (predicate()) return;\n if (performance.now() >= deadline) throw new Error('conformance: condition never became true');\n checkpoint = await terminal.waitForCheckpointChange({\n after: checkpoint,\n timeout: Math.max(0, deadline - performance.now()),\n });\n }\n}\n\n/** Catch helper: awaits a rejection and returns the error, typed. */\nexport async function rejection<T>(promise: Promise<T>): Promise<unknown> {\n try {\n await promise;\n return null;\n } catch (error) {\n return error;\n }\n}\n","/** Exact, content-addressed instrumentation for Ink 7.1.1's renderer. */\n\nimport { createHash } from 'node:crypto';\nimport certified from './certified-instrumentation.json' with { type: 'json' };\n\ninterface InkInstrumentationProfile {\n readonly version: string;\n readonly rendererSha256: string;\n readonly coreSha256: string;\n}\n\nconst BUILTIN_PROFILES: readonly InkInstrumentationProfile[] = certified.profiles;\nexport const INK_VERSION = BUILTIN_PROFILES.at(-1)?.version ?? 'unsupported';\nexport const INK_RENDER_CAPTURE = Symbol.for('termwright.ink.render-capture.v1');\nexport const INK_FRAME_CONTEXT = Symbol.for('termwright.ink.frame-context.v1');\nexport const INK_INSTRUMENTATION_SENTINEL = Symbol.for('termwright.ink.instrumentation.v1');\n\nexport const INK_RENDERER_PATTERN = /[\\\\/](?:ink|ink@[^\\\\/]+)[\\\\/]build[\\\\/]renderer\\.js$/u;\nexport const INK_CORE_PATTERN = /[\\\\/](?:ink|ink@[^\\\\/]+)[\\\\/]build[\\\\/]ink\\.js$/u;\n\nexport interface InkInstrumentationSentinel {\n readonly version: 1;\n readonly frameworkVersion: string;\n readonly rendererChecksum: string;\n readonly coreChecksum: string;\n}\n\nexport interface InkRenderedOutput {\n readonly output: string;\n readonly outputHeight: number;\n readonly staticOutput: string;\n}\n\nexport type InkRenderCaptureHook = (\n root: object,\n result: InkRenderedOutput,\n screenReader: boolean,\n) => void;\n\nexport function instrumentationSentinel(): InkInstrumentationSentinel | undefined {\n const value = (globalThis as Record<PropertyKey, unknown>)[INK_INSTRUMENTATION_SENTINEL];\n if (value === null || typeof value !== 'object') return undefined;\n const candidate = value as Partial<InkInstrumentationSentinel>;\n const profile = instrumentationProfiles().find(\n (entry) => entry.version === candidate.frameworkVersion,\n );\n return profile !== undefined &&\n candidate.version === 1 &&\n candidate.rendererChecksum === profile.rendererSha256 &&\n candidate.coreChecksum === profile.coreSha256\n ? (candidate as InkInstrumentationSentinel)\n : undefined;\n}\n\n/** Transform the matching Ink class so every capture includes render-mode facts. */\nexport function instrumentInkCore(path: string, source: string): string | undefined {\n if (!INK_CORE_PATTERN.test(path.split('?')[0] ?? '')) return undefined;\n const checksum = createHash('sha256').update(source).digest('hex');\n const profile = instrumentationProfiles().find((entry) => entry.coreSha256 === checksum);\n if (profile === undefined) return undefined;\n const needle = ` const { output, outputHeight, staticOutput } = render(this.rootNode, this.isScreenReaderEnabled);\\n this.options.onRender?.({ renderTime: performance.now() - startTime });`;\n const replacement = ` const { output, outputHeight, staticOutput } = render(this.rootNode, this.isScreenReaderEnabled);\\n globalThis[Symbol.for(\"termwright.ink.frame-context.v1\")]?.(this.rootNode, Object.freeze({ interactive: this.interactive, alternateScreen: this.alternateScreen, debug: this.options.debug === true, stdoutIsTTY: this.options.stdout.isTTY === true, rows: getWindowSize(this.options.stdout).rows }));\\n this.options.onRender?.({ renderTime: performance.now() - startTime });`;\n if (source.split(needle).length !== 2) return undefined;\n const sentinelNeedle = `const noop = () => { };`;\n if (source.split(sentinelNeedle).length !== 2) return undefined;\n const sentinel = `const __termwrightInkSentinelSymbol = Symbol.for(\"termwright.ink.instrumentation.v1\");\\nconst __termwrightInkPriorSentinel = globalThis[__termwrightInkSentinelSymbol] ?? {};\\nglobalThis[__termwrightInkSentinelSymbol] = Object.freeze({ ...__termwrightInkPriorSentinel, version: 1, frameworkVersion: \"${profile.version}\", coreChecksum: \"${checksum}\" });`;\n return source\n .replace(sentinelNeedle, `${sentinel}\\n${sentinelNeedle}`)\n .replace(needle, replacement);\n}\n\n/** Transform only the byte-exact renderer shipped by Ink 7.1.1. */\nexport function instrumentInkRenderer(path: string, source: string): string | undefined {\n if (!INK_RENDERER_PATTERN.test(path.split('?')[0] ?? '')) return undefined;\n const checksum = createHash('sha256').update(source).digest('hex');\n const profile = instrumentationProfiles().find((entry) => entry.rendererSha256 === checksum);\n if (profile === undefined) return undefined;\n\n const insertion = \"import Output from './output.js';\";\n if (source.split(insertion).length !== 2) return undefined;\n let output = source.replace(insertion, `${insertion}\\n${runtime(profile.version, checksum)}`);\n\n const screenReaderReturn = ` return {\n output,\n outputHeight,\n staticOutput: staticOutput ? \\`${'${staticOutput}'}\\\\n\\` : '',\n };`;\n const screenReaderReplacement = ` return __termwrightCaptureInkRenderer(node, {\n output,\n outputHeight,\n staticOutput: staticOutput ? \\`${'${staticOutput}'}\\\\n\\` : '',\n }, true);`;\n const normalReturn = ` return {\n output: generatedOutput,\n outputHeight,\n // Newline at the end is needed, because static output doesn't have one, so\n // interactive output will override last line of static output\n staticOutput: staticOutput ? \\`${'${staticOutput.get().output}'}\\\\n\\` : '',\n };`;\n const normalReplacement = ` return __termwrightCaptureInkRenderer(node, {\n output: generatedOutput,\n outputHeight,\n // Newline at the end is needed, because static output doesn't have one, so\n // interactive output will override last line of static output\n staticOutput: staticOutput ? \\`${'${staticOutput.get().output}'}\\\\n\\` : '',\n }, false);`;\n const emptyReturn = ` return {\n output: '',\n outputHeight: 0,\n staticOutput: '',\n };`;\n const emptyReplacement = ` return __termwrightCaptureInkRenderer(node, {\n output: '',\n outputHeight: 0,\n staticOutput: '',\n }, isScreenReaderEnabled);`;\n\n for (const [needle, replacement] of [\n [screenReaderReturn, screenReaderReplacement],\n [normalReturn, normalReplacement],\n [emptyReturn, emptyReplacement],\n ] as const) {\n if (output.split(needle).length !== 2) return undefined;\n output = output.replace(needle, replacement);\n }\n return output;\n}\n\nfunction runtime(frameworkVersion: string, checksum: string): string {\n return `const __termwrightInkCaptureSymbol = Symbol.for(\"termwright.ink.render-capture.v1\");\nconst __termwrightInkSentinelSymbol = Symbol.for(\"termwright.ink.instrumentation.v1\");\nconst __termwrightInkPriorSentinel = globalThis[__termwrightInkSentinelSymbol] ?? {};\nglobalThis[__termwrightInkSentinelSymbol] = Object.freeze({ ...__termwrightInkPriorSentinel, version: 1, frameworkVersion: \"${frameworkVersion}\", rendererChecksum: \"${checksum}\" });\nconst __termwrightCaptureInkRenderer = (root, result, screenReader) => {\n const capture = globalThis[__termwrightInkCaptureSymbol];\n if (typeof capture === \"function\") capture(root, result, screenReader);\n return result;\n};`;\n}\n\nfunction instrumentationProfiles(): readonly InkInstrumentationProfile[] {\n const override = certificationOverride();\n return override === undefined ? BUILTIN_PROFILES : [override, ...BUILTIN_PROFILES];\n}\n\nfunction certificationOverride(): InkInstrumentationProfile | undefined {\n const raw = process.env['TERMWRIGHT_CERTIFICATION_HOOK_PROFILE'];\n if (raw === undefined) return undefined;\n if (process.env['GITHUB_ACTIONS'] !== 'true') return undefined;\n try {\n const value = JSON.parse(raw) as Record<string, unknown>;\n const digest = process.env['TERMWRIGHT_CERTIFICATION_CANDIDATE_DIGEST'];\n const revision = process.env['TERMWRIGHT_CERTIFICATION_SOURCE_REVISION'];\n if (\n value['framework'] !== 'ink' ||\n !/^sha256:[a-f0-9]{64}$/u.test(digest ?? '') ||\n revision !== process.env['GITHUB_SHA'] ||\n value['sourceRevision'] !== revision ||\n value['candidateDigest'] !== digest ||\n typeof value['version'] !== 'string' ||\n !/^[a-f0-9]{64}$/u.test(String(value['rendererSha256'])) ||\n !/^[a-f0-9]{64}$/u.test(String(value['coreSha256']))\n )\n return undefined;\n return {\n version: value['version'],\n rendererSha256: String(value['rendererSha256']),\n coreSha256: String(value['coreSha256']),\n };\n } catch {\n return undefined;\n }\n}\n","{\n \"framework\": \"ink\",\n \"profiles\": [\n {\n \"coreSha256\": \"f632f6176e593183f0c0bb6e4a6e8a28d65f1c3899a33a84d4a95d26e1a82a58\",\n \"rendererSha256\": \"9e72b27731c38daac7e9f978e24f7bf1210c5cc26bf973e30f08c3ad4a9fe374\",\n \"version\": \"7.1.1\"\n }\n ],\n \"schemaVersion\": 1\n}\n","/** Minimal React renderer instrumentation observer used by the Ink probe spike. */\n\nimport type { InkDomElement } from './observe.js';\n\nconst BRIDGE = Symbol.for('@termwright/probe-ink/react-commit-bridge.v1');\n\ninterface RendererMetadata {\n readonly rendererPackageName?: unknown;\n readonly rendererVersion?: unknown;\n}\n\ninterface FiberRootLike {\n readonly containerInfo?: unknown;\n readonly current?: FiberLike;\n}\n\ninterface FiberLike {\n readonly stateNode?: unknown;\n readonly memoizedProps?: unknown;\n readonly child?: FiberLike | null;\n readonly sibling?: FiberLike | null;\n}\n\ninterface DevToolsHookLike {\n readonly supportsFiber?: boolean;\n inject?(renderer: RendererMetadata): unknown;\n onCommitFiberRoot?(rendererId: unknown, root: FiberRootLike, ...rest: readonly unknown[]): void;\n onCommitFiberUnmount?(rendererId: unknown, fiber: unknown): void;\n [BRIDGE]?: ReactCommitBridge;\n [key: PropertyKey]: unknown;\n}\n\nexport interface InkRendererRegistration {\n readonly rendererId: unknown;\n readonly packageName: 'ink';\n readonly version?: string;\n}\n\nexport type InkCommitEvent =\n | {\n readonly type: 'commit';\n readonly renderer: InkRendererRegistration;\n readonly fiberRoot: FiberRootLike;\n readonly root: InkDomElement;\n }\n | {\n readonly type: 'unmount';\n readonly renderer: InkRendererRegistration;\n readonly fiber: unknown;\n }\n | {\n readonly type: 'invalid-root';\n readonly renderer: InkRendererRegistration;\n readonly fiberRoot: FiberRootLike;\n readonly containerInfo: unknown;\n };\n\nexport interface InkReconcilerInstrumentation {\n injectIntoDevTools(): unknown;\n}\n\nexport interface ReactCommitBridgeLease {\n readonly bridge: ReactCommitBridge;\n release(): void;\n}\n\n/**\n * Experimental, deliberately Fiber-dependent correlation used to measure\n * which source accessibility props Ink drops from its committed host DOM.\n * It is not used by the production observer or accepted as a stable seam.\n */\nexport interface InkHostPropCorrelation {\n readonly hostProps?: Readonly<Record<string, unknown>>;\n readonly sourceProps?: Readonly<Record<string, unknown>>;\n readonly accessibleName?: string;\n readonly ariaHidden?: boolean;\n}\n\ntype Listener = (event: InkCommitEvent) => void;\n\n/**\n * A process-global observer which composes with an already-installed hook.\n * Renderer ids are always the ids returned to React by that hook.\n */\nexport class ReactCommitBridge {\n readonly #renderers = new Map<unknown, InkRendererRegistration>();\n readonly #roots = new Map<object, InkDomElement>();\n readonly #listeners = new Set<Listener>();\n #nextRendererId = 1;\n\n register(renderer: RendererMetadata, delegatedId?: unknown): unknown {\n const rendererId = delegatedId === undefined ? this.#nextRendererId++ : delegatedId;\n if (typeof rendererId === 'number' && Number.isInteger(rendererId)) {\n this.#nextRendererId = Math.max(this.#nextRendererId, rendererId + 1);\n }\n if (renderer.rendererPackageName === 'ink') {\n this.#renderers.set(rendererId, {\n rendererId,\n packageName: 'ink',\n ...(typeof renderer.rendererVersion === 'string'\n ? { version: renderer.rendererVersion }\n : {}),\n });\n }\n return rendererId;\n }\n\n commit(rendererId: unknown, fiberRoot: FiberRootLike): void {\n const renderer = this.#renderers.get(rendererId);\n if (renderer === undefined) return;\n const containerInfo = fiberRoot.containerInfo;\n if (!isInkRoot(containerInfo)) {\n this.#emit({ type: 'invalid-root', renderer, fiberRoot, containerInfo });\n return;\n }\n this.#roots.set(fiberRoot as object, containerInfo);\n this.#emit({ type: 'commit', renderer, fiberRoot, root: containerInfo });\n }\n\n unmount(rendererId: unknown, fiber: unknown): void {\n const renderer = this.#renderers.get(rendererId);\n if (renderer !== undefined) this.#emit({ type: 'unmount', renderer, fiber });\n }\n\n subscribe(listener: Listener): () => void {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n }\n\n roots(): readonly InkDomElement[] {\n return [...this.#roots.values()];\n }\n\n hasInkRenderer(): boolean {\n return this.#renderers.size > 0;\n }\n\n #emit(event: InkCommitEvent): void {\n for (const listener of this.#listeners) {\n try {\n listener(event);\n } catch {\n // Instrumentation observers must never break React's commit callback.\n }\n }\n }\n}\n\n/** Install or reuse the bridge without replacing the user's hook behavior. */\nexport function installReactCommitBridge(\n target: typeof globalThis = globalThis,\n): ReactCommitBridge {\n const holder = target as typeof globalThis & {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHookLike;\n };\n const existing = holder.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n const installed = existing?.[BRIDGE];\n if (installed !== undefined) return installed;\n\n const bridge = new ReactCommitBridge();\n const hook = Object.create(existing ?? null) as DevToolsHookLike;\n Object.defineProperties(hook, {\n supportsFiber: { value: true, enumerable: true, configurable: true },\n inject: {\n configurable: true,\n value(renderer: RendererMetadata): unknown {\n const delegatedId = existing?.inject?.call(existing, renderer);\n return bridge.register(renderer, delegatedId);\n },\n },\n onCommitFiberRoot: {\n configurable: true,\n value(rendererId: unknown, root: FiberRootLike, ...rest: readonly unknown[]): void {\n try {\n existing?.onCommitFiberRoot?.call(existing, rendererId, root, ...rest);\n } finally {\n bridge.commit(rendererId, root);\n }\n },\n },\n onCommitFiberUnmount: {\n configurable: true,\n value(rendererId: unknown, fiber: unknown): void {\n try {\n existing?.onCommitFiberUnmount?.call(existing, rendererId, fiber);\n } finally {\n bridge.unmount(rendererId, fiber);\n }\n },\n },\n [BRIDGE]: { value: bridge },\n });\n try {\n const descriptor = Object.getOwnPropertyDescriptor(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__');\n if (\n descriptor?.configurable === true &&\n (('writable' in descriptor && descriptor.writable === false) ||\n (!('writable' in descriptor) && descriptor.set === undefined))\n ) {\n Object.defineProperty(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__', {\n value: hook,\n writable: true,\n enumerable: descriptor.enumerable ?? false,\n configurable: true,\n });\n } else {\n holder.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;\n }\n } catch (cause) {\n throw new Error(\n 'Ink semantic probe unavailable: the existing React renderer instrumentation hook cannot be composed.',\n { cause },\n );\n }\n return bridge;\n}\n\ninterface BridgeLeaseRecord {\n readonly bridge: ReactCommitBridge;\n readonly hook: DevToolsHookLike;\n readonly priorDescriptor?: PropertyDescriptor;\n references: number;\n}\n\nconst bridgeLeases = new WeakMap<object, BridgeLeaseRecord>();\n\n/**\n * Acquire a process-hook lease for transactional adapter setup. The final\n * release restores the exact prior property descriptor, but only while our\n * hook is still current. A bridge installed independently is never removed.\n */\nexport function acquireReactCommitBridge(\n target: typeof globalThis = globalThis,\n): ReactCommitBridgeLease {\n const holder = target as typeof globalThis & {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHookLike;\n };\n const currentRecord = bridgeLeases.get(target);\n if (currentRecord !== undefined && holder.__REACT_DEVTOOLS_GLOBAL_HOOK__ === currentRecord.hook) {\n currentRecord.references += 1;\n return leaseFor(target, currentRecord);\n }\n\n const existingBridge = holder.__REACT_DEVTOOLS_GLOBAL_HOOK__?.[BRIDGE];\n const priorDescriptor = Object.getOwnPropertyDescriptor(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__');\n const bridge = installReactCommitBridge(target);\n const hook = holder.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n if (hook === undefined) {\n throw new Error(\n 'Ink semantic probe unavailable: React renderer instrumentation hook installation disappeared.',\n );\n }\n // If another subsystem installed this bridge, this adapter may subscribe to\n // it but must not claim ownership of the process-global hook.\n if (existingBridge !== undefined) return { bridge, release() {} };\n const record: BridgeLeaseRecord = {\n bridge,\n hook,\n ...(priorDescriptor === undefined ? {} : { priorDescriptor }),\n references: 1,\n };\n bridgeLeases.set(target, record);\n return leaseFor(target, record);\n}\n\nfunction leaseFor(target: typeof globalThis, record: BridgeLeaseRecord): ReactCommitBridgeLease {\n let released = false;\n return {\n bridge: record.bridge,\n release() {\n if (released) return;\n released = true;\n record.references -= 1;\n if (record.references > 0) return;\n bridgeLeases.delete(target);\n const holder = target as typeof globalThis & {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHookLike;\n };\n if (holder.__REACT_DEVTOOLS_GLOBAL_HOOK__ !== record.hook) return;\n if (record.priorDescriptor === undefined) {\n delete holder.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n } else {\n Object.defineProperty(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__', record.priorDescriptor);\n }\n },\n };\n}\n\nconst activatedReconcilers = new WeakMap<object, WeakSet<object>>();\n\n/**\n * Enable Ink's existing reconciler seam directly. This intentionally does not\n * set DEV and therefore cannot load the DevTools UI/backend or open a socket.\n */\nexport function activateInkRendererObservation(\n reconciler: InkReconcilerInstrumentation,\n target: typeof globalThis = globalThis,\n): ReactCommitBridge {\n const bridge = installReactCommitBridge(target);\n let bridges = activatedReconcilers.get(reconciler);\n if (bridges === undefined) {\n bridges = new WeakSet<object>();\n activatedReconcilers.set(reconciler, bridges);\n }\n if (!bridges.has(bridge)) {\n // React 19's reconciler currently returns false even after synchronously\n // calling hook.inject(). Registration, not that implementation-detail\n // return value, is the capability proof.\n reconciler.injectIntoDevTools();\n if (!bridge.hasInkRenderer())\n throw new Error(\n 'Ink semantic probe unavailable: React renderer instrumentation did not register Ink.',\n );\n bridges.add(bridge);\n }\n return bridge;\n}\n\n/**\n * Correlate committed Ink host objects with the nearest source component\n * props. This POC proves that `aria-label`/`aria-hidden`, which Ink omits from\n * normal-mode host DOM, remain recoverable through Fiber. The returned map is\n * a measurement aid, not a production contract: every field is structural\n * React internals and must fail absent rather than fabricate data.\n */\nexport function correlateInkHostProps(\n fiberRoot: FiberRootLike,\n options: { readonly maxFibers?: number } = {},\n): ReadonlyMap<InkDomElement, InkHostPropCorrelation> {\n const correlations = new Map<InkDomElement, InkHostPropCorrelation>();\n const maxFibers = options.maxFibers ?? 100_000;\n let visitedFibers = 0;\n const walk = (\n fiber: FiberLike | null | undefined,\n candidateSourceProps?: Readonly<Record<string, unknown>>,\n ): void => {\n for (\n let current = fiber;\n current !== null && current !== undefined;\n current = current.sibling\n ) {\n visitedFibers += 1;\n if (visitedFibers > maxFibers) {\n throw new Error(\n 'Ink Fiber accessibility correlation exceeded its bounded traversal limit.',\n );\n }\n const props = record(current.memoizedProps);\n const sourceProps = hasAccessibilitySourceProps(props) ? props : candidateSourceProps;\n if (isInkElement(current.stateNode)) {\n correlations.set(current.stateNode, {\n ...(props === undefined ? {} : { hostProps: props }),\n ...(sourceProps === undefined ? {} : { sourceProps }),\n ...(typeof sourceProps?.['aria-label'] === 'string'\n ? { accessibleName: sourceProps['aria-label'] }\n : {}),\n ...(typeof sourceProps?.['aria-hidden'] === 'boolean'\n ? { ariaHidden: sourceProps['aria-hidden'] }\n : {}),\n });\n walk(current.child, undefined);\n } else {\n walk(current.child, sourceProps);\n }\n }\n };\n walk(fiberRoot.current?.child);\n return correlations;\n}\n\n/** Fail closed instead of accepting a foreign or incomplete committed root. */\nexport function requireCommittedInkRoot(event: InkCommitEvent): InkDomElement {\n if (event.type !== 'commit') {\n throw new Error(\n 'Ink semantic probe unavailable: React renderer instrumentation did not expose expected committed Ink root.',\n );\n }\n return event.root;\n}\n\nfunction isInkRoot(value: unknown): value is InkDomElement {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as {\n readonly nodeName?: unknown;\n readonly childNodes?: unknown;\n };\n return candidate.nodeName === 'ink-root' && Array.isArray(candidate.childNodes);\n}\n\nfunction isInkElement(value: unknown): value is InkDomElement {\n if (typeof value !== 'object' || value === null) return false;\n const nodeName = (value as { readonly nodeName?: unknown }).nodeName;\n return (\n nodeName === 'ink-root' ||\n nodeName === 'ink-box' ||\n nodeName === 'ink-text' ||\n nodeName === 'ink-virtual-text'\n );\n}\n\nfunction record(value: unknown): Readonly<Record<string, unknown>> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Readonly<Record<string, unknown>>)\n : undefined;\n}\n\nfunction hasAccessibilitySourceProps(\n props: Readonly<Record<string, unknown>> | undefined,\n): boolean {\n return (\n props !== undefined &&\n (Object.hasOwn(props, 'aria-label') ||\n Object.hasOwn(props, 'aria-hidden') ||\n Object.hasOwn(props, 'aria-role') ||\n Object.hasOwn(props, 'aria-state'))\n );\n}\n","import type { ProbeAnnotations, ProtocolLimits } from '@termwright/protocol';\nimport { validateProbeAnnotations } from '@termwright/protocol';\n\nconst REGISTRY = Symbol.for('termwright.annotation.ink.v1');\n\ninterface StoredAnnotation {\n readonly role?: unknown;\n readonly name?: unknown;\n readonly description?: unknown;\n readonly testId?: unknown;\n readonly extended?: unknown;\n readonly actions?: unknown;\n readonly labelledBy?: readonly WeakRef<object>[];\n readonly describedBy?: readonly WeakRef<object>[];\n}\n\ninterface AnnotationSlot {\n readonly current?: StoredAnnotation;\n}\n\ninterface AnnotationChannel {\n readonly entries: WeakMap<object, AnnotationSlot>;\n readonly listeners: Set<() => void>;\n}\n\nfunction channel(): AnnotationChannel {\n const scope = globalThis as Record<PropertyKey, unknown>;\n const present = scope[REGISTRY] as Partial<AnnotationChannel> | undefined;\n if (present?.entries instanceof WeakMap && present.listeners instanceof Set) {\n return present as AnnotationChannel;\n }\n const created: AnnotationChannel = {\n entries: new WeakMap<object, AnnotationSlot>(),\n listeners: new Set<() => void>(),\n };\n Object.defineProperty(scope, REGISTRY, { configurable: true, value: created });\n return created;\n}\n\n/** Re-capture after an annotation attaches to a newly reconciled host. */\nexport function onInkAnnotationChange(handler: () => void): () => void {\n const listeners = channel().listeners;\n listeners.add(handler);\n return () => listeners.delete(handler);\n}\n\nfunction strings(\n refs: unknown,\n idFor: (node: object) => string,\n maxTargets: number,\n): string[] | null | undefined {\n if (refs === undefined) return undefined;\n if (!Array.isArray(refs)) return null;\n const length = Object.getOwnPropertyDescriptor(refs, 'length')?.value;\n if (!Number.isSafeInteger(length) || length < 0 || length > maxTargets) return null;\n const ids: string[] = [];\n for (let index = 0; index < length; index += 1) {\n try {\n const descriptor = Object.getOwnPropertyDescriptor(refs, String(index));\n if (descriptor === undefined || !('value' in descriptor)) return null;\n const ref = descriptor.value;\n if (!(ref instanceof WeakRef)) return null;\n const target = WeakRef.prototype.deref.call(ref) as object | undefined;\n if (target !== undefined) ids.push(idFor(target));\n } catch {\n return null;\n }\n }\n return ids.length === 0 ? undefined : ids;\n}\n\nfunction ownData(value: object, key: keyof StoredAnnotation): unknown {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined;\n}\n\n/** Read author intent without taking a runtime dependency on the optional SDK. */\nexport function annotationForInkNode(\n node: object,\n idFor: (node: object) => string,\n limits: ProtocolLimits,\n): ProbeAnnotations | undefined {\n try {\n const slot = channel().entries.get(node);\n const value = slot?.current;\n if (value === undefined) return undefined;\n const role = ownData(value, 'role');\n const name = ownData(value, 'name');\n const description = ownData(value, 'description');\n const testId = ownData(value, 'testId');\n const extended = ownData(value, 'extended');\n const actions = ownData(value, 'actions');\n const labelledBy = strings(ownData(value, 'labelledBy'), idFor, limits.maxRelationTargets);\n const describedBy = strings(ownData(value, 'describedBy'), idFor, limits.maxRelationTargets);\n const candidate = {\n ...(role === undefined ? {} : { role }),\n ...(name === undefined ? {} : { name }),\n ...(description === undefined ? {} : { description }),\n ...(testId === undefined ? {} : { testId }),\n ...(extended === undefined ? {} : { extended }),\n ...(actions === undefined ? {} : { actions }),\n ...(labelledBy === undefined ? {} : { labelledBy }),\n ...(describedBy === undefined ? {} : { describedBy }),\n };\n if (Object.keys(candidate).length === 0) return undefined;\n const validated = validateProbeAnnotations(candidate, limits);\n return validated.ok ? validated.annotations : undefined;\n } catch {\n return undefined;\n }\n}\n","/** Ink's retained host tree to framework-neutral Probe IR. */\n\nimport type {\n ProbeAccessibilityHints,\n ProbeFrame,\n ProbeObject,\n ProbeObservedState,\n ProbeRect,\n ProbeUnobservableField,\n ProtocolLimits,\n} from '@termwright/protocol';\nimport { annotationForInkNode } from './annotations.js';\nimport type { RelativeGeometry } from './frame-capture.js';\n\n/** Structural subset of Ink's DOM node. No runtime import from `ink`. */\nexport interface InkDomElement {\n readonly nodeName: 'ink-root' | 'ink-box' | 'ink-text' | 'ink-virtual-text';\n readonly childNodes: readonly InkDomNode[];\n readonly parentNode?: InkDomElement;\n readonly style?: Readonly<Record<string, unknown>> & { readonly display?: string };\n readonly internal_static?: boolean;\n readonly staticNode?: InkDomElement;\n readonly internal_accessibility?: {\n readonly role?: string;\n readonly state?: {\n readonly checked?: boolean;\n readonly disabled?: boolean;\n readonly expanded?: boolean;\n readonly readonly?: boolean;\n readonly selected?: boolean;\n readonly busy?: boolean;\n readonly multiline?: boolean;\n readonly required?: boolean;\n readonly multiselectable?: boolean;\n };\n };\n}\n\nexport interface InkTextNode {\n readonly nodeName: '#text';\n readonly nodeValue: string;\n readonly parentNode?: InkDomElement;\n}\n\nexport type InkDomNode = InkDomElement | InkTextNode;\n\n/** Public Ink measurement function, kept injectable for tests and isolation. */\nexport type MeasureElement = (node: InkDomElement) => {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n};\n\nexport interface ObserveInkOptions {\n readonly frame: number;\n readonly limits: ProtocolLimits;\n /** The probe's own hidden Box. It is the sole injected node and is omitted. */\n readonly excluded?: InkDomElement | null;\n /** Renderer-retained roots (notably committed <Static>) detached by Ink later. */\n readonly retainedRoots?: readonly InkDomElement[];\n readonly retainedChildren?: ReadonlyMap<InkDomElement, readonly InkDomNode[]>;\n readonly measureElement?: MeasureElement;\n /** Geometry frozen by the certified 7.1.1 renderer instrumentation. */\n readonly geometry?: ReadonlyMap<InkDomElement, RelativeGeometry>;\n}\n\nexport interface InkObservation {\n readonly frame: ProbeFrame;\n readonly truncated: boolean;\n readonly geometryRegions: ReadonlyMap<string, 'live' | 'static'>;\n}\n\nconst isElement = (node: InkDomNode): node is InkDomElement => node.nodeName !== '#text';\n\n/**\n * Observe every Ink host element, including plain unannotated layout boxes.\n *\n * Source component names do not survive Ink's reconciler. `frameworkType` is\n * therefore deliberately one of Ink's four host kinds; inventing `Button` or\n * a component stack here would be false provenance.\n */\nexport function observeInkTree(root: InkDomElement, options: ObserveInkOptions): InkObservation {\n const objects: ProbeObject[] = [];\n const ids = identityStore(root);\n let truncated = false;\n const geometryRegions = new Map<string, 'live' | 'static'>();\n const visited = new Set<InkDomElement>();\n\n const visit = (\n node: InkDomElement,\n parent: InkDomElement | undefined,\n depth: number,\n ancestorHidden: boolean,\n ): void => {\n if (node === options.excluded || visited.has(node)) return;\n visited.add(node);\n if (depth > options.limits.maxDepth || objects.length >= options.limits.maxNodes) {\n truncated = true;\n return;\n }\n\n const hidden = ancestorHidden || node.style?.display === 'none';\n const state = observedState(node, !hidden);\n const annotations = annotationForInkNode(\n node,\n (target) => ids.idFor(target as InkDomElement),\n options.limits,\n );\n const accessibility = observedAccessibility(node);\n const geometry = geometryOf(node, options);\n const identity = ids.idFor(node);\n const region = options.geometry?.get(node)?.region;\n if (region !== undefined) geometryRegions.set(identity, region);\n // Probe IR's `text` is the object's own text, never a descendant-derived\n // accessible name. The recognizer applies name-from-content over the tree.\n const children = options.retainedChildren?.get(node) ?? node.childNodes;\n const text = isTextHost(node) ? textOf(children, options.limits.maxStringBytes) : undefined;\n const unobservable = unobservableFor(\n node,\n geometry?.intendedRect !== undefined,\n text !== undefined,\n );\n\n objects.push({\n identity: { kind: 'stable', value: identity },\n frameworkType: node.nodeName,\n ...(parent === undefined ? {} : { parent: ids.idFor(parent) }),\n ...(geometry === undefined ? {} : { geometry }),\n ...(state === undefined ? {} : { state }),\n ...(text === undefined ? {} : { text }),\n ...(accessibility === undefined ? {} : { accessibility }),\n ...(annotations === undefined ? {} : { annotations }),\n unobservable,\n });\n\n for (const child of children) {\n // Raw `#text` values are payload owned by their `ink-text` host, not a\n // fifth host kind. The text is retained on that host above.\n if (isElement(child)) visit(child, node, depth + 1, hidden);\n }\n };\n\n visit(root, undefined, 0, false);\n // Ink removes committed <Static> children from the live root but retains the\n // exact host subtree in root.staticNode for the separately rendered static\n // output. Observe that retained subtree as a root child when it is no longer\n // reachable through childNodes; the identity and captured layout stay exact.\n if (root.staticNode !== undefined) visit(root.staticNode, root, 1, false);\n for (const retained of options.retainedRoots ?? []) visit(retained, root, 1, false);\n return { frame: { frame: options.frame, objects }, truncated, geometryRegions };\n}\n\n/** Weak identity is stable for exactly the lifetime of Ink's host object. */\nconst stores = new WeakMap<InkDomElement, IdentityStore>();\n\ninterface IdentityStore {\n idFor(node: InkDomElement): string;\n}\n\nfunction identityStore(root: InkDomElement): IdentityStore {\n let store = stores.get(root);\n if (store !== undefined) return store;\n const ids = new WeakMap<InkDomElement, string>();\n let nextId = 0;\n store = {\n idFor(node) {\n const existing = ids.get(node);\n if (existing !== undefined) return existing;\n nextId += 1;\n const id = String(nextId);\n ids.set(node, id);\n return id;\n },\n };\n stores.set(root, store);\n return store;\n}\n\nfunction isTextHost(node: InkDomElement): boolean {\n return node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text';\n}\n\nfunction observedAccessibility(node: InkDomElement): ProbeAccessibilityHints | undefined {\n const role = node.internal_accessibility?.role;\n return role === undefined ? undefined : { role };\n}\n\nfunction observedState(node: InkDomElement, displayed: boolean): ProbeObservedState | undefined {\n const accessibility = node.internal_accessibility?.state;\n const state: ProbeObservedState = {\n displayed,\n ...(accessibility?.checked === undefined ? {} : { checked: accessibility.checked }),\n ...(accessibility?.disabled === undefined ? {} : { disabled: accessibility.disabled }),\n ...(accessibility?.expanded === undefined ? {} : { expanded: accessibility.expanded }),\n ...(accessibility?.readonly === undefined ? {} : { readonly: accessibility.readonly }),\n ...(accessibility?.selected === undefined ? {} : { selected: accessibility.selected }),\n ...(accessibility?.busy === undefined ? {} : { busy: accessibility.busy }),\n ...(accessibility?.multiline === undefined ? {} : { multiline: accessibility.multiline }),\n ...(accessibility?.required === undefined ? {} : { required: accessibility.required }),\n ...(accessibility?.multiselectable === undefined\n ? {}\n : { multiselectable: accessibility.multiselectable }),\n };\n return state;\n}\n\nfunction geometryOf(\n node: InkDomElement,\n options: ObserveInkOptions,\n): { readonly intendedRect: ProbeRect; readonly visibleRect: ProbeRect } | undefined {\n const geometry = options.geometry?.get(node);\n return geometry === undefined\n ? undefined\n : { intendedRect: geometry.intended, visibleRect: geometry.visible };\n}\n\nfunction textOf(children: readonly InkDomNode[], maxBytes: number): string | undefined {\n const parts: string[] = [];\n let bytes = 0;\n\n const append = (value: string): void => {\n for (const codePoint of value) {\n const size = Buffer.byteLength(codePoint, 'utf8');\n if (bytes + size > maxBytes) return;\n parts.push(codePoint);\n bytes += size;\n }\n };\n\n // Raw #text children are this host's payload. Nested host elements retain\n // their own ProbeObjects, so folding them in here would violate the IR's\n // own-text contract and duplicate them during name-from-content inference.\n for (const child of children) {\n if (bytes >= maxBytes) break;\n if (!isElement(child)) append(child.nodeValue);\n }\n const text = parts.join('').replace(/\\s+/gu, ' ').trim();\n return text.length === 0 ? undefined : text;\n}\n\nfunction unobservableFor(\n node: InkDomElement,\n hasGeometry: boolean,\n hasText: boolean,\n): readonly ProbeUnobservableField[] {\n const result: ProbeUnobservableField[] = [\n 'focused',\n 'value',\n 'selectedIndex',\n 'textSelection',\n 'scroll',\n 'scrollExtent',\n 'paintOrder',\n ];\n const state = node.internal_accessibility?.state;\n if (state?.disabled === undefined) result.push('disabled');\n if (state?.checked === undefined) result.push('checked');\n if (state?.expanded === undefined) result.push('expanded');\n if (state?.readonly === undefined) result.push('readonly');\n if (state?.selected === undefined) result.push('selected');\n if (state?.busy === undefined) result.push('busy');\n if (state?.multiline === undefined) result.push('multiline');\n if (!hasGeometry) result.push('intendedRect', 'visibleRect');\n if (!hasText && isTextHost(node)) result.push('text');\n return result;\n}\n","/** Synchronized from package.json by scripts/sync-protocol-version.mjs. */\nexport const PACKAGE_VERSION = '0.3.0';\n","import type { ProbeInfo } from '@termwright/protocol';\nimport { instrumentationSentinel, INK_VERSION } from './instrumentation.js';\nimport { PACKAGE_VERSION } from './version.js';\n\n/** Static probe identity, kept independent from the render-session runtime. */\nexport function probeInfo(\n frameworkVersion = instrumentationSentinel()?.frameworkVersion ?? INK_VERSION,\n): ProbeInfo {\n return {\n framework: 'ink',\n frameworkVersion,\n probeVersion: PACKAGE_VERSION,\n identityKind: 'stable',\n capabilities: ['stable-identity', 'intended-rect', 'visible-rect', 'annotations'],\n instrumentation: {\n highestTier: 'T3',\n semanticClass: 'A',\n degradedCapabilities: [],\n },\n };\n}\n","/** Certified Ink render capture to revision-paired semantic snapshots. */\n\nimport type { ProbeFrame, ProtocolLimits, SemanticSnapshot } from '@termwright/protocol';\nimport { writeWindowsConsoleMarker } from '@termwright/pty';\nimport { recognize } from '@termwright/recognizers';\nimport type { ProbeChannel } from '@termwright/probe-runtime';\nimport { observeInkTree, type InkDomElement } from './observe.js';\nimport type { InkFrameCapture } from './frame-capture.js';\nimport type { InkTerminalTracker, TerminalPosition } from './terminal-tracker.js';\nexport { probeInfo } from './probe-info.js';\n\nexport interface InkSessionOptions {\n readonly channel: ProbeChannel;\n readonly resolveRoot: () => InkDomElement | null;\n readonly resolveExcluded?: () => InkDomElement | null;\n readonly resolveCapture: (root: InkDomElement) => InkFrameCapture | undefined;\n /** Resolves after Ink has enqueued and flushed every stdout write for the captured render. */\n readonly waitForRenderFlush: () => Promise<void>;\n readonly stdout: NodeJS.WriteStream;\n /** Writes the authenticated marker through the same ordered transport as the frame. */\n readonly writeMarker: (marker: string) => Promise<void>;\n readonly tracker: InkTerminalTracker;\n readonly onGuaranteeViolation?: (error: Error) => void;\n}\n\nexport interface InkProbeSession {\n readonly revision: number;\n readonly frames: number;\n /** Freeze a renderer commit; refresh-only calls wait when the host tree is ahead of its capture. */\n notifyRender(options?: {\n readonly allowUnsettled?: boolean;\n /** Resolve with the first publication at or causally after this frame. */\n readonly awaitPublication?: boolean;\n }): Promise<number | null>;\n flush(): Promise<void>;\n stop(): void;\n}\n\ninterface FrozenFrame {\n readonly number: number;\n readonly capture: InkFrameCapture;\n readonly observation: ReturnType<typeof observeInkTree>;\n}\n\nexport function createInkSession(options: InkSessionOptions): InkProbeSession {\n let revision = 0;\n let frames = 0;\n let latestFrame = 0;\n let stopped = false;\n let queue: Promise<void> = Promise.resolve();\n const publicationWaiters: Array<{\n readonly targetFrame: number;\n readonly resolve: (revision: number) => void;\n readonly reject: (error: Error) => void;\n }> = [];\n\n const fail = (error: unknown): void => {\n if (stopped) return;\n stopped = true;\n const failure = error instanceof Error ? error : new Error(String(error));\n for (const waiter of publicationWaiters.splice(0)) waiter.reject(failure);\n options.onGuaranteeViolation?.(failure);\n options.channel.close();\n };\n\n const stop = (): void => {\n if (stopped) return;\n stopped = true;\n const failure = new Error('Ink probe stopped');\n for (const waiter of publicationWaiters.splice(0)) waiter.reject(failure);\n // A normal application exit or explicit cleanup is not a semantic\n // guarantee violation. Keep the typed failure callback exclusively for\n // capture/publication/marker faults, while graceful teardown simply\n // closes the producer after rejecting its owned causal waiters.\n options.channel.close();\n };\n\n const resolvePublications = (frame: number, publishedRevision: number): void => {\n for (let index = publicationWaiters.length - 1; index >= 0; index -= 1) {\n const waiter = publicationWaiters[index];\n if (waiter === undefined || waiter.targetFrame > frame) continue;\n publicationWaiters.splice(index, 1);\n waiter.resolve(publishedRevision);\n }\n };\n\n const publish = async (frozen: FrozenFrame): Promise<number | null> => {\n await nextMacrotask();\n // A marker authenticates the terminal bytes for this render, so it must\n // follow Ink's own stdout flush boundary, not just the probe's shadow drain.\n await options.waitForRenderFlush();\n await options.tracker.drain();\n if (stopped) return null;\n if (!options.channel.isOpen) {\n fail(new Error('Ink semantic channel closed before publication'));\n return null;\n }\n if (frozen.number !== latestFrame) {\n options.channel.recordCoalescedEvent();\n return null;\n }\n const context = frozen.capture.context;\n if (context === undefined) throw new Error('certified Ink frame context is unavailable');\n if (frozen.capture.screenReader) {\n throw new Error('Ink screen-reader output has no authoritative per-node cell geometry');\n }\n const position = options.tracker.position();\n if ((context.alternateScreen ? 'alternate' : 'normal') !== position.buffer) {\n throw new Error('Ink render mode and committed VT buffer disagree');\n }\n const columns = options.stdout.columns ?? 80;\n const rows = options.stdout.rows ?? 24;\n const qualified = qualifyFrame(frozen, position, columns, rows);\n revision += 1;\n const snapshot: SemanticSnapshot = recognize(qualified, {\n sessionId: options.channel.session.sessionId,\n revision,\n columns,\n rows,\n framework: 'ink',\n paintOrderKnown: false,\n maxStringBytes: options.channel.session.limits.maxStringBytes,\n });\n const marker = options.channel.publish(snapshot, {\n probeEvents: qualified.objects.length + (qualified.operations?.length ?? 0),\n });\n if (marker === undefined) throw new Error('Ink semantic publication was refused');\n // There must be no async gap between the final frame check and enqueueing\n // its marker: a newer Ink render could otherwise write in between them.\n // The selected transport establishes FRAME -> MARKER; awaiting it makes\n // `flush()` an actual publication boundary for teardown.\n await options.writeMarker(marker);\n resolvePublications(frozen.number, revision);\n return revision;\n };\n\n return {\n get revision() {\n return revision;\n },\n get frames() {\n return frames;\n },\n notifyRender(notifyOptions = {}) {\n if (stopped) return Promise.resolve(null);\n try {\n const root = options.resolveRoot();\n if (root === null) throw new Error('Ink committed frame has no retained root');\n const capture = options.resolveCapture(root);\n if (capture === undefined || capture.root !== root) {\n throw new Error('Ink committed frame has no matching certified renderer capture');\n }\n const excluded = options.resolveExcluded?.();\n const observation = observeInkTree(root, {\n frame: frames,\n limits: options.channel.session.limits as ProtocolLimits,\n ...(excluded === undefined ? {} : { excluded }),\n ...(capture.staticRoots.length === 0 ? {} : { retainedRoots: capture.staticRoots }),\n ...(capture.staticChildren.size === 0\n ? {}\n : { retainedChildren: capture.staticChildren }),\n geometry: capture.geometry,\n });\n // Layout effects can register annotations after React mutates the host\n // tree but before Ink's throttled renderer has produced the matching\n // capture. That is a transient refresh state, not a committed frame\n // whose guaranteed geometry may be downgraded. The subsequent real\n // onRender call freezes it. Renderer-originated calls remain strict.\n if (hasDisplayedNodeWithoutGeometry(observation.frame)) {\n if (notifyOptions.allowUnsettled === true) return Promise.resolve(null);\n throw new Error(\n 'certified Ink renderer capture is missing geometry for a displayed host node',\n );\n }\n frames += 1;\n latestFrame = frames;\n const frozen = { number: frames, capture, observation };\n const boundary =\n notifyOptions.awaitPublication === true\n ? new Promise<number>((resolve, reject) => {\n publicationWaiters.push({ targetFrame: frozen.number, resolve, reject });\n })\n : null;\n const publication = queue\n .then(() => publish(frozen))\n .catch((error) => {\n fail(error);\n return null;\n });\n queue = publication.then(() => undefined);\n return boundary ?? publication;\n } catch (error) {\n fail(error);\n return Promise.resolve(null);\n }\n },\n async flush() {\n await queue.catch(() => undefined);\n },\n stop,\n };\n}\n\nfunction hasDisplayedNodeWithoutGeometry(frame: ProbeFrame): boolean {\n return frame.objects.some(\n (object) => object.state?.displayed !== false && object.geometry?.intendedRect === undefined,\n );\n}\n\nfunction qualifyFrame(\n frozen: FrozenFrame,\n position: TerminalPosition,\n columns: number,\n rows: number,\n): ProbeFrame {\n const { capture, observation } = frozen;\n const context = capture.context as NonNullable<InkFrameCapture['context']>;\n const fullscreen = context.stdoutIsTTY && capture.liveRows >= context.rows;\n const liveOrigin = context.alternateScreen\n ? 0\n : !context.interactive\n ? position.row\n : context.debug || fullscreen\n ? position.row - Math.max(0, capture.liveRows - 1)\n : position.row - capture.liveRows;\n const staticOrigin = liveOrigin - capture.staticRows;\n\n return {\n ...observation.frame,\n objects: observation.frame.objects.map((object) => {\n const region = observation.geometryRegions.get(object.identity.value);\n const geometry = object.geometry;\n if (\n geometry?.intendedRect === undefined ||\n geometry.visibleRect === undefined ||\n region === undefined\n )\n return object;\n const origin = region === 'live' ? liveOrigin : staticOrigin;\n const intendedRect = shift(geometry.intendedRect, origin);\n const visibleRect =\n context.interactive || region === 'static' || context.debug\n ? viewportIntersection(shift(geometry.visibleRect, origin), columns, rows)\n : { row: Math.min(Math.max(origin, 0), rows), column: 0, width: 0, height: 0 };\n return { ...object, geometry: { intendedRect, visibleRect } };\n }),\n };\n}\n\nfunction shift(\n rect: import('@termwright/protocol').ProbeRect,\n rows: number,\n): import('@termwright/protocol').ProbeRect {\n return { ...rect, row: rect.row + rows };\n}\n\nfunction viewportIntersection(\n rect: import('@termwright/protocol').ProbeRect,\n columns: number,\n rows: number,\n): import('@termwright/protocol').ProbeRect {\n const column = Math.max(0, rect.column);\n const row = Math.max(0, rect.row);\n const right = Math.max(column, Math.min(columns, rect.column + rect.width));\n const bottom = Math.max(row, Math.min(rows, rect.row + rect.height));\n return { row, column, width: right - column, height: bottom - row };\n}\n\nfunction nextMacrotask(): Promise<void> {\n return new Promise((resolve) => setImmediate(resolve));\n}\n\nexport function createInkMarkerWriter(\n stream: NodeJS.WriteStream,\n options: {\n readonly certifiedHarness: boolean;\n readonly platform?: NodeJS.Platform;\n readonly writeWindowsMarker?: (fd: number, marker: string) => void;\n },\n): (marker: string) => Promise<void> {\n const platform = options.platform ?? process.platform;\n if (!options.certifiedHarness && platform === 'win32' && stream.isTTY === true) {\n const fd = (stream as NodeJS.WriteStream & { readonly fd?: unknown }).fd;\n if (typeof fd !== 'number' || !Number.isInteger(fd) || fd < 0) {\n return () =>\n Promise.reject(new Error('Ink stdout has no certifiable Windows console handle'));\n }\n const writeNative = options.writeWindowsMarker ?? writeWindowsConsoleMarker;\n return (marker) => {\n try {\n writeNative(fd, marker);\n return Promise.resolve();\n } catch (error) {\n return Promise.reject(error instanceof Error ? error : new Error(String(error)));\n }\n };\n }\n return (marker) =>\n new Promise((resolve, reject) => {\n if (stream.writableEnded || stream.destroyed) {\n reject(new Error('Ink stdout closed before the semantic render marker could be written'));\n return;\n }\n try {\n stream.write(marker, (error?: Error | null) => {\n if (error instanceof Error) reject(error);\n else resolve();\n });\n } catch (error) {\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n });\n}\n","/**\n * Termwright-owned native PTY session.\n *\n * The POSIX implementation owns the `forkpty()` master and reads it until the\n * kernel reports EOF (EIO after the queued tail on Linux). No JavaScript\n * stream, private node-pty field, quiet window, or retry decides that output\n * has ended.\n */\nimport { createRequire } from 'node:module';\nimport { getSystemErrorName } from 'node:util';\nimport {\n spawnWindowsPty,\n writeWindowsConsoleMarker as writeNativeWindowsConsoleMarker,\n windowsConPtyRuntimeInfo,\n windowsPtyAvailable,\n windowsPtyUnavailableReason,\n type WindowsConPtyRuntimeInfo,\n} from './windows.js';\nimport { NativeWriteDrainEpoch } from './write-drain-epoch.js';\n\nexport {\n encodeConPtyApplicationInput,\n encodeWin32InputModeTerminalResponse,\n} from './windows-output-normalizer.js';\n\ntype NativeEvent =\n | { readonly type: 'data'; readonly data: Buffer }\n | {\n readonly type: 'exit';\n readonly exitCode: number;\n readonly signal: number;\n }\n | { readonly type: 'eof'; readonly code: number }\n | { readonly type: 'drain'; readonly generation: bigint }\n | { readonly type: 'error'; readonly message: string; readonly code: number };\n\ninterface NativeSession {\n readonly pid: number;\n write(data: Buffer): void;\n resize(columns: number, rows: number): boolean;\n /** Zero on delivery/already-gone; otherwise the positive POSIX errno. */\n signal(signal: number): number;\n treeState(): number;\n dispose(): void;\n}\n\ninterface NativeBinding {\n new (\n options: {\n readonly command: readonly string[];\n readonly cwd?: string;\n readonly env: readonly string[];\n readonly columns: number;\n readonly rows: number;\n },\n onEvent: (event: NativeEvent) => void,\n ): NativeSession;\n}\n\nlet cachedBinding: { readonly PosixPtySession: NativeBinding } | undefined;\n\nexport function candidatePaths(\n platform: NodeJS.Platform = process.platform,\n architecture: string = process.arch,\n): readonly string[] {\n return [\n '../build/Release/termwright_pty.node',\n `@termwright/pty-${platform}-${architecture}/termwright_pty.node`,\n ];\n}\n\nexport function loadPtyBinding(): { readonly PosixPtySession: NativeBinding } {\n if (cachedBinding !== undefined) return cachedBinding;\n if (process.platform === 'win32') {\n throw new Error('the POSIX @termwright/pty binding cannot load on Windows');\n }\n if (process.platform !== 'darwin' && process.platform !== 'linux') {\n throw new Error(`@termwright/pty does not support ${process.platform}-${process.arch}`);\n }\n const require = createRequire(import.meta.url);\n const attempts: string[] = [];\n for (const candidate of candidatePaths()) {\n try {\n cachedBinding = require(candidate) as {\n readonly PosixPtySession: NativeBinding;\n };\n return cachedBinding;\n } catch (error) {\n attempts.push(\n `${candidate}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`,\n );\n }\n }\n throw new Error(\n `no Termwright PTY addon could be loaded for ${process.platform}-${process.arch}. Tried:\\n ${attempts.join('\\n ')}`,\n );\n}\n\nlet unavailableReason: string | undefined;\n\nexport function ptyAvailable(): boolean {\n if (process.platform === 'win32') return windowsPtyAvailable();\n try {\n loadPtyBinding();\n unavailableReason = undefined;\n return true;\n } catch (error) {\n unavailableReason = error instanceof Error ? error.message : String(error);\n return false;\n }\n}\n\nexport function ptyUnavailableReason(): string | undefined {\n return process.platform === 'win32' ? windowsPtyUnavailableReason() : unavailableReason;\n}\n\nexport interface PtySpawnOptions {\n readonly command: readonly string[];\n readonly cwd?: string;\n readonly env: Readonly<Record<string, string>>;\n readonly columns: number;\n readonly rows: number;\n}\n\nexport interface PtyExit {\n readonly code: number | null;\n readonly signal: string | null;\n}\n\nexport type PtySignal = 'INT' | 'TERM' | 'KILL' | 'HUP';\nexport type { WindowsConPtyRuntimeInfo };\n\n/** Runtime provenance and strict initialization status for the Windows backend. */\nexport function conPtyRuntimeInfo(): WindowsConPtyRuntimeInfo {\n if (process.platform !== 'win32') {\n throw new Error('ConPTY runtime information is only available on Windows');\n }\n return windowsConPtyRuntimeInfo();\n}\n\n/**\n * Writes a marker through WriteConsoleW while temporarily enforcing the two\n * output-mode bits required for VT control sequences. Windows probes use this\n * instead of duplicating mode mutation and restoration logic.\n */\nexport function writeWindowsConsoleMarker(fd: number, marker: string): void {\n if (process.platform !== 'win32') {\n throw new Error('Windows console markers are only available on Windows');\n }\n writeNativeWindowsConsoleMarker(fd, marker);\n}\n\nexport interface PtyHandle {\n readonly pid: number;\n readonly outputEnded: Promise<void>;\n readonly sawRealEof: boolean;\n readonly endReason: number | undefined;\n write(data: Uint8Array): void;\n writeApplicationInput?(data: Uint8Array, kind: 'key' | 'mouse' | 'paste' | 'raw'): void;\n writeTerminalResponse?(\n data: Uint8Array,\n ): 'host-control' | 'application-direct' | 'application-win32-input';\n /** Closes the parent-owned terminal input while preserving output drain. */\n closeInput?(): void;\n resize(columns: number, rows: number): boolean;\n signal(signal: PtySignal): boolean;\n treeState(): 'alive' | 'gone' | 'unsupported';\n onData(listener: (data: Uint8Array) => void): () => void;\n onExit(listener: (status: PtyExit) => void): () => void;\n onError(listener: (error: Error) => void): () => void;\n onDrain(listener: () => void): () => void;\n dispose(): void;\n}\n\nconst signalNumbers: Readonly<Record<PtySignal, number>> = Object.freeze({\n HUP: 1,\n INT: 2,\n KILL: 9,\n TERM: 15,\n});\n\nconst signalNames: Readonly<Record<number, string>> = Object.freeze({\n 1: 'SIGHUP',\n 2: 'SIGINT',\n 3: 'SIGQUIT',\n 6: 'SIGABRT',\n 9: 'SIGKILL',\n 13: 'SIGPIPE',\n 15: 'SIGTERM',\n});\n\nfunction validateOptions(options: PtySpawnOptions): void {\n if (\n options.command.length === 0 ||\n options.command.some((part) => typeof part !== 'string' || part.includes('\\0'))\n ) {\n throw new TypeError('command must be a non-empty array of NUL-free strings');\n }\n if (\n options.cwd !== undefined &&\n (typeof options.cwd !== 'string' || options.cwd.includes('\\0'))\n ) {\n throw new TypeError('cwd must be a NUL-free string');\n }\n for (const [key, value] of Object.entries(options.env)) {\n if (\n key.length === 0 ||\n key.includes('=') ||\n key.includes('\\0') ||\n typeof value !== 'string' ||\n value.includes('\\0')\n ) {\n throw new TypeError('environment keys and values must be valid execve strings');\n }\n }\n for (const [field, value] of [\n ['columns', options.columns],\n ['rows', options.rows],\n ] as const) {\n if (!Number.isInteger(value) || value < 1 || value > 32_767) {\n throw new RangeError(`${field} must be an integer from 1 through 32767`);\n }\n }\n}\n\nexport function spawnPty(options: PtySpawnOptions): PtyHandle {\n validateOptions(options);\n if (process.platform === 'win32') {\n const session = spawnWindowsPty(options);\n return {\n get pid(): number {\n return session.pid;\n },\n get sawRealEof(): boolean {\n return session.sawRealEof;\n },\n get endReason(): number | undefined {\n return session.endReason;\n },\n outputEnded: session.outputEnded,\n write(data): void {\n session.write(data);\n },\n writeApplicationInput(data, kind): void {\n session.writeApplicationInput(data, kind);\n },\n writeTerminalResponse(data) {\n return session.writeTerminalResponse(data);\n },\n closeInput(): void {\n session.closeInput();\n },\n resize(columns, rows): boolean {\n return session.resize(columns, rows);\n },\n signal(signal): boolean {\n if (signal !== 'KILL') return false;\n session.terminateTree();\n return true;\n },\n treeState(): 'alive' | 'gone' | 'unsupported' {\n const members = session.activeProcesses();\n return members < 0 ? 'unsupported' : members === 0 ? 'gone' : 'alive';\n },\n onData(listener): () => void {\n return session.onData(listener);\n },\n onExit(listener): () => void {\n return session.onExit(listener);\n },\n onError(listener): () => void {\n return session.onError(listener);\n },\n onDrain(listener): () => void {\n return session.onDrain(listener);\n },\n dispose(): void {\n session.dispose();\n },\n };\n }\n const dataListeners = new Set<(data: Uint8Array) => void>();\n const exitListeners = new Set<(status: PtyExit) => void>();\n const errorListeners = new Set<(error: Error) => void>();\n const drainListeners = new Set<() => void>();\n let exitStatus: PtyExit | undefined;\n let fatalError: Error | undefined;\n let resolveEnded: (() => void) | undefined;\n const outputEnded = new Promise<void>((resolve) => {\n resolveEnded = resolve;\n });\n let ended = false;\n let endReason: number | undefined;\n let disposed = false;\n const writeEpoch = new NativeWriteDrainEpoch();\n\n const session = new (loadPtyBinding().PosixPtySession)(\n {\n command: [...options.command],\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n env: Object.entries(options.env).map(([key, value]) => `${key}=${value}`),\n columns: options.columns,\n rows: options.rows,\n },\n (event) => {\n switch (event.type) {\n case 'data':\n for (const listener of [...dataListeners]) listener(event.data);\n return;\n case 'exit': {\n exitStatus =\n event.signal === 0\n ? { code: event.exitCode, signal: null }\n : {\n code: null,\n signal: signalNames[event.signal] ?? `SIG${event.signal}`,\n };\n for (const listener of [...exitListeners]) listener(exitStatus);\n return;\n }\n case 'eof':\n endReason = event.code;\n ended = event.code === 0;\n resolveEnded?.();\n return;\n case 'drain':\n if (!writeEpoch.isCurrent(event.generation)) return;\n for (const listener of [...drainListeners]) listener();\n return;\n case 'error': {\n const code = getSystemErrorName(-event.code);\n const guidance =\n code === 'EMFILE'\n ? \" Raise this process's open-file limit (for example with `ulimit -n`) and retry.\"\n : code === 'ENFILE'\n ? ' The host-wide open-file table is exhausted; raise the system limit or reduce concurrent processes.'\n : '';\n fatalError ??= Object.assign(new Error(`${event.message}${guidance}`), {\n code,\n errno: event.code,\n });\n for (const listener of [...errorListeners]) listener(fatalError);\n return;\n }\n }\n },\n );\n\n return {\n get pid(): number {\n return session.pid;\n },\n get sawRealEof(): boolean {\n return ended;\n },\n get endReason(): number | undefined {\n return endReason;\n },\n outputEnded,\n write(data): void {\n if (disposed) throw new Error('PTY input is closed');\n const bytes = Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n writeEpoch.admit(bytes, (admitted) => session.write(admitted));\n },\n resize(columns, rows): boolean {\n if (\n !Number.isInteger(columns) ||\n !Number.isInteger(rows) ||\n columns < 1 ||\n rows < 1 ||\n columns > 32_767 ||\n rows > 32_767\n )\n return false;\n return !disposed && session.resize(columns, rows);\n },\n signal(signal): boolean {\n if (disposed) return false;\n const code = session.signal(signalNumbers[signal]);\n if (code === 0) return true;\n throw Object.assign(new Error(`kill(PTY process group) failed with errno ${code}`), {\n code: getSystemErrorName(-code),\n errno: code,\n });\n },\n treeState(): 'alive' | 'gone' | 'unsupported' {\n const state = disposed ? -1 : session.treeState();\n return state > 0 ? 'alive' : state === 0 ? 'gone' : 'unsupported';\n },\n onData(listener): () => void {\n dataListeners.add(listener);\n return () => dataListeners.delete(listener);\n },\n onExit(listener): () => void {\n exitListeners.add(listener);\n const observed = exitStatus;\n if (observed !== undefined)\n queueMicrotask(() => {\n if (exitListeners.has(listener)) listener(observed);\n });\n return () => exitListeners.delete(listener);\n },\n onError(listener): () => void {\n errorListeners.add(listener);\n const observed = fatalError;\n if (observed !== undefined)\n queueMicrotask(() => {\n if (errorListeners.has(listener)) listener(observed);\n });\n return () => errorListeners.delete(listener);\n },\n onDrain(listener): () => void {\n drainListeners.add(listener);\n return () => drainListeners.delete(listener);\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n session.dispose();\n resolveEnded?.();\n dataListeners.clear();\n exitListeners.clear();\n errorListeners.clear();\n drainListeners.clear();\n },\n };\n}\n","/**\n * Termwright's Windows PTY backend.\n *\n * One native session owns the pseudoconsole, both host pipe ends, the root\n * process and thread, and the job object holding the tree. Two facts follow\n * from that ownership and are the reason this package exists:\n *\n * - a session ends when the output pipe actually ends, never when a timer says\n * nothing has arrived lately;\n * - the process tree is a job object from before the root can run, so proving\n * it empty is a query rather than a race against process enumeration.\n */\n\nimport { createRequire } from 'node:module';\nimport { NativeWriteDrainEpoch } from './write-drain-epoch.js';\nimport {\n ConPtyControlPlaneNormalizer,\n ConPtyTerminalResponseRouter,\n ConPtyTerminalResponseTransport,\n encodeConPtyApplicationInput,\n type ConPtyTerminalResponseRoute,\n} from './windows-output-normalizer.js';\n\n/** Ordered messages the native session emits. Data always precedes the end. */\ntype NativeEvent =\n | { readonly type: 'data'; readonly data: Buffer }\n | { readonly type: 'exit'; readonly exitCode: number }\n | { readonly type: 'eof'; readonly code: number }\n | { readonly type: 'drain'; readonly generation: bigint }\n | { readonly type: 'notice'; readonly message: string }\n | { readonly type: 'error'; readonly message: string; readonly code: number };\n\ninterface NativeSession {\n readonly pid: number;\n write(data: Buffer): void;\n closeInput(): void;\n resize(columns: number, rows: number): boolean;\n terminateTree(): void;\n activeProcesses(): number;\n dispose(): void;\n}\n\ninterface NativeBindingConstructor {\n new (\n options: {\n readonly commandLine: string;\n readonly cwd?: string;\n readonly env?: readonly string[];\n readonly columns: number;\n readonly rows: number;\n },\n onEvent: (event: NativeEvent) => void,\n ): NativeSession;\n}\n\ninterface LoadedWindowsBinding {\n readonly ConPtySession: NativeBindingConstructor;\n conPtyRuntimeInfo(): WindowsConPtyRuntimeInfo;\n writeWindowsConsoleMarker(fd: number, marker: string): void;\n}\n\n/** Provenance and behavioral contract of the loaded Windows pseudoconsole. */\nexport interface WindowsConPtyRuntimeInfo {\n readonly provider: 'termwright-patched-openconsole';\n readonly upstreamCommit: 'dd494ac79a82a04e1e7252a91c8939a3c3039908';\n readonly patchSha256: '839ff6fb8c2d3490ee8ccd1f20310baa315475fa187b4967e5e940fa98610d1c';\n readonly hostCursorRpc: 'twh-cpr-v1';\n readonly mode: 'ordered-vt-passthrough';\n readonly policy: 'strict';\n readonly selectedHostArchitecture: '' | 'x64' | 'arm64';\n readonly failureCode: string;\n readonly failureWin32: number;\n readonly assetsValidated: boolean;\n readonly coreExports: boolean;\n readonly orderedMarkerSemantics: 'marker-authoritative-after-behavioral-certification';\n}\n\nfunction assertRuntimeInfoShape(value: WindowsConPtyRuntimeInfo): WindowsConPtyRuntimeInfo {\n if (\n value.provider !== 'termwright-patched-openconsole' ||\n value.upstreamCommit !== 'dd494ac79a82a04e1e7252a91c8939a3c3039908' ||\n value.patchSha256 !== '839ff6fb8c2d3490ee8ccd1f20310baa315475fa187b4967e5e940fa98610d1c' ||\n value.hostCursorRpc !== 'twh-cpr-v1' ||\n Object.hasOwn(value, 'package') ||\n Object.hasOwn(value, 'version') ||\n value.mode !== 'ordered-vt-passthrough' ||\n value.policy !== 'strict' ||\n (value.selectedHostArchitecture !== '' &&\n value.selectedHostArchitecture !== 'x64' &&\n value.selectedHostArchitecture !== 'arm64') ||\n typeof value.failureCode !== 'string' ||\n typeof value.failureWin32 !== 'number' ||\n typeof value.assetsValidated !== 'boolean' ||\n typeof value.coreExports !== 'boolean' ||\n value.orderedMarkerSemantics !== 'marker-authoritative-after-behavioral-certification'\n ) {\n throw new Error(`invalid vendored ConPTY capability report: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\nfunction assertCertifiedRuntimeInfo(value: WindowsConPtyRuntimeInfo): WindowsConPtyRuntimeInfo {\n assertRuntimeInfoShape(value);\n if (\n (value.selectedHostArchitecture !== 'x64' && value.selectedHostArchitecture !== 'arm64') ||\n value.failureCode !== '' ||\n value.failureWin32 !== 0 ||\n value.assetsValidated !== true ||\n value.coreExports !== true\n ) {\n throw new Error(`uncertified vendored ConPTY runtime: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\nlet cachedBinding: LoadedWindowsBinding | undefined;\nlet cachedDiagnosticBinding: LoadedWindowsBinding | undefined;\n\n/**\n * Where the addon is looked for, in order.\n *\n * The locally compiled binary comes first so that a working tree tests what it\n * just built rather than a published prebuild that happens to be installed\n * beside it — the alternative is a change to this addon that CI certifies\n * against the previous release.\n */\nexport function windowsCandidatePaths(architecture: string): readonly string[] {\n return [\n '../build/Release/termwright_pty.node',\n `@termwright/pty-win32-${architecture}/termwright_pty.node`,\n ];\n}\n\n/** Loads the compiled addon, or explains why this platform has none. */\nexport function loadWindowsBinding(): LoadedWindowsBinding {\n if (cachedBinding !== undefined) return cachedBinding;\n if (process.platform !== 'win32') {\n throw new Error('@termwright/pty Windows binding cannot load on a non-Windows host');\n }\n const require = createRequire(import.meta.url);\n const attempts: string[] = [];\n for (const candidate of windowsCandidatePaths(process.arch)) {\n try {\n const resolved = require.resolve(candidate);\n const loaded = require(resolved) as LoadedWindowsBinding;\n assertCertifiedRuntimeInfo(loaded.conPtyRuntimeInfo());\n cachedBinding = loaded;\n return cachedBinding;\n } catch (error) {\n // Kept per candidate. \"No addon\" is the same sentence whether the\n // prebuild for this architecture was never published, the install\n // skipped it, or it is present and failed to load — and those are three\n // different things to do next.\n attempts.push(\n `${candidate}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`,\n );\n }\n }\n throw new Error(\n `no termwright ConPTY addon could be loaded for win32-${process.arch}. Tried:\\n ${attempts.join('\\n ')}`,\n );\n}\n\n/**\n * Returns a capability report even when strict runtime initialization failed.\n * A healthy candidate is preferred over an earlier diagnostic-only candidate.\n */\nexport function windowsConPtyRuntimeInfo(): WindowsConPtyRuntimeInfo {\n if (cachedBinding !== undefined) {\n return assertRuntimeInfoShape(cachedBinding.conPtyRuntimeInfo());\n }\n if (process.platform !== 'win32') {\n throw new Error('@termwright/pty Windows binding cannot load on a non-Windows host');\n }\n const require = createRequire(import.meta.url);\n const attempts: string[] = [];\n for (const candidate of windowsCandidatePaths(process.arch)) {\n try {\n const resolved = require.resolve(candidate);\n const loaded = require(resolved) as LoadedWindowsBinding;\n const runtime = assertRuntimeInfoShape(loaded.conPtyRuntimeInfo());\n if (\n runtime.failureCode === '' &&\n runtime.failureWin32 === 0 &&\n runtime.assetsValidated &&\n runtime.coreExports\n ) {\n cachedBinding = loaded;\n return runtime;\n }\n cachedDiagnosticBinding ??= loaded;\n } catch (error) {\n attempts.push(\n `${candidate}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`,\n );\n }\n }\n if (cachedDiagnosticBinding !== undefined) {\n return assertRuntimeInfoShape(cachedDiagnosticBinding.conPtyRuntimeInfo());\n }\n throw new Error(\n `no termwright ConPTY addon could be loaded for win32-${process.arch}. Tried:\\n ${attempts.join('\\n ')}`,\n );\n}\n\nlet unavailableReason: string | undefined;\n\n/** True when the addon is present and usable in this process. */\nexport function windowsPtyAvailable(): boolean {\n try {\n loadWindowsBinding();\n unavailableReason = undefined;\n return true;\n } catch (error) {\n // Kept, because \"not available\" is the least useful half of the answer.\n // A missing file, an ABI mismatch and a load-time failure inside the addon\n // all arrive here, and they are three different pieces of work.\n unavailableReason = error instanceof Error ? error.message : String(error);\n return false;\n }\n}\n\n/** Why the addon could not be loaded, as the loader reported it. */\nexport function windowsPtyUnavailableReason(): string | undefined {\n return unavailableReason;\n}\n\n/**\n * Writes an in-band marker to a real Windows console without inheriting a\n * framework's possibly-disabled VT output mode.\n *\n * The native primitive restores the exact original console mode before it\n * returns or throws. A successful return means WriteConsoleW accepted every\n * UTF-16 code unit synchronously.\n */\nexport function writeWindowsConsoleMarker(fd: number, marker: string): void {\n if (!Number.isInteger(fd) || fd < 0) {\n throw new RangeError('Windows console marker fd must be a non-negative integer');\n }\n if (typeof marker !== 'string' || marker.length === 0) {\n throw new TypeError('Windows console marker must be a non-empty string');\n }\n loadWindowsBinding().writeWindowsConsoleMarker(fd, marker);\n}\n\n/**\n * Quotes one argument the way CommandLineToArgvW parses it.\n *\n * Windows has no argv: the child re-parses a single string, so the caller's\n * exact arguments only survive if they are quoted to that specific grammar.\n * Backslashes are literal except immediately before a quote, where they are\n * doubled — including the run that precedes the closing quote.\n */\nexport function quoteWindowsArgument(argument: string): string {\n if (argument.length > 0 && !/[\\s\"]/u.test(argument)) return argument;\n let quoted = '\"';\n let backslashes = 0;\n for (const character of argument) {\n if (character === '\\\\') {\n backslashes += 1;\n continue;\n }\n if (character === '\"') {\n quoted += '\\\\'.repeat(backslashes * 2 + 1);\n quoted += '\"';\n backslashes = 0;\n continue;\n }\n quoted += '\\\\'.repeat(backslashes);\n quoted += character;\n backslashes = 0;\n }\n quoted += '\\\\'.repeat(backslashes * 2);\n return `${quoted}\"`;\n}\n\n/** Joins a command into the single string CreateProcessW takes. */\nexport function buildCommandLine(command: readonly string[]): string {\n if (command.length === 0) throw new TypeError('a ConPTY command needs at least an executable');\n return command.map(quoteWindowsArgument).join(' ');\n}\n\n/** Renders an environment map as the block CreateProcessW expects. */\nexport function buildEnvironment(env: Readonly<Record<string, string>>): readonly string[] {\n return Object.entries(env)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => `${key}=${value}`);\n}\n\nexport interface WindowsPtySpawnOptions {\n readonly command: readonly string[];\n readonly cwd?: string;\n readonly env: Readonly<Record<string, string>>;\n readonly columns: number;\n readonly rows: number;\n}\n\nexport interface WindowsPtyExit {\n readonly code: number | null;\n readonly signal: string | null;\n}\n\n/**\n * A live ConPTY session.\n *\n * `exited` and `outputEnded` are deliberately separate. A root process can\n * finish while its descendants still hold the pseudoconsole, so the last byte\n * of a session routinely arrives after the process that started it is gone.\n */\nexport interface WindowsPtyHandle {\n readonly pid: number;\n readonly runtimeInfo: WindowsConPtyRuntimeInfo;\n readonly outputEnded: Promise<void>;\n /**\n * True only when the output pipe actually ended.\n *\n * `outputEnded` also settles on disposal so a teardown cannot hang, which\n * means resolving it is not by itself evidence of EOF. This is the flag that\n * separates the two, and nothing but the reader sets it.\n *\n * The end itself is reached by the tree emptying: the job reports zero\n * active processes, which means no byte can follow, and the console is\n * closed only then. The reader still ends on the pipe rather than on a\n * timer — what changed is that the moment is chosen by evidence.\n */\n readonly sawRealEof: boolean;\n /**\n * The Win32 code the terminating read reported, or 0 for a clean end.\n *\n * A stream that ended for the wrong reason looks exactly like one that ended\n * properly, and telling them apart is the claim this backend exists to make.\n */\n readonly endReason: number | undefined;\n /**\n * The session's own account of its lifecycle, oldest first.\n *\n * Root exit, what the job said, and when the console was closed. These\n * moments are only observable while they happen: the console takes its\n * evidence with it when it goes, so anything reconstructed afterwards is\n * inference. Kept bounded, because a session is not a log file.\n */\n readonly notices: readonly string[];\n write(data: Uint8Array): void;\n /**\n * Stops further terminal input and closes only that pipe side. The process,\n * pseudoconsole and authoritative output stream stay alive.\n */\n closeInput(): void;\n writeApplicationInput(data: Uint8Array, kind: 'key' | 'mouse' | 'paste' | 'raw'): void;\n writeTerminalResponse(data: Uint8Array): ConPtyTerminalResponseRoute;\n resize(columns: number, rows: number): boolean;\n terminateTree(): void;\n activeProcesses(): number;\n onData(listener: (data: Uint8Array) => void): () => void;\n onExit(listener: (status: WindowsPtyExit) => void): () => void;\n onError(listener: (error: Error) => void): () => void;\n onDrain(listener: () => void): () => void;\n /**\n * Lifecycle notices as they are recorded.\n *\n * A notice describing an instant arrives after the event it follows, so\n * reading `notices` inside an exit listener sees the state before it. This\n * is how a caller waits for the account rather than racing it.\n */\n onNotice(listener: (message: string) => void): () => void;\n dispose(): void;\n}\n\nexport function spawnWindowsPty(options: WindowsPtySpawnOptions): WindowsPtyHandle {\n const binding = loadWindowsBinding();\n const dataListeners = new Set<(data: Uint8Array) => void>();\n const exitListeners = new Set<(status: WindowsPtyExit) => void>();\n const errorListeners = new Set<(error: Error) => void>();\n const drainListeners = new Set<() => void>();\n const noticeListeners = new Set<(message: string) => void>();\n\n let resolveEnded: (() => void) | undefined;\n const outputEnded = new Promise<void>((resolve) => {\n resolveEnded = resolve;\n });\n let ended = false;\n let endReason: number | undefined;\n let disposed = false;\n let inputClosed = false;\n const writeEpoch = new NativeWriteDrainEpoch();\n const terminalResponseRouter = new ConPtyTerminalResponseRouter();\n const terminalResponseTransport = new ConPtyTerminalResponseTransport();\n const outputNormalizer = new ConPtyControlPlaneNormalizer((query) =>\n terminalResponseRouter.noteHostQuery(query),\n );\n const notices: string[] = [];\n const NOTICE_LIMIT = 64;\n\n const write = (data: Uint8Array): void => {\n if (disposed || inputClosed) throw new Error('ConPTY input is closed');\n const bytes = Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n writeEpoch.admit(bytes, (admitted) => session.write(admitted));\n };\n\n const session = new binding.ConPtySession(\n {\n commandLine: buildCommandLine(options.command),\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n env: buildEnvironment(options.env),\n columns: options.columns,\n rows: options.rows,\n },\n (event) => {\n switch (event.type) {\n case 'data':\n {\n const data = outputNormalizer.push(event.data);\n if (data.length > 0) {\n for (const listener of [...dataListeners]) listener(data);\n }\n }\n return;\n case 'exit': {\n // A Windows exit code is not a signal. Reporting it as one would\n // invent POSIX semantics the platform does not have.\n const status: WindowsPtyExit = { code: event.exitCode, signal: null };\n for (const listener of [...exitListeners]) listener(status);\n return;\n }\n case 'eof':\n {\n // A possible prefix withheld across a ReadFile boundary is child\n // output unless the rest of the host structure actually arrives.\n // Release it on the same ordered channel, before authoritative EOF.\n const tail = outputNormalizer.finish();\n if (tail.length > 0) {\n for (const listener of [...dataListeners]) listener(tail);\n }\n }\n endReason = event.code;\n // Delivered on the same ordered channel as the data before it, so\n // every chunk has already reached its listeners by now.\n ended = true;\n resolveEnded?.();\n return;\n case 'drain':\n if (!writeEpoch.isCurrent(event.generation)) return;\n for (const listener of [...drainListeners]) listener();\n return;\n case 'notice':\n // Oldest dropped first: a session that somehow produces more of\n // these than the bound must not grow without limit, and the last\n // ones are the ones that describe how it ended.\n if (notices.length >= NOTICE_LIMIT) notices.shift();\n notices.push(event.message);\n for (const listener of [...noticeListeners]) listener(event.message);\n return;\n case 'error': {\n const failure = Object.assign(new Error(event.message), {\n win32: event.code,\n });\n for (const listener of [...errorListeners]) listener(failure);\n return;\n }\n }\n },\n );\n\n return {\n get pid(): number {\n return session.pid;\n },\n get runtimeInfo(): WindowsConPtyRuntimeInfo {\n return windowsConPtyRuntimeInfo();\n },\n get sawRealEof(): boolean {\n return ended;\n },\n get endReason(): number | undefined {\n return endReason;\n },\n get notices(): readonly string[] {\n return [...notices];\n },\n outputEnded,\n write(data: Uint8Array): void {\n write(data);\n },\n closeInput(): void {\n if (disposed || inputClosed) return;\n inputClosed = true;\n session.closeInput();\n },\n writeApplicationInput(data, kind): void {\n write(encodeConPtyApplicationInput(data, kind));\n },\n writeTerminalResponse(data: Uint8Array): ConPtyTerminalResponseRoute {\n const route = terminalResponseRouter.route(data);\n write(terminalResponseTransport.encode(route, data));\n return route;\n },\n resize(columns: number, rows: number): boolean {\n return disposed ? false : session.resize(columns, rows);\n },\n terminateTree(): void {\n if (!disposed) session.terminateTree();\n },\n activeProcesses(): number {\n return disposed ? -1 : session.activeProcesses();\n },\n onData(listener): () => void {\n dataListeners.add(listener);\n return () => dataListeners.delete(listener);\n },\n onExit(listener): () => void {\n exitListeners.add(listener);\n return () => exitListeners.delete(listener);\n },\n onError(listener): () => void {\n errorListeners.add(listener);\n return () => errorListeners.delete(listener);\n },\n onDrain(listener): () => void {\n drainListeners.add(listener);\n return () => drainListeners.delete(listener);\n },\n onNotice(listener): () => void {\n noticeListeners.add(listener);\n return () => noticeListeners.delete(listener);\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n session.dispose();\n // Disposal is not evidence of EOF. It unblocks anyone waiting only so a\n // teardown cannot hang; whether the stream truly ended is recorded by\n // `ended`, which nothing but the reader sets.\n resolveEnded?.();\n dataListeners.clear();\n exitListeners.clear();\n errorListeners.clear();\n drainListeners.clear();\n noticeListeners.clear();\n },\n };\n}\n","/** Keeps native drain edges tied to the writes they actually completed. */\nexport class NativeWriteDrainEpoch {\n #generation = 0n;\n\n admit<T extends Uint8Array>(data: T, write: (data: T) => void): void {\n // Advance only after native admission succeeds. Rejected and zero-length\n // writes therefore leave the JavaScript and native generations aligned.\n write(data);\n if (data.byteLength > 0) this.#generation += 1n;\n }\n\n isCurrent(generation: bigint): boolean {\n return generation === this.#generation;\n }\n}\n","/**\n * Removes control-plane modes injected by the vendored ConPTY host.\n *\n * The passthrough ConPTY deliberately enables focus and Win32 input modes for\n * its own input transport. Those bytes describe the host, not the child, and\n * must not reach the terminal emulator as application-owned mode evidence.\n * The transform is byte based: VT control sequences are ASCII and can be\n * divided at any byte by the anonymous output pipe.\n */\n\nimport { parseConPtyHostCursorResponse } from '@termwright/protocol';\n\nconst ESC = 0x1b;\n\nconst bytes = (...values: number[]): Buffer => Buffer.from(values);\n\nconst DA1 = bytes(ESC, 0x5b, 0x63);\nconst WINDOW_DEICONIFY = bytes(ESC, 0x5b, 0x31, 0x74);\nconst WINDOW_ICONIFY = bytes(ESC, 0x5b, 0x32, 0x74);\nconst FOCUS_ON = bytes(ESC, 0x5b, 0x3f, 0x31, 0x30, 0x30, 0x34, 0x68);\nconst FOCUS_OFF = bytes(ESC, 0x5b, 0x3f, 0x31, 0x30, 0x30, 0x34, 0x6c);\nconst WIN32_ON = bytes(ESC, 0x5b, 0x3f, 0x39, 0x30, 0x30, 0x31, 0x68);\nconst WIN32_OFF = bytes(ESC, 0x5b, 0x3f, 0x39, 0x30, 0x30, 0x31, 0x6c);\nconst RIS = bytes(ESC, 0x63);\nconst HOST_CURSOR_REQUEST_PREFIX = Buffer.from('\\x1b]8488;twh-cpr-v1:q:', 'ascii');\nconst HOST_CURSOR_REQUEST_TOKEN_BYTES = 32;\nconst HOST_CURSOR_REPLY_NAMESPACE = Buffer.from('\\x1b]8488;twh-cpr-v1:r:', 'ascii');\n\ninterface Rewrite {\n readonly input: Buffer;\n readonly output: Buffer;\n readonly hostQueries?: readonly ConPtyHostQuery[];\n}\n\nexport type ConPtyHostQuery = 'primary-device-attributes';\nexport type ConPtyTerminalResponseRoute = 'host-control' | 'application-win32-input';\n\nexport function encodeWin32InputModeTerminalResponse(data: Uint8Array): Buffer {\n let encoded = '';\n for (const byte of data) {\n if (byte > 0x7f) {\n throw new TypeError(\n `terminal response contains non-ASCII byte 0x${byte.toString(16).padStart(2, '0')}`,\n );\n }\n encoded += `\\u001b[0;0;${byte};1;0;1_`;\n }\n return Buffer.from(encoded, 'ascii');\n}\n\nexport function encodeConPtyApplicationInput(\n data: Uint8Array,\n kind: 'key' | 'mouse' | 'paste' | 'raw',\n): Buffer {\n // Preserve the complete physical Escape event. Reusing the byte-oriented\n // terminal-response encoder would deliver UnicodeChar=ESC but erase the\n // VK/scan identity observed by ReadConsoleInput applications.\n return kind === 'key' && data.byteLength === 1 && data[0] === 0x1b\n ? Buffer.from('\\x1b[27;1;27;1;0;1_', 'ascii')\n : Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n}\n\nexport class ConPtyTerminalResponseTransport {\n encode(route: ConPtyTerminalResponseRoute, data: Uint8Array): Buffer {\n if (route === 'application-win32-input') {\n return encodeWin32InputModeTerminalResponse(data);\n }\n return Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n }\n}\n\nfunction isHostResponse(response: Buffer): boolean {\n const text = response.toString('ascii');\n return /^\\x1b\\[\\?[\\d;]*c$/u.test(text);\n}\n\n/**\n * Preserves the ownership of startup queries emitted by ConPTY itself.\n *\n * Host-control replies must be written as raw VT so OpenConsole consumes\n * them. Application replies must use Win32 Input Mode so they reach the\n * child as terminal protocol bytes. The queue is populated by the same\n * split-safe startup rewrite that exposes each query to the emulator.\n */\nexport class ConPtyTerminalResponseRouter {\n readonly #hostQueries: ConPtyHostQuery[] = [];\n\n noteHostQuery(query: ConPtyHostQuery): void {\n this.#hostQueries.push(query);\n }\n\n route(response: Uint8Array): ConPtyTerminalResponseRoute {\n const bytes = Buffer.from(response.buffer, response.byteOffset, response.byteLength);\n const query = this.#hostQueries[0];\n // Cursor synchronization is a private request-addressed OSC RPC in the\n // pinned host. Standard CPR is therefore always application-owned and can\n // never be stolen by a host capture state.\n if (parseConPtyHostCursorResponse(bytes) !== null) return 'host-control';\n // The versioned reply prefix is reserved to the pinned host. A stale or\n // malformed reply must still travel raw to OpenConsole, which consumes it\n // fail-closed; encoding it as W32IM would expose host control bytes to the\n // application input queue. Other OSC 8488 payloads remain application-owned.\n if (bytes.subarray(0, HOST_CURSOR_REPLY_NAMESPACE.length).equals(HOST_CURSOR_REPLY_NAMESPACE)) {\n return 'host-control';\n }\n if (query === undefined) return 'application-win32-input';\n if (!isHostResponse(bytes)) {\n throw new Error(`terminal answered ${query} ConPTY host query with an unexpected response`);\n }\n this.#hostQueries.shift();\n return 'host-control';\n }\n}\n\nconst STARTUP_REWRITES: readonly Rewrite[] = [\n {\n // VtIo's ordinary startup handshake.\n input: Buffer.concat([DA1, FOCUS_ON, WIN32_ON]),\n output: DA1,\n hostQueries: ['primary-device-attributes'],\n },\n];\n\ntype StartupPassThrough =\n | { readonly kind: 'complete'; readonly length: number }\n | {\n readonly kind: 'partial';\n }\n | null;\n\nfunction startupPassThrough(input: Buffer, offset: number): StartupPassThrough {\n for (const fixed of [WINDOW_DEICONIFY, WINDOW_ICONIFY]) {\n if (!hasPrefixAt(input, offset, fixed)) continue;\n return input.length - offset < fixed.length\n ? { kind: 'partial' }\n : { kind: 'complete', length: fixed.length };\n }\n if (!hasPrefixAt(input, offset, HOST_CURSOR_REQUEST_PREFIX)) return null;\n const remaining = input.length - offset;\n if (remaining < HOST_CURSOR_REQUEST_PREFIX.length) return { kind: 'partial' };\n const tokenStart = offset + HOST_CURSOR_REQUEST_PREFIX.length;\n const availableToken = Math.min(HOST_CURSOR_REQUEST_TOKEN_BYTES, input.length - tokenStart);\n for (let index = 0; index < availableToken; index += 1) {\n const byte = input[tokenStart + index]!;\n const hexadecimal = (byte >= 0x30 && byte <= 0x39) || (byte >= 0x61 && byte <= 0x66);\n if (!hexadecimal) return null;\n }\n const total = HOST_CURSOR_REQUEST_PREFIX.length + HOST_CURSOR_REQUEST_TOKEN_BYTES + 1;\n if (remaining < total) return { kind: 'partial' };\n if (input[offset + total - 1] !== 0x07) return null;\n return { kind: 'complete', length: total };\n}\n\nconst HOST_REWRITES: readonly Rewrite[] = [\n // AdaptDispatch reinjects these immediately after the child reset that\n // caused them. Keeping the reset preserves the child's original bytes.\n { input: Buffer.concat([FOCUS_OFF, FOCUS_ON]), output: FOCUS_OFF },\n { input: Buffer.concat([WIN32_OFF, WIN32_ON]), output: WIN32_OFF },\n { input: Buffer.concat([RIS, FOCUS_ON, WIN32_ON]), output: RIS },\n];\n\nfunction hasPrefixAt(input: Buffer, offset: number, pattern: Buffer): boolean {\n const available = Math.min(input.length - offset, pattern.length);\n for (let index = 0; index < available; index += 1) {\n if (input[offset + index] !== pattern[index]) return false;\n }\n return true;\n}\n\n/**\n * A deterministic streaming transducer for one ConPTY output stream.\n *\n * `push()` may retain only a suffix which could still become a host rewrite.\n * `finish()` releases that suffix verbatim, so a truncated or merely similar\n * child sequence is never lost at authoritative EOF.\n */\nexport class ConPtyControlPlaneNormalizer {\n #pending = Buffer.alloc(0);\n #atStreamStart = true;\n #finished = false;\n\n constructor(readonly onHostQuery: (query: ConPtyHostQuery) => void = () => undefined) {}\n\n push(chunk: Uint8Array): Buffer {\n if (this.#finished) {\n throw new Error('ConPTY output arrived after authoritative EOF');\n }\n if (chunk.byteLength === 0) return Buffer.alloc(0);\n\n const incoming = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n const input = this.#pending.length === 0 ? incoming : Buffer.concat([this.#pending, incoming]);\n this.#pending = Buffer.alloc(0);\n\n const output: Buffer[] = [];\n let literalStart = 0;\n let offset = 0;\n\n while (offset < input.length) {\n if (this.#atStreamStart) {\n const passThrough = startupPassThrough(input, offset);\n if (passThrough?.kind === 'partial') {\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n this.#pending = Buffer.from(input.subarray(offset));\n return output.length === 0 ? Buffer.alloc(0) : Buffer.concat(output);\n }\n if (passThrough?.kind === 'complete') {\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n output.push(input.subarray(offset, offset + passThrough.length));\n offset += passThrough.length;\n literalStart = offset;\n continue;\n }\n }\n const rewrites = this.#atStreamStart\n ? [...STARTUP_REWRITES, ...HOST_REWRITES]\n : HOST_REWRITES;\n let rewritten = false;\n let awaitingSuffix = false;\n\n for (const rewrite of rewrites) {\n if (!hasPrefixAt(input, offset, rewrite.input)) continue;\n const remaining = input.length - offset;\n if (remaining < rewrite.input.length) {\n awaitingSuffix = true;\n break;\n }\n\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n output.push(rewrite.output);\n for (const query of rewrite.hostQueries ?? []) this.onHostQuery(query);\n offset += rewrite.input.length;\n literalStart = offset;\n this.#atStreamStart = false;\n rewritten = true;\n break;\n }\n\n if (rewritten) continue;\n if (awaitingSuffix) {\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n this.#pending = Buffer.from(input.subarray(offset));\n return output.length === 0 ? Buffer.alloc(0) : Buffer.concat(output);\n }\n\n // This byte cannot begin any host-owned structure. It is child output.\n this.#atStreamStart = false;\n offset += 1;\n }\n\n if (literalStart < input.length) output.push(input.subarray(literalStart));\n return output.length === 0 ? Buffer.alloc(0) : Buffer.concat(output);\n }\n\n finish(): Buffer {\n if (this.#finished) return Buffer.alloc(0);\n this.#finished = true;\n const tail = this.#pending;\n this.#pending = Buffer.alloc(0);\n return tail;\n }\n}\n","/**\n * Probe IR → semantic tree.\n *\n * The probe reports what it saw; this decides what it means. Keeping the two\n * apart is what lets six frameworks disagree about what is knowable without\n * that disagreement leaking into the tree — and it is why this module is a pure\n * function over data, testable without a process, a framework or a socket.\n *\n * Merge precedence, from decision D6: **annotation > recognizer > framework >\n * correlation > heuristic**, with one exception that matters more than the\n * order does — physical facts are never overridden by an annotation. An author\n * may name a thing; an author may not declare where it is on screen, whether it\n * has focus, or whether it is visible.\n */\n\nimport {\n DEFAULT_LIMITS,\n evidence,\n SEMANTIC_ROLES,\n type ProbeFrame,\n type ProbeObject,\n type Observation,\n type ProvenanceSource,\n type Rect,\n type SemanticNode,\n type SemanticRole,\n type SemanticSnapshot,\n type SemanticState,\n} from '@termwright/protocol';\nimport { namesFromContent, normalizeName } from './naming.js';\nimport { roleForInkAria, roleForInkHost } from './ink.js';\nimport { roleForOpenTuiClass } from './opentui.js';\n\n/** Everything the tree needs that a frame does not carry. */\nexport interface RecognizeContext {\n readonly sessionId: string;\n readonly revision: number;\n readonly columns: number;\n readonly rows: number;\n /** Which framework produced the frame; selects the level-2 role map. */\n readonly framework: string;\n /** Whether the probe reported paint order, which decides occlusion. */\n readonly paintOrderKnown?: boolean;\n /** Bound for a name correlated from descendant text. */\n readonly maxStringBytes?: number;\n}\n\nconst ROLES: ReadonlySet<string> = new Set(SEMANTIC_ROLES);\nconst UTF8_ENCODER = new TextEncoder();\n\n/** Level-2 maps, by framework. Others land on `generic`, which is legitimate. */\nconst ROLE_MAPS: Readonly<Record<string, (frameworkType: string) => SemanticRole | undefined>> =\n Object.freeze({ ink: roleForInkHost, opentui: roleForOpenTuiClass });\n\n/**\n * Resolve a role, in the normative order.\n *\n * An annotation that names a role outside the closed set is **not** silently\n * dropped: it falls through to the framework map, and the bad value stays\n * visible in the annotations rather than becoming an invented role.\n */\nfunction resolveRole(\n object: ProbeObject,\n framework: string,\n): {\n role: SemanticRole;\n source: ProvenanceSource;\n} {\n const annotated = object.annotations?.role;\n if (annotated !== undefined && ROLES.has(annotated)) {\n return { role: annotated as SemanticRole, source: 'annotation' };\n }\n if (framework === 'ink' && object.accessibility?.role !== undefined) {\n const mapped = roleForInkAria(object.accessibility.role);\n if (mapped !== undefined) return { role: mapped, source: 'framework' };\n }\n const mapped = ROLE_MAPS[framework]?.(object.frameworkType);\n if (mapped !== undefined) return { role: mapped, source: 'recognizer' };\n return { role: 'generic', source: 'recognizer' };\n}\n\n/**\n * Resolve a name.\n *\n * An annotation wins, including a deliberate empty string. Otherwise the text\n * the probe observed is used **only** for roles that take their name from\n * content; everything else keeps an empty name rather than absorbing the label\n * of something inside it.\n */\nfunction resolveName(\n object: ProbeObject,\n role: SemanticRole,\n subtreeText: ReadonlyMap<string, string>,\n): { name: string; source: ProvenanceSource } {\n const annotated = object.annotations?.name;\n if (annotated !== undefined) return { name: annotated, source: 'annotation' };\n if (object.accessibility?.name !== undefined) {\n return { name: object.accessibility.name, source: 'framework' };\n }\n if (namesFromContent(role) && object.text !== undefined) {\n return { name: normalizeName(object.text), source: 'framework' };\n }\n if (namesFromContent(role)) {\n const correlated = subtreeText.get(object.identity.value);\n if (correlated !== undefined && correlated.length > 0) {\n return { name: normalizeName(correlated), source: 'recognizer' };\n }\n }\n return { name: '', source: 'recognizer' };\n}\n\n/**\n * Text reachable below each object, bounded once at every subtree.\n *\n * Probe IR keeps own text and descendants separate. Name-from-content is a\n * recognizer inference over parent links, not a reason for a probe to claim a\n * container directly carried its children's string. A nested annotated host is\n * an accessible-content boundary: it can name itself, but its label must not\n * leak into an outer control.\n */\nfunction collectSubtreeText(\n frame: ProbeFrame,\n maxStringBytes: number,\n): ReadonlyMap<string, string> {\n const objectById = new Map(frame.objects.map((object) => [object.identity.value, object]));\n const children = new Map<string, string[]>();\n for (const object of frame.objects) {\n if (object.parent === undefined || !objectById.has(object.parent)) continue;\n const siblings = children.get(object.parent) ?? [];\n siblings.push(object.identity.value);\n children.set(object.parent, siblings);\n }\n\n const memo = new Map<string, string>();\n const visiting = new Set<string>();\n const collect = (id: string): string => {\n const held = memo.get(id);\n if (held !== undefined) return held;\n if (visiting.has(id)) return '';\n visiting.add(id);\n const object = objectById.get(id);\n let result = object?.text ?? '';\n for (const child of children.get(id) ?? []) {\n const childText = collect(child);\n const childObject = objectById.get(child);\n if (\n childObject?.annotations?.role !== undefined ||\n childObject?.accessibility?.role !== undefined\n )\n continue;\n if (childText.length === 0) continue;\n // Separate host elements are separate content runs. Raw text fragments\n // inside one host were already joined by the probe, but sibling hosts\n // need a word boundary (`<Text>Save</Text><Text>now</Text>`).\n result = clampUtf8(\n result.length === 0 ? childText : `${result} ${childText}`,\n maxStringBytes,\n );\n if (UTF8_ENCODER.encode(result).byteLength >= maxStringBytes) break;\n }\n visiting.delete(id);\n result = clampUtf8(result, maxStringBytes);\n memo.set(id, result);\n return result;\n };\n\n for (const object of frame.objects) collect(object.identity.value);\n return memo;\n}\n\nfunction clampUtf8(value: string, maxBytes: number): string {\n if (UTF8_ENCODER.encode(value).byteLength <= maxBytes) return value;\n let result = '';\n let bytes = 0;\n for (const codePoint of value) {\n const size = UTF8_ENCODER.encode(codePoint).byteLength;\n if (bytes + size > maxBytes) break;\n result += codePoint;\n bytes += size;\n }\n return result;\n}\n\n/** Map observed state onto the protocol's closed state set. */\nfunction resolveState(\n object: ProbeObject,\n hiddenByGeometry: boolean,\n offscreen: boolean,\n): SemanticState | undefined {\n const observed = object.state;\n const state: Record<string, unknown> = {};\n\n if (observed?.focused !== undefined) state['focused'] = observed.focused;\n if (observed?.disabled !== undefined) state['disabled'] = observed.disabled;\n if (observed?.checked !== undefined) state['checked'] = observed.checked;\n if (observed?.expanded !== undefined) state['expanded'] = observed.expanded;\n if (observed?.readonly !== undefined) state['readonly'] = observed.readonly;\n if (observed?.selected !== undefined) state['selected'] = observed.selected;\n if (observed?.busy !== undefined) state['busy'] = observed.busy;\n if (observed?.multiline !== undefined) state['multiline'] = observed.multiline;\n if (observed?.required !== undefined) state['required'] = observed.required;\n if (observed?.multiselectable !== undefined) state['multiselectable'] = observed.multiselectable;\n if (observed?.displayed !== undefined) state['hidden'] = !observed.displayed;\n // Probe IR keeps item selection distinct from a text range. The semantic\n // tree represents the highlighted item's zero-based index as the matching\n // one-based collection position.\n if (observed?.selectedIndex !== undefined) state['positionInSet'] = observed.selectedIndex + 1;\n // Clipped entirely away is a different fact from the framework's own display\n // flag, and both end up as `hidden` because the wire has one field for it.\n if (hiddenByGeometry) state['hidden'] = true;\n if (offscreen) {\n state['hidden'] = true;\n state['offscreen'] = true;\n }\n\n return Object.keys(state).length === 0 ? undefined : (state as SemanticState);\n}\n\n/**\n * Turn one observed frame into a semantic snapshot.\n *\n * Every object becomes a node: nothing is dropped for being unrecognised, which\n * is what `generic` plus `frameworkType` is for. Parent links are rewritten\n * from probe identities to node ids, and an object whose parent did not survive\n * is attached to the root rather than left dangling — a snapshot with a missing\n * parent is refused by validation, and losing the subtree would be worse than\n * reparenting it.\n */\nexport function recognize(frame: ProbeFrame, context: RecognizeContext): SemanticSnapshot {\n // Ink boxes do not own their rendered label; it survives only in descendant\n // text hosts. Other framework probes already define the meaning of their own\n // `text` field and must not silently gain Ink's host-specific correlation.\n const subtreeText: ReadonlyMap<string, string> =\n context.framework === 'ink'\n ? collectSubtreeText(frame, context.maxStringBytes ?? DEFAULT_LIMITS.maxStringBytes)\n : new Map();\n const idByIdentity = new Map<string, string>();\n for (const object of frame.objects) {\n idByIdentity.set(object.identity.value, `n${object.identity.value}`);\n }\n\n const nodes: SemanticNode[] = [];\n const rootIds: string[] = [];\n\n for (const object of frame.objects) {\n const id = idByIdentity.get(object.identity.value) as string;\n const { role, source: roleSource } = resolveRole(object, context.framework);\n const { name, source: nameSource } = resolveName(object, role, subtreeText);\n\n const intended = object.geometry?.intendedRect;\n const visible = object.geometry?.visibleRect;\n const hiddenByGeometry = visible !== undefined && (visible.width === 0 || visible.height === 0);\n const offscreen =\n hiddenByGeometry && intended !== undefined && intended.width > 0 && intended.height > 0;\n const state = resolveState(object, hiddenByGeometry, offscreen);\n const parentId = object.parent === undefined ? undefined : idByIdentity.get(object.parent);\n if (parentId === undefined) rootIds.push(id);\n\n // One source for the node, exceptions listed. Physical facts always come\n // from the framework: an annotation may not move a widget on screen.\n const px: Record<string, ProvenanceSource> = {};\n if (nameSource !== roleSource) px['name'] = nameSource;\n if (object.geometry !== undefined) px['geometry'] = 'framework';\n if (state !== undefined) px['state'] = 'framework';\n if (object.annotations?.description !== undefined && roleSource !== 'annotation') {\n px['description'] = 'annotation';\n } else if (object.accessibility?.description !== undefined && roleSource !== 'framework') {\n px['description'] = 'framework';\n }\n if (object.annotations?.testId !== undefined && roleSource !== 'annotation') {\n px['testId'] = 'annotation';\n }\n if (object.annotations?.extended !== undefined && roleSource !== 'annotation') {\n px['extended'] = 'annotation';\n }\n if (object.annotations?.actions !== undefined && roleSource !== 'annotation') {\n px['actions'] = 'annotation';\n }\n if (object.annotations?.inputRecipes !== undefined && roleSource !== 'annotation') {\n px['inputRecipes'] = 'annotation';\n }\n if (object.annotations?.labelledBy !== undefined && roleSource !== 'annotation') {\n px['labelledBy'] = 'annotation';\n }\n if (object.annotations?.describedBy !== undefined && roleSource !== 'annotation') {\n px['describedBy'] = 'annotation';\n }\n\n const labelledBy = object.annotations?.labelledBy\n ?.map((identity) => idByIdentity.get(identity))\n .filter((target): target is string => target !== undefined);\n const describedBy = object.annotations?.describedBy\n ?.map((identity) => idByIdentity.get(identity))\n .filter((target): target is string => target !== undefined);\n\n const displayed: Observation<boolean> =\n object.state?.displayed !== undefined\n ? {\n status: 'known',\n value: object.state.displayed,\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n }\n : object.unobservable?.includes('displayed') === true\n ? { status: 'unsupported', capability: 'displayed', reason: 'framework-unobservable' }\n : { status: 'unsupported', capability: 'displayed', reason: 'framework-unobservable' };\n const intendedRect: Observation<Rect> =\n displayed.status === 'known' &&\n displayed.value === false &&\n displayed.evidence.strength === 'authoritative'\n ? {\n status: 'absent',\n reason: 'not-displayed',\n evidence: { ...displayed.evidence, strength: 'authoritative' },\n }\n : object.geometry?.intendedRect !== undefined\n ? {\n status: 'known',\n value: object.geometry.intendedRect,\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n }\n : object.unobservable?.includes('intendedRect') === true\n ? {\n status: 'unsupported',\n capability: 'intended-rect',\n reason: 'framework-unobservable',\n }\n : {\n status: 'unsupported',\n capability: 'intended-geometry',\n reason: 'framework-unobservable',\n };\n const visibleRect: Observation<Rect> =\n displayed.status === 'known' &&\n displayed.value === false &&\n displayed.evidence.strength === 'authoritative'\n ? {\n status: 'absent',\n reason: 'not-displayed',\n evidence: { ...displayed.evidence, strength: 'authoritative' },\n }\n : displayed.status === 'known' && displayed.value === false\n ? {\n status: 'unsupported',\n capability: 'clipped-geometry',\n reason: 'framework-unobservable',\n }\n : object.geometry?.visibleRect !== undefined\n ? {\n status: 'known',\n value: object.geometry.visibleRect,\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n }\n : object.unobservable?.includes('visibleRect') === true\n ? {\n status: 'unsupported',\n capability: 'visible-rect',\n reason: 'framework-unobservable',\n }\n : {\n status: 'unsupported',\n capability: 'clipped-geometry',\n reason: 'framework-unobservable',\n };\n\n nodes.push({\n id,\n ...(parentId === undefined ? {} : { parentId }),\n role,\n name,\n ...(object.annotations?.description === undefined\n ? object.accessibility?.description === undefined\n ? {}\n : { description: object.accessibility.description }\n : { description: object.annotations.description }),\n frameworkType: object.frameworkType,\n geometry: { displayed, intendedRect, visibleRect },\n ...(state === undefined ? {} : { state }),\n ...(object.annotations?.testId === undefined ? {} : { testId: object.annotations.testId }),\n ...(object.annotations?.extended === undefined\n ? {}\n : { extended: object.annotations.extended }),\n ...(object.annotations?.actions === undefined ? {} : { actions: object.annotations.actions }),\n ...(object.annotations?.inputRecipes === undefined\n ? {}\n : { inputRecipes: object.annotations.inputRecipes }),\n ...(labelledBy === undefined || labelledBy.length === 0 ? {} : { labelledBy }),\n ...(describedBy === undefined || describedBy.length === 0 ? {} : { describedBy }),\n ...(object.state?.value === undefined\n ? {}\n : {\n value: {\n status: 'known' as const,\n value: object.state.value,\n sensitivity: object.state?.valueSensitivity ?? 'sensitive',\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n },\n }),\n p: roleSource,\n ...(Object.keys(px).length === 0 ? {} : { px }),\n });\n }\n\n return {\n v: 2,\n sessionId: context.sessionId,\n revision: context.revision,\n columns: context.columns,\n rows: context.rows,\n rootIds,\n nodes,\n coordinateSpace: {\n status: 'known',\n value: 'viewport-cells',\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n },\n hitGrid: {\n status: 'unsupported',\n capability: 'pointer-hit-grid',\n reason: 'framework-unobservable',\n },\n };\n}\n","/**\n * Where a name comes from, and — more often — where it must not.\n *\n * The protocol's adapter conventions gate descendant-text naming to nine roles.\n * The rule exists because naming containers from their content makes\n * `getByRole('region', {name: 'Approve'})` match the dialog *containing* the\n * Approve button, so every ancestor of a label becomes a plausible match and\n * locators stop being selective. That failure is quiet: the tree looks richer,\n * and the tests get worse.\n */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\n/**\n * Roles whose accessible name comes from the text they contain.\n *\n * Normative list, from the protocol README. Anything outside it is a container\n * and keeps an empty name unless an annotation gives it one.\n */\nexport const NAME_FROM_CONTENT: ReadonlySet<SemanticRole> = new Set<SemanticRole>([\n 'button',\n 'listitem',\n 'menuitem',\n 'tab',\n 'checkbox',\n 'radio',\n 'cell',\n 'row',\n 'heading',\n]);\n\n/**\n * Whether a role takes its name from the text it contains.\n *\n * `text` is included beyond the normative list: a text node's string is its\n * *own* content — naming source 2, \"the widget's own label\" — rather than a\n * descendant widget's. Without it `getByText` would have nothing to match.\n */\nexport function namesFromContent(role: SemanticRole): boolean {\n return role === 'text' || NAME_FROM_CONTENT.has(role);\n}\n\n/** Collapse whitespace the way a terminal reader would see it. */\nexport function normalizeName(text: string): string {\n return text.replace(/\\s+/gu, ' ').trim();\n}\n","/** Conservative role map for the only host kinds Ink retains after reconcile. */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\nconst HOST_ROLES: Readonly<Record<string, SemanticRole>> = Object.freeze({\n 'ink-root': 'application',\n 'ink-text': 'text',\n 'ink-virtual-text': 'text',\n 'ink-box': 'generic',\n});\n\n/**\n * Resolve only facts the host kind itself proves.\n *\n * Ink discards source component names before host creation, so a plain box is\n * never promoted to `button` from its children or styling. A retained\n * `aria-role` travels separately as framework-native accessibility metadata.\n */\nexport function roleForInkHost(host: string): SemanticRole | undefined {\n return HOST_ROLES[host];\n}\n\nconst ARIA_ROLES: Readonly<Record<string, SemanticRole>> = Object.freeze({\n button: 'button',\n checkbox: 'checkbox',\n combobox: 'generic',\n list: 'list',\n listbox: 'list',\n listitem: 'listitem',\n menu: 'menu',\n menuitem: 'menuitem',\n option: 'listitem',\n progressbar: 'progressbar',\n radio: 'radio',\n radiogroup: 'generic',\n tab: 'tab',\n tablist: 'generic',\n table: 'table',\n textbox: 'textbox',\n timer: 'status',\n toolbar: 'generic',\n});\n\n/** Map Ink's retained aria vocabulary without guessing ambiguous containers. */\nexport function roleForInkAria(role: string): SemanticRole | undefined {\n return ARIA_ROLES[role];\n}\n","/**\n * OpenTUI's widget vocabulary, mapped onto the protocol's closed role set.\n *\n * OpenTUI has **no accessibility layer at all** — the Phase 0 audit found no\n * `role`, no `aria`, no `checked` anywhere in its types — so the class name is\n * the only signal there is. That makes this map level 2 of role resolution: it\n * runs after an author annotation and before `generic`.\n *\n * The map is deliberately short. A widget with no unambiguous counterpart stays\n * `generic` and keeps its `frameworkType`, which is what the protocol's D1\n * decision exists for: an unrecognised widget survives with its bounds, text\n * and children instead of being dropped, and a test can still find it by what\n * the framework called it. Inventing `tab` for `TabSelectRenderable` would be\n * the opposite trade — a role that reads right and makes locators match the\n * wrong thing.\n */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\nconst ROLE_BY_CLASS: Readonly<Record<string, SemanticRole>> = Object.freeze({\n RootRenderable: 'application',\n TextRenderable: 'text',\n TextNodeRenderable: 'text',\n RootTextNodeRenderable: 'text',\n CodeRenderable: 'text',\n MarkdownRenderable: 'text',\n ASCIIFontRenderable: 'text',\n InputRenderable: 'textbox',\n TextareaRenderable: 'textbox',\n EditBufferRenderable: 'textbox',\n SelectRenderable: 'list',\n TextTableRenderable: 'table',\n ScrollBarRenderable: 'scrollbar',\n});\n\n/**\n * The role OpenTUI's class name implies, if any.\n *\n * @returns a role, or `undefined` when the class has no unambiguous\n * counterpart — `BoxRenderable`, `ScrollBoxRenderable`, `TabSelectRenderable`,\n * `SliderRenderable` and every application subclass land there on purpose.\n */\nexport function roleForOpenTuiClass(frameworkType: string): SemanticRole | undefined {\n return Object.hasOwn(ROLE_BY_CLASS, frameworkType) ? ROLE_BY_CLASS[frameworkType] : undefined;\n}\n\n/**\n * Whether a class is one of OpenTUI's own.\n *\n * Used to decide whether an unmapped `frameworkType` is a widget we chose not\n * to classify or an application's own subclass — a distinction worth keeping,\n * because the second is the case `generic` was designed for.\n */\nexport function isOpenTuiClass(frameworkType: string): boolean {\n return frameworkType.endsWith('Renderable');\n}\n","/** Replacement source for Ink's public entry module. */\n\n/** Marker on the shim's re-import of the untouched module. */\nexport const ORIGINAL_MARKER = 'termwright-original=1';\n\n/**\n * Ink's ESM entry under ordinary node_modules and Bun's versioned cache.\n * Matching the resolved path, rather than the bare specifier, is required by\n * both loader APIs used here.\n */\nexport const INK_ENTRY_PATTERN = /[\\\\/](?:ink|ink@[^\\\\/]+)[\\\\/]build[\\\\/]index\\.js$/u;\n\n/** The separately-built runtime imported by replacement module source. */\nexport const INSTRUMENT_URL = new URL('./instrument.js', import.meta.url).href;\n\nexport function shouldShim(urlOrPath: string): boolean {\n if (urlOrPath.includes(ORIGINAL_MARKER)) return false;\n return INK_ENTRY_PATTERN.test(urlOrPath.split('?')[0] ?? '');\n}\n\nexport function originalUrl(urlOrPath: string): string {\n return `${urlOrPath}${urlOrPath.includes('?') ? '&' : '?'}${ORIGINAL_MARKER}`;\n}\n\n/** Ink's renderer instance, resolved beside the intercepted public entry. */\nexport function reconcilerUrl(urlOrPath: string): string {\n const [path] = urlOrPath.split('?');\n if (path === undefined || !INK_ENTRY_PATTERN.test(path)) {\n throw new Error(`Cannot resolve Ink reconciler beside ${urlOrPath}`);\n }\n return path.replace(/index\\.js$/u, 'reconciler.js');\n}\n\n/**\n * Forward the complete Ink namespace and shadow only `render`.\n *\n * The wrapper receives the already-marked original namespace, so the runtime\n * never imports `ink` itself and cannot recurse through the loader hook.\n */\nexport function buildShimSource(target: string, instrumentUrl = INSTRUMENT_URL): string {\n const original = JSON.stringify(originalUrl(target));\n const reconciler = JSON.stringify(reconcilerUrl(target));\n const instrument = JSON.stringify(instrumentUrl);\n return `import * as __termwright_original from ${original};\nimport __termwright_reconciler from ${reconciler};\nimport {wrapInkRender as __termwright_wrap} from ${instrument};\nexport * from ${original};\n\nexport const render = __termwright_wrap(__termwright_original, {reconciler: __termwright_reconciler});\n`;\n}\n","/** Runtime activation shared by the two preload entry points. */\n\nimport { ENV_ENDPOINT, ENV_TOKEN } from '@termwright/protocol';\n\n/** Runtimes into which the Ink probe can be injected. */\nexport type ProbeRuntime = 'bun' | 'node';\n\n/** Read-only environment view, so activation is testable without mutation. */\nexport type EnvSource = Readonly<Record<string, string | undefined>>;\n\n/** Both secrets are required. A partial environment remains fully dormant. */\nexport function isInstrumented(env: EnvSource): boolean {\n const endpoint = env[ENV_ENDPOINT];\n const token = env[ENV_TOKEN];\n return (\n typeof endpoint === 'string' &&\n endpoint.length > 0 &&\n typeof token === 'string' &&\n token.length > 0\n );\n}\n","/** Build an application command with the zero-config Ink preload attached. */\n\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { ProbeRuntime } from './runtime.js';\n\n/** Published preload paths; callers never have to guess package layout. */\nexport const PROBE_ENTRIES = {\n bun: fileURLToPath(new URL('./bun-preload.js', import.meta.url)),\n node: fileURLToPath(new URL('./node-hook.js', import.meta.url)),\n} as const;\n\nexport interface ProbeCommand {\n readonly command: readonly string[];\n readonly runtime: ProbeRuntime;\n}\n\n/** Prefix a normal Node or Bun command with the matching preload flag. */\nexport function withProbe(runtime: ProbeRuntime, argv: readonly string[]): ProbeCommand {\n if (argv.length === 0) throw new Error('withProbe needs an interpreter in argv');\n const [interpreter, ...rest] = argv as [string, ...string[]];\n const flag = runtime === 'bun' ? '--preload' : '--import';\n return {\n command: [interpreter, flag, runtimePreloadSpecifier(runtime, PROBE_ENTRIES[runtime]), ...rest],\n runtime,\n };\n}\n\n/** Node needs a file URL on Windows; Bun's Windows preload resolver needs a native path. */\nexport function runtimePreloadSpecifier(runtime: ProbeRuntime, entry: string): string {\n return runtime === 'bun' ? entry : pathToFileURL(entry).href;\n}\n","interface OwnedSocket {\n destroy(): unknown;\n on(event: 'error', listener: () => void): unknown;\n once(event: 'close', listener: () => void): unknown;\n pause(): unknown;\n resume(): unknown;\n}\n\ninterface ClosableServer {\n close(callback: (error?: Error) => void): unknown;\n}\n\n/** Owns accepted peers across listener startup and causal shutdown. */\nexport class ProbePeerOwner {\n readonly #sockets = new Set<OwnedSocket>();\n readonly #pending: OwnedSocket[] = [];\n #handler: ((socket: OwnedSocket) => void) | null = null;\n #closing = false;\n #closePromise: Promise<void> | null = null;\n\n admit(socket: OwnedSocket): boolean {\n socket.pause();\n this.#sockets.add(socket);\n socket.on('error', () => socket.destroy());\n socket.once('close', () => {\n this.#sockets.delete(socket);\n const pending = this.#pending.indexOf(socket);\n if (pending >= 0) this.#pending.splice(pending, 1);\n });\n if (this.#closing) {\n socket.destroy();\n return false;\n }\n if (this.#handler === null) this.#pending.push(socket);\n else this.#deliver(socket);\n return true;\n }\n\n activate(handler: (socket: OwnedSocket) => void): void {\n if (this.#handler !== null) throw new Error('probe peer owner is already active');\n this.#handler = handler;\n for (const socket of this.#pending.splice(0)) this.#deliver(socket);\n }\n\n close(server: ClosableServer): Promise<void> {\n this.#closePromise ??= this.#close(server);\n return this.#closePromise;\n }\n\n async #close(server: ClosableServer): Promise<void> {\n this.#closing = true;\n const closed = new Promise<void>((resolve, reject) => {\n server.close((error) => (error === undefined ? resolve() : reject(error)));\n });\n this.#pending.length = 0;\n for (const socket of this.#sockets) socket.destroy();\n await closed;\n }\n\n #deliver(socket: OwnedSocket): void {\n this.#handler?.(socket);\n socket.resume();\n }\n}\n","import type { ExitStatus } from '@termwright/driver';\nimport type { PtyProcess } from '@termwright/driver/experimental';\n\nconst DEFAULT_WATCHDOG_MS = 10_000;\n\nexport interface ProbeProcessShutdownResources {\n readonly pty: PtyProcess;\n readonly closeAdmission: () => Promise<void>;\n readonly closeTerminalResponseAdmission: () => void;\n readonly drainParser: () => Promise<void>;\n readonly disposeParser: () => void;\n readonly removeArtifacts: () => Promise<void>;\n readonly watchdogMs?: number;\n}\n\n/**\n * Owns the causal boundary between a probe fixture and its teardown artifacts.\n *\n * Root exit is not output EOF on ConPTY. Conversely, disposing a PTY settles\n * `outputEnded` so teardown cannot hang, but that settlement is explicitly not\n * proof that the reader reached its source. This coordinator therefore waits\n * for both the already-armed exit observer and authoritative EOF, then drains\n * the terminal parser, before it disposes either parser or PTY and before it\n * unlinks files the child may still have open.\n */\nexport class ProbeProcessShutdown {\n readonly #resources: ProbeProcessShutdownResources;\n readonly #exit: Promise<ExitStatus>;\n readonly #resolveExit: (status: ExitStatus) => void;\n #exitStatus: ExitStatus | null = null;\n #stopPromise: Promise<void> | null = null;\n\n constructor(resources: ProbeProcessShutdownResources) {\n this.#resources = resources;\n let resolveExit!: (status: ExitStatus) => void;\n this.#exit = new Promise<ExitStatus>((resolve) => {\n resolveExit = resolve;\n });\n this.#resolveExit = resolveExit;\n }\n\n observeExit(status: ExitStatus): void {\n if (this.#exitStatus !== null) return;\n this.#exitStatus = Object.freeze({ ...status });\n this.#resolveExit(this.#exitStatus);\n }\n\n stop(): Promise<void> {\n this.#stopPromise ??= this.#stop();\n return this.#stopPromise;\n }\n\n async #stop(): Promise<void> {\n const failures: unknown[] = [];\n let admissionClosedSuccessfully = false;\n let admissionClosed: Promise<void>;\n try {\n admissionClosed = this.#resources.closeAdmission().then(\n () => {\n admissionClosedSuccessfully = true;\n },\n (error: unknown) => {\n failures.push(error);\n },\n );\n } catch (error) {\n failures.push(error);\n admissionClosed = Promise.resolve();\n }\n\n const controller = new AbortController();\n let causalBoundaryReached = false;\n try {\n await this.#withWatchdog(this.#reachCausalBoundary(controller.signal), controller);\n causalBoundaryReached = true;\n } catch (error) {\n failures.push(error);\n }\n\n // The alive-tree branch revokes replies immediately before hard kill\n // closes ConPTY input. A process that was already gone keeps the bridge\n // through its remaining output and parser drain, since a queued host query\n // can still require a response before OpenConsole publishes EOF.\n try {\n this.#resources.closeTerminalResponseAdmission();\n } catch (error) {\n failures.push(error);\n }\n\n for (const dispose of [\n () => this.#resources.pty.dispose(),\n () => this.#resources.disposeParser(),\n ]) {\n try {\n dispose();\n } catch (error) {\n failures.push(error);\n }\n }\n\n try {\n await admissionClosed;\n } catch (error) {\n failures.push(error);\n }\n\n // A failed process/output/parser boundary leaves the artifact in place as\n // evidence. Retrying its deletion would replace a causal contract with a\n // timing heuristic and can hide the handle owner that broke teardown.\n if (admissionClosedSuccessfully && causalBoundaryReached) {\n try {\n await this.#resources.removeArtifacts();\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, 'adapter probe cleanup failed');\n }\n\n async #reachCausalBoundary(signal: AbortSignal): Promise<void> {\n const { pty } = this.#resources;\n const initialTree = pty.treeState?.() ?? 'unsupported';\n if (initialTree === 'alive') {\n if (pty.hardKillTree === undefined) {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY exposes no owned-tree kill operation',\n );\n }\n this.#resources.closeTerminalResponseAdmission();\n await pty.hardKillTree(signal);\n } else if (initialTree === 'unsupported') {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY exposes no authoritative owned-tree state',\n );\n }\n\n const outputEnded = pty.outputEnded;\n if (outputEnded === undefined) {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY exposes no output EOF barrier',\n );\n }\n\n await this.#awaitOrAbort(\n Promise.all([this.#exit, outputEnded]).then(() => undefined),\n signal,\n );\n signal.throwIfAborted();\n if (pty.sawOutputEnd?.() !== true) {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY output ended without authoritative EOF',\n );\n }\n if (pty.treeState?.() !== 'gone') {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the owned process tree was not confirmed gone at EOF',\n );\n }\n await this.#awaitOrAbort(this.#resources.drainParser(), signal);\n signal.throwIfAborted();\n }\n\n async #awaitOrAbort(operation: Promise<void>, signal: AbortSignal): Promise<void> {\n signal.throwIfAborted();\n let removeAbortListener = (): void => undefined;\n const aborted = new Promise<never>((_resolve, reject) => {\n const onAbort = (): void => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', onAbort);\n });\n try {\n await Promise.race([operation, aborted]);\n } finally {\n removeAbortListener();\n }\n }\n\n async #withWatchdog(operation: Promise<void>, controller: AbortController): Promise<void> {\n const watchdogMs = this.#resources.watchdogMs ?? DEFAULT_WATCHDOG_MS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expired = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(\n new Error(\n `adapter probe teardown did not reach exit, authoritative EOF, and parser drain ` +\n `within its ${String(watchdogMs)} ms watchdog`,\n ),\n );\n controller.abort();\n }, watchdogMs);\n });\n try {\n await Promise.race([operation, expired]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n controller.abort();\n }\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport { mkdtemp } from 'node:fs/promises';\nimport { createServer, type Server } from 'node:net';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { PtyProcess } from '@termwright/driver/experimental';\nimport { ProbePeerOwner } from './probe-peer-owner.js';\nimport { rollbackProbeStart } from './probe-start-cleanup.js';\n\nexport type ProbeListen = (server: Server, endpoint: string) => Promise<void>;\n\nexport interface ProbeEndpointAcquisition {\n readonly listen?: ProbeListen;\n /** Exercises directory ownership on named-pipe hosts in the transaction test. */\n readonly allocateDirectory?: boolean;\n}\n\n/** Mutable ownership boundary for every resource acquired during probe startup. */\nexport class ProbeStartupTransaction {\n readonly peers = new ProbePeerOwner();\n server: Server | null = null;\n directory: string | null = null;\n endpoint: string | null = null;\n debugFile: string | null = null;\n pty: PtyProcess | null = null;\n\n async acquireEndpoint(\n instrument: boolean,\n options: ProbeEndpointAcquisition = {},\n ): Promise<void> {\n if (!instrument) return;\n this.server = createServer();\n this.server.on('connection', (socket) => this.peers.admit(socket));\n if (process.platform !== 'win32' || options.allocateDirectory === true) {\n this.directory = await mkdtemp(join(tmpdir(), 'termwright-probe-'));\n }\n if (process.platform === 'win32') {\n this.endpoint = `\\\\\\\\.\\\\pipe\\\\termwright-probe-${randomBytes(16).toString('hex')}`;\n } else {\n this.endpoint = join(this.directory as string, 'semantic.sock');\n }\n await (options.listen ?? listenServer)(this.server, this.endpoint);\n }\n\n rollback(primary: unknown): Promise<never> {\n const server = this.server;\n const pty = this.pty;\n return rollbackProbeStart(primary, {\n ...(server === null || !server.listening\n ? {}\n : { closeAdmission: () => this.peers.close(server) }),\n ...(pty === null ? {} : { disposePty: () => pty.dispose() }),\n directory: this.directory,\n debugFile: this.debugFile,\n });\n }\n}\n\nfunction listenServer(server: Server, endpoint: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const onError = (error: Error): void => {\n server.removeListener('listening', onListening);\n reject(error);\n };\n const onListening = (): void => {\n server.removeListener('error', onError);\n resolve();\n };\n server.once('error', onError);\n server.once('listening', onListening);\n server.listen(endpoint);\n });\n}\n","import { rm } from 'node:fs/promises';\n\nexport interface ProbeStartCleanup {\n readonly closeAdmission?: () => Promise<void>;\n readonly disposePty?: () => void;\n readonly directory?: string | null;\n readonly debugFile?: string | null;\n}\n\n/** Rolls back every resource acquired before AdapterProbe startup committed. */\nexport async function rollbackProbeStart(\n primary: unknown,\n cleanup: ProbeStartCleanup,\n): Promise<never> {\n const failures: unknown[] = [primary];\n let serverClosed = Promise.resolve();\n if (cleanup.closeAdmission !== undefined) {\n try {\n serverClosed = cleanup.closeAdmission();\n } catch (error) {\n failures.push(error);\n }\n }\n if (cleanup.disposePty !== undefined) {\n try {\n cleanup.disposePty();\n } catch (error) {\n failures.push(error);\n }\n }\n try {\n await serverClosed;\n } catch (error) {\n failures.push(error);\n }\n for (const path of [cleanup.directory, cleanup.debugFile]) {\n if (path === undefined || path === null) continue;\n try {\n await rm(path, { recursive: path === cleanup.directory, force: true });\n } catch (error) {\n failures.push(error);\n }\n }\n if (failures.length === 1) throw primary;\n throw new AggregateError(failures, 'adapter probe startup and rollback failed', {\n cause: primary,\n });\n}\n"],"mappings":"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","names":["existsSync","readFileSync","tmpdir","join","DEFAULT_LIMITS","PROTOCOL_ID","rm","tmpdir","join","randomBytes","DEFAULT_LIMITS","ENV_ENDPOINT","ENV_TOKEN","fileURLToPath","fileURLToPath","bytes","join","join","ENV_ENDPOINT","ENV_TOKEN","join","tmpdir","randomBytes","rm","DEFAULT_LIMITS","join","tmpdir","PROTOCOL_ID","DEFAULT_LIMITS","observation","existsSync","readFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter-conformance.ts","../src/support/probe.ts","../src/support/pty.ts","../../probe-ink/src/instrumentation.ts","../../probe-ink/src/certified-instrumentation.json","../../probe-ink/src/react-commit-bridge.ts","../../probe-ink/src/annotations.ts","../../probe-ink/src/observe.ts","../../probe-ink/src/version.ts","../../probe-ink/src/probe-info.ts","../../probe-ink/src/session.ts","../../pty/src/index.ts","../../pty/src/windows.ts","../../pty/src/write-drain-epoch.ts","../../pty/src/windows-output-normalizer.ts","../../recognizers/src/recognize.ts","../../recognizers/src/naming.ts","../../recognizers/src/ink.ts","../../recognizers/src/opentui.ts","../../probe-ink/src/shim.ts","../../probe-ink/src/runtime.ts","../../probe-ink/src/launch.ts","../src/support/probe-peer-owner.ts","../src/support/probe-process-shutdown.ts","../src/support/probe-startup.ts","../src/support/probe-start-cleanup.ts"],"sourcesContent":["/**\n * The adapter contract suite — the part of this package that is meant to be\n * used from outside it.\n *\n * An adapter is conforming when five things hold, and they are the same five in\n * every language: it stays dormant without an endpoint, it completes the\n * handshake, every snapshot it publishes is valid, it orders each revision as\n * snapshot → commit → marker-after-the-frame, and it survives losing the\n * channel without taking the application with it.\n *\n * The suite drives the adapter as a subprocess and observes only bytes and\n * frames, so a Python, Go or Rust adapter self-certifies exactly like the Ink\n * one — nothing here imports an adapter.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n ADAPTER_CAPABILITIES,\n validateSnapshot,\n DEFAULT_LIMITS,\n PROTOCOL_ID,\n} from '@termwright/protocol';\nimport type { SemanticSnapshot } from '@termwright/protocol';\nimport {\n AdapterProbe,\n MARKER_TEXT_PREFIX,\n type AdapterCommand,\n type ProbeObservation,\n} from './support/probe.js';\nimport { commandAvailable, ptyAvailable } from './support/pty.js';\n\n/** How to start, drive and stop the adapter under test. */\nexport interface AdapterConformanceOptions {\n /** Name of the adapter, used in the test titles. */\n readonly name: string;\n /** Command that starts the instrumented application. */\n spawn(): AdapterCommand;\n /**\n * Optional command rendering the same UI with the adapter compiled out. When\n * given, the dormant run is compared against it byte for byte — the strongest\n * form of the dormant rule. Without it, a dormant run is only checked for\n * silence on the wire.\n */\n baseline?(): AdapterCommand;\n /** Text that proves the first frame reached the terminal. */\n readonly ready: string | RegExp;\n /**\n * An input that changes the screen, and the text that proves it landed.\n *\n * The suite sends it **more than once** — a dormant run, a run that has to\n * produce a second revision, and a run after the channel was cut all need a\n * render. Pick something whose repetition is harmless.\n */\n readonly interaction: { readonly input: string; readonly expect: string | RegExp };\n /**\n * An input that makes the application exit, and the status it exits with.\n *\n * It must work from **any** state repeated `interaction` can reach. A key\n * that quits only while a particular widget has focus is not a quit input:\n * the tview example's documented `q` types into its text field once focus has\n * cycled that far, so its registration uses Ctrl+C instead.\n */\n readonly quit: { readonly input: string; readonly exitCode?: number };\n readonly columns?: number;\n readonly rows?: number;\n /** Assert that intended geometry is authoritative in viewport cells. */\n readonly expectIntendedGeometry?: boolean;\n /**\n * Opt out of the \"publishes a tree before any input\" obligation.\n *\n * By default an adapter must publish a non-empty tree once the handshake\n * completes, with no input sent — an app that is addressable only after the\n * first keystroke is not addressable at all to a driver that has just\n * launched it. Some apps legitimately render nothing until an event arrives;\n * pass a reason, which is printed in the test title so the exemption stays\n * visible rather than becoming folklore.\n */\n readonly treeBeforeInput?: { readonly required: false; readonly reason: string };\n /**\n * How to make the application log, for an adapter that announces the `logs`\n * capability. Without it the log obligations are skipped; with it they are\n * asserted, and an adapter that announces `logs` but never delivers one\n * fails here rather than in a user's test.\n */\n /**\n * How to check the normative adapter conventions (protocol README, \"Adapter\n * semantics conventions\"). Rules 1, 2 and 4 are largely judgement calls from\n * outside; what is listed here is what a subprocess can actually observe.\n *\n * A rule an adapter cannot follow is a *declared deviation*, not a failure:\n * name it in `deviations` and the matching check is skipped with the reason\n * in the test title, so the exemption stays visible instead of becoming\n * folklore.\n */\n readonly conventions?: {\n /** A test id the fixture sets by author annotation (rule 3). */\n readonly annotatedTestId?: string;\n /** A textbox whose field is empty, to prove `value: ''` (rule 5). */\n readonly emptyTextboxTestId?: string;\n /** A container with no name of its own, wrapping text (rule 2). */\n readonly unnamedContainerTestId?: string;\n /**\n * Test ids whose `value` is author-annotated. The role gate in rule 5\n * bounds *derived* values; an annotation may put one on any role, and only\n * the registration knows which is which.\n */\n readonly annotatedValues?: readonly string[];\n /**\n * The adapter's README. Its `## Deviations` section is the single source of\n * truth for what this adapter cannot do (rule 6), so a declared limitation\n * is read from there rather than repeated in the registration — two copies\n * of the same fact disagree eventually, and the README is the one a user\n * reads.\n */\n readonly readmePath?: string;\n };\n readonly logs?: {\n /**\n * Input that makes the application write a record. Omit it for an app that\n * logs on its own (at startup, say) — the obligation then waits for a\n * record rather than provoking one.\n */\n readonly input?: string;\n /** A substring of the logged message, used to prove it stayed off-screen. */\n readonly expect: string;\n };\n /** How long the handshake may take. Default 10 s. */\n readonly timeoutMs?: number;\n /**\n * A cheap command that must succeed before this adapter can be certified here.\n * Compilers and other descendant-producing preparation must run before the\n * native host opens; this probe is collection-time capability validation only.\n * When it fails the whole suite skips and the reason is in the block's name,\n * exactly as a missing pseudo-terminal does.\n */\n readonly requires?: {\n readonly probe: readonly string[];\n readonly label: string;\n readonly cwd?: string;\n readonly timeoutMs?: number;\n readonly env?: Readonly<Record<string, string>>;\n };\n}\n\n/**\n * Directory the convention summaries are written to, and read back by\n * `scripts/conformance.mjs` when it prints the matrix.\n */\nexport const CONVENTION_SUMMARY_DIR = join(tmpdir(), 'termwright-conformance-conventions');\n\n/**\n * Records what each rule concluded, so the matrix can print a per-adapter\n * roll-up of declared deviations.\n *\n * This exists because a hand-maintained table of per-adapter gaps went stale\n * within one round of being written — a generated one cannot. Nothing here is\n * a gate: the file is a report, and the tests already decided pass or fail.\n */\nfunction writeConventionSummary(\n name: string,\n declared: Map<string, string[]>,\n outcomes: readonly ConventionOutcome[],\n): void {\n try {\n mkdirSync(CONVENTION_SUMMARY_DIR, { recursive: true });\n const file = join(CONVENTION_SUMMARY_DIR, `${name.replace(/[^\\w.-]+/gu, '_')}.json`);\n writeFileSync(\n file,\n JSON.stringify(\n {\n adapter: name,\n declared: Object.fromEntries(declared),\n outcomes,\n // A rule declared in the README that no check covers: the suite\n // cannot confirm or refute it, and saying so is more honest than\n // letting it read as verified.\n unverified: [...declared.keys()].filter(\n (rule) => !outcomes.some((outcome) => outcome.rule === rule),\n ),\n },\n null,\n 2,\n ),\n 'utf8',\n );\n } catch {\n // A report that cannot be written must not fail a conformance run.\n }\n}\n\n/**\n * Waits until the child stops writing, so two captures end at comparable\n * points. A stabilisation, not a deadline: an app that never stops writing\n * makes the comparison fail loudly rather than pass by accident.\n */\nasync function settle(probe: AdapterProbe, quietMs = 250, budgetMs = 5_000): Promise<void> {\n const deadline = performance.now() + budgetMs;\n let seen = -1;\n for (;;) {\n const length = probe.observe().stdout.length;\n if (length === seen) return;\n seen = length;\n if (performance.now() >= deadline) return;\n await new Promise((resolve) => {\n setTimeout(resolve, quietMs);\n });\n }\n}\n\n/**\n * Rule numbers declared in an adapter's `## Deviations` section.\n *\n * Three shapes are in use and all are accepted, because the suite should not\n * dictate anyone's prose: `**Rule 2 — …**` (a heading per entry, Ink),\n * `- **…** (rule 3).` (the number at the end of a bullet, the language\n * clients), and a markdown table whose first column is `2 — …` (OpenTUI).\n *\n * Entries that name no rule are kept under `other`: they are still declared\n * limitations, and dropping them would make the roll-up quietly incomplete.\n */\nexport function parseDeclaredDeviations(readme: string): Map<string, string[]> {\n const declared = new Map<string, string[]>();\n const start = readme.indexOf('## Deviations');\n if (start < 0) return declared;\n const rest = readme.slice(start + '## Deviations'.length);\n const end = rest.indexOf('\\n## ');\n const section = end < 0 ? rest : rest.slice(0, end);\n\n for (const line of section.split('\\n')) {\n for (const match of line.matchAll(/\\*\\*Rule (\\d+)\\s*—\\s*([^*]+?)\\.?\\*\\*/gu)) {\n add(declared, match[1] as string, (match[2] as string).trim());\n }\n for (const match of line.matchAll(/\\*\\*(.+?)\\*\\*\\s*\\(rule (\\d+)\\)/gu)) {\n add(declared, match[2] as string, (match[1] as string).trim());\n }\n const trimmed = line.trim();\n if (\n trimmed.startsWith('|') &&\n !/^\\|[\\s:|-]*\\|?$/u.test(trimmed) &&\n !/^\\|\\s*rule\\s*\\|/iu.test(trimmed)\n ) {\n // A table row. The rule number and its title share the first cell\n // (`2 — name sources`); a row that names no rule carries its text in the\n // second cell instead.\n const cells = trimmed\n .split('|')\n .slice(1, -1)\n .map((cell) => cell.trim());\n const numbered = /^(\\d+)\\s*[—-]\\s*(.+)$/u.exec(cells[0] ?? '');\n if (numbered !== null) {\n add(declared, numbered[1] as string, (numbered[2] as string).trim());\n } else if ((cells[1] ?? '').length > 0) {\n add(declared, 'other', cells[1] as string);\n }\n }\n }\n return declared;\n}\n\nfunction add(map: Map<string, string[]>, key: string, value: string): void {\n map.set(key, [...(map.get(key) ?? []), value]);\n}\n\n/** What a convention check concluded, for the run summary. */\ninterface ConventionOutcome {\n readonly rule: string;\n readonly what: string;\n readonly status: 'compliant' | 'documented' | 'checked-despite-declaration' | 'violation';\n readonly detail?: string;\n}\n\n/** Roles that are containers: never named from what they contain (rule 2). */\nconst CONTAINER_ROLES: ReadonlySet<string> = new Set([\n 'region',\n 'dialog',\n 'list',\n 'table',\n 'application',\n 'menu',\n]);\n\nconst snapshotsOf = (observation: ProbeObservation): SemanticSnapshot[] =>\n observation.messages\n .filter((entry) => entry.message.type === 'snapshot')\n .map((entry) => (entry.message as { snapshot: SemanticSnapshot }).snapshot);\n\n/**\n * Registers the adapter contract suite for one adapter.\n *\n * Call it at the top level of a test file; it declares its own `describe`.\n *\n * `vitest` is imported dynamically, so the package stays importable from a\n * plain script that only wants the fixture paths or the probe. That is why the\n * function is async: `await` it at the top level of the test file, which is\n * where vitest collects the suite from.\n *\n * @example\n * ```ts\n * await runAdapterConformance({\n * name: 'my-framework-probe',\n * spawn: () => ({ command: ['node', 'app.mjs'] }),\n * ready: 'Ready',\n * interaction: { input: '\\t', expect: '[Save]' },\n * quit: { input: 'q', exitCode: 0 },\n * });\n * ```\n */\nexport async function runAdapterConformance(options: AdapterConformanceOptions): Promise<void> {\n const { afterAll, afterEach, beforeEach, describe, expect } = await import('vitest');\n const { it: resourceAwareIt } = await import('@termwright/resource-broker/vitest');\n const it = resourceAwareIt.resources({ terminals: 1, traceWriters: 0 });\n const timeout = options.timeoutMs ?? 10_000;\n const toolchain =\n options.requires === undefined ||\n commandAvailable(options.requires.probe, {\n ...(options.requires.cwd === undefined ? {} : { cwd: options.requires.cwd }),\n ...(options.requires.timeoutMs === undefined\n ? {}\n : { timeoutMs: options.requires.timeoutMs }),\n ...(options.requires.env === undefined ? {} : { env: options.requires.env }),\n });\n const probeOptions = {\n ...(options.columns === undefined ? {} : { columns: options.columns }),\n ...(options.rows === undefined ? {} : { rows: options.rows }),\n };\n\n const title = !ptyAvailable()\n ? `adapter conformance: ${options.name} (skipped: no pseudo-terminal here)`\n : toolchain\n ? `adapter conformance: ${options.name}`\n : `adapter conformance: ${options.name} (skipped: ${options.requires?.label ?? 'toolchain'} unavailable)`;\n\n describe.skipIf(!ptyAvailable() || !toolchain)(title, { timeout: timeout * 4 }, () => {\n describe('the dormant rule', () => {\n it('opens nothing and emits no marker without an endpoint', async () => {\n const probe = await AdapterProbe.start(options.spawn(), {\n ...probeOptions,\n instrument: false,\n });\n try {\n await probe.waitForText(options.ready, timeout);\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n\n const observation = probe.observe();\n expect(observation.connections).toBe(0);\n expect(observation.messages).toHaveLength(0);\n expect(observation.text).not.toContain(MARKER_TEXT_PREFIX);\n } finally {\n await probe.stop();\n }\n });\n\n it.skipIf(options.baseline === undefined)(\n 'produces the same bytes as a build without the adapter',\n async () => {\n // Nothing is written to the child during this comparison. A\n // pseudo-terminal echoes the suite's own keystrokes, and whether an\n // echoed byte lands between two frames depends on when the app took\n // raw mode — so a stream containing our input compares the tty's\n // timing, not the adapter's output. Measured on the Ink fixture: 3\n // mismatches in 30 pairs with input (always a stray 0x09, the tab the\n // suite itself sent), 0 in 40 pairs without.\n const startup = async (\n command: AdapterCommand,\n ): Promise<{ readonly stdout: Uint8Array; readonly screen: string }> => {\n const probe = await AdapterProbe.start(command, { ...probeOptions, instrument: false });\n try {\n await probe.waitForText(options.ready, timeout);\n await settle(probe);\n const observation = probe.observe();\n return { stdout: observation.stdout, screen: observation.screen };\n } finally {\n await probe.stop();\n }\n };\n\n const instrumented = await startup(options.spawn());\n const plain = await startup((options.baseline as () => AdapterCommand)());\n if (process.platform === 'win32') {\n // ConPTY itself emits process-lifecycle control sequences. Their\n // chunking/order can differ between two identical child binaries,\n // so the raw host stream is not an application-byte oracle on\n // Windows. Feed both streams through the same terminal emulator\n // and require the complete visible grids to be identical. The\n // adjacent dormant test separately proves zero connections,\n // messages and Termwright markers.\n expect(instrumented.screen).toBe(plain.screen);\n } else {\n // POSIX PTYs expose the child stream directly: one extra escape\n // sequence from instrumentation is a real dormant-rule failure.\n expect(Buffer.from(instrumented.stdout).toString('binary')).toBe(\n Buffer.from(plain.stdout).toString('binary'),\n );\n }\n },\n );\n });\n\n describe('an instrumented session', () => {\n let probe: AdapterProbe;\n /** Everything observed before a single byte was written to the child. */\n let beforeInput: ProbeObservation;\n\n beforeEach(\n async () => {\n probe = await AdapterProbe.start(options.spawn(), probeOptions);\n await probe.waitForText(options.ready, timeout);\n await probe.waitFor(\n (observation) => snapshotsOf(observation).length > 0,\n timeout,\n 'a first snapshot from the adapter',\n );\n beforeInput = probe.observe();\n },\n // Starting the process, waiting for its first frame, and completing the\n // adapter handshake are separate bounded operations. Vitest otherwise\n // applies its 10-second hook default even though the suite has a larger\n // timeout, which makes real adapters flaky under a parallel root run.\n timeout * 4,\n );\n\n afterEach(async () => {\n await probe?.stop();\n });\n\n it('completes the handshake before anything else', async () => {\n const { messages, connections } = probe.observe();\n const first = messages[0];\n\n expect(connections).toBe(1);\n expect(first?.message.type).toBe('hello');\n const hello = first?.message as {\n protocol: string;\n adapter: { name: string; version: string };\n capabilities: readonly string[];\n };\n expect(hello.protocol).toBe(PROTOCOL_ID);\n expect(hello.adapter.name.length).toBeGreaterThan(0);\n expect(hello.adapter.version.length).toBeGreaterThan(0);\n expect(\n hello.capabilities.every((entry) =>\n (ADAPTER_CAPABILITIES as readonly string[]).includes(entry),\n ),\n ).toBe(true);\n expect(hello.capabilities).toContain('tree');\n // A second hello, or a hello after other traffic, is a protocol fault.\n expect(messages.filter((entry) => entry.message.type === 'hello')).toHaveLength(1);\n\n // Nothing may precede the handshake, log records least of all: their\n // budget is granted *in* the reply to this message.\n const firstLog = messages.findIndex((entry) => entry.message.type === 'log');\n expect(firstLog === -1 || firstLog > 0).toBe(true);\n });\n\n it(\n options.treeBeforeInput === undefined\n ? 'publishes a usable tree before any input'\n : `publishes a usable tree before any input (exempt: ${options.treeBeforeInput.reason})`,\n { skip: options.treeBeforeInput !== undefined },\n () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n\n // A driver launches an app and addresses it. An adapter that only\n // publishes once a key has been pressed is not addressable at that\n // moment, and a suite that sends input before looking would never\n // notice — which is exactly how this class of bug reached a shipped\n // adapter.\n expect(latest, 'no snapshot arrived before any input was sent').toBeDefined();\n expect(latest?.nodes.length ?? 0).toBeGreaterThan(0);\n expect(latest?.rootIds.length ?? 0).toBeGreaterThan(0);\n\n // A tree of one anonymous root is empty in every sense that matters:\n // nothing in it can be located by role and name.\n const addressable = (latest?.nodes ?? []).filter(\n (node) => node.name.length > 0 || node.testId !== undefined,\n );\n expect(\n addressable.length,\n 'the tree has no node that a locator could address',\n ).toBeGreaterThan(0);\n },\n );\n\n it('publishes only valid snapshots, bound to this session', async () => {\n const observation = probe.observe();\n const snapshots = snapshotsOf(observation);\n\n expect(observation.faults).toEqual([]);\n expect(snapshots.length).toBeGreaterThan(0);\n for (const snapshot of snapshots) {\n expect(validateSnapshot(snapshot, DEFAULT_LIMITS)).toMatchObject({ ok: true });\n expect(snapshot.sessionId).toBe(probe.sessionId);\n expect(snapshot.v).toBe(2);\n const ids = new Set(snapshot.nodes.map((node) => node.id));\n for (const node of snapshot.nodes) {\n if (node.parentId === undefined) expect(snapshot.rootIds).toContain(node.id);\n else expect(ids.has(node.parentId)).toBe(true);\n }\n }\n\n const revisions = snapshots.map((snapshot) => snapshot.revision);\n expect([...revisions]).toEqual([...new Set(revisions)].sort((left, right) => left - right));\n });\n\n it.skipIf(options.expectIntendedGeometry !== true)(\n 'publishes intended geometry in viewport cells',\n () => {\n const snapshots = snapshotsOf(probe.observe());\n const latest = snapshots[snapshots.length - 1];\n expect(latest).toBeDefined();\n const bounded =\n latest?.nodes.filter((node) => node.geometry.intendedRect.status === 'known') ?? [];\n expect(bounded.length).toBeGreaterThan(0);\n for (const node of bounded) {\n const bounds =\n node.geometry.intendedRect.status === 'known'\n ? node.geometry.intendedRect.value\n : undefined;\n expect(bounds).toBeDefined();\n if (bounds === undefined) continue;\n expect(bounds.row).toBeGreaterThanOrEqual(0);\n expect(bounds.column).toBeGreaterThanOrEqual(0);\n expect(bounds.row).toBeLessThan(latest?.rows ?? 0);\n expect(bounds.column).toBeLessThan(latest?.columns ?? 0);\n }\n },\n );\n\n it('orders every revision as snapshot, then commit, then marker', async () => {\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n await probe.waitFor(\n (observation) => observation.markers.length >= 2,\n timeout,\n 'a second render marker',\n );\n\n // The socket and the terminal are independent streams, so a marker can\n // be read before the frame describing the same revision has been\n // parsed. The contract is that each revision *eventually* has all\n // three parts; waiting for that is not leniency, it is the difference\n // between asserting the contract and asserting arrival order.\n const complete = (observation: ProbeObservation): boolean =>\n observation.markers.every(\n (marker) =>\n observation.messages.some(\n (entry) =>\n entry.message.type === 'snapshot' &&\n (entry.message as { snapshot: SemanticSnapshot }).snapshot.revision ===\n marker.revision,\n ) &&\n observation.messages.some(\n (entry) =>\n entry.message.type === 'revision-commit' &&\n entry.message.revision === marker.revision,\n ),\n );\n await probe.waitFor(complete, timeout, 'every marker paired with a snapshot and a commit');\n\n const observation = probe.observe();\n const markers = observation.markers;\n expect(markers.length).toBeGreaterThan(0);\n expect(markers.map((marker) => marker.revision)).toEqual(\n [...markers.map((marker) => marker.revision)].sort((left, right) => left - right),\n );\n\n for (const marker of markers) {\n const snapshot = observation.messages.find(\n (entry) =>\n entry.message.type === 'snapshot' &&\n (entry.message as { snapshot: SemanticSnapshot }).snapshot.revision ===\n marker.revision,\n );\n const commit = observation.messages.find(\n (entry) =>\n entry.message.type === 'revision-commit' &&\n entry.message.revision === marker.revision,\n );\n expect(snapshot, `no snapshot for revision ${marker.revision}`).toBeDefined();\n expect(commit, `no commit for revision ${marker.revision}`).toBeDefined();\n\n const snapshotIndex = observation.messages.indexOf(snapshot!);\n expect(snapshotIndex).toBeLessThan(observation.messages.indexOf(commit!));\n }\n\n // Each marker commits a frame, so there must be output between one\n // marker and the next, and the first one cannot open the stream.\n //\n // The socket and the terminal are two independent streams, and the\n // event loop may deliver a later chunk of one before an earlier message\n // of the other. Comparing a marker's byte offset against the stdout\n // position recorded when its frame arrived therefore measures delivery\n // scheduling, not adapter ordering, and is deliberately not asserted.\n let previousEnd = 0;\n for (const marker of markers) {\n expect(marker.offset).toBeGreaterThan(previousEnd);\n previousEnd = marker.offset;\n }\n });\n\n const conventions = options.conventions ?? {};\n const readme =\n conventions.readmePath !== undefined && existsSync(conventions.readmePath)\n ? readFileSync(conventions.readmePath, 'utf8')\n : '';\n const declared = parseDeclaredDeviations(readme);\n const outcomes: ConventionOutcome[] = [];\n\n it('reads the deviations its README declares', () => {\n if (conventions.readmePath === undefined) return;\n if (!readme.includes('## Deviations')) return;\n\n // A section the parser cannot read is worse than a missing one: the\n // three-state logic silently collapses to two, and every documented\n // limitation starts reporting as an error against the adapter that\n // took the trouble to declare it.\n //\n // Detectable without understanding any shape: a section that *has\n // structure* — bullets, table rows, bold lead-ins — but yields no\n // entries is a parser gap. A section that is plain prose saying there\n // is nothing to declare is not, and must not be failed for it.\n const start = readme.indexOf('## Deviations');\n const rest = readme.slice(start + '## Deviations'.length);\n const end = rest.indexOf('\\n## ');\n const section = end < 0 ? rest : rest.slice(0, end);\n const structured = /^\\s*[-*|]/mu.test(section) || section.includes('**');\n if (!structured) return;\n\n expect(\n declared.size,\n `${options.name} has a \"## Deviations\" section with entries this suite could not ` +\n 'read; its declarations would be invisible and its documented limitations would ' +\n 'report as errors. Teach `parseDeclaredDeviations` the shape it uses.',\n ).toBeGreaterThan(0);\n });\n\n /**\n * Runs one rule and decides what its result means.\n *\n * Three states, not two. A rule an adapter cannot follow and *says* it\n * cannot follow is a documented limitation, not a failure — failing it\n * would give the first author who honestly describes their framework a\n * red run for doing exactly what rule 6 asks, which is the shortest path\n * to people hiding deviations instead of declaring them.\n *\n * A check that passes while a declaration exists is deliberately *not*\n * called stale. One rule has more aspects than a subprocess can observe:\n * Ink declares a rule 3 limitation about native identifiers while\n * satisfying the annotation half of the same rule, and both are true.\n * The summary records the coincidence so a reader can re-read the\n * README; calling it a defect would be a false signal, and a suite that\n * cries wolf gets ignored.\n */\n const convention = (rule: string, what: string, check: () => string | null): void => {\n const failure = check();\n const titles = declared.get(rule) ?? [];\n if (failure === null) {\n outcomes.push(\n titles.length === 0\n ? { rule, what, status: 'compliant' }\n : { rule, what, status: 'checked-despite-declaration', detail: titles.join('; ') },\n );\n return;\n }\n if (titles.length > 0) {\n outcomes.push({\n rule,\n what,\n status: 'documented',\n detail: `${titles.join('; ')} — ${failure}`,\n });\n return;\n }\n outcomes.push({ rule, what, status: 'violation', detail: failure });\n expect.fail(`convention ${rule} (${what}): ${failure}`);\n };\n\n afterAll(() => {\n writeConventionSummary(options.name, declared, outcomes);\n });\n\n it('convention 3: an author-annotated test id reaches the wire', () => {\n if (conventions.annotatedTestId === undefined) return;\n const wanted = conventions.annotatedTestId;\n convention('3', 'an annotated test id reaches the wire', () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n const node = latest?.nodes.find((entry) => entry.testId === wanted);\n return node === undefined\n ? `no node carries the test id ${JSON.stringify(wanted)}`\n : null;\n });\n });\n\n it('convention 5: an empty textbox publishes an empty value', () => {\n if (conventions.emptyTextboxTestId === undefined) return;\n const wanted = conventions.emptyTextboxTestId;\n convention('5', 'an empty textbox publishes an empty value', () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n const node = latest?.nodes.find((entry) => entry.testId === wanted);\n if (node === undefined) return `no node carries the test id ${JSON.stringify(wanted)}`;\n // `''` means the field is empty; absent means \"not a value-bearing\n // widget\". A wire format that drops empty strings turns the first\n // into the second and makes `toHaveValue('')` unassertable.\n return node.value?.status === 'known' && node.value.value === ''\n ? null\n : `the value is ${JSON.stringify(node.value)}, not an empty known value`;\n });\n });\n\n it('convention 5: value is derived only for value-bearing roles', () => {\n convention('5', 'value is derived only for value-bearing roles', () => {\n const annotated = new Set(conventions.annotatedValues ?? []);\n const nodes = snapshotsOf(beforeInput).at(-1)?.nodes ?? [];\n const offenders = nodes.filter(\n (node) =>\n node.value !== undefined &&\n node.role !== 'textbox' &&\n node.role !== 'progressbar' &&\n !(node.testId !== undefined && annotated.has(node.testId)),\n );\n if (offenders.length > 0) {\n return `derived a value outside {textbox, progressbar}: ${offenders\n .map((node) => `${node.role} ${JSON.stringify(node.name)}`)\n .join(', ')}`;\n }\n // A boolean is a state, not contents: publishing `value: \"true\"`\n // makes a checkbox look like a textbox containing that word.\n const booleans = nodes.filter(\n (node) =>\n node.value?.status === 'known' &&\n (node.value.value === 'true' || node.value.value === 'false'),\n );\n return booleans.length === 0\n ? null\n : `published a boolean as a value on ${booleans.map((node) => node.role).join(', ')}`;\n });\n });\n\n it('convention 2: no container is named from the text it contains', () => {\n convention('2', 'no container is named from the text it contains', () => {\n const nodes = snapshotsOf(beforeInput).at(-1)?.nodes ?? [];\n const children = new Map<string, string[]>();\n for (const node of nodes) {\n if (node.parentId === undefined) continue;\n children.set(node.parentId, [...(children.get(node.parentId) ?? []), node.id]);\n }\n const descendantText = (id: string): string[] => {\n const out: string[] = [];\n const pending = [...(children.get(id) ?? [])];\n while (pending.length > 0) {\n const next = nodes.find((node) => node.id === pending.pop());\n if (next === undefined) continue;\n if (next.name.length > 0) out.push(next.name);\n pending.push(...(children.get(next.id) ?? []));\n }\n return out;\n };\n\n // Naming containers from content is what makes\n // getByRole('region', {name: 'Approve'}) match the dialog *around*\n // the button, so every ancestor of a label becomes a plausible match\n // for it. Both failure shapes are visible from the tree alone.\n const offenders = nodes\n .filter((node) => CONTAINER_ROLES.has(node.role) && node.name.length > 0)\n .filter((node) => {\n const texts = descendantText(node.id);\n const joined = texts.join(' ').replace(/\\s+/gu, ' ').trim();\n return texts.includes(node.name) || (joined.length > 0 && joined === node.name);\n });\n return offenders.length === 0\n ? null\n : `named from content: ${offenders.map((node) => `${node.role} ${JSON.stringify(node.name)}`).join(', ')}`;\n });\n });\n\n it('convention 2: a container with no label of its own has an empty name', () => {\n if (conventions.unnamedContainerTestId === undefined) return;\n const wanted = conventions.unnamedContainerTestId;\n convention('2', 'an unlabelled container has an empty name', () => {\n const node = snapshotsOf(beforeInput)\n .at(-1)\n ?.nodes.find((entry) => entry.testId === wanted);\n if (node === undefined) return `no node carries the test id ${JSON.stringify(wanted)}`;\n return node.name === '' ? null : `the container is named ${JSON.stringify(node.name)}`;\n });\n });\n\n it.skipIf(conventions.readmePath === undefined)(\n 'declares its deviations in its README (advisory)',\n () => {\n // Rules 1, 2 and 4 cannot be judged from outside a subprocess, so the\n // README is the only evidence that a difference was considered rather\n // than overlooked. Advisory on purpose: a missing heading is a\n // documentation gap, not a broken adapter, and failing here would\n // make a conformance run red for something no user can observe.\n const path = conventions.readmePath as string;\n const text = existsSync(path) ? readFileSync(path, 'utf8') : '';\n if (!text.includes('## Deviations')) {\n // Rule 6 follows the adapter, not the package: something that\n // publishes no tree has nothing to declare. Anything reaching this\n // suite does publish one, so the heading is expected here.\n process.stderr.write(\n `conformance: ${options.name} has no \"## Deviations\" section in ${path}; ` +\n 'rules 1, 2 and 4 are unverifiable from outside, so an undeclared difference is invisible\\n',\n );\n }\n expect(true).toBe(true);\n },\n );\n\n it('sends log records only if it negotiated the channel', async () => {\n const hello = beforeInput.messages[0]?.message as { capabilities: readonly string[] };\n if (hello.capabilities.includes('logs')) return;\n\n // An adapter is free not to support logs. What it is not free to do is\n // send records anyway: the budget granted in the handshake is what\n // bounds them, and one that was never granted cannot bound anything.\n // The driver closes the channel over this, so an adapter that does it\n // is broken in production rather than merely untidy.\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n expect(\n probe.observe().logs,\n 'the adapter sent log records without announcing the logs capability',\n ).toEqual([]);\n });\n\n it.skipIf(options.logs === undefined)(\n 'carries a log record without printing it',\n async () => {\n const logs = options.logs as NonNullable<AdapterConformanceOptions['logs']>;\n const hello = probe.observe().messages[0]?.message as { capabilities: readonly string[] };\n expect(\n hello.capabilities.includes('logs'),\n 'the registration declares logs, but the adapter never announced the capability',\n ).toBe(true);\n\n const before = probe.observe().logs.length;\n if (logs.input !== undefined) await probe.write(logs.input);\n // An app that logs on its own may already have; one that logs on demand\n // has just been asked to. Either way the wait is for a record.\n await probe.waitFor(\n (observation) => observation.logs.length > (logs.input === undefined ? 0 : before),\n timeout,\n 'a log record over the negotiated channel',\n );\n\n const observation = probe.observe();\n const record = observation.logs.find((entry) => entry.message.includes(logs.expect));\n expect(record, `no log record matched ${JSON.stringify(logs.expect)}`).toBeDefined();\n // `seq` is a non-negative counter, so the first record of a session is\n // legitimately 0; what matters is the relation between records.\n expect(record?.seq).toBeGreaterThanOrEqual(0);\n\n // Strictly increasing within a session: a consumer counting errors must\n // not be able to count one twice, and a gap must mean a real loss.\n const seqs = observation.logs.map((entry) => entry.seq);\n expect(seqs).toEqual([...seqs].sort((left, right) => left - right));\n expect(new Set(seqs).size).toBe(seqs.length);\n\n // The whole point of the capability: the record reaches the driver and\n // never the terminal. A TUI that printed it would corrupt its render.\n expect(observation.screen).not.toContain(logs.expect);\n expect(observation.text).not.toContain(logs.expect);\n },\n );\n\n it('keeps the application alive when the channel is cut', async () => {\n const before = probe.observe();\n probe.cutChannel();\n\n await probe.write(options.interaction.input);\n // The observed text is cumulative, so `waitForText` would match output\n // the application produced before the cut. Growth is what proves it is\n // still rendering.\n await probe.waitFor(\n (observation) => observation.text.length > before.text.length,\n timeout,\n 'any further output from the child',\n );\n const after = probe.observe();\n\n // The application keeps rendering; the adapter goes quiet and does not\n // reconnect behind the driver's back.\n expect(after.text.length).toBeGreaterThan(before.text.length);\n expect(after.connections).toBe(1);\n expect(probe.exitStatus).toBeNull();\n\n await probe.write(options.quit.input);\n const status = await probe.waitForExit(timeout);\n if (options.quit.exitCode !== undefined) expect(status.code).toBe(options.quit.exitCode);\n });\n });\n });\n}\n","/**\n * A minimal driver, built for one job: watching what an adapter actually puts\n * on the wire.\n *\n * `@termwright/driver` deliberately hides frame ordering behind a settled tree,\n * which is the right API for tests of applications and the wrong one for tests\n * of adapters. The probe therefore speaks the protocol itself — endpoint,\n * handshake, framing, marker verification — and records every message together\n * with how many stdout bytes had been written when it arrived. That is what\n * makes the §4.3 ordering contract (snapshot → commit → marker-after-frame)\n * observable at all.\n *\n * It does emulate a terminal, because it has to: an adapter is free to draw by\n * positioning each run of cells (tview does), so the text a user sees exists\n * only on a rendered grid and never as contiguous bytes on the wire. Waiting\n * for text therefore reads the grid, while marker offsets read the byte stream.\n *\n * It is still not a second driver: it never locates and never acts.\n */\nimport { type Server, type Socket } from 'node:net';\nimport { readFileSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomBytes, randomUUID } from 'node:crypto';\nimport {\n DEFAULT_LIMITS,\n ENV_ENDPOINT,\n ENV_TOKEN,\n MARKER_OSC_CODE,\n MARKER_OSC_PREFIX,\n parseAdapterMessage,\n PROTOCOL_ID,\n createFrameDecoder,\n encodeFrame,\n generateToken,\n verifyMarkerPayload,\n type AdapterToDriverMessage,\n type HelloAckMessage,\n type LogRecord,\n} from '@termwright/protocol';\nimport type { Terminal } from '@xterm/headless';\nimport { createNativePtyBackend, VtScreen, type PtyProcess } from '@termwright/driver/experimental';\nimport { environment } from './pty.js';\nimport { ProbePeerOwner } from './probe-peer-owner.js';\nimport { ProbeProcessShutdown } from './probe-process-shutdown.js';\nimport { ProbeStartupTransaction } from './probe-startup.js';\n\n/** How a fixture is started. Everything else about it is opaque to the probe. */\nexport interface AdapterCommand {\n readonly command: readonly string[];\n readonly env?: Readonly<Record<string, string>>;\n readonly cwd?: string;\n}\n\n/** One message the adapter sent, stamped with the stdout position at arrival. */\nexport interface RecordedMessage {\n readonly message: AdapterToDriverMessage;\n /** Bytes of stdout the probe had received when this frame was parsed. */\n readonly stdoutBytes: number;\n readonly atMs: number;\n}\n\n/** One verified render marker found in stdout. */\nexport interface RecordedMarker {\n readonly revision: number;\n /** Offset of the marker's first byte in the stdout stream. */\n readonly offset: number;\n readonly atMs: number;\n}\n\n/** Log budget the probe grants to an adapter that announces `logs`. */\nconst LOG_BUDGET = Object.freeze({ enabled: true, maxRecordsPerSecond: 200, burst: 400 });\n\n/** A frame the probe refused; a conforming adapter produces none. */\nexport interface RecordedFault {\n readonly code: string;\n readonly detail: string;\n}\n\nexport interface ProbeOptions {\n readonly columns?: number;\n readonly rows?: number;\n /** Set `false` to withhold the instrumentation env — the dormant-run case. */\n readonly instrument?: boolean;\n}\n\n/** Everything the probe observed, readable while the child is still running. */\nexport interface ProbeObservation {\n readonly messages: readonly RecordedMessage[];\n readonly markers: readonly RecordedMarker[];\n readonly faults: readonly RecordedFault[];\n readonly connections: number;\n readonly stdout: Uint8Array;\n /** Raw bytes decoded as UTF-8: what was written, in order, escapes included. */\n readonly text: string;\n /** The visible grid, one row per line — what a user would actually see. */\n readonly screen: string;\n /** Application log records the adapter sent, in arrival order. */\n readonly logs: readonly LogRecord[];\n}\n\n/**\n * A marker on the wire: `OSC 8487 ; twm;<rev>;<mac>` closed by BEL or ST.\n *\n * Both terminators are matched because both are legal — an implementation\n * emits BEL, but a receiver that only understood BEL would reject a\n * conforming adapter, and this probe stands in for a receiver.\n */\nconst MARKER_PATTERN = new RegExp(\n `\\\\x1b\\\\]${MARKER_OSC_CODE};(${MARKER_OSC_PREFIX}[0-9]+;[A-Za-z0-9_-]+)(?:\\\\x07|\\\\x1b\\\\\\\\)`,\n 'gu',\n);\n\n/**\n * Runs one fixture under a pseudo-terminal with a protocol endpoint attached.\n *\n * @example\n * ```ts\n * const probe = await AdapterProbe.start({ command: ['node', 'app.mjs'] }, {});\n * await probe.waitForText('Ready');\n * await probe.write('\\t');\n * const { messages, markers } = probe.observe();\n * await probe.stop();\n * ```\n */\nexport class AdapterProbe {\n readonly sessionId: string;\n readonly token: string;\n\n readonly #server: Server | null;\n readonly #directory: string | null;\n readonly #pty: PtyProcess;\n readonly #vt: VtScreen;\n readonly #terminal: Terminal;\n #detachTerminalResponse: (() => void) | null;\n readonly #startedAt = performance.now();\n readonly #messages: RecordedMessage[] = [];\n readonly #markers: RecordedMarker[] = [];\n readonly #faults: RecordedFault[] = [];\n readonly #logs: LogRecord[] = [];\n #chunks: Uint8Array[] = [];\n #bytes = 0;\n #text = '';\n #markerScanFrom = 0;\n #connections = 0;\n #socket: Socket | null = null;\n readonly #peers: ProbePeerOwner;\n #exit: { code: number | null; signal: string | null } | null = null;\n /** Where the adapter writes its own account of attaching, if it writes one. */\n #debugFile: string | null = null;\n readonly #shutdown: ProbeProcessShutdown;\n readonly #changeWaiters = new Set<() => void>();\n\n private constructor(\n identity: { readonly sessionId: string; readonly token: string },\n server: Server | null,\n directory: string | null,\n pty: PtyProcess,\n size: { readonly columns: number; readonly rows: number },\n peers: ProbePeerOwner,\n ) {\n this.sessionId = identity.sessionId;\n this.token = identity.token;\n this.#server = server;\n this.#directory = directory;\n this.#pty = pty;\n this.#peers = peers;\n this.#vt = new VtScreen({\n columns: size.columns,\n rows: size.rows,\n scrollbackLines: 1_000,\n });\n this.#terminal = this.#vt.terminal;\n // This probe is the terminal emulator, so it owns terminal-generated\n // replies just like TerminalSession does. Without this bridge the pinned\n // Windows host can block a framework's GCSBI on its private cursor query,\n // leaving the first frame visible while every later draw waits forever.\n this.#detachTerminalResponse = this.#vt.onResponse((response) =>\n this.#writeTerminalResponse(response.data),\n );\n this.#shutdown = new ProbeProcessShutdown({\n pty,\n closeAdmission: () =>\n this.#server === null ? Promise.resolve() : this.#peers.close(this.#server),\n closeTerminalResponseAdmission: () => this.#closeTerminalResponseAdmission(),\n drainParser: () => this.#vt.drain(),\n disposeParser: () => {\n this.#notifyChange();\n this.#vt.dispose();\n },\n removeArtifacts: () => this.#removeArtifacts(),\n });\n }\n\n /** Creates the endpoint (unless dormant), then spawns the fixture. */\n static async start(command: AdapterCommand, options: ProbeOptions = {}): Promise<AdapterProbe> {\n const instrument = options.instrument ?? true;\n const sessionId = randomUUID();\n const token = generateToken();\n const startup = new ProbeStartupTransaction();\n try {\n await startup.acquireEndpoint(instrument);\n const { server, directory, endpoint, peers } = startup;\n const env = environment(command.env);\n // A dormant run must not merely lack our endpoint — it must not inherit one\n // from whatever process is running the suite.\n delete env[ENV_ENDPOINT];\n delete env[ENV_TOKEN];\n if (endpoint !== null) {\n env[ENV_ENDPOINT] = endpoint;\n env[ENV_TOKEN] = token;\n }\n\n // The adapter's own account of why it did or did not attach. The probe can\n // only see the outside — no connection arrived — which leaves \"wrong\n // transport\", \"driver not listening\" and \"never started\" indistinguishable.\n // The clients write that distinction here (1bbe0f9), and a failure quotes\n // the file, so the attribution lands in the message rather than in an\n // artifact somebody has to go and find. A path, never `TERMWRIGHT_DEBUG=1`:\n // that means \"log to stderr\", which under a pty lands in the middle of the\n // frame this suite makes assertions about.\n startup.debugFile = join(\n tmpdir(),\n `termwright-adapter-debug-${randomBytes(8).toString('hex')}.log`,\n );\n env['TERMWRIGHT_DEBUG_FILE'] = startup.debugFile;\n\n const size = { columns: options.columns ?? 80, rows: options.rows ?? 24 };\n startup.pty = createNativePtyBackend().spawn({\n command: command.command,\n ...(command.cwd === undefined ? {} : { cwd: command.cwd }),\n env,\n columns: size.columns,\n rows: size.rows,\n // This probe is itself the terminal emulator. Do not inherit a harness\n // shell's `TERM=dumb`: the child is connected to our xterm-compatible\n // parser regardless of which terminal launched Vitest.\n term: 'xterm-256color',\n });\n const pty = startup.pty;\n\n const probe = new AdapterProbe({ sessionId, token }, server, directory, pty, size, peers);\n probe.#debugFile = startup.debugFile;\n\n pty.onData((data) => probe.#onData(data));\n pty.onExit((status) => {\n probe.#exit = status;\n probe.#shutdown.observeExit(status);\n probe.#notifyChange();\n });\n peers.activate((socket) => probe.#onConnection(socket as Socket));\n return probe;\n } catch (error) {\n return startup.rollback(error);\n }\n }\n\n /** Everything observed so far. Safe to call at any point. */\n observe(): ProbeObservation {\n return {\n messages: [...this.#messages],\n markers: [...this.#markers],\n faults: [...this.#faults],\n connections: this.#connections,\n stdout: this.#stdout(),\n text: this.#text,\n screen: this.screenText(),\n logs: this.#logs.map((entry) => entry),\n };\n }\n\n /** The visible grid as text, trailing whitespace trimmed per row. */\n screenText(): string {\n const buffer = this.#terminal.buffer.active;\n const rows: string[] = [];\n for (let row = 0; row < this.#terminal.rows; row += 1) {\n rows.push(buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? '');\n }\n return rows.join('\\n');\n }\n\n /** The child's exit status, or `null` while it is still running. */\n get exitStatus(): { code: number | null; signal: string | null } | null {\n return this.#exit;\n }\n\n /** Writes raw bytes to the child, exactly as a terminal would. */\n async write(input: string): Promise<void> {\n this.#pty.write(new TextEncoder().encode(input));\n await Promise.resolve();\n }\n\n /**\n * Resolves once `needle` appears on the rendered grid.\n *\n * Matching the byte stream instead would only work for adapters that happen\n * to write their text contiguously: a framework that positions each run of\n * cells never emits `focus: reject` as those twelve bytes in a row.\n */\n async waitForText(needle: string | RegExp, timeoutMs = 10_000): Promise<void> {\n const deadline = performance.now() + timeoutMs;\n for (;;) {\n const change = this.#armChange(deadline);\n const screen = this.screenText();\n if (needle instanceof RegExp ? needle.test(screen) : screen.includes(needle)) {\n change.cancel();\n return;\n }\n if (performance.now() >= deadline) {\n change.cancel();\n throw new Error(\n `adapter conformance: ${String(needle)} never appeared on the fixture's screen\\n` +\n `screen was:\\n${screen}`,\n );\n }\n await change.wait();\n }\n }\n\n /**\n * Resolves once `predicate` holds over the current observation.\n *\n * `what` names the thing being waited for, and the failure carries what the\n * probe could see when it gave up. \"Condition never became true\" is not a\n * result anybody can act on: an adapter that never connected, one that\n * connected and published nothing, and one whose binary died all produce it,\n * and only the observation tells them apart.\n */\n async waitFor(\n predicate: (observation: ProbeObservation) => boolean,\n timeoutMs = 10_000,\n what = 'the condition',\n ): Promise<void> {\n const deadline = performance.now() + timeoutMs;\n for (;;) {\n const change = this.#armChange(deadline);\n if (predicate(this.observe())) {\n change.cancel();\n return;\n }\n if (performance.now() >= deadline) {\n change.cancel();\n throw new Error(`adapter conformance: ${what} never happened — ${this.describe()}`);\n }\n await change.wait();\n }\n }\n\n /** What the probe has seen so far, for a failure that has to explain itself. */\n describe(): string {\n const { messages, connections } = this.observe();\n const kinds = new Map<string, number>();\n for (const recorded of messages) {\n const kind = recorded.message.type;\n kinds.set(kind, (kinds.get(kind) ?? 0) + 1);\n }\n const traffic =\n kinds.size === 0 ? 'no messages' : [...kinds].map(([kind, n]) => `${kind}×${n}`).join(', ');\n const screen = this.screenText()\n .trimEnd()\n .split('\\n')\n .filter((line) => line.trim() !== '');\n const exit = this.#exit === null ? 'still running' : `exited ${JSON.stringify(this.#exit)}`;\n return (\n `${connections} connection(s) to the endpoint, ${traffic}; the child is ${exit}; ` +\n `last screen line: ${JSON.stringify(screen.at(-1) ?? '')}\\n${this.#adapterAccount()}`\n );\n }\n\n /**\n * What the adapter says about its own attach, if the client writes it.\n *\n * The outside view cannot tell \"dialled the wrong transport\" from \"the driver\n * was not listening\" from \"the process never started\" — all three look like\n * no connection. The clients write that distinction to `TERMWRIGHT_DEBUG_FILE`,\n * so it is quoted here rather than left in a file nobody opens. An adapter\n * that writes nothing is itself an answer, and says so.\n */\n #adapterAccount(): string {\n if (this.#debugFile === null) return 'adapter debug log: not requested';\n let contents: string;\n try {\n contents = readFileSync(this.#debugFile, 'utf8');\n } catch {\n return `adapter debug log: nothing written to ${this.#debugFile} — the client either predates the log or never ran`;\n }\n const lines = contents.trimEnd().split('\\n').slice(-12);\n return `adapter debug log (last ${lines.length} line(s)):\\n ${lines.join('\\n ')}`;\n }\n\n /** Waits for the child to exit and returns its status. */\n async waitForExit(timeoutMs = 10_000): Promise<{ code: number | null; signal: string | null }> {\n const deadline = performance.now() + timeoutMs;\n while (this.#exit === null) {\n const change = this.#armChange(deadline);\n if (this.#exit !== null) {\n change.cancel();\n break;\n }\n if (performance.now() >= deadline) {\n change.cancel();\n throw new Error('adapter conformance: the fixture never exited');\n }\n await change.wait();\n }\n return this.#exit;\n }\n\n /** Cuts the semantic channel without touching the child: the disconnect case. */\n cutChannel(): void {\n this.#socket?.destroy();\n this.#socket = null;\n }\n\n /** Stops the child and releases the endpoint. Idempotent. */\n stop(): Promise<void> {\n return this.#shutdown.stop();\n }\n\n async #removeArtifacts(): Promise<void> {\n const failures: unknown[] = [];\n if (this.#directory !== null) {\n try {\n await rm(this.#directory, { recursive: true, force: true });\n } catch (error) {\n failures.push(error);\n }\n }\n if (this.#debugFile !== null) {\n try {\n await rm(this.#debugFile, { force: true });\n } catch (error) {\n failures.push(error);\n }\n }\n this.#notifyChange();\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1)\n throw new AggregateError(failures, 'adapter probe artifact cleanup failed');\n }\n\n // -------------------------------------------------------------------------\n\n #stdout(): Uint8Array {\n const out = new Uint8Array(this.#bytes);\n let offset = 0;\n for (const chunk of this.#chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n this.#chunks = [out];\n return out;\n }\n\n #onData(data: Uint8Array): void {\n this.#chunks.push(data);\n this.#bytes += data.length;\n this.#text += Buffer.from(data).toString('utf8');\n void this.#vt.write(data).finally(() => this.#notifyChange());\n this.#scanMarkers();\n this.#notifyChange();\n }\n\n /** Returns emulator-owned replies without presenting them as user input. */\n #writeTerminalResponse(response: string): void {\n const data = Buffer.from(response, 'utf8');\n const write = this.#pty.writeTerminalResponse;\n if (write === undefined) {\n this.#pty.write(data, 'raw');\n return;\n }\n write.call(this.#pty, data);\n }\n\n #closeTerminalResponseAdmission(): void {\n this.#detachTerminalResponse?.();\n this.#detachTerminalResponse = null;\n }\n\n /** Finds render markers in the byte stream and verifies each against the token. */\n #scanMarkers(): void {\n MARKER_PATTERN.lastIndex = this.#markerScanFrom;\n for (;;) {\n const match = MARKER_PATTERN.exec(this.#text);\n if (match === null) break;\n const payload = match[1] ?? '';\n const verified = verifyMarkerPayload(payload, this.token, this.sessionId);\n if (verified === null) {\n this.#faults.push({\n code: 'marker',\n detail: `marker did not verify: ${JSON.stringify(payload)}`,\n });\n } else {\n this.#markers.push({ revision: verified.revision, offset: match.index, atMs: this.#now() });\n }\n this.#markerScanFrom = match.index + match[0].length;\n }\n MARKER_PATTERN.lastIndex = 0;\n }\n\n #onConnection(socket: Socket): void {\n this.#connections += 1;\n this.#notifyChange();\n if (this.#socket !== null) {\n // One adapter per session; a second connection is a conformance failure.\n this.#faults.push({\n code: 'second-connection',\n detail: 'the adapter opened a second channel',\n });\n socket.destroy();\n return;\n }\n this.#socket = socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n socket.on('data', (chunk: Buffer) => {\n let frames: readonly unknown[];\n try {\n frames = decoder.push(chunk);\n } catch (error) {\n this.#faults.push({\n code: 'framing',\n detail: error instanceof Error ? error.message : String(error),\n });\n this.#notifyChange();\n socket.destroy();\n return;\n }\n for (const frame of frames) this.#onFrame(socket, frame);\n });\n socket.on('close', () => {\n if (this.#socket === socket) this.#socket = null;\n this.#notifyChange();\n });\n }\n\n #onFrame(socket: Socket, frame: unknown): void {\n // The probe parses with the real protocol parser: a hand-written check\n // would only prove that the fixture agrees with itself.\n const parsed = parseAdapterMessage(frame, DEFAULT_LIMITS);\n if (!parsed.ok) {\n this.#faults.push({ code: parsed.code, detail: parsed.detail });\n this.#notifyChange();\n return;\n }\n this.#messages.push({ message: parsed.message, stdoutBytes: this.#bytes, atMs: this.#now() });\n if (parsed.message.type === 'log') this.#logs.push(parsed.message.record);\n this.#notifyChange();\n if (parsed.message.type !== 'hello') return;\n\n const ack: HelloAckMessage = {\n type: 'hello-ack',\n protocol: PROTOCOL_ID,\n sessionId: this.sessionId,\n limits: DEFAULT_LIMITS,\n subscribe: 'snapshots',\n marker: { enabled: parsed.message.capabilities.includes('render-revisions') },\n // Granted only to an adapter that asked: an adapter that never announced\n // `logs` must not be handed a budget it can then claim it was given.\n ...(parsed.message.capabilities.includes('logs') ? { logs: LOG_BUDGET } : {}),\n };\n socket.write(encodeFrame(ack, DEFAULT_LIMITS.maxFrameBytes));\n }\n\n #now(): number {\n return performance.now() - this.#startedAt;\n }\n\n #notifyChange(): void {\n for (const resolve of [...this.#changeWaiters]) resolve();\n }\n\n #armChange(deadline: number): { wait(): Promise<void>; cancel(): void } {\n let settled = false;\n let resolvePromise!: () => void;\n const finish = () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n this.#changeWaiters.delete(finish);\n resolvePromise();\n };\n const promise = new Promise<void>((resolve) => {\n resolvePromise = resolve;\n });\n const timer = setTimeout(finish, Math.max(0, deadline - performance.now()));\n timer.unref?.();\n this.#changeWaiters.add(finish);\n return { wait: () => promise, cancel: finish };\n }\n}\n\n/** The marker prefix, re-exported so suites can assert on dormant output. */\nexport const MARKER_TEXT_PREFIX = `\\x1b]${MARKER_OSC_CODE};${MARKER_OSC_PREFIX}`;\n","/**\n * Shared harness plumbing for the conformance suites: fixture resolution,\n * pseudo-terminal availability and session bookkeeping.\n *\n * Every suite in this package drives real child processes. Where no PTY can be\n * opened — a sandboxed CI runner, a missing prebuild — the suites skip rather\n * than fail, because \"this machine cannot open a terminal\" is not a conformance\n * result. Set `TERMWRIGHT_SKIP_PTY=1` to skip them explicitly.\n */\nimport { spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { launchTerminal, type LaunchOptions, type TerminalHarness } from '@termwright/driver';\nimport { nativePtyAvailable } from '@termwright/driver/experimental';\nimport { withProbe } from '@termwright/probe-ink';\n\n/**\n * Absolute path of a fixture that ships with this package.\n *\n * The fixtures are shipped as sources rather than bundled, because they are\n * meant to be launched as `node <path>` by suites in other packages and in\n * other languages' CI. They are therefore located from the package root, which\n * is the one anchor that is the same whether this module was loaded from `src/`\n * during development or from the bundled `dist/`.\n */\nexport function fixturePath(name: string): string {\n return join(packageRoot(), 'src', 'fixtures', name);\n}\n\n/**\n * Absolute path inside the repository, for suites that reach outside this\n * package — the language clients live in `clients/`, not in `packages/`.\n */\nexport function repositoryPath(...segments: readonly string[]): string {\n return join(packageRoot(), '..', '..', ...segments);\n}\n\nlet cachedRoot: string | null = null;\n\nfunction packageRoot(): string {\n if (cachedRoot !== null) return cachedRoot;\n let directory = dirname(fileURLToPath(import.meta.url));\n for (;;) {\n if (existsSync(join(directory, 'package.json'))) {\n cachedRoot = directory;\n return directory;\n }\n const parent = dirname(directory);\n if (parent === directory) {\n throw new Error(\n '@termwright/conformance: could not locate the package root from this module',\n );\n }\n directory = parent;\n }\n}\n\n/** The fixtures published for adapter authors and other packages' suites. */\nexport const CONFORMANCE_FIXTURES = Object.freeze({\n /** Uninstrumented app: proves the generic fallback (§20.1). */\n generic: () => fixturePath('generic-app.mjs'),\n /** Shell-shaped app emitting OSC 133 marks; `--marks=off` suppresses them. */\n prompt: () => fixturePath('prompt-app.mjs'),\n /** Normal-render Ink app used to exercise launch-time probe attachment. */\n inkProbe: () => fixturePath('ink-probe-app.mjs'),\n /** Hostile wire peer; takes a scenario name as its first argument (§20.3). */\n adversarialPeer: () => fixturePath('adversarial-peer.mjs'),\n});\n\nlet cachedPty: boolean | null = null;\n\n/**\n * Whether this machine can open a pseudo-terminal at all.\n *\n * @returns `false` when `TERMWRIGHT_SKIP_PTY=1` or the native binding cannot\n * be loaded and validated. Real child creation remains inside a test attempt.\n */\nexport function ptyAvailable(): boolean {\n if (cachedPty !== null) return cachedPty;\n cachedPty = nativePtyAvailable();\n return cachedPty;\n}\n\n/** `process.env` with the `undefined` values dropped, as PTY spawning requires. */\nexport function environment(extra?: Readonly<Record<string, string>>): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (value !== undefined) env[key] = value;\n }\n return { ...env, ...extra };\n}\n\n/**\n * Absolute path of a Python interpreter that can import the given modules.\n *\n * Two problems at once. The executable is `python3` on POSIX and often only\n * `python` on Windows, and `node-pty` resolves neither reliably — the first\n * Windows run failed with `File not found:` while a `spawnSync` probe of the\n * same name had just succeeded. Asking the interpreter for `sys.executable`\n * turns whichever name works into an absolute path a pty can spawn.\n *\n * @returns the interpreter path, or `null` when no candidate can import them.\n */\nexport function pythonWith(\n modules: readonly string[],\n extraEnv?: Readonly<Record<string, string>>,\n): string | null {\n const script = `import ${modules.join(', ')}, sys; print(sys.executable)`;\n for (const candidate of ['python3', 'python']) {\n if (\n !commandAvailable([candidate, '-c', `import ${modules.join(', ')}`], {\n quiet: true,\n ...(extraEnv === undefined ? {} : { env: extraEnv }),\n })\n )\n continue;\n const resolved = spawnSync(candidate, ['-c', script], {\n encoding: 'utf8',\n env: environment(extraEnv),\n });\n const path = (resolved.stdout ?? '').trim();\n if (resolved.status === 0 && path.length > 0) return path;\n }\n return null;\n}\n\n/** Options every conformance session shares; suites override what they need. */\nexport interface FixtureLaunchOptions extends Partial<Omit<LaunchOptions, 'command'>> {\n /** Extra arguments appended to `node <fixture>`. */\n readonly args?: readonly string[];\n /** Attach the zero-config framework probe to the otherwise normal command. */\n readonly probe?: 'ink';\n /**\n * Text that proves the fixture started drawing. Waiting for it here rather\n * than in each suite is what lets the failure be diagnosed: a fixture that\n * printed nothing at all failed to start, which is a very different problem\n * from one that started and drew the wrong thing.\n */\n readonly ready?: string | RegExp;\n}\n\n/**\n * A set of sessions closed together, so one wedged fixture cannot leak a child\n * process into the next test.\n *\n * @example\n * ```ts\n * const sessions = createSessionPool();\n * afterEach(sessions.closeAll);\n * const terminal = await sessions.launch(CONFORMANCE_FIXTURES.generic());\n * ```\n */\nexport interface SessionPool {\n launch(fixture: string, options?: FixtureLaunchOptions): Promise<TerminalHarness>;\n closeAll(): Promise<void>;\n}\n\nexport function createSessionPool(): SessionPool {\n const open: TerminalHarness[] = [];\n return {\n async launch(fixture, options = {}) {\n const { args = [], probe, ready: _ready, ...launchOptions } = options;\n const base = [process.execPath, fixture, ...args];\n const terminal = await launchTerminal({\n command: probe === 'ink' ? withProbe('node', base).command : base,\n columns: 80,\n rows: 24,\n // No `env` and no `envMode`: the suites run against the secret-safe\n // 'replace' default, which is what a user gets. Forwarding the runner's\n // whole environment here would quietly make every suite an 'inherit'\n // test and leave the default uncovered.\n // Conformance runs start a fresh Node process, a pseudo-terminal and a\n // socket per test, and several suites run beside other builds. The\n // driver's defaults are tight enough that machine load, rather than the\n // implementation, would decide the result — a genuine failure still\n // fails here, just later.\n timeouts: { text: 30_000, action: 30_000, exit: 30_000, idle: 10_000 },\n ...launchOptions,\n });\n open.push(terminal);\n if (options.ready !== undefined) await waitForStart(terminal, options.ready, fixture);\n return terminal;\n },\n async closeAll() {\n while (open.length > 0) {\n const terminal = open.pop();\n await terminal?.close();\n }\n },\n };\n}\n\n/**\n * Waits for a fixture's first output, and says which way it failed.\n *\n * `waitForText` can only report that text never appeared, which reads as a\n * rendering problem. Distinguishing \"the child wrote nothing at all\" from \"the\n * child wrote something else\" is the difference between hunting a fixture bug\n * and hunting a spawn or scheduling one — and on a heavily loaded machine it is\n * always the latter.\n */\nasync function waitForStart(\n terminal: TerminalHarness,\n ready: string | RegExp,\n fixture: string,\n): Promise<void> {\n let bytes = 0;\n const off = terminal.events.on('output', ({ data }) => {\n bytes += data.length;\n });\n try {\n await terminal.waitForText(ready);\n } catch (error) {\n const exit = await Promise.race([terminal.exit, Promise.resolve(null)]);\n const detail =\n bytes === 0\n ? `it produced no output at all${exit === null ? ' and is still running' : `; it exited ${JSON.stringify(exit)}`}`\n : `it produced ${bytes} bytes but never drew ${String(ready)}`;\n throw new Error(\n `conformance: ${fixture.split('/').pop() ?? fixture} did not start — ${detail}`,\n {\n cause: error,\n },\n );\n } finally {\n off();\n }\n}\n\n/**\n * Whether a toolchain command succeeds here.\n *\n * Used to decide, at collection time, whether an adapter written in another\n * language can be certified on this machine at all. A missing interpreter is\n * not a conformance result, so the suite skips and says why — the same rule as\n * a missing pseudo-terminal.\n *\n * @param command - argv to run; exit status 0 counts as available.\n * @returns `false` when the command is missing, fails, or exceeds `timeoutMs`.\n *\n * @example\n * ```ts\n * commandAvailable(['python3', '-c', 'import textual']);\n * ```\n */\nexport function commandAvailable(\n command: readonly string[],\n options: {\n readonly cwd?: string;\n readonly timeoutMs?: number;\n readonly quiet?: boolean;\n readonly env?: Readonly<Record<string, string>>;\n } = {},\n): boolean {\n const [binary, ...args] = command;\n if (binary === undefined) return false;\n const printable = command.join(' ');\n try {\n const result = spawnSync(binary, args, {\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n timeout: options.timeoutMs ?? 120_000,\n encoding: 'utf8',\n env: environment(options.env),\n });\n if (result.status === 0) return true;\n // A skipped suite has to say *why* it skipped, or a probe that broke looks\n // exactly like a toolchain that was never installed.\n if (options.quiet === true) return false;\n const reason =\n result.error?.message ??\n (result.signal === null ? `exit ${String(result.status)}` : `signal ${result.signal}`);\n process.stderr.write(\n `conformance: probe \\`${printable}\\` failed (${reason})\\n` +\n `${(result.stderr ?? '').trim().split('\\n').slice(-3).join('\\n')}\\n`,\n );\n return false;\n } catch (error) {\n if (options.quiet !== true) {\n process.stderr.write(\n `conformance: probe \\`${printable}\\` could not run: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n }\n return false;\n }\n}\n\n/**\n * Turns on the child's mouse reporting and waits for the exact observed mode.\n * Every certified PTY backend, including pinned passthrough ConPTY, carries\n * the child's DECSET to the emulator.\n */\nexport async function enableMouseReporting(\n terminal: TerminalHarness,\n mode: 'click' | 'drag',\n): Promise<void> {\n const expected = mode === 'click' ? 'vt200' : 'drag';\n await terminal.press(mode === 'click' ? 'm' : 'M');\n await waitForTerminal(terminal, () => terminal.screen().modes.mouseTracking === expected);\n}\n\n/** Turns mouse reporting off and waits for the observed DECSET reset. */\nexport async function disableMouseReporting(terminal: TerminalHarness): Promise<void> {\n await terminal.press('m');\n await waitForTerminal(terminal, () => terminal.screen().modes.mouseTracking === 'none');\n}\n\n/**\n * Asks the child to enable focus reporting and waits until DECSET 1004 is\n * observed through owned checkpoint changes.\n */\nexport async function enableFocusReporting(terminal: TerminalHarness): Promise<void> {\n await terminal.press('f');\n await waitForTerminal(terminal, () => terminal.screen().modes.focusReporting === 'on');\n}\n\n/** Turns focus reporting off and waits for the observed DECSET reset. */\nexport async function disableFocusReporting(terminal: TerminalHarness): Promise<void> {\n await terminal.press('f');\n await waitForTerminal(terminal, () => terminal.screen().modes.focusReporting === 'off');\n}\n\n/**\n * Runs a burst out and answers where the published revision came to rest.\n *\n * A wall-clock budget for \"two hundred revisions arrived\" is a bet on the\n * platform's throughput, and it is the wrong question: what a burst settles on\n * depends on how far the terminal falls behind the socket while it is running,\n * which is the platform's business. Measured rather than assumed — the caller\n * then asserts what is true of the answer it got. `target` only bounds the\n * wait; reaching it is not required here.\n */\nexport async function settledRevision(\n terminal: TerminalHarness,\n target: number,\n stallMs = 15_000,\n): Promise<number> {\n let seen = terminal.semanticTree()?.revision ?? 0;\n let progressed = performance.now();\n let checkpoint = terminal.checkpoint();\n for (;;) {\n const current = terminal.semanticTree()?.revision ?? 0;\n if (current >= target) return current;\n if (current > seen) {\n seen = current;\n progressed = performance.now();\n }\n if (performance.now() - progressed > stallMs) return seen;\n const remaining = Math.max(0, stallMs - (performance.now() - progressed));\n try {\n checkpoint = await terminal.waitForCheckpointChange({\n after: checkpoint,\n timeout: remaining,\n });\n } catch {\n return seen;\n }\n }\n}\n\n/** How many times each diagnostic code was recorded — for failure messages. */\nexport function diagnosticTally(terminal: TerminalHarness): string {\n const counts = new Map<string, number>();\n for (const entry of terminal.diagnostics()) {\n counts.set(entry.code, (counts.get(entry.code) ?? 0) + 1);\n }\n return [...counts].map(([code, count]) => `${code}×${count}`).join(', ') || 'none';\n}\n\n/** Waits on the driver's owned observation generation until a predicate holds. */\nasync function waitForTerminal(\n terminal: TerminalHarness,\n predicate: () => boolean,\n timeoutMs = 15_000,\n): Promise<void> {\n const deadline = performance.now() + timeoutMs;\n let checkpoint = terminal.checkpoint();\n for (;;) {\n if (predicate()) return;\n if (performance.now() >= deadline) throw new Error('conformance: condition never became true');\n checkpoint = await terminal.waitForCheckpointChange({\n after: checkpoint,\n timeout: Math.max(0, deadline - performance.now()),\n });\n }\n}\n\n/** Catch helper: awaits a rejection and returns the error, typed. */\nexport async function rejection<T>(promise: Promise<T>): Promise<unknown> {\n try {\n await promise;\n return null;\n } catch (error) {\n return error;\n }\n}\n","/** Exact, content-addressed instrumentation for Ink 7.1.1's renderer. */\n\nimport { createHash } from 'node:crypto';\nimport certified from './certified-instrumentation.json' with { type: 'json' };\n\ninterface InkInstrumentationProfile {\n readonly version: string;\n readonly rendererSha256: string;\n readonly coreSha256: string;\n}\n\nconst BUILTIN_PROFILES: readonly InkInstrumentationProfile[] = certified.profiles;\nexport const INK_VERSION = BUILTIN_PROFILES.at(-1)?.version ?? 'unsupported';\nexport const INK_RENDER_CAPTURE = Symbol.for('termwright.ink.render-capture.v1');\nexport const INK_FRAME_CONTEXT = Symbol.for('termwright.ink.frame-context.v1');\nexport const INK_INSTRUMENTATION_SENTINEL = Symbol.for('termwright.ink.instrumentation.v1');\n\nexport const INK_RENDERER_PATTERN = /[\\\\/](?:ink|ink@[^\\\\/]+)[\\\\/]build[\\\\/]renderer\\.js$/u;\nexport const INK_CORE_PATTERN = /[\\\\/](?:ink|ink@[^\\\\/]+)[\\\\/]build[\\\\/]ink\\.js$/u;\n\nexport interface InkInstrumentationSentinel {\n readonly version: 1;\n readonly frameworkVersion: string;\n readonly rendererChecksum: string;\n readonly coreChecksum: string;\n}\n\nexport interface InkRenderedOutput {\n readonly output: string;\n readonly outputHeight: number;\n readonly staticOutput: string;\n}\n\nexport type InkRenderCaptureHook = (\n root: object,\n result: InkRenderedOutput,\n screenReader: boolean,\n) => void;\n\nexport function instrumentationSentinel(): InkInstrumentationSentinel | undefined {\n const value = (globalThis as Record<PropertyKey, unknown>)[INK_INSTRUMENTATION_SENTINEL];\n if (value === null || typeof value !== 'object') return undefined;\n const candidate = value as Partial<InkInstrumentationSentinel>;\n const profile = instrumentationProfiles().find(\n (entry) => entry.version === candidate.frameworkVersion,\n );\n return profile !== undefined &&\n candidate.version === 1 &&\n candidate.rendererChecksum === profile.rendererSha256 &&\n candidate.coreChecksum === profile.coreSha256\n ? (candidate as InkInstrumentationSentinel)\n : undefined;\n}\n\n/** Transform the matching Ink class so every capture includes render-mode facts. */\nexport function instrumentInkCore(path: string, source: string): string | undefined {\n if (!INK_CORE_PATTERN.test(path.split('?')[0] ?? '')) return undefined;\n const checksum = createHash('sha256').update(source).digest('hex');\n const profile = instrumentationProfiles().find((entry) => entry.coreSha256 === checksum);\n if (profile === undefined) return undefined;\n const needle = ` const { output, outputHeight, staticOutput } = render(this.rootNode, this.isScreenReaderEnabled);\\n this.options.onRender?.({ renderTime: performance.now() - startTime });`;\n const replacement = ` const { output, outputHeight, staticOutput } = render(this.rootNode, this.isScreenReaderEnabled);\\n globalThis[Symbol.for(\"termwright.ink.frame-context.v1\")]?.(this.rootNode, Object.freeze({ interactive: this.interactive, alternateScreen: this.alternateScreen, debug: this.options.debug === true, stdoutIsTTY: this.options.stdout.isTTY === true, rows: getWindowSize(this.options.stdout).rows }));\\n this.options.onRender?.({ renderTime: performance.now() - startTime });`;\n if (source.split(needle).length !== 2) return undefined;\n const sentinelNeedle = `const noop = () => { };`;\n if (source.split(sentinelNeedle).length !== 2) return undefined;\n const sentinel = `const __termwrightInkSentinelSymbol = Symbol.for(\"termwright.ink.instrumentation.v1\");\\nconst __termwrightInkPriorSentinel = globalThis[__termwrightInkSentinelSymbol] ?? {};\\nglobalThis[__termwrightInkSentinelSymbol] = Object.freeze({ ...__termwrightInkPriorSentinel, version: 1, frameworkVersion: \"${profile.version}\", coreChecksum: \"${checksum}\" });`;\n return source\n .replace(sentinelNeedle, `${sentinel}\\n${sentinelNeedle}`)\n .replace(needle, replacement);\n}\n\n/** Transform only the byte-exact renderer shipped by Ink 7.1.1. */\nexport function instrumentInkRenderer(path: string, source: string): string | undefined {\n if (!INK_RENDERER_PATTERN.test(path.split('?')[0] ?? '')) return undefined;\n const checksum = createHash('sha256').update(source).digest('hex');\n const profile = instrumentationProfiles().find((entry) => entry.rendererSha256 === checksum);\n if (profile === undefined) return undefined;\n\n const insertion = \"import Output from './output.js';\";\n if (source.split(insertion).length !== 2) return undefined;\n let output = source.replace(insertion, `${insertion}\\n${runtime(profile.version, checksum)}`);\n\n const screenReaderReturn = ` return {\n output,\n outputHeight,\n staticOutput: staticOutput ? \\`${'${staticOutput}'}\\\\n\\` : '',\n };`;\n const screenReaderReplacement = ` return __termwrightCaptureInkRenderer(node, {\n output,\n outputHeight,\n staticOutput: staticOutput ? \\`${'${staticOutput}'}\\\\n\\` : '',\n }, true);`;\n const normalReturn = ` return {\n output: generatedOutput,\n outputHeight,\n // Newline at the end is needed, because static output doesn't have one, so\n // interactive output will override last line of static output\n staticOutput: staticOutput ? \\`${'${staticOutput.get().output}'}\\\\n\\` : '',\n };`;\n const normalReplacement = ` return __termwrightCaptureInkRenderer(node, {\n output: generatedOutput,\n outputHeight,\n // Newline at the end is needed, because static output doesn't have one, so\n // interactive output will override last line of static output\n staticOutput: staticOutput ? \\`${'${staticOutput.get().output}'}\\\\n\\` : '',\n }, false);`;\n const emptyReturn = ` return {\n output: '',\n outputHeight: 0,\n staticOutput: '',\n };`;\n const emptyReplacement = ` return __termwrightCaptureInkRenderer(node, {\n output: '',\n outputHeight: 0,\n staticOutput: '',\n }, isScreenReaderEnabled);`;\n\n for (const [needle, replacement] of [\n [screenReaderReturn, screenReaderReplacement],\n [normalReturn, normalReplacement],\n [emptyReturn, emptyReplacement],\n ] as const) {\n if (output.split(needle).length !== 2) return undefined;\n output = output.replace(needle, replacement);\n }\n return output;\n}\n\nfunction runtime(frameworkVersion: string, checksum: string): string {\n return `const __termwrightInkCaptureSymbol = Symbol.for(\"termwright.ink.render-capture.v1\");\nconst __termwrightInkSentinelSymbol = Symbol.for(\"termwright.ink.instrumentation.v1\");\nconst __termwrightInkPriorSentinel = globalThis[__termwrightInkSentinelSymbol] ?? {};\nglobalThis[__termwrightInkSentinelSymbol] = Object.freeze({ ...__termwrightInkPriorSentinel, version: 1, frameworkVersion: \"${frameworkVersion}\", rendererChecksum: \"${checksum}\" });\nconst __termwrightCaptureInkRenderer = (root, result, screenReader) => {\n const capture = globalThis[__termwrightInkCaptureSymbol];\n if (typeof capture === \"function\") capture(root, result, screenReader);\n return result;\n};`;\n}\n\nfunction instrumentationProfiles(): readonly InkInstrumentationProfile[] {\n const override = certificationOverride();\n return override === undefined ? BUILTIN_PROFILES : [override, ...BUILTIN_PROFILES];\n}\n\nfunction certificationOverride(): InkInstrumentationProfile | undefined {\n const raw = process.env['TERMWRIGHT_CERTIFICATION_HOOK_PROFILE'];\n if (raw === undefined) return undefined;\n if (process.env['GITHUB_ACTIONS'] !== 'true') return undefined;\n try {\n const value = JSON.parse(raw) as Record<string, unknown>;\n const digest = process.env['TERMWRIGHT_CERTIFICATION_CANDIDATE_DIGEST'];\n const revision = process.env['TERMWRIGHT_CERTIFICATION_SOURCE_REVISION'];\n if (\n value['framework'] !== 'ink' ||\n !/^sha256:[a-f0-9]{64}$/u.test(digest ?? '') ||\n revision !== process.env['GITHUB_SHA'] ||\n value['sourceRevision'] !== revision ||\n value['candidateDigest'] !== digest ||\n typeof value['version'] !== 'string' ||\n !/^[a-f0-9]{64}$/u.test(String(value['rendererSha256'])) ||\n !/^[a-f0-9]{64}$/u.test(String(value['coreSha256']))\n )\n return undefined;\n return {\n version: value['version'],\n rendererSha256: String(value['rendererSha256']),\n coreSha256: String(value['coreSha256']),\n };\n } catch {\n return undefined;\n }\n}\n","{\n \"framework\": \"ink\",\n \"profiles\": [\n {\n \"coreSha256\": \"f632f6176e593183f0c0bb6e4a6e8a28d65f1c3899a33a84d4a95d26e1a82a58\",\n \"rendererSha256\": \"9e72b27731c38daac7e9f978e24f7bf1210c5cc26bf973e30f08c3ad4a9fe374\",\n \"version\": \"7.1.1\"\n }\n ],\n \"schemaVersion\": 1\n}\n","/** Minimal React renderer instrumentation observer used by the Ink probe spike. */\n\nimport type { InkDomElement } from './observe.js';\n\nconst BRIDGE = Symbol.for('@termwright/probe-ink/react-commit-bridge.v1');\n\ninterface RendererMetadata {\n readonly rendererPackageName?: unknown;\n readonly rendererVersion?: unknown;\n}\n\ninterface FiberRootLike {\n readonly containerInfo?: unknown;\n readonly current?: FiberLike;\n}\n\ninterface FiberLike {\n readonly stateNode?: unknown;\n readonly memoizedProps?: unknown;\n readonly child?: FiberLike | null;\n readonly sibling?: FiberLike | null;\n}\n\ninterface DevToolsHookLike {\n readonly supportsFiber?: boolean;\n inject?(renderer: RendererMetadata): unknown;\n onCommitFiberRoot?(rendererId: unknown, root: FiberRootLike, ...rest: readonly unknown[]): void;\n onCommitFiberUnmount?(rendererId: unknown, fiber: unknown): void;\n [BRIDGE]?: ReactCommitBridge;\n [key: PropertyKey]: unknown;\n}\n\nexport interface InkRendererRegistration {\n readonly rendererId: unknown;\n readonly packageName: 'ink';\n readonly version?: string;\n}\n\nexport type InkCommitEvent =\n | {\n readonly type: 'commit';\n readonly renderer: InkRendererRegistration;\n readonly fiberRoot: FiberRootLike;\n readonly root: InkDomElement;\n }\n | {\n readonly type: 'unmount';\n readonly renderer: InkRendererRegistration;\n readonly fiber: unknown;\n }\n | {\n readonly type: 'invalid-root';\n readonly renderer: InkRendererRegistration;\n readonly fiberRoot: FiberRootLike;\n readonly containerInfo: unknown;\n };\n\nexport interface InkReconcilerInstrumentation {\n injectIntoDevTools(): unknown;\n}\n\nexport interface ReactCommitBridgeLease {\n readonly bridge: ReactCommitBridge;\n release(): void;\n}\n\n/**\n * Experimental, deliberately Fiber-dependent correlation used to measure\n * which source accessibility props Ink drops from its committed host DOM.\n * It is not used by the production observer or accepted as a stable seam.\n */\nexport interface InkHostPropCorrelation {\n readonly hostProps?: Readonly<Record<string, unknown>>;\n readonly sourceProps?: Readonly<Record<string, unknown>>;\n readonly accessibleName?: string;\n readonly ariaHidden?: boolean;\n}\n\ntype Listener = (event: InkCommitEvent) => void;\n\n/**\n * A process-global observer which composes with an already-installed hook.\n * Renderer ids are always the ids returned to React by that hook.\n */\nexport class ReactCommitBridge {\n readonly #renderers = new Map<unknown, InkRendererRegistration>();\n readonly #roots = new Map<object, InkDomElement>();\n readonly #listeners = new Set<Listener>();\n #nextRendererId = 1;\n\n register(renderer: RendererMetadata, delegatedId?: unknown): unknown {\n const rendererId = delegatedId === undefined ? this.#nextRendererId++ : delegatedId;\n if (typeof rendererId === 'number' && Number.isInteger(rendererId)) {\n this.#nextRendererId = Math.max(this.#nextRendererId, rendererId + 1);\n }\n if (renderer.rendererPackageName === 'ink') {\n this.#renderers.set(rendererId, {\n rendererId,\n packageName: 'ink',\n ...(typeof renderer.rendererVersion === 'string'\n ? { version: renderer.rendererVersion }\n : {}),\n });\n }\n return rendererId;\n }\n\n commit(rendererId: unknown, fiberRoot: FiberRootLike): void {\n const renderer = this.#renderers.get(rendererId);\n if (renderer === undefined) return;\n const containerInfo = fiberRoot.containerInfo;\n if (!isInkRoot(containerInfo)) {\n this.#emit({ type: 'invalid-root', renderer, fiberRoot, containerInfo });\n return;\n }\n this.#roots.set(fiberRoot as object, containerInfo);\n this.#emit({ type: 'commit', renderer, fiberRoot, root: containerInfo });\n }\n\n unmount(rendererId: unknown, fiber: unknown): void {\n const renderer = this.#renderers.get(rendererId);\n if (renderer !== undefined) this.#emit({ type: 'unmount', renderer, fiber });\n }\n\n subscribe(listener: Listener): () => void {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n }\n\n roots(): readonly InkDomElement[] {\n return [...this.#roots.values()];\n }\n\n hasInkRenderer(): boolean {\n return this.#renderers.size > 0;\n }\n\n #emit(event: InkCommitEvent): void {\n for (const listener of this.#listeners) {\n try {\n listener(event);\n } catch {\n // Instrumentation observers must never break React's commit callback.\n }\n }\n }\n}\n\n/** Install or reuse the bridge without replacing the user's hook behavior. */\nexport function installReactCommitBridge(\n target: typeof globalThis = globalThis,\n): ReactCommitBridge {\n const holder = target as typeof globalThis & {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHookLike;\n };\n const existing = holder.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n const installed = existing?.[BRIDGE];\n if (installed !== undefined) return installed;\n\n const bridge = new ReactCommitBridge();\n const hook = Object.create(existing ?? null) as DevToolsHookLike;\n Object.defineProperties(hook, {\n supportsFiber: { value: true, enumerable: true, configurable: true },\n inject: {\n configurable: true,\n value(renderer: RendererMetadata): unknown {\n const delegatedId = existing?.inject?.call(existing, renderer);\n return bridge.register(renderer, delegatedId);\n },\n },\n onCommitFiberRoot: {\n configurable: true,\n value(rendererId: unknown, root: FiberRootLike, ...rest: readonly unknown[]): void {\n try {\n existing?.onCommitFiberRoot?.call(existing, rendererId, root, ...rest);\n } finally {\n bridge.commit(rendererId, root);\n }\n },\n },\n onCommitFiberUnmount: {\n configurable: true,\n value(rendererId: unknown, fiber: unknown): void {\n try {\n existing?.onCommitFiberUnmount?.call(existing, rendererId, fiber);\n } finally {\n bridge.unmount(rendererId, fiber);\n }\n },\n },\n [BRIDGE]: { value: bridge },\n });\n try {\n const descriptor = Object.getOwnPropertyDescriptor(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__');\n if (\n descriptor?.configurable === true &&\n (('writable' in descriptor && descriptor.writable === false) ||\n (!('writable' in descriptor) && descriptor.set === undefined))\n ) {\n Object.defineProperty(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__', {\n value: hook,\n writable: true,\n enumerable: descriptor.enumerable ?? false,\n configurable: true,\n });\n } else {\n holder.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;\n }\n } catch (cause) {\n throw new Error(\n 'Ink semantic probe unavailable: the existing React renderer instrumentation hook cannot be composed.',\n { cause },\n );\n }\n return bridge;\n}\n\ninterface BridgeLeaseRecord {\n readonly bridge: ReactCommitBridge;\n readonly hook: DevToolsHookLike;\n readonly priorDescriptor?: PropertyDescriptor;\n references: number;\n}\n\nconst bridgeLeases = new WeakMap<object, BridgeLeaseRecord>();\n\n/**\n * Acquire a process-hook lease for transactional adapter setup. The final\n * release restores the exact prior property descriptor, but only while our\n * hook is still current. A bridge installed independently is never removed.\n */\nexport function acquireReactCommitBridge(\n target: typeof globalThis = globalThis,\n): ReactCommitBridgeLease {\n const holder = target as typeof globalThis & {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHookLike;\n };\n const currentRecord = bridgeLeases.get(target);\n if (currentRecord !== undefined && holder.__REACT_DEVTOOLS_GLOBAL_HOOK__ === currentRecord.hook) {\n currentRecord.references += 1;\n return leaseFor(target, currentRecord);\n }\n\n const existingBridge = holder.__REACT_DEVTOOLS_GLOBAL_HOOK__?.[BRIDGE];\n const priorDescriptor = Object.getOwnPropertyDescriptor(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__');\n const bridge = installReactCommitBridge(target);\n const hook = holder.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n if (hook === undefined) {\n throw new Error(\n 'Ink semantic probe unavailable: React renderer instrumentation hook installation disappeared.',\n );\n }\n // If another subsystem installed this bridge, this adapter may subscribe to\n // it but must not claim ownership of the process-global hook.\n if (existingBridge !== undefined) return { bridge, release() {} };\n const record: BridgeLeaseRecord = {\n bridge,\n hook,\n ...(priorDescriptor === undefined ? {} : { priorDescriptor }),\n references: 1,\n };\n bridgeLeases.set(target, record);\n return leaseFor(target, record);\n}\n\nfunction leaseFor(target: typeof globalThis, record: BridgeLeaseRecord): ReactCommitBridgeLease {\n let released = false;\n return {\n bridge: record.bridge,\n release() {\n if (released) return;\n released = true;\n record.references -= 1;\n if (record.references > 0) return;\n bridgeLeases.delete(target);\n const holder = target as typeof globalThis & {\n __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHookLike;\n };\n if (holder.__REACT_DEVTOOLS_GLOBAL_HOOK__ !== record.hook) return;\n if (record.priorDescriptor === undefined) {\n delete holder.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n } else {\n Object.defineProperty(holder, '__REACT_DEVTOOLS_GLOBAL_HOOK__', record.priorDescriptor);\n }\n },\n };\n}\n\nconst activatedReconcilers = new WeakMap<object, WeakSet<object>>();\n\n/**\n * Enable Ink's existing reconciler seam directly. This intentionally does not\n * set DEV and therefore cannot load the DevTools UI/backend or open a socket.\n */\nexport function activateInkRendererObservation(\n reconciler: InkReconcilerInstrumentation,\n target: typeof globalThis = globalThis,\n): ReactCommitBridge {\n const bridge = installReactCommitBridge(target);\n let bridges = activatedReconcilers.get(reconciler);\n if (bridges === undefined) {\n bridges = new WeakSet<object>();\n activatedReconcilers.set(reconciler, bridges);\n }\n if (!bridges.has(bridge)) {\n // React 19's reconciler currently returns false even after synchronously\n // calling hook.inject(). Registration, not that implementation-detail\n // return value, is the capability proof.\n reconciler.injectIntoDevTools();\n if (!bridge.hasInkRenderer())\n throw new Error(\n 'Ink semantic probe unavailable: React renderer instrumentation did not register Ink.',\n );\n bridges.add(bridge);\n }\n return bridge;\n}\n\n/**\n * Correlate committed Ink host objects with the nearest source component\n * props. This POC proves that `aria-label`/`aria-hidden`, which Ink omits from\n * normal-mode host DOM, remain recoverable through Fiber. The returned map is\n * a measurement aid, not a production contract: every field is structural\n * React internals and must fail absent rather than fabricate data.\n */\nexport function correlateInkHostProps(\n fiberRoot: FiberRootLike,\n options: { readonly maxFibers?: number } = {},\n): ReadonlyMap<InkDomElement, InkHostPropCorrelation> {\n const correlations = new Map<InkDomElement, InkHostPropCorrelation>();\n const maxFibers = options.maxFibers ?? 100_000;\n let visitedFibers = 0;\n const walk = (\n fiber: FiberLike | null | undefined,\n candidateSourceProps?: Readonly<Record<string, unknown>>,\n ): void => {\n for (\n let current = fiber;\n current !== null && current !== undefined;\n current = current.sibling\n ) {\n visitedFibers += 1;\n if (visitedFibers > maxFibers) {\n throw new Error(\n 'Ink Fiber accessibility correlation exceeded its bounded traversal limit.',\n );\n }\n const props = record(current.memoizedProps);\n const sourceProps = hasAccessibilitySourceProps(props) ? props : candidateSourceProps;\n if (isInkElement(current.stateNode)) {\n correlations.set(current.stateNode, {\n ...(props === undefined ? {} : { hostProps: props }),\n ...(sourceProps === undefined ? {} : { sourceProps }),\n ...(typeof sourceProps?.['aria-label'] === 'string'\n ? { accessibleName: sourceProps['aria-label'] }\n : {}),\n ...(typeof sourceProps?.['aria-hidden'] === 'boolean'\n ? { ariaHidden: sourceProps['aria-hidden'] }\n : {}),\n });\n walk(current.child, undefined);\n } else {\n walk(current.child, sourceProps);\n }\n }\n };\n walk(fiberRoot.current?.child);\n return correlations;\n}\n\n/** Fail closed instead of accepting a foreign or incomplete committed root. */\nexport function requireCommittedInkRoot(event: InkCommitEvent): InkDomElement {\n if (event.type !== 'commit') {\n throw new Error(\n 'Ink semantic probe unavailable: React renderer instrumentation did not expose expected committed Ink root.',\n );\n }\n return event.root;\n}\n\nfunction isInkRoot(value: unknown): value is InkDomElement {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as {\n readonly nodeName?: unknown;\n readonly childNodes?: unknown;\n };\n return candidate.nodeName === 'ink-root' && Array.isArray(candidate.childNodes);\n}\n\nfunction isInkElement(value: unknown): value is InkDomElement {\n if (typeof value !== 'object' || value === null) return false;\n const nodeName = (value as { readonly nodeName?: unknown }).nodeName;\n return (\n nodeName === 'ink-root' ||\n nodeName === 'ink-box' ||\n nodeName === 'ink-text' ||\n nodeName === 'ink-virtual-text'\n );\n}\n\nfunction record(value: unknown): Readonly<Record<string, unknown>> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Readonly<Record<string, unknown>>)\n : undefined;\n}\n\nfunction hasAccessibilitySourceProps(\n props: Readonly<Record<string, unknown>> | undefined,\n): boolean {\n return (\n props !== undefined &&\n (Object.hasOwn(props, 'aria-label') ||\n Object.hasOwn(props, 'aria-hidden') ||\n Object.hasOwn(props, 'aria-role') ||\n Object.hasOwn(props, 'aria-state'))\n );\n}\n","import type { ProbeAnnotations, ProtocolLimits } from '@termwright/protocol';\nimport { validateProbeAnnotations } from '@termwright/protocol';\n\nconst REGISTRY = Symbol.for('termwright.annotation.ink.v1');\n\ninterface StoredAnnotation {\n readonly role?: unknown;\n readonly name?: unknown;\n readonly description?: unknown;\n readonly testId?: unknown;\n readonly extended?: unknown;\n readonly actions?: unknown;\n readonly labelledBy?: readonly WeakRef<object>[];\n readonly describedBy?: readonly WeakRef<object>[];\n}\n\ninterface AnnotationSlot {\n readonly current?: StoredAnnotation;\n}\n\ninterface AnnotationChannel {\n readonly entries: WeakMap<object, AnnotationSlot>;\n readonly listeners: Set<() => void>;\n}\n\nfunction channel(): AnnotationChannel {\n const scope = globalThis as Record<PropertyKey, unknown>;\n const present = scope[REGISTRY] as Partial<AnnotationChannel> | undefined;\n if (present?.entries instanceof WeakMap && present.listeners instanceof Set) {\n return present as AnnotationChannel;\n }\n const created: AnnotationChannel = {\n entries: new WeakMap<object, AnnotationSlot>(),\n listeners: new Set<() => void>(),\n };\n Object.defineProperty(scope, REGISTRY, { configurable: true, value: created });\n return created;\n}\n\n/** Re-capture after an annotation attaches to a newly reconciled host. */\nexport function onInkAnnotationChange(handler: () => void): () => void {\n const listeners = channel().listeners;\n listeners.add(handler);\n return () => listeners.delete(handler);\n}\n\nfunction strings(\n refs: unknown,\n idFor: (node: object) => string,\n maxTargets: number,\n): string[] | null | undefined {\n if (refs === undefined) return undefined;\n if (!Array.isArray(refs)) return null;\n const length = Object.getOwnPropertyDescriptor(refs, 'length')?.value;\n if (!Number.isSafeInteger(length) || length < 0 || length > maxTargets) return null;\n const ids: string[] = [];\n for (let index = 0; index < length; index += 1) {\n try {\n const descriptor = Object.getOwnPropertyDescriptor(refs, String(index));\n if (descriptor === undefined || !('value' in descriptor)) return null;\n const ref = descriptor.value;\n if (!(ref instanceof WeakRef)) return null;\n const target = WeakRef.prototype.deref.call(ref) as object | undefined;\n if (target !== undefined) ids.push(idFor(target));\n } catch {\n return null;\n }\n }\n return ids.length === 0 ? undefined : ids;\n}\n\nfunction ownData(value: object, key: keyof StoredAnnotation): unknown {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined;\n}\n\n/** Read author intent without taking a runtime dependency on the optional SDK. */\nexport function annotationForInkNode(\n node: object,\n idFor: (node: object) => string,\n limits: ProtocolLimits,\n): ProbeAnnotations | undefined {\n try {\n const slot = channel().entries.get(node);\n const value = slot?.current;\n if (value === undefined) return undefined;\n const role = ownData(value, 'role');\n const name = ownData(value, 'name');\n const description = ownData(value, 'description');\n const testId = ownData(value, 'testId');\n const extended = ownData(value, 'extended');\n const actions = ownData(value, 'actions');\n const labelledBy = strings(ownData(value, 'labelledBy'), idFor, limits.maxRelationTargets);\n const describedBy = strings(ownData(value, 'describedBy'), idFor, limits.maxRelationTargets);\n const candidate = {\n ...(role === undefined ? {} : { role }),\n ...(name === undefined ? {} : { name }),\n ...(description === undefined ? {} : { description }),\n ...(testId === undefined ? {} : { testId }),\n ...(extended === undefined ? {} : { extended }),\n ...(actions === undefined ? {} : { actions }),\n ...(labelledBy === undefined ? {} : { labelledBy }),\n ...(describedBy === undefined ? {} : { describedBy }),\n };\n if (Object.keys(candidate).length === 0) return undefined;\n const validated = validateProbeAnnotations(candidate, limits);\n return validated.ok ? validated.annotations : undefined;\n } catch {\n return undefined;\n }\n}\n","/** Ink's retained host tree to framework-neutral Probe IR. */\n\nimport type {\n ProbeAccessibilityHints,\n ProbeFrame,\n ProbeObject,\n ProbeObservedState,\n ProbeRect,\n ProbeUnobservableField,\n ProtocolLimits,\n} from '@termwright/protocol';\nimport { annotationForInkNode } from './annotations.js';\nimport type { RelativeGeometry } from './frame-capture.js';\n\n/** Structural subset of Ink's DOM node. No runtime import from `ink`. */\nexport interface InkDomElement {\n readonly nodeName: 'ink-root' | 'ink-box' | 'ink-text' | 'ink-virtual-text';\n readonly childNodes: readonly InkDomNode[];\n readonly parentNode?: InkDomElement;\n readonly style?: Readonly<Record<string, unknown>> & { readonly display?: string };\n readonly internal_static?: boolean;\n readonly staticNode?: InkDomElement;\n readonly internal_accessibility?: {\n readonly role?: string;\n readonly state?: {\n readonly checked?: boolean;\n readonly disabled?: boolean;\n readonly expanded?: boolean;\n readonly readonly?: boolean;\n readonly selected?: boolean;\n readonly busy?: boolean;\n readonly multiline?: boolean;\n readonly required?: boolean;\n readonly multiselectable?: boolean;\n };\n };\n}\n\nexport interface InkTextNode {\n readonly nodeName: '#text';\n readonly nodeValue: string;\n readonly parentNode?: InkDomElement;\n}\n\nexport type InkDomNode = InkDomElement | InkTextNode;\n\n/** Public Ink measurement function, kept injectable for tests and isolation. */\nexport type MeasureElement = (node: InkDomElement) => {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n};\n\nexport interface ObserveInkOptions {\n readonly frame: number;\n readonly limits: ProtocolLimits;\n /** The probe's own hidden Box. It is the sole injected node and is omitted. */\n readonly excluded?: InkDomElement | null;\n /** Renderer-retained roots (notably committed <Static>) detached by Ink later. */\n readonly retainedRoots?: readonly InkDomElement[];\n readonly retainedChildren?: ReadonlyMap<InkDomElement, readonly InkDomNode[]>;\n readonly measureElement?: MeasureElement;\n /** Geometry frozen by the certified 7.1.1 renderer instrumentation. */\n readonly geometry?: ReadonlyMap<InkDomElement, RelativeGeometry>;\n}\n\nexport interface InkObservation {\n readonly frame: ProbeFrame;\n readonly truncated: boolean;\n readonly geometryRegions: ReadonlyMap<string, 'live' | 'static'>;\n}\n\nconst isElement = (node: InkDomNode): node is InkDomElement => node.nodeName !== '#text';\n\n/**\n * Observe every Ink host element, including plain unannotated layout boxes.\n *\n * Source component names do not survive Ink's reconciler. `frameworkType` is\n * therefore deliberately one of Ink's four host kinds; inventing `Button` or\n * a component stack here would be false provenance.\n */\nexport function observeInkTree(root: InkDomElement, options: ObserveInkOptions): InkObservation {\n const objects: ProbeObject[] = [];\n const ids = identityStore(root);\n let truncated = false;\n const geometryRegions = new Map<string, 'live' | 'static'>();\n const visited = new Set<InkDomElement>();\n\n const visit = (\n node: InkDomElement,\n parent: InkDomElement | undefined,\n depth: number,\n ancestorHidden: boolean,\n ): void => {\n if (node === options.excluded || visited.has(node)) return;\n visited.add(node);\n if (depth > options.limits.maxDepth || objects.length >= options.limits.maxNodes) {\n truncated = true;\n return;\n }\n\n const hidden = ancestorHidden || node.style?.display === 'none';\n const state = observedState(node, !hidden);\n const annotations = annotationForInkNode(\n node,\n (target) => ids.idFor(target as InkDomElement),\n options.limits,\n );\n const accessibility = observedAccessibility(node);\n const geometry = geometryOf(node, options);\n const identity = ids.idFor(node);\n const region = options.geometry?.get(node)?.region;\n if (region !== undefined) geometryRegions.set(identity, region);\n // Probe IR's `text` is the object's own text, never a descendant-derived\n // accessible name. The recognizer applies name-from-content over the tree.\n const children = options.retainedChildren?.get(node) ?? node.childNodes;\n const text = isTextHost(node) ? textOf(children, options.limits.maxStringBytes) : undefined;\n const unobservable = unobservableFor(\n node,\n geometry?.intendedRect !== undefined,\n text !== undefined,\n );\n\n objects.push({\n identity: { kind: 'stable', value: identity },\n frameworkType: node.nodeName,\n ...(parent === undefined ? {} : { parent: ids.idFor(parent) }),\n ...(geometry === undefined ? {} : { geometry }),\n ...(state === undefined ? {} : { state }),\n ...(text === undefined ? {} : { text }),\n ...(accessibility === undefined ? {} : { accessibility }),\n ...(annotations === undefined ? {} : { annotations }),\n unobservable,\n });\n\n for (const child of children) {\n // Raw `#text` values are payload owned by their `ink-text` host, not a\n // fifth host kind. The text is retained on that host above.\n if (isElement(child)) visit(child, node, depth + 1, hidden);\n }\n };\n\n visit(root, undefined, 0, false);\n // Ink removes committed <Static> children from the live root but retains the\n // exact host subtree in root.staticNode for the separately rendered static\n // output. Observe that retained subtree as a root child when it is no longer\n // reachable through childNodes; the identity and captured layout stay exact.\n if (root.staticNode !== undefined) visit(root.staticNode, root, 1, false);\n for (const retained of options.retainedRoots ?? []) visit(retained, root, 1, false);\n return { frame: { frame: options.frame, objects }, truncated, geometryRegions };\n}\n\n/** Weak identity is stable for exactly the lifetime of Ink's host object. */\nconst stores = new WeakMap<InkDomElement, IdentityStore>();\n\ninterface IdentityStore {\n idFor(node: InkDomElement): string;\n}\n\nfunction identityStore(root: InkDomElement): IdentityStore {\n let store = stores.get(root);\n if (store !== undefined) return store;\n const ids = new WeakMap<InkDomElement, string>();\n let nextId = 0;\n store = {\n idFor(node) {\n const existing = ids.get(node);\n if (existing !== undefined) return existing;\n nextId += 1;\n const id = String(nextId);\n ids.set(node, id);\n return id;\n },\n };\n stores.set(root, store);\n return store;\n}\n\nfunction isTextHost(node: InkDomElement): boolean {\n return node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text';\n}\n\nfunction observedAccessibility(node: InkDomElement): ProbeAccessibilityHints | undefined {\n const role = node.internal_accessibility?.role;\n return role === undefined ? undefined : { role };\n}\n\nfunction observedState(node: InkDomElement, displayed: boolean): ProbeObservedState | undefined {\n const accessibility = node.internal_accessibility?.state;\n const state: ProbeObservedState = {\n displayed,\n ...(accessibility?.checked === undefined ? {} : { checked: accessibility.checked }),\n ...(accessibility?.disabled === undefined ? {} : { disabled: accessibility.disabled }),\n ...(accessibility?.expanded === undefined ? {} : { expanded: accessibility.expanded }),\n ...(accessibility?.readonly === undefined ? {} : { readonly: accessibility.readonly }),\n ...(accessibility?.selected === undefined ? {} : { selected: accessibility.selected }),\n ...(accessibility?.busy === undefined ? {} : { busy: accessibility.busy }),\n ...(accessibility?.multiline === undefined ? {} : { multiline: accessibility.multiline }),\n ...(accessibility?.required === undefined ? {} : { required: accessibility.required }),\n ...(accessibility?.multiselectable === undefined\n ? {}\n : { multiselectable: accessibility.multiselectable }),\n };\n return state;\n}\n\nfunction geometryOf(\n node: InkDomElement,\n options: ObserveInkOptions,\n): { readonly intendedRect: ProbeRect; readonly visibleRect: ProbeRect } | undefined {\n const geometry = options.geometry?.get(node);\n return geometry === undefined\n ? undefined\n : { intendedRect: geometry.intended, visibleRect: geometry.visible };\n}\n\nfunction textOf(children: readonly InkDomNode[], maxBytes: number): string | undefined {\n const parts: string[] = [];\n let bytes = 0;\n\n const append = (value: string): void => {\n for (const codePoint of value) {\n const size = Buffer.byteLength(codePoint, 'utf8');\n if (bytes + size > maxBytes) return;\n parts.push(codePoint);\n bytes += size;\n }\n };\n\n // Raw #text children are this host's payload. Nested host elements retain\n // their own ProbeObjects, so folding them in here would violate the IR's\n // own-text contract and duplicate them during name-from-content inference.\n for (const child of children) {\n if (bytes >= maxBytes) break;\n if (!isElement(child)) append(child.nodeValue);\n }\n const text = parts.join('').replace(/\\s+/gu, ' ').trim();\n return text.length === 0 ? undefined : text;\n}\n\nfunction unobservableFor(\n node: InkDomElement,\n hasGeometry: boolean,\n hasText: boolean,\n): readonly ProbeUnobservableField[] {\n const result: ProbeUnobservableField[] = [\n 'focused',\n 'value',\n 'selectedIndex',\n 'textSelection',\n 'scroll',\n 'scrollExtent',\n 'paintOrder',\n ];\n const state = node.internal_accessibility?.state;\n if (state?.disabled === undefined) result.push('disabled');\n if (state?.checked === undefined) result.push('checked');\n if (state?.expanded === undefined) result.push('expanded');\n if (state?.readonly === undefined) result.push('readonly');\n if (state?.selected === undefined) result.push('selected');\n if (state?.busy === undefined) result.push('busy');\n if (state?.multiline === undefined) result.push('multiline');\n if (!hasGeometry) result.push('intendedRect', 'visibleRect');\n if (!hasText && isTextHost(node)) result.push('text');\n return result;\n}\n","/** Synchronized from package.json by scripts/sync-protocol-version.mjs. */\nexport const PACKAGE_VERSION = '0.3.2';\n","import type { ProbeInfo } from '@termwright/protocol';\nimport { instrumentationSentinel, INK_VERSION } from './instrumentation.js';\nimport { PACKAGE_VERSION } from './version.js';\n\n/** Static probe identity, kept independent from the render-session runtime. */\nexport function probeInfo(\n frameworkVersion = instrumentationSentinel()?.frameworkVersion ?? INK_VERSION,\n): ProbeInfo {\n return {\n framework: 'ink',\n frameworkVersion,\n probeVersion: PACKAGE_VERSION,\n identityKind: 'stable',\n capabilities: ['stable-identity', 'intended-rect', 'visible-rect', 'annotations'],\n instrumentation: {\n highestTier: 'T3',\n semanticClass: 'A',\n degradedCapabilities: [],\n },\n };\n}\n","/** Certified Ink render capture to revision-paired semantic snapshots. */\n\nimport type { ProbeFrame, ProtocolLimits, SemanticSnapshot } from '@termwright/protocol';\nimport { writeWindowsConsoleMarker } from '@termwright/pty';\nimport { recognize } from '@termwright/recognizers';\nimport type { ProbeChannel } from '@termwright/probe-runtime';\nimport { observeInkTree, type InkDomElement } from './observe.js';\nimport type { InkFrameCapture } from './frame-capture.js';\nimport type { InkTerminalTracker, TerminalPosition } from './terminal-tracker.js';\nexport { probeInfo } from './probe-info.js';\n\nexport interface InkSessionOptions {\n readonly channel: ProbeChannel;\n readonly resolveRoot: () => InkDomElement | null;\n readonly resolveExcluded?: () => InkDomElement | null;\n readonly resolveCapture: (root: InkDomElement) => InkFrameCapture | undefined;\n /** Resolves after Ink has enqueued and flushed every stdout write for the captured render. */\n readonly waitForRenderFlush: () => Promise<void>;\n readonly stdout: NodeJS.WriteStream;\n /** Writes the authenticated marker through the same ordered transport as the frame. */\n readonly writeMarker: (marker: string) => Promise<void>;\n readonly tracker: InkTerminalTracker;\n readonly onGuaranteeViolation?: (error: Error) => void;\n}\n\nexport interface InkProbeSession {\n readonly revision: number;\n readonly frames: number;\n /** Freeze a renderer commit; refresh-only calls wait when the host tree is ahead of its capture. */\n notifyRender(options?: {\n readonly allowUnsettled?: boolean;\n /** Resolve with the first publication at or causally after this frame. */\n readonly awaitPublication?: boolean;\n }): Promise<number | null>;\n flush(): Promise<void>;\n stop(): void;\n}\n\ninterface FrozenFrame {\n readonly number: number;\n readonly capture: InkFrameCapture;\n readonly observation: ReturnType<typeof observeInkTree>;\n}\n\nexport function createInkSession(options: InkSessionOptions): InkProbeSession {\n let revision = 0;\n let frames = 0;\n let latestFrame = 0;\n let stopped = false;\n let queue: Promise<void> = Promise.resolve();\n const publicationWaiters: Array<{\n readonly targetFrame: number;\n readonly resolve: (revision: number) => void;\n readonly reject: (error: Error) => void;\n }> = [];\n\n const fail = (error: unknown): void => {\n if (stopped) return;\n stopped = true;\n const failure = error instanceof Error ? error : new Error(String(error));\n for (const waiter of publicationWaiters.splice(0)) waiter.reject(failure);\n options.onGuaranteeViolation?.(failure);\n options.channel.close();\n };\n\n const stop = (): void => {\n if (stopped) return;\n stopped = true;\n const failure = new Error('Ink probe stopped');\n for (const waiter of publicationWaiters.splice(0)) waiter.reject(failure);\n // A normal application exit or explicit cleanup is not a semantic\n // guarantee violation. Keep the typed failure callback exclusively for\n // capture/publication/marker faults, while graceful teardown simply\n // closes the producer after rejecting its owned causal waiters.\n options.channel.close();\n };\n\n const resolvePublications = (frame: number, publishedRevision: number): void => {\n for (let index = publicationWaiters.length - 1; index >= 0; index -= 1) {\n const waiter = publicationWaiters[index];\n if (waiter === undefined || waiter.targetFrame > frame) continue;\n publicationWaiters.splice(index, 1);\n waiter.resolve(publishedRevision);\n }\n };\n\n const publish = async (frozen: FrozenFrame): Promise<number | null> => {\n await nextMacrotask();\n // A marker authenticates the terminal bytes for this render, so it must\n // follow Ink's own stdout flush boundary, not just the probe's shadow drain.\n await options.waitForRenderFlush();\n await options.tracker.drain();\n if (stopped) return null;\n if (!options.channel.isOpen) {\n fail(new Error('Ink semantic channel closed before publication'));\n return null;\n }\n if (frozen.number !== latestFrame) {\n options.channel.recordCoalescedEvent();\n return null;\n }\n const context = frozen.capture.context;\n if (context === undefined) throw new Error('certified Ink frame context is unavailable');\n if (frozen.capture.screenReader) {\n throw new Error('Ink screen-reader output has no authoritative per-node cell geometry');\n }\n const position = options.tracker.position();\n if ((context.alternateScreen ? 'alternate' : 'normal') !== position.buffer) {\n throw new Error('Ink render mode and committed VT buffer disagree');\n }\n const columns = options.stdout.columns ?? 80;\n const rows = options.stdout.rows ?? 24;\n const qualified = qualifyFrame(frozen, position, columns, rows);\n revision += 1;\n const snapshot: SemanticSnapshot = recognize(qualified, {\n sessionId: options.channel.session.sessionId,\n revision,\n columns,\n rows,\n framework: 'ink',\n paintOrderKnown: false,\n maxStringBytes: options.channel.session.limits.maxStringBytes,\n });\n const marker = options.channel.publish(snapshot, {\n probeEvents: qualified.objects.length + (qualified.operations?.length ?? 0),\n });\n if (marker === undefined) throw new Error('Ink semantic publication was refused');\n // There must be no async gap between the final frame check and enqueueing\n // its marker: a newer Ink render could otherwise write in between them.\n // The selected transport establishes FRAME -> MARKER; awaiting it makes\n // `flush()` an actual publication boundary for teardown.\n await options.writeMarker(marker);\n resolvePublications(frozen.number, revision);\n return revision;\n };\n\n return {\n get revision() {\n return revision;\n },\n get frames() {\n return frames;\n },\n notifyRender(notifyOptions = {}) {\n if (stopped) return Promise.resolve(null);\n try {\n const root = options.resolveRoot();\n if (root === null) throw new Error('Ink committed frame has no retained root');\n const capture = options.resolveCapture(root);\n if (capture === undefined || capture.root !== root) {\n throw new Error('Ink committed frame has no matching certified renderer capture');\n }\n const excluded = options.resolveExcluded?.();\n const observation = observeInkTree(root, {\n frame: frames,\n limits: options.channel.session.limits as ProtocolLimits,\n ...(excluded === undefined ? {} : { excluded }),\n ...(capture.staticRoots.length === 0 ? {} : { retainedRoots: capture.staticRoots }),\n ...(capture.staticChildren.size === 0\n ? {}\n : { retainedChildren: capture.staticChildren }),\n geometry: capture.geometry,\n });\n // Layout effects can register annotations after React mutates the host\n // tree but before Ink's throttled renderer has produced the matching\n // capture. That is a transient refresh state, not a committed frame\n // whose guaranteed geometry may be downgraded. The subsequent real\n // onRender call freezes it. Renderer-originated calls remain strict.\n if (hasDisplayedNodeWithoutGeometry(observation.frame)) {\n if (notifyOptions.allowUnsettled === true) return Promise.resolve(null);\n throw new Error(\n 'certified Ink renderer capture is missing geometry for a displayed host node',\n );\n }\n frames += 1;\n latestFrame = frames;\n const frozen = { number: frames, capture, observation };\n const boundary =\n notifyOptions.awaitPublication === true\n ? new Promise<number>((resolve, reject) => {\n publicationWaiters.push({ targetFrame: frozen.number, resolve, reject });\n })\n : null;\n const publication = queue\n .then(() => publish(frozen))\n .catch((error) => {\n fail(error);\n return null;\n });\n queue = publication.then(() => undefined);\n return boundary ?? publication;\n } catch (error) {\n fail(error);\n return Promise.resolve(null);\n }\n },\n async flush() {\n await queue.catch(() => undefined);\n },\n stop,\n };\n}\n\nfunction hasDisplayedNodeWithoutGeometry(frame: ProbeFrame): boolean {\n return frame.objects.some(\n (object) => object.state?.displayed !== false && object.geometry?.intendedRect === undefined,\n );\n}\n\nfunction qualifyFrame(\n frozen: FrozenFrame,\n position: TerminalPosition,\n columns: number,\n rows: number,\n): ProbeFrame {\n const { capture, observation } = frozen;\n const context = capture.context as NonNullable<InkFrameCapture['context']>;\n const fullscreen = context.stdoutIsTTY && capture.liveRows >= context.rows;\n const liveOrigin = context.alternateScreen\n ? 0\n : !context.interactive\n ? position.row\n : context.debug || fullscreen\n ? position.row - Math.max(0, capture.liveRows - 1)\n : position.row - capture.liveRows;\n const staticOrigin = liveOrigin - capture.staticRows;\n\n return {\n ...observation.frame,\n objects: observation.frame.objects.map((object) => {\n const region = observation.geometryRegions.get(object.identity.value);\n const geometry = object.geometry;\n if (\n geometry?.intendedRect === undefined ||\n geometry.visibleRect === undefined ||\n region === undefined\n )\n return object;\n const origin = region === 'live' ? liveOrigin : staticOrigin;\n const intendedRect = shift(geometry.intendedRect, origin);\n const visibleRect =\n context.interactive || region === 'static' || context.debug\n ? viewportIntersection(shift(geometry.visibleRect, origin), columns, rows)\n : { row: Math.min(Math.max(origin, 0), rows), column: 0, width: 0, height: 0 };\n return { ...object, geometry: { intendedRect, visibleRect } };\n }),\n };\n}\n\nfunction shift(\n rect: import('@termwright/protocol').ProbeRect,\n rows: number,\n): import('@termwright/protocol').ProbeRect {\n return { ...rect, row: rect.row + rows };\n}\n\nfunction viewportIntersection(\n rect: import('@termwright/protocol').ProbeRect,\n columns: number,\n rows: number,\n): import('@termwright/protocol').ProbeRect {\n const column = Math.max(0, rect.column);\n const row = Math.max(0, rect.row);\n const right = Math.max(column, Math.min(columns, rect.column + rect.width));\n const bottom = Math.max(row, Math.min(rows, rect.row + rect.height));\n return { row, column, width: right - column, height: bottom - row };\n}\n\nfunction nextMacrotask(): Promise<void> {\n return new Promise((resolve) => setImmediate(resolve));\n}\n\nexport function createInkMarkerWriter(\n stream: NodeJS.WriteStream,\n options: {\n readonly certifiedHarness: boolean;\n readonly platform?: NodeJS.Platform;\n readonly writeWindowsMarker?: (fd: number, marker: string) => void;\n },\n): (marker: string) => Promise<void> {\n const platform = options.platform ?? process.platform;\n if (!options.certifiedHarness && platform === 'win32' && stream.isTTY === true) {\n const fd = (stream as NodeJS.WriteStream & { readonly fd?: unknown }).fd;\n if (typeof fd !== 'number' || !Number.isInteger(fd) || fd < 0) {\n return () =>\n Promise.reject(new Error('Ink stdout has no certifiable Windows console handle'));\n }\n const writeNative = options.writeWindowsMarker ?? writeWindowsConsoleMarker;\n return (marker) => {\n try {\n writeNative(fd, marker);\n return Promise.resolve();\n } catch (error) {\n return Promise.reject(error instanceof Error ? error : new Error(String(error)));\n }\n };\n }\n return (marker) =>\n new Promise((resolve, reject) => {\n if (stream.writableEnded || stream.destroyed) {\n reject(new Error('Ink stdout closed before the semantic render marker could be written'));\n return;\n }\n try {\n stream.write(marker, (error?: Error | null) => {\n if (error instanceof Error) reject(error);\n else resolve();\n });\n } catch (error) {\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n });\n}\n","/**\n * Termwright-owned native PTY session.\n *\n * The POSIX implementation owns the `forkpty()` master and reads it until the\n * kernel reports EOF (EIO after the queued tail on Linux). No JavaScript\n * stream, private node-pty field, quiet window, or retry decides that output\n * has ended.\n */\nimport { createRequire } from 'node:module';\nimport { getSystemErrorName } from 'node:util';\nimport {\n spawnWindowsPty,\n writeWindowsConsoleMarker as writeNativeWindowsConsoleMarker,\n windowsConPtyRuntimeInfo,\n windowsPtyAvailable,\n windowsPtyUnavailableReason,\n type WindowsConPtyRuntimeInfo,\n} from './windows.js';\nimport { NativeWriteDrainEpoch } from './write-drain-epoch.js';\n\nexport {\n encodeConPtyApplicationInput,\n encodeConPtyApplicationTerminalResponse,\n} from './windows-output-normalizer.js';\n\ntype NativeEvent =\n | { readonly type: 'data'; readonly data: Buffer }\n | {\n readonly type: 'exit';\n readonly exitCode: number;\n readonly signal: number;\n }\n | { readonly type: 'eof'; readonly code: number }\n | { readonly type: 'drain'; readonly generation: bigint }\n | { readonly type: 'error'; readonly message: string; readonly code: number };\n\ninterface NativeSession {\n readonly pid: number;\n write(data: Buffer): void;\n resize(columns: number, rows: number): boolean;\n /** Zero on delivery/already-gone; otherwise the positive POSIX errno. */\n signal(signal: number): number;\n treeState(): number;\n dispose(): void;\n}\n\ninterface NativeBinding {\n new (\n options: {\n readonly command: readonly string[];\n readonly cwd?: string;\n readonly env: readonly string[];\n readonly columns: number;\n readonly rows: number;\n },\n onEvent: (event: NativeEvent) => void,\n ): NativeSession;\n}\n\nlet cachedBinding: { readonly PosixPtySession: NativeBinding } | undefined;\n\nexport function candidatePaths(\n platform: NodeJS.Platform = process.platform,\n architecture: string = process.arch,\n): readonly string[] {\n return [\n '../build/Release/termwright_pty.node',\n `@termwright/pty-${platform}-${architecture}/termwright_pty.node`,\n ];\n}\n\nexport function loadPtyBinding(): { readonly PosixPtySession: NativeBinding } {\n if (cachedBinding !== undefined) return cachedBinding;\n if (process.platform === 'win32') {\n throw new Error('the POSIX @termwright/pty binding cannot load on Windows');\n }\n if (process.platform !== 'darwin' && process.platform !== 'linux') {\n throw new Error(`@termwright/pty does not support ${process.platform}-${process.arch}`);\n }\n const require = createRequire(import.meta.url);\n const attempts: string[] = [];\n for (const candidate of candidatePaths()) {\n try {\n cachedBinding = require(candidate) as {\n readonly PosixPtySession: NativeBinding;\n };\n return cachedBinding;\n } catch (error) {\n attempts.push(\n `${candidate}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`,\n );\n }\n }\n throw new Error(\n `no Termwright PTY addon could be loaded for ${process.platform}-${process.arch}. Tried:\\n ${attempts.join('\\n ')}`,\n );\n}\n\nlet unavailableReason: string | undefined;\n\nexport function ptyAvailable(): boolean {\n if (process.platform === 'win32') return windowsPtyAvailable();\n try {\n loadPtyBinding();\n unavailableReason = undefined;\n return true;\n } catch (error) {\n unavailableReason = error instanceof Error ? error.message : String(error);\n return false;\n }\n}\n\nexport function ptyUnavailableReason(): string | undefined {\n return process.platform === 'win32' ? windowsPtyUnavailableReason() : unavailableReason;\n}\n\nexport interface PtySpawnOptions {\n readonly command: readonly string[];\n readonly cwd?: string;\n readonly env: Readonly<Record<string, string>>;\n readonly columns: number;\n readonly rows: number;\n}\n\nexport interface PtyExit {\n readonly code: number | null;\n readonly signal: string | null;\n}\n\nexport type PtySignal = 'INT' | 'TERM' | 'KILL' | 'HUP';\nexport type { WindowsConPtyRuntimeInfo };\n\n/** Runtime provenance and strict initialization status for the Windows backend. */\nexport function conPtyRuntimeInfo(): WindowsConPtyRuntimeInfo {\n if (process.platform !== 'win32') {\n throw new Error('ConPTY runtime information is only available on Windows');\n }\n return windowsConPtyRuntimeInfo();\n}\n\n/**\n * Writes a marker through WriteConsoleW while temporarily enforcing the two\n * output-mode bits required for VT control sequences. Windows probes use this\n * instead of duplicating mode mutation and restoration logic.\n */\nexport function writeWindowsConsoleMarker(fd: number, marker: string): void {\n if (process.platform !== 'win32') {\n throw new Error('Windows console markers are only available on Windows');\n }\n writeNativeWindowsConsoleMarker(fd, marker);\n}\n\nexport interface PtyHandle {\n readonly pid: number;\n readonly outputEnded: Promise<void>;\n readonly sawRealEof: boolean;\n readonly endReason: number | undefined;\n write(data: Uint8Array): void;\n writeApplicationInput?(data: Uint8Array, kind: 'key' | 'mouse' | 'paste' | 'raw'): void;\n writeTerminalResponse?(\n data: Uint8Array,\n ): 'host-control' | 'application-envelope' | 'application-direct';\n /** Closes the parent-owned terminal input while preserving output drain. */\n closeInput?(): void;\n resize(columns: number, rows: number): boolean;\n signal(signal: PtySignal): boolean;\n treeState(): 'alive' | 'gone' | 'unsupported';\n onData(listener: (data: Uint8Array) => void): () => void;\n onExit(listener: (status: PtyExit) => void): () => void;\n onError(listener: (error: Error) => void): () => void;\n onDrain(listener: () => void): () => void;\n dispose(): void;\n}\n\nconst signalNumbers: Readonly<Record<PtySignal, number>> = Object.freeze({\n HUP: 1,\n INT: 2,\n KILL: 9,\n TERM: 15,\n});\n\nconst signalNames: Readonly<Record<number, string>> = Object.freeze({\n 1: 'SIGHUP',\n 2: 'SIGINT',\n 3: 'SIGQUIT',\n 6: 'SIGABRT',\n 9: 'SIGKILL',\n 13: 'SIGPIPE',\n 15: 'SIGTERM',\n});\n\nfunction validateOptions(options: PtySpawnOptions): void {\n if (\n options.command.length === 0 ||\n options.command.some((part) => typeof part !== 'string' || part.includes('\\0'))\n ) {\n throw new TypeError('command must be a non-empty array of NUL-free strings');\n }\n if (\n options.cwd !== undefined &&\n (typeof options.cwd !== 'string' || options.cwd.includes('\\0'))\n ) {\n throw new TypeError('cwd must be a NUL-free string');\n }\n for (const [key, value] of Object.entries(options.env)) {\n if (\n key.length === 0 ||\n key.includes('=') ||\n key.includes('\\0') ||\n typeof value !== 'string' ||\n value.includes('\\0')\n ) {\n throw new TypeError('environment keys and values must be valid execve strings');\n }\n }\n for (const [field, value] of [\n ['columns', options.columns],\n ['rows', options.rows],\n ] as const) {\n if (!Number.isInteger(value) || value < 1 || value > 32_767) {\n throw new RangeError(`${field} must be an integer from 1 through 32767`);\n }\n }\n}\n\nexport function spawnPty(options: PtySpawnOptions): PtyHandle {\n validateOptions(options);\n if (process.platform === 'win32') {\n const session = spawnWindowsPty(options);\n return {\n get pid(): number {\n return session.pid;\n },\n get sawRealEof(): boolean {\n return session.sawRealEof;\n },\n get endReason(): number | undefined {\n return session.endReason;\n },\n outputEnded: session.outputEnded,\n write(data): void {\n session.write(data);\n },\n writeApplicationInput(data, kind): void {\n session.writeApplicationInput(data, kind);\n },\n writeTerminalResponse(data) {\n return session.writeTerminalResponse(data);\n },\n closeInput(): void {\n session.closeInput();\n },\n resize(columns, rows): boolean {\n return session.resize(columns, rows);\n },\n signal(signal): boolean {\n if (signal !== 'KILL') return false;\n session.terminateTree();\n return true;\n },\n treeState(): 'alive' | 'gone' | 'unsupported' {\n const members = session.activeProcesses();\n return members < 0 ? 'unsupported' : members === 0 ? 'gone' : 'alive';\n },\n onData(listener): () => void {\n return session.onData(listener);\n },\n onExit(listener): () => void {\n return session.onExit(listener);\n },\n onError(listener): () => void {\n return session.onError(listener);\n },\n onDrain(listener): () => void {\n return session.onDrain(listener);\n },\n dispose(): void {\n session.dispose();\n },\n };\n }\n const dataListeners = new Set<(data: Uint8Array) => void>();\n const exitListeners = new Set<(status: PtyExit) => void>();\n const errorListeners = new Set<(error: Error) => void>();\n const drainListeners = new Set<() => void>();\n let exitStatus: PtyExit | undefined;\n let fatalError: Error | undefined;\n let resolveEnded: (() => void) | undefined;\n const outputEnded = new Promise<void>((resolve) => {\n resolveEnded = resolve;\n });\n let ended = false;\n let endReason: number | undefined;\n let disposed = false;\n const writeEpoch = new NativeWriteDrainEpoch();\n\n const session = new (loadPtyBinding().PosixPtySession)(\n {\n command: [...options.command],\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n env: Object.entries(options.env).map(([key, value]) => `${key}=${value}`),\n columns: options.columns,\n rows: options.rows,\n },\n (event) => {\n switch (event.type) {\n case 'data':\n for (const listener of [...dataListeners]) listener(event.data);\n return;\n case 'exit': {\n exitStatus =\n event.signal === 0\n ? { code: event.exitCode, signal: null }\n : {\n code: null,\n signal: signalNames[event.signal] ?? `SIG${event.signal}`,\n };\n for (const listener of [...exitListeners]) listener(exitStatus);\n return;\n }\n case 'eof':\n endReason = event.code;\n ended = event.code === 0;\n resolveEnded?.();\n return;\n case 'drain':\n if (!writeEpoch.isCurrent(event.generation)) return;\n for (const listener of [...drainListeners]) listener();\n return;\n case 'error': {\n const code = getSystemErrorName(-event.code);\n const guidance =\n code === 'EMFILE'\n ? \" Raise this process's open-file limit (for example with `ulimit -n`) and retry.\"\n : code === 'ENFILE'\n ? ' The host-wide open-file table is exhausted; raise the system limit or reduce concurrent processes.'\n : '';\n fatalError ??= Object.assign(new Error(`${event.message}${guidance}`), {\n code,\n errno: event.code,\n });\n for (const listener of [...errorListeners]) listener(fatalError);\n return;\n }\n }\n },\n );\n\n return {\n get pid(): number {\n return session.pid;\n },\n get sawRealEof(): boolean {\n return ended;\n },\n get endReason(): number | undefined {\n return endReason;\n },\n outputEnded,\n write(data): void {\n if (disposed) throw new Error('PTY input is closed');\n const bytes = Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n writeEpoch.admit(bytes, (admitted) => session.write(admitted));\n },\n resize(columns, rows): boolean {\n if (\n !Number.isInteger(columns) ||\n !Number.isInteger(rows) ||\n columns < 1 ||\n rows < 1 ||\n columns > 32_767 ||\n rows > 32_767\n )\n return false;\n return !disposed && session.resize(columns, rows);\n },\n signal(signal): boolean {\n if (disposed) return false;\n const code = session.signal(signalNumbers[signal]);\n if (code === 0) return true;\n throw Object.assign(new Error(`kill(PTY process group) failed with errno ${code}`), {\n code: getSystemErrorName(-code),\n errno: code,\n });\n },\n treeState(): 'alive' | 'gone' | 'unsupported' {\n const state = disposed ? -1 : session.treeState();\n return state > 0 ? 'alive' : state === 0 ? 'gone' : 'unsupported';\n },\n onData(listener): () => void {\n dataListeners.add(listener);\n return () => dataListeners.delete(listener);\n },\n onExit(listener): () => void {\n exitListeners.add(listener);\n const observed = exitStatus;\n if (observed !== undefined)\n queueMicrotask(() => {\n if (exitListeners.has(listener)) listener(observed);\n });\n return () => exitListeners.delete(listener);\n },\n onError(listener): () => void {\n errorListeners.add(listener);\n const observed = fatalError;\n if (observed !== undefined)\n queueMicrotask(() => {\n if (errorListeners.has(listener)) listener(observed);\n });\n return () => errorListeners.delete(listener);\n },\n onDrain(listener): () => void {\n drainListeners.add(listener);\n return () => drainListeners.delete(listener);\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n session.dispose();\n resolveEnded?.();\n dataListeners.clear();\n exitListeners.clear();\n errorListeners.clear();\n drainListeners.clear();\n },\n };\n}\n","/**\n * Termwright's Windows PTY backend.\n *\n * One native session owns the pseudoconsole, both host pipe ends, the root\n * process and thread, and the job object holding the tree. Two facts follow\n * from that ownership and are the reason this package exists:\n *\n * - a session ends when the output pipe actually ends, never when a timer says\n * nothing has arrived lately;\n * - the process tree is a job object from before the root can run, so proving\n * it empty is a query rather than a race against process enumeration.\n */\n\nimport { createRequire } from 'node:module';\nimport { NativeWriteDrainEpoch } from './write-drain-epoch.js';\nimport {\n ConPtyControlPlaneNormalizer,\n ConPtyTerminalResponseRouter,\n ConPtyTerminalResponseTransport,\n encodeConPtyApplicationInput,\n type ConPtyTerminalResponseRoute,\n} from './windows-output-normalizer.js';\n\n/** Ordered messages the native session emits. Data always precedes the end. */\ntype NativeEvent =\n | { readonly type: 'data'; readonly data: Buffer }\n | { readonly type: 'exit'; readonly exitCode: number }\n | { readonly type: 'eof'; readonly code: number }\n | { readonly type: 'drain'; readonly generation: bigint }\n | { readonly type: 'notice'; readonly message: string }\n | { readonly type: 'error'; readonly message: string; readonly code: number };\n\ninterface NativeSession {\n readonly pid: number;\n write(data: Buffer): void;\n closeInput(): void;\n resize(columns: number, rows: number): boolean;\n terminateTree(): void;\n activeProcesses(): number;\n dispose(): void;\n}\n\ninterface NativeBindingConstructor {\n new (\n options: {\n readonly commandLine: string;\n readonly cwd?: string;\n readonly env?: readonly string[];\n readonly columns: number;\n readonly rows: number;\n },\n onEvent: (event: NativeEvent) => void,\n ): NativeSession;\n}\n\ninterface LoadedWindowsBinding {\n readonly ConPtySession: NativeBindingConstructor;\n conPtyRuntimeInfo(): WindowsConPtyRuntimeInfo;\n writeWindowsConsoleMarker(fd: number, marker: string): void;\n}\n\n/** Provenance and behavioral contract of the loaded Windows pseudoconsole. */\nexport interface WindowsConPtyRuntimeInfo {\n readonly provider: 'termwright-patched-openconsole';\n readonly upstreamCommit: 'dd494ac79a82a04e1e7252a91c8939a3c3039908';\n readonly patchSha256: 'eae93025548fe697fa08242587e28abaeb06cc5ca7646fff0d9bef280c77770c';\n readonly hostCursorRpc: 'twh-cpr-v1';\n readonly applicationReplyRpc: 'twh-app-reply-v1';\n readonly mode: 'ordered-vt-passthrough';\n readonly policy: 'strict';\n readonly selectedHostArchitecture: '' | 'x64' | 'arm64';\n readonly failureCode: string;\n readonly failureWin32: number;\n readonly assetsValidated: boolean;\n readonly coreExports: boolean;\n readonly orderedMarkerSemantics: 'marker-authoritative-after-behavioral-certification';\n}\n\nfunction assertRuntimeInfoShape(value: WindowsConPtyRuntimeInfo): WindowsConPtyRuntimeInfo {\n if (\n value.provider !== 'termwright-patched-openconsole' ||\n value.upstreamCommit !== 'dd494ac79a82a04e1e7252a91c8939a3c3039908' ||\n value.patchSha256 !== 'eae93025548fe697fa08242587e28abaeb06cc5ca7646fff0d9bef280c77770c' ||\n value.hostCursorRpc !== 'twh-cpr-v1' ||\n value.applicationReplyRpc !== 'twh-app-reply-v1' ||\n Object.hasOwn(value, 'package') ||\n Object.hasOwn(value, 'version') ||\n value.mode !== 'ordered-vt-passthrough' ||\n value.policy !== 'strict' ||\n (value.selectedHostArchitecture !== '' &&\n value.selectedHostArchitecture !== 'x64' &&\n value.selectedHostArchitecture !== 'arm64') ||\n typeof value.failureCode !== 'string' ||\n typeof value.failureWin32 !== 'number' ||\n typeof value.assetsValidated !== 'boolean' ||\n typeof value.coreExports !== 'boolean' ||\n value.orderedMarkerSemantics !== 'marker-authoritative-after-behavioral-certification'\n ) {\n throw new Error(`invalid vendored ConPTY capability report: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\nfunction assertCertifiedRuntimeInfo(value: WindowsConPtyRuntimeInfo): WindowsConPtyRuntimeInfo {\n assertRuntimeInfoShape(value);\n if (\n (value.selectedHostArchitecture !== 'x64' && value.selectedHostArchitecture !== 'arm64') ||\n value.failureCode !== '' ||\n value.failureWin32 !== 0 ||\n value.assetsValidated !== true ||\n value.coreExports !== true\n ) {\n throw new Error(`uncertified vendored ConPTY runtime: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\nlet cachedBinding: LoadedWindowsBinding | undefined;\nlet cachedDiagnosticBinding: LoadedWindowsBinding | undefined;\n\n/**\n * Where the addon is looked for, in order.\n *\n * The locally compiled binary comes first so that a working tree tests what it\n * just built rather than a published prebuild that happens to be installed\n * beside it — the alternative is a change to this addon that CI certifies\n * against the previous release.\n */\nexport function windowsCandidatePaths(architecture: string): readonly string[] {\n return [\n '../build/Release/termwright_pty.node',\n `@termwright/pty-win32-${architecture}/termwright_pty.node`,\n ];\n}\n\n/** Loads the compiled addon, or explains why this platform has none. */\nexport function loadWindowsBinding(): LoadedWindowsBinding {\n if (cachedBinding !== undefined) return cachedBinding;\n if (process.platform !== 'win32') {\n throw new Error('@termwright/pty Windows binding cannot load on a non-Windows host');\n }\n const require = createRequire(import.meta.url);\n const attempts: string[] = [];\n for (const candidate of windowsCandidatePaths(process.arch)) {\n try {\n const resolved = require.resolve(candidate);\n const loaded = require(resolved) as LoadedWindowsBinding;\n assertCertifiedRuntimeInfo(loaded.conPtyRuntimeInfo());\n cachedBinding = loaded;\n return cachedBinding;\n } catch (error) {\n // Kept per candidate. \"No addon\" is the same sentence whether the\n // prebuild for this architecture was never published, the install\n // skipped it, or it is present and failed to load — and those are three\n // different things to do next.\n attempts.push(\n `${candidate}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`,\n );\n }\n }\n throw new Error(\n `no termwright ConPTY addon could be loaded for win32-${process.arch}. Tried:\\n ${attempts.join('\\n ')}`,\n );\n}\n\n/**\n * Returns a capability report even when strict runtime initialization failed.\n * A healthy candidate is preferred over an earlier diagnostic-only candidate.\n */\nexport function windowsConPtyRuntimeInfo(): WindowsConPtyRuntimeInfo {\n if (cachedBinding !== undefined) {\n return assertRuntimeInfoShape(cachedBinding.conPtyRuntimeInfo());\n }\n if (process.platform !== 'win32') {\n throw new Error('@termwright/pty Windows binding cannot load on a non-Windows host');\n }\n const require = createRequire(import.meta.url);\n const attempts: string[] = [];\n for (const candidate of windowsCandidatePaths(process.arch)) {\n try {\n const resolved = require.resolve(candidate);\n const loaded = require(resolved) as LoadedWindowsBinding;\n const runtime = assertRuntimeInfoShape(loaded.conPtyRuntimeInfo());\n if (\n runtime.failureCode === '' &&\n runtime.failureWin32 === 0 &&\n runtime.assetsValidated &&\n runtime.coreExports\n ) {\n cachedBinding = loaded;\n return runtime;\n }\n cachedDiagnosticBinding ??= loaded;\n } catch (error) {\n attempts.push(\n `${candidate}: ${error instanceof Error ? error.message.split('\\n')[0] : String(error)}`,\n );\n }\n }\n if (cachedDiagnosticBinding !== undefined) {\n return assertRuntimeInfoShape(cachedDiagnosticBinding.conPtyRuntimeInfo());\n }\n throw new Error(\n `no termwright ConPTY addon could be loaded for win32-${process.arch}. Tried:\\n ${attempts.join('\\n ')}`,\n );\n}\n\nlet unavailableReason: string | undefined;\n\n/** True when the addon is present and usable in this process. */\nexport function windowsPtyAvailable(): boolean {\n try {\n loadWindowsBinding();\n unavailableReason = undefined;\n return true;\n } catch (error) {\n // Kept, because \"not available\" is the least useful half of the answer.\n // A missing file, an ABI mismatch and a load-time failure inside the addon\n // all arrive here, and they are three different pieces of work.\n unavailableReason = error instanceof Error ? error.message : String(error);\n return false;\n }\n}\n\n/** Why the addon could not be loaded, as the loader reported it. */\nexport function windowsPtyUnavailableReason(): string | undefined {\n return unavailableReason;\n}\n\n/**\n * Writes an in-band marker to a real Windows console without inheriting a\n * framework's possibly-disabled VT output mode.\n *\n * The native primitive restores the exact original console mode before it\n * returns or throws. A successful return means WriteConsoleW accepted every\n * UTF-16 code unit synchronously.\n */\nexport function writeWindowsConsoleMarker(fd: number, marker: string): void {\n if (!Number.isInteger(fd) || fd < 0) {\n throw new RangeError('Windows console marker fd must be a non-negative integer');\n }\n if (typeof marker !== 'string' || marker.length === 0) {\n throw new TypeError('Windows console marker must be a non-empty string');\n }\n loadWindowsBinding().writeWindowsConsoleMarker(fd, marker);\n}\n\n/**\n * Quotes one argument the way CommandLineToArgvW parses it.\n *\n * Windows has no argv: the child re-parses a single string, so the caller's\n * exact arguments only survive if they are quoted to that specific grammar.\n * Backslashes are literal except immediately before a quote, where they are\n * doubled — including the run that precedes the closing quote.\n */\nexport function quoteWindowsArgument(argument: string): string {\n if (argument.length > 0 && !/[\\s\"]/u.test(argument)) return argument;\n let quoted = '\"';\n let backslashes = 0;\n for (const character of argument) {\n if (character === '\\\\') {\n backslashes += 1;\n continue;\n }\n if (character === '\"') {\n quoted += '\\\\'.repeat(backslashes * 2 + 1);\n quoted += '\"';\n backslashes = 0;\n continue;\n }\n quoted += '\\\\'.repeat(backslashes);\n quoted += character;\n backslashes = 0;\n }\n quoted += '\\\\'.repeat(backslashes * 2);\n return `${quoted}\"`;\n}\n\n/** Joins a command into the single string CreateProcessW takes. */\nexport function buildCommandLine(command: readonly string[]): string {\n if (command.length === 0) throw new TypeError('a ConPTY command needs at least an executable');\n return command.map(quoteWindowsArgument).join(' ');\n}\n\n/** Renders an environment map as the block CreateProcessW expects. */\nexport function buildEnvironment(env: Readonly<Record<string, string>>): readonly string[] {\n return Object.entries(env)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => `${key}=${value}`);\n}\n\nexport interface WindowsPtySpawnOptions {\n readonly command: readonly string[];\n readonly cwd?: string;\n readonly env: Readonly<Record<string, string>>;\n readonly columns: number;\n readonly rows: number;\n}\n\nexport interface WindowsPtyExit {\n readonly code: number | null;\n readonly signal: string | null;\n}\n\n/**\n * A live ConPTY session.\n *\n * `exited` and `outputEnded` are deliberately separate. A root process can\n * finish while its descendants still hold the pseudoconsole, so the last byte\n * of a session routinely arrives after the process that started it is gone.\n */\nexport interface WindowsPtyHandle {\n readonly pid: number;\n readonly runtimeInfo: WindowsConPtyRuntimeInfo;\n readonly outputEnded: Promise<void>;\n /**\n * True only when the output pipe actually ended.\n *\n * `outputEnded` also settles on disposal so a teardown cannot hang, which\n * means resolving it is not by itself evidence of EOF. This is the flag that\n * separates the two, and nothing but the reader sets it.\n *\n * The end itself is reached by the tree emptying: the job reports zero\n * active processes, which means no byte can follow, and the console is\n * closed only then. The reader still ends on the pipe rather than on a\n * timer — what changed is that the moment is chosen by evidence.\n */\n readonly sawRealEof: boolean;\n /**\n * The Win32 code the terminating read reported, or 0 for a clean end.\n *\n * A stream that ended for the wrong reason looks exactly like one that ended\n * properly, and telling them apart is the claim this backend exists to make.\n */\n readonly endReason: number | undefined;\n /**\n * The session's own account of its lifecycle, oldest first.\n *\n * Root exit, what the job said, and when the console was closed. These\n * moments are only observable while they happen: the console takes its\n * evidence with it when it goes, so anything reconstructed afterwards is\n * inference. Kept bounded, because a session is not a log file.\n */\n readonly notices: readonly string[];\n write(data: Uint8Array): void;\n /**\n * Stops further terminal input and closes only that pipe side. The process,\n * pseudoconsole and authoritative output stream stay alive.\n */\n closeInput(): void;\n writeApplicationInput(data: Uint8Array, kind: 'key' | 'mouse' | 'paste' | 'raw'): void;\n writeTerminalResponse(data: Uint8Array): ConPtyTerminalResponseRoute;\n resize(columns: number, rows: number): boolean;\n terminateTree(): void;\n activeProcesses(): number;\n onData(listener: (data: Uint8Array) => void): () => void;\n onExit(listener: (status: WindowsPtyExit) => void): () => void;\n onError(listener: (error: Error) => void): () => void;\n onDrain(listener: () => void): () => void;\n /**\n * Lifecycle notices as they are recorded.\n *\n * A notice describing an instant arrives after the event it follows, so\n * reading `notices` inside an exit listener sees the state before it. This\n * is how a caller waits for the account rather than racing it.\n */\n onNotice(listener: (message: string) => void): () => void;\n dispose(): void;\n}\n\nexport function spawnWindowsPty(options: WindowsPtySpawnOptions): WindowsPtyHandle {\n const binding = loadWindowsBinding();\n const dataListeners = new Set<(data: Uint8Array) => void>();\n const exitListeners = new Set<(status: WindowsPtyExit) => void>();\n const errorListeners = new Set<(error: Error) => void>();\n const drainListeners = new Set<() => void>();\n const noticeListeners = new Set<(message: string) => void>();\n\n let resolveEnded: (() => void) | undefined;\n const outputEnded = new Promise<void>((resolve) => {\n resolveEnded = resolve;\n });\n let ended = false;\n let endReason: number | undefined;\n let disposed = false;\n let inputClosed = false;\n const writeEpoch = new NativeWriteDrainEpoch();\n const terminalResponseRouter = new ConPtyTerminalResponseRouter();\n const terminalResponseTransport = new ConPtyTerminalResponseTransport();\n const outputNormalizer = new ConPtyControlPlaneNormalizer((query) =>\n terminalResponseRouter.noteHostQuery(query),\n );\n const notices: string[] = [];\n const NOTICE_LIMIT = 64;\n\n const write = (data: Uint8Array): void => {\n if (disposed || inputClosed) throw new Error('ConPTY input is closed');\n const bytes = Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n writeEpoch.admit(bytes, (admitted) => session.write(admitted));\n };\n\n const session = new binding.ConPtySession(\n {\n commandLine: buildCommandLine(options.command),\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n env: buildEnvironment(options.env),\n columns: options.columns,\n rows: options.rows,\n },\n (event) => {\n switch (event.type) {\n case 'data':\n {\n const data = outputNormalizer.push(event.data);\n if (data.length > 0) {\n for (const listener of [...dataListeners]) listener(data);\n }\n }\n return;\n case 'exit': {\n // A Windows exit code is not a signal. Reporting it as one would\n // invent POSIX semantics the platform does not have.\n const status: WindowsPtyExit = { code: event.exitCode, signal: null };\n for (const listener of [...exitListeners]) listener(status);\n return;\n }\n case 'eof':\n {\n // A possible prefix withheld across a ReadFile boundary is child\n // output unless the rest of the host structure actually arrives.\n // Release it on the same ordered channel, before authoritative EOF.\n const tail = outputNormalizer.finish();\n if (tail.length > 0) {\n for (const listener of [...dataListeners]) listener(tail);\n }\n }\n endReason = event.code;\n // Delivered on the same ordered channel as the data before it, so\n // every chunk has already reached its listeners by now.\n ended = true;\n resolveEnded?.();\n return;\n case 'drain':\n if (!writeEpoch.isCurrent(event.generation)) return;\n for (const listener of [...drainListeners]) listener();\n return;\n case 'notice':\n // Oldest dropped first: a session that somehow produces more of\n // these than the bound must not grow without limit, and the last\n // ones are the ones that describe how it ended.\n if (notices.length >= NOTICE_LIMIT) notices.shift();\n notices.push(event.message);\n for (const listener of [...noticeListeners]) listener(event.message);\n return;\n case 'error': {\n const failure = Object.assign(new Error(event.message), {\n win32: event.code,\n });\n for (const listener of [...errorListeners]) listener(failure);\n return;\n }\n }\n },\n );\n\n return {\n get pid(): number {\n return session.pid;\n },\n get runtimeInfo(): WindowsConPtyRuntimeInfo {\n return windowsConPtyRuntimeInfo();\n },\n get sawRealEof(): boolean {\n return ended;\n },\n get endReason(): number | undefined {\n return endReason;\n },\n get notices(): readonly string[] {\n return [...notices];\n },\n outputEnded,\n write(data: Uint8Array): void {\n write(data);\n },\n closeInput(): void {\n if (disposed || inputClosed) return;\n inputClosed = true;\n session.closeInput();\n },\n writeApplicationInput(data, kind): void {\n write(encodeConPtyApplicationInput(data, kind));\n },\n writeTerminalResponse(data: Uint8Array): ConPtyTerminalResponseRoute {\n const route = terminalResponseRouter.route(data);\n write(terminalResponseTransport.encode(route, data));\n return route;\n },\n resize(columns: number, rows: number): boolean {\n return disposed ? false : session.resize(columns, rows);\n },\n terminateTree(): void {\n if (!disposed) session.terminateTree();\n },\n activeProcesses(): number {\n return disposed ? -1 : session.activeProcesses();\n },\n onData(listener): () => void {\n dataListeners.add(listener);\n return () => dataListeners.delete(listener);\n },\n onExit(listener): () => void {\n exitListeners.add(listener);\n return () => exitListeners.delete(listener);\n },\n onError(listener): () => void {\n errorListeners.add(listener);\n return () => errorListeners.delete(listener);\n },\n onDrain(listener): () => void {\n drainListeners.add(listener);\n return () => drainListeners.delete(listener);\n },\n onNotice(listener): () => void {\n noticeListeners.add(listener);\n return () => noticeListeners.delete(listener);\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n session.dispose();\n // Disposal is not evidence of EOF. It unblocks anyone waiting only so a\n // teardown cannot hang; whether the stream truly ended is recorded by\n // `ended`, which nothing but the reader sets.\n resolveEnded?.();\n dataListeners.clear();\n exitListeners.clear();\n errorListeners.clear();\n drainListeners.clear();\n noticeListeners.clear();\n },\n };\n}\n","/** Keeps native drain edges tied to the writes they actually completed. */\nexport class NativeWriteDrainEpoch {\n #generation = 0n;\n\n admit<T extends Uint8Array>(data: T, write: (data: T) => void): void {\n // Advance only after native admission succeeds. Rejected and zero-length\n // writes therefore leave the JavaScript and native generations aligned.\n write(data);\n if (data.byteLength > 0) this.#generation += 1n;\n }\n\n isCurrent(generation: bigint): boolean {\n return generation === this.#generation;\n }\n}\n","/**\n * Removes control-plane modes injected by the vendored ConPTY host.\n *\n * The passthrough ConPTY deliberately enables focus and Win32 input modes for\n * its own input transport. Those bytes describe the host, not the child, and\n * must not reach the terminal emulator as application-owned mode evidence.\n * The transform is byte based: VT control sequences are ASCII and can be\n * divided at any byte by the anonymous output pipe.\n */\n\nimport { parseConPtyHostCursorResponse } from '@termwright/protocol';\n\nconst ESC = 0x1b;\n\nconst bytes = (...values: number[]): Buffer => Buffer.from(values);\n\nconst DA1 = bytes(ESC, 0x5b, 0x63);\nconst WINDOW_DEICONIFY = bytes(ESC, 0x5b, 0x31, 0x74);\nconst WINDOW_ICONIFY = bytes(ESC, 0x5b, 0x32, 0x74);\nconst FOCUS_ON = bytes(ESC, 0x5b, 0x3f, 0x31, 0x30, 0x30, 0x34, 0x68);\nconst FOCUS_OFF = bytes(ESC, 0x5b, 0x3f, 0x31, 0x30, 0x30, 0x34, 0x6c);\nconst WIN32_ON = bytes(ESC, 0x5b, 0x3f, 0x39, 0x30, 0x30, 0x31, 0x68);\nconst WIN32_OFF = bytes(ESC, 0x5b, 0x3f, 0x39, 0x30, 0x30, 0x31, 0x6c);\nconst RIS = bytes(ESC, 0x63);\nconst HOST_CURSOR_REQUEST_PREFIX = Buffer.from('\\x1b]8488;twh-cpr-v1:q:', 'ascii');\nconst HOST_CURSOR_REQUEST_TOKEN_BYTES = 32;\nconst HOST_CURSOR_REPLY_NAMESPACE = Buffer.from('\\x1b]8488;twh-cpr-v1:r:', 'ascii');\nconst APPLICATION_REPLY_PREFIX = Buffer.from('\\x1b]8488;twh-app-reply-v1:', 'ascii');\nconst OSC_TERMINATOR = Buffer.from('\\x07', 'ascii');\nconst MAX_APPLICATION_REPLY_BYTES = 4_096;\n\ninterface Rewrite {\n readonly input: Buffer;\n readonly output: Buffer;\n readonly hostQueries?: readonly ConPtyHostQuery[];\n}\n\nexport type ConPtyHostQuery = 'primary-device-attributes';\nexport type ConPtyTerminalResponseRoute = 'host-control' | 'application-envelope';\n\nexport function encodeConPtyApplicationTerminalResponse(data: Uint8Array): Buffer {\n if (data.byteLength === 0 || data.byteLength > MAX_APPLICATION_REPLY_BYTES) {\n throw new RangeError(\n `terminal response must contain between 1 and ${MAX_APPLICATION_REPLY_BYTES} bytes`,\n );\n }\n for (const byte of data) {\n if (byte > 0x7f) {\n throw new TypeError(\n `terminal response contains non-ASCII byte 0x${byte.toString(16).padStart(2, '0')}`,\n );\n }\n }\n const bytes = Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n return Buffer.concat([\n APPLICATION_REPLY_PREFIX,\n Buffer.from(`${bytes.byteLength}:${bytes.toString('hex')}`, 'ascii'),\n OSC_TERMINATOR,\n ]);\n}\n\nexport class ConPtyTerminalResponseTransport {\n encode(route: ConPtyTerminalResponseRoute, data: Uint8Array): Buffer {\n return route === 'application-envelope'\n ? encodeConPtyApplicationTerminalResponse(data)\n : Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n }\n}\n\nexport function encodeConPtyApplicationInput(\n data: Uint8Array,\n kind: 'key' | 'mouse' | 'paste' | 'raw',\n): Buffer {\n // Preserve the complete physical Escape event. Reusing the byte-oriented\n // terminal-response encoder would deliver UnicodeChar=ESC but erase the\n // VK/scan identity observed by ReadConsoleInput applications.\n return kind === 'key' && data.byteLength === 1 && data[0] === 0x1b\n ? Buffer.from('\\x1b[27;1;27;1;0;1_', 'ascii')\n : Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n}\n\nfunction isHostResponse(response: Buffer): boolean {\n const text = response.toString('ascii');\n return /^\\x1b\\[\\?[\\d;]*c$/u.test(text);\n}\n\n/**\n * Preserves the ownership of startup queries emitted by ConPTY itself.\n *\n * Host-control replies travel raw to OpenConsole. Application replies use the\n * private atomic envelope so OpenConsole can commit the decoded bytes to the\n * child in one input-buffer operation. The route keeps that ownership\n * explicit and is populated by the same split-safe startup rewrite that\n * exposes each host query to the emulator.\n */\nexport class ConPtyTerminalResponseRouter {\n readonly #hostQueries: ConPtyHostQuery[] = [];\n\n noteHostQuery(query: ConPtyHostQuery): void {\n this.#hostQueries.push(query);\n }\n\n route(response: Uint8Array): ConPtyTerminalResponseRoute {\n const bytes = Buffer.from(response.buffer, response.byteOffset, response.byteLength);\n const query = this.#hostQueries[0];\n // Cursor synchronization is a private request-addressed OSC RPC in the\n // pinned host. Standard CPR is therefore always application-owned and can\n // never be stolen by a host capture state.\n if (parseConPtyHostCursorResponse(bytes) !== null) return 'host-control';\n // The versioned reply prefix is reserved to the pinned host. A stale or\n // malformed reply must still travel raw to OpenConsole, which consumes it\n // fail-closed; encoding it as W32IM would expose host control bytes to the\n // application input queue. Other OSC 8488 payloads remain application-owned.\n if (bytes.subarray(0, HOST_CURSOR_REPLY_NAMESPACE.length).equals(HOST_CURSOR_REPLY_NAMESPACE)) {\n return 'host-control';\n }\n // A terminal reply is one control-plane transaction. The private envelope\n // makes OpenConsole buffer the complete payload through the OSC terminator,\n // then commit it with one InputBuffer::WriteString call. Sending raw CSI\n // is not sufficient: with VT input disabled OpenConsole can interpret a\n // CPR as F3. Encoding each byte as a separate KEY_EVENT is also invalid:\n // it exposes a printable tail when a VT reader resolves ESC too early.\n if (query === undefined) return 'application-envelope';\n if (!isHostResponse(bytes)) {\n throw new Error(`terminal answered ${query} ConPTY host query with an unexpected response`);\n }\n this.#hostQueries.shift();\n return 'host-control';\n }\n}\n\nconst STARTUP_REWRITES: readonly Rewrite[] = [\n {\n // VtIo's ordinary startup handshake.\n input: Buffer.concat([DA1, FOCUS_ON, WIN32_ON]),\n output: DA1,\n hostQueries: ['primary-device-attributes'],\n },\n];\n\ntype StartupPassThrough =\n | { readonly kind: 'complete'; readonly length: number }\n | {\n readonly kind: 'partial';\n }\n | null;\n\nfunction startupPassThrough(input: Buffer, offset: number): StartupPassThrough {\n for (const fixed of [WINDOW_DEICONIFY, WINDOW_ICONIFY]) {\n if (!hasPrefixAt(input, offset, fixed)) continue;\n return input.length - offset < fixed.length\n ? { kind: 'partial' }\n : { kind: 'complete', length: fixed.length };\n }\n if (!hasPrefixAt(input, offset, HOST_CURSOR_REQUEST_PREFIX)) return null;\n const remaining = input.length - offset;\n if (remaining < HOST_CURSOR_REQUEST_PREFIX.length) return { kind: 'partial' };\n const tokenStart = offset + HOST_CURSOR_REQUEST_PREFIX.length;\n const availableToken = Math.min(HOST_CURSOR_REQUEST_TOKEN_BYTES, input.length - tokenStart);\n for (let index = 0; index < availableToken; index += 1) {\n const byte = input[tokenStart + index]!;\n const hexadecimal = (byte >= 0x30 && byte <= 0x39) || (byte >= 0x61 && byte <= 0x66);\n if (!hexadecimal) return null;\n }\n const total = HOST_CURSOR_REQUEST_PREFIX.length + HOST_CURSOR_REQUEST_TOKEN_BYTES + 1;\n if (remaining < total) return { kind: 'partial' };\n if (input[offset + total - 1] !== 0x07) return null;\n return { kind: 'complete', length: total };\n}\n\nconst HOST_REWRITES: readonly Rewrite[] = [\n // AdaptDispatch reinjects these immediately after the child reset that\n // caused them. Keeping the reset preserves the child's original bytes.\n { input: Buffer.concat([FOCUS_OFF, FOCUS_ON]), output: FOCUS_OFF },\n { input: Buffer.concat([WIN32_OFF, WIN32_ON]), output: WIN32_OFF },\n { input: Buffer.concat([RIS, FOCUS_ON, WIN32_ON]), output: RIS },\n];\n\nfunction hasPrefixAt(input: Buffer, offset: number, pattern: Buffer): boolean {\n const available = Math.min(input.length - offset, pattern.length);\n for (let index = 0; index < available; index += 1) {\n if (input[offset + index] !== pattern[index]) return false;\n }\n return true;\n}\n\n/**\n * A deterministic streaming transducer for one ConPTY output stream.\n *\n * `push()` may retain only a suffix which could still become a host rewrite.\n * `finish()` releases that suffix verbatim, so a truncated or merely similar\n * child sequence is never lost at authoritative EOF.\n */\nexport class ConPtyControlPlaneNormalizer {\n #pending = Buffer.alloc(0);\n #atStreamStart = true;\n #finished = false;\n\n constructor(readonly onHostQuery: (query: ConPtyHostQuery) => void = () => undefined) {}\n\n push(chunk: Uint8Array): Buffer {\n if (this.#finished) {\n throw new Error('ConPTY output arrived after authoritative EOF');\n }\n if (chunk.byteLength === 0) return Buffer.alloc(0);\n\n const incoming = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n const input = this.#pending.length === 0 ? incoming : Buffer.concat([this.#pending, incoming]);\n this.#pending = Buffer.alloc(0);\n\n const output: Buffer[] = [];\n let literalStart = 0;\n let offset = 0;\n\n while (offset < input.length) {\n if (this.#atStreamStart) {\n const passThrough = startupPassThrough(input, offset);\n if (passThrough?.kind === 'partial') {\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n this.#pending = Buffer.from(input.subarray(offset));\n return output.length === 0 ? Buffer.alloc(0) : Buffer.concat(output);\n }\n if (passThrough?.kind === 'complete') {\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n output.push(input.subarray(offset, offset + passThrough.length));\n offset += passThrough.length;\n literalStart = offset;\n continue;\n }\n }\n const rewrites = this.#atStreamStart\n ? [...STARTUP_REWRITES, ...HOST_REWRITES]\n : HOST_REWRITES;\n let rewritten = false;\n let awaitingSuffix = false;\n\n for (const rewrite of rewrites) {\n if (!hasPrefixAt(input, offset, rewrite.input)) continue;\n const remaining = input.length - offset;\n if (remaining < rewrite.input.length) {\n awaitingSuffix = true;\n break;\n }\n\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n output.push(rewrite.output);\n for (const query of rewrite.hostQueries ?? []) this.onHostQuery(query);\n offset += rewrite.input.length;\n literalStart = offset;\n this.#atStreamStart = false;\n rewritten = true;\n break;\n }\n\n if (rewritten) continue;\n if (awaitingSuffix) {\n if (literalStart < offset) output.push(input.subarray(literalStart, offset));\n this.#pending = Buffer.from(input.subarray(offset));\n return output.length === 0 ? Buffer.alloc(0) : Buffer.concat(output);\n }\n\n // This byte cannot begin any host-owned structure. It is child output.\n this.#atStreamStart = false;\n offset += 1;\n }\n\n if (literalStart < input.length) output.push(input.subarray(literalStart));\n return output.length === 0 ? Buffer.alloc(0) : Buffer.concat(output);\n }\n\n finish(): Buffer {\n if (this.#finished) return Buffer.alloc(0);\n this.#finished = true;\n const tail = this.#pending;\n this.#pending = Buffer.alloc(0);\n return tail;\n }\n}\n","/**\n * Probe IR → semantic tree.\n *\n * The probe reports what it saw; this decides what it means. Keeping the two\n * apart is what lets six frameworks disagree about what is knowable without\n * that disagreement leaking into the tree — and it is why this module is a pure\n * function over data, testable without a process, a framework or a socket.\n *\n * Merge precedence, from decision D6: **annotation > recognizer > framework >\n * correlation > heuristic**, with one exception that matters more than the\n * order does — physical facts are never overridden by an annotation. An author\n * may name a thing; an author may not declare where it is on screen, whether it\n * has focus, or whether it is visible.\n */\n\nimport {\n DEFAULT_LIMITS,\n evidence,\n SEMANTIC_ROLES,\n type ProbeFrame,\n type ProbeObject,\n type Observation,\n type ProvenanceSource,\n type Rect,\n type SemanticNode,\n type SemanticRole,\n type SemanticSnapshot,\n type SemanticState,\n} from '@termwright/protocol';\nimport { namesFromContent, normalizeName } from './naming.js';\nimport { roleForInkAria, roleForInkHost } from './ink.js';\nimport { roleForOpenTuiClass } from './opentui.js';\n\n/** Everything the tree needs that a frame does not carry. */\nexport interface RecognizeContext {\n readonly sessionId: string;\n readonly revision: number;\n readonly columns: number;\n readonly rows: number;\n /** Which framework produced the frame; selects the level-2 role map. */\n readonly framework: string;\n /** Whether the probe reported paint order, which decides occlusion. */\n readonly paintOrderKnown?: boolean;\n /** Bound for a name correlated from descendant text. */\n readonly maxStringBytes?: number;\n}\n\nconst ROLES: ReadonlySet<string> = new Set(SEMANTIC_ROLES);\nconst UTF8_ENCODER = new TextEncoder();\n\n/** Level-2 maps, by framework. Others land on `generic`, which is legitimate. */\nconst ROLE_MAPS: Readonly<Record<string, (frameworkType: string) => SemanticRole | undefined>> =\n Object.freeze({ ink: roleForInkHost, opentui: roleForOpenTuiClass });\n\n/**\n * Resolve a role, in the normative order.\n *\n * An annotation that names a role outside the closed set is **not** silently\n * dropped: it falls through to the framework map, and the bad value stays\n * visible in the annotations rather than becoming an invented role.\n */\nfunction resolveRole(\n object: ProbeObject,\n framework: string,\n): {\n role: SemanticRole;\n source: ProvenanceSource;\n} {\n const annotated = object.annotations?.role;\n if (annotated !== undefined && ROLES.has(annotated)) {\n return { role: annotated as SemanticRole, source: 'annotation' };\n }\n if (framework === 'ink' && object.accessibility?.role !== undefined) {\n const mapped = roleForInkAria(object.accessibility.role);\n if (mapped !== undefined) return { role: mapped, source: 'framework' };\n }\n const mapped = ROLE_MAPS[framework]?.(object.frameworkType);\n if (mapped !== undefined) return { role: mapped, source: 'recognizer' };\n return { role: 'generic', source: 'recognizer' };\n}\n\n/**\n * Resolve a name.\n *\n * An annotation wins, including a deliberate empty string. Otherwise the text\n * the probe observed is used **only** for roles that take their name from\n * content; everything else keeps an empty name rather than absorbing the label\n * of something inside it.\n */\nfunction resolveName(\n object: ProbeObject,\n role: SemanticRole,\n subtreeText: ReadonlyMap<string, string>,\n): { name: string; source: ProvenanceSource } {\n const annotated = object.annotations?.name;\n if (annotated !== undefined) return { name: annotated, source: 'annotation' };\n if (object.accessibility?.name !== undefined) {\n return { name: object.accessibility.name, source: 'framework' };\n }\n if (namesFromContent(role) && object.text !== undefined) {\n return { name: normalizeName(object.text), source: 'framework' };\n }\n if (namesFromContent(role)) {\n const correlated = subtreeText.get(object.identity.value);\n if (correlated !== undefined && correlated.length > 0) {\n return { name: normalizeName(correlated), source: 'recognizer' };\n }\n }\n return { name: '', source: 'recognizer' };\n}\n\n/**\n * Text reachable below each object, bounded once at every subtree.\n *\n * Probe IR keeps own text and descendants separate. Name-from-content is a\n * recognizer inference over parent links, not a reason for a probe to claim a\n * container directly carried its children's string. A nested annotated host is\n * an accessible-content boundary: it can name itself, but its label must not\n * leak into an outer control.\n */\nfunction collectSubtreeText(\n frame: ProbeFrame,\n maxStringBytes: number,\n): ReadonlyMap<string, string> {\n const objectById = new Map(frame.objects.map((object) => [object.identity.value, object]));\n const children = new Map<string, string[]>();\n for (const object of frame.objects) {\n if (object.parent === undefined || !objectById.has(object.parent)) continue;\n const siblings = children.get(object.parent) ?? [];\n siblings.push(object.identity.value);\n children.set(object.parent, siblings);\n }\n\n const memo = new Map<string, string>();\n const visiting = new Set<string>();\n const collect = (id: string): string => {\n const held = memo.get(id);\n if (held !== undefined) return held;\n if (visiting.has(id)) return '';\n visiting.add(id);\n const object = objectById.get(id);\n let result = object?.text ?? '';\n for (const child of children.get(id) ?? []) {\n const childText = collect(child);\n const childObject = objectById.get(child);\n if (\n childObject?.annotations?.role !== undefined ||\n childObject?.accessibility?.role !== undefined\n )\n continue;\n if (childText.length === 0) continue;\n // Separate host elements are separate content runs. Raw text fragments\n // inside one host were already joined by the probe, but sibling hosts\n // need a word boundary (`<Text>Save</Text><Text>now</Text>`).\n result = clampUtf8(\n result.length === 0 ? childText : `${result} ${childText}`,\n maxStringBytes,\n );\n if (UTF8_ENCODER.encode(result).byteLength >= maxStringBytes) break;\n }\n visiting.delete(id);\n result = clampUtf8(result, maxStringBytes);\n memo.set(id, result);\n return result;\n };\n\n for (const object of frame.objects) collect(object.identity.value);\n return memo;\n}\n\nfunction clampUtf8(value: string, maxBytes: number): string {\n if (UTF8_ENCODER.encode(value).byteLength <= maxBytes) return value;\n let result = '';\n let bytes = 0;\n for (const codePoint of value) {\n const size = UTF8_ENCODER.encode(codePoint).byteLength;\n if (bytes + size > maxBytes) break;\n result += codePoint;\n bytes += size;\n }\n return result;\n}\n\n/** Map observed state onto the protocol's closed state set. */\nfunction resolveState(\n object: ProbeObject,\n hiddenByGeometry: boolean,\n offscreen: boolean,\n): SemanticState | undefined {\n const observed = object.state;\n const state: Record<string, unknown> = {};\n\n if (observed?.focused !== undefined) state['focused'] = observed.focused;\n if (observed?.disabled !== undefined) state['disabled'] = observed.disabled;\n if (observed?.checked !== undefined) state['checked'] = observed.checked;\n if (observed?.expanded !== undefined) state['expanded'] = observed.expanded;\n if (observed?.readonly !== undefined) state['readonly'] = observed.readonly;\n if (observed?.selected !== undefined) state['selected'] = observed.selected;\n if (observed?.busy !== undefined) state['busy'] = observed.busy;\n if (observed?.multiline !== undefined) state['multiline'] = observed.multiline;\n if (observed?.required !== undefined) state['required'] = observed.required;\n if (observed?.multiselectable !== undefined) state['multiselectable'] = observed.multiselectable;\n if (observed?.displayed !== undefined) state['hidden'] = !observed.displayed;\n // Probe IR keeps item selection distinct from a text range. The semantic\n // tree represents the highlighted item's zero-based index as the matching\n // one-based collection position.\n if (observed?.selectedIndex !== undefined) state['positionInSet'] = observed.selectedIndex + 1;\n // Clipped entirely away is a different fact from the framework's own display\n // flag, and both end up as `hidden` because the wire has one field for it.\n if (hiddenByGeometry) state['hidden'] = true;\n if (offscreen) {\n state['hidden'] = true;\n state['offscreen'] = true;\n }\n\n return Object.keys(state).length === 0 ? undefined : (state as SemanticState);\n}\n\n/**\n * Turn one observed frame into a semantic snapshot.\n *\n * Every object becomes a node: nothing is dropped for being unrecognised, which\n * is what `generic` plus `frameworkType` is for. Parent links are rewritten\n * from probe identities to node ids, and an object whose parent did not survive\n * is attached to the root rather than left dangling — a snapshot with a missing\n * parent is refused by validation, and losing the subtree would be worse than\n * reparenting it.\n */\nexport function recognize(frame: ProbeFrame, context: RecognizeContext): SemanticSnapshot {\n // Ink boxes do not own their rendered label; it survives only in descendant\n // text hosts. Other framework probes already define the meaning of their own\n // `text` field and must not silently gain Ink's host-specific correlation.\n const subtreeText: ReadonlyMap<string, string> =\n context.framework === 'ink'\n ? collectSubtreeText(frame, context.maxStringBytes ?? DEFAULT_LIMITS.maxStringBytes)\n : new Map();\n const idByIdentity = new Map<string, string>();\n for (const object of frame.objects) {\n idByIdentity.set(object.identity.value, `n${object.identity.value}`);\n }\n\n const nodes: SemanticNode[] = [];\n const rootIds: string[] = [];\n\n for (const object of frame.objects) {\n const id = idByIdentity.get(object.identity.value) as string;\n const { role, source: roleSource } = resolveRole(object, context.framework);\n const { name, source: nameSource } = resolveName(object, role, subtreeText);\n\n const intended = object.geometry?.intendedRect;\n const visible = object.geometry?.visibleRect;\n const hiddenByGeometry = visible !== undefined && (visible.width === 0 || visible.height === 0);\n const offscreen =\n hiddenByGeometry && intended !== undefined && intended.width > 0 && intended.height > 0;\n const state = resolveState(object, hiddenByGeometry, offscreen);\n const parentId = object.parent === undefined ? undefined : idByIdentity.get(object.parent);\n if (parentId === undefined) rootIds.push(id);\n\n // One source for the node, exceptions listed. Physical facts always come\n // from the framework: an annotation may not move a widget on screen.\n const px: Record<string, ProvenanceSource> = {};\n if (nameSource !== roleSource) px['name'] = nameSource;\n if (object.geometry !== undefined) px['geometry'] = 'framework';\n if (state !== undefined) px['state'] = 'framework';\n if (object.annotations?.description !== undefined && roleSource !== 'annotation') {\n px['description'] = 'annotation';\n } else if (object.accessibility?.description !== undefined && roleSource !== 'framework') {\n px['description'] = 'framework';\n }\n if (object.annotations?.testId !== undefined && roleSource !== 'annotation') {\n px['testId'] = 'annotation';\n }\n if (object.annotations?.extended !== undefined && roleSource !== 'annotation') {\n px['extended'] = 'annotation';\n }\n if (object.annotations?.actions !== undefined && roleSource !== 'annotation') {\n px['actions'] = 'annotation';\n }\n if (object.annotations?.inputRecipes !== undefined && roleSource !== 'annotation') {\n px['inputRecipes'] = 'annotation';\n }\n if (object.annotations?.labelledBy !== undefined && roleSource !== 'annotation') {\n px['labelledBy'] = 'annotation';\n }\n if (object.annotations?.describedBy !== undefined && roleSource !== 'annotation') {\n px['describedBy'] = 'annotation';\n }\n\n const labelledBy = object.annotations?.labelledBy\n ?.map((identity) => idByIdentity.get(identity))\n .filter((target): target is string => target !== undefined);\n const describedBy = object.annotations?.describedBy\n ?.map((identity) => idByIdentity.get(identity))\n .filter((target): target is string => target !== undefined);\n\n const displayed: Observation<boolean> =\n object.state?.displayed !== undefined\n ? {\n status: 'known',\n value: object.state.displayed,\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n }\n : object.unobservable?.includes('displayed') === true\n ? { status: 'unsupported', capability: 'displayed', reason: 'framework-unobservable' }\n : { status: 'unsupported', capability: 'displayed', reason: 'framework-unobservable' };\n const intendedRect: Observation<Rect> =\n displayed.status === 'known' &&\n displayed.value === false &&\n displayed.evidence.strength === 'authoritative'\n ? {\n status: 'absent',\n reason: 'not-displayed',\n evidence: { ...displayed.evidence, strength: 'authoritative' },\n }\n : object.geometry?.intendedRect !== undefined\n ? {\n status: 'known',\n value: object.geometry.intendedRect,\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n }\n : object.unobservable?.includes('intendedRect') === true\n ? {\n status: 'unsupported',\n capability: 'intended-rect',\n reason: 'framework-unobservable',\n }\n : {\n status: 'unsupported',\n capability: 'intended-geometry',\n reason: 'framework-unobservable',\n };\n const visibleRect: Observation<Rect> =\n displayed.status === 'known' &&\n displayed.value === false &&\n displayed.evidence.strength === 'authoritative'\n ? {\n status: 'absent',\n reason: 'not-displayed',\n evidence: { ...displayed.evidence, strength: 'authoritative' },\n }\n : displayed.status === 'known' && displayed.value === false\n ? {\n status: 'unsupported',\n capability: 'clipped-geometry',\n reason: 'framework-unobservable',\n }\n : object.geometry?.visibleRect !== undefined\n ? {\n status: 'known',\n value: object.geometry.visibleRect,\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n }\n : object.unobservable?.includes('visibleRect') === true\n ? {\n status: 'unsupported',\n capability: 'visible-rect',\n reason: 'framework-unobservable',\n }\n : {\n status: 'unsupported',\n capability: 'clipped-geometry',\n reason: 'framework-unobservable',\n };\n\n nodes.push({\n id,\n ...(parentId === undefined ? {} : { parentId }),\n role,\n name,\n ...(object.annotations?.description === undefined\n ? object.accessibility?.description === undefined\n ? {}\n : { description: object.accessibility.description }\n : { description: object.annotations.description }),\n frameworkType: object.frameworkType,\n geometry: { displayed, intendedRect, visibleRect },\n ...(state === undefined ? {} : { state }),\n ...(object.annotations?.testId === undefined ? {} : { testId: object.annotations.testId }),\n ...(object.annotations?.extended === undefined\n ? {}\n : { extended: object.annotations.extended }),\n ...(object.annotations?.actions === undefined ? {} : { actions: object.annotations.actions }),\n ...(object.annotations?.inputRecipes === undefined\n ? {}\n : { inputRecipes: object.annotations.inputRecipes }),\n ...(labelledBy === undefined || labelledBy.length === 0 ? {} : { labelledBy }),\n ...(describedBy === undefined || describedBy.length === 0 ? {} : { describedBy }),\n ...(object.state?.value === undefined\n ? {}\n : {\n value: {\n status: 'known' as const,\n value: object.state.value,\n sensitivity: object.state?.valueSensitivity ?? 'sensitive',\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n },\n }),\n p: roleSource,\n ...(Object.keys(px).length === 0 ? {} : { px }),\n });\n }\n\n return {\n v: 2,\n sessionId: context.sessionId,\n revision: context.revision,\n columns: context.columns,\n rows: context.rows,\n rootIds,\n nodes,\n coordinateSpace: {\n status: 'known',\n value: 'viewport-cells',\n evidence: evidence('framework', 'instrumented', 'authoritative', context.framework),\n },\n hitGrid: {\n status: 'unsupported',\n capability: 'pointer-hit-grid',\n reason: 'framework-unobservable',\n },\n };\n}\n","/**\n * Where a name comes from, and — more often — where it must not.\n *\n * The protocol's adapter conventions gate descendant-text naming to nine roles.\n * The rule exists because naming containers from their content makes\n * `getByRole('region', {name: 'Approve'})` match the dialog *containing* the\n * Approve button, so every ancestor of a label becomes a plausible match and\n * locators stop being selective. That failure is quiet: the tree looks richer,\n * and the tests get worse.\n */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\n/**\n * Roles whose accessible name comes from the text they contain.\n *\n * Normative list, from the protocol README. Anything outside it is a container\n * and keeps an empty name unless an annotation gives it one.\n */\nexport const NAME_FROM_CONTENT: ReadonlySet<SemanticRole> = new Set<SemanticRole>([\n 'button',\n 'listitem',\n 'menuitem',\n 'tab',\n 'checkbox',\n 'radio',\n 'cell',\n 'row',\n 'heading',\n]);\n\n/**\n * Whether a role takes its name from the text it contains.\n *\n * `text` is included beyond the normative list: a text node's string is its\n * *own* content — naming source 2, \"the widget's own label\" — rather than a\n * descendant widget's. Without it `getByText` would have nothing to match.\n */\nexport function namesFromContent(role: SemanticRole): boolean {\n return role === 'text' || NAME_FROM_CONTENT.has(role);\n}\n\n/** Collapse whitespace the way a terminal reader would see it. */\nexport function normalizeName(text: string): string {\n return text.replace(/\\s+/gu, ' ').trim();\n}\n","/** Conservative role map for the only host kinds Ink retains after reconcile. */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\nconst HOST_ROLES: Readonly<Record<string, SemanticRole>> = Object.freeze({\n 'ink-root': 'application',\n 'ink-text': 'text',\n 'ink-virtual-text': 'text',\n 'ink-box': 'generic',\n});\n\n/**\n * Resolve only facts the host kind itself proves.\n *\n * Ink discards source component names before host creation, so a plain box is\n * never promoted to `button` from its children or styling. A retained\n * `aria-role` travels separately as framework-native accessibility metadata.\n */\nexport function roleForInkHost(host: string): SemanticRole | undefined {\n return HOST_ROLES[host];\n}\n\nconst ARIA_ROLES: Readonly<Record<string, SemanticRole>> = Object.freeze({\n button: 'button',\n checkbox: 'checkbox',\n combobox: 'generic',\n list: 'list',\n listbox: 'list',\n listitem: 'listitem',\n menu: 'menu',\n menuitem: 'menuitem',\n option: 'listitem',\n progressbar: 'progressbar',\n radio: 'radio',\n radiogroup: 'generic',\n tab: 'tab',\n tablist: 'generic',\n table: 'table',\n textbox: 'textbox',\n timer: 'status',\n toolbar: 'generic',\n});\n\n/** Map Ink's retained aria vocabulary without guessing ambiguous containers. */\nexport function roleForInkAria(role: string): SemanticRole | undefined {\n return ARIA_ROLES[role];\n}\n","/**\n * OpenTUI's widget vocabulary, mapped onto the protocol's closed role set.\n *\n * OpenTUI has **no accessibility layer at all** — the Phase 0 audit found no\n * `role`, no `aria`, no `checked` anywhere in its types — so the class name is\n * the only signal there is. That makes this map level 2 of role resolution: it\n * runs after an author annotation and before `generic`.\n *\n * The map is deliberately short. A widget with no unambiguous counterpart stays\n * `generic` and keeps its `frameworkType`, which is what the protocol's D1\n * decision exists for: an unrecognised widget survives with its bounds, text\n * and children instead of being dropped, and a test can still find it by what\n * the framework called it. Inventing `tab` for `TabSelectRenderable` would be\n * the opposite trade — a role that reads right and makes locators match the\n * wrong thing.\n */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\nconst ROLE_BY_CLASS: Readonly<Record<string, SemanticRole>> = Object.freeze({\n RootRenderable: 'application',\n TextRenderable: 'text',\n TextNodeRenderable: 'text',\n RootTextNodeRenderable: 'text',\n CodeRenderable: 'text',\n MarkdownRenderable: 'text',\n ASCIIFontRenderable: 'text',\n InputRenderable: 'textbox',\n TextareaRenderable: 'textbox',\n EditBufferRenderable: 'textbox',\n SelectRenderable: 'list',\n TextTableRenderable: 'table',\n ScrollBarRenderable: 'scrollbar',\n});\n\n/**\n * The role OpenTUI's class name implies, if any.\n *\n * @returns a role, or `undefined` when the class has no unambiguous\n * counterpart — `BoxRenderable`, `ScrollBoxRenderable`, `TabSelectRenderable`,\n * `SliderRenderable` and every application subclass land there on purpose.\n */\nexport function roleForOpenTuiClass(frameworkType: string): SemanticRole | undefined {\n return Object.hasOwn(ROLE_BY_CLASS, frameworkType) ? ROLE_BY_CLASS[frameworkType] : undefined;\n}\n\n/**\n * Whether a class is one of OpenTUI's own.\n *\n * Used to decide whether an unmapped `frameworkType` is a widget we chose not\n * to classify or an application's own subclass — a distinction worth keeping,\n * because the second is the case `generic` was designed for.\n */\nexport function isOpenTuiClass(frameworkType: string): boolean {\n return frameworkType.endsWith('Renderable');\n}\n","/** Replacement source for Ink's public entry module. */\n\n/** Marker on the shim's re-import of the untouched module. */\nexport const ORIGINAL_MARKER = 'termwright-original=1';\n\n/**\n * Ink's ESM entry under ordinary node_modules and Bun's versioned cache.\n * Matching the resolved path, rather than the bare specifier, is required by\n * both loader APIs used here.\n */\nexport const INK_ENTRY_PATTERN = /[\\\\/](?:ink|ink@[^\\\\/]+)[\\\\/]build[\\\\/]index\\.js$/u;\n\n/** The separately-built runtime imported by replacement module source. */\nexport const INSTRUMENT_URL = new URL('./instrument.js', import.meta.url).href;\n\nexport function shouldShim(urlOrPath: string): boolean {\n if (urlOrPath.includes(ORIGINAL_MARKER)) return false;\n return INK_ENTRY_PATTERN.test(urlOrPath.split('?')[0] ?? '');\n}\n\nexport function originalUrl(urlOrPath: string): string {\n return `${urlOrPath}${urlOrPath.includes('?') ? '&' : '?'}${ORIGINAL_MARKER}`;\n}\n\n/** Ink's renderer instance, resolved beside the intercepted public entry. */\nexport function reconcilerUrl(urlOrPath: string): string {\n const [path] = urlOrPath.split('?');\n if (path === undefined || !INK_ENTRY_PATTERN.test(path)) {\n throw new Error(`Cannot resolve Ink reconciler beside ${urlOrPath}`);\n }\n return path.replace(/index\\.js$/u, 'reconciler.js');\n}\n\n/**\n * Forward the complete Ink namespace and shadow only `render`.\n *\n * The wrapper receives the already-marked original namespace, so the runtime\n * never imports `ink` itself and cannot recurse through the loader hook.\n */\nexport function buildShimSource(target: string, instrumentUrl = INSTRUMENT_URL): string {\n const original = JSON.stringify(originalUrl(target));\n const reconciler = JSON.stringify(reconcilerUrl(target));\n const instrument = JSON.stringify(instrumentUrl);\n return `import * as __termwright_original from ${original};\nimport __termwright_reconciler from ${reconciler};\nimport {wrapInkRender as __termwright_wrap} from ${instrument};\nexport * from ${original};\n\nexport const render = __termwright_wrap(__termwright_original, {reconciler: __termwright_reconciler});\n`;\n}\n","/** Runtime activation shared by the two preload entry points. */\n\nimport { ENV_ENDPOINT, ENV_TOKEN } from '@termwright/protocol';\n\n/** Runtimes into which the Ink probe can be injected. */\nexport type ProbeRuntime = 'bun' | 'node';\n\n/** Read-only environment view, so activation is testable without mutation. */\nexport type EnvSource = Readonly<Record<string, string | undefined>>;\n\n/** Both secrets are required. A partial environment remains fully dormant. */\nexport function isInstrumented(env: EnvSource): boolean {\n const endpoint = env[ENV_ENDPOINT];\n const token = env[ENV_TOKEN];\n return (\n typeof endpoint === 'string' &&\n endpoint.length > 0 &&\n typeof token === 'string' &&\n token.length > 0\n );\n}\n","/** Build an application command with the zero-config Ink preload attached. */\n\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { ProbeRuntime } from './runtime.js';\n\n/** Published preload paths; callers never have to guess package layout. */\nexport const PROBE_ENTRIES = {\n bun: fileURLToPath(new URL('./bun-preload.js', import.meta.url)),\n node: fileURLToPath(new URL('./node-hook.js', import.meta.url)),\n} as const;\n\nexport interface ProbeCommand {\n readonly command: readonly string[];\n readonly runtime: ProbeRuntime;\n}\n\n/** Prefix a normal Node or Bun command with the matching preload flag. */\nexport function withProbe(runtime: ProbeRuntime, argv: readonly string[]): ProbeCommand {\n if (argv.length === 0) throw new Error('withProbe needs an interpreter in argv');\n const [interpreter, ...rest] = argv as [string, ...string[]];\n const flag = runtime === 'bun' ? '--preload' : '--import';\n return {\n command: [interpreter, flag, runtimePreloadSpecifier(runtime, PROBE_ENTRIES[runtime]), ...rest],\n runtime,\n };\n}\n\n/** Node needs a file URL on Windows; Bun's Windows preload resolver needs a native path. */\nexport function runtimePreloadSpecifier(runtime: ProbeRuntime, entry: string): string {\n return runtime === 'bun' ? entry : pathToFileURL(entry).href;\n}\n","interface OwnedSocket {\n destroy(): unknown;\n on(event: 'error', listener: () => void): unknown;\n once(event: 'close', listener: () => void): unknown;\n pause(): unknown;\n resume(): unknown;\n}\n\ninterface ClosableServer {\n close(callback: (error?: Error) => void): unknown;\n}\n\n/** Owns accepted peers across listener startup and causal shutdown. */\nexport class ProbePeerOwner {\n readonly #sockets = new Set<OwnedSocket>();\n readonly #pending: OwnedSocket[] = [];\n #handler: ((socket: OwnedSocket) => void) | null = null;\n #closing = false;\n #closePromise: Promise<void> | null = null;\n\n admit(socket: OwnedSocket): boolean {\n socket.pause();\n this.#sockets.add(socket);\n socket.on('error', () => socket.destroy());\n socket.once('close', () => {\n this.#sockets.delete(socket);\n const pending = this.#pending.indexOf(socket);\n if (pending >= 0) this.#pending.splice(pending, 1);\n });\n if (this.#closing) {\n socket.destroy();\n return false;\n }\n if (this.#handler === null) this.#pending.push(socket);\n else this.#deliver(socket);\n return true;\n }\n\n activate(handler: (socket: OwnedSocket) => void): void {\n if (this.#handler !== null) throw new Error('probe peer owner is already active');\n this.#handler = handler;\n for (const socket of this.#pending.splice(0)) this.#deliver(socket);\n }\n\n close(server: ClosableServer): Promise<void> {\n this.#closePromise ??= this.#close(server);\n return this.#closePromise;\n }\n\n async #close(server: ClosableServer): Promise<void> {\n this.#closing = true;\n const closed = new Promise<void>((resolve, reject) => {\n server.close((error) => (error === undefined ? resolve() : reject(error)));\n });\n this.#pending.length = 0;\n for (const socket of this.#sockets) socket.destroy();\n await closed;\n }\n\n #deliver(socket: OwnedSocket): void {\n this.#handler?.(socket);\n socket.resume();\n }\n}\n","import type { ExitStatus } from '@termwright/driver';\nimport type { PtyProcess } from '@termwright/driver/experimental';\n\nconst DEFAULT_WATCHDOG_MS = 10_000;\n\nexport interface ProbeProcessShutdownResources {\n readonly pty: PtyProcess;\n readonly closeAdmission: () => Promise<void>;\n readonly closeTerminalResponseAdmission: () => void;\n readonly drainParser: () => Promise<void>;\n readonly disposeParser: () => void;\n readonly removeArtifacts: () => Promise<void>;\n readonly watchdogMs?: number;\n}\n\n/**\n * Owns the causal boundary between a probe fixture and its teardown artifacts.\n *\n * Root exit is not output EOF on ConPTY. Conversely, disposing a PTY settles\n * `outputEnded` so teardown cannot hang, but that settlement is explicitly not\n * proof that the reader reached its source. This coordinator therefore waits\n * for both the already-armed exit observer and authoritative EOF, then drains\n * the terminal parser, before it disposes either parser or PTY and before it\n * unlinks files the child may still have open.\n */\nexport class ProbeProcessShutdown {\n readonly #resources: ProbeProcessShutdownResources;\n readonly #exit: Promise<ExitStatus>;\n readonly #resolveExit: (status: ExitStatus) => void;\n #exitStatus: ExitStatus | null = null;\n #stopPromise: Promise<void> | null = null;\n\n constructor(resources: ProbeProcessShutdownResources) {\n this.#resources = resources;\n let resolveExit!: (status: ExitStatus) => void;\n this.#exit = new Promise<ExitStatus>((resolve) => {\n resolveExit = resolve;\n });\n this.#resolveExit = resolveExit;\n }\n\n observeExit(status: ExitStatus): void {\n if (this.#exitStatus !== null) return;\n this.#exitStatus = Object.freeze({ ...status });\n this.#resolveExit(this.#exitStatus);\n }\n\n stop(): Promise<void> {\n this.#stopPromise ??= this.#stop();\n return this.#stopPromise;\n }\n\n async #stop(): Promise<void> {\n const failures: unknown[] = [];\n let admissionClosedSuccessfully = false;\n let admissionClosed: Promise<void>;\n try {\n admissionClosed = this.#resources.closeAdmission().then(\n () => {\n admissionClosedSuccessfully = true;\n },\n (error: unknown) => {\n failures.push(error);\n },\n );\n } catch (error) {\n failures.push(error);\n admissionClosed = Promise.resolve();\n }\n\n const controller = new AbortController();\n let causalBoundaryReached = false;\n try {\n await this.#withWatchdog(this.#reachCausalBoundary(controller.signal), controller);\n causalBoundaryReached = true;\n } catch (error) {\n failures.push(error);\n }\n\n // The alive-tree branch revokes replies immediately before hard kill\n // closes ConPTY input. A process that was already gone keeps the bridge\n // through its remaining output and parser drain, since a queued host query\n // can still require a response before OpenConsole publishes EOF.\n try {\n this.#resources.closeTerminalResponseAdmission();\n } catch (error) {\n failures.push(error);\n }\n\n for (const dispose of [\n () => this.#resources.pty.dispose(),\n () => this.#resources.disposeParser(),\n ]) {\n try {\n dispose();\n } catch (error) {\n failures.push(error);\n }\n }\n\n try {\n await admissionClosed;\n } catch (error) {\n failures.push(error);\n }\n\n // A failed process/output/parser boundary leaves the artifact in place as\n // evidence. Retrying its deletion would replace a causal contract with a\n // timing heuristic and can hide the handle owner that broke teardown.\n if (admissionClosedSuccessfully && causalBoundaryReached) {\n try {\n await this.#resources.removeArtifacts();\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, 'adapter probe cleanup failed');\n }\n\n async #reachCausalBoundary(signal: AbortSignal): Promise<void> {\n const { pty } = this.#resources;\n const initialTree = pty.treeState?.() ?? 'unsupported';\n if (initialTree === 'alive') {\n if (pty.hardKillTree === undefined) {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY exposes no owned-tree kill operation',\n );\n }\n this.#resources.closeTerminalResponseAdmission();\n await pty.hardKillTree(signal);\n } else if (initialTree === 'unsupported') {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY exposes no authoritative owned-tree state',\n );\n }\n\n const outputEnded = pty.outputEnded;\n if (outputEnded === undefined) {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY exposes no output EOF barrier',\n );\n }\n\n await this.#awaitOrAbort(\n Promise.all([this.#exit, outputEnded]).then(() => undefined),\n signal,\n );\n signal.throwIfAborted();\n if (pty.sawOutputEnd?.() !== true) {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the PTY output ended without authoritative EOF',\n );\n }\n if (pty.treeState?.() !== 'gone') {\n throw new Error(\n `adapter probe cannot prove teardown of process ${String(pty.pid)}: ` +\n 'the owned process tree was not confirmed gone at EOF',\n );\n }\n await this.#awaitOrAbort(this.#resources.drainParser(), signal);\n signal.throwIfAborted();\n }\n\n async #awaitOrAbort(operation: Promise<void>, signal: AbortSignal): Promise<void> {\n signal.throwIfAborted();\n let removeAbortListener = (): void => undefined;\n const aborted = new Promise<never>((_resolve, reject) => {\n const onAbort = (): void => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n removeAbortListener = () => signal.removeEventListener('abort', onAbort);\n });\n try {\n await Promise.race([operation, aborted]);\n } finally {\n removeAbortListener();\n }\n }\n\n async #withWatchdog(operation: Promise<void>, controller: AbortController): Promise<void> {\n const watchdogMs = this.#resources.watchdogMs ?? DEFAULT_WATCHDOG_MS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expired = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(\n new Error(\n `adapter probe teardown did not reach exit, authoritative EOF, and parser drain ` +\n `within its ${String(watchdogMs)} ms watchdog`,\n ),\n );\n controller.abort();\n }, watchdogMs);\n });\n try {\n await Promise.race([operation, expired]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n controller.abort();\n }\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport { mkdtemp } from 'node:fs/promises';\nimport { createServer, type Server } from 'node:net';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { PtyProcess } from '@termwright/driver/experimental';\nimport { ProbePeerOwner } from './probe-peer-owner.js';\nimport { rollbackProbeStart } from './probe-start-cleanup.js';\n\nexport type ProbeListen = (server: Server, endpoint: string) => Promise<void>;\n\nexport interface ProbeEndpointAcquisition {\n readonly listen?: ProbeListen;\n /** Exercises directory ownership on named-pipe hosts in the transaction test. */\n readonly allocateDirectory?: boolean;\n}\n\n/** Mutable ownership boundary for every resource acquired during probe startup. */\nexport class ProbeStartupTransaction {\n readonly peers = new ProbePeerOwner();\n server: Server | null = null;\n directory: string | null = null;\n endpoint: string | null = null;\n debugFile: string | null = null;\n pty: PtyProcess | null = null;\n\n async acquireEndpoint(\n instrument: boolean,\n options: ProbeEndpointAcquisition = {},\n ): Promise<void> {\n if (!instrument) return;\n this.server = createServer();\n this.server.on('connection', (socket) => this.peers.admit(socket));\n if (process.platform !== 'win32' || options.allocateDirectory === true) {\n this.directory = await mkdtemp(join(tmpdir(), 'termwright-probe-'));\n }\n if (process.platform === 'win32') {\n this.endpoint = `\\\\\\\\.\\\\pipe\\\\termwright-probe-${randomBytes(16).toString('hex')}`;\n } else {\n this.endpoint = join(this.directory as string, 'semantic.sock');\n }\n await (options.listen ?? listenServer)(this.server, this.endpoint);\n }\n\n rollback(primary: unknown): Promise<never> {\n const server = this.server;\n const pty = this.pty;\n return rollbackProbeStart(primary, {\n ...(server === null || !server.listening\n ? {}\n : { closeAdmission: () => this.peers.close(server) }),\n ...(pty === null ? {} : { disposePty: () => pty.dispose() }),\n directory: this.directory,\n debugFile: this.debugFile,\n });\n }\n}\n\nfunction listenServer(server: Server, endpoint: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const onError = (error: Error): void => {\n server.removeListener('listening', onListening);\n reject(error);\n };\n const onListening = (): void => {\n server.removeListener('error', onError);\n resolve();\n };\n server.once('error', onError);\n server.once('listening', onListening);\n server.listen(endpoint);\n });\n}\n","import { rm } from 'node:fs/promises';\n\nexport interface ProbeStartCleanup {\n readonly closeAdmission?: () => Promise<void>;\n readonly disposePty?: () => void;\n readonly directory?: string | null;\n readonly debugFile?: string | null;\n}\n\n/** Rolls back every resource acquired before AdapterProbe startup committed. */\nexport async function rollbackProbeStart(\n primary: unknown,\n cleanup: ProbeStartCleanup,\n): Promise<never> {\n const failures: unknown[] = [primary];\n let serverClosed = Promise.resolve();\n if (cleanup.closeAdmission !== undefined) {\n try {\n serverClosed = cleanup.closeAdmission();\n } catch (error) {\n failures.push(error);\n }\n }\n if (cleanup.disposePty !== undefined) {\n try {\n cleanup.disposePty();\n } catch (error) {\n failures.push(error);\n }\n }\n try {\n await serverClosed;\n } catch (error) {\n failures.push(error);\n }\n for (const path of [cleanup.directory, cleanup.debugFile]) {\n if (path === undefined || path === null) continue;\n try {\n await rm(path, { recursive: path === cleanup.directory, force: true });\n } catch (error) {\n failures.push(error);\n }\n }\n if (failures.length === 1) throw primary;\n throw new AggregateError(failures, 'adapter probe startup and rollback failed', {\n cause: primary,\n });\n}\n"],"mappings":"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","names":["existsSync","readFileSync","tmpdir","join","DEFAULT_LIMITS","PROTOCOL_ID","rm","tmpdir","join","randomBytes","DEFAULT_LIMITS","ENV_ENDPOINT","ENV_TOKEN","fileURLToPath","fileURLToPath","bytes","join","join","ENV_ENDPOINT","ENV_TOKEN","join","tmpdir","randomBytes","rm","DEFAULT_LIMITS","join","tmpdir","PROTOCOL_ID","DEFAULT_LIMITS","observation","existsSync","readFileSync"]}
|