@camstack/types 1.1.19 → 1.1.21

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 (36) hide show
  1. package/dist/addon.js +1 -1
  2. package/dist/addon.mjs +1 -1
  3. package/dist/capabilities/battery.cap.d.ts +6 -0
  4. package/dist/capabilities/capability-definition.d.ts +39 -0
  5. package/dist/capabilities/consumables.cap.d.ts +35 -0
  6. package/dist/capabilities/device-manager.cap.d.ts +396 -15
  7. package/dist/capabilities/index.d.ts +1 -1
  8. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +7 -3
  9. package/dist/device/device-binding.d.ts +9 -3
  10. package/dist/device/device-management.d.ts +87 -6
  11. package/dist/device/index.d.ts +2 -2
  12. package/dist/device/schema-fields.d.ts +13 -0
  13. package/dist/expression/ast.d.ts +56 -0
  14. package/dist/expression/builtins.d.ts +19 -0
  15. package/dist/expression/compile.d.ts +17 -0
  16. package/dist/expression/errors.d.ts +16 -0
  17. package/dist/expression/evaluator.d.ts +14 -0
  18. package/dist/expression/index.d.ts +25 -0
  19. package/dist/expression/limits.d.ts +30 -0
  20. package/dist/expression/link-expression.d.ts +44 -0
  21. package/dist/expression/parser.d.ts +12 -0
  22. package/dist/expression/tokenizer.d.ts +38 -0
  23. package/dist/generated/addon-api.d.ts +452 -4
  24. package/dist/generated/device-proxy.d.ts +1 -1
  25. package/dist/generated/method-access-map.d.ts +1 -1
  26. package/dist/generated/system-proxy.d.ts +1 -1
  27. package/dist/index.d.ts +5 -2
  28. package/dist/index.js +1342 -20
  29. package/dist/index.mjs +1313 -21
  30. package/dist/interfaces/agent.d.ts +21 -0
  31. package/dist/{sleep-MHm--th-.mjs → sleep-BO1nweKv.mjs} +1 -0
  32. package/dist/{sleep-BabrCASa.js → sleep-DaQgDq90.js} +1 -0
  33. package/dist/units/convert.d.ts +49 -0
  34. package/dist/units/index.d.ts +8 -0
  35. package/dist/units/unit-table.d.ts +270 -0
  36. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-BabrCASa.js");
2
+ const require_sleep = require("./sleep-DaQgDq90.js");
3
3
  const require_err_msg = require("./err-msg-COpsHMw2.js");
4
4
  let zod = require("zod");
5
5
  //#region src/health/wiring-health.ts
@@ -2394,6 +2394,787 @@ var DEVICE_TYPE_INFO = { ["camera"]: {
2394
2394
  icon: "camera"
2395
2395
  } };
2396
2396
  //#endregion
