@camstack/types 1.2.453 → 1.2.455

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.455] - 2026-10-31
11
+
12
+ ### Changed
13
+
14
+ - Notification verdicts carry the subject and reach the retrain listing (new optional verdict fields on retrain frames and tracks)
15
+ - An automation or notification step now names its devices as a target (named devices, or every device that offers an action, optionally in one room), and a stored step is read in that shape
16
+ - Schedules take a time spec: a clock time, a sun event (sunrise, sunset, dawn, dusk) with an offset, or a date; the site now reports its time zone
17
+
18
+ ## [1.2.454] - 2026-10-31
19
+
20
+ ### Changed
21
+
22
+ - Remote layout groups accept an optional size and hideTitle for dividers
23
+
10
24
  ## [1.2.453] - 2026-10-30
11
25
 
12
26
  ### Changed
@@ -0,0 +1,86 @@
1
+ /**
2
+ * WHO an action step acts on (D983) — one target shape for every place that
3
+ * performs an action (automation steps, Action entries, notification
4
+ * sequences, scheduler points):
5
+ *
6
+ * - `devices` — device ids the operator picked, frozen at save time;
7
+ * - `domain` — every device that offers the step's VOCABULARY action,
8
+ * optionally narrowed to one device type and one room, resolved WHEN THE
9
+ * STEP RUNS (a device added later is included). The action always comes
10
+ * from the vocabulary (`cover.open`), never from a raw cap name, so a
11
+ * `domain` target needs an `actionId` (enforced on the step schema);
12
+ * - `var` — a `$var` resolved from the run's scope.
13
+ *
14
+ * {@link ActionTargetResolver} turns a target into device ids. It is pure
15
+ * (a {@link DeviceDirectory} is handed in) so the hub's core-blocks service and
16
+ * a generated automation block share it.
17
+ *
18
+ * A room is the device's `location` label (`DeviceInfo.location`); two labels
19
+ * name the same room when `locationIconKey` agrees (trimmed, lower-cased).
20
+ */
21
+ import { z } from 'zod';
22
+ import { DeviceType } from '../device/device-type.js';
23
+ import { EventCategory } from '../enums/event-category.js';
24
+ import type { IScopedLogger } from '../interfaces/logging.js';
25
+ export declare const ActionTargetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
26
+ kind: z.ZodLiteral<"devices">;
27
+ ids: z.ZodArray<z.ZodNumber>;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ kind: z.ZodLiteral<"domain">;
30
+ deviceType: z.ZodOptional<z.ZodEnum<typeof DeviceType>>;
31
+ roomId: z.ZodOptional<z.ZodString>;
32
+ }, z.core.$strip>, z.ZodObject<{
33
+ kind: z.ZodLiteral<"var">;
34
+ name: z.ZodString;
35
+ }, z.core.$strip>], "kind">;
36
+ export type ActionTarget = z.infer<typeof ActionTargetSchema>;
37
+ /** What a resolver needs to know about one device. */
38
+ export interface DirectoryDevice {
39
+ readonly id: number;
40
+ readonly type: DeviceType;
41
+ /** The room label, `null` when unset. */
42
+ readonly location: string | null;
43
+ readonly disabled: boolean;
44
+ }
45
+ /**
46
+ * The device registry as the resolver sees it. `listDeviceIdsByCaps` is the
47
+ * per-binding walk (D12/D13) done where the bindings live; `listDevices`
48
+ * reads the listed devices' type and room.
49
+ */
50
+ export interface DeviceDirectory {
51
+ listDeviceIdsByCaps(capNames: readonly string[]): Promise<readonly number[]>;
52
+ listDevices(deviceIds: readonly number[]): Promise<readonly DirectoryDevice[]>;
53
+ }
54
+ /** The events after which a cached domain is stale. */
55
+ export declare const ACTION_TARGET_INVALIDATION_EVENTS: readonly EventCategory[];
56
+ /** The result of resolving a target. */
57
+ export interface ResolvedTarget {
58
+ readonly deviceIds: readonly number[];
59
+ /** Set when nothing matched — the reason, for the trace. */
60
+ readonly emptyReason?: string;
61
+ }
62
+ /** Who asked, for the log line of an empty resolution. */
63
+ export interface ResolveOrigin {
64
+ readonly actionId: string | undefined;
65
+ /** The automation / action / scheduler the step belongs to. */
66
+ readonly owner: string | undefined;
67
+ }
68
+ /** The bag `var` targets read from. */
69
+ export type TargetVars = Readonly<Record<string, unknown>>;
70
+ export declare class ActionTargetResolver {
71
+ private readonly directory;
72
+ private readonly logger;
73
+ /** cap name → its devices, until a registry event says otherwise. */
74
+ private readonly byCap;
75
+ constructor(directory: DeviceDirectory, logger: IScopedLogger);
76
+ /** Forget every cached domain (a device was added, removed, moved or re-bound). */
77
+ invalidate(): void;
78
+ private devicesOfCap;
79
+ /**
80
+ * The devices `target` means for `actionId` right now. An EMPTY answer is
81
+ * logged and carries its reason — the caller traces it; it is never silent.
82
+ * Throws when the target cannot be resolved at all (an unknown action, a
83
+ * `var` that is not a device id).
84
+ */
85
+ resolve(target: ActionTarget, actionId: string | undefined, vars: TargetVars, origin: ResolveOrigin): Promise<ResolvedTarget>;
86
+ }
@@ -86,9 +86,21 @@ export declare const ActionEntrySchema: z.ZodObject<{
86
86
  seconds: z.ZodNumber;
87
87
  }, z.core.$strip>, z.ZodObject<{
88
88
  kind: z.ZodLiteral<"cap">;
89
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
89
+ actionId: z.ZodOptional<z.ZodString>;
90
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
91
+ kind: z.ZodLiteral<"devices">;
92
+ ids: z.ZodArray<z.ZodNumber>;
93
+ }, z.core.$strip>, z.ZodObject<{
94
+ kind: z.ZodLiteral<"domain">;
95
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
96
+ roomId: z.ZodOptional<z.ZodString>;
97
+ }, z.core.$strip>, z.ZodObject<{
98
+ kind: z.ZodLiteral<"var">;
99
+ name: z.ZodString;
100
+ }, z.core.$strip>], "kind">>;
101
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
90
102
  $var: z.ZodString;
