@camstack/types 1.2.428 → 1.2.430

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/addon/session-health.d.ts +124 -0
  3. package/dist/addon.js +4 -3
  4. package/dist/addon.mjs +4 -3
  5. package/dist/automation/action.d.ts +210 -0
  6. package/dist/automation/condition-eval.d.ts +64 -0
  7. package/dist/automation/detection-trigger.d.ts +66 -0
  8. package/dist/automation/index.d.ts +12 -2
  9. package/dist/automation/recipe.d.ts +103 -1
  10. package/dist/automation/run-gate.d.ts +43 -0
  11. package/dist/automation/step-runner.d.ts +82 -0
  12. package/dist/automation/trace.d.ts +325 -0
  13. package/dist/capabilities/action-source.cap.d.ts +74 -0
  14. package/dist/capabilities/actions.cap.d.ts +459 -0
  15. package/dist/capabilities/core-blocks.cap.d.ts +761 -0
  16. package/dist/capabilities/index.d.ts +5 -1
  17. package/dist/capabilities/pet-feeder.cap.d.ts +15 -1
  18. package/dist/client.js +73 -49
  19. package/dist/client.mjs +73 -49
  20. package/dist/enums/event-category.d.ts +5 -0
  21. package/dist/enums.js +1 -1
  22. package/dist/enums.mjs +1 -1
  23. package/dist/{event-category-Ave2VSxY.js → event-category-BhzwWQCH.js} +5 -0
  24. package/dist/{event-category-CT-hz5Rb.mjs → event-category-hdMR7CTW.mjs} +5 -0
  25. package/dist/generated/addon-api.d.ts +135 -0
  26. package/dist/generated/addon-cap-facts.d.ts +1 -1
  27. package/dist/generated/capability-router-map.d.ts +10 -4
  28. package/dist/generated/collection-array-methods.d.ts +1 -1
  29. package/dist/generated/device-proxy.d.ts +2 -0
  30. package/dist/generated/history-catalog.d.ts +1 -1
  31. package/dist/generated/method-access-map.d.ts +1 -1
  32. package/dist/generated/method-device-selectors.d.ts +2 -2
  33. package/dist/generated/system-proxy.d.ts +5 -1
  34. package/dist/index.d.ts +6 -2
  35. package/dist/index.js +3211 -1093
  36. package/dist/index.mjs +3153 -1094
  37. package/dist/{sleep-edKiOXLo.js → sleep-5zRztaHa.js} +24 -2
  38. package/dist/{sleep--jXS9hWo.mjs → sleep-BHjNez61.mjs} +24 -2
  39. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,11 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.430] - 2026-10-27
11
+
12
+ ### Added
13
+
14
+ - Actions: declared list entries (action-source, actions caps), an action step for automations, and run traces kept in the hub ledger
15
+ - 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
16
+
17
+ ## [1.2.429] - 2026-10-27
18
+
19
+ ### Added
20
+
21
+ - pet-feeder reports bowlFood, eating and supportsMarkRefilled
22
+
10
23
  ## [1.2.428] - 2026-10-27
11
24
 
12
25
  ### Added
13
26
 
14
27
  - The published package now includes its README and CHANGELOG.
28
+ - Cloud integrations raise one alert when their session is lost and clear it when it is back
15
29
 
16
30
  ## [1.2.427] - 2026-10-27