2397
+ //#region src/expression/errors.ts
2398
+ /**
2399
+ * Error types for the safe expression engine. Two distinct classes so callers
2400
+ * can tell a compile-time (grammar) failure from a runtime (evaluation)
2401
+ * failure — both are non-fatal to the host: read paths degrade to "skip link".
2402
+ */
2403
+ /** Thrown by the tokenizer / parser. Carries a 0-based source `position` when
2404
+ * the failure is anchored to a character (author-facing inline feedback). */
2405
+ var ExpressionParseError = class extends Error {
2406
+ position;
2407
+ constructor(message, position) {
2408
+ super(message);
2409
+ this.name = "ExpressionParseError";
2410
+ this.position = position;
2411
+ }
2412
+ };
2413
+ /** Thrown by the evaluator (unknown identifier, type mismatch, non-finite
2414
+ * result, unknown builtin, step-budget exceeded). */
2415
+ var ExpressionEvalError = class extends Error {
2416
+ constructor(message) {
2417
+ super(message);
2418
+ this.name = "ExpressionEvalError";
2419
+ }
2420
+ };
2421
+ //#endregion
2422
+ //#region src/expression/limits.ts
2423
+ /**
2424
+ * Resource-bound constants for the safe expression engine.
2425
+ *
2426
+ * Every bound is defense-in-depth: the grammar is non-Turing-complete (no
2427
+ * loops, recursion, lambdas or member access — see `ast.ts`), so evaluation is
2428
+ * O(nodeCount) by construction. These caps merely put a hard ceiling on the
2429
+ * work a single author-supplied expression can request, so a hostile or
2430
+ * accidental pathological string can never spend unbounded CPU/memory.
2431
+ */
2432
+ /** Max source length (chars) — checked BEFORE tokenizing so a huge string is
2433
+ * rejected without allocation. */
2434
+ var MAX_EXPRESSION_SOURCE_LENGTH = 2048;
2435
+ /** Max AST nodes — checked during parse; a deeply nested grouping that exceeds
2436
+ * this is rejected as "expression too complex". */
2437
+ var MAX_EXPRESSION_AST_NODES = 256;
2438
+ /** Defense-in-depth walker step budget — one increment per node visit during
2439
+ * evaluation. The grammar guarantees O(nodeCount) walks, so this can only trip
2440
+ * on a crafted maximum-size AST. */
2441
+ var MAX_EXPRESSION_EVAL_STEPS = 4096;
2442
+ /** Max named bindings on one `DeviceLinkExpressionSource`. */
2443
+ var MAX_EXPRESSION_BINDINGS = 32;
2444
+ /** Max positional arguments to any builtin call. */
2445
+ var MAX_EXPRESSION_CALL_ARGS = 16;
2446
+ /** LRU compile-cache capacity (parsed ASTs keyed by raw source string). */
2447
+ var EXPRESSION_COMPILE_CACHE_CAPACITY = 256;
2448
+ /** A legal binding / identifier name. */
2449
+ var EXPRESSION_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
2450
+ /** Binding names an author may NOT use: `now` is auto-injected; the literal
2451
+ * keywords lex as values, not identifiers, so binding to them is meaningless. */
2452
+ var RESERVED_BINDING_NAMES = new Set([
2453
+ "now",
2454
+ "true",
2455
+ "false",
2456
+ "null"
2457
+ ]);
2458
+ //#endregion
2459
+ //#region src/expression/tokenizer.ts
2460
+ /**
2461
+ * Tokenizer for the safe expression mini-language. Hand-rolled, single-pass,
2462
+ * zero-dependency. The grammar is deliberately boring: decimal numbers,
2463
+ * single/double-quoted strings with a tiny escape set, identifiers, the three
2464
+ * value keywords (`true`/`false`/`null`) and a fixed punctuator set. Anything
2465
+ * outside that — a bare `.`, `=`, `[`, `]`, `{`, `}`, `;`, backtick, `&`, `|` —
2466
+ * is a parse error with a source position, so member access / assignment /
2467
+ * template literals are lexically impossible.
2468
+ */
2469
+ var KEYWORDS = new Set([
2470
+ "true",
2471
+ "false",
2472
+ "null"
2473
+ ]);
2474
+ function isDigit(ch) {
2475
+ return ch >= "0" && ch <= "9";
2476
+ }
2477
+ function isIdentStart(ch) {
2478
+ return ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_";
2479
+ }
2480
+ function isIdentPart(ch) {
2481
+ return isIdentStart(ch) || isDigit(ch);
2482
+ }
2483
+ function isWhitespace(ch) {
2484
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
2485
+ }
2486
+ /** Tokenize `source` into a flat token list ending with a single `eof` token.
2487
+ * Throws `ExpressionParseError` on any illegal character or unterminated
2488
+ * string. */
2489
+ function tokenize(source) {
2490
+ if (source.length > 2048) throw new ExpressionParseError(`expression too long (${source.length} > ${MAX_EXPRESSION_SOURCE_LENGTH} chars)`, 0);
2491
+ const tokens = [];
2492
+ let i = 0;
2493
+ const n = source.length;
2494
+ while (i < n) {
2495
+ const ch = source[i];
2496
+ if (isWhitespace(ch)) {
2497
+ i += 1;
2498
+ continue;
2499
+ }
2500
+ if (isDigit(ch)) {
2501
+ const start = i;
2502
+ while (i < n && isDigit(source[i])) i += 1;
2503
+ if (i < n && source[i] === ".") {
2504
+ if (i + 1 >= n || !isDigit(source[i + 1])) throw new ExpressionParseError("malformed number: decimal point needs a digit", i);
2505
+ i += 1;
2506
+ while (i < n && isDigit(source[i])) i += 1;
2507
+ }
2508
+ const text = source.slice(start, i);
2509
+ const value = Number(text);
2510
+ if (!Number.isFinite(value)) throw new ExpressionParseError(`malformed number: '${text}'`, start);
2511
+ tokens.push({
2512
+ type: "number",
2513
+ value,
2514
+ pos: start
2515
+ });
2516
+ continue;
2517
+ }
2518
+ if (ch === "'" || ch === "\"") {
2519
+ const quote = ch;
2520
+ const start = i;
2521
+ i += 1;
2522
+ let out = "";
2523
+ let closed = false;
2524
+ while (i < n) {
2525
+ const c = source[i];
2526
+ if (c === "\\") {
2527
+ const next = i + 1 < n ? source[i + 1] : "";
2528
+ if (next === "\\" || next === "'" || next === "\"") {
2529
+ out += next;
2530
+ i += 2;
2531
+ continue;
2532
+ }
2533
+ throw new ExpressionParseError(`invalid string escape: '\\${next}'`, i);
2534
+ }
2535
+ if (c === quote) {
2536
+ closed = true;
2537
+ i += 1;
2538
+ break;
2539
+ }
2540
+ out += c;
2541
+ i += 1;
2542
+ }
2543
+ if (!closed) throw new ExpressionParseError("unterminated string literal", start);
2544
+ tokens.push({
2545
+ type: "string",
2546
+ value: out,
2547
+ pos: start
2548
+ });
2549
+ continue;
2550
+ }
2551
+ if (isIdentStart(ch)) {
2552
+ const start = i;
2553
+ while (i < n && isIdentPart(source[i])) i += 1;
2554
+ const text = source.slice(start, i);
2555
+ if (KEYWORDS.has(text)) tokens.push({
2556
+ type: "keyword",
2557
+ keyword: keywordOf(text),
2558
+ pos: start
2559
+ });
2560
+ else tokens.push({
2561
+ type: "identifier",
2562
+ name: text,
2563
+ pos: start
2564
+ });
2565
+ continue;
2566
+ }
2567
+ const two = i + 1 < n ? source.slice(i, i + 2) : "";
2568
+ if (two === "<=" || two === ">=" || two === "==" || two === "!=" || two === "&&" || two === "||") {
2569
+ tokens.push({
2570
+ type: "punct",
2571
+ punct: two,
2572
+ pos: i
2573
+ });
2574
+ i += 2;
2575
+ continue;
2576
+ }
2577
+ if (isSinglePunct(ch)) {
2578
+ tokens.push({
2579
+ type: "punct",
2580
+ punct: ch,
2581
+ pos: i
2582
+ });
2583
+ i += 1;
2584
+ continue;
2585
+ }
2586
+ throw new ExpressionParseError(`unexpected character '${ch}'`, i);
2587
+ }
2588
+ tokens.push({
2589
+ type: "eof",
2590
+ pos: n
2591
+ });
2592
+ return tokens;
2593
+ }
2594
+ function keywordOf(text) {
2595
+ if (text === "true") return "true";
2596
+ if (text === "false") return "false";
2597
+ return "null";
2598
+ }
2599
+ function isSinglePunct(ch) {
2600
+ return ch === "(" || ch === ")" || ch === "," || ch === "?" || ch === ":" || ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%" || ch === "!" || ch === "<" || ch === ">";
2601
+ }
2602
+ //#endregion
2603
+ //#region src/expression/builtins.ts
2604
+ /**
2605
+ * Frozen, null-prototype builtin function table for the expression engine
2606
+ * (spec §4 rule 4). The table is the SOLE surface of callable functions: the
2607
+ * parser rejects any callee not in it, and the evaluator gates each call on an
2608
+ * own-property check against it.
2609
+ *
2610
+ * Because the object has a NULL prototype AND is `Object.freeze`d:
2611
+ * - it cannot be polluted (no `__proto__` / `constructor` write reaches it);
2612
+ * - a lookup for `toString` / `hasOwnProperty` / `constructor` finds NOTHING
2613
+ * (there is no `Object.prototype` in the chain), so those names are not
2614
+ * callable — they are simply "unknown function" at parse time.
2615
+ *
2616
+ * Every numeric argument is validated as a finite number and every numeric
2617
+ * RESULT is re-checked finite, so `/0`, `sqrt(-1)` (→ NaN) and overflow
2618
+ * (`pow(10,400)` → Infinity) all raise `ExpressionEvalError` and fail the link
2619
+ * closed rather than emitting a garbage value.
2620
+ */
2621
+ function asFiniteNumber(value, name, index) {
2622
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
2623
+ return value;
2624
+ }
2625
+ function asString$1(value, name, index) {
2626
+ if (typeof value !== "string") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a string`);
2627
+ return value;
2628
+ }
2629
+ function finiteResult(value, name) {
2630
+ if (!Number.isFinite(value)) throw new ExpressionEvalError(`${name}: produced a non-finite result`);
2631
+ return value;
2632
+ }
2633
+ function allFiniteNumbers(args, name) {
2634
+ return args.map((a, idx) => asFiniteNumber(a, name, idx));
2635
+ }
2636
+ var INF = Number.POSITIVE_INFINITY;
2637
+ var table = {
2638
+ min: {
2639
+ minArgs: 1,
2640
+ maxArgs: INF,
2641
+ apply: (args) => finiteResult(Math.min(...allFiniteNumbers(args, "min")), "min")
2642
+ },
2643
+ max: {
2644
+ minArgs: 1,
2645
+ maxArgs: INF,
2646
+ apply: (args) => finiteResult(Math.max(...allFiniteNumbers(args, "max")), "max")
2647
+ },
2648
+ abs: {
2649
+ minArgs: 1,
2650
+ maxArgs: 1,
2651
+ apply: (args) => finiteResult(Math.abs(asFiniteNumber(args[0], "abs", 0)), "abs")
2652
+ },
2653
+ floor: {
2654
+ minArgs: 1,
2655
+ maxArgs: 1,
2656
+ apply: (args) => finiteResult(Math.floor(asFiniteNumber(args[0], "floor", 0)), "floor")
2657
+ },
2658
+ ceil: {
2659
+ minArgs: 1,
2660
+ maxArgs: 1,
2661
+ apply: (args) => finiteResult(Math.ceil(asFiniteNumber(args[0], "ceil", 0)), "ceil")
2662
+ },
2663
+ sqrt: {
2664
+ minArgs: 1,
2665
+ maxArgs: 1,
2666
+ apply: (args) => finiteResult(Math.sqrt(asFiniteNumber(args[0], "sqrt", 0)), "sqrt")
2667
+ },
2668
+ round: {
2669
+ minArgs: 1,
2670
+ maxArgs: 2,
2671
+ apply: (args) => {
2672
+ const x = asFiniteNumber(args[0], "round", 0);
2673
+ const digits = args.length > 1 ? Math.trunc(asFiniteNumber(args[1], "round", 1)) : 0;
2674
+ if (digits < 0 || digits > 100) throw new ExpressionEvalError("round: digits must be between 0 and 100");
2675
+ const factor = 10 ** digits;
2676
+ return finiteResult(Math.round(x * factor) / factor, "round");
2677
+ }
2678
+ },
2679
+ pow: {
2680
+ minArgs: 2,
2681
+ maxArgs: 2,
2682
+ apply: (args) => finiteResult(asFiniteNumber(args[0], "pow", 0) ** asFiniteNumber(args[1], "pow", 1), "pow")
2683
+ },
2684
+ clamp: {
2685
+ minArgs: 3,
2686
+ maxArgs: 3,
2687
+ apply: (args) => {
2688
+ const x = asFiniteNumber(args[0], "clamp", 0);
2689
+ const lo = asFiniteNumber(args[1], "clamp", 1);
2690
+ const hi = asFiniteNumber(args[2], "clamp", 2);
2691
+ if (lo > hi) throw new ExpressionEvalError("clamp: lower bound is greater than upper bound");
2692
+ return finiteResult(Math.min(hi, Math.max(lo, x)), "clamp");
2693
+ }
2694
+ },
2695
+ avg: {
2696
+ minArgs: 1,
2697
+ maxArgs: INF,
2698
+ apply: (args) => {
2699
+ const nums = allFiniteNumbers(args, "avg");
2700
+ return finiteResult(nums.reduce((acc, v) => acc + v, 0) / nums.length, "avg");
2701
+ }
2702
+ },
2703
+ sum: {
2704
+ minArgs: 1,
2705
+ maxArgs: INF,
2706
+ apply: (args) => finiteResult(allFiniteNumbers(args, "sum").reduce((acc, v) => acc + v, 0), "sum")
2707
+ },
2708
+ coalesce: {
2709
+ minArgs: 1,
2710
+ maxArgs: INF,
2711
+ apply: (args) => {
2712
+ for (const a of args) if (a !== null) return a;
2713
+ return null;
2714
+ }
2715
+ },
2716
+ age: {
2717
+ minArgs: 2,
2718
+ maxArgs: 2,
2719
+ apply: (args) => finiteResult(asFiniteNumber(args[0], "age", 0) - asFiniteNumber(args[1], "age", 1), "age")
2720
+ },
2721
+ convert: {
2722
+ minArgs: 3,
2723
+ maxArgs: 3,
2724
+ apply: (args, hooks) => {
2725
+ const x = asFiniteNumber(args[0], "convert", 0);
2726
+ const from = asString$1(args[1], "convert", 1).trim();
2727
+ const to = asString$1(args[2], "convert", 2).trim();
2728
+ if (hooks.convert) {
2729
+ const out = hooks.convert(x, from, to);
2730
+ if (out === null) throw new ExpressionEvalError(`convert: cannot convert '${from}' to '${to}'`);
2731
+ return finiteResult(out, "convert");
2732
+ }
2733
+ if (from === to) return x;
2734
+ throw new ExpressionEvalError("convert: unit conversion table not installed");
2735
+ }
2736
+ }
2737
+ };
2738
+ /** Frozen, null-prototype builtin table. */
2739
+ var EXPRESSION_BUILTINS = Object.freeze(Object.assign(Object.create(null), table));
2740
+ /** The set of valid builtin names — used by the parser to reject unknown
2741
+ * callees at parse time (immediate author feedback). */
2742
+ var EXPRESSION_BUILTIN_NAMES = new Set(Object.keys(table));
2743
+ //#endregion
2744
+ //#region src/expression/parser.ts
2745
+ /**
2746
+ * Pratt (precedence-climbing) parser for the safe expression mini-language.
2747
+ *
2748
+ * Precedence (low → high): ternary `?:` (right-assoc) → `||` → `&&` → equality
2749
+ * → relational → additive → multiplicative → unary `! -` → call / primary.
2750
+ * Calls are ONLY `IDENT '(' args? ')'` at primary position — the callee is a
2751
+ * string validated against the builtin table at parse time, so an unknown
2752
+ * function is rejected immediately (author feedback) and a persisted expression
2753
+ * that references a since-removed builtin degrades at read.
2754
+ *
2755
+ * A node counter caps total AST size (`MAX_EXPRESSION_AST_NODES`) and call
2756
+ * arity is capped (`MAX_EXPRESSION_CALL_ARGS`) — both raise `ExpressionParseError`.
2757
+ */
2758
+ /** Binary/logical operator precedence (higher binds tighter). */
2759
+ var BINARY_PRECEDENCE = {
2760
+ "||": 1,
2761
+ "&&": 2,
2762
+ "==": 3,
2763
+ "!=": 3,
2764
+ "<": 4,
2765
+ "<=": 4,
2766
+ ">": 4,
2767
+ ">=": 4,
2768
+ "+": 5,
2769
+ "-": 5,
2770
+ "*": 6,
2771
+ "/": 6,
2772
+ "%": 6
2773
+ };
2774
+ function isLogicalOp(op) {
2775
+ return op === "&&" || op === "||";
2776
+ }
2777
+ function isBinaryOp(op) {
2778
+ return op === "+" || op === "-" || op === "*" || op === "/" || op === "%" || op === "==" || op === "!=" || op === "<" || op === "<=" || op === ">" || op === ">=";
2779
+ }
2780
+ var Parser = class {
2781
+ tokens;
2782
+ pos = 0;
2783
+ nodeCount = 0;
2784
+ identifiers = /* @__PURE__ */ new Set();
2785
+ callees = /* @__PURE__ */ new Set();
2786
+ constructor(tokens) {
2787
+ this.tokens = tokens;
2788
+ }
2789
+ parse() {
2790
+ const ast = this.parseTernary();
2791
+ const tok = this.peek();
2792
+ if (tok.type !== "eof") throw new ExpressionParseError("unexpected trailing input", tok.pos);
2793
+ return {
2794
+ ast,
2795
+ identifiers: this.identifiers,
2796
+ callees: this.callees,
2797
+ nodeCount: this.nodeCount
2798
+ };
2799
+ }
2800
+ peek() {
2801
+ return this.tokens[this.pos];
2802
+ }
2803
+ next() {
2804
+ return this.tokens[this.pos++];
2805
+ }
2806
+ /** Consume a punctuator token, erroring if the next token isn't it. */
2807
+ expectPunct(punct) {
2808
+ const tok = this.peek();
2809
+ if (tok.type !== "punct" || tok.punct !== punct) throw new ExpressionParseError(`expected '${punct}'`, tok.pos);
2810
+ this.pos += 1;
2811
+ }
2812
+ matchPunct(punct) {
2813
+ const tok = this.peek();
2814
+ if (tok.type === "punct" && tok.punct === punct) {
2815
+ this.pos += 1;
2816
+ return true;
2817
+ }
2818
+ return false;
2819
+ }
2820
+ countNode() {
2821
+ this.nodeCount += 1;
2822
+ if (this.nodeCount > 256) throw new ExpressionParseError("expression too complex", this.peek().pos);
2823
+ }
2824
+ parseTernary() {
2825
+ const test = this.parseBinary(1);
2826
+ if (this.matchPunct("?")) {
2827
+ const consequent = this.parseTernary();
2828
+ this.expectPunct(":");
2829
+ const alternate = this.parseTernary();
2830
+ this.countNode();
2831
+ return {
2832
+ kind: "conditional",
2833
+ test,
2834
+ consequent,
2835
+ alternate
2836
+ };
2837
+ }
2838
+ return test;
2839
+ }
2840
+ parseBinary(minPrec) {
2841
+ let left = this.parseUnary();
2842
+ for (;;) {
2843
+ const tok = this.peek();
2844
+ if (tok.type !== "punct") break;
2845
+ const prec = BINARY_PRECEDENCE[tok.punct];
2846
+ if (prec === void 0 || prec < minPrec) break;
2847
+ const op = tok.punct;
2848
+ this.pos += 1;
2849
+ const right = this.parseBinary(prec + 1);
2850
+ this.countNode();
2851
+ if (isLogicalOp(op)) left = {
2852
+ kind: "logical",
2853
+ op,
2854
+ left,
2855
+ right
2856
+ };
2857
+ else if (isBinaryOp(op)) left = {
2858
+ kind: "binary",
2859
+ op,
2860
+ left,
2861
+ right
2862
+ };
2863
+ else throw new ExpressionParseError(`unexpected operator '${op}'`, tok.pos);
2864
+ }
2865
+ return left;
2866
+ }
2867
+ parseUnary() {
2868
+ const tok = this.peek();
2869
+ if (tok.type === "punct" && (tok.punct === "!" || tok.punct === "-")) {
2870
+ const op = tok.punct;
2871
+ this.pos += 1;
2872
+ const operand = this.parseUnary();
2873
+ this.countNode();
2874
+ return {
2875
+ kind: "unary",
2876
+ op,
2877
+ operand
2878
+ };
2879
+ }
2880
+ return this.parsePrimary();
2881
+ }
2882
+ parsePrimary() {
2883
+ const tok = this.next();
2884
+ switch (tok.type) {
2885
+ case "number":
2886
+ this.countNode();
2887
+ return {
2888
+ kind: "literal",
2889
+ value: tok.value
2890
+ };
2891
+ case "string":
2892
+ this.countNode();
2893
+ return {
2894
+ kind: "literal",
2895
+ value: tok.value
2896
+ };
2897
+ case "keyword":
2898
+ this.countNode();
2899
+ return {
2900
+ kind: "literal",
2901
+ value: tok.keyword === "null" ? null : tok.keyword === "true"
2902
+ };
2903
+ case "identifier": {
2904
+ const nextTok = this.peek();
2905
+ if (nextTok.type === "punct" && nextTok.punct === "(") return this.parseCall(tok.name, tok.pos);
2906
+ this.identifiers.add(tok.name);
2907
+ this.countNode();
2908
+ return {
2909
+ kind: "identifier",
2910
+ name: tok.name
2911
+ };
2912
+ }
2913
+ case "punct":
2914
+ if (tok.punct === "(") {
2915
+ const inner = this.parseTernary();
2916
+ this.expectPunct(")");
2917
+ return inner;
2918
+ }
2919
+ throw new ExpressionParseError(`unexpected token '${tok.punct}'`, tok.pos);
2920
+ case "eof": throw new ExpressionParseError("unexpected end of expression", tok.pos);
2921
+ }
2922
+ }
2923
+ parseCall(callee, pos) {
2924
+ if (!EXPRESSION_BUILTIN_NAMES.has(callee)) throw new ExpressionParseError(`unknown function '${callee}'`, pos);
2925
+ this.expectPunct("(");
2926
+ const args = [];
2927
+ if (!this.matchPunct(")")) for (;;) {
2928
+ args.push(this.parseTernary());
2929
+ if (args.length > 16) throw new ExpressionParseError(`too many arguments to '${callee}'`, pos);
2930
+ if (this.matchPunct(",")) continue;
2931
+ this.expectPunct(")");
2932
+ break;
2933
+ }
2934
+ this.callees.add(callee);
2935
+ this.countNode();
2936
+ return {
2937
+ kind: "call",
2938
+ callee,
2939
+ args
2940
+ };
2941
+ }
2942
+ };
2943
+ /** Tokenize + parse `source` into a validated `ParsedExpression`. Throws
2944
+ * `ExpressionParseError` on any lexical or grammatical failure. */
2945
+ function parseExpression(source) {
2946
+ return new Parser(tokenize(source)).parse();
2947
+ }
2948
+ //#endregion
2949
+ //#region src/expression/evaluator.ts
2950
+ /**
2951
+ * Tree-walking evaluator for the safe expression mini-language.
2952
+ *
2953
+ * SECURITY (spec §4 rule 2/5):
2954
+ * - The scope is an `Object.create(null)` copy of ONLY the caller's own
2955
+ * enumerable binding entries, so `name in scope` is a pure own-key check and
2956
+ * `constructor` / `__proto__` / `toString` are plain unknown identifiers.
2957
+ * - Performs ZERO I/O and never touches `globalThis` / `Date` / `Math`
2958
+ * directly — the only external calls are into the frozen builtin table.
2959
+ * - The grammar has no loops/recursion/lambdas, so a walk is O(nodeCount) by
2960
+ * construction; the step counter is defense-in-depth for a crafted max-size
2961
+ * AST. Nothing blocks: there are no timers, awaits or unbounded loops.
2962
+ */
2963
+ var EMPTY_HOOKS = Object.freeze({});
2964
+ /** Build a null-prototype scope from own-enumerable binding entries. Inherited
2965
+ * keys of the input (e.g. from a `{__proto__: {...}}` payload) are NOT copied,
2966
+ * so nothing smuggles in via the prototype chain. */
2967
+ function createExpressionScope(bindings) {
2968
+ const scope = Object.create(null);
2969
+ for (const key of Object.keys(bindings)) if (Object.prototype.hasOwnProperty.call(bindings, key)) scope[key] = bindings[key];
2970
+ return scope;
2971
+ }
2972
+ function isFiniteNumber(value) {
2973
+ return typeof value === "number" && Number.isFinite(value);
2974
+ }
2975
+ /** JS truthiness of a primitive value. */
2976
+ function truthy(value) {
2977
+ return Boolean(value);
2978
+ }
2979
+ function requireFinite(value, context) {
2980
+ if (!Number.isFinite(value)) throw new ExpressionEvalError(`${context} produced a non-finite result`);
2981
+ return value;
2982
+ }
2983
+ function step(ctx) {
2984
+ ctx.steps += 1;
2985
+ if (ctx.steps > ctx.maxSteps) throw new ExpressionEvalError("expression evaluation step budget exceeded");
2986
+ }
2987
+ function evalNode(node, ctx) {
2988
+ step(ctx);
2989
+ switch (node.kind) {
2990
+ case "literal": return node.value;
2991
+ case "identifier":
2992
+ if (!(node.name in ctx.scope)) throw new ExpressionEvalError(`unknown identifier: ${node.name}`);
2993
+ return ctx.scope[node.name];
2994
+ case "unary": return evalUnary(node.op, evalNode(node.operand, ctx));
2995
+ case "binary": return evalBinary(node.op, evalNode(node.left, ctx), evalNode(node.right, ctx));
2996
+ case "logical": {
2997
+ const left = evalNode(node.left, ctx);
2998
+ if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx) : left;
2999
+ return truthy(left) ? left : evalNode(node.right, ctx);
3000
+ }
3001
+ case "conditional": return truthy(evalNode(node.test, ctx)) ? evalNode(node.consequent, ctx) : evalNode(node.alternate, ctx);
3002
+ case "call": return evalCall(node.callee, node.args.map((a) => evalNode(a, ctx)), ctx.hooks);
3003
+ }
3004
+ }
3005
+ function evalUnary(op, operand) {
3006
+ if (op === "!") return !truthy(operand);
3007
+ if (!isFiniteNumber(operand)) throw new ExpressionEvalError("unary \"-\" requires a finite number");
3008
+ return requireFinite(-operand, "unary \"-\"");
3009
+ }
3010
+ function evalBinary(op, left, right) {
3011
+ switch (op) {
3012
+ case "==": return left === right;
3013
+ case "!=": return left !== right;
3014
+ case "+":
3015
+ if (typeof left === "string" && typeof right === "string") return left + right;
3016
+ if (isFiniteNumber(left) && isFiniteNumber(right)) return requireFinite(left + right, "\"+\"");
3017
+ throw new ExpressionEvalError("\"+\" requires two numbers or two strings");
3018
+ case "-":
3019
+ case "*":
3020
+ case "/":
3021
+ case "%":
3022
+ if (!isFiniteNumber(left) || !isFiniteNumber(right)) throw new ExpressionEvalError(`"${op}" requires two finite numbers`);
3023
+ return requireFinite(op === "-" ? left - right : op === "*" ? left * right : op === "/" ? left / right : left % right, `"${op}"`);
3024
+ case "<":
3025
+ case "<=":
3026
+ case ">":
3027
+ case ">=":
3028
+ if (isFiniteNumber(left) && isFiniteNumber(right)) return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
3029
+ if (typeof left === "string" && typeof right === "string") return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
3030
+ throw new ExpressionEvalError(`"${op}" requires two numbers or two strings`);
3031
+ }
3032
+ }
3033
+ function evalCall(callee, args, hooks) {
3034
+ if (!Object.prototype.hasOwnProperty.call(EXPRESSION_BUILTINS, callee)) throw new ExpressionEvalError(`unknown function: ${callee}`);
3035
+ const builtin = EXPRESSION_BUILTINS[callee];
3036
+ if (args.length < builtin.minArgs || args.length > builtin.maxArgs) throw new ExpressionEvalError(`${callee}: wrong number of arguments (${args.length})`);
3037
+ return builtin.apply(args, hooks);
3038
+ }
3039
+ /** Evaluate an AST node against a scope. Throws `ExpressionEvalError` on any
3040
+ * runtime failure (unknown identifier, type mismatch, non-finite result,
3041
+ * step-budget exhaustion). */
3042
+ function evaluateAst(node, scope, opts) {
3043
+ return evalNode(node, {
3044
+ scope,
3045
+ hooks: opts?.hooks ?? EMPTY_HOOKS,
3046
+ maxSteps: opts?.maxSteps ?? 4096,
3047
+ steps: 0
3048
+ });
3049
+ }
3050
+ //#endregion
3051
+ //#region src/expression/compile.ts
3052
+ /**
3053
+ * LRU compile cache for parsed expressions (spec §2.4 "parse once … LRU keyed
3054
+ * by expr"). The cache stores BOTH successes and failures (negative caching),
3055
+ * so a corrupt persisted string costs exactly one tokenize+parse total — not
3056
+ * one per read on a hot resolve path.
3057
+ *
3058
+ * The cache is a module-level singleton: entries are pure, content-addressed
3059
+ * ASTs keyed by the raw source string, so sharing one instance across all
3060
+ * callers is safe and maximises hit rate.
3061
+ */
3062
+ var cache = /* @__PURE__ */ new Map();
3063
+ function getCached(source) {
3064
+ const hit = cache.get(source);
3065
+ if (hit !== void 0) {
3066
+ cache.delete(source);
3067
+ cache.set(source, hit);
3068
+ return hit;
3069
+ }
3070
+ let result;
3071
+ try {
3072
+ result = {
3073
+ ok: true,
3074
+ parsed: parseExpression(source)
3075
+ };
3076
+ } catch (err) {
3077
+ result = {
3078
+ ok: false,
3079
+ error: err instanceof ExpressionParseError ? err.message : String(err)
3080
+ };
3081
+ }
3082
+ cache.set(source, result);
3083
+ if (cache.size > 256) {
3084
+ const oldest = cache.keys().next().value;
3085
+ if (oldest !== void 0) cache.delete(oldest);
3086
+ }
3087
+ return result;
3088
+ }
3089
+ /** Compile `source` to a `ParsedExpression`, throwing `ExpressionParseError`
3090
+ * on failure. LRU/negative-cached. */
3091
+ function compileExpression(source) {
3092
+ const result = getCached(source);
3093
+ if (result.ok) return result.parsed;
3094
+ throw new ExpressionParseError(result.error);
3095
+ }
3096
+ /** Compile `source`, returning a discriminated result instead of throwing.
3097
+ * Used by read paths that must degrade rather than raise. LRU/negative-cached. */
3098
+ function compileExpressionSafe(source) {
3099
+ return getCached(source);
3100
+ }
3101
+ //#endregion
3102
+ //#region src/expression/link-expression.ts
3103
+ /**
3104
+ * DeviceLink-facing helpers for the expression engine — the single seam both
3105
+ * resolver channels (async provider-read + sync mirror) and the wire-schema
3106
+ * `superRefine` share, so validation and evaluation semantics stay identical
3107
+ * everywhere.
3108
+ */
3109
+ /** The `now` epoch-ms binding is auto-injected into every evaluation and is a
3110
+ * reserved binding name (authors may not rebind it). */
3111
+ var EXPRESSION_INJECTED_NOW = "now";
3112
+ /**
3113
+ * Coerce an untrusted `getByPath` / mirror read to an `ExpressionValue`.
3114
+ * Non-primitive values (objects, arrays, `undefined`, functions, bigint,
3115
+ * symbol) and non-finite numbers become `undefined` so the caller can apply
3116
+ * its binding-miss policy (→ `null`). `null` itself is a valid value.
3117
+ */
3118
+ function toExpressionValue(raw) {
3119
+ if (raw === null) return null;
3120
+ if (typeof raw === "string") return raw;
3121
+ if (typeof raw === "boolean") return raw;
3122
+ if (typeof raw === "number") return Number.isFinite(raw) ? raw : void 0;
3123
+ }
3124
+ /**
3125
+ * Author-time validation. Returns `null` when the source is valid, else a
3126
+ * human-readable error message. Checks: the expression compiles; binding count
3127
+ * is within `MAX_EXPRESSION_BINDINGS`; every binding name is a legal identifier,
3128
+ * is not reserved (`now`/keywords) and does not shadow a builtin; and every
3129
+ * FREE identifier of the AST is covered by a binding or the injected `now`.
3130
+ */
3131
+ function validateExpressionSource(src) {
3132
+ const names = Object.keys(src.bindings);
3133
+ if (names.length > 32) return `too many bindings (${names.length} > 32)`;
3134
+ for (const name of names) {
3135
+ if (!EXPRESSION_IDENTIFIER_RE.test(name)) return `invalid binding name '${name}'`;
3136
+ if (RESERVED_BINDING_NAMES.has(name)) return `binding name '${name}' is reserved`;
3137
+ if (EXPRESSION_BUILTIN_NAMES.has(name)) return `binding name '${name}' shadows a builtin function`;
3138
+ }
3139
+ const compiled = compileExpressionSafe(src.expr);
3140
+ if (!compiled.ok) return compiled.error;
3141
+ const bound = new Set(names);
3142
+ for (const id of compiled.parsed.identifiers) {
3143
+ if (id === "now") continue;
3144
+ if (!bound.has(id)) return `expression references unbound identifier '${id}'`;
3145
+ }
3146
+ return null;
3147
+ }
3148
+ /**
3149
+ * Shared read-path evaluation for BOTH resolver channels. Builds a null-proto
3150
+ * scope from `bindingValues` plus the injected `now` (supplied by the caller
3151
+ * for determinism/testability), compiles via the LRU, and evaluates. Any
3152
+ * failure (parse or eval) returns `{ ok: false }` — the caller treats that as
3153
+ * "skip this link".
3154
+ */
3155
+ function evaluateLinkExpression(expr, bindingValues, now, opts) {
3156
+ const compiled = compileExpressionSafe(expr);
3157
+ if (!compiled.ok) return {
3158
+ ok: false,
3159
+ error: compiled.error
3160
+ };
3161
+ const scope = createExpressionScope({
3162
+ ...bindingValues,
3163
+ ["now"]: now
3164
+ });
3165
+ try {
3166
+ return {
3167
+ ok: true,
3168
+ value: evaluateAst(compiled.parsed.ast, scope, opts)
3169
+ };
3170
+ } catch (err) {
3171
+ return {
3172
+ ok: false,
3173
+ error: err instanceof ExpressionEvalError ? err.message : String(err)
3174
+ };
3175
+ }
3176
+ }
3177
+ //#endregion
2397
3178
  //#region src/device/accessory.ts
2398
3179
  /**
2399
3180
  * Accessory device helpers — shared across drivers.
@@ -3574,7 +4355,13 @@ onStatusChanged: { data: zod.z.object({
3574
4355
  }) } },
3575
4356
  status: {
3576
4357
  schema: BatteryStatusSchema,
3577
- kind: "push"
4358
+ kind: "push",
4359
+ empty: {
4360
+ percentage: 0,
4361
+ charging: "none",
4362
+ sleeping: false,
4363
+ lastUpdated: 0
4364
+ }
3578
4365
  },
3579
4366
  /**
3580
4367
  * Runtime-state slice — every provider that registers this cap
@@ -4754,7 +5541,25 @@ reset: require_sleep.method(zod.z.object({
4754
5541
  }) },
4755
5542
  status: {
4756
5543
  schema: ConsumablesStatusSchema,
4757
- kind: "push"
5544
+ kind: "push",
5545
+ empty: {
5546
+ items: [],
5547
+ lastChangedAt: 0
5548
+ },
5549
+ itemArray: {
5550
+ path: "items",
5551
+ keyField: "key",
5552
+ labelField: "label",
5553
+ itemSchema: ConsumableItemSchema,
5554
+ emptyItem: {
5555
+ key: "",
5556
+ label: "",
5557
+ level: null,
5558
+ status: null,
5559
+ lastResetAt: null,
5560
+ resettable: false
5561
+ }
5562
+ }
4758
5563
  },
4759
5564
  runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() })
4760
5565
  };
@@ -9609,6 +10414,334 @@ var BaseDevice = class {
9609
10414
  }
9610
10415
  };
9611
10416
  //#endregion
10417
+ //#region src/units/unit-table.ts
10418
+ /**
10419
+ * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
10420
+ * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
10421
+ * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
10422
+ * decimals). K derives from `°C = K − 273.15`.
10423
+ */
10424
+ var UNIT_TABLE = {
10425
+ "°C": {
10426
+ dimension: "temperature",
10427
+ factor: 1,
10428
+ offset: 0
10429
+ },
10430
+ "°F": {
10431
+ dimension: "temperature",
10432
+ factor: 5 / 9,
10433
+ offset: -160 / 9
10434
+ },
10435
+ K: {
10436
+ dimension: "temperature",
10437
+ factor: 1,
10438
+ offset: -273.15
10439
+ },
10440
+ hPa: {
10441
+ dimension: "pressure",
10442
+ factor: 1,
10443
+ offset: 0
10444
+ },
10445
+ kPa: {
10446
+ dimension: "pressure",
10447
+ factor: 10,
10448
+ offset: 0
10449
+ },
10450
+ Pa: {
10451
+ dimension: "pressure",
10452
+ factor: .01,
10453
+ offset: 0
10454
+ },
10455
+ mbar: {
10456
+ dimension: "pressure",
10457
+ factor: 1,
10458
+ offset: 0
10459
+ },
10460
+ bar: {
10461
+ dimension: "pressure",
10462
+ factor: 1e3,
10463
+ offset: 0
10464
+ },
10465
+ inHg: {
10466
+ dimension: "pressure",
10467
+ factor: 33.8639,
10468
+ offset: 0
10469
+ },
10470
+ mmHg: {
10471
+ dimension: "pressure",
10472
+ factor: 1.33322,
10473
+ offset: 0
10474
+ },
10475
+ psi: {
10476
+ dimension: "pressure",
10477
+ factor: 68.9476,
10478
+ offset: 0
10479
+ },
10480
+ "m/s": {
10481
+ dimension: "speed",
10482
+ factor: 1,
10483
+ offset: 0
10484
+ },
10485
+ "km/h": {
10486
+ dimension: "speed",
10487
+ factor: 1 / 3.6,
10488
+ offset: 0
10489
+ },
10490
+ mph: {
10491
+ dimension: "speed",
10492
+ factor: .44704,
10493
+ offset: 0
10494
+ },
10495
+ kn: {
10496
+ dimension: "speed",
10497
+ factor: .514444,
10498
+ offset: 0
10499
+ },
10500
+ "mm/h": {
10501
+ dimension: "precipitation-rate",
10502
+ factor: 1,
10503
+ offset: 0
10504
+ },
10505
+ "in/h": {
10506
+ dimension: "precipitation-rate",
10507
+ factor: 25.4,
10508
+ offset: 0
10509
+ },
10510
+ m: {
10511
+ dimension: "length",
10512
+ factor: 1,
10513
+ offset: 0
10514
+ },
10515
+ mm: {
10516
+ dimension: "length",
10517
+ factor: .001,
10518
+ offset: 0
10519
+ },
10520
+ cm: {
10521
+ dimension: "length",
10522
+ factor: .01,
10523
+ offset: 0
10524
+ },
10525
+ km: {
10526
+ dimension: "length",
10527
+ factor: 1e3,
10528
+ offset: 0
10529
+ },
10530
+ in: {
10531
+ dimension: "length",
10532
+ factor: .0254,
10533
+ offset: 0
10534
+ },
10535
+ ft: {
10536
+ dimension: "length",
10537
+ factor: .3048,
10538
+ offset: 0
10539
+ },
10540
+ mi: {
10541
+ dimension: "length",
10542
+ factor: 1609.344,
10543
+ offset: 0
10544
+ },
10545
+ lx: {
10546
+ dimension: "illuminance",
10547
+ factor: 1,
10548
+ offset: 0
10549
+ },
10550
+ "W/m²": {
10551
+ dimension: "irradiance",
10552
+ factor: 1,
10553
+ offset: 0
10554
+ },
10555
+ W: {
10556
+ dimension: "power",
10557
+ factor: 1,
10558
+ offset: 0
10559
+ },
10560
+ kW: {
10561
+ dimension: "power",
10562
+ factor: 1e3,
10563
+ offset: 0
10564
+ },
10565
+ VA: {
10566
+ dimension: "apparent-power",
10567
+ factor: 1,
10568
+ offset: 0
10569
+ },
10570
+ Wh: {
10571
+ dimension: "energy",
10572
+ factor: 1,
10573
+ offset: 0
10574
+ },
10575
+ kWh: {
10576
+ dimension: "energy",
10577
+ factor: 1e3,
10578
+ offset: 0
10579
+ },
10580
+ MWh: {
10581
+ dimension: "energy",
10582
+ factor: 1e6,
10583
+ offset: 0
10584
+ },
10585
+ V: {
10586
+ dimension: "voltage",
10587
+ factor: 1,
10588
+ offset: 0
10589
+ },
10590
+ mV: {
10591
+ dimension: "voltage",
10592
+ factor: .001,
10593
+ offset: 0
10594
+ },
10595
+ A: {
10596
+ dimension: "current",
10597
+ factor: 1,
10598
+ offset: 0
10599
+ },
10600
+ mA: {
10601
+ dimension: "current",
10602
+ factor: .001,
10603
+ offset: 0
10604
+ },
10605
+ ppm: {
10606
+ dimension: "concentration-volume",
10607
+ factor: 1,
10608
+ offset: 0
10609
+ },
10610
+ ppb: {
10611
+ dimension: "concentration-volume",
10612
+ factor: .001,
10613
+ offset: 0
10614
+ },
10615
+ "µg/m³": {
10616
+ dimension: "concentration-mass",
10617
+ factor: 1,
10618
+ offset: 0
10619
+ },
10620
+ "mg/m³": {
10621
+ dimension: "concentration-mass",
10622
+ factor: 1e3,
10623
+ offset: 0
10624
+ },
10625
+ kg: {
10626
+ dimension: "mass",
10627
+ factor: 1,
10628
+ offset: 0
10629
+ },
10630
+ g: {
10631
+ dimension: "mass",
10632
+ factor: .001,
10633
+ offset: 0
10634
+ },
10635
+ lb: {
10636
+ dimension: "mass",
10637
+ factor: .453592,
10638
+ offset: 0
10639
+ },
10640
+ oz: {
10641
+ dimension: "mass",
10642
+ factor: .0283495,
10643
+ offset: 0
10644
+ },
10645
+ "%": {
10646
+ dimension: "percentage",
10647
+ factor: 1,
10648
+ offset: 0
10649
+ }
10650
+ };
10651
+ //#endregion
10652
+ //#region src/units/convert.ts
10653
+ /**
10654
+ * Pure unit-conversion API over `UNIT_TABLE`. No dependencies, no side effects.
10655
+ *
10656
+ * Conversion is refused (throws `UnitConversionError`, or returns `undefined`
10657
+ * from the `try*` variant) whenever a unit is unknown or the two units belong
10658
+ * to different dimensions — cross-dimension conversion is deliberately
10659
+ * impossible so a render override can never make the value and its unit label
10660
+ * disagree.
10661
+ *
10662
+ * Callers pass units through `normalizeUnit` (`../device/source-info.ts`) BEFORE
10663
+ * calling here — the table keys only canonical spellings.
10664
+ */
10665
+ /** Thrown by `convertUnit` when a conversion is refused. Carries the offending
10666
+ * units + a machine-readable `reason` for callers that branch on the cause. */
10667
+ var UnitConversionError = class extends Error {
10668
+ from;
10669
+ to;
10670
+ reason;
10671
+ constructor(from, to, reason) {
10672
+ super(`[units] cannot convert '${from}' → '${to}': ${reason}`);
10673
+ this.name = "UnitConversionError";
10674
+ this.from = from;
10675
+ this.to = to;
10676
+ this.reason = reason;
10677
+ }
10678
+ };
10679
+ /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
10680
+ function specFor(unit) {
10681
+ return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
10682
+ }
10683
+ /** The dimension a unit belongs to, or `undefined` for an unknown unit. */
10684
+ function unitDimension(unit) {
10685
+ return specFor(unit)?.dimension;
10686
+ }
10687
+ /** Value in `spec`'s unit → its dimension's canonical unit. */
10688
+ function toCanonical(value, spec) {
10689
+ return value * spec.factor + spec.offset;
10690
+ }
10691
+ /** Canonical-unit value → the unit described by `spec`. */
10692
+ function fromCanonical(canonical, spec) {
10693
+ return (canonical - spec.offset) / spec.factor;
10694
+ }
10695
+ /**
10696
+ * Convert `value` from unit `from` to unit `to`. Identity when `from === to`.
10697
+ * Throws `UnitConversionError` when either unit is unknown or the units belong
10698
+ * to different dimensions.
10699
+ */
10700
+ function convertUnit(value, from, to) {
10701
+ if (from === to) return value;
10702
+ const fromSpec = specFor(from);
10703
+ if (!fromSpec) throw new UnitConversionError(from, to, "unknown-from");
10704
+ const toSpec = specFor(to);
10705
+ if (!toSpec) throw new UnitConversionError(from, to, "unknown-to");
10706
+ if (fromSpec.dimension !== toSpec.dimension) throw new UnitConversionError(from, to, "cross-dimension");
10707
+ return fromCanonical(toCanonical(value, fromSpec), toSpec);
10708
+ }
10709
+ /**
10710
+ * Graceful variant for the render path: returns the converted value, or
10711
+ * `undefined` when the conversion is refused (unknown unit / cross-dimension).
10712
+ * Never throws — a render caller falls back to the raw value + source unit.
10713
+ */
10714
+ function tryConvertUnit(value, from, to) {
10715
+ if (from === to) return value;
10716
+ const fromSpec = specFor(from);
10717
+ if (!fromSpec) return void 0;
10718
+ const toSpec = specFor(to);
10719
+ if (!toSpec) return void 0;
10720
+ if (fromSpec.dimension !== toSpec.dimension) return void 0;
10721
+ return fromCanonical(toCanonical(value, fromSpec), toSpec);
10722
+ }
10723
+ /**
10724
+ * Whether `value` can be converted between `from` and `to` (same dimension,
10725
+ * both known). Cheap boolean probe for the UI's same-dimension unit picker and
10726
+ * the resolver's convertibility branch — compute ONCE so a unit label and its
10727
+ * value math can never disagree.
10728
+ */
10729
+ function canConvertUnit(from, to) {
10730
+ if (from === to) return true;
10731
+ const fromDim = unitDimension(from);
10732
+ if (fromDim === void 0) return false;
10733
+ return fromDim === unitDimension(to);
10734
+ }
10735
+ /**
10736
+ * Every known unit spelling in a dimension (table insertion order — canonical
10737
+ * unit first). Powers the UI's same-dimension unit picker.
10738
+ */
10739
+ function unitsForDimension(dimension) {
10740
+ const out = [];
10741
+ for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
10742
+ return out;
10743
+ }
10744
+ //#endregion
9612
10745
  //#region src/capabilities/device-provider.cap.ts
9613
10746
  var ProviderStatusSchema = zod.z.object({
9614
10747
  connected: zod.z.boolean(),
@@ -10221,6 +11354,17 @@ function enumerateSchemaFields(schema, prefix = "") {
10221
11354
  }
10222
11355
  return out;
10223
11356
  }
11357
+ /** Enumerate the per-item wireable fields of an item-array cap (see
11358
+ * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
11359
+ * `keyField` (the key comes from the link's `itemKey`, never from a wired
11360
+ * source), each tagged `item: true` so the authoring UI collects an
11361
+ * `itemKey` alongside the field. Never throws. */
11362
+ function enumerateItemArrayFields(itemArray) {
11363
+ return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
11364
+ ...f,
11365
+ item: true
11366
+ }));
11367
+ }
10224
11368
  //#endregion
10225
11369
  //#region src/utils/zone-rule-eval.ts
10226
11370
  /**
@@ -10456,6 +11600,8 @@ function createSystemProxy(api) {
10456
11600
  registerDevice: (input) => dispatch("deviceManager", "registerDevice", "mutation", input),
10457
11601
  removeDevice: (input) => dispatch("deviceManager", "removeDevice", "mutation", input),
10458
11602
  persistConfig: (input) => dispatch("deviceManager", "persistConfig", "mutation", input),
11603
+ getRoleDisplayDefaults: (input) => dispatch("deviceManager", "getRoleDisplayDefaults", "query", input),
11604
+ setRoleDisplayDefaults: (input) => dispatch("deviceManager", "setRoleDisplayDefaults", "mutation", input),
10459
11605
  listLocations: (input) => dispatch("deviceManager", "listLocations", "query", input),
10460
11606
  addLocation: (input) => dispatch("deviceManager", "addLocation", "mutation", input),
10461
11607
  removeLocation: (input) => dispatch("deviceManager", "removeLocation", "mutation", input),
@@ -13323,14 +14469,63 @@ var ChildLayoutEntrySchema = zod.z.object({
13323
14469
  collapsed: zod.z.boolean().optional()
13324
14470
  });
13325
14471
  /** Cap-wire shape of a DeviceLink — structurally mirrors `DeviceLink` in
13326
- * `device-management.ts`. */
14472
+ * `device-management.ts`. Source is a union: a FIELD source copies a sibling
14473
+ * accessory's status field (`kind` optional/absent for wire compat); a
14474
+ * LITERAL source carries a per-device constant (no sibling is read); a
14475
+ * GLOBAL source (P2e) copies ANY device's status field, addressed by the
14476
+ * source device's full re-sync-stable `stableId`. */
14477
+ var DeviceLinkFieldSourceSchema = zod.z.object({
14478
+ kind: zod.z.literal("field").optional(),
14479
+ sourceKey: zod.z.string(),
14480
+ cap: zod.z.string(),
14481
+ fieldPath: zod.z.string()
14482
+ });
14483
+ var DeviceLinkLiteralSourceSchema = zod.z.object({
14484
+ kind: zod.z.literal("literal"),
14485
+ value: zod.z.union([
14486
+ zod.z.string(),
14487
+ zod.z.number(),
14488
+ zod.z.boolean(),
14489
+ zod.z.null()
14490
+ ])
14491
+ });
14492
+ var DeviceLinkGlobalSourceSchema = zod.z.object({
14493
+ kind: zod.z.literal("global"),
14494
+ sourceStableId: zod.z.string(),
14495
+ cap: zod.z.string(),
14496
+ fieldPath: zod.z.string()
14497
+ });
14498
+ /** Expression source (Stage X): compute the target field from N named bindings
14499
+ * via the safe expression engine. Bindings are field | literal | global — never
14500
+ * another expression (no nesting). The `superRefine` runs the SAME author-time
14501
+ * validation as `validateExpressionSource` (compiles the expr, checks binding
14502
+ * names + identifier coverage) so every wire boundary that parses a DeviceLink
14503
+ * (tRPC mount, kernel create pre-seed, projection output) validates-at-write.
14504
+ * Compiles are LRU-cached, so repeated validation of the same expr is a hit. */
14505
+ var DeviceLinkExpressionSourceSchema = zod.z.object({
14506
+ kind: zod.z.literal("expression"),
14507
+ expr: zod.z.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
14508
+ bindings: zod.z.record(zod.z.string().regex(EXPRESSION_IDENTIFIER_RE), zod.z.union([
14509
+ DeviceLinkFieldSourceSchema,
14510
+ DeviceLinkLiteralSourceSchema,
14511
+ DeviceLinkGlobalSourceSchema
14512
+ ]))
14513
+ }).superRefine((src, ctx) => {
14514
+ const err = validateExpressionSource(src);
14515
+ if (err !== null) ctx.addIssue({
14516
+ code: "custom",
14517
+ message: err,
14518
+ path: ["expr"]
14519
+ });
14520
+ });
13327
14521
  var DeviceLinkSchema = zod.z.object({
13328
14522
  id: zod.z.string(),
13329
- source: zod.z.object({
13330
- sourceKey: zod.z.string(),
13331
- cap: zod.z.string(),
13332
- fieldPath: zod.z.string()
13333
- }),
14523
+ source: zod.z.union([
14524
+ DeviceLinkFieldSourceSchema,
14525
+ DeviceLinkLiteralSourceSchema,
14526
+ DeviceLinkGlobalSourceSchema,
14527
+ DeviceLinkExpressionSourceSchema
14528
+ ]),
13334
14529
  target: zod.z.object({
13335
14530
  cap: zod.z.string(),
13336
14531
  fieldPath: zod.z.string(),
@@ -13359,6 +14554,31 @@ var DeviceLinkSchema = zod.z.object({
13359
14554
  })
13360
14555
  ]).optional()
13361
14556
  });
14557
+ /** Cap-wire shape of a per-cap display refinement — mirrors
14558
+ * `DeviceCapDisplayOverride` in `device-management.ts`. */
14559
+ var DeviceCapDisplayOverrideSchema = zod.z.object({
14560
+ unit: zod.z.string().min(1).optional(),
14561
+ precision: zod.z.number().int().min(0).max(10).optional()
14562
+ });
14563
+ /** Cap-wire shape of an operator-authored per-device display override —
14564
+ * mirrors `DeviceDisplayOverride` in `device-management.ts`. `precision`
14565
+ * bounds mirror `numeric-sensor.cap.ts` (`int 0-10`). */
14566
+ var DeviceDisplayOverrideSchema = zod.z.object({
14567
+ icon: zod.z.string().min(1).optional(),
14568
+ label: zod.z.string().min(1).optional(),
14569
+ unit: zod.z.string().min(1).optional(),
14570
+ precision: zod.z.number().int().min(0).max(10).optional(),
14571
+ hidden: zod.z.boolean().optional(),
14572
+ perCap: zod.z.record(zod.z.string(), DeviceCapDisplayOverrideSchema).optional()
14573
+ });
14574
+ /** Cap-wire shape of a per-role display default — mirrors `RoleDisplayDefault`
14575
+ * in `device-management.ts`. Keyed by `DeviceRole` string (role strings cross
14576
+ * the wire as plain strings everywhere else — cf. `DeviceInfoSchema.role`). */
14577
+ var RoleDisplayDefaultSchema = zod.z.object({
14578
+ unit: zod.z.string().min(1).optional(),
14579
+ precision: zod.z.number().int().min(0).max(10).optional(),
14580
+ icon: zod.z.string().min(1).optional()
14581
+ });
13362
14582
  /**
13363
14583
  * Serializable projection of a live IDevice.
13364
14584
  * Returned by listAll, getDevice, getChildren.
@@ -13414,7 +14634,9 @@ var DeviceInfoSchema = zod.z.object({
13414
14634
  * `DeviceMeta.childLayout`. Absent ⇒ no layout declared. */
13415
14635
  childLayout: zod.z.array(ChildLayoutEntrySchema).readonly().optional(),
13416
14636
  /** Operator-authored cross-device field wirings. See `DeviceMeta.deviceLinks`. */
13417
- deviceLinks: zod.z.array(DeviceLinkSchema).readonly().optional()
14637
+ deviceLinks: zod.z.array(DeviceLinkSchema).readonly().optional(),
14638
+ /** Operator-authored per-device display override. See `DeviceMeta.display`. */
14639
+ display: DeviceDisplayOverrideSchema.optional()
13418
14640
  });
13419
14641
  var ConfigEntrySchema = zod.z.object({
13420
14642
  key: zod.z.string(),
@@ -13479,7 +14701,9 @@ var DeviceMetaSchema = zod.z.object({
13479
14701
  deviceLinks: zod.z.array(DeviceLinkSchema).readonly().optional(),
13480
14702
  /** Semantic role string (`DeviceRole`) — propagated from the spawn pre-seed.
13481
14703
  * Optional: only present for accessory children that carry a known role. */
13482
- role: zod.z.string().nullable().optional()
14704
+ role: zod.z.string().nullable().optional(),
14705
+ /** Operator-authored per-device display override. See `DeviceMeta.display`. */
14706
+ display: DeviceDisplayOverrideSchema.optional()
13483
14707
  });
13484
14708
  /** ConfigUISchema passed through as unknown — mirrors device-provider's CreationSchemaOutputSchema */
13485
14709
  var ConfigUISchemaOutput = zod.z.unknown().nullable();
@@ -13635,9 +14859,42 @@ var deviceManagerCapability = {
13635
14859
  kind: "mutation",
13636
14860
  auth: "admin"
13637
14861
  }),
14862
+ /** Set (or clear) the per-device display override on the meta row. Mirrors
14863
+ * `setChildLayout` persistence; `null` clears the override entirely. The
14864
+ * override unit(s) are normalized (`normalizeUnit`) at write so the render
14865
+ * path's `UNIT_TABLE` lookups always hit canonical spellings. Persisted,
14866
+ * projected, and preserved across re-register/restore. Idempotent. */
14867
+ setDisplay: require_sleep.method(zod.z.object({
14868
+ deviceId: zod.z.number(),
14869
+ display: DeviceDisplayOverrideSchema.nullable()
14870
+ }), zod.z.void(), {
14871
+ kind: "mutation",
14872
+ auth: "admin"
14873
+ }),
14874
+ /** Read the operator-authored per-role display defaults (unit/precision/
14875
+ * icon), keyed by `DeviceRole` string. Empty record when none set. */
14876
+ getRoleDisplayDefaults: require_sleep.method(zod.z.object({}), zod.z.object({ defaults: zod.z.record(zod.z.string(), RoleDisplayDefaultSchema) }), { kind: "query" }),
14877
+ /** Replace the per-role display defaults whole-record (full replace — the
14878
+ * caller sends the complete map). Override unit(s) are normalized at write.
14879
+ * Not per-device, so nothing is emitted; the UI invalidates its own query
14880
+ * on mutate. */
14881
+ setRoleDisplayDefaults: require_sleep.method(zod.z.object({ defaults: zod.z.record(zod.z.string(), RoleDisplayDefaultSchema) }), zod.z.void(), {
14882
+ kind: "mutation",
14883
+ auth: "admin"
14884
+ }),
13638
14885
  /** List the wireable status-schema fields per cap bound to a device.
13639
- * Powers the Wiring tab's field pickers. Caps without a status schema are omitted. */
13640
- getWireableFields: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.object({ caps: zod.z.array(zod.z.object({
14886
+ * Powers the Wiring tab's field pickers. Caps without a status schema are
14887
+ * omitted. Item-array caps (`status.itemArray`, e.g. consumables) also
14888
+ * emit their per-item fields tagged `item: true` (a link targeting one
14889
+ * must carry a `target.itemKey`) plus the cap-level `itemArray`
14890
+ * descriptor. `includeSynthesizable: true` (TARGET pickers only) unions
14891
+ * in unbound device-scoped caps that declare `status.empty` and match
14892
+ * the device's type — so the FIRST link to a synthesize-only cap
14893
+ * (consumables on an HA vacuum) can be authored. */
14894
+ getWireableFields: require_sleep.method(zod.z.object({
14895
+ deviceId: zod.z.number(),
14896
+ includeSynthesizable: zod.z.boolean().optional()
14897
+ }), zod.z.object({ caps: zod.z.array(zod.z.object({
13641
14898
  cap: zod.z.string(),
13642
14899
  fields: zod.z.array(zod.z.object({
13643
14900
  path: zod.z.string(),
@@ -13647,8 +14904,13 @@ var deviceManagerCapability = {
13647
14904
  "boolean",
13648
14905
  "enum"
13649
14906
  ]),
13650
- enumValues: zod.z.array(zod.z.string()).optional()
13651
- })).readonly()
14907
+ enumValues: zod.z.array(zod.z.string()).optional(),
14908
+ item: zod.z.boolean().optional()
14909
+ })).readonly(),
14910
+ itemArray: zod.z.object({
14911
+ path: zod.z.string(),
14912
+ keyField: zod.z.string()
14913
+ }).optional()
13652
14914
  })).readonly() }), { kind: "query" }),
13653
14915
  /** Stamp (or update) the semantic role on the device's meta row.
13654
14916
  * Called by the kernel's `create()` / `spawnAccessoryChild` pre-seed
@@ -13799,7 +15061,11 @@ var deviceManagerCapability = {
13799
15061
  deviceId: zod.z.number(),
13800
15062
  entries: zod.z.array(zod.z.object({
13801
15063
  capName: zod.z.string(),
13802
- kind: zod.z.enum(["native", "wrapped"]),
15064
+ kind: zod.z.enum([
15065
+ "native",
15066
+ "wrapped",
15067
+ "linked"
15068
+ ]),
13803
15069
  providerAddonId: zod.z.string(),
13804
15070
  providerNodeId: zod.z.string(),
13805
15071
  nativeAddonId: zod.z.string()
@@ -13817,7 +15083,11 @@ var deviceManagerCapability = {
13817
15083
  deviceId: zod.z.number(),
13818
15084
  entries: zod.z.array(zod.z.object({
13819
15085
  capName: zod.z.string(),
13820
- kind: zod.z.enum(["native", "wrapped"]),
15086
+ kind: zod.z.enum([
15087
+ "native",
15088
+ "wrapped",
15089
+ "linked"
15090
+ ]),
13821
15091
  providerAddonId: zod.z.string(),
13822
15092
  providerNodeId: zod.z.string(),
13823
15093
  nativeAddonId: zod.z.string()
@@ -15705,7 +16975,10 @@ var pipelineOrchestratorCapability = {
15705
16975
  methods: {
15706
16976
  /**
15707
16977
  * Pin a camera's pipeline to a specific agent (L1 affinity).
15708
- * The orchestrator re-evaluates the assignment immediately.
16978
+ * The orchestrator re-evaluates the assignment immediately and persists
16979
+ * the pin under the canonical `pipelineNodeId` device-store key (the
16980
+ * legacy `preferredAgent` key is nulled on write and kept only as a
16981
+ * read-only fallback for stores written before the unification).
15709
16982
  */
15710
16983
  assignPipeline: require_sleep.method(zod.z.object({
15711
16984
  deviceId: zod.z.number(),
@@ -15716,8 +16989,9 @@ var pipelineOrchestratorCapability = {
15716
16989
  }),
15717
16990
  /**
15718
16991
  * Clear a camera's pipeline pin and let the auto-balancer re-pick
15719
- * the optimal agent. The orchestrator persists `preferredAgent=null`
15720
- * (and `pipelineNodeId='auto'`), then re-runs the balancer with the
16992
+ * the optimal agent. The orchestrator persists the canonical
16993
+ * `pipelineNodeId='auto'` (and nulls the legacy `preferredAgent`),
16994
+ * then re-runs the balancer with the
15721
16995
  * cached `RunnerCameraConfig` and migrates only when the chosen
15722
16996
  * node differs. The camera stays in `getPipelineAssignments()` —
15723
16997
  * just with `pinned=false`. If no runner is currently available
@@ -22556,6 +23830,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
22556
23830
  addonId: null,
22557
23831
  access: "view"
22558
23832
  },
23833
+ "deviceManager.getRoleDisplayDefaults": {
23834
+ capName: "device-manager",
23835
+ capScope: "system",
23836
+ addonId: null,
23837
+ access: "view"
23838
+ },
22559
23839
  "deviceManager.getSettingsSchema": {
22560
23840
  capName: "device-manager",
22561
23841
  capScope: "system",
@@ -22706,6 +23986,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
22706
23986
  addonId: null,
22707
23987
  access: "create"
22708
23988
  },
23989
+ "deviceManager.setDisplay": {
23990
+ capName: "device-manager",
23991
+ capScope: "system",
23992
+ addonId: null,
23993
+ access: "create"
23994
+ },
22709
23995
  "deviceManager.setIntegrationId": {
22710
23996
  capName: "device-manager",
22711
23997
  capScope: "system",
@@ -22748,6 +24034,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
22748
24034
  addonId: null,
22749
24035
  access: "create"
22750
24036
  },
24037
+ "deviceManager.setRoleDisplayDefaults": {
24038
+ capName: "device-manager",
24039
+ capScope: "system",
24040
+ addonId: null,
24041
+ access: "create"
24042
+ },
22751
24043
  "deviceManager.setStreamProfileMap": {
22752
24044
  capName: "device-manager",
22753
24045
  capScope: "system",
@@ -26701,6 +27993,11 @@ exports.DisposerChain = require_sleep.DisposerChain;
26701
27993
  exports.DoorbellPressEventSchema = DoorbellPressEventSchema;
26702
27994
  exports.DoorbellStatusSchema = DoorbellStatusSchema;
26703
27995
  exports.EVENT_PAD_MS = EVENT_PAD_MS;
27996
+ exports.EXPRESSION_BUILTINS = EXPRESSION_BUILTINS;
27997
+ exports.EXPRESSION_BUILTIN_NAMES = EXPRESSION_BUILTIN_NAMES;
27998
+ exports.EXPRESSION_COMPILE_CACHE_CAPACITY = EXPRESSION_COMPILE_CACHE_CAPACITY;
27999
+ exports.EXPRESSION_IDENTIFIER_RE = EXPRESSION_IDENTIFIER_RE;
28000
+ exports.EXPRESSION_INJECTED_NOW = EXPRESSION_INJECTED_NOW;
26704
28001
  exports.ElementConfigStore = ElementConfigStore;
26705
28002
  exports.EmbeddingInfoSchema = EmbeddingInfoSchema;
26706
28003
  exports.EmbeddingResultSchema = EmbeddingResultSchema;
@@ -26719,6 +28016,8 @@ exports.ExportSetupFieldSchema = ExportSetupFieldSchema;
26719
28016
  exports.ExportSetupSchema = ExportSetupSchema;
26720
28017
  exports.ExposedDeviceSchema = ExposedDeviceSchema;
26721
28018
  exports.ExposedResourceSchema = ExposedResourceSchema;
28019
+ exports.ExpressionEvalError = ExpressionEvalError;
28020
+ exports.ExpressionParseError = ExpressionParseError;
26722
28021
  exports.FanControlStatusSchema = FanControlStatusSchema;
26723
28022
  exports.FanDirectionSchema = FanDirectionSchema;
26724
28023
  exports.FeatureManifestSchema = FeatureManifestSchema;
@@ -26757,6 +28056,11 @@ exports.LogEntrySchema = LogEntrySchema;
26757
28056
  exports.LogLevelSchema = LogLevelSchema;
26758
28057
  exports.LogStreamEntrySchema = LogStreamEntrySchema;
26759
28058
  exports.MACRO_LABELS = MACRO_LABELS;
28059
+ exports.MAX_EXPRESSION_AST_NODES = MAX_EXPRESSION_AST_NODES;
28060
+ exports.MAX_EXPRESSION_BINDINGS = MAX_EXPRESSION_BINDINGS;
28061
+ exports.MAX_EXPRESSION_CALL_ARGS = MAX_EXPRESSION_CALL_ARGS;
28062
+ exports.MAX_EXPRESSION_EVAL_STEPS = MAX_EXPRESSION_EVAL_STEPS;
28063
+ exports.MAX_EXPRESSION_SOURCE_LENGTH = MAX_EXPRESSION_SOURCE_LENGTH;
26760
28064
  exports.METHOD_ACCESS_MAP = METHOD_ACCESS_MAP;
26761
28065
  exports.MODEL_FORMATS = MODEL_FORMATS;
26762
28066
  exports.MaskGridDimsSchema = MaskGridDimsSchema;
@@ -26860,6 +28164,7 @@ exports.PtzPresetSchema = PtzPresetSchema;
26860
28164
  exports.PtzStatusSchema = PtzStatusSchema;
26861
28165
  exports.QueryFilterSchema = QueryFilterSchema;
26862
28166
  exports.RECOGNITION_TYPES = RECOGNITION_TYPES;
28167
+ exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
26863
28168
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
26864
28169
  exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
26865
28170
  exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
@@ -26975,7 +28280,9 @@ exports.TrackSchema = TrackSchema;
26975
28280
  exports.TrackStateSchema = TrackStateSchema;
26976
28281
  exports.TrackedDetectionSchema = TrackedDetectionSchema;
26977
28282
  exports.TurnServerSchema = TurnServerSchema;
28283
+ exports.UNIT_TABLE = UNIT_TABLE;
26978
28284
  exports.UnifiedBrokerInfoSchema = BrokerInfoSchema$1;
28285
+ exports.UnitConversionError = UnitConversionError;
26979
28286
  exports.UpdateIntegrationInputSchema = UpdateIntegrationInputSchema;
26980
28287
  exports.UpdateStatusSchema = UpdateStatusSchema;
26981
28288
  exports.UpdateUserInputSchema = UpdateUserInputSchema;
@@ -27046,21 +28353,26 @@ exports.buttonCapability = buttonCapability;
27046
28353
  exports.cameraCredentialsCapability = cameraCredentialsCapability;
27047
28354
  exports.cameraPipelineConfigCapability = cameraPipelineConfigCapability;
27048
28355
  exports.cameraStreamsCapability = cameraStreamsCapability;
28356
+ exports.canConvertUnit = canConvertUnit;
27049
28357
  exports.carbonMonoxideCapability = carbonMonoxideCapability;
27050
28358
  exports.cellsToRects = cellsToRects;
27051
28359
  exports.classifyStream = classifyStream;
27052
28360
  exports.classifyStreams = classifyStreams;
27053
28361
  exports.climateControlCapability = climateControlCapability;
27054
28362
  exports.colorCapability = colorCapability;
28363
+ exports.compileExpression = compileExpression;
28364
+ exports.compileExpressionSafe = compileExpressionSafe;
27055
28365
  exports.connectivityCapability = connectivityCapability;
27056
28366
  exports.consumablesCapability = consumablesCapability;
27057
28367
  exports.contactCapability = contactCapability;
27058
28368
  exports.controlCapability = controlCapability;
28369
+ exports.convertUnit = convertUnit;
27059
28370
  exports.cosineSimilarity = cosineSimilarity;
27060
28371
  exports.coverCapability = coverCapability;
27061
28372
  exports.createDeviceProxy = require_sleep.createDeviceProxy;
27062
28373
  exports.createDurableState = require_sleep.createDurableState;
27063
28374
  exports.createEvent = require_sleep.createEvent;
28375
+ exports.createExpressionScope = createExpressionScope;
27064
28376
  exports.createLazyTrpcSource = require_sleep.createLazyTrpcSource;
27065
28377
  exports.createMirrorSource = require_sleep.createMirrorSource;
27066
28378
  exports.createRuntimeStateBridge = createRuntimeStateBridge;
@@ -27088,8 +28400,11 @@ exports.emitDownForOwnedCaps = require_sleep.emitDownForOwnedCaps;
27088
28400
  exports.emitReadiness = require_sleep.emitReadiness;
27089
28401
  exports.encodeProfileFromStreamShape = encodeProfileFromStreamShape;
27090
28402
  exports.enumSensorCapability = enumSensorCapability;
28403
+ exports.enumerateItemArrayFields = enumerateItemArrayFields;
27091
28404
  exports.enumerateSchemaFields = enumerateSchemaFields;
27092
28405
  exports.errMsg = require_err_msg.errMsg;
28406
+ exports.evaluateAst = evaluateAst;
28407
+ exports.evaluateLinkExpression = evaluateLinkExpression;
27093
28408
  exports.evaluateZoneRules = evaluateZoneRules;
27094
28409
  exports.event = require_sleep.event;
27095
28410
  exports.eventEmitterCapability = eventEmitterCapability;
@@ -27165,6 +28480,7 @@ exports.numericSensorCapability = numericSensorCapability;
27165
28480
  exports.oauthIntegrationCapability = oauthIntegrationCapability;
27166
28481
  exports.osdCapability = osdCapability;
27167
28482
  exports.parseCameraStreamConfig = parseCameraStreamConfig;
28483
+ exports.parseExpression = parseExpression;
27168
28484
  exports.parseJsonArray = require_sleep.parseJsonArray;
27169
28485
  exports.parseJsonObject = require_sleep.parseJsonObject;
27170
28486
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
@@ -27238,14 +28554,20 @@ exports.taskTargetSchema = taskTargetSchema;
27238
28554
  exports.temperatureSensorCapability = temperatureSensorCapability;
27239
28555
  exports.textToHtml = textToHtml;
27240
28556
  exports.toDeviceSummary = toDeviceSummary;
28557
+ exports.toExpressionValue = toExpressionValue;
27241
28558
  exports.toStreamSourceEntry = toStreamSourceEntry;
27242
28559
  exports.toastCapability = toastCapability;
28560
+ exports.tokenize = tokenize;
27243
28561
  exports.transcodeBody = transcodeBody;
28562
+ exports.tryConvertUnit = tryConvertUnit;
27244
28563
  exports.turnProviderCapability = turnProviderCapability;
28564
+ exports.unitDimension = unitDimension;
28565
+ exports.unitsForDimension = unitsForDimension;
27245
28566
  exports.updateCapability = updateCapability;
27246
28567
  exports.userManagementCapability = userManagementCapability;
27247
28568
  exports.userPasskeysCapability = userPasskeysCapability;
27248
28569
  exports.vacuumControlCapability = vacuumControlCapability;
28570
+ exports.validateExpressionSource = validateExpressionSource;
27249
28571
  exports.valveCapability = valveCapability;
27250
28572
  exports.vibrationCapability = vibrationCapability;
27251
28573
  exports.videoclipsCapability = videoclipsCapability;