91
- }, z.core.$strip>]>;
103
+ }, z.core.$strip>]>>;
92
104
  cap: z.ZodString;
93
105
  method: z.ZodString;
94
106
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -122,9 +134,21 @@ export declare const ActionInputSchema: z.ZodObject<{
122
134
  seconds: z.ZodNumber;
123
135
  }, z.core.$strip>, z.ZodObject<{
124
136
  kind: z.ZodLiteral<"cap">;
125
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
137
+ actionId: z.ZodOptional<z.ZodString>;
138
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
139
+ kind: z.ZodLiteral<"devices">;
140
+ ids: z.ZodArray<z.ZodNumber>;
141
+ }, z.core.$strip>, z.ZodObject<{
142
+ kind: z.ZodLiteral<"domain">;
143
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
144
+ roomId: z.ZodOptional<z.ZodString>;
145
+ }, z.core.$strip>, z.ZodObject<{
146
+ kind: z.ZodLiteral<"var">;
147
+ name: z.ZodString;
148
+ }, z.core.$strip>], "kind">>;
149
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
126
150
  $var: z.ZodString;
127
- }, z.core.$strip>]>;
151
+ }, z.core.$strip>]>>;
128
152
  cap: z.ZodString;
129
153
  method: z.ZodString;
130
154
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -18,10 +18,10 @@ export type { StationaryChangedInput, StationaryDecision, StationaryTrigger, Sta
18
18
  export { evaluateStationaryTrigger } from './stationary-trigger.js';
19
19
  export type { CooldownInput, CooldownVerdict, RunGateAdmission } from './run-gate.js';
20
20
  export { RunGate } from './run-gate.js';
21
- export type { AutomationConditionTraceNode, AutomationRunOutcome, AutomationRunTrace, AutomationRunVerdict, AutomationStateDoc, AutomationStepTrace, AutomationTriggerTrace, TraceNodeOutcome, TraceValue, } from './trace.js';
22
- export { AutomationConditionTraceNodeSchema, EMPTY_AUTOMATION_STATE, mergeSkipTraces, AutomationRunOutcomeSchema, AutomationRunTraceSchema, AutomationRunVerdictSchema, AutomationStateDocSchema, AutomationStepTraceSchema, AutomationTriggerTraceSchema, appendTrace, buildStepArgsTrace, buildTriggerTrace, isSkippedTrace, MAX_TRACE_ARG_LENGTH, MAX_TRACE_ARGS, REDACTED_ARG, skipSignature, TraceNodeOutcomeSchema, TraceValueSchema, } from './trace.js';
23
- export type { ActionStep, AutomationAction, AutomationCapStep, AutomationRecipe, AutomationRunMode, LegacyAutomationRecipe, } from './recipe.js';
24
- export { ActionStepSchema, AutomationActionSchema, AutomationCapStepSchema, AutomationWaitStepSchema, AutomationRecipeSchema, AutomationRunModeSchema, CurrentAutomationRecipeSchema, convertLegacyRecipe, DEFAULT_TRACE_KEEP, LegacyAutomationRecipeSchema, MAX_TRACE_KEEP, MAX_CONDITION_DEPTH, MAX_CONDITION_LEAVES, validateRecipeBounds, } from './recipe.js';
21
+ export type { AutomationConditionTraceNode, AutomationRunOutcome, AutomationRunTrace, AutomationRunVerdict, AutomationStateDoc, AutomationStepTrace, AutomationTriggerTrace, StepDeviceTrace, TraceNodeOutcome, TraceValue, } from './trace.js';
22
+ export { AutomationConditionTraceNodeSchema, EMPTY_AUTOMATION_STATE, mergeSkipTraces, AutomationRunOutcomeSchema, AutomationRunTraceSchema, AutomationRunVerdictSchema, AutomationStateDocSchema, AutomationStepTraceSchema, AutomationTriggerTraceSchema, appendTrace, buildStepArgsTrace, buildTriggerTrace, isSkippedTrace, MAX_TRACE_ARG_LENGTH, MAX_STEP_DEVICE_OUTCOMES, MAX_TRACE_ARGS, REDACTED_ARG, skipSignature, StepDeviceTraceSchema, TraceNodeOutcomeSchema, TraceValueSchema, } from './trace.js';
23
+ export type { ActionStep, AutomationAction, AutomationCapStep, AutomationRecipe, AutomationRunMode, LegacyAutomationRecipe, NormalizedCapStep, } from './recipe.js';
24
+ export { ActionStepSchema, AutomationActionSchema, AutomationCapStepSchema, AutomationWaitStepSchema, AutomationRecipeSchema, AutomationRunModeSchema, CurrentAutomationRecipeSchema, convertLegacyRecipe, DEFAULT_TRACE_KEEP, LegacyAutomationRecipeSchema, MAX_TRACE_KEEP, MAX_CONDITION_DEPTH, MAX_CONDITION_LEAVES, normalizeCapStep, normalizeStepTargets, validateRecipeBounds, } from './recipe.js';
25
25
  export type { AutomationAll, AutomationAny, AutomationConditionOperator, AutomationEventLeaf, AutomationLeaf, AutomationNot, AutomationStateLeaf, AutomationWhen, DetectionLeaf, DeviceBecomesLeaf, DeviceIsLeaf, ExpressionLeaf, StationaryLeaf, StationaryPresentLeaf, } from './when.js';
26
26
  export { AutomationConditionOperatorSchema, AutomationWhenSchema, countWhenLeaves, forEachLeaf, hasEventLeaves, isEventLeaf, isLeaf, MAX_WHEN_DEPTH, MAX_WHEN_LEAVES, validateWhenTree, whenDepth, } from './when.js';
27
27
  export type { LegacyCondition, LegacyTrigger, } from './legacy-recipe.js';
@@ -29,6 +29,8 @@ export { convertLegacyCondition, convertLegacyWhen, LegacyConditionSchema, Legac
29
29
  export type { DetectionLabelSource, DetectionSubject, IdentityLabel, PlateLabel, SubClassLabel, SubjectObservation, SubjectVerdict, } from './detection-subject.js';
30
30
  export { anySubjectMatches, DETECTION_LABEL_SOURCES, DetectionSubjectSchema, describeSubject, StationarySubjectSchema, subClassesOf, subjectFromClassName, subjectMatches, } from './detection-subject.js';
31
31
  export * from './action.js';
32
+ export * from './action-target.js';
33
+ export * from './nc-action-target.js';
32
34
  export * from './step-runner.js';
33
35
  export { UnitWarningSchema, capUnitResolver, normalizeActionUnits, normalizeConditionUnits, normalizeRecipeUnits, } from './normalize-units.js';
34
36
  export type { UnitResolver, UnitWarning, UnitsNormalized } from './normalize-units.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * D983 for a notification sequence's cap step: the ONE reader of its target.
3
+ *
4
+ * A step stored before D983 names one device (`deviceId`); it is read as a
5
+ * `devices` target, and `actionId` is filled when the cap and method match
6
+ * exactly one vocabulary action. A step already in the new shape passes
7
+ * through. Nothing else in the notification centre reads `deviceId`.
8
+ */
9
+ import type { NcRuleAction } from '../capabilities/notification-rules.cap.js';
10
+ import type { ActionTarget } from './action-target.js';
11
+ type NcCapAction = Extract<NcRuleAction, {
12
+ kind: 'cap';
13
+ }>;
14
+ export interface NcCapTarget {
15
+ readonly target: ActionTarget;
16
+ readonly actionId: string | undefined;
17
+ }
18
+ export declare function normalizeNcCapTarget(action: NcCapAction): NcCapTarget;
19
+ /** The step as it is stored from now on: `target` (and `actionId`) set, no `deviceId`. */
20
+ export declare function normalizeNcAction(action: NcRuleAction): NcRuleAction;
21
+ export {};
@@ -24,7 +24,7 @@ import type { CapabilityDefinition } from '../capabilities/capability-definition
24
24
  import { type ParamMeta } from '../units/quantity-input.js';
25
25
  import type { ReadingQuantity } from '../units/quantity.js';
26
26
  import { z } from 'zod';
27
- import type { AutomationAction, AutomationCapStep, AutomationRecipe } from './recipe.js';
27
+ import { type AutomationAction, type AutomationCapStep, type AutomationRecipe } from './recipe.js';
28
28
  import type { AutomationWhen } from './when.js';
29
29
  /** What the boundary needs to know about caps. */
30
30
  export interface UnitResolver {
@@ -24,8 +24,10 @@
24
24
  */
25
25
  import { z } from 'zod';
26
26
  import { type QuantityValue } from '../units/quantity-input.js';
27
+ import { type ActionTarget } from './action-target.js';
27
28
  import { type AutomationWhen } from './when.js';
28
29
  export type { QuantityValue };
30
+ export type { ActionTarget };
29
31
  /**
30
32
  * One action step.
31
33
  *
@@ -48,17 +50,74 @@ export declare const AutomationWaitStepSchema: z.ZodObject<{
48
50
  kind: z.ZodLiteral<"wait">;
49
51
  seconds: z.ZodNumber;
50
52
  }, z.core.$strip>;
53
+ /**
54
+ * The fields of a cap step, before the target rules are applied. Exported for
55
+ * a shape that is "a cap call, narrowed" (a remote-control binding is one
56
+ * device); a step is {@link AutomationCapStepSchema}.
57
+ */
58
+ export declare const AutomationCapStepBaseSchema: z.ZodObject<{
59
+ kind: z.ZodLiteral<"cap">;
60
+ actionId: z.ZodOptional<z.ZodString>;
61
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
62
+ kind: z.ZodLiteral<"devices">;
63
+ ids: z.ZodArray<z.ZodNumber>;
64
+ }, z.core.$strip>, z.ZodObject<{
65
+ kind: z.ZodLiteral<"domain">;
66
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
67
+ roomId: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>, z.ZodObject<{
69
+ kind: z.ZodLiteral<"var">;
70
+ name: z.ZodString;
71
+ }, z.core.$strip>], "kind">>;
72
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
73
+ $var: z.ZodString;
74
+ }, z.core.$strip>]>>;
75
+ cap: z.ZodString;
76
+ method: z.ZodString;
77
+ args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
78
+ argUnits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
79
+ }, z.core.$strip>;
51
80
  export declare const AutomationCapStepSchema: z.ZodObject<{
52
81
  kind: z.ZodLiteral<"cap">;
53
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
82
+ actionId: z.ZodOptional<z.ZodString>;
83
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
84
+ kind: z.ZodLiteral<"devices">;
85
+ ids: z.ZodArray<z.ZodNumber>;
86
+ }, z.core.$strip>, z.ZodObject<{
87
+ kind: z.ZodLiteral<"domain">;
88
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
89
+ roomId: z.ZodOptional<z.ZodString>;
90
+ }, z.core.$strip>, z.ZodObject<{
91
+ kind: z.ZodLiteral<"var">;
92
+ name: z.ZodString;
93
+ }, z.core.$strip>], "kind">>;
94
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
54
95
  $var: z.ZodString;
