@termwright/protocol 0.2.0

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.
@@ -0,0 +1,1565 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Environment variable names injected by the driver before spawning the child. */
4
+ declare const ENV_ENDPOINT = "TERMWRIGHT_ENDPOINT";
5
+ declare const ENV_TOKEN = "TERMWRIGHT_TOKEN";
6
+ declare const ENV_PROTOCOL = "TERMWRIGHT_PROTOCOL";
7
+ /** Current protocol major version. */
8
+ declare const PROTOCOL_VERSION: 1;
9
+ declare const PROTOCOL_ID: "termwright/1";
10
+ /** Qualified observation protocol. V1 remains exported for existing adapters. */
11
+ declare const PROTOCOL_V2_ID: "termwright/2";
12
+ type ProtocolId = typeof PROTOCOL_ID | typeof PROTOCOL_V2_ID;
13
+ declare const SUPPORTED_PROTOCOL_IDS: readonly ProtocolId[];
14
+ /** Entropy behind a session token, in bytes (256 bits). */
15
+ declare const TOKEN_BYTES = 32;
16
+ /**
17
+ * Mint a session token for `TERMWRIGHT_TOKEN`.
18
+ *
19
+ * **The token is an opaque UTF-8 string end to end.** Whatever lands in the
20
+ * env var is what both sides feed to the HMAC as the key — the driver must not
21
+ * decode it back to bytes, and an adapter must not re-encode it. Honouring
22
+ * that is what keeps non-JS clients (Python, Go, Rust) interoperable, since
23
+ * they only ever see the string.
24
+ *
25
+ * The encoding here (base64url, 43 characters) is therefore a convention, not
26
+ * a constraint: it is compact, shell-safe, and free of `=` padding.
27
+ *
28
+ * @returns A fresh 256-bit token. Never log or embed it; it authenticates the
29
+ * render markers.
30
+ */
31
+ declare function generateToken(): string;
32
+
33
+ /**
34
+ * Typed protocol failures. Everything in this package fails closed: a hostile
35
+ * or merely malformed input never produces a partially-trusted value, it
36
+ * produces a {@link ProtocolViolation} (imperative APIs) or a structured
37
+ * `{ ok: false }` result (validation APIs).
38
+ */
39
+ /** Machine-readable reason a value was rejected. */
40
+ type ProtocolViolationCode =
41
+ /** Declared frame length exceeds the negotiated ceiling. */
42
+ 'frame-oversized'
43
+ /** Frame header/body is structurally impossible (zero length, bad JSON). */
44
+ | 'frame-malformed'
45
+ /** Frame body is not well-formed UTF-8. */
46
+ | 'frame-encoding'
47
+ /** Decoder already failed; it is poisoned and refuses further input. */
48
+ | 'decoder-poisoned'
49
+ /** Value is not representable as JSON (undefined, bigint, function, NaN…). */
50
+ | 'dto-scalar'
51
+ /** String contains unpaired surrogates. */
52
+ | 'dto-string'
53
+ /** Object graph is not a tree: the same object is reachable twice. */
54
+ | 'dto-alias'
55
+ /** Property is an accessor (getter/setter) rather than plain data. */
56
+ | 'dto-accessor'
57
+ /** Value carries symbol keys. */
58
+ | 'dto-symbol'
59
+ /** Value is a Proxy, or has a prototype other than Object/Array/null. */
60
+ | 'dto-prototype'
61
+ /** Array has holes or extra own properties. */
62
+ | 'dto-sparse'
63
+ /** Property name is reserved (`__proto__`, `constructor`, `prototype`). */
64
+ | 'dto-key'
65
+ /** Nesting exceeds the permitted depth. */
66
+ | 'dto-depth'
67
+ /** A marker argument is outside its permitted domain. */
68
+ | 'marker-argument';
69
+ /**
70
+ * Thrown when untrusted input violates a protocol invariant.
71
+ *
72
+ * Never carries the offending value or the session token — only a code and a
73
+ * short structural description safe to log.
74
+ */
75
+ declare class ProtocolViolation extends Error {
76
+ /** Machine-readable reason. */
77
+ readonly code: ProtocolViolationCode;
78
+ constructor(code: ProtocolViolationCode, message: string);
79
+ }
80
+
81
+ /**
82
+ * v1 semantic roles. ARIA-aligned; closed set. Unknown roles must be rejected
83
+ * during validation — they never silently acquire behavior.
84
+ */
85
+ declare const SEMANTIC_ROLES: readonly ["application", "region", "dialog", "alert", "status", "list", "listitem", "menu", "menuitem", "button", "checkbox", "radio", "tab", "textbox", "heading", "text", "progressbar", "separator", "scrollbar", "table", "row", "cell", "generic"];
86
+ type SemanticRole = (typeof SEMANTIC_ROLES)[number];
87
+ /** Descriptive action capabilities. Diagnostic/strategy hints, never callback endpoints. */
88
+ declare const SEMANTIC_ACTIONS: readonly ["focus", "activate", "toggle", "setValue", "scroll", "select", "expand"];
89
+ type SemanticAction = (typeof SEMANTIC_ACTIONS)[number];
90
+
91
+ /**
92
+ * Conservative defaults and absolute maxima. Callers may tighten defaults but
93
+ * can never widen the absolute maxima.
94
+ */
95
+ interface ProtocolLimits {
96
+ readonly maxFrameBytes: number;
97
+ /**
98
+ * Byte ceiling for one snapshot or probe frame.
99
+ *
100
+ * 2 MiB rather than 1: at the measured 217.5 B/node a full `maxNodes` tree
101
+ * is 1 062 KiB before a single provenance byte, so the old default
102
+ * contradicted the node ceiling it shipped with.
103
+ */
104
+ readonly maxSnapshotBytes: number;
105
+ readonly maxNodes: number;
106
+ readonly maxDepth: number;
107
+ readonly maxStringBytes: number;
108
+ readonly maxRelationTargets: number;
109
+ readonly maxQueuedFrames: number;
110
+ readonly maxPendingWaiters: number;
111
+ readonly maxSessions: number;
112
+ /** Byte ceiling for one serialised application log record. */
113
+ readonly maxLogRecordBytes: number;
114
+ /** Log records the driver buffers per session before evicting the oldest. */
115
+ readonly maxLogQueue: number;
116
+ }
117
+ declare const DEFAULT_LIMITS: ProtocolLimits;
118
+ declare const ABSOLUTE_LIMITS: ProtocolLimits;
119
+ /** Default semantic negotiation window (ms) before a session settles as generic. */
120
+ declare const DEFAULT_NEGOTIATION_MS = 250;
121
+
122
+ /**
123
+ * Probe IR — what an instrumented process **observed**, not what it means.
124
+ *
125
+ * A probe reports facts; a recognizer turns them into the semantic tree. The
126
+ * split exists because the six frameworks disagree about what is even knowable,
127
+ * and collapsing that disagreement early is how a tree ends up asserting things
128
+ * no framework ever said.
129
+ *
130
+ * Three rules shape every type here, each forced by the Phase 0 audits:
131
+ *
132
+ * 1. **Never fabricate identity.** Immediate-mode frameworks have none, and a
133
+ * synthesised ordinal presented as a handle is worse than no handle: a test
134
+ * written against it fails later and looks flaky rather than wrong. Identity
135
+ * is therefore a typed capability with `frame-local` as a first-class value.
136
+ * 2. **Intent is not ownership.** The rectangle a widget was drawn *into* is not
137
+ * the cells it ended up owning; later writes win and no framework records
138
+ * who painted what. The two are separate fields, and only one framework
139
+ * computes the second.
140
+ * 3. **Absent and unobservable are different facts.** A state a framework does
141
+ * not expose is not a state that is off. The IR says which is which, rather
142
+ * than letting `undefined` mean both.
143
+ *
144
+ * Naming note: the words `region` and `area` are avoided throughout. Each
145
+ * carries at least three conflicting meanings across the audited frameworks,
146
+ * and an IR that reuses them inherits every one of those ambiguities.
147
+ */
148
+ /**
149
+ * How an object's identity behaves across frames.
150
+ *
151
+ * `frame-local` is a legitimate answer, not a degraded one: in immediate mode
152
+ * the widget is consumed by the render and nothing upstream survives to be
153
+ * named again. A consumer must not correlate `frame-local` values between
154
+ * frames.
155
+ */
156
+ type ProbeIdentityKind = 'stable' | 'frame-local';
157
+ /** An object's identity, tagged with what it is worth. */
158
+ interface ProbeIdentity {
159
+ readonly kind: ProbeIdentityKind;
160
+ /** Unique within its frame; unique across the session only when `stable`. */
161
+ readonly value: string;
162
+ }
163
+ /**
164
+ * A rectangle in terminal cells.
165
+ *
166
+ * Deliberately not called a region or an area: `row`/`column` are absolute
167
+ * cell coordinates, and negative origins are legal because a widget may be
168
+ * partly scrolled off.
169
+ */
170
+ interface ProbeRect {
171
+ readonly row: number;
172
+ readonly column: number;
173
+ readonly width: number;
174
+ readonly height: number;
175
+ }
176
+ /**
177
+ * Where an object was drawn.
178
+ *
179
+ * `intendedRect` is where it *asked* to draw. It is a statement of intent, not
180
+ * a claim on cells: frameworks do not clip it, do not validate it against the
181
+ * viewport, and a later write silently wins. For overlapping UIs — popups,
182
+ * modals, shadows — it is not where the object ended up.
183
+ *
184
+ * `visibleRect` is the intersection with the clip imposed by ancestors, which
185
+ * is the closest any framework gets to "what the user can see". Only one of
186
+ * the six computes it; everywhere else it is absent, and inferring it from
187
+ * `intendedRect` would be inventing a fact.
188
+ */
189
+ interface ProbeGeometry {
190
+ readonly intendedRect?: ProbeRect;
191
+ readonly visibleRect?: ProbeRect;
192
+ }
193
+ /** Scroll position, in cells, of a scrollable object's viewport. */
194
+ interface ProbeScroll {
195
+ readonly row: number;
196
+ readonly column: number;
197
+ }
198
+ /** Total scrollable extent, in cells. Absent where a framework cannot report it. */
199
+ interface ProbeExtent {
200
+ readonly rows: number;
201
+ readonly columns: number;
202
+ }
203
+ /**
204
+ * State a probe read directly from the framework.
205
+ *
206
+ * Every field is optional, and absence means "not reported by this probe".
207
+ * A field the framework is *known* not to expose belongs in
208
+ * {@link ProbeObject.unobservable} instead, so a consumer can tell "off" from
209
+ * "unknowable".
210
+ *
211
+ * The three selection facts have separate names on purpose. An accessibility
212
+ * `selected` flag, a highlighted collection index and a selected text range
213
+ * are not interchangeable, even though frameworks often call all three
214
+ * "selection".
215
+ */
216
+ interface ProbeObservedState {
217
+ readonly focused?: boolean;
218
+ readonly disabled?: boolean;
219
+ readonly checked?: boolean | 'mixed';
220
+ readonly expanded?: boolean;
221
+ readonly readonly?: boolean;
222
+ readonly selected?: boolean;
223
+ readonly busy?: boolean;
224
+ readonly multiline?: boolean;
225
+ /**
226
+ * Whether the framework's own display flag is on. Distinct from being
227
+ * scrolled out of view, which shows up as an empty `visibleRect`.
228
+ */
229
+ readonly displayed?: boolean;
230
+ /** Contents of a value-bearing widget. `''` means empty, not absent. */
231
+ readonly value?: string;
232
+ /** Highlighted item in a collection, by index. Not a text selection. */
233
+ readonly selectedIndex?: number;
234
+ /** Selected text range within this object. Not an item selection. */
235
+ readonly textSelection?: {
236
+ readonly start: number;
237
+ readonly end: number;
238
+ };
239
+ readonly scroll?: ProbeScroll;
240
+ readonly scrollExtent?: ProbeExtent;
241
+ }
242
+ /** Field names a probe can declare unobservable. */
243
+ declare const PROBE_UNOBSERVABLE_FIELDS: readonly ["focused", "disabled", "checked", "expanded", "readonly", "selected", "busy", "multiline", "displayed", "value", "selectedIndex", "textSelection", "scroll", "scrollExtent", "intendedRect", "visibleRect", "paintOrder", "text", "parent"];
244
+ type ProbeUnobservableField = (typeof PROBE_UNOBSERVABLE_FIELDS)[number];
245
+ /**
246
+ * Author-supplied annotations carried verbatim.
247
+ *
248
+ * The probe does not interpret these — a recognizer does, at the top of the
249
+ * merge precedence. `role` is deliberately a free string here: it is whatever
250
+ * the author wrote, and validating it against the closed role set is the
251
+ * recognizer's job, which can then report a bad annotation instead of silently
252
+ * dropping it.
253
+ */
254
+ interface ProbeAccessibilityHints {
255
+ /** Framework-native accessibility role, in the framework's vocabulary. */
256
+ readonly role?: string;
257
+ readonly name?: string;
258
+ readonly description?: string;
259
+ }
260
+ interface ProbeAnnotations {
261
+ readonly role?: string;
262
+ readonly name?: string;
263
+ readonly testId?: string;
264
+ readonly description?: string;
265
+ /** Application-domain JSON state, kept outside the portable state flags. */
266
+ readonly extended?: SemanticExtendedState;
267
+ /** Descriptive action intent; never callbacks or a second input channel. */
268
+ readonly actions?: readonly SemanticAction[];
269
+ /** Probe identity values of author-declared labelling relationships. */
270
+ readonly labelledBy?: readonly string[];
271
+ /** Probe identity values of author-declared description relationships. */
272
+ readonly describedBy?: readonly string[];
273
+ }
274
+ /**
275
+ * One object a probe observed in a frame.
276
+ *
277
+ * `frameworkType` is required. It is the framework's own name for the thing —
278
+ * a class name, a constructor name, a widget type — and it is what keeps an
279
+ * unrecognised widget alive as a `generic` node instead of being dropped. Its
280
+ * quality varies enormously (Textual gives a full class ancestry; Ink gives one
281
+ * of four host-element names), so a recognizer must treat it as a hint, not a
282
+ * classification.
283
+ */
284
+ interface ProbeObject {
285
+ readonly identity: ProbeIdentity;
286
+ readonly frameworkType: string;
287
+ /** Parent's identity value; absent for a root. */
288
+ readonly parent?: string;
289
+ readonly geometry?: ProbeGeometry;
290
+ readonly state?: ProbeObservedState;
291
+ /** Text the object itself carries, not its descendants'. */
292
+ readonly text?: string;
293
+ /** Accessibility metadata retained by the framework itself, not author SDK data. */
294
+ readonly accessibility?: ProbeAccessibilityHints;
295
+ readonly annotations?: ProbeAnnotations;
296
+ /**
297
+ * Where this object sits in paint order: higher was painted later, and
298
+ * therefore on top.
299
+ *
300
+ * Available in three of the six frameworks (a compositor hit-test, a z-order
301
+ * child list, a paint-order key) and absent in the rest. It is the only fact
302
+ * that makes "is my target actually the thing at this cell" answerable
303
+ * without inventing cell ownership, which no framework records.
304
+ */
305
+ readonly paintOrder?: number;
306
+ /**
307
+ * Facts this framework cannot report for this object. Distinct from a field
308
+ * simply being absent, which means the probe did not report it this time.
309
+ */
310
+ readonly unobservable?: readonly ProbeUnobservableField[];
311
+ }
312
+ /**
313
+ * A render or layout call the probe intercepted.
314
+ *
315
+ * Only some frameworks expose a call stream, and in immediate mode it is the
316
+ * *only* structure that exists — there is no tree to walk, just an ordered list
317
+ * of "this type was drawn into this rectangle". `ordinal` is the position in
318
+ * that stream and is meaningful only within its frame.
319
+ */
320
+ interface ProbeOperation {
321
+ readonly kind: 'render' | 'layout';
322
+ readonly ordinal: number;
323
+ /** Identity of the object this call concerned, when the probe can attribute it. */
324
+ readonly target?: ProbeIdentity;
325
+ readonly frameworkType?: string;
326
+ readonly intendedRect?: ProbeRect;
327
+ }
328
+ /**
329
+ * One observed frame.
330
+ *
331
+ * `objects` may be empty and `operations` may carry everything: that is what an
332
+ * immediate-mode frame looks like, and a flat op list is a legal degenerate
333
+ * tree rather than an error.
334
+ */
335
+ interface ProbeFrame {
336
+ /** Monotonic within the session. Every framework has exactly one of these. */
337
+ readonly frame: number;
338
+ readonly objects: readonly ProbeObject[];
339
+ readonly operations?: readonly ProbeOperation[];
340
+ }
341
+ /** Optional abilities a probe declares at handshake time. */
342
+ declare const PROBE_CAPABILITIES: readonly ["stable-identity", "visible-rect", "operations", "annotations", "frame-begin", "paint-order"];
343
+ type ProbeCapability = (typeof PROBE_CAPABILITIES)[number];
344
+ /**
345
+ * What a probe says about itself when it attaches.
346
+ *
347
+ * @remarks
348
+ * `frame-begin` is optional for a reason that is easy to get wrong. No audited
349
+ * framework offers a hook guaranteed to fire before every frame: one lets a
350
+ * pre-draw hook veto the frame entirely (so the post-draw hook never runs), one
351
+ * exposes only a post-frame hook, and one decouples submission from the flush
352
+ * with a ticker. A consumer must therefore never read "no frame-begin" as "no
353
+ * frame in progress" — doing so turns four of the six frameworks into a hang
354
+ * rather than an error.
355
+ */
356
+ interface ProbeInfo {
357
+ /** Framework name, e.g. `ink`, `textual`, `ratatui`. */
358
+ readonly framework: string;
359
+ readonly frameworkVersion?: string;
360
+ /** Version of the probe itself, so a mismatch is diagnosable. */
361
+ readonly probeVersion: string;
362
+ /** The best identity this probe can offer for any object. */
363
+ readonly identityKind: ProbeIdentityKind;
364
+ readonly capabilities: readonly ProbeCapability[];
365
+ }
366
+ /**
367
+ * Where a semantic fact came from.
368
+ *
369
+ * Ranked: an annotation is what the author said, a recognizer is what our rules
370
+ * concluded, `framework` is what the framework itself reported, `correlation`
371
+ * is what matching across sources implied, and `heuristic` is a guess that
372
+ * happened to be useful. The merge precedence follows this order, except that
373
+ * physical facts — bounds, focus, visibility, cells — are never casually
374
+ * overridden by an annotation: an author may name a thing, but may not declare
375
+ * where it is on screen.
376
+ */
377
+ declare const PROVENANCE_SOURCES: readonly ["annotation", "recognizer", "framework", "correlation", "heuristic"];
378
+ type ProvenanceSource = (typeof PROVENANCE_SOURCES)[number];
379
+
380
+ /**
381
+ * Resolving IR geometry into the single rectangle a semantic node publishes.
382
+ *
383
+ * The IR keeps `intendedRect` and `visibleRect` apart because they are
384
+ * different facts. `SemanticNode.bounds` is one rectangle, so somewhere the two
385
+ * have to collapse — and that collapse is a decision, not a formatting step.
386
+ *
387
+ * The decision: **`bounds` is always the best known *visible* geometry.** A
388
+ * consumer never has to ask which of the two it is holding, because the answer
389
+ * is always the same one. Publishing both rectangles instead would push "which
390
+ * of these did you mean" onto every consumer of the tree — the same one-field-
391
+ * two-jobs problem, moved rather than solved.
392
+ *
393
+ * What a consumer still cannot know from `bounds` alone is whether something
394
+ * else was painted on top. That is what {@link ResolvedBounds.occlusion}
395
+ * carries, and it is why the two are resolved together here rather than in five
396
+ * independent implementations.
397
+ */
398
+
399
+ /** Whether occlusion is knowable for this node. */
400
+ type OcclusionKnowledge = 'known' | 'unknown';
401
+ /** Which of the IR rectangles the published bounds came from. */
402
+ type BoundsSource = 'visible' | 'clipped' | 'intended';
403
+ /** The rectangle a node publishes, plus what is known about it. */
404
+ interface ResolvedBounds {
405
+ readonly rect: Rect;
406
+ /**
407
+ * `known` only when the probe reports paint order. Without it, a rectangle
408
+ * says where a widget is, not whether a pointer aimed there reaches it.
409
+ */
410
+ readonly occlusion: OcclusionKnowledge;
411
+ readonly source: BoundsSource;
412
+ /**
413
+ * True when the clip removed the rectangle entirely — the node exists and is
414
+ * scrolled out of view.
415
+ *
416
+ * A normalizer maps this to **`state.hidden: true` plus
417
+ * `state.offscreen: true`**. Both are needed and they say different things:
418
+ * `hidden` because a zero-area rectangle cannot intersect the viewport and
419
+ * validation refuses it otherwise, and `offscreen` because scrolled-away is
420
+ * not the same state as never-displayed, and a consumer reading the tree has
421
+ * no other way to tell them apart.
422
+ */
423
+ readonly clippedAway: boolean;
424
+ }
425
+ /** Settings for {@link resolveNodeBounds}. */
426
+ interface ResolveBoundsOptions {
427
+ /**
428
+ * The clip imposed by ancestors, where the framework exposes one and has not
429
+ * already applied it to `visibleRect`.
430
+ */
431
+ readonly clip?: ProbeRect;
432
+ /** Whether the probe reports paint order for this object. */
433
+ readonly paintOrderKnown?: boolean;
434
+ }
435
+ /**
436
+ * Collapse IR geometry into the rectangle a semantic node publishes.
437
+ *
438
+ * Three tiers, best first:
439
+ * 1. `visibleRect`, where the framework computed the clip intersection itself;
440
+ * 2. `intendedRect ∩ clip`, where a clip is known but not pre-applied;
441
+ * 3. `intendedRect` alone, as a last resort — it is where the widget *asked* to
442
+ * draw, which is the only thing left when nothing knows about clipping.
443
+ *
444
+ * @param geometry - IR geometry for the object, if it reported any.
445
+ * @param options - Clip and paint-order knowledge.
446
+ * @returns The resolved bounds, or `undefined` when the object reported no
447
+ * geometry at all. A bounds-free node is a legal, expected state — one audited
448
+ * framework hands over a rendered string with no coordinates anywhere — and
449
+ * inventing a rectangle for it would be worse than having none.
450
+ */
451
+ declare function resolveNodeBounds(geometry: ProbeGeometry | undefined, options?: ResolveBoundsOptions): ResolvedBounds | undefined;
452
+
453
+ /** Why a fact could not be observed. Unknown is retryable; unsupported is not. */
454
+ type ObservationUnknownReason = 'not-reported' | 'temporary' | 'clip-unobservable' | 'legacy-unqualified';
455
+ type ObservationAbsentReason = 'detached' | 'not-displayed' | 'not-laid-out';
456
+ type ObservationUnsupportedReason = 'capability' | 'framework-unobservable' | 'not-negotiated';
457
+ type ObservationEvidence = 'adapter' | 'probe' | 'terminal-grid' | 'viewport-clip' | 'paint-order' | 'hit-grid' | 'legacy-v1';
458
+ /**
459
+ * A fact with its epistemic state preserved.
460
+ *
461
+ * Consumers must never coerce `unknown`/`unsupported` to false, nor absence to
462
+ * an empty value. That rule prevents assertions from passing because a probe
463
+ * simply could not observe the requested property.
464
+ */
465
+ type Observation<T> = {
466
+ readonly status: 'known';
467
+ readonly value: T;
468
+ readonly evidence: ObservationEvidence;
469
+ } | {
470
+ readonly status: 'absent';
471
+ readonly reason: ObservationAbsentReason;
472
+ } | {
473
+ readonly status: 'unknown';
474
+ readonly reason: ObservationUnknownReason;
475
+ } | {
476
+ readonly status: 'unsupported';
477
+ readonly capability: string;
478
+ readonly reason: ObservationUnsupportedReason;
479
+ };
480
+ /** Atomic identity of the screen/tree pair used for an observation. */
481
+ interface ObservationStamp {
482
+ readonly sessionId: string;
483
+ readonly screenRevision: number;
484
+ readonly semanticRevision: number | null;
485
+ }
486
+ type CoordinateSpace = 'viewport-cells' | 'framework-local-cells';
487
+ interface LocatorGeometry {
488
+ readonly stamp: ObservationStamp;
489
+ readonly coordinateSpace: Observation<CoordinateSpace>;
490
+ readonly intendedRect: Observation<Rect>;
491
+ readonly visibleRect: Observation<Rect>;
492
+ }
493
+ interface ViewportIntersection {
494
+ /** Half-open intersection in viewport cell coordinates. */
495
+ readonly rect: Rect;
496
+ /** Intersection area / intended area. Zero-area intended rect has ratio 0. */
497
+ readonly ratio: number;
498
+ readonly fullyInside: boolean;
499
+ }
500
+ interface LocatorVisibility {
501
+ readonly stamp: ObservationStamp;
502
+ readonly attached: Observation<boolean>;
503
+ readonly displayed: Observation<boolean>;
504
+ readonly viewport: Observation<ViewportIntersection>;
505
+ readonly offscreen: Observation<boolean>;
506
+ }
507
+ interface CellPoint {
508
+ readonly row: number;
509
+ readonly column: number;
510
+ }
511
+ interface PointerHitTest {
512
+ readonly stamp: ObservationStamp;
513
+ readonly point: Observation<CellPoint>;
514
+ readonly receivesEvents: Observation<boolean>;
515
+ /** Ref of the actual recipient, when the producer can identify it. */
516
+ readonly recipient: Observation<string>;
517
+ }
518
+ type SpatialRelation = 'contains' | 'inside' | 'overlaps' | 'left-of' | 'right-of' | 'above' | 'below' | 'aligned-left' | 'aligned-right' | 'aligned-top' | 'aligned-bottom' | 'adjacent-horizontal' | 'adjacent-vertical';
519
+ /** Correct half-open rectangle intersection. Touching edges do not overlap. */
520
+ declare function intersectRects(a: Rect, b: Rect): Rect;
521
+ declare function rectArea(rect: Rect): number;
522
+ declare function viewportIntersection(rect: Rect, columns: number, rows: number): ViewportIntersection;
523
+ declare function spatialRelation(a: Rect, relation: SpatialRelation, b: Rect): boolean;
524
+
525
+ /** Zero-based viewport cell coordinates. */
526
+ interface Rect {
527
+ readonly row: number;
528
+ readonly column: number;
529
+ readonly width: number;
530
+ readonly height: number;
531
+ }
532
+ /** Closed state set. No arbitrary records. */
533
+ interface SemanticState {
534
+ readonly disabled?: boolean;
535
+ readonly focused?: boolean;
536
+ readonly selected?: boolean;
537
+ readonly checked?: boolean | 'mixed';
538
+ readonly expanded?: boolean;
539
+ readonly modal?: boolean;
540
+ readonly busy?: boolean;
541
+ readonly hidden?: boolean;
542
+ /**
543
+ * The node exists in the layout, but every one of its cells falls outside the
544
+ * visible area — it is scrolled out, and scrolling can bring it back.
545
+ *
546
+ * Named for the claim a test author makes ("this row is off screen"), not for
547
+ * the mechanism that produced it. Clipping is how it happens; being off
548
+ * screen is what it means.
549
+ *
550
+ * **Absent means "not claiming"**, not "on screen". A producer that cannot
551
+ * observe clipping simply omits it, which is why this is a positive
552
+ * assertion rather than a tri-state.
553
+ *
554
+ * It exists so that `bounds: undefined` keeps its single meaning — "this
555
+ * producer does not know the geometry". Before this field, an adapter had to
556
+ * choose between saying "no geometry" and saying "scrolled away", and those
557
+ * are different facts that a consumer reading a tree generically could not
558
+ * tell apart.
559
+ *
560
+ * Implies {@link SemanticState.hidden}: if every cell is outside the visible
561
+ * area then the node is not visible, and validation refuses the pair
562
+ * `offscreen: true` without `hidden: true`.
563
+ */
564
+ readonly offscreen?: boolean;
565
+ readonly readonly?: boolean;
566
+ readonly multiline?: boolean;
567
+ readonly orientation?: 'horizontal' | 'vertical';
568
+ readonly level?: number;
569
+ readonly positionInSet?: number;
570
+ readonly setSize?: number;
571
+ readonly scrollOffset?: number;
572
+ readonly scrollExtent?: number;
573
+ }
574
+ /** Maps grapheme offsets of a node's text to cell coordinates (optional capability). */
575
+ interface SemanticTextRange {
576
+ readonly startOffset: number;
577
+ readonly endOffset: number;
578
+ readonly rect: Rect;
579
+ }
580
+ /**
581
+ * Deterministic JSON data owned by the application domain, not by the portable
582
+ * semantic vocabulary. Containers are allowed, but runtime objects/functions
583
+ * are not; validation applies the protocol's normal depth, byte and collection
584
+ * ceilings recursively.
585
+ */
586
+ interface SemanticExtendedArray extends ReadonlyArray<SemanticExtendedValue> {
587
+ }
588
+ interface SemanticExtendedObject {
589
+ readonly [key: string]: SemanticExtendedValue;
590
+ }
591
+ type SemanticExtendedValue = null | boolean | number | string | SemanticExtendedArray | SemanticExtendedObject;
592
+ /** Application-defined state, deliberately separate from {@link SemanticState}. */
593
+ type SemanticExtendedState = SemanticExtendedObject;
594
+ interface SemanticNode {
595
+ readonly id: string;
596
+ readonly parentId?: string;
597
+ readonly role: SemanticRole;
598
+ readonly name: string;
599
+ readonly description?: string;
600
+ readonly value?: string;
601
+ /**
602
+ * The node's **visible** geometry, guaranteed.
603
+ *
604
+ * Normalizers resolve this to the best known visible rectangle — the clip
605
+ * intersection where a framework computes one, `intendedRect ∩ clip` where a
606
+ * clip is known, and the intended rectangle only as a last resort. A consumer
607
+ * therefore never has to ask which rectangle it is holding.
608
+ *
609
+ * Still optional: class-B/C frameworks publish nodes without trustworthy
610
+ * coordinates, and one framework hands over a rendered string with no
611
+ * geometry anywhere. Absent bounds is a normal state, not a degraded one.
612
+ */
613
+ readonly bounds?: Rect;
614
+ /**
615
+ * Whether it is knowable that something else was painted over this node.
616
+ *
617
+ * `bounds` says where the node is; it does not say whether a pointer aimed
618
+ * there reaches it. Only some frameworks expose paint order, so this is
619
+ * `'known'` only when the probe reported it. **Absent means `'unknown'`** —
620
+ * the conservative value is the default, so a producer has to claim
621
+ * knowledge rather than have it assumed.
622
+ *
623
+ * A consumer performing pointer actions should refuse on `'unknown'` rather
624
+ * than click and hope: the input lands somewhere real, and if it lands on
625
+ * another widget the result is attributed to this one. That is a silent
626
+ * false green, which is a worse failure than a refusal.
627
+ */
628
+ readonly occlusion?: OcclusionKnowledge;
629
+ readonly state?: SemanticState;
630
+ /** Application-specific, serializable state; never promoted to portable flags. */
631
+ readonly extended?: SemanticExtendedState;
632
+ readonly actions?: readonly SemanticAction[];
633
+ readonly labelledBy?: readonly string[];
634
+ readonly describedBy?: readonly string[];
635
+ readonly textRanges?: readonly SemanticTextRange[];
636
+ /** Author-supplied test id (getByTestId). */
637
+ readonly testId?: string;
638
+ /**
639
+ * The framework's own name for this widget — a class name, a constructor
640
+ * name, a widget type.
641
+ *
642
+ * **Required when `role` is `generic`.** An unrecognised widget must survive
643
+ * as a generic node keeping its bounds, text and children, instead of being
644
+ * dropped with its children reparented. `frameworkType` is what makes such a
645
+ * node identifiable: without it a generic node says only "something was
646
+ * here", which is barely better than the drop it replaced.
647
+ */
648
+ readonly frameworkType?: string;
649
+ /**
650
+ * Provenance: where this node's facts came from.
651
+ *
652
+ * One source for the whole node, because node facts overwhelmingly share
653
+ * one. Exceptions go in {@link SemanticNode.px}, so a mixed node pays only
654
+ * for the fields that actually differ. Descriptive per-property strings were
655
+ * ruled out by arithmetic — they cost about +91 % against a budget that is
656
+ * already tight.
657
+ */
658
+ readonly p?: ProvenanceSource;
659
+ /** Per-field provenance, for fields whose source differs from `p`. */
660
+ readonly px?: Readonly<Record<string, ProvenanceSource>>;
661
+ /**
662
+ * Protocol v2 qualified layout facts. V1 snapshots MUST omit this field;
663
+ * their legacy `bounds` projection deliberately remains unchanged.
664
+ */
665
+ readonly geometry?: NodeGeometryObservations;
666
+ }
667
+ /** Layout facts reported independently so absence never masquerades as false. */
668
+ interface NodeGeometryObservations {
669
+ readonly displayed: Observation<boolean>;
670
+ readonly intendedRect: Observation<Rect>;
671
+ readonly visibleRect: Observation<Rect>;
672
+ }
673
+ /** One half-open run of cells with an exact pointer recipient. */
674
+ interface PointerHitRegion {
675
+ /** Canonical non-empty row run: `height` is always 1. */
676
+ readonly rect: Rect;
677
+ readonly recipientId: string;
678
+ }
679
+ /** A complete point-ownership map for a committed frame. */
680
+ interface PointerHitGrid {
681
+ readonly regions: readonly PointerHitRegion[];
682
+ }
683
+ interface CursorInfo {
684
+ readonly row: number;
685
+ readonly column: number;
686
+ readonly visible: boolean;
687
+ readonly shape?: 'block' | 'underline' | 'bar';
688
+ }
689
+ interface SemanticSnapshot {
690
+ readonly v: 1 | 2;
691
+ readonly sessionId: string;
692
+ /** Positive, strictly increasing within a semantic session. */
693
+ readonly revision: number;
694
+ readonly columns: number;
695
+ readonly rows: number;
696
+ readonly cursor?: CursorInfo;
697
+ readonly rootIds: readonly string[];
698
+ readonly nodes: readonly SemanticNode[];
699
+ /** Required by v2, forbidden by strict v1 validation. */
700
+ readonly coordinateSpace?: Observation<CoordinateSpace>;
701
+ /**
702
+ * Required by v2. `known` means a complete map, not a sample or paint-order
703
+ * approximation. Cells absent from a known map have no semantic recipient.
704
+ */
705
+ readonly hitGrid?: Observation<PointerHitGrid>;
706
+ }
707
+
708
+ /** Machine-readable source of truth for geometry/visibility support. */
709
+ interface FrameworkObservationCapabilities {
710
+ readonly framework: 'generic' | 'textual' | 'opentui' | 'ink' | 'tview' | 'ratatui' | 'charm';
711
+ readonly identity: 'stable' | 'frame-local' | 'none';
712
+ readonly attached: 'supported';
713
+ readonly displayed: 'supported' | 'conditional' | 'unsupported';
714
+ readonly intendedRect: 'supported' | 'conditional' | 'unsupported';
715
+ readonly visibleRect: 'supported' | 'conditional' | 'unsupported';
716
+ readonly hitTest: 'supported' | 'conditional' | 'unsupported';
717
+ readonly reason: string;
718
+ }
719
+ type CapabilityAvailability = 'supported' | 'conditional' | 'unsupported';
720
+ type GeometryOperation = 'keyboard-actions' | 'pointer-actions' | 'toBeAttached' | 'toBeDetached' | 'toBeDisplayed' | 'toBeHidden' | 'toBeVisible' | 'toBeOffscreen' | 'toBeInViewport' | 'toReceivePointerEvents' | 'toHaveBounds' | 'toHaveSpatialRelation' | 'cellSnapshot';
721
+ interface FrameworkOperationCapability {
722
+ readonly framework: FrameworkObservationCapabilities['framework'];
723
+ readonly operation: GeometryOperation;
724
+ readonly availability: CapabilityAvailability;
725
+ readonly reason: string;
726
+ }
727
+ declare const FRAMEWORK_OBSERVATION_CAPABILITIES: readonly FrameworkObservationCapabilities[];
728
+ declare function frameworkObservationCapabilities(framework: string): FrameworkObservationCapabilities | undefined;
729
+ /**
730
+ * Normative operation matrix, derived from the fact registry. Documentation
731
+ * validates against this export; adapters cannot gain an assertion merely by
732
+ * changing prose.
733
+ */
734
+ declare const FRAMEWORK_OPERATION_CAPABILITIES: readonly FrameworkOperationCapability[];
735
+
736
+ /**
737
+ * The field names of a semantic node and of its state, as data.
738
+ *
739
+ * These exist because a schema is invisible to anything that is not TypeScript.
740
+ * The cross-language vector generator and the client comparators cannot see a
741
+ * zod shape, so until now they carried hand-maintained field lists — and three
742
+ * fields (`frameworkType`, `occlusion`, `p`/`px`) reached three clients late
743
+ * precisely because nobody remembered to extend those lists. A generator that
744
+ * reads this array cannot forget a field the schema already has.
745
+ *
746
+ * Derived from the schema rather than written out, so there is one source of
747
+ * truth and not a third copy to drift. The list does not vary with limits: only
748
+ * the bounds inside the fields do.
749
+ */
750
+
751
+ /**
752
+ * Every field name on `SemanticNode`.
753
+ *
754
+ * The `keyof` annotation is the load-bearing part: a field present in the
755
+ * schema but missing from the interface fails to compile here, which is the
756
+ * half of the drift a runtime test cannot catch early.
757
+ */
758
+ declare const SEMANTIC_NODE_KEYS: readonly (Exclude<keyof SemanticNode, 'geometry'>)[];
759
+ /** Every field name on `SemanticState`. */
760
+ declare const SEMANTIC_STATE_KEYS: readonly (keyof SemanticState)[];
761
+
762
+ /** Structured result: never throws hostile data onward. */
763
+ type ValidationResult = {
764
+ readonly ok: true;
765
+ readonly snapshot: SemanticSnapshot;
766
+ } | {
767
+ readonly ok: false;
768
+ readonly code: ValidationErrorCode;
769
+ readonly detail: string;
770
+ };
771
+ type ValidationErrorCode = 'schema' | 'unknown-role' | 'duplicate-id' | 'missing-parent' | 'cycle' | 'depth' | 'count' | 'string-bytes' | 'bad-rect' | 'revision' | 'bytes';
772
+ /**
773
+ * Full snapshot validation per spec §8.2: unique ids, existing+acyclic parent
774
+ * relations, dense bounded arrays, Unicode scalar strings within byte bounds,
775
+ * safe-integer rects intersecting the viewport unless state.hidden, strictly
776
+ * increasing revisions (checked by caller against session state), deep
777
+ * immutability of the returned value.
778
+ *
779
+ * The value is first projected with {@link projectDto}, so getters on hostile
780
+ * input are rejected without being invoked and the returned snapshot is a
781
+ * deep-frozen plain copy that shares no references with the input.
782
+ *
783
+ * @param value - Untrusted candidate snapshot.
784
+ * @param limits - Active limits; callers may tighten but never widen these.
785
+ * @returns `{ ok: true, snapshot }` with a deep-frozen snapshot, or
786
+ * `{ ok: false, code, detail }`. Never throws.
787
+ */
788
+ declare function validateSnapshot(value: unknown, limits: ProtocolLimits): ValidationResult;
789
+
790
+ /**
791
+ * Application log records carried over the semantic channel.
792
+ *
793
+ * A TUI cannot print diagnostics to the screen without corrupting the render,
794
+ * so applications write them to an internal logger instead. The `logs`
795
+ * capability lets an instrumented adapter forward those records to the driver,
796
+ * where they become assertable test state rather than invisible side effects.
797
+ *
798
+ * Records are bounded exactly like snapshots: projected into frozen plain DTOs
799
+ * before retention, checked against a byte ceiling, and rejected wholesale on
800
+ * any violation. A misbehaving logger degrades into dropped records, never
801
+ * into unbounded driver memory.
802
+ */
803
+
804
+ /**
805
+ * Severity ladder, ordered from least to most severe. Deliberately the
806
+ * intersection of the ladders used by pino, winston, consola, Python
807
+ * `logging`, Go `slog` and Rust `tracing`, so every bridge maps onto it
808
+ * without inventing a level.
809
+ */
810
+ declare const LOG_LEVELS: readonly ["trace", "debug", "info", "warn", "error", "fatal"];
811
+ type LogLevel = (typeof LOG_LEVELS)[number];
812
+ /** Numeric severity, useful for threshold comparisons. Higher is more severe. */
813
+ declare const LOG_LEVEL_SEVERITY: Readonly<Record<LogLevel, number>>;
814
+ /**
815
+ * Structured attribute value. Scalars only, by design: nested objects make
816
+ * record size unbounded and depth-dependent, and every bridge already has to
817
+ * flatten for its own transport. `@termwright/logs` does the flattening.
818
+ */
819
+ type LogAttrValue = string | number | boolean | null;
820
+ /** Maximum number of attribute keys on one record. */
821
+ declare const MAX_LOG_ATTRS = 64;
822
+ /**
823
+ * One application log record.
824
+ *
825
+ * @remarks
826
+ * `ts` is **Unix epoch milliseconds**, not session-relative: the adapter has no
827
+ * reliable view of when the driver considers the session to have started, so
828
+ * the only clock both sides can agree on without negotiation is the wall
829
+ * clock. The driver rebases it onto the session/cast timeline.
830
+ */
831
+ interface LogRecord {
832
+ /** Unix epoch milliseconds when the record was produced. */
833
+ readonly ts: number;
834
+ readonly level: LogLevel;
835
+ /** Human-readable message, already formatted by the source logger. */
836
+ readonly message: string;
837
+ /** Flat structured context. Nested values are flattened by the bridge. */
838
+ readonly attrs?: Readonly<Record<string, LogAttrValue>>;
839
+ /** Logger/channel name, e.g. `http` or `db.pool`. */
840
+ readonly logger?: string;
841
+ /**
842
+ * Per-session counter assigned by the adapter, **strictly increasing**: every
843
+ * record carries a `seq` greater than the previous one on the same session.
844
+ *
845
+ * The two failure modes are distinguishable on purpose:
846
+ * - a **gap upward** means records were dropped at the source (rate limit,
847
+ * queue overflow) rather than lost in transit, and is expected under load;
848
+ * - a **duplicate or a decrease** means the sender is broken, so the receiver
849
+ * rejects that record and emits a diagnostic instead of retaining it.
850
+ *
851
+ * This is a rule *between* records, not about the shape of one, so
852
+ * {@link validateLogRecord} cannot enforce it — it only checks that `seq` is
853
+ * a non-negative safe integer. Ordering is enforced by the driver, which is
854
+ * the only party that sees the whole session.
855
+ */
856
+ readonly seq: number;
857
+ /** Semantic revision current when the record was produced, when known. */
858
+ readonly revision?: number;
859
+ }
860
+ /** Structured result: never throws hostile data onward. */
861
+ type LogValidationResult = {
862
+ readonly ok: true;
863
+ readonly record: LogRecord;
864
+ } | {
865
+ readonly ok: false;
866
+ readonly code: ValidationErrorCode;
867
+ readonly detail: string;
868
+ };
869
+ /**
870
+ * Validate an untrusted log record.
871
+ *
872
+ * Mirrors {@link import('./validate.js').validateSnapshot}: the value is
873
+ * projected into a frozen plain DTO first (so getters are rejected without
874
+ * being invoked), then measured against the byte ceiling, then checked field
875
+ * by field.
876
+ *
877
+ * @param value - Untrusted candidate record.
878
+ * @param limits - Active limits; `maxLogRecordBytes` and `maxStringBytes` apply.
879
+ * @returns `{ ok: true, record }` with a deep-frozen record, or a typed
880
+ * failure. Never throws.
881
+ */
882
+ declare function validateLogRecord(value: unknown, limits: ProtocolLimits): LogValidationResult;
883
+
884
+ /**
885
+ * Tree deltas: incremental semantic updates bound to an exact base revision.
886
+ *
887
+ * A delta is only ever applied to the revision it names. There is no
888
+ * speculative patching and no fuzzy rebasing: if the receiver does not hold
889
+ * exactly `baseRevision`, it asks for a full snapshot with `get-tree` and
890
+ * throws the delta away (origin spec §8.3). A wrong tree is far more expensive
891
+ * than a redundant snapshot, because every assertion downstream inherits the
892
+ * error silently.
893
+ *
894
+ * ## Composition semantics
895
+ *
896
+ * The semantic tree is a flat node list joined by `parentId`, so a delta is a
897
+ * set of upserts plus a set of removals:
898
+ *
899
+ * - **`changed`** upserts by id: a node absent from the base is inserted, and
900
+ * a node already present is **replaced wholesale**, never field-merged.
901
+ * Merging would need a third state meaning "unset this optional field",
902
+ * which the wire has no way to express.
903
+ * - **`removed`** removes each id **together with its whole subtree**. Cascade
904
+ * is what keeps a delta small — dropping a dialog is one id, not one id per
905
+ * descendant — and it is the only rule that cannot leave orphans behind.
906
+ * - **`rootIds`**, when present, replaces the root list outright. When absent
907
+ * the base roots carry over, minus anything the removals took.
908
+ * - **`cursor`**, when present, replaces the cursor. When absent it is
909
+ * unchanged. Everything else about the viewport — columns, rows, session id
910
+ * — is inherited and cannot be changed by a delta.
911
+ *
912
+ * Order matters: removals are applied first, then upserts. That lets one delta
913
+ * move a node out of a removed subtree by re-adding it in `changed`.
914
+ */
915
+
916
+ /**
917
+ * An incremental update to a semantic tree.
918
+ *
919
+ * Carries no viewport or session id: those belong to the snapshot the delta is
920
+ * composed onto, and a change to them requires a full snapshot. The cursor is
921
+ * the exception — it moves far too often to be worth a snapshot each time.
922
+ */
923
+ interface TreeDelta {
924
+ /** The revision this delta is composed onto. Must match exactly. */
925
+ readonly baseRevision: number;
926
+ /** The revision produced by applying it. Strictly greater than the base. */
927
+ readonly revision: number;
928
+ /** Nodes to insert or replace, keyed by `id`. */
929
+ readonly changed: readonly SemanticNode[];
930
+ /** Node ids to remove, each together with its subtree. */
931
+ readonly removed: readonly string[];
932
+ /** Replacement root list; absent means the base roots carry over. */
933
+ readonly rootIds?: readonly string[];
934
+ /**
935
+ * Replacement cursor; **absent means unchanged**.
936
+ *
937
+ * Without this a diffs-only session could never move the cursor, which in a
938
+ * TUI moves on nearly every keystroke — the mode would be useless for
939
+ * exactly the interactive applications it exists to make cheap.
940
+ *
941
+ * A delta can set the cursor but **cannot clear it**, and the two are not
942
+ * the same thing: `{ visible: false }` says there is a cursor and it is
943
+ * hidden, while an absent `SemanticSnapshot.cursor` says there is no cursor
944
+ * information at all. `cursor` is the only optional field on a snapshot, so
945
+ * it is the only one with this asymmetry.
946
+ *
947
+ * **Producer obligation:** a producer whose tree transitions from having a
948
+ * cursor to having none MUST send a full snapshot rather than a delta.
949
+ * Emitting a delta there would leave the receiver holding a cursor the
950
+ * application has stopped reporting — stale state that looks live. The same
951
+ * rule already applies to `columns`/`rows`, which a delta also cannot change.
952
+ */
953
+ readonly cursor?: CursorInfo;
954
+ }
955
+ /** Structured result: never throws hostile data onward. */
956
+ type DeltaValidationResult = {
957
+ readonly ok: true;
958
+ readonly delta: TreeDelta;
959
+ } | {
960
+ readonly ok: false;
961
+ readonly code: ValidationErrorCode;
962
+ readonly detail: string;
963
+ };
964
+ /**
965
+ * Validate the **shape** of an untrusted delta.
966
+ *
967
+ * This checks everything that can be known without the base tree: bounded
968
+ * sizes, well-formed nodes, unique ids, and a base/revision pair that moves
969
+ * forward. It deliberately cannot check parent existence, acyclicity, depth or
970
+ * whether bounds fall inside the viewport — all of those are properties of the
971
+ * *composed* tree, and {@link applyTreeDelta} checks them there.
972
+ *
973
+ * @param value - Untrusted candidate delta (without the message `type` field).
974
+ * @param limits - Active limits.
975
+ * @returns `{ ok: true, delta }` with a deep-frozen delta, or a typed failure.
976
+ * Never throws.
977
+ */
978
+ declare function validateTreeDelta(value: unknown, limits: ProtocolLimits): DeltaValidationResult;
979
+ /**
980
+ * Compose a delta onto the snapshot it names, then validate the result.
981
+ *
982
+ * The base revision must match **exactly**; a mismatch is reported rather than
983
+ * patched around, so the caller can fall back to `get-tree` per origin §8.3.
984
+ *
985
+ * All the invariants a delta cannot check on its own — parents exist, the tree
986
+ * is acyclic, depth and counts are within limits, bounds intersect the viewport
987
+ * — are checked here against the composed tree, by running the composed result
988
+ * through {@link validateSnapshot}. A delta is therefore never trusted to
989
+ * produce a valid tree; it is only trusted to describe one.
990
+ *
991
+ * @param base - The snapshot the delta is composed onto.
992
+ * @param delta - A delta that already passed {@link validateTreeDelta}.
993
+ * @param limits - Active limits.
994
+ * @returns The composed, deep-frozen snapshot, or a typed failure. Never throws.
995
+ */
996
+ declare function applyTreeDelta(base: SemanticSnapshot, delta: TreeDelta, limits: ProtocolLimits): ValidationResult;
997
+
998
+ /**
999
+ * AccessKit export: `SemanticSnapshot` → an AccessKit `TreeUpdate`.
1000
+ *
1001
+ * A pure transformation into AccessKit's serde JSON shape. This module takes
1002
+ * **no dependency** on AccessKit — the protocol package depends on `zod` only —
1003
+ * so the output is data a bridge can hand to a real adapter, not a binding.
1004
+ *
1005
+ * ## Why there is no native bridge in 1.0
1006
+ *
1007
+ * AccessKit's platform adapters attach an accessibility tree to a **native
1008
+ * window**: an `NSView` on macOS, an `HWND` on Windows, a toplevel on AT-SPI.
1009
+ * A terminal application has none of those. The terminal emulator owns the
1010
+ * window, and the application under test is a child process writing bytes to a
1011
+ * pseudo-terminal. There is nothing for an adapter to attach to, and nothing an
1012
+ * assistive technology could route back to us.
1013
+ *
1014
+ * The geometry gap is the same problem seen from the other side. Our `bounds`
1015
+ * are **terminal cells** — row 3, column 12 — while AccessKit's `Rect` is in
1016
+ * physical pixels relative to the window origin. Converting requires the cell
1017
+ * size and window position, which live in the emulator, not in the process
1018
+ * being tested. Guessing a cell size would produce coordinates that look
1019
+ * authoritative and point nowhere.
1020
+ *
1021
+ * So the export is *bridge-ready*, not a bridge: it is the half of the problem
1022
+ * that can be solved correctly without a window. An embedder that does own one
1023
+ * (a GUI terminal emulator embedding termwright) can supply {@link
1024
+ * AccessKitExportOptions.cellSize} and get real geometry.
1025
+ *
1026
+ * ## Schema provenance
1027
+ *
1028
+ * Shapes verified against `accesskit` 0.24.1 (docs.rs, August 2026):
1029
+ * `TreeUpdate { nodes, tree, tree_id, focus }`, `Tree { root, toolkit_name,
1030
+ * toolkit_version }`, `NodeId(u64)`, `Rect { x0, y0, x1, y1 }`, and
1031
+ * `#[serde(rename_all = "camelCase")]` on `Role`, `Action` and `Node`.
1032
+ * `TreeId` is a UUID, with the nil UUID reserved for the root tree.
1033
+ */
1034
+
1035
+ /** The nil UUID, which AccessKit reserves for the root tree (`TreeId::ROOT`). */
1036
+ declare const ACCESSKIT_ROOT_TREE_ID = "00000000-0000-0000-0000-000000000000";
1037
+ /** AccessKit's `Rect`: minimum and maximum coordinates, not origin plus size. */
1038
+ interface AccessKitRect {
1039
+ readonly x0: number;
1040
+ readonly y0: number;
1041
+ readonly x1: number;
1042
+ readonly y1: number;
1043
+ }
1044
+ /** AccessKit's `Toggled`, used for tri-state checkboxes. */
1045
+ type AccessKitToggled = 'false' | 'true' | 'mixed';
1046
+ /**
1047
+ * An AccessKit `Node` in its serde JSON form. Only the properties this export
1048
+ * can populate faithfully are modelled.
1049
+ */
1050
+ interface AccessKitNode {
1051
+ readonly role: string;
1052
+ readonly label?: string;
1053
+ readonly description?: string;
1054
+ readonly value?: string;
1055
+ readonly children?: readonly number[];
1056
+ readonly bounds?: AccessKitRect;
1057
+ readonly actions?: readonly string[];
1058
+ readonly labelledBy?: readonly number[];
1059
+ readonly describedBy?: readonly number[];
1060
+ readonly disabled?: boolean;
1061
+ readonly selected?: boolean;
1062
+ readonly expanded?: boolean;
1063
+ readonly busy?: boolean;
1064
+ readonly modal?: boolean;
1065
+ readonly hidden?: boolean;
1066
+ readonly readOnly?: boolean;
1067
+ readonly toggled?: AccessKitToggled;
1068
+ }
1069
+ /** AccessKit's `Tree`. */
1070
+ interface AccessKitTree {
1071
+ readonly root: number;
1072
+ readonly toolkitName?: string;
1073
+ readonly toolkitVersion?: string;
1074
+ }
1075
+ /** AccessKit's `TreeUpdate`. */
1076
+ interface AccessKitTreeUpdate {
1077
+ readonly nodes: readonly (readonly [number, AccessKitNode])[];
1078
+ readonly tree?: AccessKitTree;
1079
+ readonly treeId: string;
1080
+ readonly focus: number;
1081
+ }
1082
+ /** Settings for {@link toAccessKitTreeUpdate}. */
1083
+ interface AccessKitExportOptions {
1084
+ /** Tree identity; defaults to the nil UUID AccessKit reserves for the root. */
1085
+ readonly treeId?: string;
1086
+ readonly toolkitName?: string;
1087
+ readonly toolkitVersion?: string;
1088
+ /**
1089
+ * Pixel size of one terminal cell. Supply it only if you genuinely know it —
1090
+ * an embedder that owns the window does; a headless test run does not.
1091
+ * Without it `bounds` is omitted and cell rects are reported separately.
1092
+ */
1093
+ readonly cellSize?: {
1094
+ readonly width: number;
1095
+ readonly height: number;
1096
+ };
1097
+ }
1098
+ /** The export, plus the cell geometry AccessKit has nowhere to put. */
1099
+ interface AccessKitExport {
1100
+ readonly update: AccessKitTreeUpdate;
1101
+ /**
1102
+ * Cell-space rects keyed by AccessKit node id, for every node that had
1103
+ * `bounds`. AccessKit's `Node` has no extension point for foreign
1104
+ * coordinates, so carrying them alongside is the honest option: a consumer
1105
+ * that understands terminal cells can use them, and one that does not is
1106
+ * not misled by pixel coordinates that were never measured.
1107
+ */
1108
+ readonly cellBounds: Readonly<Record<string, Rect>>;
1109
+ }
1110
+ /**
1111
+ * ARIA-aligned protocol roles to AccessKit roles.
1112
+ *
1113
+ * Every target is a real `accesskit::Role` variant in its camelCase serde
1114
+ * spelling. `textbox` is resolved per node rather than here, because a
1115
+ * multiline textbox maps to a different AccessKit role.
1116
+ */
1117
+ declare const ACCESSKIT_ROLE_BY_SEMANTIC_ROLE: Readonly<Record<SemanticRole, string>>;
1118
+ /**
1119
+ * Map a protocol node id (a string) onto an AccessKit node id (a number).
1120
+ *
1121
+ * Stable across processes and languages: SHA-256 of the UTF-8 id, truncated to
1122
+ * 53 bits. At the protocol's 5 000-node ceiling the collision probability is
1123
+ * about 1.4e-9, and {@link toAccessKitTreeUpdate} detects a collision rather
1124
+ * than silently merging two nodes.
1125
+ *
1126
+ * @param id - Protocol node id.
1127
+ */
1128
+ declare function accessKitNodeId(id: string): number;
1129
+ /**
1130
+ * Convert a validated semantic snapshot into an AccessKit `TreeUpdate`.
1131
+ *
1132
+ * Two structural differences from our model are worth knowing:
1133
+ *
1134
+ * - **Focus is a tree-level property.** AccessKit puts `focus` on the
1135
+ * `TreeUpdate`, not on a node, so the node carrying `state.focused` becomes
1136
+ * the update's focus. If no node claims focus, the root does.
1137
+ * - **Children are explicit.** Our tree is a flat list joined by `parentId`;
1138
+ * AccessKit nodes carry a `children` array, which is derived here in the
1139
+ * snapshot's node order.
1140
+ *
1141
+ * @param snapshot - A snapshot that already passed `validateSnapshot`.
1142
+ * @param options - Tree identity, toolkit metadata and optional cell geometry.
1143
+ * @throws {ProtocolViolation} If two node ids collide in the 53-bit id space.
1144
+ */
1145
+ declare function toAccessKitTreeUpdate(snapshot: SemanticSnapshot, options?: AccessKitExportOptions): AccessKitExport;
1146
+
1147
+ /**
1148
+ * Validation for Probe IR frames.
1149
+ *
1150
+ * Same discipline as the semantic tree: project into a frozen plain DTO first
1151
+ * so a getter on hostile input is rejected without running, then measure
1152
+ * against the byte ceiling, then check the shape. A probe runs inside the
1153
+ * process under test, which may be broken or malicious, so this is a hostile
1154
+ * boundary in exactly the way the adapter channel is.
1155
+ */
1156
+
1157
+ /** Structured result: never throws hostile data onward. */
1158
+ type ProbeValidationResult = {
1159
+ readonly ok: true;
1160
+ readonly frame: ProbeFrame;
1161
+ } | {
1162
+ readonly ok: false;
1163
+ readonly code: ValidationErrorCode;
1164
+ readonly detail: string;
1165
+ };
1166
+ /** Result of validating one optional-SDK annotation at the probe boundary. */
1167
+ type ProbeAnnotationValidationResult = {
1168
+ readonly ok: true;
1169
+ readonly annotations: ProbeAnnotations;
1170
+ } | {
1171
+ readonly ok: false;
1172
+ readonly code: ValidationErrorCode;
1173
+ readonly detail: string;
1174
+ };
1175
+ /** Schema for the handshake block a probe sends about itself. */
1176
+ declare const probeInfoSchema: z.ZodObject<{
1177
+ framework: z.ZodString;
1178
+ frameworkVersion: z.ZodOptional<z.ZodString>;
1179
+ probeVersion: z.ZodString;
1180
+ identityKind: z.ZodEnum<{
1181
+ stable: "stable";
1182
+ "frame-local": "frame-local";
1183
+ }>;
1184
+ capabilities: z.ZodArray<z.ZodEnum<{
1185
+ "stable-identity": "stable-identity";
1186
+ "visible-rect": "visible-rect";
1187
+ operations: "operations";
1188
+ annotations: "annotations";
1189
+ "frame-begin": "frame-begin";
1190
+ "paint-order": "paint-order";
1191
+ }>>;
1192
+ }, z.core.$strict>;
1193
+ /**
1194
+ * Validate a probe's self-description.
1195
+ *
1196
+ * Enforces the one consistency rule the pair has: a probe may not claim the
1197
+ * `stable-identity` capability while declaring `identityKind: 'frame-local'`.
1198
+ * Those two together would tell a consumer it is safe to correlate objects
1199
+ * across frames in a framework where nothing survives the frame.
1200
+ */
1201
+ declare function validateProbeInfo(value: unknown): {
1202
+ readonly ok: true;
1203
+ readonly info: ProbeInfo;
1204
+ } | {
1205
+ readonly ok: false;
1206
+ readonly detail: string;
1207
+ };
1208
+ /**
1209
+ * Validate an untrusted probe frame.
1210
+ *
1211
+ * Beyond the shape, three cross-object rules are checked, each of them a way an
1212
+ * IR frame can be internally inconsistent rather than merely malformed:
1213
+ * identities must be unique within the frame, a declared parent must exist in
1214
+ * the same frame, and a field cannot be both reported and declared
1215
+ * unobservable.
1216
+ *
1217
+ * @param value - Untrusted candidate frame.
1218
+ * @param limits - Active limits; `maxNodes`, `maxStringBytes` and
1219
+ * `maxSnapshotBytes` apply.
1220
+ * @returns `{ ok: true, frame }` deep-frozen, or a typed failure. Never throws.
1221
+ */
1222
+ declare function validateProbeFrame(value: unknown, limits: ProtocolLimits): ProbeValidationResult;
1223
+ /**
1224
+ * Validate one developer annotation before adding it to an otherwise trusted
1225
+ * framework observation.
1226
+ *
1227
+ * Annotation registries intentionally use `Symbol.for` so an optional SDK and
1228
+ * an injected probe can meet without importing one another. That also makes
1229
+ * the registry a hostile boundary: application code can forge an entry with a
1230
+ * getter, cycle, oversized value or unknown action. Reusing the complete frame
1231
+ * validator here keeps both boundaries byte-for-byte consistent. Callers can
1232
+ * then omit only the bad annotation instead of losing the framework frame or
1233
+ * closing the probe channel.
1234
+ */
1235
+ declare function validateProbeAnnotations(value: unknown, limits: ProtocolLimits): ProbeAnnotationValidationResult;
1236
+
1237
+ /**
1238
+ * Wire messages. Transport: length-prefixed JSON frames (see framing.ts).
1239
+ * CDP-like: adapter pushes commits; driver issues requests; either side may
1240
+ * send errors. All messages are validated against limits BEFORE retention.
1241
+ */
1242
+ declare const ADAPTER_CAPABILITIES: readonly ["tree", "bounds", "absolute-bounds", "states", "actions", "text-ranges", "render-revisions", "tree-diffs", "logs", "qualified-observations", "pointer-hit-grid"];
1243
+ type AdapterCapability = (typeof ADAPTER_CAPABILITIES)[number];
1244
+ /** adapter → driver, exactly once, before any other message. */
1245
+ interface HelloMessage {
1246
+ readonly type: 'hello';
1247
+ readonly protocol: ProtocolId;
1248
+ readonly token: string;
1249
+ readonly adapter: {
1250
+ readonly name: string;
1251
+ readonly version: string;
1252
+ };
1253
+ readonly capabilities: readonly AdapterCapability[];
1254
+ /**
1255
+ * Present when the sender is a probe rather than a hand-written adapter.
1256
+ *
1257
+ * Carries what the probe can actually offer — framework and versions, the
1258
+ * best identity it can produce, and its optional abilities — so the driver
1259
+ * negotiates against measured capability rather than assuming a floor.
1260
+ */
1261
+ readonly probe?: ProbeInfo;
1262
+ }
1263
+ /** driver → adapter, reply to hello. */
1264
+ interface HelloAckMessage {
1265
+ readonly type: 'hello-ack';
1266
+ readonly protocol: ProtocolId;
1267
+ readonly sessionId: string;
1268
+ readonly limits: ProtocolLimits;
1269
+ /**
1270
+ * Which traffic the driver wants pushed.
1271
+ *
1272
+ * `diffs` is only ever selected for an adapter that announced the
1273
+ * `tree-diffs` capability, so an adapter that does not know the value never
1274
+ * receives it — the closed set grew without breaking anyone, because the
1275
+ * adapter opts in first.
1276
+ */
1277
+ readonly subscribe: 'snapshots' | 'revisions' | 'diffs';
1278
+ /** Marker configuration: producer must emit the signed OSC 8487 commit marker. */
1279
+ readonly marker: {
1280
+ readonly enabled: boolean;
1281
+ };
1282
+ /**
1283
+ * Log-channel budget, sent only when the adapter announced the `logs`
1284
+ * capability. **Absent means logs are disabled** — an adapter that receives
1285
+ * no `logs` field must not emit `log` messages at all.
1286
+ *
1287
+ * The adapter enforces the rate itself and drops locally when over budget,
1288
+ * leaving a gap in `LogRecord.seq` so the driver can report how many records
1289
+ * were lost. Enforcing it at the source is what keeps a log storm from
1290
+ * consuming the frame budget the semantic tree needs.
1291
+ */
1292
+ readonly logs?: {
1293
+ readonly enabled: boolean;
1294
+ /** Sustained ceiling on records per second. */
1295
+ readonly maxRecordsPerSecond: number;
1296
+ /** Records allowed in a burst on top of the sustained rate. */
1297
+ readonly burst: number;
1298
+ };
1299
+ }
1300
+ /** adapter → driver after each committed render (always, regardless of mode). */
1301
+ interface RevisionCommitMessage {
1302
+ readonly type: 'revision-commit';
1303
+ readonly revision: number;
1304
+ }
1305
+ /** adapter → driver, full snapshot for a revision (subscribe: 'snapshots'). */
1306
+ interface SnapshotMessage {
1307
+ readonly type: 'snapshot';
1308
+ readonly snapshot: SemanticSnapshot;
1309
+ }
1310
+ /** driver → adapter, request full snapshot (latest, or a held revision). */
1311
+ interface GetTreeRequest {
1312
+ readonly type: 'get-tree';
1313
+ readonly requestId: number;
1314
+ readonly revision?: number;
1315
+ }
1316
+ /** adapter → driver, response to get-tree. */
1317
+ interface GetTreeResponse {
1318
+ readonly type: 'get-tree-result';
1319
+ readonly requestId: number;
1320
+ readonly snapshot?: SemanticSnapshot;
1321
+ readonly error?: string;
1322
+ }
1323
+ /**
1324
+ * adapter → driver, a frame has started (capability `frame-begin`).
1325
+ *
1326
+ * **Optional, and its absence means nothing.** No audited framework offers a
1327
+ * hook guaranteed to fire before every frame: one lets a pre-draw hook veto the
1328
+ * frame entirely, so the post-draw hook never runs; one exposes only a
1329
+ * post-frame hook; one decouples submission from the flush with a ticker. A
1330
+ * receiver that reads "no frame-begin" as "no frame in progress" turns four of
1331
+ * the six frameworks into a hang rather than an error.
1332
+ *
1333
+ * `FRAME_END` is the existing `revision-commit`, which stays advisory.
1334
+ *
1335
+ * **Abandoned frames**: a probe may begin a frame and never finish it — a
1336
+ * crash, an interrupted render. A `frame-begin` for revision N implicitly
1337
+ * closes every frame below N. Without that rule an open frame waits forever,
1338
+ * which is the timeout it replaced, only now wearing a false air of precision.
1339
+ */
1340
+ interface FrameBeginMessage {
1341
+ readonly type: 'frame-begin';
1342
+ readonly revision: number;
1343
+ }
1344
+ /**
1345
+ * adapter → driver, an incremental tree update (capability `tree-diffs`,
1346
+ * `subscribe: 'diffs'`).
1347
+ *
1348
+ * Bound to an exact base revision: see `delta.ts` for composition semantics.
1349
+ * A receiver that does not hold `baseRevision` must request a full snapshot
1350
+ * with `get-tree` rather than patch speculatively.
1351
+ */
1352
+ interface TreeDeltaMessage extends TreeDelta {
1353
+ readonly type: 'tree-delta';
1354
+ }
1355
+ /**
1356
+ * adapter → driver, one application log record (capability `logs`).
1357
+ *
1358
+ * Sent only after the driver enabled logs in `hello-ack`. Records are
1359
+ * independent of renders: they are not paired with a revision and never gate
1360
+ * snapshot publication.
1361
+ */
1362
+ interface LogMessage {
1363
+ readonly type: 'log';
1364
+ readonly record: LogRecord;
1365
+ }
1366
+ /** either direction: terminal protocol error; sender closes after emitting. */
1367
+ interface ProtocolErrorMessage {
1368
+ readonly type: 'error';
1369
+ readonly code: 'bad-token' | 'bad-version' | 'malformed' | 'limit-exceeded' | 'internal';
1370
+ readonly message: string;
1371
+ }
1372
+ type AdapterToDriverMessage = HelloMessage | RevisionCommitMessage | SnapshotMessage | GetTreeResponse | TreeDeltaMessage | FrameBeginMessage | LogMessage | ProtocolErrorMessage;
1373
+ type DriverToAdapterMessage = HelloAckMessage | GetTreeRequest | ProtocolErrorMessage;
1374
+ /** Outcome of parsing one wire message. Mirrors `ProtocolErrorMessage['code']`. */
1375
+ type MessageParseResult<T> = {
1376
+ readonly ok: true;
1377
+ readonly message: T;
1378
+ } | {
1379
+ readonly ok: false;
1380
+ readonly code: 'bad-version' | 'malformed' | 'limit-exceeded';
1381
+ readonly detail: string;
1382
+ };
1383
+ /**
1384
+ * Parse and validate one adapter → driver message.
1385
+ *
1386
+ * **Strict reader**: this is the hostile-input boundary, so unknown fields are
1387
+ * rejected rather than ignored. See {@link parseDriverMessage} for why the
1388
+ * other direction is tolerant.
1389
+ *
1390
+ * @param value - Untrusted decoded frame body.
1391
+ * @param limits - Active session limits, applied to any embedded snapshot.
1392
+ * @returns A frozen message on success, or a typed failure. Never throws.
1393
+ */
1394
+ declare function parseAdapterMessage(value: unknown, limits: ProtocolLimits): MessageParseResult<AdapterToDriverMessage>;
1395
+ /**
1396
+ * Parse and validate one driver → adapter message.
1397
+ *
1398
+ * **Tolerant reader.** Unlike {@link parseAdapterMessage}, unknown envelope
1399
+ * fields are ignored rather than rejected, and are carried through to the
1400
+ * caller so a reader that does understand them still can. Known fields stay
1401
+ * strictly type-checked, and closed sets (`type`, `code`, `subscribe`) stay
1402
+ * closed — an unknown message type is still `malformed`.
1403
+ *
1404
+ * The asymmetry is about who is speaking, not about the message. The driver is
1405
+ * the trusted party and behaviour is governed by negotiated capabilities, so a
1406
+ * newer driver may add an optional field without invalidating every adapter
1407
+ * already published. Traffic in the other direction crosses the hostile-input
1408
+ * boundary and stays strict.
1409
+ *
1410
+ * @param value - Decoded frame body from the driver.
1411
+ * @param limits - Active session limits used for the projection depth bound.
1412
+ * @returns A frozen message on success, or a typed failure. Never throws.
1413
+ */
1414
+ declare function parseDriverMessage(value: unknown, limits: ProtocolLimits): MessageParseResult<DriverToAdapterMessage>;
1415
+
1416
+ /**
1417
+ * Render-commit marker: emitted by the adapter into the PTY stdout AFTER the
1418
+ * last byte of the render belonging to revision N. It is a frame COMMIT
1419
+ * signal (Neovim `flush` semantics), never a data carrier.
1420
+ *
1421
+ * Encoding: a private OSC sequence terminated by BEL:
1422
+ *
1423
+ * OSC 8487 ; 'twm;' <revision> ';' <mac> BEL
1424
+ * i.e. `\x1b]8487;twm;{rev};{mac}\x07`
1425
+ *
1426
+ * where mac = base64url(HMAC-SHA256(token, `${sessionId}:${revision}`))
1427
+ * truncated to 16 bytes. The driver's VT layer registers an OSC handler,
1428
+ * verifies the MAC, and removes the sequence from the visible grid. Ordinary
1429
+ * application output cannot forge it. Emitted only after a successful
1430
+ * handshake; never during a normal (non-instrumented) run.
1431
+ *
1432
+ * ## Why OSC and not DCS
1433
+ *
1434
+ * ConPTY rewrites the stream it forwards. A passthrough probe run in CI across
1435
+ * the three platforms showed it dropping DCS, APC and OSC 8, while passing
1436
+ * private OSC with either terminator, and OSC 133. DCS therefore could not
1437
+ * carry a marker on Windows at all.
1438
+ *
1439
+ * One encoding is used everywhere rather than negotiating per platform: two
1440
+ * paths double the surface that has to stay correct, and the path used least
1441
+ * is the one that rots unnoticed. BEL is emitted rather than ST because it is
1442
+ * the terminator ConPTY was observed to forward most reliably; receivers
1443
+ * accept both, since a VT parser consumes the terminator before dispatching
1444
+ * anyway.
1445
+ *
1446
+ * ## Why 8487
1447
+ *
1448
+ * OSC numbers have no registry, only convention, so the number is chosen to
1449
+ * sit clear of everything in use: xterm's allocations (0–14, 46, 50, 52, 104,
1450
+ * 110–119), OSC 8 hyperlinks, 9 and 1337 (iTerm2), 99 and 30001 (kitty), 133
1451
+ * (FinalTerm shell integration — also the sequence ConPTY is known to
1452
+ * forward), 633 (VS Code), 697 (ConEmu) and 777–779 (urxvt/VTE). 8487 is the
1453
+ * ASCII codes of `T` and `W` — termwright — and appears in none of them.
1454
+ *
1455
+ * The `twm;` tag after the number is kept as a self-identifying guard: if
1456
+ * anything ever does claim 8487, a marker still says what it is instead of
1457
+ * being mistaken for that other feature's payload.
1458
+ */
1459
+ /** The private OSC number carrying render-commit markers. */
1460
+ declare const MARKER_OSC_CODE = 8487;
1461
+ /**
1462
+ * The tag opening a marker payload, immediately after `OSC 8487;`.
1463
+ *
1464
+ * A VT parser hands an OSC handler everything after the number and its
1465
+ * separator, which is exactly what {@link verifyMarkerPayload} expects:
1466
+ *
1467
+ * ```ts
1468
+ * term.parser.registerOscHandler(MARKER_OSC_CODE, (data) => {
1469
+ * const marker = verifyMarkerPayload(data, token, sessionId);
1470
+ * if (marker !== null) commit(marker.revision);
1471
+ * return true; // consumed: keeps the sequence out of the visible grid
1472
+ * });
1473
+ * ```
1474
+ */
1475
+ declare const MARKER_OSC_PREFIX = "twm;";
1476
+ /** Bytes of HMAC-SHA256 output retained in the marker MAC. */
1477
+ declare const MARKER_MAC_BYTES = 16;
1478
+ interface RenderMarker {
1479
+ readonly revision: number;
1480
+ readonly mac: string;
1481
+ }
1482
+ /**
1483
+ * Build the full escape sequence for a marker.
1484
+ *
1485
+ * @param token - Per-launch session token (`TERMWRIGHT_TOKEN`); used as the
1486
+ * HMAC key and never appears in the emitted bytes.
1487
+ * @param sessionId - Session id from the handshake, bound into the MAC so a
1488
+ * marker from one session cannot be replayed into another.
1489
+ * @param revision - Positive safe integer identifying the committed render.
1490
+ * @returns The complete `OSC … BEL` sequence to write to stdout.
1491
+ * @throws {ProtocolViolation} If the revision is not a positive safe integer,
1492
+ * or the token/sessionId are empty.
1493
+ */
1494
+ declare function encodeMarker(token: string, sessionId: string, revision: number): string;
1495
+ /**
1496
+ * Parse+verify an OSC payload (the part after `OSC 8487;`). Returns null on any mismatch.
1497
+ *
1498
+ * Total function: hostile payloads yield `null`, never an exception. The MAC
1499
+ * comparison is constant-time, and only canonically-formatted revisions are
1500
+ * accepted so `1` and `01` cannot both authenticate the same commit.
1501
+ *
1502
+ * A trailing BEL or ST is tolerated. A VT parser consumes the terminator
1503
+ * before dispatching, so a handler normally passes a payload without one,
1504
+ * while a caller scanning raw output with a regex may keep it — both must work.
1505
+ *
1506
+ * @param payload - Everything after `OSC 8487;`, i.e. `twm;{rev};{mac}`.
1507
+ * @param token - Per-launch session token used as the HMAC key.
1508
+ * @param sessionId - Session id the marker must be bound to.
1509
+ */
1510
+ declare function verifyMarkerPayload(payload: string, token: string, sessionId: string): RenderMarker | null;
1511
+
1512
+ /**
1513
+ * Wire framing: 4-byte big-endian unsigned length prefix + UTF-8 JSON body.
1514
+ * The length is checked against limits.maxFrameBytes BEFORE any decoding;
1515
+ * oversized, partial or duplicated frames fail closed with a typed error.
1516
+ * Decoded values MUST be projected into immutable plain DTOs (no accessors,
1517
+ * proxies, symbols, functions, non-plain prototypes) before retention.
1518
+ */
1519
+ interface FrameDecoder {
1520
+ /** Feed raw bytes; returns fully decoded, validated, frozen messages. */
1521
+ push(chunk: Uint8Array): readonly unknown[];
1522
+ /** Bytes currently buffered (bounded by maxFrameBytes + 4). */
1523
+ readonly buffered: number;
1524
+ }
1525
+ /** Size of the big-endian length prefix that precedes every frame body. */
1526
+ declare const FRAME_HEADER_BYTES = 4;
1527
+ /**
1528
+ * Create a streaming decoder for length-prefixed JSON frames.
1529
+ *
1530
+ * A frame whose declared length exceeds `maxFrameBytes` is rejected before its
1531
+ * body is read. Any violation poisons the decoder permanently: subsequent
1532
+ * `push` calls throw rather than resynchronising on attacker-chosen offsets.
1533
+ *
1534
+ * @param maxFrameBytes - Per-frame byte ceiling; must be a positive safe integer.
1535
+ * @throws {ProtocolViolation} On an invalid ceiling, or (from `push`) on any
1536
+ * oversized, malformed, non-UTF-8 or non-projectable frame.
1537
+ */
1538
+ declare function createFrameDecoder(maxFrameBytes: number): FrameDecoder;
1539
+ /**
1540
+ * Serialise a message into a single length-prefixed frame.
1541
+ *
1542
+ * @param message - A JSON-representable value.
1543
+ * @param maxFrameBytes - Per-frame byte ceiling applied to the encoded body.
1544
+ * @returns Header + UTF-8 JSON body, ready to write to the transport.
1545
+ * @throws {ProtocolViolation} If the value is not JSON-representable or the
1546
+ * encoded body exceeds `maxFrameBytes`.
1547
+ */
1548
+ declare function encodeFrame(message: unknown, maxFrameBytes: number): Uint8Array;
1549
+ /**
1550
+ * Deep-project an untrusted parsed value into a frozen, plain, JSON-safe DTO.
1551
+ * Throws ProtocolViolation on aliases, cycles, sparse arrays, accessors,
1552
+ * non-JSON scalars, or depth/size beyond limits.
1553
+ *
1554
+ * Properties are inspected with `Object.getOwnPropertyDescriptor`, so a getter
1555
+ * on hostile input is detected and rejected without ever being invoked.
1556
+ *
1557
+ * @param value - Untrusted input, typically the result of `JSON.parse`.
1558
+ * @param maxDepth - Maximum nesting depth; the root sits at depth 0.
1559
+ * @returns A structurally identical, deep-frozen copy. The `T` type parameter
1560
+ * is an unchecked assertion — validate the shape separately.
1561
+ * @throws {ProtocolViolation}
1562
+ */
1563
+ declare function projectDto<T>(value: unknown, maxDepth: number): T;
1564
+
1565
+ export { ABSOLUTE_LIMITS, ACCESSKIT_ROLE_BY_SEMANTIC_ROLE, ACCESSKIT_ROOT_TREE_ID, ADAPTER_CAPABILITIES, type AccessKitExport, type AccessKitExportOptions, type AccessKitNode, type AccessKitRect, type AccessKitToggled, type AccessKitTree, type AccessKitTreeUpdate, type AdapterCapability, type AdapterToDriverMessage, type BoundsSource, type CapabilityAvailability, type CellPoint, type CoordinateSpace, type CursorInfo, DEFAULT_LIMITS, DEFAULT_NEGOTIATION_MS, type DeltaValidationResult, type DriverToAdapterMessage, ENV_ENDPOINT, ENV_PROTOCOL, ENV_TOKEN, FRAMEWORK_OBSERVATION_CAPABILITIES, FRAMEWORK_OPERATION_CAPABILITIES, FRAME_HEADER_BYTES, type FrameBeginMessage, type FrameDecoder, type FrameworkObservationCapabilities, type FrameworkOperationCapability, type GeometryOperation, type GetTreeRequest, type GetTreeResponse, type HelloAckMessage, type HelloMessage, LOG_LEVELS, LOG_LEVEL_SEVERITY, type LocatorGeometry, type LocatorVisibility, type LogAttrValue, type LogLevel, type LogMessage, type LogRecord, type LogValidationResult, MARKER_MAC_BYTES, MARKER_OSC_CODE, MARKER_OSC_PREFIX, MAX_LOG_ATTRS, type MessageParseResult, type NodeGeometryObservations, type Observation, type ObservationAbsentReason, type ObservationEvidence, type ObservationStamp, type ObservationUnknownReason, type ObservationUnsupportedReason, type OcclusionKnowledge, PROBE_CAPABILITIES, PROBE_UNOBSERVABLE_FIELDS, PROTOCOL_ID, PROTOCOL_V2_ID, PROTOCOL_VERSION, PROVENANCE_SOURCES, type PointerHitGrid, type PointerHitRegion, type PointerHitTest, type ProbeAccessibilityHints, type ProbeAnnotationValidationResult, type ProbeAnnotations, type ProbeCapability, type ProbeExtent, type ProbeFrame, type ProbeGeometry, type ProbeIdentity, type ProbeIdentityKind, type ProbeInfo, type ProbeObject, type ProbeObservedState, type ProbeOperation, type ProbeRect, type ProbeScroll, type ProbeUnobservableField, type ProbeValidationResult, type ProtocolErrorMessage, type ProtocolId, type ProtocolLimits, ProtocolViolation, type ProtocolViolationCode, type ProvenanceSource, type Rect, type RenderMarker, type ResolveBoundsOptions, type ResolvedBounds, type RevisionCommitMessage, SEMANTIC_ACTIONS, SEMANTIC_NODE_KEYS, SEMANTIC_ROLES, SEMANTIC_STATE_KEYS, SUPPORTED_PROTOCOL_IDS, type SemanticAction, type SemanticExtendedArray, type SemanticExtendedObject, type SemanticExtendedState, type SemanticExtendedValue, type SemanticNode, type SemanticRole, type SemanticSnapshot, type SemanticState, type SemanticTextRange, type SnapshotMessage, type SpatialRelation, TOKEN_BYTES, type TreeDelta, type TreeDeltaMessage, type ValidationErrorCode, type ValidationResult, type ViewportIntersection, accessKitNodeId, applyTreeDelta, createFrameDecoder, encodeFrame, encodeMarker, frameworkObservationCapabilities, generateToken, intersectRects, parseAdapterMessage, parseDriverMessage, probeInfoSchema, projectDto, rectArea, resolveNodeBounds, spatialRelation, toAccessKitTreeUpdate, validateLogRecord, validateProbeAnnotations, validateProbeFrame, validateProbeInfo, validateSnapshot, validateTreeDelta, verifyMarkerPayload, viewportIntersection };