@camstack/types 1.2.50 → 1.2.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Recipe → TypeScript. The half that makes "a visual automation compiles to a
3
+ * block; it does not get an engine" true rather than aspirational
4
+ * (design decision 4, `docs/design/2026-08-08-automation-scripts-custom-devices.md`).
5
+ *
6
+ * **The generated source is derived, never authoritative.** It is regenerated on
7
+ * every compile and shown read-only; the RECIPE is what the operator edited.
8
+ * That resolves the open question §8.2 left to this phase ("is the generated
9
+ * code stored for audit, or regenerated?"): storing it creates a second copy
10
+ * that can disagree with the recipe, and a second copy that can disagree is the
11
+ * failure this whole chapter exists to avoid.
12
+ *
13
+ * The recipe travels INSIDE the generated file as `AUTOMATION_RECIPE`, a frozen
14
+ * JSON literal. That is deliberate and it is not a second store: there is still
15
+ * exactly one row in `core_blocks`, and the recipe is part of the one artefact
16
+ * that row holds. It is what lets the editor re-open a visually-authored
17
+ * automation without `CoreBlock.source` having shipped yet — and when the
18
+ * `source` union does ship, this literal becomes redundant and is dropped in the
19
+ * same change that starts persisting the recipe properly.
20
+ *
21
+ * Nothing here evaluates anything. The generated code is compiled by esbuild
22
+ * inside the block pipeline exactly like hand-written code — no `eval`, no
23
+ * `new Function`, no `vm`, which `scripts/check-no-dynamic-eval.ts` enforces
24
+ * repo-wide.
25
+ */
26
+ import type { AutomationRecipe } from './recipe.js';
27
+ export interface GenerateAutomationBlockInput {
28
+ /** The block's name — the ONE name for the artefact (§2.1). */
29
+ readonly name: string;
30
+ readonly recipe: AutomationRecipe;
31
+ }
32
+ /**
33
+ * Compile a recipe to the TypeScript of a core block.
34
+ *
35
+ * Throws when the recipe is out of bounds — generation is not the place to
36
+ * discover that, but it is the last place it can still be caught before code
37
+ * that the operator did not author starts running.
38
+ */
39
+ export declare function generateAutomationBlock(input: GenerateAutomationBlockInput): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Automation recipes — the declarative half of a visually-authored automation,
3
+ * and the generator that turns one into the TypeScript of an ordinary core
4
+ * block. There is no engine here and there must never be one: the block runner
5
+ * is the runtime and `core_blocks` is the store.
6
+ *
7
+ * This is the module barrel; leaf files inside @camstack/types MUST import from
8
+ * the deep modules (e.g. `../automation/recipe.js`), never through this file OR
9
+ * the root barrel (see biome-plugins/no-types-barrel-leaf-import.grit).
10
+ */
11
+ export type { GenerateAutomationBlockInput } from './codegen.js';
12
+ export { generateAutomationBlock } from './codegen.js';
13
+ export type { AutomationAction, AutomationCondition, AutomationConditionAll, AutomationConditionAny, AutomationConditionExpression, AutomationConditionLeaf, AutomationConditionNot, AutomationConditionOperator, AutomationRecipe, AutomationTrigger, } from './recipe.js';
14
+ export { AutomationActionSchema, AutomationConditionOperatorSchema, AutomationConditionSchema, AutomationRecipeSchema, AutomationTriggerSchema, conditionDepth, countConditionLeaves, MAX_CONDITION_DEPTH, MAX_CONDITION_LEAVES, validateRecipeBounds, } from './recipe.js';
@@ -0,0 +1,194 @@
1
+ /**
2
+ * The automation RECIPE — what a visually-authored automation is, before it
3
+ * becomes code.
4
+ *
5
+ * This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
6
+ * Phase 6. A recipe is not an engine and never evaluates anything: it is a
7
+ * declarative description that {@link generateAutomationBlock} compiles to the
8
+ * TypeScript of an ordinary core block. There is therefore no second rule
9
+ * engine, no second store and no second runtime — the block runner is the
10
+ * runtime, `core_blocks` is the store (§2.1).
11
+ *
12
+ * Three vocabularies, each salvaged rather than invented:
13
+ *
14
+ * - **Triggers** — the only genuinely new surface. Four members, none of which
15
+ * touches notification delivery. The NC keeps its four *pipeline* triggers;
16
+ * these are device/time triggers and the two sets do not overlap.
17
+ * - **Conditions** — `ALL`/`ANY`/`NOT` over two leaf kinds, bounded. The
18
+ * expression leaf is the bounded engine at `../expression/`, which is where
19
+ * arithmetic between two device fields lives and the only place it lives.
20
+ * - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
21
+ * fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
22
+ * this had to diverge from "verbatim", and why.
23
+ *
24
+ * Bounds are a guard, not a paragraph (§3.2.1): depth ≤ 3, ≤ 32 leaves. They are
25
+ * enforced by {@link validateRecipeBounds} AND by the schema, so a recipe that
26
+ * arrives over the wire cannot skip the check that the editor applies.
27
+ */
28
+ import { z } from 'zod';
29
+ import { type ExpressionBindingSource } from '../expression/binding-source.js';
30
+ /**
31
+ * Depth of a condition tree, counting the root container as level 1.
32
+ *
33
+ * Three is enough and is not arbitrary: `NOT` over a group is always rewritable
34
+ * by De Morgan (`not(any(a,b))` ≡ `all(not a, not b)`), so the shapes that would
35
+ * need a fourth level have an equivalent at three. An editor that refuses depth
36
+ * 4 should offer that rewrite rather than refusing blankly.
37
+ */
38
+ export declare const MAX_CONDITION_DEPTH = 3;
39
+ /** Leaves per automation. A UI that grows without a bound becomes a language. */
40
+ export declare const MAX_CONDITION_LEAVES = 32;
41
+ /** How a leaf compares a device field to a value. Derived from the field's
42
+ * `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
43
+ export declare const AutomationConditionOperatorSchema: z.ZodEnum<{
44
+ in: "in";
45
+ gte: "gte";
46
+ eq: "eq";
47
+ ne: "ne";
48
+ gt: "gt";
49
+ lt: "lt";
50
+ lte: "lte";
51
+ contains: "contains";
52
+ }>;
53
+ export type AutomationConditionOperator = z.infer<typeof AutomationConditionOperatorSchema>;
54
+ /** A catalog leaf: one device field compared to one value. */
55
+ export interface AutomationConditionLeaf {
56
+ readonly kind: 'condition';
57
+ readonly deviceId: number;
58
+ readonly cap: string;
59
+ readonly fieldPath: string;
60
+ readonly operator: AutomationConditionOperator;
61
+ readonly value: string | number | boolean | readonly (string | number)[];
62
+ }
63
+ /** The escape hatch: the bounded expression engine over named device bindings. */
64
+ export interface AutomationConditionExpression {
65
+ readonly kind: 'expression';
66
+ readonly expr: string;
67
+ readonly bindings: Readonly<Record<string, ExpressionBindingSource>>;
68
+ }
69
+ export interface AutomationConditionAll {
70
+ readonly kind: 'all';
71
+ readonly children: readonly AutomationCondition[];
72
+ }
73
+ export interface AutomationConditionAny {
74
+ readonly kind: 'any';
75
+ readonly children: readonly AutomationCondition[];
76
+ }
77
+ export interface AutomationConditionNot {
78
+ readonly kind: 'not';
79
+ readonly child: AutomationCondition;
80
+ }
81
+ export type AutomationCondition = AutomationConditionAll | AutomationConditionAny | AutomationConditionNot | AutomationConditionLeaf | AutomationConditionExpression;
82
+ export declare const AutomationConditionSchema: z.ZodType<AutomationCondition>;
83
+ /**
84
+ * What starts a run.
85
+ *
86
+ * D8 compliance, and it is the reason `device-state` is not merely an event
87
+ * subscription: the trigger evaluates against the **state mirror**, which is
88
+ * reconciled, and an event only WAKES the evaluation. A dropped event therefore
89
+ * DELAYS a trigger; it does not lose it. `schedule` uses `croner` — the one
90
+ * already in the repo — because `setInterval(24h)` drifts and "at 23:30" does
91
+ * not.
92
+ */
93
+ export declare const AutomationTriggerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
94
+ kind: z.ZodLiteral<"device-state">;
95
+ deviceId: z.ZodNumber;
96
+ cap: z.ZodString;
97
+ fieldPath: z.ZodString;
98
+ becomes: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
99
+ edge: z.ZodOptional<z.ZodBoolean>;
100
+ forMs: z.ZodOptional<z.ZodNumber>;
101
+ debounceMs: z.ZodOptional<z.ZodNumber>;
102
+ }, z.core.$strip>, z.ZodObject<{
103
+ kind: z.ZodLiteral<"device-event">;
104
+ category: z.ZodString;
105
+ deviceId: z.ZodOptional<z.ZodNumber>;
106
+ }, z.core.$strip>, z.ZodObject<{
107
+ kind: z.ZodLiteral<"schedule">;
108
+ cron: z.ZodString;
109
+ }, z.core.$strip>, z.ZodObject<{
110
+ kind: z.ZodLiteral<"manual">;
111
+ }, z.core.$strip>], "kind">;
112
+ export type AutomationTrigger = z.infer<typeof AutomationTriggerSchema>;
113
+ /**
114
+ * One action step.
115
+ *
116
+ * `wait` and `cap` are `NcRuleActionSchema`'s two members, kept structurally
117
+ * identical so `NcRuleActionRunner` runs them unchanged — its device-scope
118
+ * check, stop-at-first-failure and per-sequence throttle are the whole reason
119
+ * to reuse it, and none of them are re-implemented here.
120
+ *
121
+ * **The one divergence, and it is forced.** `NcRuleActionSchema.cap.deviceId` is
122
+ * a literal `z.number().int()`, and the NC runner's own `RunSequencesInput`
123
+ * documents its subject device as *"for the log tag, never for routing"*. So an
124
+ * NC action can never target the device that triggered it — which is fine for
125
+ * the NC (its rules already scope to a device) and fatal for an automation
126
+ * ("sound the siren of the camera that saw the person"). `deviceId` therefore
127
+ * also accepts `{ $var }`, resolved from the run's `vars` bag BEFORE the runner
128
+ * is called. The runner still receives a number and is untouched; the
129
+ * resolution is the recipe's job, not the runner's.
130
+ */
131
+ export declare const AutomationActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
132
+ kind: z.ZodLiteral<"wait">;
133
+ seconds: z.ZodNumber;
134
+ }, z.core.$strip>, z.ZodObject<{
135
+ kind: z.ZodLiteral<"cap">;
136
+ deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodObject<{
137
+ $var: z.ZodString;
138
+ }, z.core.$strip>]>;
139
+ cap: z.ZodString;
140
+ method: z.ZodString;
141
+ args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
142
+ }, z.core.$strip>, z.ZodObject<{
143
+ kind: z.ZodLiteral<"code">;
144
+ code: z.ZodString;
145
+ }, z.core.$strip>], "kind">;
146
+ export type AutomationAction = z.infer<typeof AutomationActionSchema>;
147
+ export declare const AutomationRecipeSchema: z.ZodObject<{
148
+ triggers: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
149
+ kind: z.ZodLiteral<"device-state">;
150
+ deviceId: z.ZodNumber;
151
+ cap: z.ZodString;
152
+ fieldPath: z.ZodString;
153
+ becomes: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
154
+ edge: z.ZodOptional<z.ZodBoolean>;
155
+ forMs: z.ZodOptional<z.ZodNumber>;
156
+ debounceMs: z.ZodOptional<z.ZodNumber>;
157
+ }, z.core.$strip>, z.ZodObject<{
158
+ kind: z.ZodLiteral<"device-event">;
159
+ category: z.ZodString;
160
+ deviceId: z.ZodOptional<z.ZodNumber>;
161
+ }, z.core.$strip>, z.ZodObject<{
162
+ kind: z.ZodLiteral<"schedule">;
163
+ cron: z.ZodString;
164
+ }, z.core.$strip>, z.ZodObject<{
165
+ kind: z.ZodLiteral<"manual">;
166
+ }, z.core.$strip>], "kind">>;
167
+ conditions: z.ZodOptional<z.ZodType<AutomationCondition, unknown, z.core.$ZodTypeInternals<AutomationCondition, unknown>>>;
168
+ actions: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
169
+ kind: z.ZodLiteral<"wait">;
170
+ seconds: z.ZodNumber;
171
+ }, z.core.$strip>, z.ZodObject<{
172
+ kind: z.ZodLiteral<"cap">;
173
+ deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodObject<{
174
+ $var: z.ZodString;
175
+ }, z.core.$strip>]>;
176
+ cap: z.ZodString;
177
+ method: z.ZodString;
178
+ args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
179
+ }, z.core.$strip>, z.ZodObject<{
180
+ kind: z.ZodLiteral<"code">;
181
+ code: z.ZodString;
182
+ }, z.core.$strip>], "kind">>;
183
+ }, z.core.$strip>;
184
+ export type AutomationRecipe = z.infer<typeof AutomationRecipeSchema>;
185
+ /** Depth of a tree, root container = 1. A bare leaf is depth 1. */
186
+ export declare function conditionDepth(cond: AutomationCondition): number;
187
+ /** Leaves only — containers do not count toward the leaf budget. */
188
+ export declare function countConditionLeaves(cond: AutomationCondition): number;
189
+ /**
190
+ * Author-time bounds check. Returns `null` when the recipe is legal, else a
191
+ * message naming the bound it broke — the editor shows it verbatim, so it has
192
+ * to read like something an operator can act on.
193
+ */
194
+ export declare function validateRecipeBounds(recipe: AutomationRecipe): string | null;
@@ -30,6 +30,23 @@ import { type InferProvider } from './capability-definition.js';
30
30
  * too many — a drift would show as a logs pane that is simply always empty.