55
- }, z.core.$strip>]>;
96
+ }, z.core.$strip>]>>;
56
97
  cap: z.ZodString;
57
98
  method: z.ZodString;
58
99
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
59
100
  argUnits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
60
101
  }, z.core.$strip>;
61
102
  export type AutomationCapStep = z.infer<typeof AutomationCapStepSchema>;
103
+ /** A cap step after {@link normalizeCapStep}: `target` always set, no legacy `deviceId`. */
104
+ export type NormalizedCapStep = Omit<AutomationCapStep, 'deviceId' | 'target'> & {
105
+ readonly target: ActionTarget;
106
+ };
107
+ /**
108
+ * The ONE reader of a stored cap step. A step written before D983 carries
109
+ * `deviceId`; it becomes a `target`, and `actionId` is filled when its cap +
110
+ * method (+ the args it sends) match exactly one vocabulary action. A step in
111
+ * the new shape passes through (its `deviceId`, if any, is dropped). Nothing
112
+ * else in the repo reads `deviceId`.
113
+ */
114
+ export declare function normalizeCapStep(step: AutomationCapStep): NormalizedCapStep;
115
+ /**
116
+ * {@link normalizeCapStep} over a step list: cap steps are rewritten to the
117
+ * new target shape, every other step passes through. The one function a store
118
+ * reads through and a save boundary writes through.
119
+ */
120
+ export declare function normalizeStepTargets<A extends AutomationAction>(steps: readonly A[]): (A | NormalizedCapStep)[];
62
121
  /**
63
122
  * The steps an internal Action may contain: cap and wait, and nothing that can
64
123
  * call back into an Action — so an Action can never reach itself (no cycles).
@@ -68,9 +127,21 @@ export declare const ActionStepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
68
127
  seconds: z.ZodNumber;
69
128
  }, z.core.$strip>, z.ZodObject<{
70
129
  kind: z.ZodLiteral<"cap">;
71
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
130
+ actionId: z.ZodOptional<z.ZodString>;
131
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
132
+ kind: z.ZodLiteral<"devices">;
133
+ ids: z.ZodArray<z.ZodNumber>;
134
+ }, z.core.$strip>, z.ZodObject<{
135
+ kind: z.ZodLiteral<"domain">;
136
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
137
+ roomId: z.ZodOptional<z.ZodString>;
138
+ }, z.core.$strip>, z.ZodObject<{
139
+ kind: z.ZodLiteral<"var">;
140
+ name: z.ZodString;
141
+ }, z.core.$strip>], "kind">>;
142
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
72
143
  $var: z.ZodString;
73
- }, z.core.$strip>]>;
144
+ }, z.core.$strip>]>>;
74
145
  cap: z.ZodString;
75
146
  method: z.ZodString;
76
147
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -82,9 +153,21 @@ export declare const AutomationActionSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
82
153
  seconds: z.ZodNumber;
83
154
  }, z.core.$strip>, z.ZodObject<{
84
155
  kind: z.ZodLiteral<"cap">;
85
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
156
+ actionId: z.ZodOptional<z.ZodString>;
157
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
158
+ kind: z.ZodLiteral<"devices">;
159
+ ids: z.ZodArray<z.ZodNumber>;
160
+ }, z.core.$strip>, z.ZodObject<{
161
+ kind: z.ZodLiteral<"domain">;
162
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
163
+ roomId: z.ZodOptional<z.ZodString>;
164
+ }, z.core.$strip>, z.ZodObject<{
165
+ kind: z.ZodLiteral<"var">;
166
+ name: z.ZodString;
167
+ }, z.core.$strip>], "kind">>;
168
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
86
169
  $var: z.ZodString;
87
- }, z.core.$strip>]>;
170
+ }, z.core.$strip>]>>;
88
171
  cap: z.ZodString;
89
172
  method: z.ZodString;
90
173
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -136,9 +219,21 @@ export declare const CurrentAutomationRecipeSchema: z.ZodObject<{
136
219
  seconds: z.ZodNumber;
137
220
  }, z.core.$strip>, z.ZodObject<{
138
221
  kind: z.ZodLiteral<"cap">;
139
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
222
+ actionId: z.ZodOptional<z.ZodString>;
223
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
224
+ kind: z.ZodLiteral<"devices">;
225
+ ids: z.ZodArray<z.ZodNumber>;
226
+ }, z.core.$strip>, z.ZodObject<{
227
+ kind: z.ZodLiteral<"domain">;
228
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
229
+ roomId: z.ZodOptional<z.ZodString>;
230
+ }, z.core.$strip>, z.ZodObject<{
231
+ kind: z.ZodLiteral<"var">;
232
+ name: z.ZodString;
233
+ }, z.core.$strip>], "kind">>;
234
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
140
235
  $var: z.ZodString;
141
- }, z.core.$strip>]>;
236
+ }, z.core.$strip>]>>;
142
237
  cap: z.ZodString;
143
238
  method: z.ZodString;
144
239
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -213,9 +308,21 @@ export declare const LegacyAutomationRecipeSchema: z.ZodObject<{
213
308
  seconds: z.ZodNumber;
214
309
  }, z.core.$strip>, z.ZodObject<{
215
310
  kind: z.ZodLiteral<"cap">;
216
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
311
+ actionId: z.ZodOptional<z.ZodString>;
312
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
313
+ kind: z.ZodLiteral<"devices">;
314
+ ids: z.ZodArray<z.ZodNumber>;
315
+ }, z.core.$strip>, z.ZodObject<{
316
+ kind: z.ZodLiteral<"domain">;
317
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
318
+ roomId: z.ZodOptional<z.ZodString>;
319
+ }, z.core.$strip>, z.ZodObject<{
320
+ kind: z.ZodLiteral<"var">;
321
+ name: z.ZodString;
322
+ }, z.core.$strip>], "kind">>;
323
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
217
324
  $var: z.ZodString;
218
- }, z.core.$strip>]>;
325
+ }, z.core.$strip>]>>;
219
326
  cap: z.ZodString;
220
327
  method: z.ZodString;
221
328
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -259,9 +366,21 @@ export declare const AutomationRecipeSchema: z.ZodUnion<readonly [z.ZodObject<{
259
366
  seconds: z.ZodNumber;
260
367
  }, z.core.$strip>, z.ZodObject<{
261
368
  kind: z.ZodLiteral<"cap">;
262
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
369
+ actionId: z.ZodOptional<z.ZodString>;
370
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
371
+ kind: z.ZodLiteral<"devices">;
372
+ ids: z.ZodArray<z.ZodNumber>;
373
+ }, z.core.$strip>, z.ZodObject<{
374
+ kind: z.ZodLiteral<"domain">;
375
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
376
+ roomId: z.ZodOptional<z.ZodString>;
377
+ }, z.core.$strip>, z.ZodObject<{
378
+ kind: z.ZodLiteral<"var">;
379
+ name: z.ZodString;
380
+ }, z.core.$strip>], "kind">>;
381
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
263
382
  $var: z.ZodString;
264
- }, z.core.$strip>]>;
383
+ }, z.core.$strip>]>>;
265
384
  cap: z.ZodString;
266
385
  method: z.ZodString;
267
386
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -333,9 +452,21 @@ export declare const AutomationRecipeSchema: z.ZodUnion<readonly [z.ZodObject<{
333
452
  seconds: z.ZodNumber;
334
453
  }, z.core.$strip>, z.ZodObject<{
335
454
  kind: z.ZodLiteral<"cap">;
336
- deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
455
+ actionId: z.ZodOptional<z.ZodString>;
456
+ target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
457
+ kind: z.ZodLiteral<"devices">;
458
+ ids: z.ZodArray<z.ZodNumber>;
459
+ }, z.core.$strip>, z.ZodObject<{
460
+ kind: z.ZodLiteral<"domain">;
461
+ deviceType: z.ZodOptional<z.ZodEnum<typeof import("../index.js").DeviceType>>;
462
+ roomId: z.ZodOptional<z.ZodString>;
463
+ }, z.core.$strip>, z.ZodObject<{
464
+ kind: z.ZodLiteral<"var">;
465
+ name: z.ZodString;
466
+ }, z.core.$strip>], "kind">>;
467
+ deviceId: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
337
468
  $var: z.ZodString;
338
- }, z.core.$strip>]>;
469
+ }, z.core.$strip>]>>;
339
470
  cap: z.ZodString;
340
471
  method: z.ZodString;
341
472
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -367,9 +498,21 @@ export declare const AutomationRecipeSchema: z.ZodUnion<readonly [z.ZodObject<{
367
498
  when: AutomationWhen;
368
499
  actions: ({
369
500
  kind: "cap";
370
- deviceId: number | number[] | {
501
+ actionId?: string | undefined;
502
+ target?: {
503
+ kind: "devices";
504
+ ids: number[];
505
+ } | {
506
+ kind: "domain";
507
+ deviceType?: import("../index.js").DeviceType | undefined;
508
+ roomId?: string | undefined;
509
+ } | {
510
+ kind: "var";
511
+ name: string;
512
+ } | undefined;
513
+ deviceId?: number | number[] | {
371
514
  $var: string;
372
- };
515
+ } | undefined;
373
516
  cap: string;
374
517
  method: string;
375
518
  args?: Record<string, unknown> | undefined;
@@ -438,9 +581,21 @@ export declare const AutomationRecipeSchema: z.ZodUnion<readonly [z.ZodObject<{
438
581
  conditions?: import("./legacy-recipe.js").LegacyCondition | undefined;
439
582
  actions: ({
440
583
  kind: "cap";
441
- deviceId: number | number[] | {
584
+ actionId?: string | undefined;
585
+ target?: {
586
+ kind: "devices";
587
+ ids: number[];
588
+ } | {
589
+ kind: "domain";
590
+ deviceType?: import("../index.js").DeviceType | undefined;
591
+ roomId?: string | undefined;
592
+ } | {
593
+ kind: "var";
594
+ name: string;
595
+ } | undefined;
596
+ deviceId?: number | number[] | {
442
597
  $var: string;
443
- };
598
+ } | undefined;
444
599
  cap: string;
445
600
  method: string;
446
601
  args?: Record<string, unknown> | undefined;
@@ -8,18 +8,25 @@
8
8
  * an intent the operator never expressed), and every step is traced — the ones
9
9
  * after a failure, or after an abort, are `not-run` with the reason.
10
10
  */
