@camstack/addon-notifiers 1.2.12 → 1.2.13
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/dist/addon.js +776 -275
- package/dist/addon.mjs +776 -275
- package/package.json +1 -1
package/dist/addon.mjs
CHANGED
|
@@ -9209,8 +9209,11 @@ function prepareNotification(caps, n) {
|
|
|
9209
9209
|
});
|
|
9210
9210
|
}
|
|
9211
9211
|
const inActions = n.actions ?? [];
|
|
9212
|
-
const
|
|
9213
|
-
if (inActions.length >
|
|
9212
|
+
const kept = inActions.slice(0, Math.max(0, caps.actions));
|
|
9213
|
+
if (inActions.length > kept.length) dropped.push("actions");
|
|
9214
|
+
const iconsSupported = caps.actionIcons === true;
|
|
9215
|
+
if (!iconsSupported && kept.some((a) => a.icon !== void 0)) dropped.push("actionIcons");
|
|
9216
|
+
const actions = iconsSupported ? kept : kept.map(({ icon: _icon, ...rest }) => rest);
|
|
9214
9217
|
let clickUrl = null;
|
|
9215
9218
|
if (n.clickUrl !== void 0) if (caps.clickUrl) clickUrl = n.clickUrl;
|
|
9216
9219
|
else dropped.push("clickUrl");
|
|
@@ -9309,6 +9312,290 @@ var MaskGridDimsSchema = object({
|
|
|
9309
9312
|
height: number()
|
|
9310
9313
|
});
|
|
9311
9314
|
/**
|
|
9315
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
9316
|
+
*
|
|
9317
|
+
* Apprise-derived model (see
|
|
9318
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
9319
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
9320
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
9321
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
9322
|
+
* message to what the kind supports — callers never special-case a service.
|
|
9323
|
+
*
|
|
9324
|
+
* DESIGN DECISIONS (locked):
|
|
9325
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
9326
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
9327
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
9328
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
9329
|
+
* alternative would fork the UI per addon and cannot host the
|
|
9330
|
+
* discovery→adopt flow.
|
|
9331
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
9332
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
9333
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
9334
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
9335
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
9336
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
9337
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
9338
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
9339
|
+
* base64 fallback needed.
|
|
9340
|
+
*
|
|
9341
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
9342
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
9343
|
+
* admin "Integrations" page.
|
|
9344
|
+
*/
|
|
9345
|
+
/**
|
|
9346
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
9347
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
9348
|
+
*/
|
|
9349
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
9350
|
+
"image",
|
|
9351
|
+
"video",
|
|
9352
|
+
"gif",
|
|
9353
|
+
"audio",
|
|
9354
|
+
"icon"
|
|
9355
|
+
]);
|
|
9356
|
+
/**
|
|
9357
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
9358
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
9359
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
9360
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
9361
|
+
*/
|
|
9362
|
+
var AttachmentSchema = object({
|
|
9363
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
9364
|
+
url: string().optional(),
|
|
9365
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
9366
|
+
mime: string().optional(),
|
|
9367
|
+
name: string().optional()
|
|
9368
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
9369
|
+
var NotificationFormatSchema = _enum([
|
|
9370
|
+
"text",
|
|
9371
|
+
"markdown",
|
|
9372
|
+
"html"
|
|
9373
|
+
]);
|
|
9374
|
+
/**
|
|
9375
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
9376
|
+
*
|
|
9377
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
9378
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
9379
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
9380
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
9381
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
9382
|
+
*
|
|
9383
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
9384
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
9385
|
+
* differently.
|
|
9386
|
+
*/
|
|
9387
|
+
var NotificationActionIconSchema = _enum([
|
|
9388
|
+
"acknowledge",
|
|
9389
|
+
"dismiss",
|
|
9390
|
+
"silence",
|
|
9391
|
+
"view",
|
|
9392
|
+
"play",
|
|
9393
|
+
"open",
|
|
9394
|
+
"close",
|
|
9395
|
+
"lock",
|
|
9396
|
+
"unlock",
|
|
9397
|
+
"arm",
|
|
9398
|
+
"disarm",
|
|
9399
|
+
"light",
|
|
9400
|
+
"alert"
|
|
9401
|
+
]);
|
|
9402
|
+
/** A single tap-through action button. */
|
|
9403
|
+
var NotificationActionSchema = object({
|
|
9404
|
+
id: string(),
|
|
9405
|
+
label: string(),
|
|
9406
|
+
url: string().optional(),
|
|
9407
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
9408
|
+
icon: NotificationActionIconSchema.optional(),
|
|
9409
|
+
/**
|
|
9410
|
+
* Renders in a warning style where the notifier supports it.
|
|
9411
|
+
*
|
|
9412
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
9413
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
9414
|
+
* does not buy.
|
|
9415
|
+
*/
|
|
9416
|
+
destructive: boolean().optional()
|
|
9417
|
+
});
|
|
9418
|
+
/**
|
|
9419
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
9420
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
9421
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
9422
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
9423
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
9424
|
+
* `priority` for that one target.
|
|
9425
|
+
*/
|
|
9426
|
+
var NotificationSchema = object({
|
|
9427
|
+
body: string(),
|
|
9428
|
+
title: string().optional(),
|
|
9429
|
+
format: NotificationFormatSchema.default("text"),
|
|
9430
|
+
priority: number().int().min(1).max(5).default(3),
|
|
9431
|
+
level: string().optional(),
|
|
9432
|
+
attachments: array(AttachmentSchema).optional(),
|
|
9433
|
+
clickUrl: string().optional(),
|
|
9434
|
+
actions: array(NotificationActionSchema).optional(),
|
|
9435
|
+
sound: string().optional(),
|
|
9436
|
+
ttl: number().optional(),
|
|
9437
|
+
tag: string().optional(),
|
|
9438
|
+
deviceId: number().optional(),
|
|
9439
|
+
eventId: string().optional(),
|
|
9440
|
+
metadata: record(string(), unknown()).optional()
|
|
9441
|
+
});
|
|
9442
|
+
/** One declared native severity/priority level for a kind. */
|
|
9443
|
+
var TargetKindLevelSchema = object({
|
|
9444
|
+
id: string(),
|
|
9445
|
+
label: string(),
|
|
9446
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
9447
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
9448
|
+
flags: object({
|
|
9449
|
+
critical: boolean().optional(),
|
|
9450
|
+
silent: boolean().optional(),
|
|
9451
|
+
noPush: boolean().optional()
|
|
9452
|
+
}).optional(),
|
|
9453
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
9454
|
+
requires: array(string()).optional(),
|
|
9455
|
+
description: string().optional()
|
|
9456
|
+
});
|
|
9457
|
+
/** The full capability block consulted before dispatch. */
|
|
9458
|
+
var TargetKindCapsSchema = object({
|
|
9459
|
+
attachments: object({
|
|
9460
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
9461
|
+
mode: _enum([
|
|
9462
|
+
"url",
|
|
9463
|
+
"bytes",
|
|
9464
|
+
"both"
|
|
9465
|
+
]),
|
|
9466
|
+
max: number().int().nonnegative(),
|
|
9467
|
+
maxBytes: number().int().positive().optional()
|
|
9468
|
+
}),
|
|
9469
|
+
/** Max action buttons (0 = none). */
|
|
9470
|
+
actions: number().int().nonnegative(),
|
|
9471
|
+
/**
|
|
9472
|
+
* Whether this kind renders a per-action ICON.
|
|
9473
|
+
*
|
|
9474
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
9475
|
+
* run on the addon cap path — three production failures in one day taught
|
|
9476
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
9477
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
9478
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
9479
|
+
*/
|
|
9480
|
+
actionIcons: boolean().optional(),
|
|
9481
|
+
levels: array(TargetKindLevelSchema),
|
|
9482
|
+
format: array(NotificationFormatSchema),
|
|
9483
|
+
clickUrl: boolean(),
|
|
9484
|
+
sound: boolean(),
|
|
9485
|
+
ttl: boolean(),
|
|
9486
|
+
bodyMaxLen: number().int().positive()
|
|
9487
|
+
});
|
|
9488
|
+
/**
|
|
9489
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
9490
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
9491
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
9492
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
9493
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
9494
|
+
*/
|
|
9495
|
+
var ConfigSchemaPassthrough$1 = unknown();
|
|
9496
|
+
var TargetKindSchema = object({
|
|
9497
|
+
kind: string(),
|
|
9498
|
+
label: string(),
|
|
9499
|
+
icon: string(),
|
|
9500
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
9501
|
+
addonId: string(),
|
|
9502
|
+
/**
|
|
9503
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
9504
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
9505
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
9506
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
9507
|
+
*
|
|
9508
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
9509
|
+
* client, and a native client joins it onto its own hub base.
|
|
9510
|
+
*
|
|
9511
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
9512
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
9513
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
9514
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
9515
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
9516
|
+
*/
|
|
9517
|
+
iconUrl: string().optional(),
|
|
9518
|
+
/**
|
|
9519
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
9520
|
+
*
|
|
9521
|
+
* The server knows this and therefore says it, because the client cannot
|
|
9522
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
9523
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
9524
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
9525
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
9526
|
+
*
|
|
9527
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
9528
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
9529
|
+
* its raster path, which is the safe default for an unknown image.
|
|
9530
|
+
*/
|
|
9531
|
+
iconMediaType: string().optional(),
|
|
9532
|
+
configSchema: ConfigSchemaPassthrough$1,
|
|
9533
|
+
supportsDiscovery: boolean(),
|
|
9534
|
+
caps: TargetKindCapsSchema
|
|
9535
|
+
});
|
|
9536
|
+
/**
|
|
9537
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
9538
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
9539
|
+
* round-trip a stored secret to the UI.
|
|
9540
|
+
*/
|
|
9541
|
+
var TargetSchema = object({
|
|
9542
|
+
id: string(),
|
|
9543
|
+
name: string(),
|
|
9544
|
+
kind: string(),
|
|
9545
|
+
addonId: string(),
|
|
9546
|
+
enabled: boolean(),
|
|
9547
|
+
config: record(string(), unknown())
|
|
9548
|
+
});
|
|
9549
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
9550
|
+
var DiscoveredTargetSchema = object({
|
|
9551
|
+
kind: string(),
|
|
9552
|
+
suggestedName: string(),
|
|
9553
|
+
config: record(string(), unknown())
|
|
9554
|
+
});
|
|
9555
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
9556
|
+
var RenderedAsSchema = object({
|
|
9557
|
+
level: string(),
|
|
9558
|
+
format: NotificationFormatSchema,
|
|
9559
|
+
attachmentsSent: number().int().nonnegative(),
|
|
9560
|
+
actionsSent: number().int().nonnegative(),
|
|
9561
|
+
truncated: boolean(),
|
|
9562
|
+
dropped: array(string())
|
|
9563
|
+
});
|
|
9564
|
+
var SendResultSchema = object({
|
|
9565
|
+
success: boolean(),
|
|
9566
|
+
error: string().optional(),
|
|
9567
|
+
renderedAs: RenderedAsSchema.optional()
|
|
9568
|
+
});
|
|
9569
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
9570
|
+
var TestResultSchema = SendResultSchema;
|
|
9571
|
+
var notificationOutputCapability = {
|
|
9572
|
+
name: "notification-output",
|
|
9573
|
+
scope: "system",
|
|
9574
|
+
mode: "collection",
|
|
9575
|
+
methods: {
|
|
9576
|
+
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
9577
|
+
listTargets: method(object({}), array(TargetSchema)),
|
|
9578
|
+
discoverTargets: method(object({
|
|
9579
|
+
kind: string(),
|
|
9580
|
+
config: record(string(), unknown()).optional()
|
|
9581
|
+
}), array(DiscoveredTargetSchema)),
|
|
9582
|
+
send: method(object({
|
|
9583
|
+
targetId: string(),
|
|
9584
|
+
notification: NotificationSchema
|
|
9585
|
+
}), SendResultSchema, { kind: "mutation" }),
|
|
9586
|
+
testTarget: method(object({
|
|
9587
|
+
targetId: string(),
|
|
9588
|
+
sample: NotificationSchema.optional()
|
|
9589
|
+
}), TestResultSchema, { kind: "mutation" }),
|
|
9590
|
+
upsertTarget: method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }),
|
|
9591
|
+
deleteTarget: method(object({ targetId: string() }), _void(), { kind: "mutation" }),
|
|
9592
|
+
setTargetEnabled: method(object({
|
|
9593
|
+
targetId: string(),
|
|
9594
|
+
enabled: boolean()
|
|
9595
|
+
}), _void(), { kind: "mutation" })
|
|
9596
|
+
}
|
|
9597
|
+
};
|
|
9598
|
+
/**
|
|
9312
9599
|
* notification-rules — the Notification Center rule surface (P1 core).
|
|
9313
9600
|
*
|
|
9314
9601
|
* Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
|
|
@@ -9438,7 +9725,115 @@ var NcZoneConditionSchema = object({
|
|
|
9438
9725
|
* The P1 condition set — a flat AND of groups; absent group = pass;
|
|
9439
9726
|
* membership lists are OR within the list (spec §2.3).
|
|
9440
9727
|
*/
|
|
9728
|
+
/**
|
|
9729
|
+
* What a rule may actuate.
|
|
9730
|
+
*
|
|
9731
|
+
* **No hand-maintained allowlist** (operator decision, and the right one — a
|
|
9732
|
+
* written list of methods is a third parallel map to keep aligned, and this
|
|
9733
|
+
* repo has paid for those). The boundary instead comes from a property the
|
|
9734
|
+
* capabilities already carry: an action may target only a **device-scoped**
|
|
9735
|
+
* capability method.
|
|
9736
|
+
*
|
|
9737
|
+
* That is not decoration. A rule can be authored by a NON-ADMIN — personal
|
|
9738
|
+
* rules are a supported flow — and the executor runs with the addon's
|
|
9739
|
+
* privileges, so an unbounded action is an arbitrary RPC channel with a
|
|
9740
|
+
* privilege escalation attached. Restricting to device scope excludes the
|
|
9741
|
+
* system caps (`device-manager.removeDevice` and friends) by construction,
|
|
9742
|
+
* costs nothing to maintain, and cannot rot: a cap that stops being
|
|
9743
|
+
* device-scoped stops being actuatable in the same change.
|
|
9744
|
+
*
|
|
9745
|
+
* The executor enforces it; {@link NcRuleActionSchema} carries the intent.
|
|
9746
|
+
*/
|
|
9747
|
+
/**
|
|
9748
|
+
* One step of a sequence.
|
|
9749
|
+
*
|
|
9750
|
+
* `wait` is a first-class step rather than a property of the next action: it is
|
|
9751
|
+
* what makes a sequence a SEQUENCE and not a list — "unlock, wait 5s, open"
|
|
9752
|
+
* cannot be expressed otherwise.
|
|
9753
|
+
*/
|
|
9754
|
+
var NcRuleActionSchema = discriminatedUnion("kind", [object({
|
|
9755
|
+
kind: literal("wait"),
|
|
9756
|
+
seconds: number().min(0).max(300)
|
|
9757
|
+
}), object({
|
|
9758
|
+
kind: literal("cap"),
|
|
9759
|
+
deviceId: number().int(),
|
|
9760
|
+
/** Capability name, e.g. `alarm-panel`. */
|
|
9761
|
+
cap: string().min(1),
|
|
9762
|
+
/** Method on it. The executor refuses a non-device-scoped cap. */
|
|
9763
|
+
method: string().min(1),
|
|
9764
|
+
/** Method arguments, minus `deviceId` (the executor injects it). */
|
|
9765
|
+
args: record(string(), unknown()).optional()
|
|
9766
|
+
})]);
|
|
9767
|
+
/**
|
|
9768
|
+
* A named, ordered run of steps with its own throttle.
|
|
9769
|
+
*
|
|
9770
|
+
* `minDelaySec` exists because a noisy rule otherwise hammers a physical
|
|
9771
|
+
* actuator — the rule's own cooldown governs NOTIFICATIONS, which is a
|
|
9772
|
+
* different budget from "how often may this gate actually open".
|
|
9773
|
+
*/
|
|
9774
|
+
var NcRuleActionSequenceSchema = object({
|
|
9775
|
+
name: string().min(1).max(120),
|
|
9776
|
+
enabled: boolean(),
|
|
9777
|
+
minDelaySec: number().int().min(0).max(86400).optional(),
|
|
9778
|
+
actions: array(NcRuleActionSchema).min(1)
|
|
9779
|
+
});
|
|
9780
|
+
/**
|
|
9781
|
+
* One button carried by the notification, running a named sequence on tap.
|
|
9782
|
+
*
|
|
9783
|
+
* **Read this before adding a button that does something physical.** The tap
|
|
9784
|
+
* arrives over a link that travelled through third-party infrastructure — ntfy,
|
|
9785
|
+
* a push relay, whatever forwarded the message — and the callback's ONLY
|
|
9786
|
+
* authority is the token in that link: single-use, short-lived, bound to this
|
|
9787
|
+
* one action of this one notification. It does not identify who tapped.
|
|
9788
|
+
* Whoever holds the notification can run the button, once, inside the window.
|
|
9789
|
+
* That is the operator's explicit choice (2026-08-05), and `destructive` is a
|
|
9790
|
+
* rendering hint, not a second gate. [D47](decisions/adr-0047.md).
|
|
9791
|
+
*/
|
|
9792
|
+
var NcRuleNotificationButtonSchema = object({
|
|
9793
|
+
/** Stable id — travels in the callback and identifies the button in logs. */
|
|
9794
|
+
id: string().min(1).max(64),
|
|
9795
|
+
label: string().min(1).max(40),
|
|
9796
|
+
/** Name of a sequence in `onTrigger`. The dispatcher drops a button whose
|
|
9797
|
+
* sequence does not exist rather than minting a token for nothing. */
|
|
9798
|
+
sequence: string().min(1).max(120),
|
|
9799
|
+
icon: NotificationActionIconSchema.optional(),
|
|
9800
|
+
destructive: boolean().optional()
|
|
9801
|
+
});
|
|
9802
|
+
/**
|
|
9803
|
+
* Sequences a rule runs, by hook point.
|
|
9804
|
+
*
|
|
9805
|
+
* ONLY `onTrigger` is here, deliberately. The reference also has activation /
|
|
9806
|
+
* deactivation / reset / post-generation hooks, and they are wanted — but this
|
|
9807
|
+
* repo's expensive failure mode is declaring a surface nothing produces, so a
|
|
9808
|
+
* hook appears here in the same change that produces its edge, never before.
|
|
9809
|
+
*/
|
|
9810
|
+
var NcRuleActionsSchema = object({
|
|
9811
|
+
/** Runs when the rule MATCHES. */
|
|
9812
|
+
onTrigger: array(NcRuleActionSequenceSchema).optional(),
|
|
9813
|
+
/**
|
|
9814
|
+
* Buttons the NOTIFICATION carries, each running one of this rule's
|
|
9815
|
+
* sequences when tapped.
|
|
9816
|
+
*
|
|
9817
|
+
* Deliberately a REFERENCE to a sequence rather than a second place to
|
|
9818
|
+
* author steps. A button that could define its own actions would be a
|
|
9819
|
+
* parallel actuation vocabulary — the executor's device-scope check, the
|
|
9820
|
+
* stop-at-first-failure rule and the per-sequence throttle all live on
|
|
9821
|
+
* sequences, and a second authoring surface would drift from every one of
|
|
9822
|
+
* them.
|
|
9823
|
+
*
|
|
9824
|
+
* A sequence reachable ONLY by a button simply appears in `onTrigger` with
|
|
9825
|
+
* `enabled: false`: it is then authored, throttled and validated like the
|
|
9826
|
+
* rest, and nothing runs it automatically.
|
|
9827
|
+
*/
|
|
9828
|
+
buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
|
|
9829
|
+
});
|
|
9441
9830
|
var NcConditionsSchema = object({
|
|
9831
|
+
/** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
|
|
9832
|
+
deviceState: object({
|
|
9833
|
+
deviceId: number().int(),
|
|
9834
|
+
/** Any of these matches. */
|
|
9835
|
+
states: array(string().min(1)).min(1)
|
|
9836
|
+
}).optional(),
|
|
9442
9837
|
/** Device scope — absent = all devices. */
|
|
9443
9838
|
devices: array(number()).optional(),
|
|
9444
9839
|
/** Detector class names (any overlap with the record's class set). */
|
|
@@ -9639,6 +10034,14 @@ var NcMediaPolicySchema = object({
|
|
|
9639
10034
|
clipPreRollSec: number().int().min(0).max(30).optional(),
|
|
9640
10035
|
clipPostRollSec: number().int().min(0).max(30).optional(),
|
|
9641
10036
|
/**
|
|
10037
|
+
* Playback rate of the attached gif / clip. Absent = 2x.
|
|
10038
|
+
*
|
|
10039
|
+
* A notification clip is GLANCED at on a lock screen, not watched: at real
|
|
10040
|
+
* time an eight-second passage is eight seconds of the recipient's attention
|
|
10041
|
+
* and twice the bytes. 1 is real time for the operator who wants it.
|
|
10042
|
+
*/
|
|
10043
|
+
clipSpeed: number().min(1).max(8).optional(),
|
|
10044
|
+
/**
|
|
9642
10045
|
* Which stream profile the footage is cut from. Absent = the CHEAPEST
|
|
9643
10046
|
* assigned profile: a notification is watched on a phone, so the 4K
|
|
9644
10047
|
* rendition would burn CPU to produce a file the client downscales anyway.
|
|
@@ -9710,7 +10113,30 @@ var NcRuleInputSchema = object({
|
|
|
9710
10113
|
* behaviour, visible to all, read-only in the viewer). Present = personal
|
|
9711
10114
|
* rule owned by this userId. Server-stamped; never trusted from a client.
|
|
9712
10115
|
*/
|
|
9713
|
-
ownerUserId: string().optional()
|
|
10116
|
+
ownerUserId: string().optional(),
|
|
10117
|
+
/**
|
|
10118
|
+
* May a non-admin snooze this rule for EVERYONE, not just themselves?
|
|
10119
|
+
*
|
|
10120
|
+
* A snooze is personal by default — it silences the person who set it. This
|
|
10121
|
+
* opts THIS rule into the "the gardener is here all afternoon" case, where
|
|
10122
|
+
* silencing the camera for the whole household is legitimate. It silences
|
|
10123
|
+
* other people, so it is off unless a rule deliberately allows it.
|
|
10124
|
+
*
|
|
10125
|
+
* `.optional()`, deliberately NOT `.default()`: a Zod default does not run on
|
|
10126
|
+
* the addon cap path (three production failures in one day), so absent is
|
|
10127
|
+
* read as `false` by {@link canSetGlobal} in the engine. Admins are not bound
|
|
10128
|
+
* by this flag — see the scope rules on that function.
|
|
10129
|
+
*/
|
|
10130
|
+
snoozeAllowGlobal: boolean().optional(),
|
|
10131
|
+
/**
|
|
10132
|
+
* Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
|
|
10133
|
+
*
|
|
10134
|
+
* This is what makes the rule set the alarm's trigger set without the alarm
|
|
10135
|
+
* being a special case: arming is
|
|
10136
|
+
* `{ cap: 'alarm-panel', method: 'arm', args: { mode: 'away' } }`, the same
|
|
10137
|
+
* shape as every other actuation.
|
|
10138
|
+
*/
|
|
10139
|
+
actions: NcRuleActionsSchema.optional()
|
|
9714
10140
|
});
|
|
9715
10141
|
/**
|
|
9716
10142
|
* Partial patch for `updateRule` — any subset of the input fields, plus the
|
|
@@ -9781,7 +10207,8 @@ var NcConditionDescriptorSchema = object({
|
|
|
9781
10207
|
"packagePhase",
|
|
9782
10208
|
"crossingSelect",
|
|
9783
10209
|
"polygonDraw",
|
|
9784
|
-
"occupancy"
|
|
10210
|
+
"occupancy",
|
|
10211
|
+
"deviceState"
|
|
9785
10212
|
]),
|
|
9786
10213
|
operator: _enum([
|
|
9787
10214
|
"in",
|
|
@@ -9795,7 +10222,28 @@ var NcConditionDescriptorSchema = object({
|
|
|
9795
10222
|
/** Which delivery kinds the condition applies to. */
|
|
9796
10223
|
appliesTo: array(NcDeliverySchema),
|
|
9797
10224
|
phase: string(),
|
|
9798
|
-
description: string().optional()
|
|
10225
|
+
description: string().optional(),
|
|
10226
|
+
/**
|
|
10227
|
+
* The CHOICES for a single-choice widget (`sourceSelect`, `crossingSelect`,
|
|
10228
|
+
* `packagePhase`, …), served with the descriptor.
|
|
10229
|
+
*
|
|
10230
|
+
* Before this the descriptor said which widget to render and not what to put
|
|
10231
|
+
* in it, so every option list lived in three places: this file's enums, the
|
|
10232
|
+
* admin's `NC_*_OPTIONS` and the viewer's `NC_*_VALUES`. That triple mirror
|
|
10233
|
+
* is the drift that emptied the viewer's rule editor on 2026-08-04 — the app
|
|
10234
|
+
* mirrors the cap by hand, so it is only ever as current as its last build.
|
|
10235
|
+
*
|
|
10236
|
+
* With the options on the wire, a condition of an EXISTING `valueType` costs
|
|
10237
|
+
* zero client changes. Clients keep a local fallback for an older hub that
|
|
10238
|
+
* does not send them; absent here is "use your own list", not "no choices".
|
|
10239
|
+
*/
|
|
10240
|
+
options: array(object({
|
|
10241
|
+
/** Written to the rule verbatim. `''` means the ABSENT state. */
|
|
10242
|
+
value: string(),
|
|
10243
|
+
label: string(),
|
|
10244
|
+
/** What THIS choice matches — shown one at a time, under the control. */
|
|
10245
|
+
hint: string().optional()
|
|
10246
|
+
})).readonly().optional()
|
|
9799
10247
|
});
|
|
9800
10248
|
/**
|
|
9801
10249
|
* The delivery lifecycle status of a history row — a straight read of the
|
|
@@ -9880,6 +10328,74 @@ var NcHistoryFilterSchema = object({
|
|
|
9880
10328
|
until: number().optional(),
|
|
9881
10329
|
limit: number().int().min(1).max(500).default(100)
|
|
9882
10330
|
});
|
|
10331
|
+
/**
|
|
10332
|
+
* What a snooze covers. Broader scopes win when several overlap, so one window
|
|
10333
|
+
* leaves ONE digest rather than a rule snooze and a whole-feed snooze both
|
|
10334
|
+
* summarising the same silence.
|
|
10335
|
+
*/
|
|
10336
|
+
var NcSnoozeScopeSchema = _enum([
|
|
10337
|
+
"rule",
|
|
10338
|
+
"device",
|
|
10339
|
+
"all"
|
|
10340
|
+
]);
|
|
10341
|
+
/**
|
|
10342
|
+
* Client-authored snooze. The server stamps `userId`, `startedAt` and
|
|
10343
|
+
* `expiresAt` — a DURATION is sent rather than an instant so a client with a
|
|
10344
|
+
* skewed clock cannot author a window that is already over, or never ends.
|
|
10345
|
+
*/
|
|
10346
|
+
var NcSnoozeInputSchema = object({
|
|
10347
|
+
scope: NcSnoozeScopeSchema,
|
|
10348
|
+
/** Required when `scope: 'rule'` — a scoped snooze with no id matches
|
|
10349
|
+
* NOTHING rather than degrading to "everything". */
|
|
10350
|
+
ruleId: string().optional(),
|
|
10351
|
+
/** Required when `scope: 'device'`. */
|
|
10352
|
+
deviceId: number().int().optional(),
|
|
10353
|
+
durationMinutes: number().int().min(1).max(1440),
|
|
10354
|
+
/**
|
|
10355
|
+
* Silence this for EVERY recipient, not just the caller. Permission is
|
|
10356
|
+
* checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
|
|
10357
|
+
* broader scopes). Absent = personal.
|
|
10358
|
+
*/
|
|
10359
|
+
global: boolean().optional(),
|
|
10360
|
+
/**
|
|
10361
|
+
* Deliver a summary of what was suppressed when the window ends. Absent =
|
|
10362
|
+
* ON: someone silencing a nuisance camera wants it off, someone silencing a
|
|
10363
|
+
* SECURITY camera wants to know what they missed, and choosing "off" for
|
|
10364
|
+
* everybody is how a snooze becomes an outage. Resolved to a concrete
|
|
10365
|
+
* boolean by the server at create time — never left to a Zod default, which
|
|
10366
|
+
* does not run on the addon cap path.
|
|
10367
|
+
*/
|
|
10368
|
+
summary: boolean().optional()
|
|
10369
|
+
});
|
|
10370
|
+
/** A persisted snooze window. */
|
|
10371
|
+
var NcSnoozeSchema = object({
|
|
10372
|
+
id: string(),
|
|
10373
|
+
/** Who set it. Also who it silences, unless `global`. */
|
|
10374
|
+
userId: string(),
|
|
10375
|
+
scope: NcSnoozeScopeSchema,
|
|
10376
|
+
ruleId: string().optional(),
|
|
10377
|
+
deviceId: number().int().optional(),
|
|
10378
|
+
startedAt: number(),
|
|
10379
|
+
/** Exclusive: at exactly this instant the snooze is over. Expiry is a
|
|
10380
|
+
* COMPARISON, not a job — no sweeper can leave the operator silenced. */
|
|
10381
|
+
expiresAt: number(),
|
|
10382
|
+
global: boolean(),
|
|
10383
|
+
summary: boolean(),
|
|
10384
|
+
/** When the end-of-window digest went out. Absent = not sent (yet, or the
|
|
10385
|
+
* window has not closed, or `summary` is false). */
|
|
10386
|
+
digestSentAt: number().optional()
|
|
10387
|
+
});
|
|
10388
|
+
object({
|
|
10389
|
+
snoozeId: string(),
|
|
10390
|
+
targetId: string(),
|
|
10391
|
+
ruleId: string(),
|
|
10392
|
+
ruleName: string(),
|
|
10393
|
+
deviceId: number().int(),
|
|
10394
|
+
/** How many notifications this snooze hid for that pair. */
|
|
10395
|
+
count: number().int(),
|
|
10396
|
+
firstAt: number(),
|
|
10397
|
+
lastAt: number()
|
|
10398
|
+
});
|
|
9883
10399
|
method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), method(object({ ruleId: string() }), object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }), method(object({ rule: NcRuleInputSchema }), object({ rule: NcRuleSchema }), {
|
|
9884
10400
|
kind: "mutation",
|
|
9885
10401
|
auth: "admin",
|
|
@@ -9909,7 +10425,13 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
9909
10425
|
}), method(object({}), object({
|
|
9910
10426
|
catalog: array(NcConditionDescriptorSchema),
|
|
9911
10427
|
taxonomy: NcTaxonomySchema.optional()
|
|
9912
|
-
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" })
|
|
10428
|
+
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
10429
|
+
kind: "mutation",
|
|
10430
|
+
caller: "required"
|
|
10431
|
+
}), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
|
|
10432
|
+
kind: "mutation",
|
|
10433
|
+
caller: "required"
|
|
10434
|
+
});
|
|
9913
10435
|
/**
|
|
9914
10436
|
* TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
|
|
9915
10437
|
*
|
|
@@ -10657,7 +11179,15 @@ method(object({
|
|
|
10657
11179
|
format: _enum(["gif", "mp4"]).default("gif"),
|
|
10658
11180
|
maxWidth: number().int().min(120).max(1920).default(480),
|
|
10659
11181
|
/** GIF only — MP4 keeps the source cadence. */
|
|
10660
|
-
fps: number().int().min(1).max(15).default(5)
|
|
11182
|
+
fps: number().int().min(1).max(15).default(5),
|
|
11183
|
+
/**
|
|
11184
|
+
* Playback rate. A notification clip is GLANCED at on a lock screen,
|
|
11185
|
+
* not watched, so 2x is the default: the recipient sees the whole
|
|
11186
|
+
* passage in half the time and the GIF is half the bytes. `1` is real
|
|
11187
|
+
* time. Applies to MP4 as well — the operator set a speed, not a GIF
|
|
11188
|
+
* speed.
|
|
11189
|
+
*/
|
|
11190
|
+
speed: number().min(1).max(8).default(2)
|
|
10661
11191
|
}), object({
|
|
10662
11192
|
base64: string(),
|
|
10663
11193
|
mime: string(),
|
|
@@ -16325,7 +16855,20 @@ method(object({
|
|
|
16325
16855
|
}), _void(), {
|
|
16326
16856
|
kind: "mutation",
|
|
16327
16857
|
auth: "admin"
|
|
16328
|
-
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
|
|
16858
|
+
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
|
|
16859
|
+
addonId: string().optional(),
|
|
16860
|
+
/**
|
|
16861
|
+
* `slim` omits `config` (returned `{}`), `metadata` (null) and the
|
|
16862
|
+
* `sourceInfo` derived from config — and skips the per-device settings
|
|
16863
|
+
* read that produces them. Everything identifying a device (id, name,
|
|
16864
|
+
* type, online, features, isCamera, parent/link ids) is unchanged.
|
|
16865
|
+
* Do not use it for dispatch routing, which needs `sourceInfo`.
|
|
16866
|
+
*/
|
|
16867
|
+
projection: _enum(["full", "slim"]).optional(),
|
|
16868
|
+
/** Return only camera devices. Filtering server-side instead of
|
|
16869
|
+
* shipping 293 rows to find 12. */
|
|
16870
|
+
isCamera: boolean().optional()
|
|
16871
|
+
}), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
|
|
16329
16872
|
mode: DeviceLinkModeSchema,
|
|
16330
16873
|
devices: array(LinkedDeviceSchema)
|
|
16331
16874
|
})), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
|
|
@@ -16764,14 +17307,14 @@ var LlmProfileSchema = object({
|
|
|
16764
17307
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
16765
17308
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
16766
17309
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
16767
|
-
var ConfigSchemaPassthrough
|
|
17310
|
+
var ConfigSchemaPassthrough = unknown();
|
|
16768
17311
|
var LlmProfileKindDescriptorSchema = object({
|
|
16769
17312
|
kind: LlmProfileKindSchema,
|
|
16770
17313
|
label: string(),
|
|
16771
17314
|
icon: string(),
|
|
16772
17315
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
16773
17316
|
addonId: string(),
|
|
16774
|
-
configSchema: ConfigSchemaPassthrough
|
|
17317
|
+
configSchema: ConfigSchemaPassthrough
|
|
16775
17318
|
});
|
|
16776
17319
|
var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
|
|
16777
17320
|
var LlmDefaultSchema = object({
|
|
@@ -17261,282 +17804,143 @@ var StartEmbeddedInputSchema = object({
|
|
|
17261
17804
|
});
|
|
17262
17805
|
var StartEmbeddedResultSchema = object({
|
|
17263
17806
|
id: string(),
|
|
17264
|
-
url: string()
|
|
17265
|
-
});
|
|
17266
|
-
var StatusSchema = object({
|
|
17267
|
-
brokerCount: number(),
|
|
17268
|
-
embeddedRunning: boolean()
|
|
17269
|
-
});
|
|
17270
|
-
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
17271
|
-
var NetworkEndpointSchema = object({
|
|
17272
|
-
url: string(),
|
|
17273
|
-
hostname: string(),
|
|
17274
|
-
port: number(),
|
|
17275
|
-
protocol: _enum(["http", "https"])
|
|
17276
|
-
});
|
|
17277
|
-
var NetworkAccessStatusSchema = object({
|
|
17278
|
-
connected: boolean(),
|
|
17279
|
-
endpoint: NetworkEndpointSchema.nullable(),
|
|
17280
|
-
error: string().optional()
|
|
17281
|
-
});
|
|
17282
|
-
/**
|
|
17283
|
-
* Optional, richer endpoint shape returned by providers that expose
|
|
17284
|
-
* MORE than one ingress concurrently (Tailscale Ingress with mixed
|
|
17285
|
-
* serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
|
|
17286
|
-
* the originating provider config (mode + sourcePort) so the
|
|
17287
|
-
* orchestrator UI can label rows distinctly. Providers that expose only
|
|
17288
|
-
* one endpoint just omit `listEndpoints` from their provider impl.
|
|
17289
|
-
*/
|
|
17290
|
-
var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
17291
|
-
/**
|
|
17292
|
-
* Stable id within the provider — typically `<mode>-<sourcePort>` so
|
|
17293
|
-
* the orchestrator can dedupe across `listEndpoints` polls.
|
|
17294
|
-
*/
|
|
17295
|
-
id: string(),
|
|
17296
|
-
/** Operator-facing label (mirrors `MeshEndpoint.label`). */
|
|
17297
|
-
label: string(),
|
|
17298
|
-
/** Optional provider-specific mode tag, used for icon/colour in admin UI. */
|
|
17299
|
-
mode: string().optional(),
|
|
17300
|
-
/** Originating local port the ingress fronts (informational). */
|
|
17301
|
-
sourcePort: number().optional()
|
|
17302
|
-
});
|
|
17303
|
-
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
17304
|
-
/**
|
|
17305
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
17306
|
-
*
|
|
17307
|
-
* Apprise-derived model (see
|
|
17308
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
17309
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
17310
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
17311
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
17312
|
-
* message to what the kind supports — callers never special-case a service.
|
|
17313
|
-
*
|
|
17314
|
-
* DESIGN DECISIONS (locked):
|
|
17315
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
17316
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
17317
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
17318
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
17319
|
-
* alternative would fork the UI per addon and cannot host the
|
|
17320
|
-
* discovery→adopt flow.
|
|
17321
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
17322
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
17323
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
17324
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
17325
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
17326
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
17327
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
17328
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
17329
|
-
* base64 fallback needed.
|
|
17330
|
-
*
|
|
17331
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
17332
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
17333
|
-
* admin "Integrations" page.
|
|
17334
|
-
*/
|
|
17335
|
-
/**
|
|
17336
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
17337
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
17338
|
-
*/
|
|
17339
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
17340
|
-
"image",
|
|
17341
|
-
"video",
|
|
17342
|
-
"gif",
|
|
17343
|
-
"audio",
|
|
17344
|
-
"icon"
|
|
17345
|
-
]);
|
|
17346
|
-
/**
|
|
17347
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
17348
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
17349
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
17350
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
17351
|
-
*/
|
|
17352
|
-
var AttachmentSchema = object({
|
|
17353
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
17354
|
-
url: string().optional(),
|
|
17355
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
17356
|
-
mime: string().optional(),
|
|
17357
|
-
name: string().optional()
|
|
17358
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
17359
|
-
var NotificationFormatSchema = _enum([
|
|
17360
|
-
"text",
|
|
17361
|
-
"markdown",
|
|
17362
|
-
"html"
|
|
17363
|
-
]);
|
|
17364
|
-
/** A single tap-through action button. */
|
|
17365
|
-
var NotificationActionSchema = object({
|
|
17366
|
-
id: string(),
|
|
17367
|
-
label: string(),
|
|
17368
|
-
url: string().optional()
|
|
17369
|
-
});
|
|
17370
|
-
/**
|
|
17371
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
17372
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
17373
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
17374
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
17375
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
17376
|
-
* `priority` for that one target.
|
|
17377
|
-
*/
|
|
17378
|
-
var NotificationSchema = object({
|
|
17379
|
-
body: string(),
|
|
17380
|
-
title: string().optional(),
|
|
17381
|
-
format: NotificationFormatSchema.default("text"),
|
|
17382
|
-
priority: number().int().min(1).max(5).default(3),
|
|
17383
|
-
level: string().optional(),
|
|
17384
|
-
attachments: array(AttachmentSchema).optional(),
|
|
17385
|
-
clickUrl: string().optional(),
|
|
17386
|
-
actions: array(NotificationActionSchema).optional(),
|
|
17387
|
-
sound: string().optional(),
|
|
17388
|
-
ttl: number().optional(),
|
|
17389
|
-
tag: string().optional(),
|
|
17390
|
-
deviceId: number().optional(),
|
|
17391
|
-
eventId: string().optional(),
|
|
17392
|
-
metadata: record(string(), unknown()).optional()
|
|
17393
|
-
});
|
|
17394
|
-
/** One declared native severity/priority level for a kind. */
|
|
17395
|
-
var TargetKindLevelSchema = object({
|
|
17396
|
-
id: string(),
|
|
17397
|
-
label: string(),
|
|
17398
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
17399
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
17400
|
-
flags: object({
|
|
17401
|
-
critical: boolean().optional(),
|
|
17402
|
-
silent: boolean().optional(),
|
|
17403
|
-
noPush: boolean().optional()
|
|
17404
|
-
}).optional(),
|
|
17405
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
17406
|
-
requires: array(string()).optional(),
|
|
17407
|
-
description: string().optional()
|
|
17807
|
+
url: string()
|
|
17408
17808
|
});
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
format: array(NotificationFormatSchema),
|
|
17425
|
-
clickUrl: boolean(),
|
|
17426
|
-
sound: boolean(),
|
|
17427
|
-
ttl: boolean(),
|
|
17428
|
-
bodyMaxLen: number().int().positive()
|
|
17809
|
+
var StatusSchema = object({
|
|
17810
|
+
brokerCount: number(),
|
|
17811
|
+
embeddedRunning: boolean()
|
|
17812
|
+
});
|
|
17813
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
17814
|
+
var NetworkEndpointSchema = object({
|
|
17815
|
+
url: string(),
|
|
17816
|
+
hostname: string(),
|
|
17817
|
+
port: number(),
|
|
17818
|
+
protocol: _enum(["http", "https"])
|
|
17819
|
+
});
|
|
17820
|
+
var NetworkAccessStatusSchema = object({
|
|
17821
|
+
connected: boolean(),
|
|
17822
|
+
endpoint: NetworkEndpointSchema.nullable(),
|
|
17823
|
+
error: string().optional()
|
|
17429
17824
|
});
|
|
17430
17825
|
/**
|
|
17431
|
-
*
|
|
17432
|
-
*
|
|
17433
|
-
*
|
|
17434
|
-
* the
|
|
17435
|
-
*
|
|
17826
|
+
* Optional, richer endpoint shape returned by providers that expose
|
|
17827
|
+
* MORE than one ingress concurrently (Tailscale Ingress with mixed
|
|
17828
|
+
* serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
|
|
17829
|
+
* the originating provider config (mode + sourcePort) so the
|
|
17830
|
+
* orchestrator UI can label rows distinctly. Providers that expose only
|
|
17831
|
+
* one endpoint just omit `listEndpoints` from their provider impl.
|
|
17436
17832
|
*/
|
|
17437
|
-
var
|
|
17438
|
-
var TargetKindSchema = object({
|
|
17439
|
-
kind: string(),
|
|
17440
|
-
label: string(),
|
|
17441
|
-
icon: string(),
|
|
17442
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
17443
|
-
addonId: string(),
|
|
17444
|
-
/**
|
|
17445
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
17446
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
17447
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
17448
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
17449
|
-
*
|
|
17450
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
17451
|
-
* client, and a native client joins it onto its own hub base.
|
|
17452
|
-
*
|
|
17453
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
17454
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
17455
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
17456
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
17457
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
17458
|
-
*/
|
|
17459
|
-
iconUrl: string().optional(),
|
|
17833
|
+
var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
17460
17834
|
/**
|
|
17461
|
-
*
|
|
17462
|
-
*
|
|
17463
|
-
* The server knows this and therefore says it, because the client cannot
|
|
17464
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
17465
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
17466
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
17467
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
17468
|
-
*
|
|
17469
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
17470
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
17471
|
-
* its raster path, which is the safe default for an unknown image.
|
|
17835
|
+
* Stable id within the provider — typically `<mode>-<sourcePort>` so
|
|
17836
|
+
* the orchestrator can dedupe across `listEndpoints` polls.
|
|
17472
17837
|
*/
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17838
|
+
id: string(),
|
|
17839
|
+
/** Operator-facing label (mirrors `MeshEndpoint.label`). */
|
|
17840
|
+
label: string(),
|
|
17841
|
+
/** Optional provider-specific mode tag, used for icon/colour in admin UI. */
|
|
17842
|
+
mode: string().optional(),
|
|
17843
|
+
/** Originating local port the ingress fronts (informational). */
|
|
17844
|
+
sourcePort: number().optional()
|
|
17477
17845
|
});
|
|
17846
|
+
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
17478
17847
|
/**
|
|
17479
|
-
*
|
|
17480
|
-
*
|
|
17481
|
-
*
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
|
|
17486
|
-
|
|
17487
|
-
|
|
17848
|
+
* core-blocks — user-authored TypeScript, stored in the kernel and executed in
|
|
17849
|
+
* its own process.
|
|
17850
|
+
*
|
|
17851
|
+
* Spec: `docs/superpowers/specs/2026-08-04-core-blocks-and-synthetic-devices-design.md`.
|
|
17852
|
+
*
|
|
17853
|
+
* The first use is **owning devices without being a device provider**: a block
|
|
17854
|
+
* declares devices under a system or custom integration and drives their state,
|
|
17855
|
+
* with the same `ctx` an addon gets. Automations come later; nothing here
|
|
17856
|
+
* models a trigger.
|
|
17857
|
+
*
|
|
17858
|
+
* **Stated plainly, because it does not change by being true:** a block has an
|
|
17859
|
+
* addon's powers — devices, storage, the event bus, `ctx.api`. It is a plugin
|
|
17860
|
+
* with no review step. What makes that survivable is not a sandbox, it is
|
|
17861
|
+
* PROCESS ISOLATION: one process per block, supervised by `CrashSupervisor`,
|
|
17862
|
+
* so a block that throws or never returns is marked `failed` and visible
|
|
17863
|
+
* instead of taking the hub with it (D6). Every method here is admin-only, and
|
|
17864
|
+
* must stay so.
|
|
17865
|
+
*/
|
|
17866
|
+
/** Where a block runs. The operator chooses — a block driving a device on an
|
|
17867
|
+
* agent is the reason placement is not fixed to the hub. */
|
|
17868
|
+
var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
|
|
17869
|
+
/** What a block's process is doing. Mirrors the addon runner's own lifecycle so
|
|
17870
|
+
* a failing block reads the same way a failing addon does. */
|
|
17871
|
+
var CoreBlockStatusSchema = _enum([
|
|
17872
|
+
"stopped",
|
|
17873
|
+
"starting",
|
|
17874
|
+
"running",
|
|
17875
|
+
"failed"
|
|
17876
|
+
]);
|
|
17877
|
+
/** Client-authored fields. */
|
|
17878
|
+
var CoreBlockInputSchema = object({
|
|
17879
|
+
name: string().min(1).max(120),
|
|
17880
|
+
/** TypeScript source. Compiled server-side before it is ever stored — a
|
|
17881
|
+
* block that does not compile is a fork failure the operator would meet
|
|
17882
|
+
* minutes later, in a log, instead of in the editor. */
|
|
17883
|
+
code: string().max(2e5),
|
|
17488
17884
|
enabled: boolean(),
|
|
17489
|
-
|
|
17490
|
-
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
config: record(string(), unknown())
|
|
17885
|
+
placement: CoreBlockPlacementSchema,
|
|
17886
|
+
/**
|
|
17887
|
+
* Integration the block's devices hang from. Absent = the system integration
|
|
17888
|
+
* blocks share. A block may declare its own instead.
|
|
17889
|
+
*/
|
|
17890
|
+
integrationId: string().optional()
|
|
17496
17891
|
});
|
|
17497
|
-
/**
|
|
17498
|
-
var
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17892
|
+
/** A stored block. */
|
|
17893
|
+
var CoreBlockSchema = CoreBlockInputSchema.extend({
|
|
17894
|
+
id: string(),
|
|
17895
|
+
createdAt: number(),
|
|
17896
|
+
updatedAt: number(),
|
|
17897
|
+
/** Server-stamped author. */
|
|
17898
|
+
createdBy: string(),
|
|
17899
|
+
status: CoreBlockStatusSchema,
|
|
17900
|
+
/**
|
|
17901
|
+
* Why the block is not running, when it is not. The operator's ONLY window
|
|
17902
|
+
* into a block that failed at load — a block that is silently absent is the
|
|
17903
|
+
* failure mode this whole feature has to avoid.
|
|
17904
|
+
*/
|
|
17905
|
+
lastError: string().optional(),
|
|
17906
|
+
/** Ms epoch of the last state change. */
|
|
17907
|
+
lastChangedAt: number()
|
|
17505
17908
|
});
|
|
17506
|
-
|
|
17507
|
-
|
|
17909
|
+
/** What a compile attempt produced. */
|
|
17910
|
+
var CoreBlockCompileResultSchema = object({
|
|
17911
|
+
ok: boolean(),
|
|
17912
|
+
/** Present when `ok` is false — the first error, in the author's words. */
|
|
17508
17913
|
error: string().optional(),
|
|
17509
|
-
|
|
17914
|
+
line: number().optional(),
|
|
17915
|
+
column: number().optional()
|
|
17510
17916
|
});
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
|
|
17516
|
-
|
|
17517
|
-
|
|
17518
|
-
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
|
|
17529
|
-
|
|
17530
|
-
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
}
|
|
17539
|
-
};
|
|
17917
|
+
method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }), method(object({ blockId: string() }), object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }), method(object({ block: CoreBlockInputSchema }), object({ block: CoreBlockSchema }), {
|
|
17918
|
+
kind: "mutation",
|
|
17919
|
+
auth: "admin",
|
|
17920
|
+
caller: "required"
|
|
17921
|
+
}), method(object({
|
|
17922
|
+
blockId: string(),
|
|
17923
|
+
block: CoreBlockInputSchema.partial()
|
|
17924
|
+
}), object({ block: CoreBlockSchema }), {
|
|
17925
|
+
kind: "mutation",
|
|
17926
|
+
auth: "admin",
|
|
17927
|
+
caller: "required"
|
|
17928
|
+
}), method(object({ blockId: string() }), object({ success: literal(true) }), {
|
|
17929
|
+
kind: "mutation",
|
|
17930
|
+
auth: "admin"
|
|
17931
|
+
}), method(object({
|
|
17932
|
+
blockId: string(),
|
|
17933
|
+
enabled: boolean()
|
|
17934
|
+
}), object({ block: CoreBlockSchema }), {
|
|
17935
|
+
kind: "mutation",
|
|
17936
|
+
auth: "admin"
|
|
17937
|
+
}), method(object({ code: string() }), CoreBlockCompileResultSchema, {
|
|
17938
|
+
kind: "mutation",
|
|
17939
|
+
auth: "admin"
|
|
17940
|
+
}), method(object({}), object({ libs: array(object({
|
|
17941
|
+
filePath: string(),
|
|
17942
|
+
content: string()
|
|
17943
|
+
})) }), { auth: "admin" });
|
|
17540
17944
|
/**
|
|
17541
17945
|
* Zod schemas for persisted record types.
|
|
17542
17946
|
*
|
|
@@ -17784,6 +18188,11 @@ var EventKindDescriptorSchema = object({
|
|
|
17784
18188
|
deviceId: number()
|
|
17785
18189
|
})
|
|
17786
18190
|
});
|
|
18191
|
+
/** One camera's event vocabulary, as returned by `listEventKindsBatch`. */
|
|
18192
|
+
var EventKindsForDeviceSchema = object({
|
|
18193
|
+
deviceId: number(),
|
|
18194
|
+
kinds: array(EventKindDescriptorSchema).readonly()
|
|
18195
|
+
});
|
|
17787
18196
|
var SensorEventSchema = object({
|
|
17788
18197
|
id: string(),
|
|
17789
18198
|
/** The CAMERA the event is attributed to (a sensor linked to N cameras
|
|
@@ -18040,6 +18449,19 @@ var MediaFileSchema = object({
|
|
|
18040
18449
|
sizeBytes: number(),
|
|
18041
18450
|
timestamp: number()
|
|
18042
18451
|
});
|
|
18452
|
+
/**
|
|
18453
|
+
* One media row WITHOUT its bytes.
|
|
18454
|
+
*
|
|
18455
|
+
* A track's media is 5-8 MB of base64 (measured: 7.67 MB across 23 files for a
|
|
18456
|
+
* 140 s track), and a client that renders tiles from the media data plane needs
|
|
18457
|
+
* to know only WHAT EXISTS — the bytes then arrive per tile, lazily, over HTTP
|
|
18458
|
+
* with an immutable cache, instead of all at once inside a tRPC response that
|
|
18459
|
+
* blocks the whole view.
|
|
18460
|
+
*
|
|
18461
|
+
* `sizeBytes` is carried because it is what lets a client decide between the
|
|
18462
|
+
* stored blob and a `?variant=thumb` rendering without fetching either.
|
|
18463
|
+
*/
|
|
18464
|
+
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
18043
18465
|
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
18044
18466
|
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
18045
18467
|
var DeviceEventQueryInput = object({
|
|
@@ -18189,7 +18611,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
18189
18611
|
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
18190
18612
|
kind: "mutation",
|
|
18191
18613
|
auth: "admin"
|
|
18192
|
-
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
|
|
18614
|
+
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
|
|
18193
18615
|
deviceId: number(),
|
|
18194
18616
|
since: number().optional(),
|
|
18195
18617
|
until: number().optional(),
|
|
@@ -18263,7 +18685,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
18263
18685
|
}), array(MediaFileSchema).readonly()), method(object({
|
|
18264
18686
|
trackId: string(),
|
|
18265
18687
|
kinds: array(MediaFileKindEnum).optional()
|
|
18266
|
-
}), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
|
|
18688
|
+
}), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
|
|
18267
18689
|
deviceId: number(),
|
|
18268
18690
|
timestamp: number(),
|
|
18269
18691
|
frameWidth: number(),
|
|
@@ -22853,6 +23275,54 @@ Object.freeze({
|
|
|
22853
23275
|
addonId: null,
|
|
22854
23276
|
access: "create"
|
|
22855
23277
|
},
|
|
23278
|
+
"coreBlocks.compile": {
|
|
23279
|
+
capName: "core-blocks",
|
|
23280
|
+
capScope: "system",
|
|
23281
|
+
addonId: null,
|
|
23282
|
+
access: "create"
|
|
23283
|
+
},
|
|
23284
|
+
"coreBlocks.create": {
|
|
23285
|
+
capName: "core-blocks",
|
|
23286
|
+
capScope: "system",
|
|
23287
|
+
addonId: null,
|
|
23288
|
+
access: "create"
|
|
23289
|
+
},
|
|
23290
|
+
"coreBlocks.delete": {
|
|
23291
|
+
capName: "core-blocks",
|
|
23292
|
+
capScope: "system",
|
|
23293
|
+
addonId: null,
|
|
23294
|
+
access: "delete"
|
|
23295
|
+
},
|
|
23296
|
+
"coreBlocks.get": {
|
|
23297
|
+
capName: "core-blocks",
|
|
23298
|
+
capScope: "system",
|
|
23299
|
+
addonId: null,
|
|
23300
|
+
access: "view"
|
|
23301
|
+
},
|
|
23302
|
+
"coreBlocks.getTypeDefs": {
|
|
23303
|
+
capName: "core-blocks",
|
|
23304
|
+
capScope: "system",
|
|
23305
|
+
addonId: null,
|
|
23306
|
+
access: "view"
|
|
23307
|
+
},
|
|
23308
|
+
"coreBlocks.list": {
|
|
23309
|
+
capName: "core-blocks",
|
|
23310
|
+
capScope: "system",
|
|
23311
|
+
addonId: null,
|
|
23312
|
+
access: "view"
|
|
23313
|
+
},
|
|
23314
|
+
"coreBlocks.setEnabled": {
|
|
23315
|
+
capName: "core-blocks",
|
|
23316
|
+
capScope: "system",
|
|
23317
|
+
addonId: null,
|
|
23318
|
+
access: "create"
|
|
23319
|
+
},
|
|
23320
|
+
"coreBlocks.update": {
|
|
23321
|
+
capName: "core-blocks",
|
|
23322
|
+
capScope: "system",
|
|
23323
|
+
addonId: null,
|
|
23324
|
+
access: "create"
|
|
23325
|
+
},
|
|
22856
23326
|
"cover.close": {
|
|
22857
23327
|
capName: "cover",
|
|
22858
23328
|
capScope: "device",
|
|
@@ -24761,12 +25231,24 @@ Object.freeze({
|
|
|
24761
25231
|
addonId: null,
|
|
24762
25232
|
access: "create"
|
|
24763
25233
|
},
|
|
25234
|
+
"notificationRules.cancelSnooze": {
|
|
25235
|
+
capName: "notification-rules",
|
|
25236
|
+
capScope: "system",
|
|
25237
|
+
addonId: null,
|
|
25238
|
+
access: "create"
|
|
25239
|
+
},
|
|
24764
25240
|
"notificationRules.createRule": {
|
|
24765
25241
|
capName: "notification-rules",
|
|
24766
25242
|
capScope: "system",
|
|
24767
25243
|
addonId: null,
|
|
24768
25244
|
access: "create"
|
|
24769
25245
|
},
|
|
25246
|
+
"notificationRules.createSnooze": {
|
|
25247
|
+
capName: "notification-rules",
|
|
25248
|
+
capScope: "system",
|
|
25249
|
+
addonId: null,
|
|
25250
|
+
access: "create"
|
|
25251
|
+
},
|
|
24770
25252
|
"notificationRules.deleteRule": {
|
|
24771
25253
|
capName: "notification-rules",
|
|
24772
25254
|
capScope: "system",
|
|
@@ -24797,6 +25279,12 @@ Object.freeze({
|
|
|
24797
25279
|
addonId: null,
|
|
24798
25280
|
access: "view"
|
|
24799
25281
|
},
|
|
25282
|
+
"notificationRules.listSnoozes": {
|
|
25283
|
+
capName: "notification-rules",
|
|
25284
|
+
capScope: "system",
|
|
25285
|
+
addonId: null,
|
|
25286
|
+
access: "view"
|
|
25287
|
+
},
|
|
24800
25288
|
"notificationRules.setRuleEnabled": {
|
|
24801
25289
|
capName: "notification-rules",
|
|
24802
25290
|
capScope: "system",
|
|
@@ -25001,6 +25489,12 @@ Object.freeze({
|
|
|
25001
25489
|
addonId: null,
|
|
25002
25490
|
access: "view"
|
|
25003
25491
|
},
|
|
25492
|
+
"pipelineAnalytics.listEventKindsBatch": {
|
|
25493
|
+
capName: "pipeline-analytics",
|
|
25494
|
+
capScope: "device",
|
|
25495
|
+
addonId: null,
|
|
25496
|
+
access: "view"
|
|
25497
|
+
},
|
|
25004
25498
|
"pipelineAnalytics.listOpsLog": {
|
|
25005
25499
|
capName: "pipeline-analytics",
|
|
25006
25500
|
capScope: "device",
|
|
@@ -25013,6 +25507,12 @@ Object.freeze({
|
|
|
25013
25507
|
addonId: null,
|
|
25014
25508
|
access: "view"
|
|
25015
25509
|
},
|
|
25510
|
+
"pipelineAnalytics.listTrackMedia": {
|
|
25511
|
+
capName: "pipeline-analytics",
|
|
25512
|
+
capScope: "device",
|
|
25513
|
+
addonId: null,
|
|
25514
|
+
access: "view"
|
|
25515
|
+
},
|
|
25016
25516
|
"pipelineAnalytics.listTracks": {
|
|
25017
25517
|
capName: "pipeline-analytics",
|
|
25018
25518
|
capScope: "device",
|
|
@@ -28825,6 +29325,7 @@ var WEBHOOK_CAPS = {
|
|
|
28825
29325
|
max: 99
|
|
28826
29326
|
},
|
|
28827
29327
|
actions: 99,
|
|
29328
|
+
actionIcons: true,
|
|
28828
29329
|
levels: [
|
|
28829
29330
|
{
|
|
28830
29331
|
id: "p1",
|