31
31
  */
32
32
  export declare const CORE_BLOCK_ADDON_PREFIX = "core-block-";
33
+ /**
34
+ * The builtin's own addon id — and, because of that, the OWNER of the
35
+ * integration every block's devices hang from.
36
+ *
37
+ * A block cannot own one. Every integration read is gated on the installed-addon
38
+ * set (`addon-registry.service.ts` → `createFilteredRegistry`) and a block's
39
+ * `core-block-<uuid>` is never in it, so its `getIntegrationByAddonId` answers
40
+ * null forever while the write succeeds — one invisible, undeletable row per
41
+ * start. `core-blocks` IS installed, so it reconciles one shared integration and
42
+ * a block names it by id (`DeclaredDevicesSpec.integrationId`), minting nothing.
43
+ *
44
+ * Exported from the contract, next to the runner prefix and for the same reason:
45
+ * a block reads it as
46
+ * `ctx.api.integrations.getByAddonId.query({ addonId: CORE_BLOCKS_ADDON_ID })`,
47
+ * so a second copy of the string is a block that silently declares into nowhere.
48
+ */
49
+ export declare const CORE_BLOCKS_ADDON_ID = "core-blocks";
33
50
  /** The addon/runner id a block's process runs under. */
34
51
  export declare function coreBlockAddonId(blockId: string): string;