11
- import type { ActionStep } from './recipe.js';
12
- import { type AutomationStepTrace } from './trace.js';
11
+ import type { ActionTarget, ResolvedTarget, ResolveOrigin } from './action-target.js';
12
+ import { type ActionStep } from './recipe.js';
13
+ import { type AutomationStepTrace, type StepDeviceTrace } from './trace.js';
13
14
  /** Static facts about a step, known before it runs. */
14
15
  export interface StepMeta {
15
16
  readonly kind: string;
16
17
  readonly cap?: string;
17
18
  readonly method?: string;
19
+ /** The vocabulary action id the step performs, when it names one. */
20
+ readonly actionId?: string;
18
21
  readonly deviceId?: number;
19
22
  }
20
23
  /** What a running step can tell the runner (the device a fan-out is on right now). */
21
24
  export interface StepScope {
22
25
  setDeviceId(deviceId: number): void;
26
+ /** One device of a multi-device step finished — traced per device. */
27
+ recordDevice(outcome: StepDeviceTrace): void;
28
+ /** The step's target resolved to no device: traced as `empty`, with the reason. */
29
+ markEmpty(reason: string): void;
23
30
  /** The parameters the step is sending NOW (substituted), for its trace. */
24
31
  setArgs(args: Readonly<Record<string, unknown>>): void;
25
32
  }
