@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.
- package/README.md +213 -605
- package/dist/action-model-BP9Znu6L.d.ts +219 -0
- package/dist/action-model.d.ts +3 -0
- package/dist/action-model.js +15 -0
- package/dist/action-model.js.map +1 -0
- package/dist/capability-graph.d.ts +90 -0
- package/dist/capability-graph.js +43 -0
- package/dist/capability-graph.js.map +1 -0
- package/dist/chunk-B4VUTTUE.js +59 -0
- package/dist/chunk-B4VUTTUE.js.map +1 -0
- package/dist/chunk-CZK6NNP3.js +389 -0
- package/dist/chunk-CZK6NNP3.js.map +1 -0
- package/dist/chunk-ODOJRXL6.js +84 -0
- package/dist/chunk-ODOJRXL6.js.map +1 -0
- package/dist/chunk-PUXRCPGY.js +112 -0
- package/dist/chunk-PUXRCPGY.js.map +1 -0
- package/dist/chunk-VBLS6E6U.js +1109 -0
- package/dist/chunk-VBLS6E6U.js.map +1 -0
- package/dist/chunk-ZZIYHDJ4.js +202 -0
- package/dist/chunk-ZZIYHDJ4.js.map +1 -0
- package/dist/contract-CH9gmj2Y.d.ts +746 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +21 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +82 -798
- package/dist/index.js +1087 -766
- package/dist/index.js.map +1 -1
- package/dist/run-events.d.ts +158 -0
- package/dist/run-events.js +27 -0
- package/dist/run-events.js.map +1 -0
- package/dist/run-journal.d.ts +55 -0
- package/dist/run-journal.js +10 -0
- package/dist/run-journal.js.map +1 -0
- package/dist/run-state.d.ts +38 -0
- package/dist/run-state.js +19 -0
- package/dist/run-state.js.map +1 -0
- package/dist/test-provider.d.ts +22 -0
- package/dist/test-provider.js +32 -0
- package/dist/test-provider.js.map +1 -0
- 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
|
|
6
|
-
var
|
|
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 =
|
|
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({
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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 =
|
|
611
|
+
const snapshot = snapshotV2;
|
|
405
612
|
return {
|
|
406
613
|
text,
|
|
407
|
-
node,
|
|
614
|
+
node: nodeV2,
|
|
408
615
|
cursor,
|
|
409
616
|
snapshot,
|
|
410
|
-
|
|
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/
|
|
789
|
-
import {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
|
813
|
-
|
|
814
|
-
return
|
|
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
|
|
817
|
-
if (
|
|
818
|
-
return
|
|
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
|
|
821
|
-
if (
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
|
1049
|
+
return mapped.size === 0 ? void 0 : [...mapped];
|
|
868
1050
|
}
|
|
869
|
-
function
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
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
|
|
1074
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
918
1075
|
for (const node of snapshot.nodes) {
|
|
919
|
-
if (
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
...
|
|
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
|
|
1304
|
-
import { z as
|
|
1305
|
-
function
|
|
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 =
|
|
1309
|
-
var nonNegative =
|
|
1310
|
-
var positive =
|
|
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 =
|
|
1314
|
-
(s) =>
|
|
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 =
|
|
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 =
|
|
1324
|
-
kind:
|
|
1156
|
+
const identity = z2.strictObject({
|
|
1157
|
+
kind: z2.enum(["stable", "frame-local"]),
|
|
1325
1158
|
value: text.min(1)
|
|
1326
1159
|
});
|
|
1327
|
-
const extendedValue =
|
|
1328
|
-
() =>
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
1337
|
-
|
|
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 =
|
|
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 =
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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:
|
|
1361
|
-
scroll:
|
|
1362
|
-
scrollExtent:
|
|
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 =
|
|
1232
|
+
const object = z2.strictObject({
|
|
1365
1233
|
identity,
|
|
1366
1234
|
frameworkType: text.min(1),
|
|
1367
1235
|
parent: text.optional(),
|
|
1368
|
-
geometry:
|
|
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:
|
|
1372
|
-
|
|
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:
|
|
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:
|
|
1270
|
+
unobservable: z2.array(z2.enum(PROBE_UNOBSERVABLE_FIELDS)).max(PROBE_UNOBSERVABLE_FIELDS.length).optional()
|
|
1384
1271
|
});
|
|
1385
|
-
const operation =
|
|
1386
|
-
kind:
|
|
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
|
|
1279
|
+
return z2.strictObject({
|
|
1393
1280
|
frame: positive,
|
|
1394
|
-
objects:
|
|
1395
|
-
operations:
|
|
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 =
|
|
1406
|
-
framework:
|
|
1407
|
-
frameworkVersion:
|
|
1408
|
-
probeVersion:
|
|
1409
|
-
identityKind:
|
|
1410
|
-
capabilities:
|
|
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 {
|
|
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
|
|
1348
|
+
return fail2(error.code === "dto-depth" ? "depth" : "schema", error.message);
|
|
1435
1349
|
}
|
|
1436
|
-
return
|
|
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
|
|
1440
|
-
const bytes =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
"
|
|
1548
|
-
"
|
|
1549
|
-
"
|
|
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 =
|
|
1796
|
+
var identifier = z4.string().max(MAX_IDENTIFIER_LENGTH);
|
|
1553
1797
|
var nonEmptyIdentifier = identifier.min(1);
|
|
1554
|
-
var safeIndex =
|
|
1555
|
-
var revisionNumber =
|
|
1556
|
-
var limitsSchema =
|
|
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:
|
|
1571
|
-
code:
|
|
1572
|
-
|
|
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 =
|
|
1575
|
-
var errorFromDriverSchema =
|
|
1576
|
-
var helloSchema =
|
|
1577
|
-
type:
|
|
1578
|
-
protocol:
|
|
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:
|
|
1581
|
-
|
|
1582
|
-
|
|
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 =
|
|
1585
|
-
type:
|
|
1866
|
+
var frameBeginSchema = z4.strictObject({
|
|
1867
|
+
type: z4.literal("frame-begin"),
|
|
1586
1868
|
revision: revisionNumber
|
|
1587
1869
|
});
|
|
1588
|
-
var revisionCommitSchema =
|
|
1589
|
-
type:
|
|
1870
|
+
var revisionCommitSchema = z4.strictObject({
|
|
1871
|
+
type: z4.literal("revision-commit"),
|
|
1590
1872
|
revision: revisionNumber
|
|
1591
1873
|
});
|
|
1592
|
-
var snapshotEnvelopeSchema =
|
|
1593
|
-
type:
|
|
1594
|
-
snapshot:
|
|
1874
|
+
var snapshotEnvelopeSchema = z4.strictObject({
|
|
1875
|
+
type: z4.literal("snapshot"),
|
|
1876
|
+
snapshot: z4.unknown()
|
|
1595
1877
|
});
|
|
1596
|
-
var
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
record: z5.unknown()
|
|
1878
|
+
var logEnvelopeSchema = z4.strictObject({
|
|
1879
|
+
type: z4.literal("log"),
|
|
1880
|
+
record: z4.unknown()
|
|
1600
1881
|
});
|
|
1601
|
-
var
|
|
1602
|
-
type:
|
|
1603
|
-
|
|
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:
|
|
1616
|
-
marker:
|
|
1617
|
-
logs:
|
|
1618
|
-
enabled:
|
|
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
|
|
1689
|
-
return {
|
|
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
|
|
1763
|
-
return {
|
|
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
|
|
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 =
|
|
1824
|
-
const actual =
|
|
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
|
-
|
|
1840
|
-
|
|
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
|
-
|
|
2157
|
+
SESSION_CAPABILITIES,
|
|
2158
|
+
TERMINAL_RUN_STATES,
|
|
1860
2159
|
TOKEN_BYTES,
|
|
1861
2160
|
accessKitNodeId,
|
|
1862
|
-
|
|
2161
|
+
canTransitionRunState,
|
|
2162
|
+
capabilityNode,
|
|
2163
|
+
capabilityRemediation,
|
|
1863
2164
|
createFrameDecoder,
|
|
2165
|
+
createRunEvent,
|
|
2166
|
+
createRunId,
|
|
2167
|
+
encodeConPtyHostCursorResponse,
|
|
1864
2168
|
encodeFrame,
|
|
1865
2169
|
encodeMarker,
|
|
1866
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2203
|
+
valueSensitivity,
|
|
1883
2204
|
verifyMarkerPayload,
|
|
1884
2205
|
viewportIntersection
|
|
1885
2206
|
};
|