17
31
 
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Session health — the STANDARD "cloud connection lost" alert (D-session-health).
3
+ *
4
+ * Every addon that keeps a cloud session for an integration (an account login, a
5
+ * refresh token, a long-lived socket) reports two facts and nothing else:
6
+ *
7
+ * - `lost(integrationId, …)` the session was refused / kicked / dropped;
8
+ * - `restored(integrationId)` a call went through again.
9
+ *
10
+ * The tracker owns everything between: a debounce (a transient reconnect never
11
+ * alerts), ONE alert per integration instance (never one per device, never one
12
+ * per failed poll), the wording (provider, integration, reason), an action the
13
+ * operator can follow, and the automatic resolve when the session is back.
14
+ *
15
+ * Alerts are durable state that the next run of the addon cannot see, so the
16
+ * first `restored()` of a process resolves whatever a previous run left active.
17
+ */
18
+ import type { Alert, AlertSeverity } from '../capabilities/alerts.cap.js';
19
+ import type { IScopedLogger } from '../interfaces/logging.js';
20
+ /** Why the session ended. `kicked` = the same account signed in elsewhere. */
21
+ export type SessionLossReason = 'kicked' | 'auth-rejected' | 'token-expired' | 'connection-lost';
22
+ /** What an alert's action looks like (the `Alert.action` shape). */
23
+ export interface SessionAlertAction {
24
+ readonly label: string;
25
+ readonly href?: string;
26
+ readonly navigation?: 'app' | 'server';
27
+ }
28
+ /** The slice of `ctx.api.alerts` the tracker needs. Structural, so `ctx.api.alerts` fits. */
29
+ export interface SessionHealthAlertsPort {
30
+ readonly emit?: {
31
+ mutate(alert: Alert): Promise<unknown>;
32
+ };
33
+ readonly update?: {
34
+ mutate(input: {
35
+ readonly alertId: string;
36
+ readonly patch: Partial<Alert>;
37
+ }): Promise<unknown>;
38
+ };
39
+ }
40
+ export declare const SESSION_LOST_ALERT_CATEGORY = "integration.session-lost";
41
+ /** A transient reconnect is quiet for this long before it is worth an alert. */
42
+ export declare const DEFAULT_SESSION_LOST_DEBOUNCE_MS: number;
43
+ export declare function sessionLostAlertId(kind: string, integrationId: string): string;
44
+ export interface BuildSessionLostAlertInput {
45
+ /** Stable provider key, e.g. `petkit` — part of the alert id and source. */
46
+ readonly kind: string;
47
+ /** Display name, e.g. `PetKit`. */
48
+ readonly providerName: string;
49
+ readonly integrationId: string;
50
+ readonly integrationName: string;
51
+ readonly reason: SessionLossReason;
52
+ /** Defaults to "Open integration" (the admin integration page). */
53
+ readonly action?: SessionAlertAction;
54
+ readonly severity?: AlertSeverity;
55
+ readonly now?: number;
56
+ }
57
+ /** Pure builder, exported so an addon with its own flow can still emit the standard shape. */
58
+ export declare function buildSessionLostAlert(input: BuildSessionLostAlertInput): Alert;
59
+ export interface SessionHealthTrackerOptions {
60
+ readonly kind: string;
61
+ readonly providerName: string;
62
+ readonly logger: IScopedLogger;
63
+ /** Read lazily: `ctx.api.alerts` may be absent early in boot. */
64
+ readonly getAlerts: () => SessionHealthAlertsPort | undefined;
65
+ readonly debounceMs?: number;
66
+ }
67
+ export interface SessionLostInfo {
68
+ readonly integrationName: string;
69
+ readonly reason: SessionLossReason;
70
+ /** Re-auth route when the addon has one; otherwise "Open integration". */
71
+ readonly action?: SessionAlertAction;
72
+ }
73
+ export declare class SessionHealthTracker {
74
+ #private;
75
+ constructor(opts: SessionHealthTrackerOptions);
76
+ /** The session is gone. Idempotent: call it on every failed poll. */
77
+ lost(integrationId: string, info: SessionLostInfo): void;
78
+ /** A call went through. Idempotent: call it on every successful poll. */
79
+ restored(integrationId: string): Promise<void>;
80
+ /** The integration is gone (deleted / disabled): drop its state and its alert. */
81
+ forget(integrationId: string): Promise<void>;
82
+ /** Stop every pending timer (addon shutdown). */
83
+ dispose(): void;
84
+ }
85
+ export interface IntegrationSessionBinding {
86
+ readonly logger: IScopedLogger;
87
+ readonly getAlerts: () => SessionHealthAlertsPort | undefined;
88
+ readonly debounceMs?: number;
89
+ }
90
+ export interface IntegrationSessionReporterOptions {
91
+ readonly kind: string;
92
+ readonly providerName: string;
93
+ /**
94
+ * Is `err` the cloud ending OUR session (kicked, token refused, sign-in
95
+ * rejected)? `null` for anything else — a network blip or a per-device error
96
+ * must never read as a lost session.
97
+ */
98
+ readonly classify: (err: unknown) => SessionLossReason | null;
99
+ /** Re-auth route when the addon has one; otherwise "Open integration". */
100
+ readonly action?: (integrationId: string) => SessionAlertAction | undefined;
101
+ }
102
+ /**
103
+ * The addon-facing face of {@link SessionHealthTracker}: a process-wide object an
104
+ * integration manager AND its device classes can reach (the kernel builds a
105
+ * device from a `DeviceContext` alone, so neither can be handed a tracker).
106
+ * The addon `bind()`s it in `onInitialize`, `unbind()`s it on shutdown and
107
+ * `forget()`s an integration that vanished; everything before `bind` is a no-op.
108
+ */
109
+ export declare class IntegrationSessionReporter {
110
+ #private;
111
+ constructor(opts: IntegrationSessionReporterOptions);
112
+ bind(binding: IntegrationSessionBinding): void;
113
+ unbind(): void;
114
+ /** The manager tells us the display name once, so a device can report by id alone. */
115
+ register(integrationId: string, integrationName: string): void;
116
+ /** A call failed. Only a session-ending failure counts; anything else is ignored. */
117
+ failure(integrationId: string, err: unknown): void;
118
+ /** A call went through. */
119
+ ok(integrationId: string): Promise<void>;
120
+ /** The integration was removed. */
121
+ forget(integrationId: string): Promise<void>;
122
+ }
123
+ /** What a manager / device depends on to report. */
124
+ export type IntegrationSessionSink = Pick<IntegrationSessionReporter, 'register' | 'failure' | 'ok'>;
package/dist/addon.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-edKiOXLo.js");
3
- const require_event_category = require("./event-category-Ave2VSxY.js");
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: 26 collection caps, 44 array methods.
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--jXS9hWo.mjs";
2
- import { t as EventCategory } from "./event-category-CT-hz5Rb.mjs";
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: 26 collection caps, 44 array methods.
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 { 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';
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';