@@ -71,11 +78,23 @@ export interface CapCall {
71
78
  readonly method: string;
72
79
  readonly args: Readonly<Record<string, unknown>>;
73
80
  }
81
+ /** How many devices of one step are driven at once. */
82
+ export declare const STEP_DEVICE_CONCURRENCY = 4;
74
83
  export interface ActionStepDeps {
75
84
  invokeCap(call: CapCall): Promise<void>;
76
85
  sleep(ms: number): Promise<void>;
77
86
  onMissingVar(name: string): void;
87
+ /** Turn a step's target into device ids (an empty answer is logged by the resolver). */
88
+ resolveTarget(target: ActionTarget, actionId: string | undefined, vars: StepVars, origin: ResolveOrigin): Promise<ResolvedTarget>;
89
+ /** The action / automation the steps belong to, for the log line of an empty target. */
90
+ readonly owner?: string;
78
91
  }
92
+ /**
93
+ * Run `work` for every device, `concurrency` at a time. EVERY device is tried
94
+ * (one refusing device must not leave the others undone); each outcome goes to
95
+ * the step's trace, in device order, and the step fails afterwards if any did.
96
+ */
97
+ export declare function runForDevices(deviceIds: readonly number[], scope: StepScope, work: (deviceId: number) => Promise<void>, concurrency?: number): Promise<void>;
79
98
  /**
80
99
  * The runnable form of an internal Action's steps, bound to one run's
81
100
  * variables. Variables are substituted per step, at the moment it runs, so a
@@ -105,16 +105,38 @@ export declare const AutomationRunVerdictSchema: z.ZodObject<{
105
105
  }, z.core.$strip>;
106
106
  export type AutomationRunVerdict = z.infer<typeof AutomationRunVerdictSchema>;
107
107
  /** One executed (or not-run) step — shared by automations and Action runs. */
