@termwright/protocol 0.2.0 → 0.3.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.
Files changed (40) hide show
  1. package/README.md +213 -605
  2. package/dist/action-model-BP9Znu6L.d.ts +219 -0
  3. package/dist/action-model.d.ts +3 -0
  4. package/dist/action-model.js +15 -0
  5. package/dist/action-model.js.map +1 -0
  6. package/dist/capability-graph.d.ts +90 -0
  7. package/dist/capability-graph.js +43 -0
  8. package/dist/capability-graph.js.map +1 -0
  9. package/dist/chunk-B4VUTTUE.js +59 -0
  10. package/dist/chunk-B4VUTTUE.js.map +1 -0
  11. package/dist/chunk-CZK6NNP3.js +389 -0
  12. package/dist/chunk-CZK6NNP3.js.map +1 -0
  13. package/dist/chunk-ODOJRXL6.js +84 -0
  14. package/dist/chunk-ODOJRXL6.js.map +1 -0
  15. package/dist/chunk-PUXRCPGY.js +112 -0
  16. package/dist/chunk-PUXRCPGY.js.map +1 -0
  17. package/dist/chunk-VBLS6E6U.js +1109 -0
  18. package/dist/chunk-VBLS6E6U.js.map +1 -0
  19. package/dist/chunk-ZZIYHDJ4.js +202 -0
  20. package/dist/chunk-ZZIYHDJ4.js.map +1 -0
  21. package/dist/contract-CH9gmj2Y.d.ts +746 -0
  22. package/dist/contract.d.ts +2 -0
  23. package/dist/contract.js +21 -0
  24. package/dist/contract.js.map +1 -0
  25. package/dist/index.d.ts +82 -798
  26. package/dist/index.js +1087 -766
  27. package/dist/index.js.map +1 -1
  28. package/dist/run-events.d.ts +158 -0
  29. package/dist/run-events.js +27 -0
  30. package/dist/run-events.js.map +1 -0
  31. package/dist/run-journal.d.ts +55 -0
  32. package/dist/run-journal.js +10 -0
  33. package/dist/run-journal.js.map +1 -0
  34. package/dist/run-state.d.ts +38 -0
  35. package/dist/run-state.js +19 -0
  36. package/dist/run-state.js.map +1 -0
  37. package/dist/test-provider.d.ts +22 -0
  38. package/dist/test-provider.js +32 -0
  39. package/dist/test-provider.js.map +1 -0
  40. package/package.json +33 -5
package/dist/index.js CHANGED
@@ -1,12 +1,78 @@
1
+ import {
2
+ ARTIFACT_VALUE_POLICIES,
3
+ DEFAULT_ARTIFACT_VALUE_POLICY,
4
+ executableText,
5
+ projectActionReceiptForArtifact,
6
+ projectSemanticSnapshotForArtifact,
7
+ publicValue,
8
+ recordActionPlan,
9
+ recordDeviceOperation,
10
+ recordValue,
11
+ sensitive,
12
+ valueSensitivity
13
+ } from "./chunk-PUXRCPGY.js";
14
+ import {
15
+ PROBE_DEGRADED_CAPABILITIES,
16
+ PROBE_INJECTION_TIERS,
17
+ PROBE_SEMANTIC_CLASSES,
18
+ PROBE_UNOBSERVABLE_FIELDS,
19
+ PROVENANCE_SOURCES,
20
+ evidence
21
+ } from "./chunk-B4VUTTUE.js";
22
+ import {
23
+ ADAPTER_CAPABILITIES,
24
+ CAPABILITY_CONFORMANCE_CLAIMS,
25
+ CAPABILITY_EDGE_KINDS,
26
+ CAPABILITY_GRAPH,
27
+ CAPABILITY_GRAPH_VERSION,
28
+ CAPABILITY_NODE_CATEGORIES,
29
+ CAPABILITY_NODE_LAYERS,
30
+ CONDITION_KINDS,
31
+ EVIDENCE_PROVIDER_CAPABILITIES,
32
+ EVIDENCE_PROVIDER_TYPES,
33
+ PROBE_CAPABILITIES,
34
+ PUBLIC_CAPABILITY_CONSUMERS,
35
+ RUNTIME_PREREQUISITES,
36
+ SESSION_CAPABILITIES,
37
+ capabilityNode,
38
+ capabilityRemediation,
39
+ resolveCapability,
40
+ sessionCapabilitiesFromProducers,
41
+ validateCapabilityGraph
42
+ } from "./chunk-VBLS6E6U.js";
43
+ import {
44
+ DEFAULT_RUN_EVENT_JOURNAL_LIMITS,
45
+ RunEventJournal
46
+ } from "./chunk-ZZIYHDJ4.js";
47
+ import {
48
+ DEFAULT_RUN_EVENT_LIMITS,
49
+ RUN_EVENT_CLASSES,
50
+ RUN_EVENT_VERSION,
51
+ RUN_ID_KINDS,
52
+ RunEventProducer,
53
+ RunEventStreamValidator,
54
+ RunIdFactory,
55
+ createRunEvent,
56
+ createRunId,
57
+ parseRunId,
58
+ validateRunEvent
59
+ } from "./chunk-CZK6NNP3.js";
60
+ import {
61
+ RUN_STATES,
62
+ RUN_STATE_TRANSITIONS,
63
+ TERMINAL_RUN_STATES,
64
+ canTransitionRunState,
65
+ isRunState,
66
+ isTerminalRunState,
67
+ validateRunStateTransition
68
+ } from "./chunk-ODOJRXL6.js";
69
+
1
70
  // src/env.ts
2
71
  import { randomBytes } from "crypto";
3
72
  var ENV_ENDPOINT = "TERMWRIGHT_ENDPOINT";
4
73
  var ENV_TOKEN = "TERMWRIGHT_TOKEN";
5
- var ENV_PROTOCOL = "TERMWRIGHT_PROTOCOL";
6
- var PROTOCOL_VERSION = 1;
7
- var PROTOCOL_ID = "termwright/1";
8
- var PROTOCOL_V2_ID = "termwright/2";
9
- var SUPPORTED_PROTOCOL_IDS = [PROTOCOL_V2_ID, PROTOCOL_ID];
74
+ var PROTOCOL_VERSION = 2;
75
+ var PROTOCOL_ID = "termwright/2";
10
76
  var TOKEN_BYTES = 32;