35
52
  /** The block id behind a generated addon/runner id, or null when the id belongs
@@ -38,7 +38,7 @@ export { cameraStreamsCapability, type ICameraStreamsProvider, type PickedCamStr
38
38
  export { extractNestedAddonId, isArrayOutputSchema, isCollectionArrayMethod, isObjectInput, isVoidInput, kebabToCamel, looseSchema, objectInputDeclaresAddonId, procedureAuthKey, } from './cap-router-predicates.js';
39
39
  export type { CapabilityDefinition, CapabilityEventSchema, CapabilityMethodAuth, CapabilityMethodCaller, CapabilityMethodKind, CapabilityMethodOptions, CapabilityMethodSchema, CapabilityMountHint, CapabilityMountKind, CapabilityStatusItemArray, CapabilityStatusKind, CapabilityStatusSchema, CapCaller, DeviceConfigDerivedFormUi, DeviceConfigSpec, DeviceConfigUiSpec, DeviceConfigWidgetUi, DeviceSettingsContribution, InferDeviceProxyCap, InferEvents, InferName, InferNativeProvider, InferProvider, InferRuntimeState, ProviderKind, UiContribution, UiContributionKind, UiContributionRemote, } from './capability-definition.js';
40
40
  export { DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, event, expandCapMethods, isDeviceConfigCap, method, resolveCapMount, } from './capability-definition.js';
41
- export { CORE_BLOCK_ADDON_PREFIX, type CoreBlock, type CoreBlockCompileResult, CoreBlockCompileResultSchema, type CoreBlockInput, CoreBlockInputSchema, type CoreBlockPlacement, CoreBlockPlacementSchema, CoreBlockSchema, type CoreBlockStatus, CoreBlockStatusSchema, coreBlockAddonId, coreBlockIdFromAddonId, coreBlocksCapability, type ICoreBlocksProvider, } from './core-blocks.cap.js';
41
+ export { CORE_BLOCK_ADDON_PREFIX, CORE_BLOCKS_ADDON_ID, type CoreBlock, type CoreBlockCompileResult, CoreBlockCompileResultSchema, type CoreBlockInput, CoreBlockInputSchema, type CoreBlockPlacement, CoreBlockPlacementSchema, CoreBlockSchema, type CoreBlockStatus, CoreBlockStatusSchema, coreBlockAddonId, coreBlockIdFromAddonId, coreBlocksCapability, type ICoreBlocksProvider, } from './core-blocks.cap.js';
42
42
  export * from './custom-actions.js';
43
43
  export type { CustomModelDescriptor, ICustomModelRegistryProvider, } from './custom-model-registry.cap.js';
44
44
  export { CustomModelDescriptorSchema, customModelRegistryCapability, } from './custom-model-registry.cap.js';
@@ -149,4 +149,4 @@ export declare const mqttBrokerCapability: {
149
149
  };
150
150
  };
151
151
  export type IMqttBrokerProvider = InferProvider<typeof mqttBrokerCapability>;
152
- export { BrokerInfoSchema, BrokerConnectionDetailsSchema, AddBrokerInputSchema, StartEmbeddedInputSchema, StatusSchema as MqttBrokerStatusSchema, };
152
+ export { AddBrokerInputSchema, BrokerConnectionDetailsSchema, BrokerInfoSchema, StartEmbeddedInputSchema, StatusSchema as MqttBrokerStatusSchema, };
@@ -403,6 +403,7 @@ declare const TrackSchema: z.ZodObject<{
403
403
  mediaKey: z.ZodString;
404
404
  }, z.core.$strip>>>;
405
405
  zonesVisited: z.ZodReadonly<z.ZodArray<z.ZodString>>;
406
+ zoneNames: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
406
407
  classes: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
407
408
  totalDistance: z.ZodNumber;
408
409
  state: z.ZodEnum<{
@@ -1047,6 +1048,7 @@ declare const RecentTracksPageSchema: z.ZodObject<{
1047
1048
  mediaKey: z.ZodString;
1048
1049
  }, z.core.$strip>>>;
1049
1050
  zonesVisited: z.ZodReadonly<z.ZodArray<z.ZodString>>;
1051
+ zoneNames: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1050
1052
  classes: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1051
1053
  totalDistance: z.ZodNumber;
1052
1054
  state: z.ZodEnum<{
@@ -1291,6 +1293,7 @@ export declare const pipelineAnalyticsCapability: {
1291
1293
  mediaKey: z.ZodString;
1292
1294
  }, z.core.$strip>>>;
1293
1295
  zonesVisited: z.ZodReadonly<z.ZodArray<z.ZodString>>;
1296
+ zoneNames: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1294
1297
  classes: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1295
1298
  totalDistance: z.ZodNumber;
1296
1299
  state: z.ZodEnum<{
@@ -1382,6 +1385,7 @@ export declare const pipelineAnalyticsCapability: {
1382
1385
  mediaKey: z.ZodString;
1383
1386
  }, z.core.$strip>>>;
1384
1387
  zonesVisited: z.ZodReadonly<z.ZodArray<z.ZodString>>;
1388
+ zoneNames: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1385
1389
  classes: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1386
1390
  totalDistance: z.ZodNumber;
1387
1391
  state: z.ZodEnum<{
@@ -1495,6 +1499,7 @@ export declare const pipelineAnalyticsCapability: {
1495
1499
  mediaKey: z.ZodString;
1496
1500
  }, z.core.$strip>>>;
1497
1501
  zonesVisited: z.ZodReadonly<z.ZodArray<z.ZodString>>;
1502
+ zoneNames: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1498
1503
  classes: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1499
1504
  totalDistance: z.ZodNumber;
1500
1505
  state: z.ZodEnum<{
@@ -1604,6 +1609,7 @@ export declare const pipelineAnalyticsCapability: {
1604
1609
  mediaKey: z.ZodString;
1605
1610
  }, z.core.$strip>>>;
1606
1611
  zonesVisited: z.ZodReadonly<z.ZodArray<z.ZodString>>;
1612
+ zoneNames: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1607
1613
  classes: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
1608
1614
  totalDistance: z.ZodNumber;
1609
1615
  state: z.ZodEnum<{
@@ -109,8 +109,29 @@ export interface DeviceDeclaration<T extends IDevice = IDevice> {
109
109
  /** Where the declaration is owned. */