108
+ /** How many per-device outcomes one step trace keeps. */
109
+ export declare const MAX_STEP_DEVICE_OUTCOMES = 200;
110
+ /** One device of a multi-device step: did the call land. */
111
+ export declare const StepDeviceTraceSchema: z.ZodObject<{
112
+ deviceId: z.ZodNumber;
113
+ outcome: z.ZodEnum<{
114
+ error: "error";
115
+ ok: "ok";
116
+ }>;
117
+ message: z.ZodOptional<z.ZodString>;
118
+ }, z.core.$strip>;
119
+ export type StepDeviceTrace = z.infer<typeof StepDeviceTraceSchema>;
108
120
  export declare const AutomationStepTraceSchema: z.ZodObject<{
109
121
  path: z.ZodString;
110
122
  kind: z.ZodString;
111
123
  cap: z.ZodOptional<z.ZodString>;
112
124
  method: z.ZodOptional<z.ZodString>;
125
+ actionId: z.ZodOptional<z.ZodString>;
113
126
  deviceId: z.ZodOptional<z.ZodNumber>;
127
+ devices: z.ZodOptional<z.ZodArray<z.ZodObject<{
128
+ deviceId: z.ZodNumber;
129
+ outcome: z.ZodEnum<{
130
+ error: "error";
131
+ ok: "ok";
132
+ }>;
133
+ message: z.ZodOptional<z.ZodString>;
134
+ }, z.core.$strip>>>;
114
135
  startedAt: z.ZodOptional<z.ZodNumber>;
115
136
  endedAt: z.ZodOptional<z.ZodNumber>;
116
137
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>]>>>;
117
138
  outcome: z.ZodEnum<{
139
+ empty: "empty";
118
140
  error: "error";
119
141
  "not-run": "not-run";
120
142
  ok: "ok";
@@ -197,11 +219,21 @@ export declare const AutomationRunTraceSchema: z.ZodObject<{
197
219
  kind: z.ZodString;
198
220
  cap: z.ZodOptional<z.ZodString>;
199
221
  method: z.ZodOptional<z.ZodString>;
222
+ actionId: z.ZodOptional<z.ZodString>;
200
223
  deviceId: z.ZodOptional<z.ZodNumber>;
224
+ devices: z.ZodOptional<z.ZodArray<z.ZodObject<{
225
+ deviceId: z.ZodNumber;
226
+ outcome: z.ZodEnum<{
227
+ error: "error";
228
+ ok: "ok";
229
+ }>;
230
+ message: z.ZodOptional<z.ZodString>;
231
+ }, z.core.$strip>>>;
201
232
  startedAt: z.ZodOptional<z.ZodNumber>;
202
233
  endedAt: z.ZodOptional<z.ZodNumber>;
203
234
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>]>>>;
204
235
  outcome: z.ZodEnum<{
236
+ empty: "empty";
205
237
  error: "error";
206
238
  "not-run": "not-run";
207
239
  ok: "ok";
@@ -303,11 +335,21 @@ export declare const AutomationStateDocSchema: z.ZodObject<{
303
335
  kind: z.ZodString;
304
336
  cap: z.ZodOptional<z.ZodString>;
305
337
  method: z.ZodOptional<z.ZodString>;
338
+ actionId: z.ZodOptional<z.ZodString>;
306
339
  deviceId: z.ZodOptional<z.ZodNumber>;
340
+ devices: z.ZodOptional<z.ZodArray<z.ZodObject<{
341
+ deviceId: z.ZodNumber;
342
+ outcome: z.ZodEnum<{
343
+ error: "error";
344
+ ok: "ok";
345
+ }>;
346
+ message: z.ZodOptional<z.ZodString>;
347
+ }, z.core.$strip>>>;
307
348
  startedAt: z.ZodOptional<z.ZodNumber>;
308
349
  endedAt: z.ZodOptional<z.ZodNumber>;
309
350
  args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>]>>>;
310
351
  outcome: z.ZodEnum<{
352
+ empty: "empty";
311
353
  error: "error";
312
354
  "not-run": "not-run";
313
355
  ok: "ok";