11
77
  function generateToken() {
12
78
  return randomBytes(TOKEN_BYTES).toString("base64url");
@@ -58,6 +124,7 @@ var SEMANTIC_ACTIONS = [
58
124
  "select",
59
125
  "expand"
60
126
  ];
127
+ var PHYSICAL_INPUT_RECIPE_ACTIONS = ["focus", "activate", "toggle", "setValue"];
61
128
 
62
129
  // src/limits.ts
63
130
  var DEFAULT_LIMITS = Object.freeze({
@@ -86,7 +153,7 @@ var ABSOLUTE_LIMITS = Object.freeze({
86
153
  maxLogRecordBytes: 256 * 1024,
87
154
  maxLogQueue: 1e4
88
155
  });
89
- var DEFAULT_NEGOTIATION_MS = 250;
156
+ var DEFAULT_NEGOTIATION_MS = 2e3;
90
157
 
91
158
  // src/observation.ts
92
159
  function intersectRects(a, b) {
@@ -147,97 +214,9 @@ function spatialRelation(a, relation, b) {
147
214
  }
148
215
  }
149
216
 
150
- // src/geometry-capabilities.ts
151
- var FRAMEWORK_OBSERVATION_CAPABILITIES = Object.freeze([
152
- { framework: "generic", identity: "none", attached: "supported", displayed: "supported", intendedRect: "supported", visibleRect: "supported", hitTest: "conditional", reason: "Grid matches are physical cells; pointer delivery still requires terminal mouse mode." },
153
- { framework: "textual", identity: "stable", attached: "supported", displayed: "supported", intendedRect: "supported", visibleRect: "supported", hitTest: "supported", reason: "The compositor exposes intended/clipped regions and Screen.get_widget_at(), the same fresh-pointer routing lookup." },
154
- { framework: "opentui", identity: "stable", attached: "supported", displayed: "supported", intendedRect: "supported", visibleRect: "unsupported", hitTest: "supported", reason: "The committed native hit grid proves fresh-pointer ownership; the renderer exposes no per-node visual clip rectangle." },
155
- { framework: "ink", identity: "stable", attached: "supported", displayed: "supported", intendedRect: "conditional", visibleRect: "unsupported", hitTest: "unsupported", reason: "Intended bounds are conditional on a viewport-stable live region; Ink exposes neither clipping nor pointer ownership." },
156
- { framework: "tview", identity: "stable", attached: "supported", displayed: "supported", intendedRect: "supported", visibleRect: "conditional", hitTest: "unsupported", reason: "Primitive rectangles do not identify the recipient after overlap." },
157
- { framework: "ratatui", identity: "frame-local", attached: "supported", displayed: "conditional", intendedRect: "supported", visibleRect: "conditional", hitTest: "unsupported", reason: "Render areas are frame-local and buffer writes do not preserve widget ownership." },
158
- { framework: "charm", identity: "frame-local", attached: "supported", displayed: "conditional", intendedRect: "unsupported", visibleRect: "unsupported", hitTest: "unsupported", reason: "Bubble Tea hands over a rendered string without attributable widget geometry." }
159
- ]);
160
- function frameworkObservationCapabilities(framework) {
161
- return FRAMEWORK_OBSERVATION_CAPABILITIES.find((entry) => entry.framework === framework);
162
- }
163
- var weakest = (...values) => values.includes("unsupported") ? "unsupported" : values.includes("conditional") ? "conditional" : "supported";
164
- var FRAMEWORK_OPERATION_CAPABILITIES = Object.freeze(
165
- FRAMEWORK_OBSERVATION_CAPABILITIES.flatMap((row) => {
166
- const visibility = weakest(row.displayed, row.visibleRect);
167
- const viewport = weakest(row.intendedRect, row.visibleRect);
168
- const reason = row.reason;
169
- return [
170
- { framework: row.framework, operation: "keyboard-actions", availability: "supported", reason: "Keyboard input is sent through the PTY and does not require geometry." },
171
- {
172
- framework: row.framework,
173
- operation: "pointer-actions",
174
- availability: row.hitTest === "unsupported" ? "unsupported" : "conditional",
175
- reason: row.hitTest === "unsupported" ? reason : "Requires an exact hit recipient and terminal mouse reporting enabled by the application."
176
- },
177
- { framework: row.framework, operation: "toBeAttached", availability: "supported", reason: "Tree membership is observed directly." },
178
- { framework: row.framework, operation: "toBeDetached", availability: "supported", reason: "Tree absence is observed directly without coercing missing layout facts." },
179
- { framework: row.framework, operation: "toBeDisplayed", availability: row.displayed, reason },
180
- { framework: row.framework, operation: "toBeHidden", availability: row.displayed, reason },
181
- { framework: row.framework, operation: "toBeVisible", availability: visibility, reason },
182
- { framework: row.framework, operation: "toBeOffscreen", availability: viewport, reason },
183
- { framework: row.framework, operation: "toBeInViewport", availability: viewport, reason },
184
- { framework: row.framework, operation: "toReceivePointerEvents", availability: row.hitTest, reason },
185
- { framework: row.framework, operation: "toHaveBounds", availability: row.intendedRect, reason },
186
- { framework: row.framework, operation: "toHaveSpatialRelation", availability: row.intendedRect, reason },
187
- { framework: row.framework, operation: "cellSnapshot", availability: row.visibleRect, reason }
188
- ];
189
- })
190
- );
191
-
192
217
  // src/node-schema.ts
193
218
  import { Buffer } from "buffer";
194
219
  import { z } from "zod";
195
-
196
- // src/probe/ir.ts
197
- var PROBE_UNOBSERVABLE_FIELDS = [
198
- "focused",
199
- "disabled",
200
- "checked",
201
- "expanded",
202
- "readonly",
203
- "selected",
204
- "busy",
205
- "multiline",
206
- "displayed",
207
- "value",
208
- "selectedIndex",
209
- "textSelection",
210
- "scroll",
211
- "scrollExtent",
212
- "intendedRect",
213
- "visibleRect",
214
- "paintOrder",
215
- "text",
216
- "parent"
217
- ];
218
- var PROBE_CAPABILITIES = [
219
- /** Identities survive across frames and may be correlated. */
220
- "stable-identity",
221
- /** `visibleRect` is computed, not guessed. */
222
- "visible-rect",
223
- /** A render/layout call stream is reported. */
224
- "operations",
225
- /** Author annotations are readable. */
226
- "annotations",
227
- /** A frame-start signal is emitted. Absent for most frameworks — see below. */
228
- "frame-begin",
229
- /** `paintOrder` is reported, so occlusion can be reasoned about. */
230
- "paint-order"
231
- ];
232
- var PROVENANCE_SOURCES = [
233
- "annotation",
234
- "recognizer",
235
- "framework",
236
- "correlation",
237
- "heuristic"
238
- ];
239
-
240
- // src/node-schema.ts
241
220
  function safeInt() {
242
221
  return z.number().refine(Number.isSafeInteger, "expected a safe integer");
243
222
  }
@@ -263,11 +242,70 @@ function build(limits) {
263
242
  width: nonNegativeInt(),
264
243
  height: nonNegativeInt()
265
244
  });
245
+ const evidence2 = z.strictObject({
246
+ source: z.enum(["framework", "application", "terminal", "recognizer", "driver"]),
247
+ method: z.enum([
248
+ "native",
249
+ "instrumented",
250
+ "declared",
251
+ "correlated",
252
+ "measured",
253
+ "derived",
254
+ "heuristic"
255
+ ]),
256
+ strength: z.enum(["authoritative", "diagnostic"]),
257
+ providerId: z.string().min(1).max(256)
258
+ });
259
+ const authoritativeEvidence = evidence2.extend({
260
+ strength: z.literal("authoritative")
261
+ });
266
262
  const observation = (value) => z.discriminatedUnion("status", [
267
- z.strictObject({ status: z.literal("known"), value, evidence: z.enum(["adapter", "probe", "terminal-grid", "viewport-clip", "paint-order", "hit-grid", "legacy-v1"]) }),
268
- z.strictObject({ status: z.literal("absent"), reason: z.enum(["detached", "not-displayed", "not-laid-out"]) }),
269
- z.strictObject({ status: z.literal("unknown"), reason: z.enum(["not-reported", "temporary", "clip-unobservable", "legacy-unqualified"]) }),
270
- z.strictObject({ status: z.literal("unsupported"), capability: text, reason: z.enum(["capability", "framework-unobservable", "not-negotiated"]) })
263
+ z.strictObject({
264
+ status: z.literal("known"),
265
+ value,
266
+ evidence: evidence2
267
+ }),
268
+ z.strictObject({
269
+ status: z.literal("absent"),
270
+ reason: z.enum(["detached", "not-displayed", "not-laid-out"]),
271
+ evidence: authoritativeEvidence
272
+ }),
273
+ z.strictObject({
274
+ status: z.literal("unknown"),
275
+ reason: z.enum(["awaiting-revision-pair", "provider-refresh", "stale-revision"])
276
+ }),
277
+ z.strictObject({
278
+ status: z.literal("unsupported"),
279
+ capability: text,
280
+ reason: z.enum(["capability", "framework-unobservable", "not-negotiated"])
281
+ })
282
+ ]);
283
+ const semanticValue = z.discriminatedUnion("status", [
284
+ z.strictObject({
285
+ status: z.literal("known"),
286
+ value: text,
287
+ sensitivity: z.enum(["public", "sensitive"]),
288
+ evidence: evidence2
289
+ }),
290
+ z.strictObject({
291
+ status: z.literal("absent"),
292
+ reason: z.enum(["detached", "not-displayed", "not-laid-out", "no-value"]),
293
+ evidence: authoritativeEvidence
294
+ }),
295
+ z.strictObject({
296
+ status: z.literal("unknown"),
297
+ reason: z.enum(["awaiting-revision-pair", "provider-refresh", "stale-revision"])
298
+ }),
299
+ z.strictObject({
300
+ status: z.literal("unsupported"),
301
+ capability: z.literal("semantic-value"),
302
+ reason: z.enum(["capability", "framework-unobservable", "not-negotiated"])
303
+ }),
304
+ z.strictObject({
305
+ status: z.literal("withheld"),
306
+ reason: z.enum(["sensitive", "artifact-policy", "provider-policy"]),
307
+ sensitivity: z.enum(["public", "sensitive"])
308
+ })
271
309
  ]);
272
310
  const state = z.strictObject({
273
311
  disabled: z.boolean().optional(),
@@ -281,12 +319,12 @@ function build(limits) {
281
319
  offscreen: z.boolean().optional(),
282
320
  readonly: z.boolean().optional(),
283
321
  multiline: z.boolean().optional(),
322
+ required: z.boolean().optional(),
323
+ multiselectable: z.boolean().optional(),
284
324
  orientation: z.union([z.literal("horizontal"), z.literal("vertical")]).optional(),
285
325
  level: positiveInt().optional(),
286
326
  positionInSet: positiveInt().optional(),
287
- setSize: nonNegativeInt().optional(),
288
- scrollOffset: nonNegativeInt().optional(),
289
- scrollExtent: nonNegativeInt().optional()
327
+ setSize: nonNegativeInt().optional()
290
328
  });
291
329
  const textRange = z.strictObject({
292
330
  startOffset: nonNegativeInt(),
@@ -313,27 +351,81 @@ function build(limits) {
313
351
  (value) => Object.keys(value).length <= limits.maxRelationTargets,
314
352
  `expected at most ${limits.maxRelationTargets} properties`
315
353
  );
354
+ const inputRecipe = z.strictObject({
355
+ action: z.enum(PHYSICAL_INPUT_RECIPE_ACTIONS),
356
+ requiresFocus: z.boolean(),
357
+ steps: z.array(
358
+ z.union([
359
+ z.strictObject({
360
+ kind: z.literal("press"),
361
+ key: text.refine((s) => s.length > 0, "key must not be empty")
362
+ }),
363
+ z.strictObject({ kind: z.literal("insert-action-value") })
364
+ ])
365
+ ).min(1).max(limits.maxRelationTargets)
366
+ }).superRefine((recipe, context) => {
367
+ const inserts = recipe.steps.filter(({ kind }) => kind === "insert-action-value").length;
368
+ if (recipe.action === "setValue" && inserts !== 1 || recipe.action !== "setValue" && inserts !== 0) {
369
+ context.addIssue({
370
+ code: "custom",
371
+ message: "setValue requires exactly one insert-action-value step"
372
+ });
373
+ }
374
+ if (recipe.action === "focus" && recipe.requiresFocus) {
375
+ context.addIssue({
376
+ code: "custom",
377
+ message: "focus recipe cannot require focus"
378
+ });
379
+ }
380
+ });
381
+ const inputRecipes = z.array(inputRecipe).max(PHYSICAL_INPUT_RECIPE_ACTIONS.length).superRefine((recipes, context) => {
382
+ if (new Set(recipes.map(({ action }) => action)).size !== recipes.length) {
383
+ context.addIssue({
384
+ code: "custom",
385
+ message: "input recipe actions must be unique"
386
+ });
387
+ }
388
+ });
389
+ const regionSpan = z.strictObject({
390
+ row: nonNegativeInt(),
391
+ from: nonNegativeInt(),
392
+ to: positiveInt()
393
+ }).refine((span) => span.to > span.from, "region span must be non-empty");
394
+ const regionSpans = z.array(regionSpan).max(limits.maxNodes).superRefine((spans, ctx) => {
395
+ let previous;
396
+ for (let index = 0; index < spans.length; index += 1) {
397
+ const current = spans[index];
398
+ if (previous !== void 0 && (current.row < previous.row || current.row === previous.row && current.from < previous.to)) {
399
+ ctx.addIssue({
400
+ code: "custom",
401
+ path: [index],
402
+ message: "region spans must be non-overlapping row-major runs"
403
+ });
404
+ return;
405
+ }
406
+ previous = current;
407
+ }
408
+ });
316
409
  const nodeFields = {
317
410
  id: text.refine((s) => s.length > 0, "node id must not be empty"),
318
411
  parentId: text.optional(),
319
412
  role: z.enum(SEMANTIC_ROLES),
320
413
  name: text,
321
414
  description: text.optional(),
322
- value: text.optional(),
323
- bounds: rect.optional(),
415
+ value: semanticValue.optional(),
324
416
  state: state.optional(),
325
417
  extended: extended.optional(),
326
418
  actions: z.array(z.enum(SEMANTIC_ACTIONS)).max(SEMANTIC_ACTIONS.length).optional(),
419
+ inputRecipes: inputRecipes.optional(),
327
420
  labelledBy: relations.optional(),
328
421
  describedBy: relations.optional(),
329
422
  textRanges: z.array(textRange).max(limits.maxRelationTargets).optional(),
330
423
  testId: text.optional(),
331
424
  frameworkType: text.optional(),
332
- occlusion: z.enum(["known", "unknown"]).optional(),
425
+ opaqueChildren: z.boolean().optional(),
333
426
  p: z.enum(PROVENANCE_SOURCES).optional(),
334
427
  px: z.record(text, z.enum(PROVENANCE_SOURCES)).optional()
335
428
  };
336
- const node = z.strictObject(nodeFields);
337
429
  const geometry = z.strictObject({
338
430
  displayed: observation(z.boolean()),
339
431
  intendedRect: observation(rect),
@@ -341,9 +433,32 @@ function build(limits) {
341
433
  });
342
434
  const nodeV2 = z.strictObject({
343
435
  ...nodeFields,
344
- bounds: z.never().optional(),
345
- occlusion: z.never().optional(),
346
- geometry
436
+ geometry,
437
+ scroll: observation(
438
+ z.strictObject({
439
+ axis: z.enum(["vertical", "horizontal"]),
440
+ offset: nonNegativeInt(),
441
+ viewport: nonNegativeInt(),
442
+ extent: nonNegativeInt()
443
+ })
444
+ ).optional(),
445
+ paintedRegion: observation(
446
+ z.strictObject({
447
+ regionBounds: rect,
448
+ spans: regionSpans
449
+ })
450
+ ).optional()
451
+ }).superRefine((node, context) => {
452
+ const intents = new Set(node.actions ?? []);
453
+ for (const [index, recipe] of (node.inputRecipes ?? []).entries()) {
454
+ if (!intents.has(recipe.action)) {
455
+ context.addIssue({
456
+ code: "custom",
457
+ path: ["inputRecipes", index, "action"],
458
+ message: `input recipe '${recipe.action}' requires the matching semantic action intent`
459
+ });
460
+ }
461
+ }
347
462
  });
348
463
  const cursor = z.strictObject({
349
464
  row: nonNegativeInt(),
@@ -351,16 +466,6 @@ function build(limits) {
351
466
  visible: z.boolean(),
352
467
  shape: z.union([z.literal("block"), z.literal("underline"), z.literal("bar")]).optional()
353
468
  });
354
- const snapshotV1 = z.strictObject({
355
- v: z.literal(1),
356
- sessionId: text.refine((s) => s.length > 0, "sessionId must not be empty"),
357
- revision: positiveInt(),
358
- columns: positiveInt(),
359
- rows: positiveInt(),
360
- cursor: cursor.optional(),
361
- rootIds: z.array(text).max(limits.maxNodes),
362
- nodes: z.array(node).max(limits.maxNodes)
363
- });
364
469
  const hitRun = z.strictObject({
365
470
  rect: z.strictObject({
366
471
  row: nonNegativeInt(),
@@ -389,6 +494,107 @@ function build(limits) {
389
494
  }
390
495
  })
391
496
  });
497
+ const providerPointerRegion = z.strictObject({
498
+ recipientId: text.refine((value) => value.length > 0, "recipient id must not be empty"),
499
+ regionBounds: rect,
500
+ spans: regionSpans
501
+ });
502
+ const providerActionRecipes = z.strictObject({
503
+ recipientId: text.refine((value) => value.length > 0, "recipient id must not be empty"),
504
+ recipes: inputRecipes
505
+ });
506
+ const providerScrollState = z.strictObject({
507
+ recipientId: text.refine((value) => value.length > 0, "recipient id must not be empty"),
508
+ axis: z.enum(["vertical", "horizontal"]),
509
+ offset: nonNegativeInt(),
510
+ viewport: nonNegativeInt(),
511
+ extent: nonNegativeInt()
512
+ }).superRefine((state2, context) => {
513
+ if (state2.offset > state2.extent || state2.viewport > state2.extent || state2.offset + state2.viewport > state2.extent) {
514
+ context.addIssue({
515
+ code: "custom",
516
+ message: "scroll state must fit inside its extent"
517
+ });
518
+ }
519
+ });
520
+ const providerEvidence = z.discriminatedUnion("status", [
521
+ z.strictObject({
522
+ providerId: text.refine((value) => value.length > 0, "provider id must not be empty"),
523
+ sessionId: text.refine((value) => value.length > 0, "provider session id must not be empty"),
524
+ revision: positiveInt(),
525
+ status: z.literal("available"),
526
+ evidence: z.strictObject({
527
+ source: z.literal("application"),
528
+ method: z.enum(["native", "instrumented", "declared"]),
529
+ strength: z.literal("authoritative"),
530
+ providerId: text.refine(
531
+ (value) => value.length > 0,
532
+ "evidence provider id must not be empty"
533
+ )
534
+ }),
535
+ pointerRegions: z.array(providerPointerRegion).max(limits.maxNodes),
536
+ paintedRegions: z.array(providerPointerRegion).max(limits.maxNodes).optional(),
537
+ inputModes: z.strictObject({
538
+ mouseTracking: z.enum(["none", "x10", "vt200", "drag", "any"]),
539
+ mouseEncoding: z.enum(["default", "sgr", "urxvt", "utf8"]),
540
+ focusReporting: z.enum(["on", "off"])
541
+ }).optional(),
542
+ focusState: z.discriminatedUnion("status", [
543
+ z.strictObject({
544
+ status: z.literal("focused"),
545
+ recipientId: text.refine(
546
+ (value) => value.length > 0,
547
+ "focused recipient id must not be empty"
548
+ )
549
+ }),
550
+ z.strictObject({ status: z.literal("none") })
551
+ ]).optional(),
552
+ actionRecipes: z.array(providerActionRecipes).max(limits.maxNodes).superRefine((entries, context) => {
553
+ const seen = /* @__PURE__ */ new Set();
554
+ for (const [index, entry] of entries.entries()) {
555
+ if (seen.has(entry.recipientId)) {
556
+ context.addIssue({
557
+ code: "custom",
558
+ path: [index, "recipientId"],
559
+ message: "provider action recipe recipients must be unique"
560
+ });
561
+ }
562
+ seen.add(entry.recipientId);
563
+ }
564
+ }).optional(),
565
+ scrollStates: z.array(providerScrollState).max(limits.maxNodes).superRefine((entries, context) => {
566
+ const seen = /* @__PURE__ */ new Set();
567
+ for (const [index, entry] of entries.entries()) {
568
+ if (seen.has(entry.recipientId)) {
569
+ context.addIssue({
570
+ code: "custom",
571
+ path: [index, "recipientId"],
572
+ message: "provider scroll recipients must be unique"
573
+ });
574
+ }
575
+ seen.add(entry.recipientId);
576
+ }
577
+ }).optional(),
578
+ hitGrid: hitGrid.optional()
579
+ }),
580
+ z.strictObject({
581
+ providerId: text.refine((value) => value.length > 0, "provider id must not be empty"),
582
+ sessionId: text.refine((value) => value.length > 0, "provider session id must not be empty"),
583
+ revision: positiveInt(),
584
+ status: z.literal("lost"),
585
+ reason: text.refine((value) => value.length > 0, "provider loss reason must not be empty")
586
+ }),
587
+ z.strictObject({
588
+ providerId: text.refine((value) => value.length > 0, "provider id must not be empty"),
589
+ sessionId: text.refine((value) => value.length > 0, "provider session id must not be empty"),
590
+ revision: positiveInt(),
591
+ status: z.literal("violation"),
592
+ reason: text.refine(
593
+ (value) => value.length > 0,
594
+ "provider violation reason must not be empty"
595
+ )
596
+ })
597
+ ]);
392
598
  const snapshotV2 = z.strictObject({
393
599
  v: z.literal(2),
394
600
  sessionId: text.refine((s) => s.length > 0, "sessionId must not be empty"),
@@ -399,17 +605,16 @@ function build(limits) {
399
605
  rootIds: z.array(text).max(limits.maxNodes),
400
606
  nodes: z.array(nodeV2).max(limits.maxNodes),
401
607
  coordinateSpace: observation(z.enum(["viewport-cells", "framework-local-cells"])),
402
- hitGrid: observation(hitGrid)
608
+ hitGrid: observation(hitGrid),
609
+ providerEvidence: z.array(providerEvidence).max(64).optional()
403
610
  });
404
- const snapshot = z.discriminatedUnion("v", [snapshotV1, snapshotV2]);
611
+ const snapshot = snapshotV2;
405
612
  return {
406
613
  text,
407
- node,
614
+ node: nodeV2,
408
615
  cursor,
409
616
  snapshot,
410
- snapshotV1,
411
- snapshotV2,
412
- nodeKeys: Object.freeze(Object.keys(node.shape)),
617
+ nodeKeys: Object.freeze(Object.keys(nodeV2.shape)),
413
618
  stateKeys: Object.freeze(Object.keys(state.shape))
414
619
  };
415
620
  }
@@ -443,10 +648,7 @@ var encoder = new TextEncoder();
443
648
  var decoder = new TextDecoder("utf-8", { fatal: true });
444
649
  function assertPositiveByteCeiling(maxFrameBytes) {
445
650
  if (!Number.isSafeInteger(maxFrameBytes) || maxFrameBytes <= 0) {
446
- throw new ProtocolViolation(
447
- "frame-malformed",
448
- "maxFrameBytes must be a positive safe integer"
449
- );
651
+ throw new ProtocolViolation("frame-malformed", "maxFrameBytes must be a positive safe integer");
450
652
  }
451
653
  }
452
654
  var BufferedFrameDecoder = class {
@@ -785,433 +987,96 @@ function validateLogRecord(value, limits) {
785
987
  return { ok: true, record: projected };
786
988
  }
787
989
 
788
- // src/delta.ts
789
- import { Buffer as Buffer4 } from "buffer";
790
- import { z as z3 } from "zod";
791
-
792
- // src/validate.ts
793
- import { Buffer as Buffer3 } from "buffer";
794
- import "zod";
795
- function fail2(code, detail) {
796
- return { ok: false, code, detail };
797
- }
798
- function codeForIssue(issue) {
799
- const path = issue.path.map(String);
800
- if (path.includes("role")) return "unknown-role";
801
- if (path.includes("revision")) return "revision";
802
- if (path.includes("bounds") || path.includes("rect")) return "bad-rect";
803
- if (issue.code === "custom" && issue.message?.includes("hit regions")) return "bad-rect";
804
- if (issue.code === "too_big" && (path.includes("nodes") || path.includes("rootIds"))) {
805
- return "count";
806
- }
807
- if (issue.code === "custom" && typeof issue.message === "string" && issue.message.includes("UTF-8 bytes")) {
808
- return "string-bytes";
809
- }
810
- return "schema";
990
+ // src/accesskit.ts
991
+ import { createHash } from "crypto";
992
+ var ACCESSKIT_ROOT_TREE_ID = "00000000-0000-0000-0000-000000000000";
993
+ var ACCESSKIT_ROLE_BY_SEMANTIC_ROLE = Object.freeze({
994
+ application: "application",
995
+ region: "region",
996
+ dialog: "dialog",
997
+ alert: "alert",
998
+ status: "status",
999
+ list: "list",
1000
+ listitem: "listItem",
1001
+ menu: "menu",
1002
+ menuitem: "menuItem",
1003
+ button: "button",
1004
+ checkbox: "checkBox",
1005
+ radio: "radioButton",
1006
+ tab: "tab",
1007
+ textbox: "textInput",
1008
+ heading: "heading",
1009
+ text: "label",
1010
+ progressbar: "progressIndicator",
1011
+ separator: "splitter",
1012
+ scrollbar: "scrollBar",
1013
+ table: "table",
1014
+ row: "row",
1015
+ cell: "cell",
1016
+ generic: "genericContainer"
1017
+ });
1018
+ var ACCESSKIT_ACTION_BY_SEMANTIC_ACTION = Object.freeze({
1019
+ focus: "focus",
1020
+ activate: "click",
1021
+ toggle: "click",
1022
+ setValue: "setValue",
1023
+ expand: "expand",
1024
+ scroll: "scrollIntoView"
1025
+ });
1026
+ var NODE_ID_BITS = 53n;
1027
+ var NODE_ID_MASK = (1n << NODE_ID_BITS) - 1n;
1028
+ function accessKitNodeId(id) {
1029
+ const digest = createHash("sha256").update(id, "utf8").digest();
1030
+ const value = digest.readBigUInt64BE(0) & NODE_ID_MASK;
1031
+ return value === 0n ? 1 : Number(value);
811
1032
  }
812
- function describeIssue(issue) {
813
- const where = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
814
- return `${where}: ${issue.message}`;
1033
+ function toggledFor(checked) {
1034
+ if (checked === void 0) return void 0;
1035
+ if (checked === "mixed") return "mixed";
1036
+ return checked ? "true" : "false";
815
1037
  }
816
- function rectIntersectsViewport(rect, columns, rows) {
817
- if (rect.width === 0 || rect.height === 0) return false;
818
- return rect.column < columns && rect.row < rows && rect.column + rect.width > 0 && rect.row + rect.height > 0;
1038
+ function accessKitRoleFor(node) {
1039
+ if (node.role === "textbox" && node.state?.multiline === true) return "multilineTextInput";
1040
+ return ACCESSKIT_ROLE_BY_SEMANTIC_ROLE[node.role];
819
1041
  }
820
- function checkNodeShape(node, snapshot, ids, limits) {
821
- if (node.role === "generic" && (node.frameworkType === void 0 || node.frameworkType === "")) {
822
- return fail2(
823
- "schema",
824
- `node ${node.id} has role 'generic' without a frameworkType; an unrecognised widget must name what the framework called it`
825
- );
826
- }
827
- if (node.state?.offscreen === true && node.state.hidden !== true) {
828
- return fail2(
829
- "schema",
830
- `node ${node.id}: state.offscreen implies state.hidden \u2014 every cell is outside the visible area, so the node cannot also be visible`
831
- );
832
- }
833
- if (node.bounds !== void 0) {
834
- const { width, height, row, column } = node.bounds;
835
- if (!Number.isSafeInteger(row + height) || !Number.isSafeInteger(column + width)) {
836
- return fail2("bad-rect", `node ${node.id}: bounds overflow the safe-integer range`);
837
- }
838
- if (node.state?.hidden !== true && !rectIntersectsViewport(node.bounds, snapshot.columns, snapshot.rows)) {
839
- return fail2(
840
- "bad-rect",
841
- `node ${node.id}: bounds do not intersect the ${snapshot.columns}x${snapshot.rows} viewport and the node is not hidden`
842
- );
843
- }
844
- }
845
- for (const range of node.textRanges ?? []) {
846
- if (range.endOffset < range.startOffset) {
847
- return fail2("bad-rect", `node ${node.id}: text range ends before it starts`);
848
- }
849
- if (!Number.isSafeInteger(range.rect.row + range.rect.height)) {
850
- return fail2("bad-rect", `node ${node.id}: text range rect overflows the safe-integer range`);
851
- }
852
- }
853
- for (const [field, targets] of [
854
- ["labelledBy", node.labelledBy],
855
- ["describedBy", node.describedBy]
856
- ]) {
857
- if (targets === void 0) continue;
858
- if (targets.length > limits.maxRelationTargets) {
859
- return fail2("count", `node ${node.id}: ${field} exceeds ${limits.maxRelationTargets} targets`);
860
- }
861
- for (const target of targets) {
862
- if (!ids.has(target)) {
863
- return fail2("missing-parent", `node ${node.id}: ${field} references unknown node ${target}`);
864
- }
865
- }
1042
+ function actionsFor(actions) {
1043
+ if (actions === void 0 || actions.length === 0) return void 0;
1044
+ const mapped = /* @__PURE__ */ new Set();
1045
+ for (const action of actions) {
1046
+ const target = ACCESSKIT_ACTION_BY_SEMANTIC_ACTION[action];
1047
+ if (target !== void 0) mapped.add(target);
866
1048
  }
867
- return null;
1049
+ return mapped.size === 0 ? void 0 : [...mapped];
868
1050
  }
869
- function computeDepths(nodes, byId) {
870
- const depths = /* @__PURE__ */ new Map();
871
- for (const start of nodes) {
872
- if (depths.has(start.id)) continue;
873
- const chain = [];
874
- const onChain = /* @__PURE__ */ new Set();
875
- let current = start;
876
- while (current !== void 0 && !depths.has(current.id)) {
877
- if (onChain.has(current.id)) return { cycleAt: current.id };
878
- onChain.add(current.id);
879
- chain.push(current.id);
880
- current = current.parentId === void 0 ? void 0 : byId.get(current.parentId);
881
- }
882
- let depth = current === void 0 ? 0 : depths.get(current.id);
883
- for (let i = chain.length - 1; i >= 0; i -= 1) {
884
- depth += 1;
885
- depths.set(chain[i], depth);
886
- }
887
- }
888
- return { depths };
1051
+ function boundsFor(rect, cellSize) {
1052
+ return {
1053
+ x0: rect.column * cellSize.width,
1054
+ y0: rect.row * cellSize.height,
1055
+ x1: (rect.column + rect.width) * cellSize.width,
1056
+ y1: (rect.row + rect.height) * cellSize.height
1057
+ };
889
1058
  }
890
- function validateSnapshot(value, limits) {
891
- let projected;
892
- try {
893
- projected = projectDto(value, limits.maxDepth);
894
- } catch (error) {
895
- if (error instanceof ProtocolViolation) {
896
- return fail2(error.code === "dto-depth" ? "depth" : "schema", error.message);
1059
+ function toAccessKitTreeUpdate(snapshot, options = {}) {
1060
+ const idOf = /* @__PURE__ */ new Map();
1061
+ const seen = /* @__PURE__ */ new Map();
1062
+ for (const node of snapshot.nodes) {
1063
+ const mapped = accessKitNodeId(node.id);
1064
+ const previous = seen.get(mapped);
1065
+ if (previous !== void 0) {
1066
+ throw new ProtocolViolation(
1067
+ "dto-key",
1068
+ `node ids "${previous}" and "${node.id}" collide in the AccessKit id space`
1069
+ );
897
1070
  }
898
- return fail2("schema", "value could not be projected into a plain DTO");
899
- }
900
- const serialised = JSON.stringify(projected);
901
- if (serialised === void 0) {
902
- return fail2("schema", "snapshot is not a JSON object");
903
- }
904
- const bytes = Buffer3.byteLength(serialised, "utf8");
905
- if (bytes > limits.maxSnapshotBytes) {
906
- return fail2("bytes", `snapshot is ${bytes} bytes, ceiling is ${limits.maxSnapshotBytes}`);
907
- }
908
- const parsed = treeSchemas(limits).snapshot.safeParse(projected);
909
- if (!parsed.success) {
910
- const issue = parsed.error.issues[0];
911
- return fail2(codeForIssue(issue), describeIssue(issue));
912
- }
913
- const snapshot = projected;
914
- if (snapshot.nodes.length > limits.maxNodes) {
915
- return fail2("count", `snapshot carries ${snapshot.nodes.length} nodes, ceiling is ${limits.maxNodes}`);
1071
+ seen.set(mapped, node.id);
1072
+ idOf.set(node.id, mapped);
916
1073
  }
917
- const byId = /* @__PURE__ */ new Map();
1074
+ const childrenOf = /* @__PURE__ */ new Map();
918
1075
  for (const node of snapshot.nodes) {
919
- if (byId.has(node.id)) {
920
- return fail2("duplicate-id", `node id ${node.id} appears more than once`);
921
- }
922
- byId.set(node.id, node);
923
- }
924
- const rootIds = /* @__PURE__ */ new Set();
925
- for (const id of snapshot.rootIds) {
926
- if (rootIds.has(id)) {
927
- return fail2("duplicate-id", `root id ${id} appears more than once`);
928
- }
929
- rootIds.add(id);
930
- const node = byId.get(id);
931
- if (node === void 0) {
932
- return fail2("missing-parent", `rootIds references unknown node ${id}`);
933
- }
934
- if (node.parentId !== void 0) {
935
- return fail2("schema", `root node ${id} declares a parent`);
936
- }
937
- }
938
- const ids = new Set(byId.keys());
939
- if (snapshot.v === 2) {
940
- if (snapshot.coordinateSpace?.status === "known" && snapshot.coordinateSpace.value !== "viewport-cells") {
941
- }
942
- if (snapshot.hitGrid?.status === "known") {
943
- for (const region of snapshot.hitGrid.value.regions) {
944
- if (!ids.has(region.recipientId)) {
945
- return fail2("missing-parent", `hitGrid references unknown recipient ${region.recipientId}`);
946
- }
947
- if (!rectIntersectsViewport(region.rect, snapshot.columns, snapshot.rows)) {
948
- return fail2("bad-rect", `hitGrid region for ${region.recipientId} does not intersect the viewport`);
949
- }
950
- }
951
- }
952
- }
953
- for (const node of snapshot.nodes) {
954
- if (node.parentId === void 0) {
955
- if (!rootIds.has(node.id)) {
956
- return fail2("schema", `parentless node ${node.id} is missing from rootIds`);
957
- }
958
- } else if (!byId.has(node.parentId)) {
959
- return fail2("missing-parent", `node ${node.id} references unknown parent ${node.parentId}`);
960
- } else if (node.parentId === node.id) {
961
- return fail2("cycle", `node ${node.id} is its own parent`);
962
- }
963
- const problem = checkNodeShape(node, snapshot, ids, limits);
964
- if (problem !== null) return problem;
965
- }
966
- const depthResult = computeDepths(snapshot.nodes, byId);
967
- if ("cycleAt" in depthResult) {
968
- return fail2("cycle", `parent chain through node ${depthResult.cycleAt} is cyclic`);
969
- }
970
- for (const [id, depth] of depthResult.depths) {
971
- if (depth > limits.maxDepth) {
972
- return fail2("depth", `node ${id} sits at depth ${depth}, ceiling is ${limits.maxDepth}`);
973
- }
974
- }
975
- if (snapshot.cursor !== void 0) {
976
- const { row, column } = snapshot.cursor;
977
- if (row >= snapshot.rows || column >= snapshot.columns) {
978
- return fail2("bad-rect", `cursor (${row}, ${column}) lies outside the viewport`);
979
- }
980
- }
981
- return { ok: true, snapshot };
982
- }
983
-
984
- // src/delta.ts
985
- function fail3(code, detail) {
986
- return { ok: false, code, detail };
987
- }
988
- var DELTA_KEYS = ["baseRevision", "revision", "changed", "removed", "rootIds", "cursor"];
989
- function validateTreeDelta(value, limits) {
990
- let projected;
991
- try {
992
- projected = projectDto(value, limits.maxDepth);
993
- } catch (error) {
994
- if (error instanceof ProtocolViolation) {
995
- return fail3(error.code === "dto-depth" ? "depth" : "schema", error.message);
996
- }
997
- return fail3("schema", "value could not be projected into a plain DTO");
998
- }
999
- const serialised = JSON.stringify(projected);
1000
- if (serialised === void 0) {
1001
- return fail3("schema", "delta is not a JSON object");
1002
- }
1003
- const bytes = Buffer4.byteLength(serialised, "utf8");
1004
- if (bytes > limits.maxSnapshotBytes) {
1005
- return fail3("bytes", `delta is ${bytes} bytes, ceiling is ${limits.maxSnapshotBytes}`);
1006
- }
1007
- if (typeof projected !== "object" || projected === null || Array.isArray(projected)) {
1008
- return fail3("schema", "delta must be an object");
1009
- }
1010
- const delta = projected;
1011
- for (const key of Object.keys(delta)) {
1012
- if (!DELTA_KEYS.includes(key)) return fail3("schema", `unknown delta property "${key}"`);
1013
- }
1014
- const { text, node, cursor } = treeSchemas(limits);
1015
- const parsed = deltaSchema(text, node, cursor, limits).safeParse(delta);
1016
- if (!parsed.success) {
1017
- const issue = parsed.error.issues[0];
1018
- const path = issue.path.map(String);
1019
- const where = path.length > 0 ? path.join(".") : "<root>";
1020
- const code = path.includes("role") ? "unknown-role" : path.includes("revision") || path.includes("baseRevision") ? "revision" : path.includes("bounds") || path.includes("rect") ? "bad-rect" : issue.code === "too_big" ? "count" : "schema";
1021
- return fail3(code, `${where}: ${issue.message}`);
1022
- }
1023
- const typed = delta;
1024
- if (typed.revision <= typed.baseRevision) {
1025
- return fail3(
1026
- "revision",
1027
- `revision ${typed.revision} must be greater than baseRevision ${typed.baseRevision}`
1028
- );
1029
- }
1030
- const total = typed.changed.length + typed.removed.length;
1031
- if (total > limits.maxNodes) {
1032
- return fail3("count", `delta touches ${total} nodes, ceiling is ${limits.maxNodes}`);
1033
- }
1034
- const changedIds = /* @__PURE__ */ new Set();
1035
- for (const entry of typed.changed) {
1036
- if (changedIds.has(entry.id)) {
1037
- return fail3("duplicate-id", `node id ${entry.id} appears twice in changed`);
1038
- }
1039
- changedIds.add(entry.id);
1040
- if (entry.parentId === entry.id) {
1041
- return fail3("cycle", `node ${entry.id} is its own parent`);
1042
- }
1043
- }
1044
- const removedIds = /* @__PURE__ */ new Set();
1045
- for (const id of typed.removed) {
1046
- if (removedIds.has(id)) return fail3("duplicate-id", `node id ${id} appears twice in removed`);
1047
- removedIds.add(id);
1048
- if (changedIds.has(id)) {
1049
- return fail3("schema", `node id ${id} is both changed and removed`);
1050
- }
1051
- }
1052
- if (typed.rootIds !== void 0) {
1053
- const seen = /* @__PURE__ */ new Set();
1054
- for (const id of typed.rootIds) {
1055
- if (seen.has(id)) return fail3("duplicate-id", `root id ${id} appears twice`);
1056
- seen.add(id);
1057
- }
1058
- }
1059
- return { ok: true, delta: typed };
1060
- }
1061
- var deltaCache = /* @__PURE__ */ new WeakMap();
1062
- function deltaSchema(text, node, cursor, limits) {
1063
- const cached = deltaCache.get(limits);
1064
- if (cached !== void 0) return cached;
1065
- const built = z3.strictObject({
1066
- baseRevision: z3.number().refine((n) => Number.isSafeInteger(n) && n > 0, "expected a positive safe integer"),
1067
- revision: z3.number().refine((n) => Number.isSafeInteger(n) && n > 0, "expected a positive safe integer"),
1068
- changed: z3.array(node).max(limits.maxNodes),
1069
- removed: z3.array(text).max(limits.maxNodes),
1070
- rootIds: z3.array(text).max(limits.maxNodes).optional(),
1071
- cursor: cursor.optional()
1072
- });
1073
- deltaCache.set(limits, built);
1074
- return built;
1075
- }
1076
- function applyTreeDelta(base, delta, limits) {
1077
- if (delta.baseRevision !== base.revision) {
1078
- return {
1079
- ok: false,
1080
- code: "revision",
1081
- detail: `delta is based on revision ${delta.baseRevision} but the held snapshot is revision ${base.revision}; request a full snapshot instead of patching`
1082
- };
1083
- }
1084
- const byId = /* @__PURE__ */ new Map();
1085
- for (const node of base.nodes) byId.set(node.id, node);
1086
- const childrenOf = /* @__PURE__ */ new Map();
1087
- for (const node of base.nodes) {
1088
- if (node.parentId === void 0) continue;
1089
- const siblings = childrenOf.get(node.parentId);
1090
- if (siblings === void 0) childrenOf.set(node.parentId, [node.id]);
1091
- else siblings.push(node.id);
1092
- }
1093
- for (const id of delta.removed) {
1094
- if (!byId.has(id)) {
1095
- return {
1096
- ok: false,
1097
- code: "missing-parent",
1098
- detail: `delta removes unknown node ${id}; the producer's base disagrees with ours, so the tree must be resynchronised rather than patched`
1099
- };
1100
- }
1101
- const pending = [id];
1102
- while (pending.length > 0) {
1103
- const current = pending.pop();
1104
- if (!byId.delete(current)) continue;
1105
- const children = childrenOf.get(current);
1106
- if (children !== void 0) pending.push(...children);
1107
- }
1108
- }
1109
- for (const node of delta.changed) byId.set(node.id, node);
1110
- const rootIds = delta.rootIds ?? base.rootIds.filter((id) => byId.has(id));
1111
- const composed = {
1112
- v: 1,
1113
- sessionId: base.sessionId,
1114
- revision: delta.revision,
1115
- columns: base.columns,
1116
- rows: base.rows,
1117
- // Absent cursor means unchanged, so the base's carries over.
1118
- ...(delta.cursor ?? base.cursor) === void 0 ? {} : { cursor: delta.cursor ?? base.cursor },
1119
- rootIds,
1120
- nodes: [...byId.values()]
1121
- };
1122
- return validateSnapshot(composed, limits);
1123
- }
1124
-
1125
- // src/accesskit.ts
1126
- import { createHash } from "crypto";
1127
- var ACCESSKIT_ROOT_TREE_ID = "00000000-0000-0000-0000-000000000000";
1128
- var ACCESSKIT_ROLE_BY_SEMANTIC_ROLE = Object.freeze({
1129
- application: "application",
1130
- region: "region",
1131
- dialog: "dialog",
1132
- alert: "alert",
1133
- status: "status",
1134
- list: "list",
1135
- listitem: "listItem",
1136
- menu: "menu",
1137
- menuitem: "menuItem",
1138
- button: "button",
1139
- checkbox: "checkBox",
1140
- radio: "radioButton",
1141
- tab: "tab",
1142
- textbox: "textInput",
1143
- heading: "heading",
1144
- text: "label",
1145
- progressbar: "progressIndicator",
1146
- separator: "splitter",
1147
- scrollbar: "scrollBar",
1148
- table: "table",
1149
- row: "row",
1150
- cell: "cell",
1151
- generic: "genericContainer"
1152
- });
1153
- var ACCESSKIT_ACTION_BY_SEMANTIC_ACTION = Object.freeze({
1154
- focus: "focus",
1155
- activate: "click",
1156
- toggle: "click",
1157
- setValue: "setValue",
1158
- expand: "expand",
1159
- scroll: "scrollIntoView"
1160
- });
1161
- var NODE_ID_BITS = 53n;
1162
- var NODE_ID_MASK = (1n << NODE_ID_BITS) - 1n;
1163
- function accessKitNodeId(id) {
1164
- const digest = createHash("sha256").update(id, "utf8").digest();
1165
- const value = digest.readBigUInt64BE(0) & NODE_ID_MASK;
1166
- return value === 0n ? 1 : Number(value);
1167
- }
1168
- function toggledFor(checked) {
1169
- if (checked === void 0) return void 0;
1170
- if (checked === "mixed") return "mixed";
1171
- return checked ? "true" : "false";
1172
- }
1173
- function accessKitRoleFor(node) {
1174
- if (node.role === "textbox" && node.state?.multiline === true) return "multilineTextInput";
1175
- return ACCESSKIT_ROLE_BY_SEMANTIC_ROLE[node.role];
1176
- }
1177
- function actionsFor(actions) {
1178
- if (actions === void 0 || actions.length === 0) return void 0;
1179
- const mapped = /* @__PURE__ */ new Set();
1180
- for (const action of actions) {
1181
- const target = ACCESSKIT_ACTION_BY_SEMANTIC_ACTION[action];
1182
- if (target !== void 0) mapped.add(target);
1183
- }
1184
- return mapped.size === 0 ? void 0 : [...mapped];
1185
- }
1186
- function boundsFor(rect, cellSize) {
1187
- return {
1188
- x0: rect.column * cellSize.width,
1189
- y0: rect.row * cellSize.height,
1190
- x1: (rect.column + rect.width) * cellSize.width,
1191
- y1: (rect.row + rect.height) * cellSize.height
1192
- };
1193
- }
1194
- function toAccessKitTreeUpdate(snapshot, options = {}) {
1195
- const idOf = /* @__PURE__ */ new Map();
1196
- const seen = /* @__PURE__ */ new Map();
1197
- for (const node of snapshot.nodes) {
1198
- const mapped = accessKitNodeId(node.id);
1199
- const previous = seen.get(mapped);
1200
- if (previous !== void 0) {
1201
- throw new ProtocolViolation(
1202
- "dto-key",
1203
- `node ids "${previous}" and "${node.id}" collide in the AccessKit id space`
1204
- );
1205
- }
1206
- seen.set(mapped, node.id);
1207
- idOf.set(node.id, mapped);
1208
- }
1209
- const childrenOf = /* @__PURE__ */ new Map();
1210
- for (const node of snapshot.nodes) {
1211
- if (node.parentId === void 0) continue;
1212
- const siblings = childrenOf.get(node.parentId);
1213
- if (siblings === void 0) childrenOf.set(node.parentId, [idOf.get(node.id)]);
1214
- else siblings.push(idOf.get(node.id));
1076
+ if (node.parentId === void 0) continue;
1077
+ const siblings = childrenOf.get(node.parentId);
1078
+ if (siblings === void 0) childrenOf.set(node.parentId, [idOf.get(node.id)]);
1079
+ else siblings.push(idOf.get(node.id));
1215
1080
  }
1216
1081
  const relation = (ids) => {
1217
1082
  if (ids === void 0 || ids.length === 0) return void 0;
@@ -1225,14 +1090,15 @@ function toAccessKitTreeUpdate(snapshot, options = {}) {
1225
1090
  const id = idOf.get(node.id);
1226
1091
  const state = node.state;
1227
1092
  if (state?.focused === true && focus === void 0) focus = id;
1228
- if (node.bounds !== void 0) cellBounds[String(id)] = node.bounds;
1093
+ const visibleRect = node.geometry.visibleRect.status === "known" ? node.geometry.visibleRect.value : void 0;
1094
+ if (visibleRect !== void 0) cellBounds[String(id)] = visibleRect;
1229
1095
  const accessKitNode = {
1230
1096
  role: accessKitRoleFor(node),
1231
1097
  ...node.name === "" ? {} : { label: node.name },
1232
1098
  ...node.description === void 0 ? {} : { description: node.description },
1233
- ...node.value === void 0 ? {} : { value: node.value },
1099
+ ...node.value?.status === "known" && node.value.sensitivity === "public" ? { value: node.value.value } : {},
1234
1100
  ...childrenOf.has(node.id) ? { children: childrenOf.get(node.id) } : {},
1235
- ...node.bounds !== void 0 && options.cellSize !== void 0 ? { bounds: boundsFor(node.bounds, options.cellSize) } : {},
1101
+ ...visibleRect !== void 0 && options.cellSize !== void 0 ? { bounds: boundsFor(visibleRect, options.cellSize) } : {},
1236
1102
  ...actionsFor(node.actions) === void 0 ? {} : { actions: actionsFor(node.actions) },
1237
1103
  ...relation(node.labelledBy) === void 0 ? {} : { labelledBy: relation(node.labelledBy) },
1238
1104
  ...relation(node.describedBy) === void 0 ? {} : { describedBy: relation(node.describedBy) },
@@ -1243,6 +1109,8 @@ function toAccessKitTreeUpdate(snapshot, options = {}) {
1243
1109
  ...state?.modal === void 0 ? {} : { modal: state.modal },
1244
1110
  ...state?.hidden === void 0 ? {} : { hidden: state.hidden },
1245
1111
  ...state?.readonly === void 0 ? {} : { readOnly: state.readonly },
1112
+ ...state?.required === void 0 ? {} : { required: state.required },
1113
+ ...state?.multiselectable === void 0 ? {} : { multiselectable: state.multiselectable },
1246
1114
  ...toggledFor(state?.checked) === void 0 ? {} : { toggled: toggledFor(state?.checked) }
1247
1115
  };
1248
1116
  nodes.push(Object.freeze([id, Object.freeze(accessKitNode)]));
@@ -1264,135 +1132,154 @@ function toAccessKitTreeUpdate(snapshot, options = {}) {
1264
1132
  return Object.freeze({ update: Object.freeze(update), cellBounds: Object.freeze(cellBounds) });
1265
1133
  }
1266
1134
 
1267
- // src/probe/bounds.ts
1268
- function intersect(a, b) {
1269
- const row = Math.max(a.row, b.row);
1270
- const column = Math.max(a.column, b.column);
1271
- const bottom = Math.min(a.row + a.height, b.row + b.height);
1272
- const right = Math.min(a.column + a.width, b.column + b.width);
1273
- const height = Math.max(0, bottom - row);
1274
- const width = Math.max(0, right - column);
1275
- return { rect: { row, column, width, height }, empty: width === 0 || height === 0 };
1276
- }
1277
- function resolveNodeBounds(geometry, options = {}) {
1278
- const occlusion = options.paintOrderKnown === true ? "known" : "unknown";
1279
- if (geometry?.visibleRect !== void 0) {
1280
- const rect = geometry.visibleRect;
1281
- return {
1282
- rect,
1283
- occlusion,
1284
- source: "visible",
1285
- clippedAway: rect.width === 0 || rect.height === 0
1286
- };
1287
- }
1288
- if (geometry?.intendedRect === void 0) return void 0;
1289
- if (options.clip !== void 0) {
1290
- const { rect, empty } = intersect(geometry.intendedRect, options.clip);
1291
- const occupiedCells = geometry.intendedRect.width > 0 && geometry.intendedRect.height > 0;
1292
- return { rect, occlusion, source: "clipped", clippedAway: empty && occupiedCells };
1293
- }
1294
- return {
1295
- rect: geometry.intendedRect,
1296
- occlusion,
1297
- source: "intended",
1298
- clippedAway: false
1299
- };
1300
- }
1301
-
1302
1135
  // src/probe/validate.ts
1303
- import { Buffer as Buffer5 } from "buffer";
1304
- import { z as z4 } from "zod";
1305
- function fail4(code, detail) {
1136
+ import { Buffer as Buffer3 } from "buffer";
1137
+ import { z as z2 } from "zod";
1138
+ function fail2(code, detail) {
1306
1139
  return { ok: false, code, detail };
1307
1140
  }
1308
- var safeInt2 = z4.number().refine(Number.isSafeInteger, "expected a safe integer");
1309
- var nonNegative = z4.number().refine((n) => Number.isSafeInteger(n) && n >= 0, "expected a non-negative safe integer");
1310
- var positive = z4.number().refine((n) => Number.isSafeInteger(n) && n > 0, "expected a positive safe integer");
1141
+ var safeInt2 = z2.number().refine(Number.isSafeInteger, "expected a safe integer");
1142
+ var nonNegative = z2.number().refine((n) => Number.isSafeInteger(n) && n >= 0, "expected a non-negative safe integer");
1143
+ var positive = z2.number().refine((n) => Number.isSafeInteger(n) && n > 0, "expected a positive safe integer");
1311
1144
  var cache2 = /* @__PURE__ */ new WeakMap();
1312
1145
  function buildFrameSchema(limits) {
1313
- const text = z4.string().refine(
1314
- (s) => Buffer5.byteLength(s, "utf8") <= limits.maxStringBytes,
1146
+ const text = z2.string().refine(
1147
+ (s) => Buffer3.byteLength(s, "utf8") <= limits.maxStringBytes,
1315
1148
  `expected at most ${limits.maxStringBytes} UTF-8 bytes`
1316
1149
  );
1317
- const rect = z4.strictObject({
1150
+ const rect = z2.strictObject({
1318
1151
  row: safeInt2,
1319
1152
  column: safeInt2,
1320
1153
  width: nonNegative,
1321
1154
  height: nonNegative
1322
1155
  });
1323
- const identity = z4.strictObject({
1324
- kind: z4.enum(["stable", "frame-local"]),
1156
+ const identity = z2.strictObject({
1157
+ kind: z2.enum(["stable", "frame-local"]),
1325
1158
  value: text.min(1)
1326
1159
  });
1327
- const extendedValue = z4.lazy(
1328
- () => z4.union([
1329
- z4.null(),
1330
- z4.boolean(),
1331
- z4.number().finite().refine(
1160
+ const extendedValue = z2.lazy(
1161
+ () => z2.union([
1162
+ z2.null(),
1163
+ z2.boolean(),
1164
+ z2.number().finite().refine(
1332
1165
  (value) => Math.abs(value) <= Number.MAX_SAFE_INTEGER,
1333
1166
  "expected a finite JSON number in the safe range"
1334
1167
  ),
1335
1168
  text,
1336
- z4.array(extendedValue).max(limits.maxRelationTargets),
1337
- z4.record(text, extendedValue).refine(
1169
+ z2.array(extendedValue).max(limits.maxRelationTargets),
1170
+ z2.record(text, extendedValue).refine(
1338
1171
  (value) => Object.keys(value).length <= limits.maxRelationTargets,
1339
1172
  `expected at most ${limits.maxRelationTargets} properties`
1340
1173
  )
1341
1174
  ])
1342
1175
  );
1343
- const extended = z4.record(text, extendedValue).refine(
1176
+ const extended = z2.record(text, extendedValue).refine(
1344
1177
  (value) => Object.keys(value).length <= limits.maxRelationTargets,
1345
1178
  `expected at most ${limits.maxRelationTargets} properties`
1346
1179
  );
1347
- const relations = z4.array(text.min(1)).max(limits.maxRelationTargets);
1348
- const state = z4.strictObject({
1349
- focused: z4.boolean().optional(),
1350
- disabled: z4.boolean().optional(),
1351
- checked: z4.union([z4.boolean(), z4.literal("mixed")]).optional(),
1352
- expanded: z4.boolean().optional(),
1353
- readonly: z4.boolean().optional(),
1354
- selected: z4.boolean().optional(),
1355
- busy: z4.boolean().optional(),
1356
- multiline: z4.boolean().optional(),
1357
- displayed: z4.boolean().optional(),
1180
+ const relations = z2.array(text.min(1)).max(limits.maxRelationTargets);
1181
+ const inputRecipe = z2.strictObject({
1182
+ action: z2.enum(PHYSICAL_INPUT_RECIPE_ACTIONS),
1183
+ requiresFocus: z2.boolean(),
1184
+ steps: z2.array(
1185
+ z2.union([
1186
+ z2.strictObject({ kind: z2.literal("press"), key: text.min(1) }),
1187
+ z2.strictObject({ kind: z2.literal("insert-action-value") })
1188
+ ])
1189
+ ).min(1).max(limits.maxRelationTargets)
1190
+ }).superRefine((recipe, context) => {
1191
+ const inserts = recipe.steps.filter(({ kind }) => kind === "insert-action-value").length;
1192
+ if (recipe.action === "setValue" && inserts !== 1 || recipe.action !== "setValue" && inserts !== 0) {
1193
+ context.addIssue({
1194
+ code: "custom",
1195
+ message: "setValue requires exactly one insert-action-value step"
1196
+ });
1197
+ }
1198
+ if (recipe.action === "focus" && recipe.requiresFocus) {
1199
+ context.addIssue({
1200
+ code: "custom",
1201
+ message: "focus recipe cannot require focus"
1202
+ });
1203
+ }
1204
+ });
1205
+ const inputRecipes = z2.array(inputRecipe).max(PHYSICAL_INPUT_RECIPE_ACTIONS.length).superRefine((recipes, context) => {
1206
+ if (new Set(recipes.map(({ action }) => action)).size !== recipes.length) {
1207
+ context.addIssue({
1208
+ code: "custom",
1209
+ message: "input recipe actions must be unique"
1210
+ });
1211
+ }
1212
+ });
1213
+ const state = z2.strictObject({
1214
+ focused: z2.boolean().optional(),
1215
+ disabled: z2.boolean().optional(),
1216
+ checked: z2.union([z2.boolean(), z2.literal("mixed")]).optional(),
1217
+ expanded: z2.boolean().optional(),
1218
+ readonly: z2.boolean().optional(),
1219
+ selected: z2.boolean().optional(),
1220
+ busy: z2.boolean().optional(),
1221
+ multiline: z2.boolean().optional(),
1222
+ required: z2.boolean().optional(),
1223
+ multiselectable: z2.boolean().optional(),
1224
+ displayed: z2.boolean().optional(),
1358
1225
  value: text.optional(),
1226
+ valueSensitivity: z2.enum(["public", "sensitive"]).optional(),
1359
1227
  selectedIndex: nonNegative.optional(),
1360
- textSelection: z4.strictObject({ start: nonNegative, end: nonNegative }).optional(),
1361
- scroll: z4.strictObject({ row: nonNegative, column: nonNegative }).optional(),
1362
- scrollExtent: z4.strictObject({ rows: nonNegative, columns: nonNegative }).optional()
1228
+ textSelection: z2.strictObject({ start: nonNegative, end: nonNegative }).optional(),
1229
+ scroll: z2.strictObject({ row: nonNegative, column: nonNegative }).optional(),
1230
+ scrollExtent: z2.strictObject({ rows: nonNegative, columns: nonNegative }).optional()
1363
1231
  });
1364
- const object = z4.strictObject({
1232
+ const object = z2.strictObject({
1365
1233
  identity,
1366
1234
  frameworkType: text.min(1),
1367
1235
  parent: text.optional(),
1368
- geometry: z4.strictObject({ intendedRect: rect.optional(), visibleRect: rect.optional() }).optional(),
1236
+ geometry: z2.strictObject({
1237
+ intendedRect: rect.optional(),
1238
+ visibleRect: rect.optional()
1239
+ }).optional(),
1369
1240
  state: state.optional(),
1370
1241
  text: text.optional(),
1371
- accessibility: z4.strictObject({ role: text.optional(), name: text.optional(), description: text.optional() }).optional(),
1372
- annotations: z4.strictObject({
1242
+ accessibility: z2.strictObject({
1243
+ role: text.optional(),
1244
+ name: text.optional(),
1245
+ description: text.optional()
1246
+ }).optional(),
1247
+ annotations: z2.strictObject({
1373
1248
  role: text.optional(),
1374
1249
  name: text.optional(),
1375
1250
  testId: text.optional(),
1376
1251
  description: text.optional(),
1377
1252
  extended: extended.optional(),
1378
- actions: z4.array(z4.enum(SEMANTIC_ACTIONS)).max(SEMANTIC_ACTIONS.length).optional(),
1253
+ actions: z2.array(z2.enum(SEMANTIC_ACTIONS)).max(SEMANTIC_ACTIONS.length).optional(),
1254
+ inputRecipes: inputRecipes.optional(),
1379
1255
  labelledBy: relations.optional(),
1380
1256
  describedBy: relations.optional()
1257
+ }).superRefine((annotations, context) => {
1258
+ const intents = new Set(annotations.actions ?? []);
1259
+ for (const [index, recipe] of (annotations.inputRecipes ?? []).entries()) {
1260
+ if (!intents.has(recipe.action)) {
1261
+ context.addIssue({
1262
+ code: "custom",
1263
+ path: ["inputRecipes", index, "action"],
1264
+ message: `input recipe '${recipe.action}' requires the matching semantic action intent`
1265
+ });
1266
+ }
1267
+ }
1381
1268
  }).optional(),
1382
1269
  paintOrder: safeInt2.optional(),
1383
- unobservable: z4.array(z4.enum(PROBE_UNOBSERVABLE_FIELDS)).max(PROBE_UNOBSERVABLE_FIELDS.length).optional()
1270
+ unobservable: z2.array(z2.enum(PROBE_UNOBSERVABLE_FIELDS)).max(PROBE_UNOBSERVABLE_FIELDS.length).optional()
1384
1271
  });
1385
- const operation = z4.strictObject({
1386
- kind: z4.enum(["render", "layout"]),
1272
+ const operation = z2.strictObject({
1273
+ kind: z2.enum(["render", "layout"]),
1387
1274
  ordinal: nonNegative,
1388
1275
  target: identity.optional(),
1389
1276
  frameworkType: text.optional(),
1390
1277
  intendedRect: rect.optional()
1391
1278
  });
1392
- return z4.strictObject({
1279
+ return z2.strictObject({
1393
1280
  frame: positive,
1394
- objects: z4.array(object).max(limits.maxNodes),
1395
- operations: z4.array(operation).max(limits.maxNodes).optional()
1281
+ objects: z2.array(object).max(limits.maxNodes),
1282
+ operations: z2.array(operation).max(limits.maxNodes).optional()
1396
1283
  });
1397
1284
  }
1398
1285
  function frameSchema(limits) {
@@ -1402,12 +1289,17 @@ function frameSchema(limits) {
1402
1289
  cache2.set(limits, built);
1403
1290
  return built;
1404
1291
  }
1405
- var probeInfoSchema = z4.strictObject({
1406
- framework: z4.string().min(1).max(128),
1407
- frameworkVersion: z4.string().max(128).optional(),
1408
- probeVersion: z4.string().min(1).max(128),
1409
- identityKind: z4.enum(["stable", "frame-local"]),
1410
- capabilities: z4.array(z4.enum(PROBE_CAPABILITIES)).max(PROBE_CAPABILITIES.length)
1292
+ var probeInfoSchema = z2.strictObject({
1293
+ framework: z2.string().min(1).max(128),
1294
+ frameworkVersion: z2.string().max(128).optional(),
1295
+ probeVersion: z2.string().min(1).max(128),
1296
+ identityKind: z2.enum(["stable", "frame-local"]),
1297
+ capabilities: z2.array(z2.enum(PROBE_CAPABILITIES)).max(PROBE_CAPABILITIES.length),
1298
+ instrumentation: z2.strictObject({
1299
+ highestTier: z2.enum(PROBE_INJECTION_TIERS),
1300
+ semanticClass: z2.enum(PROBE_SEMANTIC_CLASSES),
1301
+ degradedCapabilities: z2.array(z2.enum(PROBE_DEGRADED_CAPABILITIES)).max(PROBE_DEGRADED_CAPABILITIES.length)
1302
+ }).optional()
1411
1303
  });
1412
1304
  function validateProbeInfo(value) {
1413
1305
  const parsed = probeInfoSchema.safeParse(value);
@@ -1417,13 +1309,35 @@ function validateProbeInfo(value) {
1417
1309
  return { ok: false, detail: `${where}: ${issue.message}` };
1418
1310
  }
1419
1311
  const info = parsed.data;
1312
+ const degraded = info.instrumentation?.degradedCapabilities;
1313
+ if (degraded !== void 0 && new Set(degraded).size !== degraded.length) {
1314
+ return { ok: false, detail: "instrumentation.degradedCapabilities: duplicate capability" };
1315
+ }
1316
+ if (info.instrumentation?.semanticClass === "B" && (!degraded?.includes("intended-geometry") || !degraded.includes("clipped-geometry"))) {
1317
+ return {
1318
+ ok: false,
1319
+ detail: "semantic class B must declare both 'intended-geometry' and 'clipped-geometry' as degraded"
1320
+ };
1321
+ }
1420
1322
  if (info.identityKind === "frame-local" && info.capabilities.includes("stable-identity")) {
1421
1323
  return {
1422
1324
  ok: false,
1423
1325
  detail: "a probe declaring identityKind 'frame-local' must not claim the 'stable-identity' capability: nothing in an immediate-mode frame survives to be correlated"
1424
1326
  };
1425
1327
  }
1426
- return { ok: true, info: Object.freeze(info) };
1328
+ return {
1329
+ ok: true,
1330
+ info: Object.freeze({
1331
+ ...info,
1332
+ capabilities: Object.freeze([...info.capabilities]),
1333
+ ...info.instrumentation === void 0 ? {} : {
1334
+ instrumentation: Object.freeze({
1335
+ ...info.instrumentation,
1336
+ degradedCapabilities: Object.freeze([...info.instrumentation.degradedCapabilities])
1337
+ })
1338
+ }
1339
+ })
1340
+ };
1427
1341
  }
1428
1342
  function validateProbeFrame(value, limits) {
1429
1343
  let projected;
@@ -1431,15 +1345,15 @@ function validateProbeFrame(value, limits) {
1431
1345
  projected = projectDto(value, limits.maxDepth);
1432
1346
  } catch (error) {
1433
1347
  if (error instanceof ProtocolViolation) {
1434
- return fail4(error.code === "dto-depth" ? "depth" : "schema", error.message);
1348
+ return fail2(error.code === "dto-depth" ? "depth" : "schema", error.message);
1435
1349
  }
1436
- return fail4("schema", "value could not be projected into a plain DTO");
1350
+ return fail2("schema", "value could not be projected into a plain DTO");
1437
1351
  }
1438
1352
  const serialised = JSON.stringify(projected);
1439
- if (serialised === void 0) return fail4("schema", "probe frame is not a JSON object");
1440
- const bytes = Buffer5.byteLength(serialised, "utf8");
1353
+ if (serialised === void 0) return fail2("schema", "probe frame is not a JSON object");
1354
+ const bytes = Buffer3.byteLength(serialised, "utf8");
1441
1355
  if (bytes > limits.maxSnapshotBytes) {
1442
- return fail4("bytes", `probe frame is ${bytes} bytes, ceiling is ${limits.maxSnapshotBytes}`);
1356
+ return fail2("bytes", `probe frame is ${bytes} bytes, ceiling is ${limits.maxSnapshotBytes}`);
1443
1357
  }
1444
1358
  const parsed = frameSchema(limits).safeParse(projected);
1445
1359
  if (!parsed.success) {
@@ -1447,31 +1361,37 @@ function validateProbeFrame(value, limits) {
1447
1361
  const path = issue.path.map(String);
1448
1362
  const where = path.length > 0 ? path.join(".") : "<root>";
1449
1363
  const code = path.includes("intendedRect") || path.includes("visibleRect") ? "bad-rect" : path.includes("frame") ? "revision" : issue.code === "too_big" ? "count" : "schema";
1450
- return fail4(code, `${where}: ${issue.message}`);
1364
+ return fail2(code, `${where}: ${issue.message}`);
1451
1365
  }
1452
1366
  const frame = projected;
1453
1367
  const seen = /* @__PURE__ */ new Set();
1454
1368
  for (const object of frame.objects) {
1455
1369
  if (seen.has(object.identity.value)) {
1456
- return fail4("duplicate-id", `identity ${object.identity.value} appears twice in the frame`);
1370
+ return fail2("duplicate-id", `identity ${object.identity.value} appears twice in the frame`);
1457
1371
  }
1458
1372
  seen.add(object.identity.value);
1459
1373
  }
1460
1374
  for (const object of frame.objects) {
1461
1375
  if (object.parent !== void 0 && !seen.has(object.parent)) {
1462
- return fail4(
1376
+ return fail2(
1463
1377
  "missing-parent",
1464
1378
  `object ${object.identity.value} names parent ${object.parent}, which is not in the frame`
1465
1379
  );
1466
1380
  }
1467
1381
  if (object.parent === object.identity.value) {
1468
- return fail4("cycle", `object ${object.identity.value} is its own parent`);
1382
+ return fail2("cycle", `object ${object.identity.value} is its own parent`);
1383
+ }
1384
+ if (object.state?.valueSensitivity !== void 0 && object.state.value === void 0) {
1385
+ return fail2(
1386
+ "schema",
1387
+ `object ${object.identity.value} classifies a semantic value it did not report`
1388
+ );
1469
1389
  }
1470
1390
  const unobservable = object.unobservable;
1471
1391
  if (unobservable === void 0) continue;
1472
1392
  const declared = new Set(unobservable);
1473
1393
  if (declared.size !== unobservable.length) {
1474
- return fail4("duplicate-id", `object ${object.identity.value} repeats an unobservable field`);
1394
+ return fail2("duplicate-id", `object ${object.identity.value} repeats an unobservable field`);
1475
1395
  }
1476
1396
  for (const [field, present] of [
1477
1397
  ["text", object.text !== void 0],
@@ -1481,7 +1401,7 @@ function validateProbeFrame(value, limits) {
1481
1401
  ["paintOrder", object.paintOrder !== void 0]
1482
1402
  ]) {
1483
1403
  if (declared.has(field) && present) {
1484
- return fail4(
1404
+ return fail2(
1485
1405
  "schema",
1486
1406
  `object ${object.identity.value} reports ${field} and also declares it unobservable`
1487
1407
  );
@@ -1489,7 +1409,7 @@ function validateProbeFrame(value, limits) {
1489
1409
  }
1490
1410
  for (const [field, value_] of Object.entries(object.state ?? {})) {
1491
1411
  if (declared.has(field) && value_ !== void 0) {
1492
- return fail4(
1412
+ return fail2(
1493
1413
  "schema",
1494
1414
  `object ${object.identity.value} reports state.${field} and also declares it unobservable`
1495
1415
  );
@@ -1534,26 +1454,350 @@ function validateProbeAnnotations(value, limits) {
1534
1454
  }
1535
1455
 
1536
1456
  // src/messages.ts
1537
- import { z as z5 } from "zod";
1538
- var ADAPTER_CAPABILITIES = [
1539
- "tree",
1540
- "bounds",
1541
- "absolute-bounds",
1542
- "states",
1543
- "actions",
1544
- "text-ranges",
1545
- "render-revisions",
1546
- "tree-diffs",
1547
- "logs",
1548
- "qualified-observations",
1549
- "pointer-hit-grid"
1550
- ];
1457
+ import { z as z4 } from "zod";
1458
+
1459
+ // src/validate.ts
1460
+ import { Buffer as Buffer4 } from "buffer";
1461
+ import "zod";
1462
+ function fail3(code, detail) {
1463
+ return { ok: false, code, detail };
1464
+ }
1465
+ function codeForIssue(issue) {
1466
+ const path = issue.path.map(String);
1467
+ if (path.includes("role")) return "unknown-role";
1468
+ if (path.includes("revision")) return "revision";
1469
+ if (path.includes("bounds") || ["row", "column", "width", "height"].includes(path.at(-1) ?? ""))
1470
+ return "bad-rect";
1471
+ if (issue.code === "custom" && issue.message?.includes("hit regions")) return "bad-rect";
1472
+ if (issue.code === "too_big" && (path.includes("nodes") || path.includes("rootIds"))) {
1473
+ return "count";
1474
+ }
1475
+ if (issue.code === "custom" && typeof issue.message === "string" && issue.message.includes("UTF-8 bytes")) {
1476
+ return "string-bytes";
1477
+ }
1478
+ return "schema";
1479
+ }
1480
+ function describeIssue(issue) {
1481
+ const where = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
1482
+ return `${where}: ${issue.message}`;
1483
+ }
1484
+ function rectIntersectsViewport(rect, columns, rows) {
1485
+ if (rect.width === 0 || rect.height === 0) return false;
1486
+ return rect.column < columns && rect.row < rows && rect.column + rect.width > 0 && rect.row + rect.height > 0;
1487
+ }
1488
+ function rectContains(outer, inner) {
1489
+ return inner.row >= outer.row && inner.column >= outer.column && inner.row + inner.height <= outer.row + outer.height && inner.column + inner.width <= outer.column + outer.width;
1490
+ }
1491
+ function regionProblem(owner, region, columns, rows) {
1492
+ for (const span of region.spans) {
1493
+ if (span.row >= rows || span.from >= columns || span.to > columns) {
1494
+ return fail3("bad-rect", `${owner} span lies outside the viewport`);
1495
+ }
1496
+ if (span.row < region.regionBounds.row || span.row >= region.regionBounds.row + region.regionBounds.height || span.from < region.regionBounds.column || span.to > region.regionBounds.column + region.regionBounds.width) {
1497
+ return fail3("bad-rect", `${owner} span lies outside regionBounds`);
1498
+ }
1499
+ }
1500
+ return null;
1501
+ }
1502
+ function checkNodeShape(node, snapshot, ids, limits) {
1503
+ if (node.role === "generic" && (node.frameworkType === void 0 || node.frameworkType === "")) {
1504
+ return fail3(
1505
+ "schema",
1506
+ `node ${node.id} has role 'generic' without a frameworkType; an unrecognised widget must name what the framework called it`
1507
+ );
1508
+ }
1509
+ if (node.state?.offscreen === true && node.state.hidden !== true) {
1510
+ return fail3(
1511
+ "schema",
1512
+ `node ${node.id}: state.offscreen implies state.hidden \u2014 every cell is outside the visible area, so the node cannot also be visible`
1513
+ );
1514
+ }
1515
+ for (const [name, observation] of [
1516
+ ["intendedRect", node.geometry.intendedRect],
1517
+ ["visibleRect", node.geometry.visibleRect]
1518
+ ]) {
1519
+ if (observation.status !== "known") continue;
1520
+ const { row, column, width, height } = observation.value;
1521
+ if (!Number.isSafeInteger(row + height) || !Number.isSafeInteger(column + width)) {
1522
+ return fail3("bad-rect", `node ${node.id}: ${name} overflows the safe-integer range`);
1523
+ }
1524
+ }
1525
+ if (node.paintedRegion?.status === "known") {
1526
+ const problem = regionProblem(
1527
+ `node ${node.id} painted region`,
1528
+ node.paintedRegion.value,
1529
+ snapshot.columns,
1530
+ snapshot.rows
1531
+ );
1532
+ if (problem !== null) return problem;
1533
+ }
1534
+ const intended = node.geometry.intendedRect;
1535
+ const visible = node.geometry.visibleRect;
1536
+ if (visible.status === "known" && visible.value.width > 0 && visible.value.height > 0) {
1537
+ if (snapshot.coordinateSpace.status === "known" && snapshot.coordinateSpace.value === "viewport-cells" && !rectIntersectsViewport(visible.value, snapshot.columns, snapshot.rows)) {
1538
+ return fail3("bad-rect", `node ${node.id}: visibleRect does not intersect the viewport`);
1539
+ }
1540
+ if (intended.status === "known" && !rectContains(intended.value, visible.value)) {
1541
+ return fail3("bad-rect", `node ${node.id}: visibleRect extends outside intendedRect`);
1542
+ }
1543
+ }
1544
+ for (const range of node.textRanges ?? []) {
1545
+ if (range.endOffset < range.startOffset) {
1546
+ return fail3("bad-rect", `node ${node.id}: text range ends before it starts`);
1547
+ }
1548
+ if (!Number.isSafeInteger(range.rect.row + range.rect.height)) {
1549
+ return fail3("bad-rect", `node ${node.id}: text range rect overflows the safe-integer range`);
1550
+ }
1551
+ }
1552
+ for (const [field, targets] of [
1553
+ ["labelledBy", node.labelledBy],
1554
+ ["describedBy", node.describedBy]
1555
+ ]) {
1556
+ if (targets === void 0) continue;
1557
+ if (targets.length > limits.maxRelationTargets) {
1558
+ return fail3(
1559
+ "count",
1560
+ `node ${node.id}: ${field} exceeds ${limits.maxRelationTargets} targets`
1561
+ );
1562
+ }
1563
+ for (const target of targets) {
1564
+ if (!ids.has(target)) {
1565
+ return fail3(
1566
+ "missing-parent",
1567
+ `node ${node.id}: ${field} references unknown node ${target}`
1568
+ );
1569
+ }
1570
+ }
1571
+ }
1572
+ return null;
1573
+ }
1574
+ function computeDepths(nodes, byId) {
1575
+ const depths = /* @__PURE__ */ new Map();
1576
+ for (const start of nodes) {
1577
+ if (depths.has(start.id)) continue;
1578
+ const chain = [];
1579
+ const onChain = /* @__PURE__ */ new Set();
1580
+ let current = start;
1581
+ while (current !== void 0 && !depths.has(current.id)) {
1582
+ if (onChain.has(current.id)) return { cycleAt: current.id };
1583
+ onChain.add(current.id);
1584
+ chain.push(current.id);
1585
+ current = current.parentId === void 0 ? void 0 : byId.get(current.parentId);
1586
+ }
1587
+ let depth = current === void 0 ? 0 : depths.get(current.id);
1588
+ for (let i = chain.length - 1; i >= 0; i -= 1) {
1589
+ depth += 1;
1590
+ depths.set(chain[i], depth);
1591
+ }
1592
+ }
1593
+ return { depths };
1594
+ }
1595
+ function validateSnapshot(value, limits) {
1596
+ let projected;
1597
+ try {
1598
+ projected = projectDto(value, limits.maxDepth);
1599
+ } catch (error) {
1600
+ if (error instanceof ProtocolViolation) {
1601
+ return fail3(error.code === "dto-depth" ? "depth" : "schema", error.message);
1602
+ }
1603
+ return fail3("schema", "value could not be projected into a plain DTO");
1604
+ }
1605
+ const serialised = JSON.stringify(projected);
1606
+ if (serialised === void 0) {
1607
+ return fail3("schema", "snapshot is not a JSON object");
1608
+ }
1609
+ const bytes = Buffer4.byteLength(serialised, "utf8");
1610
+ if (bytes > limits.maxSnapshotBytes) {
1611
+ return fail3("bytes", `snapshot is ${bytes} bytes, ceiling is ${limits.maxSnapshotBytes}`);
1612
+ }
1613
+ const parsed = treeSchemas(limits).snapshot.safeParse(projected);
1614
+ if (!parsed.success) {
1615
+ const issue = parsed.error.issues[0];
1616
+ return fail3(codeForIssue(issue), describeIssue(issue));
1617
+ }
1618
+ const snapshot = projected;
1619
+ if (snapshot.nodes.length > limits.maxNodes) {
1620
+ return fail3(
1621
+ "count",
1622
+ `snapshot carries ${snapshot.nodes.length} nodes, ceiling is ${limits.maxNodes}`
1623
+ );
1624
+ }
1625
+ const byId = /* @__PURE__ */ new Map();
1626
+ for (const node of snapshot.nodes) {
1627
+ if (byId.has(node.id)) {
1628
+ return fail3("duplicate-id", `node id ${node.id} appears more than once`);
1629
+ }
1630
+ byId.set(node.id, node);
1631
+ }
1632
+ const rootIds = /* @__PURE__ */ new Set();
1633
+ for (const id of snapshot.rootIds) {
1634
+ if (rootIds.has(id)) {
1635
+ return fail3("duplicate-id", `root id ${id} appears more than once`);
1636
+ }
1637
+ rootIds.add(id);
1638
+ const node = byId.get(id);
1639
+ if (node === void 0) {
1640
+ return fail3("missing-parent", `rootIds references unknown node ${id}`);
1641
+ }
1642
+ if (node.parentId !== void 0) {
1643
+ return fail3("schema", `root node ${id} declares a parent`);
1644
+ }
1645
+ }
1646
+ const ids = new Set(byId.keys());
1647
+ if (snapshot.coordinateSpace.status === "known" && snapshot.coordinateSpace.value !== "viewport-cells") {
1648
+ }
1649
+ if (snapshot.hitGrid.status === "known") {
1650
+ for (const region of snapshot.hitGrid.value.regions) {
1651
+ if (!ids.has(region.recipientId)) {
1652
+ return fail3("missing-parent", `hitGrid references unknown recipient ${region.recipientId}`);
1653
+ }
1654
+ if (!rectIntersectsViewport(region.rect, snapshot.columns, snapshot.rows)) {
1655
+ return fail3(
1656
+ "bad-rect",
1657
+ `hitGrid region for ${region.recipientId} does not intersect the viewport`
1658
+ );
1659
+ }
1660
+ }
1661
+ }
1662
+ const providerIds = /* @__PURE__ */ new Set();
1663
+ for (const provider of snapshot.providerEvidence ?? []) {
1664
+ if (providerIds.has(provider.providerId)) {
1665
+ return fail3("provider", `provider evidence id ${provider.providerId} appears more than once`);
1666
+ }
1667
+ providerIds.add(provider.providerId);
1668
+ if (provider.sessionId !== snapshot.sessionId) {
1669
+ return fail3(
1670
+ "provider",
1671
+ `provider ${provider.providerId} evidence session ${provider.sessionId} does not match snapshot session ${snapshot.sessionId}`
1672
+ );
1673
+ }
1674
+ if (provider.revision !== snapshot.revision) {
1675
+ return fail3(
1676
+ "provider",
1677
+ `provider ${provider.providerId} evidence revision ${provider.revision} does not match snapshot revision ${snapshot.revision}`
1678
+ );
1679
+ }
1680
+ if (provider.status !== "available") continue;
1681
+ if (provider.evidence.providerId !== provider.providerId) {
1682
+ return fail3(
1683
+ "schema",
1684
+ `provider ${provider.providerId} evidence provenance names ${provider.evidence.providerId}`
1685
+ );
1686
+ }
1687
+ if (provider.focusState?.status === "focused" && !ids.has(provider.focusState.recipientId)) {
1688
+ return fail3(
1689
+ "missing-parent",
1690
+ `provider ${provider.providerId} focus references unknown recipient ${provider.focusState.recipientId}`
1691
+ );
1692
+ }
1693
+ for (const region of provider.pointerRegions) {
1694
+ if (!ids.has(region.recipientId)) {
1695
+ return fail3(
1696
+ "missing-parent",
1697
+ `provider ${provider.providerId} references unknown recipient ${region.recipientId}`
1698
+ );
1699
+ }
1700
+ const problem = regionProblem(
1701
+ `provider ${provider.providerId} span for ${region.recipientId}`,
1702
+ region,
1703
+ snapshot.columns,
1704
+ snapshot.rows
1705
+ );
1706
+ if (problem !== null) return problem;
1707
+ }
1708
+ for (const region of provider.paintedRegions ?? []) {
1709
+ if (!ids.has(region.recipientId)) {
1710
+ return fail3(
1711
+ "missing-parent",
1712
+ `provider ${provider.providerId} painted region references unknown recipient ${region.recipientId}`
1713
+ );
1714
+ }
1715
+ const problem = regionProblem(
1716
+ `provider ${provider.providerId} painted region for ${region.recipientId}`,
1717
+ region,
1718
+ snapshot.columns,
1719
+ snapshot.rows
1720
+ );
1721
+ if (problem !== null) return problem;
1722
+ }
1723
+ for (const hit of provider.hitGrid?.regions ?? []) {
1724
+ if (!ids.has(hit.recipientId)) {
1725
+ return fail3(
1726
+ "missing-parent",
1727
+ `provider ${provider.providerId} hitGrid references unknown recipient ${hit.recipientId}`
1728
+ );
1729
+ }
1730
+ if (!rectIntersectsViewport(hit.rect, snapshot.columns, snapshot.rows)) {
1731
+ return fail3(
1732
+ "bad-rect",
1733
+ `provider ${provider.providerId} hitGrid region for ${hit.recipientId} does not intersect the viewport`
1734
+ );
1735
+ }
1736
+ }
1737
+ for (const entry of provider.actionRecipes ?? []) {
1738
+ if (!ids.has(entry.recipientId)) {
1739
+ return fail3(
1740
+ "missing-parent",
1741
+ `provider ${provider.providerId} action recipes reference unknown recipient ${entry.recipientId}`
1742
+ );
1743
+ }
1744
+ const node = snapshot.nodes.find(({ id }) => id === entry.recipientId);
1745
+ const intents = new Set(node.actions ?? []);
1746
+ const missingIntent = entry.recipes.find(({ action }) => !intents.has(action));
1747
+ if (missingIntent !== void 0) {
1748
+ return fail3(
1749
+ "provider",
1750
+ `provider ${provider.providerId} ${missingIntent.action} recipe has no matching semantic action intent on ${entry.recipientId}`
1751
+ );
1752
+ }
1753
+ }
1754
+ for (const state of provider.scrollStates ?? []) {
1755
+ if (!ids.has(state.recipientId)) {
1756
+ return fail3(
1757
+ "missing-parent",
1758
+ `provider ${provider.providerId} scroll state references unknown recipient ${state.recipientId}`
1759
+ );
1760
+ }
1761
+ }
1762
+ }
1763
+ for (const node of snapshot.nodes) {
1764
+ if (node.parentId === void 0) {
1765
+ if (!rootIds.has(node.id)) {
1766
+ return fail3("schema", `parentless node ${node.id} is missing from rootIds`);
1767
+ }
1768
+ } else if (!byId.has(node.parentId)) {
1769
+ return fail3("missing-parent", `node ${node.id} references unknown parent ${node.parentId}`);
1770
+ } else if (node.parentId === node.id) {
1771
+ return fail3("cycle", `node ${node.id} is its own parent`);
1772
+ }
1773
+ const problem = checkNodeShape(node, snapshot, ids, limits);
1774
+ if (problem !== null) return problem;
1775
+ }
1776
+ const depthResult = computeDepths(snapshot.nodes, byId);
1777
+ if ("cycleAt" in depthResult) {
1778
+ return fail3("cycle", `parent chain through node ${depthResult.cycleAt} is cyclic`);
1779
+ }
1780
+ for (const [id, depth] of depthResult.depths) {
1781
+ if (depth > limits.maxDepth) {
1782
+ return fail3("depth", `node ${id} sits at depth ${depth}, ceiling is ${limits.maxDepth}`);
1783
+ }
1784
+ }
1785
+ if (snapshot.cursor !== void 0) {
1786
+ const { row, column } = snapshot.cursor;
1787
+ if (row >= snapshot.rows || column >= snapshot.columns) {
1788
+ return fail3("bad-rect", `cursor (${row}, ${column}) lies outside the viewport`);
1789
+ }
1790
+ }
1791
+ return { ok: true, snapshot };
1792
+ }
1793
+
1794
+ // src/messages.ts
1551
1795
  var MAX_IDENTIFIER_LENGTH = 1024;
1552
- var identifier = z5.string().max(MAX_IDENTIFIER_LENGTH);
1796
+ var identifier = z4.string().max(MAX_IDENTIFIER_LENGTH);
1553
1797
  var nonEmptyIdentifier = identifier.min(1);
1554
- var safeIndex = z5.number().refine((n) => Number.isSafeInteger(n) && n >= 0, "expected a non-negative safe integer");
1555
- var revisionNumber = z5.number().refine((n) => Number.isSafeInteger(n) && n > 0, "expected a positive safe integer");
1556
- var limitsSchema = z5.object({
1798
+ var safeIndex = z4.number().refine((n) => Number.isSafeInteger(n) && n >= 0, "expected a non-negative safe integer");
1799
+ var revisionNumber = z4.number().refine((n) => Number.isSafeInteger(n) && n > 0, "expected a positive safe integer");
1800
+ var limitsSchema = z4.object({
1557
1801
  maxFrameBytes: revisionNumber,
1558
1802
  maxSnapshotBytes: revisionNumber,
1559
1803
  maxNodes: revisionNumber,
@@ -1567,64 +1811,87 @@ var limitsSchema = z5.object({
1567
1811
  maxLogQueue: revisionNumber
1568
1812
  });
1569
1813
  var errorFields = {
1570
- type: z5.literal("error"),
1571
- code: z5.enum(["bad-token", "bad-version", "malformed", "limit-exceeded", "internal"]),
1572
- message: z5.string().max(MAX_IDENTIFIER_LENGTH)
1814
+ type: z4.literal("error"),
1815
+ code: z4.enum([
1816
+ "bad-token",
1817
+ "bad-version",
1818
+ "malformed",
1819
+ "limit-exceeded",
1820
+ "duplicate-semantic-key",
1821
+ "adapter-guarantee-violation",
1822
+ "capability-provider-violation",
1823
+ "internal"
1824
+ ]),
1825
+ message: z4.string().max(MAX_IDENTIFIER_LENGTH)
1573
1826
  };
1574
- var errorSchema = z5.strictObject(errorFields);
1575
- var errorFromDriverSchema = z5.object(errorFields);
1576
- var helloSchema = z5.strictObject({
1577
- type: z5.literal("hello"),
1578
- protocol: z5.union([z5.literal(PROTOCOL_ID), z5.literal(PROTOCOL_V2_ID)]),
1827
+ var errorSchema = z4.strictObject(errorFields);
1828
+ var errorFromDriverSchema = z4.object(errorFields);
1829
+ var helloSchema = z4.strictObject({
1830
+ type: z4.literal("hello"),
1831
+ protocol: z4.literal(PROTOCOL_ID),
1579
1832
  token: nonEmptyIdentifier,
1580
- adapter: z5.strictObject({ name: nonEmptyIdentifier, version: nonEmptyIdentifier }),
1581
- capabilities: z5.array(z5.enum(ADAPTER_CAPABILITIES)).max(ADAPTER_CAPABILITIES.length),
1582
- probe: probeInfoSchema.optional()
1833
+ adapter: z4.strictObject({
1834
+ name: nonEmptyIdentifier,
1835
+ version: nonEmptyIdentifier
1836
+ }),
1837
+ capabilities: z4.array(z4.enum(ADAPTER_CAPABILITIES)).max(ADAPTER_CAPABILITIES.length),
1838
+ probe: probeInfoSchema.optional(),
1839
+ providers: z4.array(
1840
+ z4.strictObject({
1841
+ id: nonEmptyIdentifier,
1842
+ version: nonEmptyIdentifier,
1843
+ method: z4.enum(["native", "declared"]),
1844
+ capabilities: z4.array(z4.enum(EVIDENCE_PROVIDER_CAPABILITIES)).min(1).max(EVIDENCE_PROVIDER_CAPABILITIES.length).refine(
1845
+ (values) => new Set(values).size === values.length,
1846
+ "provider capabilities must be unique"
1847
+ )
1848
+ })
1849
+ ).max(64).superRefine((providers, ctx) => {
1850
+ const ids = /* @__PURE__ */ new Set();
1851
+ for (let index = 0; index < providers.length; index += 1) {
1852
+ const provider = providers[index];
1853
+ const id = provider.id;
1854
+ if (ids.has(id)) {
1855
+ ctx.addIssue({
1856
+ code: "custom",
1857
+ path: [index, "id"],
1858
+ message: `duplicate provider id ${id}`
1859
+ });
1860
+ return;
1861
+ }
1862
+ ids.add(id);
1863
+ }
1864
+ }).optional()
1583
1865
  });
1584
- var frameBeginSchema = z5.strictObject({
1585
- type: z5.literal("frame-begin"),
1866
+ var frameBeginSchema = z4.strictObject({
1867
+ type: z4.literal("frame-begin"),
1586
1868
  revision: revisionNumber
1587
1869
  });
1588
- var revisionCommitSchema = z5.strictObject({
1589
- type: z5.literal("revision-commit"),
1870
+ var revisionCommitSchema = z4.strictObject({
1871
+ type: z4.literal("revision-commit"),
1590
1872
  revision: revisionNumber
1591
1873
  });
1592
- var snapshotEnvelopeSchema = z5.strictObject({
1593
- type: z5.literal("snapshot"),
1594
- snapshot: z5.unknown()
1874
+ var snapshotEnvelopeSchema = z4.strictObject({
1875
+ type: z4.literal("snapshot"),
1876
+ snapshot: z4.unknown()
1595
1877
  });
1596
- var treeDeltaTypeSchema = z5.object({ type: z5.literal("tree-delta") });
1597
- var logEnvelopeSchema = z5.strictObject({
1598
- type: z5.literal("log"),
1599
- record: z5.unknown()
1878
+ var logEnvelopeSchema = z4.strictObject({
1879
+ type: z4.literal("log"),
1880
+ record: z4.unknown()
1600
1881
  });
1601
- var getTreeResultSchema = z5.strictObject({
1602
- type: z5.literal("get-tree-result"),
1603
- requestId: safeIndex,
1604
- snapshot: z5.unknown().optional(),
1605
- error: z5.string().max(MAX_IDENTIFIER_LENGTH).optional()
1606
- }).refine(
1607
- (m) => m.snapshot === void 0 !== (m.error === void 0),
1608
- "exactly one of snapshot or error must be present"
1609
- );
1610
- var helloAckSchema = z5.object({
1611
- type: z5.literal("hello-ack"),
1612
- protocol: z5.union([z5.literal(PROTOCOL_ID), z5.literal(PROTOCOL_V2_ID)]),
1882
+ var helloAckSchema = z4.object({
1883
+ type: z4.literal("hello-ack"),
1884
+ protocol: z4.literal(PROTOCOL_ID),
1613
1885
  sessionId: nonEmptyIdentifier,
1614
1886
  limits: limitsSchema,
1615
- subscribe: z5.enum(["snapshots", "revisions", "diffs"]),
1616
- marker: z5.object({ enabled: z5.boolean() }),
1617
- logs: z5.object({
1618
- enabled: z5.boolean(),
1887
+ subscribe: z4.enum(["snapshots", "revisions"]),
1888
+ marker: z4.object({ enabled: z4.boolean() }),
1889
+ logs: z4.object({
1890
+ enabled: z4.boolean(),
1619
1891
  maxRecordsPerSecond: revisionNumber,
1620
1892
  burst: safeIndex
1621
1893
  }).optional()
1622
1894
  });
1623
- var getTreeRequestSchema = z5.object({
1624
- type: z5.literal("get-tree"),
1625
- requestId: safeIndex,
1626
- revision: revisionNumber.optional()
1627
- });
1628
1895
  function malformed(detail) {
1629
1896
  return { ok: false, code: "malformed", detail };
1630
1897
  }
@@ -1654,7 +1921,7 @@ function checkSnapshot(value, limits) {
1654
1921
  const overCapacity = result.code === "bytes" || result.code === "count" || result.code === "depth" || result.code === "string-bytes";
1655
1922
  return {
1656
1923
  ok: false,
1657
- code: overCapacity ? "limit-exceeded" : "malformed",
1924
+ code: overCapacity ? "limit-exceeded" : result.code === "provider" ? "capability-provider-violation" : "malformed",
1658
1925
  detail: `snapshot ${result.code}: ${result.detail}`
1659
1926
  };
1660
1927
  }
@@ -1668,16 +1935,6 @@ function checkLogRecord(value, limits) {
1668
1935
  detail: `log record ${result.code}: ${result.detail}`
1669
1936
  };
1670
1937
  }
1671
- function checkTreeDelta(value, limits) {
1672
- const result = validateTreeDelta(value, limits);
1673
- if (result.ok) return null;
1674
- const overCapacity = result.code === "bytes" || result.code === "count" || result.code === "depth" || result.code === "string-bytes";
1675
- return {
1676
- ok: false,
1677
- code: overCapacity ? "limit-exceeded" : "malformed",
1678
- detail: `tree delta ${result.code}: ${result.detail}`
1679
- };
1680
- }
1681
1938
  function parseAdapterMessage(value, limits) {
1682
1939
  const projected = project(value, limits);
1683
1940
  if (!projected.ok) return projected;
@@ -1685,21 +1942,15 @@ function parseAdapterMessage(value, limits) {
1685
1942
  switch (messageType(dto)) {
1686
1943
  case "hello": {
1687
1944
  const protocol = dto.protocol;
1688
- if (typeof protocol === "string" && protocol !== PROTOCOL_ID && protocol !== PROTOCOL_V2_ID) {
1689
- return { ok: false, code: "bad-version", detail: `unsupported protocol ${protocol}` };
1945
+ if (typeof protocol === "string" && protocol !== PROTOCOL_ID) {
1946
+ return {
1947
+ ok: false,
1948
+ code: "bad-version",
1949
+ detail: `unsupported protocol ${protocol}`
1950
+ };
1690
1951
  }
1691
1952
  const issue = check(helloSchema, dto);
1692
1953
  if (issue !== null) return malformed(issue);
1693
- const candidate = dto;
1694
- const qualified = candidate.capabilities.includes("qualified-observations");
1695
- if (candidate.protocol === PROTOCOL_V2_ID !== qualified) {
1696
- return malformed(
1697
- candidate.protocol === PROTOCOL_V2_ID ? "termwright/2 requires the 'qualified-observations' capability" : "'qualified-observations' requires termwright/2"
1698
- );
1699
- }
1700
- if (candidate.capabilities.includes("pointer-hit-grid") && !qualified) {
1701
- return malformed("'pointer-hit-grid' requires qualified observations");
1702
- }
1703
1954
  const probe = dto.probe;
1704
1955
  if (probe !== void 0) {
1705
1956
  const checked = validateProbeInfo(probe);
@@ -1717,27 +1968,10 @@ function parseAdapterMessage(value, limits) {
1717
1968
  const bad = checkSnapshot(dto.snapshot, limits);
1718
1969
  return bad ?? { ok: true, message: dto };
1719
1970
  }
1720
- case "get-tree-result": {
1721
- const issue = check(getTreeResultSchema, dto);
1722
- if (issue !== null) return malformed(issue);
1723
- const envelope = dto;
1724
- if (envelope.snapshot !== void 0) {
1725
- const bad = checkSnapshot(envelope.snapshot, limits);
1726
- if (bad !== null) return bad;
1727
- }
1728
- return { ok: true, message: dto };
1729
- }
1730
1971
  case "frame-begin": {
1731
1972
  const issue = check(frameBeginSchema, dto);
1732
1973
  return issue === null ? { ok: true, message: dto } : malformed(issue);
1733
1974
  }
1734
- case "tree-delta": {
1735
- const issue = check(treeDeltaTypeSchema, dto);
1736
- if (issue !== null) return malformed(issue);
1737
- const { type: _type, ...body } = dto;
1738
- const bad = checkTreeDelta(body, limits);
1739
- return bad ?? { ok: true, message: dto };
1740
- }
1741
1975
  case "log": {
1742
1976
  const issue = check(logEnvelopeSchema, dto);
1743
1977
  if (issue !== null) return malformed(issue);
@@ -1759,16 +1993,16 @@ function parseDriverMessage(value, limits) {
1759
1993
  switch (messageType(dto)) {
1760
1994
  case "hello-ack": {
1761
1995
  const protocol = dto.protocol;
1762
- if (typeof protocol === "string" && protocol !== PROTOCOL_ID && protocol !== PROTOCOL_V2_ID) {
1763
- return { ok: false, code: "bad-version", detail: `unsupported protocol ${protocol}` };
1996
+ if (typeof protocol === "string" && protocol !== PROTOCOL_ID) {
1997
+ return {
1998
+ ok: false,
1999
+ code: "bad-version",
2000
+ detail: `unsupported protocol ${protocol}`
2001
+ };
1764
2002
  }
1765
2003
  const issue = check(helloAckSchema, dto);
1766
2004
  return issue === null ? { ok: true, message: dto } : malformed(issue);
1767
2005
  }
1768
- case "get-tree": {
1769
- const issue = check(getTreeRequestSchema, dto);
1770
- return issue === null ? { ok: true, message: dto } : malformed(issue);
1771
- }
1772
2006
  case "error": {
1773
2007
  const issue = check(errorFromDriverSchema, dto);
1774
2008
  return issue === null ? { ok: true, message: dto } : malformed(issue);
@@ -1779,7 +2013,7 @@ function parseDriverMessage(value, limits) {
1779
2013
  }
1780
2014
 
1781
2015
  // src/marker.ts
1782
- import { Buffer as Buffer6 } from "buffer";
2016
+ import { Buffer as Buffer5 } from "buffer";
1783
2017
  import { createHmac, timingSafeEqual } from "crypto";
1784
2018
  var MARKER_OSC_CODE = 8487;
1785
2019
  var MARKER_OSC_PREFIX = "twm;";
@@ -1820,24 +2054,74 @@ function verifyMarkerPayload(payload, token, sessionId) {
1820
2054
  if (!MAC_TEXT.test(mac)) return null;
1821
2055
  const revision = Number(revisionText);
1822
2056
  if (!Number.isSafeInteger(revision) || revision <= 0) return null;
1823
- const expected = Buffer6.from(computeMac(token, sessionId, revision), "utf8");
1824
- const actual = Buffer6.from(mac, "utf8");
2057
+ const expected = Buffer5.from(computeMac(token, sessionId, revision), "utf8");
2058
+ const actual = Buffer5.from(mac, "utf8");
1825
2059
  if (expected.length !== actual.length) return null;
1826
2060
  if (!timingSafeEqual(expected, actual)) return null;
1827
2061
  return Object.freeze({ revision, mac });
1828
2062
  }
2063
+
2064
+ // src/conpty-host-rpc.ts
2065
+ var CONPTY_HOST_CURSOR_OSC_CODE = 8488;
2066
+ var CONPTY_HOST_CURSOR_PREFIX = "twh-cpr-v1";
2067
+ var TOKEN = "[0-9a-f]{32}";
2068
+ var REQUEST = new RegExp(`^${CONPTY_HOST_CURSOR_PREFIX}:q:(${TOKEN})$`, "u");
2069
+ var RESPONSE = new RegExp(
2070
+ `^\\x1b\\]${CONPTY_HOST_CURSOR_OSC_CODE};${CONPTY_HOST_CURSOR_PREFIX}:r:(${TOKEN}):([1-9][0-9]{0,4}):([1-9][0-9]{0,4})\\x07$`,
2071
+ "u"
2072
+ );
2073
+ var MAX_COORDINATE = 32768;
2074
+ function parseConPtyHostCursorRequest(payload) {
2075
+ const match = REQUEST.exec(payload);
2076
+ return match?.[1] === void 0 ? null : { token: match[1] };
2077
+ }
2078
+ function encodeConPtyHostCursorResponse(request, row, column) {
2079
+ if (!new RegExp(`^${TOKEN}$`, "u").test(request.token)) {
2080
+ throw new TypeError("ConPTY host cursor token must be 128-bit lowercase hexadecimal");
2081
+ }
2082
+ for (const [name, value] of [
2083
+ ["row", row],
2084
+ ["column", column]
2085
+ ]) {
2086
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_COORDINATE) {
2087
+ throw new RangeError(`ConPTY host cursor ${name} must be an integer from 1 to 32768`);
2088
+ }
2089
+ }
2090
+ return `\x1B]${CONPTY_HOST_CURSOR_OSC_CODE};${CONPTY_HOST_CURSOR_PREFIX}:r:${request.token}:${row}:${column}\x07`;
2091
+ }
2092
+ function parseConPtyHostCursorResponse(value) {
2093
+ const text = typeof value === "string" ? value : new TextDecoder("ascii", { fatal: true }).decode(value);
2094
+ const match = RESPONSE.exec(text);
2095
+ if (match?.[1] === void 0 || match[2] === void 0 || match[3] === void 0) return null;
2096
+ const row = Number(match[2]);
2097
+ const column = Number(match[3]);
2098
+ if (row > MAX_COORDINATE || column > MAX_COORDINATE) return null;
2099
+ return { token: match[1], row, column };
2100
+ }
1829
2101
  export {
1830
2102
  ABSOLUTE_LIMITS,
1831
2103
  ACCESSKIT_ROLE_BY_SEMANTIC_ROLE,
1832
2104
  ACCESSKIT_ROOT_TREE_ID,
1833
2105
  ADAPTER_CAPABILITIES,
2106
+ ARTIFACT_VALUE_POLICIES,
2107
+ CAPABILITY_CONFORMANCE_CLAIMS,
2108
+ CAPABILITY_EDGE_KINDS,
2109
+ CAPABILITY_GRAPH,
2110
+ CAPABILITY_GRAPH_VERSION,
2111
+ CAPABILITY_NODE_CATEGORIES,
2112
+ CAPABILITY_NODE_LAYERS,
2113
+ CONDITION_KINDS,
2114
+ CONPTY_HOST_CURSOR_OSC_CODE,
2115
+ CONPTY_HOST_CURSOR_PREFIX,
2116
+ DEFAULT_ARTIFACT_VALUE_POLICY,
1834
2117
  DEFAULT_LIMITS,
1835
2118
  DEFAULT_NEGOTIATION_MS,
2119
+ DEFAULT_RUN_EVENT_JOURNAL_LIMITS,
2120
+ DEFAULT_RUN_EVENT_LIMITS,
1836
2121
  ENV_ENDPOINT,
1837
- ENV_PROTOCOL,
1838
2122
  ENV_TOKEN,
1839
- FRAMEWORK_OBSERVATION_CAPABILITIES,
1840
- FRAMEWORK_OPERATION_CAPABILITIES,
2123
+ EVIDENCE_PROVIDER_CAPABILITIES,
2124
+ EVIDENCE_PROVIDER_TYPES,
1841
2125
  FRAME_HEADER_BYTES,
1842
2126
  LOG_LEVELS,
1843
2127
  LOG_LEVEL_SEVERITY,
@@ -1845,41 +2129,78 @@ export {
1845
2129
  MARKER_OSC_CODE,
1846
2130
  MARKER_OSC_PREFIX,
1847
2131
  MAX_LOG_ATTRS,
2132
+ PHYSICAL_INPUT_RECIPE_ACTIONS,
1848
2133
  PROBE_CAPABILITIES,
2134
+ PROBE_DEGRADED_CAPABILITIES,
2135
+ PROBE_INJECTION_TIERS,
2136
+ PROBE_SEMANTIC_CLASSES,
1849
2137
  PROBE_UNOBSERVABLE_FIELDS,
1850
2138
  PROTOCOL_ID,
1851
- PROTOCOL_V2_ID,
1852
2139
  PROTOCOL_VERSION,
1853
2140
  PROVENANCE_SOURCES,
2141
+ PUBLIC_CAPABILITY_CONSUMERS,
1854
2142
  ProtocolViolation,
2143
+ RUNTIME_PREREQUISITES,
2144
+ RUN_EVENT_CLASSES,
2145
+ RUN_EVENT_VERSION,
2146
+ RUN_ID_KINDS,
2147
+ RUN_STATES,
2148
+ RUN_STATE_TRANSITIONS,
2149
+ RunEventJournal,
2150
+ RunEventProducer,
2151
+ RunEventStreamValidator,
2152
+ RunIdFactory,
1855
2153
  SEMANTIC_ACTIONS,
1856
2154
  SEMANTIC_NODE_KEYS,
1857
2155
  SEMANTIC_ROLES,
1858
2156
  SEMANTIC_STATE_KEYS,
1859
- SUPPORTED_PROTOCOL_IDS,
2157
+ SESSION_CAPABILITIES,
2158
+ TERMINAL_RUN_STATES,
1860
2159
  TOKEN_BYTES,
1861
2160
  accessKitNodeId,
1862
- applyTreeDelta,
2161
+ canTransitionRunState,
2162
+ capabilityNode,
2163
+ capabilityRemediation,
1863
2164
  createFrameDecoder,
2165
+ createRunEvent,
2166
+ createRunId,
2167
+ encodeConPtyHostCursorResponse,
1864
2168
  encodeFrame,
1865
2169
  encodeMarker,
1866
- frameworkObservationCapabilities,
2170
+ evidence,
2171
+ executableText,
1867
2172
  generateToken,
1868
2173
  intersectRects,
2174
+ isRunState,
2175
+ isTerminalRunState,
1869
2176
  parseAdapterMessage,
2177
+ parseConPtyHostCursorRequest,
2178
+ parseConPtyHostCursorResponse,
1870
2179
  parseDriverMessage,
2180
+ parseRunId,
1871
2181
  probeInfoSchema,
2182
+ projectActionReceiptForArtifact,
1872
2183
  projectDto,
2184
+ projectSemanticSnapshotForArtifact,
2185
+ publicValue,
2186
+ recordActionPlan,
2187
+ recordDeviceOperation,
2188
+ recordValue,
1873
2189
  rectArea,
1874
- resolveNodeBounds,
2190
+ resolveCapability,
2191
+ sensitive,
2192
+ sessionCapabilitiesFromProducers,
1875
2193
  spatialRelation,
1876
2194
  toAccessKitTreeUpdate,
2195
+ validateCapabilityGraph,
1877
2196
  validateLogRecord,
1878
2197
  validateProbeAnnotations,
1879
2198
  validateProbeFrame,
1880
2199
  validateProbeInfo,
2200
+ validateRunEvent,
2201
+ validateRunStateTransition,
1881
2202
  validateSnapshot,
1882
- validateTreeDelta,
2203
+ valueSensitivity,
1883
2204
  verifyMarkerPayload,
1884
2205
  viewportIntersection
1885
2206
  };