@qontinui/ui-bridge-native 0.6.11 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +36 -16
  2. package/dist/control/action-executor.d.ts +42 -2
  3. package/dist/control/action-executor.d.ts.map +1 -1
  4. package/dist/control/index.js +723 -4
  5. package/dist/control/index.js.map +1 -1
  6. package/dist/control/index.mjs +723 -5
  7. package/dist/control/index.mjs.map +1 -1
  8. package/dist/control/types.d.ts +82 -1
  9. package/dist/control/types.d.ts.map +1 -1
  10. package/dist/core/abortable.d.ts +151 -0
  11. package/dist/core/abortable.d.ts.map +1 -0
  12. package/dist/core/action-effect.d.ts +74 -0
  13. package/dist/core/action-effect.d.ts.map +1 -0
  14. package/dist/core/element-actions.d.ts +38 -0
  15. package/dist/core/element-actions.d.ts.map +1 -0
  16. package/dist/core/index.d.ts +4 -0
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +853 -15
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/index.mjs +835 -16
  21. package/dist/core/index.mjs.map +1 -1
  22. package/dist/core/param-schema.d.ts +282 -0
  23. package/dist/core/param-schema.d.ts.map +1 -0
  24. package/dist/core/registry.d.ts +317 -2
  25. package/dist/core/registry.d.ts.map +1 -1
  26. package/dist/core/types.d.ts +231 -32
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/debug/index.js.map +1 -1
  29. package/dist/debug/index.mjs.map +1 -1
  30. package/dist/index.d.ts +10 -5
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1603 -118
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +1585 -119
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/react/UIBridgeNativeProvider.d.ts +1 -23
  37. package/dist/react/UIBridgeNativeProvider.d.ts.map +1 -1
  38. package/dist/react/index.js +1420 -113
  39. package/dist/react/index.js.map +1 -1
  40. package/dist/react/index.mjs +1420 -113
  41. package/dist/react/index.mjs.map +1 -1
  42. package/dist/react/useUIBridge.d.ts +19 -22
  43. package/dist/react/useUIBridge.d.ts.map +1 -1
  44. package/dist/react/useUIComponent.d.ts +113 -8
  45. package/dist/react/useUIComponent.d.ts.map +1 -1
  46. package/dist/react/useUIElement.d.ts +34 -8
  47. package/dist/react/useUIElement.d.ts.map +1 -1
  48. package/dist/server/handlers.d.ts.map +1 -1
  49. package/dist/server/http-server.d.ts +15 -2
  50. package/dist/server/http-server.d.ts.map +1 -1
  51. package/dist/server/index.js +348 -43
  52. package/dist/server/index.js.map +1 -1
  53. package/dist/server/index.mjs +348 -44
  54. package/dist/server/index.mjs.map +1 -1
  55. package/dist/server/observability.d.ts +40 -0
  56. package/dist/server/observability.d.ts.map +1 -1
  57. package/dist/server/types.d.ts +57 -1
  58. package/dist/server/types.d.ts.map +1 -1
  59. package/dist/transport/CloudRelayClient.d.ts.map +1 -1
  60. package/dist/transport/DeviceAnnouncer.d.ts.map +1 -1
  61. package/dist/transport/relay-logging.d.ts +50 -0
  62. package/dist/transport/relay-logging.d.ts.map +1 -0
  63. package/package.json +2 -2
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Parameter-schema validation for component actions.
3
+ *
4
+ * Plan `2026-08-20-ui-bridge-action-declaration-shape`, Phase 2.
5
+ *
6
+ * `ComponentAction.paramSchema` is **published to agents** — it is spread onto
7
+ * `/control/components` and `/control/component/:id` by
8
+ * `server/handlers.ts` `annotateComponentWithInvocationPaths`, and four live
9
+ * runner consumers read it off the wire. Until Phase 2 nothing enforced it, so
10
+ * an agent that read the schema and sent conforming params had no guarantee
11
+ * the handler agreed. That is worse than publishing nothing: it invites trust
12
+ * it cannot honour. This module is what makes the declaration enforceable.
13
+ *
14
+ * ---------------------------------------------------------------------------
15
+ * THE SUPPORTED SUBSET — this is a CROSS-REPO CONTRACT
16
+ * ---------------------------------------------------------------------------
17
+ *
18
+ * `qontinui-runner` reads these same schemas
19
+ * (`workflow_generation/wrapper_manifest.rs`, `commands/command_interpreter.rs`,
20
+ * `bin/wrappers_mcp.rs`), and `command_interpreter.rs` feeds one to an LLM and
21
+ * asks it to produce conforming args. So the grammar below is not a private
22
+ * implementation detail — changing it changes what those consumers may emit.
23
+ *
24
+ * **No dependency.** `@qontinui/ui-bridge` ships to browsers and has exactly
25
+ * one runtime dependency (`dom-accessibility-api`). A full JSON Schema
26
+ * validator (`ajv` and friends) would be the SDK's first heavyweight runtime
27
+ * dep, which the plan rejected on bundle size. Hence a hand-implemented subset,
28
+ * documented here rather than inferred from the code.
29
+ *
30
+ * **Two accepted top-level shapes**, because `useUIComponent`'s own author-
31
+ * facing doc has always sanctioned both:
32
+ *
33
+ * 1. **Object-schema form** — the shape `paramSchemaOf()`
34
+ * (`@qontinui/ui-bridge-wrapper`) emits:
35
+ *
36
+ * ```jsonc
37
+ * { "type": "object",
38
+ * "properties": { "username": { "type": "string" },
39
+ * "remember": { "type": "boolean" } },
40
+ * "required": ["username"],
41
+ * "additionalProperties": false }
42
+ * ```
43
+ *
44
+ * 2. **Map form** — the lightweight shorthand the doc describes as
45
+ * "a map of `{ paramName: "string" | "number" | ... }`":
46
+ *
47
+ * ```jsonc
48
+ * { "username": "string", "count": { "type": "integer", "minimum": 1 } }
49
+ * ```
50
+ *
51
+ * **Disambiguation rule** (deterministic, no heuristics). The schema is read as
52
+ * form 1 **iff** either:
53
+ *
54
+ * - it carries a `properties` object; or
55
+ * - `type === "object"` **and every other top-level key is a JSON Schema
56
+ * object-applicable keyword** ({@link OBJECT_SCHEMA_KEYWORDS}).
57
+ *
58
+ * Everything else is form 2. So `{ "type": "string" }` is a map declaring one
59
+ * param named `type` of type `string`, not a top-level string schema — a
60
+ * params bag is always an object, so a non-object top-level `type` cannot be
61
+ * describing the bag.
62
+ *
63
+ * ⚠️ **The second clause is load-bearing, and a naive `type === "object"` test
64
+ * is a silent-validation-loss bug.** Consider the map form
65
+ * `{ "type": "object", "id": "string" }` — an action taking a param literally
66
+ * named `type` whose hint is `"object"`, plus an `id`. Reading that as form 1
67
+ * would find no `properties`, express no constraint, and **silently drop every
68
+ * sibling declaration in the map**: `validateActionParams(schema, { id: 12345 })`
69
+ * would answer `{ valid: true }` for a schema that plainly says `id` is a
70
+ * string. `id` is not an object keyword, so the keyword test sends it to form 2
71
+ * and both params validate. A param named `type` is not exotic — it is one of
72
+ * the most common names there is.
73
+ *
74
+ * The one genuinely ambiguous input is a bare `{ "type": "object" }`: it is
75
+ * both an empty object schema and a one-entry map declaring a param named
76
+ * `type` of type `object`. It is read as form 1 (the empty object schema),
77
+ * which constrains nothing either way, so the choice is unobservable except in
78
+ * combination with `additionalProperties: false` — where form 1 is clearly the
79
+ * intended reading.
80
+ *
81
+ * ### Map form is a TYPE hint, never a REQUIREDNESS hint
82
+ *
83
+ * A bare string value in map form is read as a type name **only when it is one
84
+ * of the seven JSON Schema primitive names** — `string`, `number`, `integer`,
85
+ * `boolean`, `object`, `array`, `null` — which is exactly `paramSchemaOf`'s
86
+ * own `PRIMITIVE_TYPES` set. **Any other string is prose and constrains
87
+ * nothing.**
88
+ *
89
+ * That is not defensive coding, it is the corpus: a census of every literal
90
+ * `paramSchema` in the fleet (2026-08-22) found ~50 map-form declarations in
91
+ * `qontinui-runner` alone whose values are human-readable hints, not types —
92
+ * `{ count: "number (>= 1, defaults to 1)", account: "string — either a Claude
93
+ * account label … or the literal \"best\"" }`. Reading `"number (>= 1,
94
+ * defaults to 1)"` as a type name would make every one of them unvalidatable
95
+ * noise. The file that declares them says so itself: *"loose `Record<string,
96
+ * unknown>` with hint strings, no runtime validation"*.
97
+ *
98
+ * For the same reason, **map form never marks anything required.** It has no
99
+ * `required` array and no other way to express optionality, and most of those
100
+ * ~50 real declarations describe params that are explicitly optional in their
101
+ * own prose ("defaults to 1", "optional initial prompt"). Requiring by default
102
+ * would report a violation on nearly every real call. An author who needs
103
+ * required params writes form 1, which has a keyword for it.
104
+ *
105
+ * **Optionality in form 1** is exactly `required`: a property is required iff
106
+ * listed there. Note `paramSchemaOf` **omits `required` entirely** when every
107
+ * field is optional, so its absence means "nothing required", never "malformed".
108
+ *
109
+ * `optional: true` is not a JSON Schema keyword — it is this monorepo's
110
+ * convention on `paramSchemaOf`'s *input* descriptors, and `paramSchemaOf`
111
+ * strips it before the schema reaches the wire. It is honoured here (form 2
112
+ * only) purely so an unstripped input-shaped object is read the same way.
113
+ *
114
+ * **Recognised property keywords** — everything else is IGNORED:
115
+ *
116
+ * | Keyword | Applies to | Meaning |
117
+ * |---|---|---|
118
+ * | `type` | any | `"string" \| "number" \| "integer" \| "boolean" \| "object" \| "array" \| "null"`, or an array of those (union — a value matching any member passes). Any OTHER type name constrains nothing (`paramSchemaOf` passes unknown names through verbatim, so they do occur) |
119
+ * | `enum` | any | value must equal one of the listed JSON values |
120
+ * | `const` | any | value must equal this JSON value |
121
+ * | `properties` | objects | per-key sub-schemas, applied recursively |
122
+ * | `required` | objects | array of key names that must be present |
123
+ * | `additionalProperties` | objects | when exactly `false`, keys absent from `properties` are rejected — **including when the schema declares no `properties` at all**, which is the natural spelling of "takes no params". Any other value is ignored |
124
+ * | `items` | arrays | one sub-schema applied to every element, recursively |
125
+ * | `minimum` / `maximum` | numbers | inclusive bounds |
126
+ * | `minLength` / `maxLength` | strings | inclusive bounds on `.length` |
127
+ * | `pattern` | strings | ECMAScript regex, unanchored. An uncompilable **or catastrophically-backtracking** pattern is ignored, never a failure — see {@link MAX_PATTERN_LENGTH} |
128
+ *
129
+ * **⚠ Anything outside this table is IGNORED, never rejected.** `description`,
130
+ * `default`, `title`, `format`, `examples`, `$schema`, `$ref`, `oneOf`,
131
+ * `anyOf`, `allOf`, `not`, `patternProperties`, `minItems`, `exclusiveMinimum`
132
+ * … all pass silently. This is deliberate and load-bearing: a schema outside
133
+ * this subset is still *valid JSON Schema*, and rejecting an unrecognised
134
+ * keyword would convert a documentation nicety into a hard invocation failure
135
+ * for every author who wrote a richer schema than this validator reads. An
136
+ * unreadable schema means "no constraint expressed here", not "invalid".
137
+ * The same rule applies one level up: a `paramSchema` that is not a plain
138
+ * object at all (`null`, an array, a string) expresses no constraint and
139
+ * validates everything.
140
+ *
141
+ * **No coercion.** `"5"` does not satisfy `{"type":"number"}`. The runner's
142
+ * LLM router is already instructed to "coerce numeric tokens to numbers"
143
+ * (`command_interpreter.rs`), so coercing here would hide that contract rather
144
+ * than enforce it.
145
+ *
146
+ * ### A validator must return a verdict, never throw
147
+ *
148
+ * {@link validateActionParams} is called at an invocation seam whose only
149
+ * generic failure label is `UB-ACTION-FAILED` — "the handler failed". A
150
+ * validator that throws would therefore be reported as a *handler* fault, and
151
+ * the author would go debug code that never ran. Two inputs could do it, and
152
+ * both are **author-supplied schema data**, not caller params:
153
+ *
154
+ * - **A self-referential schema.** `const s = { type: 'object', properties: {} };
155
+ * s.properties.self = s;` recurses until `RangeError: Maximum call stack size
156
+ * exceeded`. Bounded by {@link MAX_SCHEMA_DEPTH}: past that depth the walk
157
+ * stops and expresses no further constraint (the ignore-don't-reject rule,
158
+ * applied to depth).
159
+ * - **A catastrophically-backtracking `pattern`.** This one does not throw at
160
+ * all — it *hangs*, which no `try`/`catch` can rescue. Guarded before
161
+ * compilation by {@link MAX_PATTERN_LENGTH} and a nested-quantifier test,
162
+ * the same pair `control/action-executor.ts` already applies to a
163
+ * caller-supplied `srcPattern`.
164
+ *
165
+ * The seams wrap the call anyway (belt and braces) and report a validator
166
+ * fault distinguishably from a handler fault — see the executors.
167
+ *
168
+ * ---------------------------------------------------------------------------
169
+ * DUPLICATE — deliberately not an import
170
+ * ---------------------------------------------------------------------------
171
+ *
172
+ * DUPLICATE OF `@qontinui/ui-bridge` `src/core/param-schema.ts` — DELIBERATELY
173
+ * NOT AN IMPORT, for the same reason as `core/abortable.ts` beside it:
174
+ * `@qontinui/ui-bridge` is an OPTIONAL peerDependency of this package and is
175
+ * absent from `dependencies`, so a consumer may install
176
+ * `@qontinui/ui-bridge-native` alone; importing from it would resolve inside
177
+ * this monorepo and then fail for that consumer.
178
+ *
179
+ * KEEP IN SYNC with `@qontinui/ui-bridge` `src/core/param-schema.ts`. The
180
+ * module has no imports and touches no DOM or Node API — only `RegExp`,
181
+ * `JSON`, `Set` and `Number`, all present in React Native. The documented
182
+ * subset above is a cross-repo contract, so the two copies drifting is a
183
+ * contract break, not a style nit.
184
+ */
185
+ /** Which rule rejected a value. Machine-readable; pairs with `message`. */
186
+ export type ParamSchemaKeyword = 'required' | 'type' | 'enum' | 'const' | 'additionalProperties' | 'minimum' | 'maximum' | 'minLength' | 'maxLength' | 'pattern';
187
+ /**
188
+ * One rejected parameter. `path` is the whole point — an agent told only
189
+ * "invalid params" cannot fix anything, so every issue names the param it is
190
+ * about.
191
+ */
192
+ export interface ParamSchemaIssue {
193
+ /**
194
+ * Location of the offending value, in dotted/bracketed form relative to the
195
+ * params object: `"username"`, `"filter.status"`, `"ids[2]"`. Empty string
196
+ * means the params object itself.
197
+ */
198
+ path: string;
199
+ /** Which rule rejected it. */
200
+ keyword: ParamSchemaKeyword;
201
+ /** What the schema demanded, rendered for a human/agent reader. */
202
+ expected: string;
203
+ /** What was actually supplied, rendered compactly. Absent for `required`. */
204
+ received?: string;
205
+ /** One-line explanation naming both the param and the reason. */
206
+ message: string;
207
+ }
208
+ /** Outcome of {@link validateActionParams}. */
209
+ export interface ParamValidationResult {
210
+ valid: boolean;
211
+ /** Empty iff `valid`. */
212
+ issues: ParamSchemaIssue[];
213
+ }
214
+ /**
215
+ * What the executor does with a schema violation.
216
+ *
217
+ * - `'enforce'` — reject the invocation before the handler runs, with
218
+ * `errorCode: 'UB-ACTION-REJECTED'` and the issue list attached.
219
+ * - `'warn'` — log the violation and invoke the handler anyway.
220
+ * - `'off'` — skip validation entirely (no schema walk, no allocation).
221
+ */
222
+ export type ParamValidationMode = 'enforce' | 'warn' | 'off';
223
+ /**
224
+ * **`'warn'`.** Decided on the plan's priority order — *powerful features →
225
+ * scalability → robustness → clean code* — not on backward compatibility,
226
+ * which is explicitly not a factor on this project.
227
+ *
228
+ * The feature Phase 2 delivers is *an enforceable declaration*, and warn mode
229
+ * delivers all of it except the refusal: the schema is walked, the violating
230
+ * param is named, and the violation is reported. Enforcement is one call to
231
+ * {@link setDefaultParamValidationMode} away, per deployment.
232
+ *
233
+ * What arming `'enforce'` by default would buy is the refusal — and the
234
+ * population it would refuse first is not attackers, it is the fleet's own
235
+ * schemas. Nothing has ever validated these declarations, so on the balance of
236
+ * evidence some existing `paramSchema` values are wrong about their own
237
+ * handlers rather than the callers being wrong about the schema; a validator
238
+ * armed on day one reports that as the caller's failure. Worse, the largest
239
+ * live producer of action params is a *model*:
240
+ * `qontinui-runner/src-tauri/src/commands/command_interpreter.rs` asks an LLM
241
+ * for "args whose keys match the chosen action's `paramSchema`", and that
242
+ * failure surfaces to a user running a slash command. Defaulting to enforce
243
+ * would convert a documentation defect into a user-visible outage on a
244
+ * population that was never measured.
245
+ *
246
+ * Robustness — third in the order, and the tiebreaker here — argues the same
247
+ * way: the robust move is to collect violations from a validator that has
248
+ * never seen production traffic before letting it refuse anything. A default
249
+ * that fails open and is *observable* beats a default that fails closed and is
250
+ * *unmeasured*.
251
+ */
252
+ export declare const DEFAULT_PARAM_VALIDATION_MODE: ParamValidationMode;
253
+ /**
254
+ * Arm or disarm validation process-wide. Call once at app boot; each
255
+ * invocation may still override it via `executeComponentAction`'s
256
+ * `{ paramValidation }` option.
257
+ */
258
+ export declare function setDefaultParamValidationMode(mode: ParamValidationMode): void;
259
+ /** The mode an invocation gets when it does not pass one. */
260
+ export declare function getDefaultParamValidationMode(): ParamValidationMode;
261
+ /** Test/teardown helper: restore {@link DEFAULT_PARAM_VALIDATION_MODE}. */
262
+ export declare function resetDefaultParamValidationMode(): void;
263
+ /**
264
+ * Validate `params` against an action's `paramSchema`.
265
+ *
266
+ * Returns `{ valid: true, issues: [] }` for anything the documented subset
267
+ * cannot read — that is the ignore-don't-reject rule, and it is why a schema
268
+ * this validator does not understand can never fail an invocation.
269
+ *
270
+ * @param schema the action's declared `paramSchema` (any shape; unreadable
271
+ * shapes constrain nothing)
272
+ * @param params the params the caller supplied. `undefined` is treated as an
273
+ * empty object, so a schema with required properties still reports them.
274
+ */
275
+ export declare function validateActionParams(schema: unknown, params: unknown): ParamValidationResult;
276
+ /**
277
+ * One-line summary of a violation set, naming the action and every offending
278
+ * param. Used as the `error` / `message` string at every invocation seam so
279
+ * the three trees phrase the rejection identically.
280
+ */
281
+ export declare function formatParamValidationFailure(componentId: string, actionId: string, issues: ParamSchemaIssue[]): string;
282
+ //# sourceMappingURL=param-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"param-schema.d.ts","sourceRoot":"","sources":["../../src/core/param-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuLG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GACV,MAAM,GACN,MAAM,GACN,OAAO,GACP,sBAAsB,GACtB,SAAS,GACT,SAAS,GACT,WAAW,GACX,WAAW,GACX,SAAS,CAAC;AAEd;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,yBAAyB;IACzB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,6BAA6B,EAAE,mBAA4B,CAAC;AAIzE;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAE7E;AAED,6DAA6D;AAC7D,wBAAgB,6BAA6B,IAAI,mBAAmB,CAEnE;AAED,2EAA2E;AAC3E,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AA6hBD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,OAAO,GACd,qBAAqB,CA0CvB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,EAAE,GACzB,MAAM,CAGR"}