110
110
  export type DeclarationPlacement = 'hub' | 'this-node';
111
111
  export interface DeclaredDevicesSpec {
112
- /** Display name of the FIXED integration the devices hang from. */
112
+ /** Display name of the FIXED integration the devices hang from. Unused when
113
+ * {@link integrationId} names an existing one. */
113
114
  readonly integrationName: string;
115
+ /**
116
+ * Hang the devices off an integration that ALREADY EXISTS, owned by somebody
117
+ * else. When set, nothing is minted and `fixed` is not re-asserted — the
118
+ * owner of that integration is responsible for both.
119
+ *
120
+ * This exists because a declarer is not always an installed addon. Every
121
+ * integration READ is filtered by `installedAddonIds`
122
+ * (`addon-registry.service.ts` → `createFilteredRegistry`), and a core
123
+ * block's addon id (`core-block-<uuid>`) is never in that set: its
124
+ * `getIntegration` answers null forever, so the mint branch runs on every
125
+ * start and leaks a row `listIntegrations` hides and `deleteIntegration`
126
+ * refuses to delete. Measured on the live hub, 2026-08-09 — two starts of one
127
+ * block left `int_0021` and `int_0022` orphaned and unreachable.
128
+ *
129
+ * A block therefore hangs its devices off the integration the `core-blocks`
130
+ * builtin owns. Ownership of the DEVICE is unaffected: that is
131
+ * `(addonId, stableId)`, and `listOwnDevices` stays addon-scoped, which is
132
+ * what keeps one block's withdrawal sweep away from another's devices.
133
+ */
134
+ readonly integrationId?: string;
114
135
  readonly devices: readonly DeviceDeclaration[];
115
136
  /**
116
137
  * Default `'hub'`: a declaration is cluster state and belongs to the node
package/dist/index.d.ts CHANGED
@@ -112,6 +112,8 @@ export * from './types/pipeline.js';
112
112
  export type { AvailableEngine, PipelineAddonSchema, PipelineDefaultStep, PipelineModelOption, PipelineSchema, PipelineSlotSchema, PipelineTemplate, PipelineTemplateStep, TemplateValidationResult, } from './types/pipeline-schema.js';
113
113
  export * from './types/pipeline-step.js';
114
114
  export * from './types/tracked.js';
115
+ export type { AutomationAction, AutomationCondition, AutomationConditionAll, AutomationConditionAny, AutomationConditionExpression, AutomationConditionLeaf, AutomationConditionNot, AutomationConditionOperator, AutomationRecipe, AutomationTrigger, GenerateAutomationBlockInput, } from './automation/index.js';
116
+ export { AutomationActionSchema, AutomationConditionOperatorSchema, AutomationConditionSchema, AutomationRecipeSchema, AutomationTriggerSchema, conditionDepth, countConditionLeaves, generateAutomationBlock, MAX_CONDITION_DEPTH, MAX_CONDITION_LEAVES, validateRecipeBounds, } from './automation/index.js';
115
117
  export * from './cap-call-context.js';
116
118
  export * from './capabilities/index.js';
117
119
  export { APPLE_SA_TO_MACRO, AUDIO_MACRO_LABELS, getAudioMacroClassIds, mapAudioLabelToMacro, YAMNET_TO_MACRO, } from './catalogs/audio-classmap.js';