@camstack/types 1.2.429 → 1.2.431
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 +13 -0
- package/dist/addon.js +4 -3
- package/dist/addon.mjs +4 -3
- package/dist/automation/action.d.ts +210 -0
- package/dist/automation/condition-eval.d.ts +64 -0
- package/dist/automation/detection-trigger.d.ts +66 -0
- package/dist/automation/index.d.ts +12 -2
- package/dist/automation/recipe.d.ts +103 -1
- package/dist/automation/run-gate.d.ts +43 -0
- package/dist/automation/step-runner.d.ts +82 -0
- package/dist/automation/trace.d.ts +325 -0
- package/dist/capabilities/action-source.cap.d.ts +74 -0
- package/dist/capabilities/actions.cap.d.ts +459 -0
- package/dist/capabilities/core-blocks.cap.d.ts +761 -0
- package/dist/capabilities/index.d.ts +5 -1
- package/dist/client.js +73 -49
- package/dist/client.mjs +73 -49
- package/dist/enums/event-category.d.ts +5 -0
- package/dist/enums.js +1 -1
- package/dist/enums.mjs +1 -1
- package/dist/{event-category-Ave2VSxY.js → event-category-BhzwWQCH.js} +5 -0
- package/dist/{event-category-CT-hz5Rb.mjs → event-category-hdMR7CTW.mjs} +5 -0
- package/dist/generated/addon-api.d.ts +135 -0
- package/dist/generated/capability-router-map.d.ts +10 -4
- package/dist/generated/collection-array-methods.d.ts +1 -1
- package/dist/generated/device-proxy.d.ts +2 -0
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/method-device-selectors.d.ts +2 -2
- package/dist/generated/system-proxy.d.ts +5 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2849 -579
- package/dist/index.mjs +2792 -580
- package/dist/interfaces/config-ui.d.ts +7 -0
- package/dist/{sleep-hRhTSDlN.js → sleep-5zRztaHa.js} +12 -2
- package/dist/{sleep-BE7IAR2p.mjs → sleep-BHjNez61.mjs} +12 -2
- package/dist/vendor-login-proxy/index.d.ts +2 -0
- package/dist/vendor-login-proxy/login-proxy.d.ts +55 -0
- package/dist/vendor-login-proxy/url-rewrite.d.ts +68 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.431] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Addons can now offer a vendor sign-in that opens the vendor's login page through the hub and finishes on the CamStack result page.
|
|
15
|
+
|
|
16
|
+
## [1.2.430] - 2026-10-27
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Actions: declared list entries (action-source, actions caps), an action step for automations, and run traces kept in the hub ledger
|
|
21
|
+
- Automations gain detection and interval triggers, per-automation run modes (single, restart, queued, parallel, cooldown), condition reads from an in-memory mirror, and a ring of run traces
|
|
22
|
+
|
|
10
23
|
## [1.2.429] - 2026-10-27
|
|
11
24
|
|
|
12
25
|
### Added
|
package/dist/addon.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_sleep = require("./sleep-
|
|
3
|
-
const require_event_category = require("./event-category-
|
|
2
|
+
const require_sleep = require("./sleep-5zRztaHa.js");
|
|
3
|
+
const require_event_category = require("./event-category-BhzwWQCH.js");
|
|
4
4
|
const require_err_msg = require("./err-msg-U7lLUMNz.js");
|
|
5
5
|
//#region src/generated/cap-input-defaults.ts
|
|
6
6
|
/**
|
|
@@ -362,9 +362,10 @@ function materialise(defaults) {
|
|
|
362
362
|
* barrel (`ALL_CAPABILITY_DEFINITIONS`), which would cost ~144MB RSS per
|
|
363
363
|
* forked runner. See docs/decisions/adr-0028.md.
|
|
364
364
|
*
|
|
365
|
-
* Coverage:
|
|
365
|
+
* Coverage: 27 collection caps, 45 array methods.
|
|
366
366
|
*/
|
|
367
367
|
var COLLECTION_ARRAY_METHODS = Object.freeze({
|
|
368
|
+
"action-source": ["listActions"],
|
|
368
369
|
"addon-pages-source": ["listPages"],
|
|
369
370
|
"addon-routes": ["getRoutes"],
|
|
370
371
|
"addon-widgets-source": ["listWidgets"],
|
package/dist/addon.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as expandCapMethods, $n as REVALIDATE_AFTER_MS, $r as isSecretMarker, $t as RecordFieldPathSchema, A as HF_REPO, An as RecordUniqueViolationError, Ar as buildSecretRef, B as withoutCallerStamp, Bn as RECORD_CLASSES, C as addonSliceHandleApi, Cn as InvalidRecordError, Dn as RecordNotFoundError, En as RECORDS_ERROR_CODES, Fn as UnknownRecordKindError, Fr as parseSecretRef, G as adminUiCapability, Gn as RECORD_MAX_DATA_BYTES, Hn as RECORD_DEFAULT_MAX_DATA_BYTES, I as deviceOpsCapability, In as asRecordsError, Jn as RECORD_QUERY_MAX, Jt as defineEntryKind, Kn as RECORD_MAX_INDEXES, L as viewerUiCapability, Ln as recordsErrorFromMessage, M as CAP_ADVANCE_FIELDS, Mn as RevConflictError, Mr as globalSecretScope, Nn as TransactRefusedError, Nr as isSecretRef, On as RecordSecretUnavailableError, Or as SECRET_REF_SCHEME, P as CAP_STATE_LEAVES, Pn as UnindexedFilterError, Pr as normalizeSecretOwner, Qn as resolveSecretViewsIn, Qr as isSecretFieldView, Qt as EntryDescriptorSchema, R as DEVICE_CHILDREN_BATCH_MAX, Rn as RECORD_BATCH_MAX, Rr as createEvent, Sn as ForeignSecretMarkerError, Tn as PlaintextSecretError, U as describeCustomActions, Un as RECORD_DELETE_BATCH, Vn as RECORD_DATABASES, Wn as RECORD_ID_MAX_LENGTH, Xn as RECORD_SETTINGS_MAX_ROWS, Xr as classifySecretWrite, Xt as createRecordsAccess, Yn as RECORD_SECRET_COLLECTION_PREFIX, Yr as SECRET_CLEAR, Yt as entryBody, Zn as RECORD_SWEEP_INTERVAL_MS, Zr as isSecretClear, Zt as assertTransactScope, _ as readErrorI18n, _n as collectZodSecretPaths, an as assertValidDeclaration, ar as registerKnownSecret, b as isDeviceScopedCap, bn as DeclarationRefusedError, c as asString, cn as REMOVE_LEAF, cr as PEER_BYTES_CHUNK_BYTES, ct as readNodePin, d as parseJsonUnknown, dn as parseSecretPath, dr as PEER_BYTES_TICKET_TTL_MS, dt as ReadinessRegistry, ei as isSecretMarkerLike, en as RecordIndexSchema, er as createSecretsAccess, f as hfModelUrl, fn as recordArrayItemPath, ft as ReadinessTimeoutError, g as ErrorI18nPayloadSchema, gr as BaseAddon, hn as secretArrayPrefixes, hr as DisposerChain, ht as scopeKey, in as StoredRecordKindDeclarationSchema, ir as knownSecretCount, jn as RecordsError, jr as formatSecretScope, kn as RecordTooLargeError, kr as arrayItemFieldPath, ln as collectSecretLeaves, lr as PEER_BYTES_MAX_OUTSTANDING, mn as recordSecretRef, ni as toSecretFieldView, nn as RecordKindNameSchema, nr as clearKnownSecrets, o as asJsonObject, on as declarationsOf, or as scrubKnownSecrets, p as BOOT_RECOVERY_BACKOFF_MS, pn as recordSecretCollection, pr as DATAPLANE_SECRET_HEADER, qn as RECORD_MAX_INDEX_FIELDS, qt as makeInMemoryRecords, r as maskUrlCredentials, rn as RecordRetentionSchema, rr as forgetKnownSecret, sn as defineRecordKinds, sr as shortSecretRefHash, st as nodePin, t as sleep, ti as makeSecretMarker, tn as RecordKindDeclarationSchema, tr as MIN_SCRUBBED_LENGTH, un as mapSecretLeaves, ur as PEER_BYTES_STALL_MS, ut as MODEL_FORMATS, vn as isZodSecret, vr as normalizeAddonInitResult, w as createEventBusSliceSource, wn as NoCallerError, x as createDeviceProxy, xn as FilterRequiredError, y as DEVICE_SCOPED_CAPS, yn as BatchTooLargeError, z as CALLER_STAMPED_CAPABILITIES, zn as RECORD_BATCH_MAX_BYTES, zr as emitReadiness } from "./sleep-
|
|
2
|
-
import { t as EventCategory } from "./event-category-
|
|
1
|
+
import { $ as expandCapMethods, $n as REVALIDATE_AFTER_MS, $r as isSecretMarker, $t as RecordFieldPathSchema, A as HF_REPO, An as RecordUniqueViolationError, Ar as buildSecretRef, B as withoutCallerStamp, Bn as RECORD_CLASSES, C as addonSliceHandleApi, Cn as InvalidRecordError, Dn as RecordNotFoundError, En as RECORDS_ERROR_CODES, Fn as UnknownRecordKindError, Fr as parseSecretRef, G as adminUiCapability, Gn as RECORD_MAX_DATA_BYTES, Hn as RECORD_DEFAULT_MAX_DATA_BYTES, I as deviceOpsCapability, In as asRecordsError, Jn as RECORD_QUERY_MAX, Jt as defineEntryKind, Kn as RECORD_MAX_INDEXES, L as viewerUiCapability, Ln as recordsErrorFromMessage, M as CAP_ADVANCE_FIELDS, Mn as RevConflictError, Mr as globalSecretScope, Nn as TransactRefusedError, Nr as isSecretRef, On as RecordSecretUnavailableError, Or as SECRET_REF_SCHEME, P as CAP_STATE_LEAVES, Pn as UnindexedFilterError, Pr as normalizeSecretOwner, Qn as resolveSecretViewsIn, Qr as isSecretFieldView, Qt as EntryDescriptorSchema, R as DEVICE_CHILDREN_BATCH_MAX, Rn as RECORD_BATCH_MAX, Rr as createEvent, Sn as ForeignSecretMarkerError, Tn as PlaintextSecretError, U as describeCustomActions, Un as RECORD_DELETE_BATCH, Vn as RECORD_DATABASES, Wn as RECORD_ID_MAX_LENGTH, Xn as RECORD_SETTINGS_MAX_ROWS, Xr as classifySecretWrite, Xt as createRecordsAccess, Yn as RECORD_SECRET_COLLECTION_PREFIX, Yr as SECRET_CLEAR, Yt as entryBody, Zn as RECORD_SWEEP_INTERVAL_MS, Zr as isSecretClear, Zt as assertTransactScope, _ as readErrorI18n, _n as collectZodSecretPaths, an as assertValidDeclaration, ar as registerKnownSecret, b as isDeviceScopedCap, bn as DeclarationRefusedError, c as asString, cn as REMOVE_LEAF, cr as PEER_BYTES_CHUNK_BYTES, ct as readNodePin, d as parseJsonUnknown, dn as parseSecretPath, dr as PEER_BYTES_TICKET_TTL_MS, dt as ReadinessRegistry, ei as isSecretMarkerLike, en as RecordIndexSchema, er as createSecretsAccess, f as hfModelUrl, fn as recordArrayItemPath, ft as ReadinessTimeoutError, g as ErrorI18nPayloadSchema, gr as BaseAddon, hn as secretArrayPrefixes, hr as DisposerChain, ht as scopeKey, in as StoredRecordKindDeclarationSchema, ir as knownSecretCount, jn as RecordsError, jr as formatSecretScope, kn as RecordTooLargeError, kr as arrayItemFieldPath, ln as collectSecretLeaves, lr as PEER_BYTES_MAX_OUTSTANDING, mn as recordSecretRef, ni as toSecretFieldView, nn as RecordKindNameSchema, nr as clearKnownSecrets, o as asJsonObject, on as declarationsOf, or as scrubKnownSecrets, p as BOOT_RECOVERY_BACKOFF_MS, pn as recordSecretCollection, pr as DATAPLANE_SECRET_HEADER, qn as RECORD_MAX_INDEX_FIELDS, qt as makeInMemoryRecords, r as maskUrlCredentials, rn as RecordRetentionSchema, rr as forgetKnownSecret, sn as defineRecordKinds, sr as shortSecretRefHash, st as nodePin, t as sleep, ti as makeSecretMarker, tn as RecordKindDeclarationSchema, tr as MIN_SCRUBBED_LENGTH, un as mapSecretLeaves, ur as PEER_BYTES_STALL_MS, ut as MODEL_FORMATS, vn as isZodSecret, vr as normalizeAddonInitResult, w as createEventBusSliceSource, wn as NoCallerError, x as createDeviceProxy, xn as FilterRequiredError, y as DEVICE_SCOPED_CAPS, yn as BatchTooLargeError, z as CALLER_STAMPED_CAPABILITIES, zn as RECORD_BATCH_MAX_BYTES, zr as emitReadiness } from "./sleep-BHjNez61.mjs";
|
|
2
|
+
import { t as EventCategory } from "./event-category-hdMR7CTW.mjs";
|
|
3
3
|
import { a as DeviceType, t as errMsg } from "./err-msg-DIorGYeC.mjs";
|
|
4
4
|
//#region src/generated/cap-input-defaults.ts
|
|
5
5
|
/**
|
|
@@ -361,9 +361,10 @@ function materialise(defaults) {
|
|
|
361
361
|
* barrel (`ALL_CAPABILITY_DEFINITIONS`), which would cost ~144MB RSS per
|
|
362
362
|
* forked runner. See docs/decisions/adr-0028.md.
|
|
363
363
|
*
|
|
364
|
-
* Coverage:
|
|
364
|
+
* Coverage: 27 collection caps, 45 array methods.
|
|
365
365
|
*/
|
|
366
366
|
var COLLECTION_ARRAY_METHODS = Object.freeze({
|
|
367
|
+
"action-source": ["listActions"],
|
|
367
368
|
"addon-pages-source": ["listPages"],
|
|
368
369
|
"addon-routes": ["getRoutes"],
|
|
369
370
|
"addon-widgets-source": ["listWidgets"],
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actions — named, runnable things an operator (or an automation) can start,
|
|
3
|
+
* declared as LIST ENTRIES, never as devices.
|
|
4
|
+
*
|
|
5
|
+
* An entry is either **internal** (a short list of cap/wait steps kept in the
|
|
6
|
+
* hub's `actions` record kind) or declared by an addon through the
|
|
7
|
+
* `action-source` collection (a Home Assistant script, a scene …). The one
|
|
8
|
+
* surface the UI and automations talk to is the `actions` cap; it keeps no
|
|
9
|
+
* catalog of its own (D3) — every `list` asks the providers live.
|
|
10
|
+
*
|
|
11
|
+
* This file is the data half: the entry schema, the variables an entry may
|
|
12
|
+
* declare, and the pure resolution of the values a run was given. It holds no
|
|
13
|
+
* runtime.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
/** What a variable's value is. `select` takes one of `options`. */
|
|
17
|
+
export declare const ActionVariableTypeSchema: z.ZodEnum<{
|
|
18
|
+
boolean: "boolean";
|
|
19
|
+
number: "number";
|
|
20
|
+
select: "select";
|
|
21
|
+
string: "string";
|
|
22
|
+
}>;
|
|
23
|
+
export type ActionVariableType = z.infer<typeof ActionVariableTypeSchema>;
|
|
24
|
+
export declare const ActionVariableValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
25
|
+
export type ActionVariableValue = z.infer<typeof ActionVariableValueSchema>;
|
|
26
|
+
/**
|
|
27
|
+
* One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
|
|
28
|
+
* value) or `{{vars.name}}` (inside a string) in their args.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ActionVariableSchema: z.ZodObject<{
|
|
31
|
+
name: z.ZodString;
|
|
32
|
+
label: z.ZodOptional<z.ZodString>;
|
|
33
|
+
type: z.ZodEnum<{
|
|
34
|
+
boolean: "boolean";
|
|
35
|
+
number: "number";
|
|
36
|
+
select: "select";
|
|
37
|
+
string: "string";
|
|
38
|
+
}>;
|
|
39
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
41
|
+
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type ActionVariable = z.infer<typeof ActionVariableSchema>;
|
|
44
|
+
export type ResolvedActionVariables = {
|
|
45
|
+
readonly ok: true;
|
|
46
|
+
readonly values: Readonly<Record<string, ActionVariableValue>>;
|
|
47
|
+
} | {
|
|
48
|
+
readonly ok: false;
|
|
49
|
+
readonly error: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The values a run starts with: what the caller gave, checked against the
|
|
53
|
+
* declaration, with defaults filled. A value the entry did not declare is
|
|
54
|
+
* REFUSED — a typo in a variable name must not silently run with the default.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveActionVariables(declared: readonly ActionVariable[] | undefined, given: Readonly<Record<string, ActionVariableValue>> | undefined): ResolvedActionVariables;
|
|
57
|
+
export declare const ActionSourceKindSchema: z.ZodEnum<{
|
|
58
|
+
addon: "addon";
|
|
59
|
+
internal: "internal";
|
|
60
|
+
}>;
|
|
61
|
+
export type ActionSourceKind = z.infer<typeof ActionSourceKindSchema>;
|
|
62
|
+
/** Prefix of an internal action's id. */
|
|
63
|
+
export declare const INTERNAL_ACTION_PREFIX = "internal:";
|
|
64
|
+
export declare function internalActionId(uuid: string): string;
|
|
65
|
+
export declare function isInternalActionId(actionId: string): boolean;
|
|
66
|
+
/** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
|
|
67
|
+
export declare function addonIdOfActionId(actionId: string): string | null;
|
|
68
|
+
export declare const ActionStatusSchema: z.ZodObject<{
|
|
69
|
+
isRunning: z.ZodBoolean;
|
|
70
|
+
lastRunAt: z.ZodNullable<z.ZodNumber>;
|
|
71
|
+
lastError: z.ZodNullable<z.ZodString>;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
export type ActionStatus = z.infer<typeof ActionStatusSchema>;
|
|
74
|
+
export declare const IDLE_ACTION_STATUS: ActionStatus;
|
|
75
|
+
export declare const ActionEntrySchema: z.ZodObject<{
|
|
76
|
+
id: z.ZodString;
|
|
77
|
+
name: z.ZodString;
|
|
78
|
+
addonId: z.ZodString;
|
|
79
|
+
source: z.ZodEnum<{
|
|
80
|
+
addon: "addon";
|
|
81
|
+
internal: "internal";
|
|
82
|
+
}>;
|
|
83
|
+
readOnly: z.ZodBoolean;
|
|
84
|
+
steps: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
85
|
+
kind: z.ZodLiteral<"wait">;
|
|
86
|
+
seconds: z.ZodNumber;
|
|
87
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
88
|
+
kind: z.ZodLiteral<"cap">;
|
|
89
|
+
deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
|
|
90
|
+
$var: z.ZodString;
|
|
91
|
+
}, z.core.$strip>]>;
|
|
92
|
+
cap: z.ZodString;
|
|
93
|
+
method: z.ZodString;
|
|
94
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
95
|
+
}, z.core.$strip>], "kind">>>;
|
|
96
|
+
variables: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
97
|
+
name: z.ZodString;
|
|
98
|
+
label: z.ZodOptional<z.ZodString>;
|
|
99
|
+
type: z.ZodEnum<{
|
|
100
|
+
boolean: "boolean";
|
|
101
|
+
number: "number";
|
|
102
|
+
select: "select";
|
|
103
|
+
string: "string";
|
|
104
|
+
}>;
|
|
105
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
106
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
107
|
+
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
108
|
+
}, z.core.$strip>>>;
|
|
109
|
+
status: z.ZodObject<{
|
|
110
|
+
isRunning: z.ZodBoolean;
|
|
111
|
+
lastRunAt: z.ZodNullable<z.ZodNumber>;
|
|
112
|
+
lastError: z.ZodNullable<z.ZodString>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
export type ActionEntry = z.infer<typeof ActionEntrySchema>;
|
|
116
|
+
/** What an operator writes for an internal action. */
|
|
117
|
+
export declare const ActionInputSchema: z.ZodObject<{
|
|
118
|
+
name: z.ZodString;
|
|
119
|
+
steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
120
|
+
kind: z.ZodLiteral<"wait">;
|
|
121
|
+
seconds: z.ZodNumber;
|
|
122
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
123
|
+
kind: z.ZodLiteral<"cap">;
|
|
124
|
+
deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
|
|
125
|
+
$var: z.ZodString;
|
|
126
|
+
}, z.core.$strip>]>;
|
|
127
|
+
cap: z.ZodString;
|
|
128
|
+
method: z.ZodString;
|
|
129
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
130
|
+
}, z.core.$strip>], "kind">>;
|
|
131
|
+
variables: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
132
|
+
name: z.ZodString;
|
|
133
|
+
label: z.ZodOptional<z.ZodString>;
|
|
134
|
+
type: z.ZodEnum<{
|
|
135
|
+
boolean: "boolean";
|
|
136
|
+
number: "number";
|
|
137
|
+
select: "select";
|
|
138
|
+
string: "string";
|
|
139
|
+
}>;
|
|
140
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
141
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
142
|
+
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
143
|
+
}, z.core.$strip>>>;
|
|
144
|
+
}, z.core.$strip>;
|
|
145
|
+
export type ActionInput = z.infer<typeof ActionInputSchema>;
|
|
146
|
+
/** Who started a run. Absent = the operator pressed it. */
|
|
147
|
+
export declare const ActionRunOriginSchema: z.ZodObject<{
|
|
148
|
+
blockId: z.ZodOptional<z.ZodString>;
|
|
149
|
+
}, z.core.$strip>;
|
|
150
|
+
export type ActionRunOrigin = z.infer<typeof ActionRunOriginSchema>;
|
|
151
|
+
/** `started` = the caller did not ask to wait. */
|
|
152
|
+
export declare const ActionRunOutcomeSchema: z.ZodUnion<readonly [z.ZodEnum<{
|
|
153
|
+
aborted: "aborted";
|
|
154
|
+
failed: "failed";
|
|
155
|
+
ran: "ran";
|
|
156
|
+
"skipped-conditions": "skipped-conditions";
|
|
157
|
+
"skipped-cooldown": "skipped-cooldown";
|
|
158
|
+
"skipped-overlap": "skipped-overlap";
|
|
159
|
+
"skipped-trigger": "skipped-trigger";
|
|
160
|
+
}>, z.ZodLiteral<"started">]>;
|
|
161
|
+
export type ActionRunOutcome = z.infer<typeof ActionRunOutcomeSchema>;
|
|
162
|
+
export declare const ActionRunResultSchema: z.ZodObject<{
|
|
163
|
+
runId: z.ZodString;
|
|
164
|
+
outcome: z.ZodUnion<readonly [z.ZodEnum<{
|
|
165
|
+
aborted: "aborted";
|
|
166
|
+
failed: "failed";
|
|
167
|
+
ran: "ran";
|
|
168
|
+
"skipped-conditions": "skipped-conditions";
|
|
169
|
+
"skipped-cooldown": "skipped-cooldown";
|
|
170
|
+
"skipped-overlap": "skipped-overlap";
|
|
171
|
+
"skipped-trigger": "skipped-trigger";
|
|
172
|
+
}>, z.ZodLiteral<"started">]>;
|
|
173
|
+
message: z.ZodOptional<z.ZodString>;
|
|
174
|
+
}, z.core.$strip>;
|
|
175
|
+
export type ActionRunResult = z.infer<typeof ActionRunResultSchema>;
|
|
176
|
+
export declare const ActionErrorCodeSchema: z.ZodEnum<{
|
|
177
|
+
busy: "busy";
|
|
178
|
+
"invalid-steps": "invalid-steps";
|
|
179
|
+
"invalid-variables": "invalid-variables";
|
|
180
|
+
"not-found": "not-found";
|
|
181
|
+
"not-linked": "not-linked";
|
|
182
|
+
"read-only": "read-only";
|
|
183
|
+
}>;
|
|
184
|
+
export type ActionErrorCode = z.infer<typeof ActionErrorCodeSchema>;
|
|
185
|
+
/**
|
|
186
|
+
* A refusal with a reason a caller can branch on. The code leads the message
|
|
187
|
+
* (`[read-only] …`) because a cap call flattens an error to its text.
|
|
188
|
+
*/
|
|
189
|
+
export declare class ActionError extends Error {
|
|
190
|
+
readonly code: ActionErrorCode;
|
|
191
|
+
constructor(code: ActionErrorCode, message: string);
|
|
192
|
+
}
|
|
193
|
+
/** The run-state change `actions.onRunChanged` carries. */
|
|
194
|
+
export declare const ActionRunChangedSchema: z.ZodObject<{
|
|
195
|
+
actionId: z.ZodString;
|
|
196
|
+
runId: z.ZodString;
|
|
197
|
+
isRunning: z.ZodBoolean;
|
|
198
|
+
outcome: z.ZodOptional<z.ZodEnum<{
|
|
199
|
+
aborted: "aborted";
|
|
200
|
+
failed: "failed";
|
|
201
|
+
ran: "ran";
|
|
202
|
+
"skipped-conditions": "skipped-conditions";
|
|
203
|
+
"skipped-cooldown": "skipped-cooldown";
|
|
204
|
+
"skipped-overlap": "skipped-overlap";
|
|
205
|
+
"skipped-trigger": "skipped-trigger";
|
|
206
|
+
}>>;
|
|
207
|
+
lastRunAt: z.ZodNullable<z.ZodNumber>;
|
|
208
|
+
lastError: z.ZodNullable<z.ZodString>;
|
|
209
|
+
}, z.core.$strip>;
|
|
210
|
+
export type ActionRunChanged = z.infer<typeof ActionRunChangedSchema>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluating an automation's condition tree against the ConditionMirror.
|
|
3
|
+
*
|
|
4
|
+
* D49: a trigger is a one-shot event, so its gate must never hang on a read
|
|
5
|
+
* that can fail. Every field a condition leaf names is therefore kept in
|
|
6
|
+
* memory — filled at start, updated off device state-changed events, reconciled
|
|
7
|
+
* on a timer — and evaluation reads that memory ONLY. A value the mirror does
|
|
8
|
+
* not hold is UNKNOWN, which is `fail` with a reason, never a throw and never a
|
|
9
|
+
* `pass`. A failed refresh changes nothing: the previous value stays.
|
|
10
|
+
*
|
|
11
|
+
* The evaluator returns a node per tree position (containers, leaves, and the
|
|
12
|
+
* ones a short-circuit never reached) with a stable path and a human reason, so
|
|
13
|
+
* the editor can paint each block.
|
|
14
|
+
*/
|
|
15
|
+
import type { AutomationCondition } from './recipe.js';
|
|
16
|
+
import type { AutomationConditionTraceNode } from './trace.js';
|
|
17
|
+
/** One device cap whose slice the mirror must hold. */
|
|
18
|
+
export type MirrorSource = {
|
|
19
|
+
readonly kind: 'device';
|
|
20
|
+
readonly deviceId: number;
|
|
21
|
+
readonly cap: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'stable';
|
|
24
|
+
readonly stableId: string;
|
|
25
|
+
readonly cap: string;
|
|
26
|
+
};
|
|
27
|
+
/** Every (device, cap) a tree reads, deduplicated. */
|
|
28
|
+
export declare function collectMirrorSources(cond: AutomationCondition | undefined): MirrorSource[];
|
|
29
|
+
export interface ConditionReader {
|
|
30
|
+
/** `undefined` = unknown. */
|
|
31
|
+
read(deviceId: number, cap: string, fieldPath: string): unknown;
|
|
32
|
+
readStable(stableId: string, cap: string, fieldPath: string): unknown;
|
|
33
|
+
}
|
|
34
|
+
export interface MirrorRefreshReport {
|
|
35
|
+
readonly updated: number;
|
|
36
|
+
/** Sources whose read failed or answered nothing — their old value stays. */
|
|
37
|
+
readonly kept: number;
|
|
38
|
+
}
|
|
39
|
+
export interface MirrorRefreshFetchers {
|
|
40
|
+
readonly fetchSlice: (deviceId: number, cap: string) => Promise<unknown>;
|
|
41
|
+
/** stableId → device id. A rejection leaves the previous map. */
|
|
42
|
+
readonly fetchStableIds: () => Promise<ReadonlyMap<string, number>>;
|
|
43
|
+
}
|
|
44
|
+
export declare class ConditionMirror implements ConditionReader {
|
|
45
|
+
private readonly sources;
|
|
46
|
+
private readonly slices;
|
|
47
|
+
private stableIds;
|
|
48
|
+
private readonly watched;
|
|
49
|
+
constructor(sources: readonly MirrorSource[]);
|
|
50
|
+
/** A `device.state-changed` event. Slices nobody reads are ignored. */
|
|
51
|
+
apply(deviceId: number, cap: string, slice: unknown): void;
|
|
52
|
+
private isStableTarget;
|
|
53
|
+
read(deviceId: number, cap: string, fieldPath: string): unknown;
|
|
54
|
+
readStable(stableId: string, cap: string, fieldPath: string): unknown;
|
|
55
|
+
/** Fill / reconcile. A read that fails or answers nothing keeps the old value. */
|
|
56
|
+
refresh(fetchers: MirrorRefreshFetchers): Promise<MirrorRefreshReport>;
|
|
57
|
+
}
|
|
58
|
+
export interface ConditionEvaluation {
|
|
59
|
+
readonly pass: boolean;
|
|
60
|
+
/** Pre-order: a container, then its children. */
|
|
61
|
+
readonly nodes: readonly AutomationConditionTraceNode[];
|
|
62
|
+
}
|
|
63
|
+
/** No condition tree is a pass with no nodes. */
|
|
64
|
+
export declare function evaluateConditionTree(cond: AutomationCondition | undefined, reader: ConditionReader, now: number): ConditionEvaluation;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `detection` trigger's per-event decision: does this track-lifecycle
|
|
3
|
+
* event start a run, and if not, WHY not.
|
|
4
|
+
*
|
|
5
|
+
* Source: `EventCategory.PipelineAnalyticsTrackLifecycle` — payload
|
|
6
|
+
* {@link PipelineAnalyticsTrackLifecyclePayload} (`deviceId`, `trackId`,
|
|
7
|
+
* `phase` start|update|end, `bestClassName`, `bestConfidence`,
|
|
8
|
+
* `zonesVisited`). The event carries the device in its DATA, not on its
|
|
9
|
+
* `source`, so the filter reads `data.deviceId`.
|
|
10
|
+
*
|
|
11
|
+
* ONE fire per `(deviceId, trackId)`: a track fires the first time it satisfies
|
|
12
|
+
* the class, score and zone conditions (which may be on an `update`, once its
|
|
13
|
+
* best confidence has risen), and never again. The fired set is bounded and
|
|
14
|
+
* forgets a track on its `end` or after {@link FIRED_TTL_MS}, so a track whose
|
|
15
|
+
* `end` was dropped (events are telemetry, D8) cannot pin memory.
|
|
16
|
+
*/
|
|
17
|
+
import type { AutomationTrigger } from './recipe.js';
|
|
18
|
+
export type DetectionTrigger = Extract<AutomationTrigger, {
|
|
19
|
+
kind: 'detection';
|
|
20
|
+
}>;
|
|
21
|
+
/** A track that never ended is forgotten after this long. */
|
|
22
|
+
export declare const FIRED_TTL_MS: number;
|
|
23
|
+
/** Hard bound on the fired set. */
|
|
24
|
+
export declare const FIRED_MAX = 512;
|
|
25
|
+
/** The fields of a track-lifecycle event the trigger reads. */
|
|
26
|
+
export interface TrackLifecycleInput {
|
|
27
|
+
readonly deviceId?: unknown;
|
|
28
|
+
readonly trackId?: unknown;
|
|
29
|
+
readonly phase?: unknown;
|
|
30
|
+
readonly bestClassName?: unknown;
|
|
31
|
+
readonly bestConfidence?: unknown;
|
|
32
|
+
readonly zonesVisited?: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** What a detection run exposes to conditions and actions. */
|
|
35
|
+
export interface DetectionVars {
|
|
36
|
+
readonly deviceId: number;
|
|
37
|
+
readonly className: string;
|
|
38
|
+
readonly trackId: string;
|
|
39
|
+
readonly score: number;
|
|
40
|
+
}
|
|
41
|
+
export type DetectionDecision =
|
|
42
|
+
/** Not this trigger's business (other device, malformed, an `end`): neither fired nor traced. */
|
|
43
|
+
{
|
|
44
|
+
readonly kind: 'ignore';
|
|
45
|
+
readonly why: string;
|
|
46
|
+
} | {
|
|
47
|
+
readonly kind: 'reject';
|
|
48
|
+
readonly reason: string;
|
|
49
|
+
readonly vars: DetectionVars;
|
|
50
|
+
} | {
|
|
51
|
+
readonly kind: 'fire';
|
|
52
|
+
readonly reason: string;
|
|
53
|
+
readonly vars: DetectionVars;
|
|
54
|
+
};
|
|
55
|
+
/** `animal` matches a dog; `dog` matches only a dog (a cat is also an animal). */
|
|
56
|
+
export declare function detectionClassMatches(wanted: string, actual: string): boolean;
|
|
57
|
+
export declare class DetectionTriggerFilter {
|
|
58
|
+
private readonly trigger;
|
|
59
|
+
/** `${deviceId}|${trackId}` → fired at. Insertion-ordered, so the oldest is first. */
|
|
60
|
+
private readonly fired;
|
|
61
|
+
constructor(trigger: DetectionTrigger);
|
|
62
|
+
get firedCount(): number;
|
|
63
|
+
evaluate(event: TrackLifecycleInput, now: number): DetectionDecision;
|
|
64
|
+
private remember;
|
|
65
|
+
private expire;
|
|
66
|
+
}
|
|
@@ -10,5 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export type { GenerateAutomationBlockInput } from './codegen.js';
|
|
12
12
|
export { generateAutomationBlock } from './codegen.js';
|
|
13
|
-
export type {
|
|
14
|
-
export {
|
|
13
|
+
export type { ConditionEvaluation, ConditionReader, MirrorRefreshFetchers, MirrorRefreshReport, MirrorSource, } from './condition-eval.js';
|
|
14
|
+
export { collectMirrorSources, ConditionMirror, evaluateConditionTree, } from './condition-eval.js';
|
|
15
|
+
export type { DetectionDecision, DetectionTrigger, DetectionVars, TrackLifecycleInput, } from './detection-trigger.js';
|
|
16
|
+
export { DetectionTriggerFilter, detectionClassMatches, FIRED_MAX, FIRED_TTL_MS, } from './detection-trigger.js';
|
|
17
|
+
export type { CooldownInput, CooldownVerdict, RunGateAdmission } from './run-gate.js';
|
|
18
|
+
export { RunGate } from './run-gate.js';
|
|
19
|
+
export type { AutomationConditionTraceNode, AutomationRunOutcome, AutomationRunTrace, AutomationRunVerdict, AutomationStateDoc, AutomationStepTrace, AutomationTriggerTrace, TraceNodeOutcome, TraceValue, } from './trace.js';
|
|
20
|
+
export { AutomationConditionTraceNodeSchema, EMPTY_AUTOMATION_STATE, mergeSkipTraces, AutomationRunOutcomeSchema, AutomationRunTraceSchema, AutomationRunVerdictSchema, AutomationStateDocSchema, AutomationStepTraceSchema, AutomationTriggerTraceSchema, appendTrace, buildTriggerTrace, isSkippedTrace, skipSignature, TraceNodeOutcomeSchema, TraceValueSchema, } from './trace.js';
|
|
21
|
+
export type { ActionStep, AutomationAction, AutomationCondition, AutomationConditionAll, AutomationConditionAny, AutomationConditionExpression, AutomationConditionLeaf, AutomationConditionNot, AutomationConditionOperator, AutomationRecipe, AutomationRunMode, AutomationTrigger, } from './recipe.js';
|
|
22
|
+
export { ActionStepSchema, AutomationActionSchema, AutomationCapStepSchema, AutomationWaitStepSchema, AutomationConditionOperatorSchema, AutomationConditionSchema, AutomationRecipeSchema, AutomationRunModeSchema, AutomationTriggerSchema, conditionDepth, countConditionLeaves, DEFAULT_TRACE_KEEP, MAX_TRACE_KEEP, MAX_CONDITION_DEPTH, MAX_CONDITION_LEAVES, validateRecipeBounds, } from './recipe.js';
|
|
23
|
+
export * from './action.js';
|
|
24
|
+
export * from './step-runner.js';
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Three vocabularies, each salvaged rather than invented:
|
|
13
13
|
*
|
|
14
|
-
* - **Triggers** — the only genuinely new surface.
|
|
14
|
+
* - **Triggers** — the only genuinely new surface. Six members, none of which
|
|
15
15
|
* touches notification delivery. The NC keeps its four *pipeline* triggers;
|
|
16
16
|
* these are device/time triggers and the two sets do not overlap.
|
|
17
17
|
* - **Conditions** — `ALL`/`ANY`/`NOT` over two leaf kinds, bounded. The
|
|
@@ -108,6 +108,15 @@ export declare const AutomationTriggerSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
108
108
|
cron: z.ZodString;
|
|
109
109
|
}, z.core.$strip>, z.ZodObject<{
|
|
110
110
|
kind: z.ZodLiteral<"manual">;
|
|
111
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
112
|
+
kind: z.ZodLiteral<"detection">;
|
|
113
|
+
classes: z.ZodArray<z.ZodString>;
|
|
114
|
+
deviceIds: z.ZodArray<z.ZodNumber>;
|
|
115
|
+
zones: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
116
|
+
minScore: z.ZodOptional<z.ZodNumber>;
|
|
117
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
118
|
+
kind: z.ZodLiteral<"interval">;
|
|
119
|
+
minutes: z.ZodNumber;
|
|
111
120
|
}, z.core.$strip>], "kind">;
|
|
112
121
|
export type AutomationTrigger = z.infer<typeof AutomationTriggerSchema>;
|
|
113
122
|
/**
|
|
@@ -128,6 +137,36 @@ export type AutomationTrigger = z.infer<typeof AutomationTriggerSchema>;
|
|
|
128
137
|
* is called. The runner still receives a number and is untouched; the
|
|
129
138
|
* resolution is the recipe's job, not the runner's.
|
|
130
139
|
*/
|
|
140
|
+
export declare const AutomationWaitStepSchema: z.ZodObject<{
|
|
141
|
+
kind: z.ZodLiteral<"wait">;
|
|
142
|
+
seconds: z.ZodNumber;
|
|
143
|
+
}, z.core.$strip>;
|
|
144
|
+
export declare const AutomationCapStepSchema: z.ZodObject<{
|
|
145
|
+
kind: z.ZodLiteral<"cap">;
|
|
146
|
+
deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
|
|
147
|
+
$var: z.ZodString;
|
|
148
|
+
}, z.core.$strip>]>;
|
|
149
|
+
cap: z.ZodString;
|
|
150
|
+
method: z.ZodString;
|
|
151
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
152
|
+
}, z.core.$strip>;
|
|
153
|
+
/**
|
|
154
|
+
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
155
|
+
* call back into an Action — so an Action can never reach itself (no cycles).
|
|
156
|
+
*/
|
|
157
|
+
export declare const ActionStepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
158
|
+
kind: z.ZodLiteral<"wait">;
|
|
159
|
+
seconds: z.ZodNumber;
|
|
160
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
161
|
+
kind: z.ZodLiteral<"cap">;
|
|
162
|
+
deviceId: z.ZodUnion<readonly [z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodObject<{
|
|
163
|
+
$var: z.ZodString;
|
|
164
|
+
}, z.core.$strip>]>;
|
|
165
|
+
cap: z.ZodString;
|
|
166
|
+
method: z.ZodString;
|
|
167
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
168
|
+
}, z.core.$strip>], "kind">;
|
|
169
|
+
export type ActionStep = z.infer<typeof ActionStepSchema>;
|
|
131
170
|
export declare const AutomationActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
132
171
|
kind: z.ZodLiteral<"wait">;
|
|
133
172
|
seconds: z.ZodNumber;
|
|
@@ -139,11 +178,46 @@ export declare const AutomationActionSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
|
|
|
139
178
|
cap: z.ZodString;
|
|
140
179
|
method: z.ZodString;
|
|
141
180
|
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
181
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
182
|
+
kind: z.ZodLiteral<"action">;
|
|
183
|
+
actionId: z.ZodString;
|
|
184
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
142
185
|
}, z.core.$strip>, z.ZodObject<{
|
|
143
186
|
kind: z.ZodLiteral<"code">;
|
|
144
187
|
code: z.ZodString;
|
|
145
188
|
}, z.core.$strip>], "kind">;
|
|
146
189
|
export type AutomationAction = z.infer<typeof AutomationActionSchema>;
|
|
190
|
+
/**
|
|
191
|
+
* How a trigger that arrives while the automation is busy (or cooling down) is
|
|
192
|
+
* handled. One of the automation's execution modes, never a property of its
|
|
193
|
+
* actions. Absent means `single`.
|
|
194
|
+
*
|
|
195
|
+
* - `single` — drop the trigger while a run is in progress (traced + logged).
|
|
196
|
+
* - `restart` — abort the run in progress (at its next step boundary), start fresh.
|
|
197
|
+
* - `queued` — queue up to `max` triggers, drop the rest (traced).
|
|
198
|
+
* - `parallel` — up to `max` runs at once, drop the rest (traced).
|
|
199
|
+
* - `cooldown` — `single`, and no new run until `minutes` after the last run
|
|
200
|
+
* STARTED; persisted across a restart. A manual trigger bypasses only this
|
|
201
|
+
* wait, never the single/queued limits.
|
|
202
|
+
*/
|
|
203
|
+
export declare const AutomationRunModeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
204
|
+
mode: z.ZodLiteral<"single">;
|
|
205
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
206
|
+
mode: z.ZodLiteral<"restart">;
|
|
207
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
208
|
+
mode: z.ZodLiteral<"queued">;
|
|
209
|
+
max: z.ZodNumber;
|
|
210
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
211
|
+
mode: z.ZodLiteral<"parallel">;
|
|
212
|
+
max: z.ZodNumber;
|
|
213
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
214
|
+
mode: z.ZodLiteral<"cooldown">;
|
|
215
|
+
minutes: z.ZodNumber;
|
|
216
|
+
}, z.core.$strip>], "mode">;
|
|
217
|
+
export type AutomationRunMode = z.infer<typeof AutomationRunModeSchema>;
|
|
218
|
+
/** Run traces kept per automation when the recipe does not say. */
|
|
219
|
+
export declare const DEFAULT_TRACE_KEEP = 20;
|
|
220
|
+
export declare const MAX_TRACE_KEEP = 100;
|
|
147
221
|
export declare const AutomationRecipeSchema: z.ZodObject<{
|
|
148
222
|
triggers: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
149
223
|
kind: z.ZodLiteral<"device-state">;
|
|
@@ -163,6 +237,15 @@ export declare const AutomationRecipeSchema: z.ZodObject<{
|
|
|
163
237
|
cron: z.ZodString;
|
|
164
238
|
}, z.core.$strip>, z.ZodObject<{
|
|
165
239
|
kind: z.ZodLiteral<"manual">;
|
|
240
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
241
|
+
kind: z.ZodLiteral<"detection">;
|
|
242
|
+
classes: z.ZodArray<z.ZodString>;
|
|
243
|
+
deviceIds: z.ZodArray<z.ZodNumber>;
|
|
244
|
+
zones: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
245
|
+
minScore: z.ZodOptional<z.ZodNumber>;
|
|
246
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
247
|
+
kind: z.ZodLiteral<"interval">;
|
|
248
|
+
minutes: z.ZodNumber;
|
|
166
249
|
}, z.core.$strip>], "kind">>;
|
|
167
250
|
conditions: z.ZodOptional<z.ZodType<AutomationCondition, unknown, z.core.$ZodTypeInternals<AutomationCondition, unknown>>>;
|
|
168
251
|
actions: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -176,10 +259,29 @@ export declare const AutomationRecipeSchema: z.ZodObject<{
|
|
|
176
259
|
cap: z.ZodString;
|
|
177
260
|
method: z.ZodString;
|
|
178
261
|
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
262
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
263
|
+
kind: z.ZodLiteral<"action">;
|
|
264
|
+
actionId: z.ZodString;
|
|
265
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
179
266
|
}, z.core.$strip>, z.ZodObject<{
|
|
180
267
|
kind: z.ZodLiteral<"code">;
|
|
181
268
|
code: z.ZodString;
|
|
182
269
|
}, z.core.$strip>], "kind">>;
|
|
270
|
+
runMode: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
271
|
+
mode: z.ZodLiteral<"single">;
|
|
272
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
273
|
+
mode: z.ZodLiteral<"restart">;
|
|
274
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
275
|
+
mode: z.ZodLiteral<"queued">;
|
|
276
|
+
max: z.ZodNumber;
|
|
277
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
278
|
+
mode: z.ZodLiteral<"parallel">;
|
|
279
|
+
max: z.ZodNumber;
|
|
280
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
281
|
+
mode: z.ZodLiteral<"cooldown">;
|
|
282
|
+
minutes: z.ZodNumber;
|
|
283
|
+
}, z.core.$strip>], "mode">>;
|
|
284
|
+
keepTraces: z.ZodOptional<z.ZodNumber>;
|
|
183
285
|
}, z.core.$strip>;
|
|
184
286
|
export type AutomationRecipe = z.infer<typeof AutomationRecipeSchema>;
|
|
185
287
|
/** Depth of a tree, root container = 1. A bare leaf is depth 1. */
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The admission gate of an automation — what its execution mode decides when a
|
|
3
|
+
* trigger arrives while a run is in progress, and (for `cooldown`) when the
|
|
4
|
+
* last run is too recent.
|
|
5
|
+
*
|
|
6
|
+
* In memory by design (D49): the decision is taken off in-process counters and
|
|
7
|
+
* never off a read that can fail. Only the cooldown's `lastRunStartedAt` is
|
|
8
|
+
* persisted (by the block, through `ctx.storage`) so a restart cannot re-fire
|
|
9
|
+
* inside the window; it is handed in, not read here.
|
|
10
|
+
*
|
|
11
|
+
* Usage: `admit()` once per trigger; every admitted run (`run` / `restart`, or
|
|
12
|
+
* a queued one when it is dequeued) ends with exactly one `release()`, which
|
|
13
|
+
* says whether a queued trigger now owns the freed slot.
|
|
14
|
+
*/
|
|
15
|
+
import type { AutomationRunMode } from './recipe.js';
|
|
16
|
+
export interface RunGateAdmission {
|
|
17
|
+
readonly decision: 'run' | 'queued' | 'restart' | 'drop';
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CooldownInput {
|
|
21
|
+
/** A manual trigger is operator intent: it skips the cooldown wait only. */
|
|
22
|
+
readonly manual: boolean;
|
|
23
|
+
readonly now: number;
|
|
24
|
+
readonly lastRunStartedAt: number | null;
|
|
25
|
+
}
|
|
26
|
+
export interface CooldownVerdict {
|
|
27
|
+
readonly ok: boolean;
|
|
28
|
+
readonly bypassed: boolean;
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class RunGate {
|
|
32
|
+
private readonly mode;
|
|
33
|
+
private active;
|
|
34
|
+
private pending;
|
|
35
|
+
constructor(mode: AutomationRunMode);
|
|
36
|
+
get activeRuns(): number;
|
|
37
|
+
get queuedTriggers(): number;
|
|
38
|
+
admit(): RunGateAdmission;
|
|
39
|
+
/** The run holding a slot ended (any outcome). True = a queued trigger took the slot. */
|
|
40
|
+
release(): boolean;
|
|
41
|
+
/** Checked AFTER the conditions pass. Always `ok` outside `cooldown` mode. */
|
|
42
|
+
cooldown(input: CooldownInput): CooldownVerdict;
|
|
43
|
+
}
|