@camstack/addon-cloudflare 1.2.5 → 1.2.6

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.
@@ -7501,16 +7501,23 @@ var StorageLocationDeclarationSchema = object({
7501
7501
  * Which node root the seeded `<id>:default` instance is placed under on a
7502
7502
  * FRESH install:
7503
7503
  * - `'data'` (default) — the node's data dir (`CAMSTACK_DATA` / boot dir),
7504
- * the appData volume. Right for small/durable data (backups, logs, models).
7504
+ * the appData volume. Right for small/durable data (logs, models).
7505
7505
  * - `'media'` — the dedicated media volume (`CAMSTACK_MEDIA_ROOT`) when that
7506
7506
  * env is set, else falls back to the data root. Right for bulky, hot media
7507
7507
  * (recordings, event media) that should stay off the appData disk.
7508
+ * - `'backup'` — the dedicated backup volume (`CAMSTACK_BACKUP_ROOT`, default
7509
+ * `/backups` in the image) so archives live on their own mount rather than
7510
+ * filling the appData disk. Falls back to the data root when unset.
7508
7511
  *
7509
7512
  * Only affects the seeded default's `basePath`; operators can repoint any
7510
7513
  * location afterwards, and a `defaultsTo` slot inherits its parent's root
7511
7514
  * regardless of this field. Absent (the common case) is treated as `'data'`.
7512
7515
  */
7513
- defaultRoot: _enum(["data", "media"]).optional()
7516
+ defaultRoot: _enum([
7517
+ "data",
7518
+ "media",
7519
+ "backup"
7520
+ ]).optional()
7514
7521
  });
7515
7522
  var DecoderStatsSchema = object({
7516
7523
  inputFps: number(),
@@ -8861,92 +8868,730 @@ var AccessoryKind = {
8861
8868
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
8862
8869
  DeviceFeature.BatteryOperated;
8863
8870
  /**
8864
- * Generic device-level status snapshot. Auto-registered by `BaseDevice`
8865
- * for every device, regardless of provider the kernel needs a uniform
8866
- * cap-keyed slice for the basic device flags every consumer expects to
8867
- * read across processes (the `online` flag in particular). Driver-specific
8868
- * caps (`battery`, `doorbell`, …) carry their domain-specific state on
8869
- * their own slices.
8871
+ * Shared geometry vocabulary for on-frame shape caps — privacy-mask,
8872
+ * motion-zones, and the detection zones/lines editor all speak this one
8873
+ * language so a single drawing-plane editor and the providers stay
8874
+ * decoupled from each cap's storage.
8870
8875
  *
8871
- * Pattern is identical to `battery`: schema-bearing `runtimeState`,
8872
- * empty `methods`, single change event. Reads land at
8873
- * `runtimeState.getCapState('device-status')`; writes at
8874
- * `runtimeState.setCapState('device-status', …)`. Cross-process
8875
- * consumers reach the same data via the `device-state` cap router
8876
- * (`getCapSlice({deviceId, capName: 'device-status'})`).
8876
+ * All coordinates are normalized 0..1 of the camera frame (top-left
8877
+ * origin). Each cap composes the SUBSET of shape kinds it supports and
8878
+ * advertises it via `supportedShapes` in its `getOptions`.
8877
8879
  */
8878
- var DeviceStatusSchema = object({
8879
- /**
8880
- * Device-level liveness. Drivers flip via `markOnline(boolean)` on
8881
- * `BaseDevice`. Provider semantics vary — RTSP aggregates broker
8882
- * stream-health, Reolink reads firmware push events, ONVIF tracks
8883
- * ping responses. This cap intentionally does NOT prescribe which
8884
- * signal drives the flag.
8885
- */
8886
- online: boolean(),
8887
- /** Ms epoch of the last `online` transition. Lets consumers tell
8888
- * apart "just came online" from "still online". */
8889
- lastChangedAt: number()
8880
+ /** A normalized 0..1 point (top-left origin). */
8881
+ var MaskPointSchema = object({
8882
+ x: number(),
8883
+ y: number()
8890
8884
  });
8891
- object({
8892
- deviceId: number(),
8893
- status: DeviceStatusSchema
8885
+ /** Axis-aligned rectangle (normalized 0..1). */
8886
+ var MaskRectShapeSchema = object({
8887
+ kind: literal("rect"),
8888
+ x: number(),
8889
+ y: number(),
8890
+ width: number(),
8891
+ height: number()
8892
+ });
8893
+ /** Free polygon — an ordered list of normalized vertices (≥3). */
8894
+ var MaskPolygonShapeSchema = object({
8895
+ kind: literal("polygon"),
8896
+ points: array(MaskPointSchema)
8897
+ });
8898
+ /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
8899
+ var MaskGridShapeSchema = object({
8900
+ kind: literal("grid"),
8901
+ gridWidth: number(),
8902
+ gridHeight: number(),
8903
+ cells: array(boolean())
8904
+ });
8905
+ discriminatedUnion("kind", [
8906
+ MaskRectShapeSchema,
8907
+ MaskPolygonShapeSchema,
8908
+ MaskGridShapeSchema,
8909
+ object({
8910
+ kind: literal("line"),
8911
+ points: array(MaskPointSchema)
8912
+ })
8913
+ ]);
8914
+ /** Every shape-kind discriminant, for `supportedShapes` advertisement. */
8915
+ var MaskShapeKindSchema = _enum([
8916
+ "rect",
8917
+ "polygon",
8918
+ "grid",
8919
+ "line"
8920
+ ]);
8921
+ /** Polygon vertex bounds when a cap supports 'polygon' (e.g. Hikvision {min:4,max:4}). */
8922
+ var MaskPolygonVerticesSchema = object({
8923
+ min: number(),
8924
+ max: number()
8925
+ });
8926
+ /** Grid dimensions when a cap supports 'grid'. */
8927
+ var MaskGridDimsSchema = object({
8928
+ width: number(),
8929
+ height: number()
8894
8930
  });
8895
8931
  /**
8896
- * Per-device feature/identity probe slice. Holds the runtime-resolved
8897
- * truth about what a device CAN do — which the kernel uses to:
8898
- * 1. Reconcile accessory children (hub-children spawn siren/floodlight/PIR
8899
- * based on what the firmware actually advertises).
8900
- * 2. Compute the public `features: DeviceFeature[]` array surfaced via
8901
- * `device-manager.listAll`.
8902
- * 3. Decide which optional caps (PTZ, intercom, doorbell, battery, …)
8903
- * to register on the device's capability surface.
8932
+ * notification-rules the Notification Center rule surface (P1 core).
8904
8933
  *
8905
- * Auto-registered by `BaseDevice` for every device. Drivers populate the
8906
- * slice from `onProbe()` (kernel calls it once after register, before
8907
- * accessory reconciliation). Consumers read via:
8908
- * `runtimeState.getCapState<FeatureProbeStatus>('feature-probe')`
8934
+ * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
8935
+ * (operator decisions D-1/D-2/D-3 are binding):
8909
8936
  *
8910
- * `flags` is an open record so each driver carries its own keys without
8911
- * a centralized schema bottleneck Reolink writes `hasPtz/hasIntercom`,
8912
- * Hikvision writes `hasSupplementalLight/hasAlarmIo`, etc.
8937
+ * - D-2: rule EVALUATION lives in `addon-post-analysis` (the
8938
+ * `notification-center` module), hooked on the durable persistence
8939
+ * moments (object-event insert, TrackCloser.closeExpired) with a
8940
+ * persisted outbox + retry — never the lossy telemetry bus (D8).
8941
+ * - D-3: urgency belongs to the RULE. `delivery: 'immediate'` fires on the
8942
+ * FIRST persisted detection matching the conditions (per-track dedup,
8943
+ * `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
8944
+ * `delivery: 'track-end'` evaluates the finalized track record at close.
8945
+ * - DISPATCH stays behind `notification-output` (rules reference targets
8946
+ * by id; per-backend params are a passthrough blob capped by the
8947
+ * target kind's own caps/degrade engine).
8913
8948
  *
8914
- * Replaces the older driver-local `deviceCache.has*` blob: the per-device
8915
- * config is for operator-edited overrides + UI snapshots; runtime probe
8916
- * results belong in runtime-state where the kernel handles persistence,
8917
- * cross-process mirroring, and reactive updates.
8949
+ * P1 scope: admin-authored rules only (`createdBy` stamped from the
8950
+ * server-injected caller identity the first `caller: 'required'`
8951
+ * adopter). The P1 condition subset is: devices, classes(+exclude),
8952
+ * minConfidence, admin zones (any/all + exclude), weekly schedule
8953
+ * windows, and the optional label/identity/plate matchers. User rules,
8954
+ * private zones, per-recipient fan-out and the wider condition table are
8955
+ * P2+ (see spec §7).
8956
+ *
8957
+ * All schemas here are the single source of truth — `NcRule` etc. are
8958
+ * `z.infer` exports; no duplicate interfaces (the advanced-notifier
8959
+ * schema/interface drift is explicitly not repeated).
8918
8960
  */
8919
- var FeatureProbeStatusSchema = object({
8961
+ /**
8962
+ * D-3: the trigger/urgency of a rule — which persistence moment evaluates it.
8963
+ * The value maps 1:1 onto the evaluated record kind:
8964
+ * - `immediate` ↔ object-event persist (lowest-latency detection burst)
8965
+ * - `track-end` ↔ TrackCloser.closeExpired (finalized track record)
8966
+ * - `device-event` ↔ SensorEventStore insert (doorbell press / sensor state
8967
+ * change of a LINKED device, one row per linked camera)
8968
+ * - `package-event` ↔ PackageDropDetector object-event insert (a `package`
8969
+ * delivery / pick-up)
8970
+ *
8971
+ * `immediate`/`track-end` carry the D-3 urgency semantics; `device-event`/
8972
+ * `package-event` are pure trigger kinds (no urgency dimension). Extending
8973
+ * this one field keeps the schema additive — a rule still declares exactly
8974
+ * one trigger.
8975
+ */
8976
+ var NcDeliverySchema = _enum([
8977
+ "immediate",
8978
+ "track-end",
8979
+ "device-event",
8980
+ "package-event"
8981
+ ]);
8982
+ /** Weekly schedule — OR of windows; absence on the rule = always active. */
8983
+ var NcScheduleSchema = object({
8984
+ windows: array(object({
8985
+ /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
8986
+ days: array(number().int().min(0).max(6)).min(1),
8987
+ startMinute: number().int().min(0).max(1439),
8988
+ endMinute: number().int().min(0).max(1439)
8989
+ })).min(1),
8990
+ /** IANA timezone; default = hub host timezone. */
8991
+ timezone: string().optional(),
8992
+ /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
8993
+ invert: boolean().optional()
8994
+ });
8995
+ /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
8996
+ var NcPlateMatcherSchema = object({
8997
+ values: array(string().min(1)).min(1),
8998
+ /** Max Levenshtein distance after normalization (uppercase alphanumeric). */
8999
+ maxDistance: number().int().min(0).max(3).default(1)
9000
+ });
9001
+ /**
9002
+ * Occupancy condition (DEVICE-EVENT trigger). Fires on a ZoneAnalytics
9003
+ * occupancy edge for a device — optionally narrowed to a single admin
9004
+ * `zoneId` and/or object `className`. `op` selects the edge/threshold:
9005
+ * - `became-occupied` (default) — count crossed 0 → ≥ `count`
9006
+ * - `became-free` — count crossed ≥ `count` → below it
9007
+ * - `>=` / `<=` — count is at/over or at/under `count`
9008
+ * `sustainSeconds` requires the condition hold continuously that long
9009
+ * before firing (debounces flicker; 0 = fire on the first matching edge).
9010
+ * Fail-closed: no ZoneAnalytics snapshot / missing zone / null snapshot ⇒
9011
+ * the condition never matches. Confirmed edge-state survives addon restarts
9012
+ * (declared SQLite collection, reseeded on boot).
9013
+ */
9014
+ var NcOccupancyConditionSchema = object({
9015
+ /** Admin zone id to scope the count to; absent = whole-frame occupancy. */
9016
+ zoneId: string().optional(),
9017
+ /** Object class to count; absent = any class. */
9018
+ className: string().optional(),
9019
+ op: _enum([
9020
+ "became-occupied",
9021
+ "became-free",
9022
+ ">=",
9023
+ "<="
9024
+ ]).default("became-occupied"),
9025
+ count: number().int().min(0).default(1),
9026
+ sustainSeconds: number().int().min(0).max(3600).default(15)
9027
+ });
9028
+ /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9029
+ var NcZoneConditionSchema = object({
9030
+ ids: array(string().min(1)).min(1),
9031
+ /** Quantifier over `ids` — at least one / every one visited. */
9032
+ match: _enum(["any", "all"]).default("any")
9033
+ });
9034
+ /**
9035
+ * The P1 condition set — a flat AND of groups; absent group = pass;
9036
+ * membership lists are OR within the list (spec §2.3).
9037
+ */
9038
+ var NcConditionsSchema = object({
9039
+ /** Device scope — absent = all devices. */
9040
+ devices: array(number()).optional(),
9041
+ /** Detector class names (any overlap with the record's class set). */
9042
+ classes: array(string().min(1)).optional(),
9043
+ /** Veto classes — any overlap fails the rule. */
9044
+ classesExclude: array(string().min(1)).optional(),
9045
+ /** Minimum detection confidence 0–1 (fails when the record has none). */
9046
+ minConfidence: number().min(0).max(1).optional(),
9047
+ /** Admin zone membership over event `zones` / track `zonesVisited`. */
9048
+ zones: NcZoneConditionSchema.optional(),
9049
+ /** Veto zones — any hit fails the rule. */
9050
+ zonesExclude: array(string().min(1)).optional(),
8920
9051
  /**
8921
- * Driver-specific flag bag. Each driver picks its own key names — the
8922
- * cap deliberately does NOT enforce a closed enum here. Reolink keys:
8923
- * `hasPtz`, `hasIntercom`, `hasDoorbell`, `hasFloodlight`, `hasSiren`,
8924
- * `hasPirSensor`, `hasAutotrack`, `hasBattery`. Hikvision keys:
8925
- * `hasSupplementalLight`, `lightHasWhiteLight`, `hasAlarmIo`, `hasPtz`.
9052
+ * Exact (case-insensitive) match on the record's collapsed `label`
9053
+ * (identity name / plate text / subclass).
8926
9054
  */
8927
- flags: record(string(), unknown()),
9055
+ labelEquals: array(string().min(1)).optional(),
8928
9056
  /**
8929
- * Coarse driver-classification lets cross-process consumers tell apart
8930
- * cameras / battery-cams / NVRs without re-running the probe. `null`
8931
- * before the first probe completes.
9057
+ * Identity matcher. P1 boundary: matched against the record's collapsed
9058
+ * `label` (the identity display name propagated by the face pipeline) —
9059
+ * identity-ID matching rides in P2 when identity ids reach the record.
8932
9060
  */
8933
- deviceType: string().nullable(),
8934
- /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
8935
- model: string().nullable(),
8936
- /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
8937
- channelCount: number().nullable(),
9061
+ identities: array(string().min(1)).optional(),
9062
+ /** Fuzzy plate matcher against the record's `label` (plate text). */
9063
+ plates: NcPlateMatcherSchema.optional(),
8938
9064
  /**
8939
- * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
8940
- * completes drivers' `getAccessoryChildren()` should treat zero as
8941
- * "probe not done yet, return empty" so accessories aren't spawned
8942
- * before the firmware is queried.
9065
+ * Identity EXCLUDE — mirror of {@link identities} with `notIn` semantics.
9066
+ * Same P1 boundary: matched against the record's collapsed `label` (the
9067
+ * identity display name). A record with NO label passes (nothing to
9068
+ * exclude), unlike the include variant which fails on an absent label.
8943
9069
  */
8944
- lastProbedAt: number(),
9070
+ identitiesExclude: array(string().min(1)).optional(),
8945
9071
  /**
8946
- * Framework convention: every runtime-state slice carries this for the
8947
- * createRuntimeStateBridge stale-check helper. We keep it in sync with
8948
- * `lastProbedAt` on every write.
8949
- */
9072
+ * Minimum server-computed key-event importance in [0,1] (`Track.importance`).
9073
+ * TRACK-END only: importance is scored at track close, so it does not exist
9074
+ * at immediate / object-event evaluation time (see catalog `appliesTo`). At
9075
+ * close the value is threaded via the close-time info (the `Track` clone is
9076
+ * captured before the DB row is updated, so it would otherwise read stale).
9077
+ * Fails when the record carries no importance (never guess quality — the
9078
+ * `minConfidence` precedent). MVP cut: a single scalar threshold.
9079
+ */
9080
+ minImportance: number().min(0).max(1).optional(),
9081
+ /**
9082
+ * Minimum track dwell in SECONDS — `(lastSeen − firstSeen) / 1000`.
9083
+ * TRACK-END only: an `immediate` / object-event subject has no closed
9084
+ * lifespan, so a dwell condition never matches immediate delivery
9085
+ * (documented choice — the object-event record carries no `firstSeen`,
9086
+ * so dwell cannot be computed from what the subject actually carries).
9087
+ */
9088
+ minDwellSeconds: number().min(0).optional(),
9089
+ /**
9090
+ * Detection provenance filter. `any` (default / absent) matches every
9091
+ * source; otherwise the subject's source must equal it. Legacy records
9092
+ * with no stamped source are treated as `pipeline`. The union spans both
9093
+ * record kinds — object events carry `pipeline` | `onboard`, synthetic
9094
+ * tracks carry `sensor`.
9095
+ */
9096
+ source: _enum([
9097
+ "pipeline",
9098
+ "onboard",
9099
+ "sensor",
9100
+ "any"
9101
+ ]).optional(),
9102
+ /**
9103
+ * Minimum identity / plate MATCH confidence in [0,1] — DISTINCT from the
9104
+ * detector `minConfidence` (that gates the object-detection score; this
9105
+ * gates the recognition/OCR match score). Fails when the subject carries
9106
+ * no label-match confidence (never guess). TRACK-END only: the confidence
9107
+ * lives on the recognition result and reaches the subject at track close.
9108
+ *
9109
+ * What it measures precisely (plumbed at track close — the closer threads
9110
+ * the value into `NcTrackClosedInfo.labelConfidence`, the same seam as
9111
+ * `importance`): the BEST recognition match confidence observed for the
9112
+ * label the track carries at close — for a face, the peak cosine similarity
9113
+ * of the ASSIGNED identity (`FaceMatch.score`, reset on an identity switch);
9114
+ * for a plate, the peak OCR read score of the best-held plate
9115
+ * (`plateText.confidence`). When BOTH a face and a plate were recognized on
9116
+ * one track the higher of the two is used. A track that ended with no
9117
+ * confident identity/plate match carries no value, so the condition fails
9118
+ * closed for it (an un-recognized subject).
9119
+ */
9120
+ minLabelConfidence: number().min(0).max(1).optional(),
9121
+ /**
9122
+ * DEVICE-EVENT only. Raw device event-type tokens (`EventFire.eventType`,
9123
+ * e.g. a doorbell `press` / `press_long`) — matched case-insensitively
9124
+ * against the token carried on the device-event subject (extracted from the
9125
+ * event-emitter runtime slice's `lastEvent.eventType`). Fails when the
9126
+ * subject carries no token. Doorbell-pulse / passive-sensor kinds emit no
9127
+ * eventType, so gate those with {@link sensorKinds} instead.
9128
+ */
9129
+ eventTypeTokens: array(string().min(1)).optional(),
9130
+ /**
9131
+ * DEVICE-EVENT only. Sensor/control taxonomy kinds (e.g. `doorbell`,
9132
+ * `contact`, `button`, `device-event`) — matched against the persisted
9133
+ * `SensorEvent.kind` (see `sensor-event-kinds.ts`). Membership is OR.
9134
+ */
9135
+ sensorKinds: array(string().min(1)).optional(),
9136
+ /**
9137
+ * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
9138
+ * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
9139
+ * when the subject's phase does not match (a subject always carries a phase
9140
+ * on the package-event trigger).
9141
+ */
9142
+ packagePhase: _enum([
9143
+ "delivered",
9144
+ "picked-up",
9145
+ "both"
9146
+ ]).optional(),
9147
+ /**
9148
+ * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
9149
+ * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
9150
+ * listed polygon (ZoneEngine membership semantics). Evaluated only when
9151
+ * the subject carries a bbox; absent bbox ⇒ the condition FAILS.
9152
+ */
9153
+ customZones: array(MaskPolygonShapeSchema).optional(),
9154
+ /**
9155
+ * DEVICE-EVENT only. ZoneAnalytics occupancy edge — fires when a device's
9156
+ * (optionally zone/class-scoped) occupancy count crosses the configured
9157
+ * threshold and holds for `sustainSeconds`. Fail-closed on missing
9158
+ * substrate (no snapshot / missing zone). See {@link NcOccupancyCondition}.
9159
+ */
9160
+ occupancy: NcOccupancyConditionSchema.optional()
9161
+ });
9162
+ /** One delivery target: a `notification-output` Target ref + passthrough params. */
9163
+ var NcRuleTargetSchema = object({
9164
+ /** `notification-output` Target id. */
9165
+ targetId: string().min(1),
9166
+ /**
9167
+ * Per-backend passthrough. Recognized keys are mapped onto the canonical
9168
+ * Notification (`priority`, `level`, `sound`, `clickUrl`, `ttl`); the
9169
+ * degrade engine drops what the backend can't render.
9170
+ */
9171
+ params: record(string(), unknown()).optional()
9172
+ });
9173
+ /**
9174
+ * Media attachment policy (P1 still-image subset).
9175
+ * - `best` — the best AVAILABLE subject image at dispatch time (D-3).
9176
+ * - `best-matching` — the media that explains WHY the rule fired: a rule
9177
+ * matched on identities attaches the subject's `faceCrop`, one matched on
9178
+ * plates attaches the `plateCrop`; a rule with no identity/plate condition
9179
+ * (or when the specific crop is missing) degrades to `best`, then
9180
+ * `keyFrame`, then no attachment — never delaying the send. The matched
9181
+ * condition summary is frozen on the outbox row at enqueue (like the rule
9182
+ * name), so the choice never drifts from the record that fired it.
9183
+ * - `keyFrame` — the clean scene frame (no subject box).
9184
+ * - `none` — no attachment.
9185
+ */
9186
+ var NcMediaPolicySchema = object({ attach: _enum([
9187
+ "best",
9188
+ "best-matching",
9189
+ "keyFrame",
9190
+ "none"
9191
+ ]).default("best") });
9192
+ /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9193
+ var NcThrottleSchema = object({
9194
+ cooldownSec: number().int().min(0).max(86400).default(60),
9195
+ /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9196
+ scope: _enum(["rule", "rule-device"]).default("rule-device")
9197
+ });
9198
+ /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9199
+ var NcRuleInputSchema = object({
9200
+ name: string().min(1).max(200),
9201
+ enabled: boolean().default(true),
9202
+ delivery: NcDeliverySchema,
9203
+ conditions: NcConditionsSchema.default({}),
9204
+ schedule: NcScheduleSchema.optional(),
9205
+ targets: array(NcRuleTargetSchema).min(1),
9206
+ media: NcMediaPolicySchema.default({ attach: "best" }),
9207
+ throttle: NcThrottleSchema.default({
9208
+ cooldownSec: 60,
9209
+ scope: "rule-device"
9210
+ }),
9211
+ /** `{{var}}` templating over camera/class/label/zones/confidence/time. */
9212
+ template: object({
9213
+ title: string().max(500).optional(),
9214
+ body: string().max(2e3).optional()
9215
+ }).optional(),
9216
+ /** Canonical notification priority ordinal (1..5); per-target overridable. */
9217
+ priority: number().int().min(1).max(5).default(3),
9218
+ /**
9219
+ * Ownership/visibility key. Absent = admin/global rule (unchanged legacy
9220
+ * behaviour, visible to all, read-only in the viewer). Present = personal
9221
+ * rule owned by this userId. Server-stamped; never trusted from a client.
9222
+ */
9223
+ ownerUserId: string().optional()
9224
+ });
9225
+ /**
9226
+ * Partial patch for `updateRule` — any subset of the input fields, plus the
9227
+ * persisted-only {@link NcRuleSchema} `disabledTargetIds` set. The latter is
9228
+ * NOT a client-authored input field (it lives on the persisted rule, not the
9229
+ * input), so it is added here explicitly to let the store's per-target opt-out
9230
+ * toggle round-trip through the shared `update` path. Viewer opt-out mutations
9231
+ * still flow through `nc.setRuleTargetEnabled` (owner-checked), never a raw
9232
+ * `updateRule` patch.
9233
+ */
9234
+ var NcRulePatchSchema = NcRuleInputSchema.partial().extend({ disabledTargetIds: array(string()).optional() });
9235
+ /** A persisted rule. */
9236
+ var NcRuleSchema = NcRuleInputSchema.extend({
9237
+ id: string(),
9238
+ /** userId of the admin who created the rule (server-stamped caller). */
9239
+ createdBy: string(),
9240
+ createdAt: number(),
9241
+ updatedAt: number(),
9242
+ /**
9243
+ * Per-target opt-out set. A targetId here is suppressed for THIS rule at
9244
+ * send time. Only a target's OWNER may add/remove its id (server-checked
9245
+ * in `nc.setRuleTargetEnabled`). Defaults to empty.
9246
+ */
9247
+ disabledTargetIds: array(string()).default([])
9248
+ });
9249
+ var NcTestResultSchema = object({
9250
+ recordId: string(),
9251
+ recordKind: _enum([
9252
+ "object-event",
9253
+ "track",
9254
+ "device-event",
9255
+ "package-event"
9256
+ ]),
9257
+ deviceId: number(),
9258
+ timestamp: number(),
9259
+ wouldFire: boolean(),
9260
+ /** Condition id that failed (first failing group), when `wouldFire` is false. */
9261
+ failedCondition: string().optional(),
9262
+ className: string().optional(),
9263
+ label: string().optional()
9264
+ });
9265
+ var NcConditionDescriptorSchema = object({
9266
+ /** Field id inside `NcConditions` (or `'schedule'` for the rule-level group). */
9267
+ id: string(),
9268
+ group: _enum([
9269
+ "scope",
9270
+ "class",
9271
+ "zones",
9272
+ "quality",
9273
+ "label",
9274
+ "schedule",
9275
+ "device",
9276
+ "package",
9277
+ "occupancy"
9278
+ ]),
9279
+ label: string(),
9280
+ /** Editor widget the UI renders — never hardcode per-condition forms. */
9281
+ valueType: _enum([
9282
+ "deviceIdList",
9283
+ "stringList",
9284
+ "number01",
9285
+ "number",
9286
+ "sourceSelect",
9287
+ "zoneSelection",
9288
+ "zoneIdList",
9289
+ "schedule",
9290
+ "plateMatcher",
9291
+ "packagePhase",
9292
+ "polygonDraw",
9293
+ "occupancy"
9294
+ ]),
9295
+ operator: _enum([
9296
+ "in",
9297
+ "notIn",
9298
+ "anyOf",
9299
+ "allOf",
9300
+ "gte",
9301
+ "fuzzyIn",
9302
+ "withinSchedule"
9303
+ ]),
9304
+ /** Which delivery kinds the condition applies to. */
9305
+ appliesTo: array(NcDeliverySchema),
9306
+ phase: string(),
9307
+ description: string().optional()
9308
+ });
9309
+ /**
9310
+ * The delivery lifecycle status of a history row — a straight read of the
9311
+ * durable outbox row's own status (single source of truth):
9312
+ * - `pending` — enqueued, in-flight or retrying with backoff
9313
+ * - `sent` — delivered (terminal)
9314
+ * - `dead` — dead-lettered after exhausting retries / a permanent
9315
+ * backend rejection / a deleted target (terminal; carries
9316
+ * the failure `error`)
9317
+ *
9318
+ * P1 has no `suppressed-quiet-hours` / `snoozed` states — those ride the P2
9319
+ * user dimension (quiet hours / snooze) and are additive when they land.
9320
+ */
9321
+ var NcHistoryStatusSchema = _enum([
9322
+ "pending",
9323
+ "sent",
9324
+ "dead"
9325
+ ]);
9326
+ /** The evaluated record kind a history row descends from (one per trigger). */
9327
+ var NcHistoryRecordKindSchema = _enum([
9328
+ "object-event",
9329
+ "track-end",
9330
+ "device-event",
9331
+ "package-event"
9332
+ ]);
9333
+ /** Subject summary frozen on the row at fire time (survives rule/record edits). */
9334
+ var NcHistorySubjectSchema = object({
9335
+ className: string(),
9336
+ label: string().optional(),
9337
+ confidence: number().optional(),
9338
+ zones: array(string()),
9339
+ timestamp: number()
9340
+ });
9341
+ /**
9342
+ * One delivery-history row. This is a read-only VIEW over the durable
9343
+ * outbox row (single source of truth — the same row the drain loop drives;
9344
+ * NO second write path, so history can never drift from delivery state).
9345
+ * The §3.2 fields map directly: `ruleId`/`targetId`/`deviceId` are columns,
9346
+ * `eventRef` is `recordKind`+`recordId`, `timestamps` are `createdAt`
9347
+ * (fire) / `updatedAt` (last transition), `status` + `error` are the
9348
+ * lifecycle. `ruleName` + `subject` are the intent snapshot frozen at
9349
+ * enqueue. `userId?` (per-recipient history) is P2 — no user dimension in
9350
+ * P1 (admin scope only).
9351
+ */
9352
+ var NcHistoryEntrySchema = object({
9353
+ /** Outbox row id — the stable dedup id `ruleId:dedupRef:targetId`. */
9354
+ id: string(),
9355
+ ruleId: string(),
9356
+ /** Rule name frozen at fire time (outlives a later rename / delete). */
9357
+ ruleName: string(),
9358
+ /** The rule urgency/trigger that produced this delivery. */
9359
+ delivery: NcDeliverySchema,
9360
+ targetId: string(),
9361
+ deviceId: number(),
9362
+ recordKind: NcHistoryRecordKindSchema,
9363
+ /** Event / track ref of the evaluated record (§3.2 `eventRef`). */
9364
+ recordId: string(),
9365
+ /** Present for track-scoped deliveries (object-event / track-end). */
9366
+ trackId: string().optional(),
9367
+ status: NcHistoryStatusSchema,
9368
+ /** Delivery attempts made so far. */
9369
+ attempts: number().int(),
9370
+ /** Fire time (outbox enqueue). */
9371
+ createdAt: number(),
9372
+ /** Last transition time (terminal for sent / dead). */
9373
+ updatedAt: number(),
9374
+ /** Failure detail — present on a `dead` row. */
9375
+ error: string().optional(),
9376
+ subject: NcHistorySubjectSchema
9377
+ });
9378
+ /**
9379
+ * Query filter for `getHistory` (spec §4.2). Every field is a narrowing
9380
+ * AND; absent = unbounded on that axis. `since`/`until` bound the fire time
9381
+ * (`createdAt`, epoch ms, inclusive). `limit` is clamped to
9382
+ * {@link NC_HISTORY_LIMIT_MAX}. `userId` (per-recipient filtering) is P2.
9383
+ */
9384
+ var NcHistoryFilterSchema = object({
9385
+ ruleId: string().optional(),
9386
+ deviceId: number().optional(),
9387
+ status: NcHistoryStatusSchema.optional(),
9388
+ since: number().optional(),
9389
+ until: number().optional(),
9390
+ limit: number().int().min(1).max(500).default(100)
9391
+ });
9392
+ 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 }), {
9393
+ kind: "mutation",
9394
+ auth: "admin",
9395
+ caller: "required"
9396
+ }), method(object({
9397
+ ruleId: string(),
9398
+ patch: NcRulePatchSchema
9399
+ }), object({ rule: NcRuleSchema }), {
9400
+ kind: "mutation",
9401
+ auth: "admin",
9402
+ caller: "required"
9403
+ }), method(object({ ruleId: string() }), object({ success: literal(true) }), {
9404
+ kind: "mutation",
9405
+ auth: "admin"
9406
+ }), method(object({
9407
+ ruleId: string(),
9408
+ enabled: boolean()
9409
+ }), object({ success: literal(true) }), {
9410
+ kind: "mutation",
9411
+ auth: "admin"
9412
+ }), method(object({
9413
+ rule: NcRuleInputSchema,
9414
+ lookbackMinutes: number().int().min(1).max(1440).default(60)
9415
+ }), object({ results: array(NcTestResultSchema) }), {
9416
+ kind: "mutation",
9417
+ auth: "admin"
9418
+ }), method(object({}), object({ catalog: array(NcConditionDescriptorSchema) })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
9419
+ /**
9420
+ * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
9421
+ *
9422
+ * Spec: `docs/superpowers/specs/2026-07-24-nc-occupancy-timelapse-design.md`
9423
+ * §3.2/§3.3.
9424
+ *
9425
+ * Deliberately NOT a capability definition and NOT an `NcRule`:
9426
+ * - Every `NcDelivery` member is a *persisted-pipeline-record* trigger. A
9427
+ * timelapse fires on a SCHEDULE WINDOW BOUNDARY, evaluates no pipeline
9428
+ * record, and produces a video it assembled itself — so it rides no
9429
+ * delivery-enum member (the enum is frozen) and no cap method. This file is
9430
+ * a plain typed schema; it does NOT go through `npm run codegen`.
9431
+ * - It shares only the delivery leg (`notification-output.send`) and the
9432
+ * persistence/ownership patterns with the Notification Center, reusing
9433
+ * {@link NcScheduleSchema} (weekly windows, midnight-crossing, invertible)
9434
+ * and {@link NcRuleTargetSchema} (target ref + passthrough params).
9435
+ *
9436
+ * Ownership is SERVER-DERIVED. `ownerUserId` / `createdBy` / `createdAt` /
9437
+ * `updatedAt` / `id` / `lastGeneratedAt` live on the PERSISTED rule only —
9438
+ * {@link TimelapseRuleInputSchema} and {@link TimelapseRulePatchSchema} do not
9439
+ * carry them, so a forged client payload can never claim or re-own a rule
9440
+ * (Zod strips unknown keys). The store stamps them from the resolved caller.
9441
+ */
9442
+ /** `{{var}}` templating over camera/rule/time — same vocabulary as `NcRule`. */
9443
+ var TimelapseTemplateSchema = object({
9444
+ title: string().max(500).optional(),
9445
+ body: string().max(2e3).optional()
9446
+ });
9447
+ var NameField = string().min(1).max(200);
9448
+ var DeviceIdsField = array(number()).min(1);
9449
+ var CadenceSecField = number().int().min(2).max(3600);
9450
+ var FramerateField = number().int().min(1).max(60);
9451
+ var TargetsField = array(NcRuleTargetSchema).min(1);
9452
+ var PriorityField = number().int().min(1).max(5);
9453
+ /**
9454
+ * Client-supplied timelapse-rule fields. The server stamps id / createdBy /
9455
+ * createdAt / updatedAt / ownerUserId / lastGeneratedAt — none of them appear
9456
+ * here (see the ownership note above).
9457
+ */
9458
+ var TimelapseRuleInputSchema = object({
9459
+ name: NameField,
9460
+ enabled: boolean().default(true),
9461
+ /** Cameras sampled by this rule — one scratch dir + one artifact per device. */
9462
+ deviceIds: DeviceIdsField,
9463
+ /**
9464
+ * Activation window(s). REQUIRED (unlike `NcRule`, where an absent schedule
9465
+ * means "always active"): a timelapse is defined by its window boundaries —
9466
+ * open clears the scratch, close assembles and delivers.
9467
+ */
9468
+ schedule: NcScheduleSchema,
9469
+ /** Force-snapshot cadence inside the window, seconds (predecessor parity). */
9470
+ cadenceSec: CadenceSecField.default(15),
9471
+ /** Output frames per second of the assembled mp4 (predecessor parity). */
9472
+ framerate: FramerateField.default(10),
9473
+ /** `notification-output` targets the finished video/thumbnail is sent to. */
9474
+ targets: TargetsField,
9475
+ template: TimelapseTemplateSchema.optional(),
9476
+ /** Canonical notification priority ordinal (1..5); per-target overridable. */
9477
+ priority: PriorityField.default(3)
9478
+ });
9479
+ object({
9480
+ name: NameField.optional(),
9481
+ enabled: boolean().optional(),
9482
+ deviceIds: DeviceIdsField.optional(),
9483
+ schedule: NcScheduleSchema.optional(),
9484
+ cadenceSec: CadenceSecField.optional(),
9485
+ framerate: FramerateField.optional(),
9486
+ targets: TargetsField.optional(),
9487
+ template: TimelapseTemplateSchema.nullable().optional(),
9488
+ priority: PriorityField.optional()
9489
+ });
9490
+ TimelapseRuleInputSchema.extend({
9491
+ id: string(),
9492
+ /**
9493
+ * Ownership/visibility key. Absent = admin/global rule (visible to all).
9494
+ * Present = personal rule owned by this userId. Server-stamped from the
9495
+ * resolved caller; never trusted from a client payload.
9496
+ */
9497
+ ownerUserId: string().optional(),
9498
+ /**
9499
+ * Epoch-ms of the last successful generation — the 1-hour re-generation
9500
+ * guard's durable state (predecessor parity). Absent = never generated.
9501
+ */
9502
+ lastGeneratedAt: number().optional(),
9503
+ /** userId of the caller who created the rule (server-stamped). */
9504
+ createdBy: string(),
9505
+ createdAt: number(),
9506
+ updatedAt: number()
9507
+ });
9508
+ /**
9509
+ * Generic device-level status snapshot. Auto-registered by `BaseDevice`
9510
+ * for every device, regardless of provider — the kernel needs a uniform
9511
+ * cap-keyed slice for the basic device flags every consumer expects to
9512
+ * read across processes (the `online` flag in particular). Driver-specific
9513
+ * caps (`battery`, `doorbell`, …) carry their domain-specific state on
9514
+ * their own slices.
9515
+ *
9516
+ * Pattern is identical to `battery`: schema-bearing `runtimeState`,
9517
+ * empty `methods`, single change event. Reads land at
9518
+ * `runtimeState.getCapState('device-status')`; writes at
9519
+ * `runtimeState.setCapState('device-status', …)`. Cross-process
9520
+ * consumers reach the same data via the `device-state` cap router
9521
+ * (`getCapSlice({deviceId, capName: 'device-status'})`).
9522
+ */
9523
+ var DeviceStatusSchema = object({
9524
+ /**
9525
+ * Device-level liveness. Drivers flip via `markOnline(boolean)` on
9526
+ * `BaseDevice`. Provider semantics vary — RTSP aggregates broker
9527
+ * stream-health, Reolink reads firmware push events, ONVIF tracks
9528
+ * ping responses. This cap intentionally does NOT prescribe which
9529
+ * signal drives the flag.
9530
+ */
9531
+ online: boolean(),
9532
+ /** Ms epoch of the last `online` transition. Lets consumers tell
9533
+ * apart "just came online" from "still online". */
9534
+ lastChangedAt: number()
9535
+ });
9536
+ object({
9537
+ deviceId: number(),
9538
+ status: DeviceStatusSchema
9539
+ });
9540
+ /**
9541
+ * Per-device feature/identity probe slice. Holds the runtime-resolved
9542
+ * truth about what a device CAN do — which the kernel uses to:
9543
+ * 1. Reconcile accessory children (hub-children spawn siren/floodlight/PIR
9544
+ * based on what the firmware actually advertises).
9545
+ * 2. Compute the public `features: DeviceFeature[]` array surfaced via
9546
+ * `device-manager.listAll`.
9547
+ * 3. Decide which optional caps (PTZ, intercom, doorbell, battery, …)
9548
+ * to register on the device's capability surface.
9549
+ *
9550
+ * Auto-registered by `BaseDevice` for every device. Drivers populate the
9551
+ * slice from `onProbe()` (kernel calls it once after register, before
9552
+ * accessory reconciliation). Consumers read via:
9553
+ * `runtimeState.getCapState<FeatureProbeStatus>('feature-probe')`
9554
+ *
9555
+ * `flags` is an open record so each driver carries its own keys without
9556
+ * a centralized schema bottleneck — Reolink writes `hasPtz/hasIntercom`,
9557
+ * Hikvision writes `hasSupplementalLight/hasAlarmIo`, etc.
9558
+ *
9559
+ * Replaces the older driver-local `deviceCache.has*` blob: the per-device
9560
+ * config is for operator-edited overrides + UI snapshots; runtime probe
9561
+ * results belong in runtime-state where the kernel handles persistence,
9562
+ * cross-process mirroring, and reactive updates.
9563
+ */
9564
+ var FeatureProbeStatusSchema = object({
9565
+ /**
9566
+ * Driver-specific flag bag. Each driver picks its own key names — the
9567
+ * cap deliberately does NOT enforce a closed enum here. Reolink keys:
9568
+ * `hasPtz`, `hasIntercom`, `hasDoorbell`, `hasFloodlight`, `hasSiren`,
9569
+ * `hasPirSensor`, `hasAutotrack`, `hasBattery`. Hikvision keys:
9570
+ * `hasSupplementalLight`, `lightHasWhiteLight`, `hasAlarmIo`, `hasPtz`.
9571
+ */
9572
+ flags: record(string(), unknown()),
9573
+ /**
9574
+ * Coarse driver-classification — lets cross-process consumers tell apart
9575
+ * cameras / battery-cams / NVRs without re-running the probe. `null`
9576
+ * before the first probe completes.
9577
+ */
9578
+ deviceType: string().nullable(),
9579
+ /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
9580
+ model: string().nullable(),
9581
+ /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
9582
+ channelCount: number().nullable(),
9583
+ /**
9584
+ * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
9585
+ * completes — drivers' `getAccessoryChildren()` should treat zero as
9586
+ * "probe not done yet, return empty" so accessories aren't spawned
9587
+ * before the firmware is queried.
9588
+ */
9589
+ lastProbedAt: number(),
9590
+ /**
9591
+ * Framework convention: every runtime-state slice carries this for the
9592
+ * createRuntimeStateBridge stale-check helper. We keep it in sync with
9593
+ * `lastProbedAt` on every write.
9594
+ */
8950
9595
  lastFetchedAt: number()
8951
9596
  });
8952
9597
  object({
@@ -11840,67 +12485,6 @@ DeviceType.Light, DeviceType.Siren, DeviceType.Switch, method(object({
11840
12485
  lastChangedAt: number()
11841
12486
  });
11842
12487
  /**
11843
- * Shared geometry vocabulary for on-frame shape caps — privacy-mask,
11844
- * motion-zones, and the detection zones/lines editor all speak this one
11845
- * language so a single drawing-plane editor and the providers stay
11846
- * decoupled from each cap's storage.
11847
- *
11848
- * All coordinates are normalized 0..1 of the camera frame (top-left
11849
- * origin). Each cap composes the SUBSET of shape kinds it supports and
11850
- * advertises it via `supportedShapes` in its `getOptions`.
11851
- */
11852
- /** A normalized 0..1 point (top-left origin). */
11853
- var MaskPointSchema = object({
11854
- x: number(),
11855
- y: number()
11856
- });
11857
- /** Axis-aligned rectangle (normalized 0..1). */
11858
- var MaskRectShapeSchema = object({
11859
- kind: literal("rect"),
11860
- x: number(),
11861
- y: number(),
11862
- width: number(),
11863
- height: number()
11864
- });
11865
- /** Free polygon — an ordered list of normalized vertices (≥3). */
11866
- var MaskPolygonShapeSchema = object({
11867
- kind: literal("polygon"),
11868
- points: array(MaskPointSchema)
11869
- });
11870
- /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
11871
- var MaskGridShapeSchema = object({
11872
- kind: literal("grid"),
11873
- gridWidth: number(),
11874
- gridHeight: number(),
11875
- cells: array(boolean())
11876
- });
11877
- discriminatedUnion("kind", [
11878
- MaskRectShapeSchema,
11879
- MaskPolygonShapeSchema,
11880
- MaskGridShapeSchema,
11881
- object({
11882
- kind: literal("line"),
11883
- points: array(MaskPointSchema)
11884
- })
11885
- ]);
11886
- /** Every shape-kind discriminant, for `supportedShapes` advertisement. */
11887
- var MaskShapeKindSchema = _enum([
11888
- "rect",
11889
- "polygon",
11890
- "grid",
11891
- "line"
11892
- ]);
11893
- /** Polygon vertex bounds when a cap supports 'polygon' (e.g. Hikvision {min:4,max:4}). */
11894
- var MaskPolygonVerticesSchema = object({
11895
- min: number(),
11896
- max: number()
11897
- });
11898
- /** Grid dimensions when a cap supports 'grid'. */
11899
- var MaskGridDimsSchema = object({
11900
- width: number(),
11901
- height: number()
11902
- });
11903
- /**
11904
12488
  * Motion-zones share the same MaskShape vocabulary as privacy-mask — the
11905
12489
  * on-camera motion-detection mask is a single `grid` region (a row-major
11906
12490
  * boolean cell lattice the camera's onboard VMD evaluates). Composing it as
@@ -13769,6 +14353,55 @@ method(object({
13769
14353
  password: string()
13770
14354
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
13771
14355
  /**
14356
+ * A live terminal session hosted by the provider addon. Output and input do
14357
+ * NOT flow through the capability — they use the addon data plane
14358
+ * (`GET /addon/terminal/<id>/out` SSE, `POST /addon/terminal/<id>/in`) because
14359
+ * terminal output must be ordered and lossless. The event bus is telemetry and
14360
+ * may drop chunks ([D8]), and a dropped chunk desynchronises the vt parser
14361
+ * permanently until a full repaint. The capability owns only lifecycle.
14362
+ */
14363
+ var TerminalSessionInfoSchema = object({
14364
+ /** Opaque session id minted by the provider on `openSession`. */
14365
+ sessionId: string(),
14366
+ /** The pre-declared profile this session runs (never a free-form command). */
14367
+ profileId: string(),
14368
+ /** Human-readable profile label for the UI session list. */
14369
+ label: string(),
14370
+ cols: number().int().positive(),
14371
+ rows: number().int().positive(),
14372
+ /** ms-epoch the session's pty was spawned. */
14373
+ startedAt: number()
14374
+ });
14375
+ /**
14376
+ * A profile the operator may open — a pre-declared, allowlisted program
14377
+ * (`monitor` → `btm`). The capability accepts only these ids; a free-form
14378
+ * command string would be remote code execution as the server's user, so it is
14379
+ * deliberately not part of the contract.
14380
+ */
14381
+ var TerminalProfileInfoSchema = object({
14382
+ profileId: string(),
14383
+ label: string(),
14384
+ description: string().optional()
14385
+ });
14386
+ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
14387
+ profileId: string(),
14388
+ cols: number().int().positive(),
14389
+ rows: number().int().positive()
14390
+ }), TerminalSessionInfoSchema, {
14391
+ kind: "mutation",
14392
+ auth: "admin"
14393
+ }), method(object({
14394
+ sessionId: string(),
14395
+ cols: number().int().positive(),
14396
+ rows: number().int().positive()
14397
+ }), _void(), {
14398
+ kind: "mutation",
14399
+ auth: "admin"
14400
+ }), method(object({ sessionId: string() }), _void(), {
14401
+ kind: "mutation",
14402
+ auth: "admin"
14403
+ });
14404
+ /**
13772
14405
  * Orchestrator-side destination metadata. The orchestrator computes
13773
14406
  * `id = <addonId>:<subId>` from its provider lookup so consumers
13774
14407
  * (admin UI, restore flow) see one canonical key.
@@ -13869,11 +14502,53 @@ var LocationStatSchema = object({
13869
14502
  fileCount: number(),
13870
14503
  present: boolean()
13871
14504
  });
14505
+ /**
14506
+ * A backup schedule — the N:M "entry" that binds one cron cadence to a
14507
+ * SET of destination locations. Supersedes the per-location cron on
14508
+ * `BackupDestinationPolicy`: an operator creates a schedule, picks the
14509
+ * `backups` locations it should write to, and the orchestrator fans a
14510
+ * single archive out to all of them when the cron fires.
14511
+ *
14512
+ * `retentionCount` is per-schedule (D-decision 2026-07-28): every
14513
+ * location targeted by this schedule keeps this many archives from
14514
+ * this schedule's runs.
14515
+ *
14516
+ * `dataSources` optionally narrows which top-level state locations
14517
+ * (db, addons, tls, …) are archived; omitted = the orchestrator's
14518
+ * default full set.
14519
+ */
14520
+ var BackupScheduleSchema = object({
14521
+ /** Stable id. Generated by the orchestrator on first upsert if absent. */
14522
+ id: string(),
14523
+ /** Operator-facing display name. */
14524
+ label: string(),
14525
+ /** 5-field POSIX cron. Empty = disabled cadence (kept for editing). */
14526
+ cron: string(),
14527
+ /** Master on/off toggle for the whole schedule. */
14528
+ enabled: boolean(),
14529
+ /** `backups`-location ids this schedule writes to (fan-out set). */
14530
+ locationIds: array(string()).readonly(),
14531
+ /** Archives kept per targeted location for this schedule. */
14532
+ retentionCount: number().int().min(1).max(1e3),
14533
+ /** Optional subset of source locations to include; omitted = all. */
14534
+ dataSources: array(string()).readonly().optional(),
14535
+ /** ms-epoch of last successful run. */
14536
+ lastRunAt: number().optional(),
14537
+ /** ms-epoch of next computed firing (read-only, filled on list). */
14538
+ nextRunAt: number().optional()
14539
+ });
13872
14540
  method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }), method(object({
13873
14541
  /** Subset of registered `backup-destination` addon ids to write to. */
13874
14542
  destinations: array(string()).optional(),
13875
14543
  locations: array(string()).optional(),
13876
- label: string().optional()
14544
+ label: string().optional(),
14545
+ /**
14546
+ * Per-run retention override applied to every targeted
14547
+ * destination. Used by schedule-driven runs (per-entry
14548
+ * retention). Omitted = each destination's own policy
14549
+ * retention (manual runs).
14550
+ */
14551
+ retentionCount: number().int().min(1).max(1e3).optional()
13877
14552
  }).optional(), array(BackupEntrySchema).readonly(), {
13878
14553
  kind: "mutation",
13879
14554
  auth: "admin"
@@ -13922,7 +14597,21 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
13922
14597
  ok: boolean(),
13923
14598
  error: string().optional(),
13924
14599
  nextRuns: array(number()).readonly()
13925
- }));
14600
+ })), method(_void(), array(BackupScheduleSchema).readonly(), { auth: "admin" }), method(object({
14601
+ id: string().optional(),
14602
+ label: string(),
14603
+ cron: string(),
14604
+ enabled: boolean(),
14605
+ locationIds: array(string()).readonly(),
14606
+ retentionCount: number().int().min(1).max(1e3),
14607
+ dataSources: array(string()).readonly().optional()
14608
+ }), BackupScheduleSchema, {
14609
+ kind: "mutation",
14610
+ auth: "admin"
14611
+ }), method(object({ id: string() }), _void(), {
14612
+ kind: "mutation",
14613
+ auth: "admin"
14614
+ });
13926
14615
  /**
13927
14616
  * `broker` — unified pub/sub broker registry, system-scoped collection.
13928
14617
  *
@@ -14788,1785 +15477,1297 @@ method(object({
14788
15477
  }), _void(), {
14789
15478
  kind: "mutation",
14790
15479
  auth: "admin"
14791
- }), method(object({
14792
- deviceId: number(),
14793
- location: string().nullable()
14794
- }), _void(), {
14795
- kind: "mutation",
14796
- auth: "admin"
14797
- }), method(object({
14798
- deviceId: number(),
14799
- type: _enum(DeviceType)
14800
- }), _void(), {
14801
- kind: "mutation",
14802
- auth: "admin"
14803
- }), method(object({
14804
- deviceId: number(),
14805
- integrationId: string()
14806
- }), _void(), {
14807
- kind: "mutation",
14808
- auth: "admin"
14809
- }), method(object({
14810
- deviceId: number(),
14811
- linkDeviceId: number().nullable()
14812
- }), _void(), {
14813
- kind: "mutation",
14814
- auth: "admin"
14815
- }), method(object({
14816
- deviceId: number(),
14817
- primaryChildEntityId: string().nullable()
14818
- }), _void(), {
14819
- kind: "mutation",
14820
- auth: "admin"
14821
- }), method(object({
14822
- deviceId: number(),
14823
- childLayout: array(ChildLayoutEntrySchema).readonly()
14824
- }), _void(), {
14825
- kind: "mutation",
14826
- auth: "admin"
14827
- }), method(object({
14828
- deviceId: number(),
14829
- deviceLinks: array(DeviceLinkSchema).readonly()
14830
- }), _void(), {
14831
- kind: "mutation",
14832
- auth: "admin"
14833
- }), method(object({
14834
- deviceId: number(),
14835
- display: DeviceDisplayOverrideSchema.nullable()
14836
- }), _void(), {
14837
- kind: "mutation",
14838
- auth: "admin"
14839
- }), method(object({}), object({ defaults: record(string(), RoleDisplayDefaultSchema) }), { kind: "query" }), method(object({ defaults: record(string(), RoleDisplayDefaultSchema) }), _void(), {
14840
- kind: "mutation",
14841
- auth: "admin"
14842
- }), method(object({
14843
- deviceId: number(),
14844
- includeSynthesizable: boolean().optional()
14845
- }), object({ caps: array(object({
14846
- cap: string(),
14847
- fields: array(object({
14848
- path: string(),
14849
- kind: _enum([
14850
- "string",
14851
- "number",
14852
- "boolean",
14853
- "enum"
14854
- ]),
14855
- enumValues: array(string()).optional(),
14856
- item: boolean().optional()
14857
- })).readonly(),
14858
- itemArray: object({
14859
- path: string(),
14860
- keyField: string()
14861
- }).optional()
14862
- })).readonly() }), { kind: "query" }), method(object({
14863
- deviceId: number(),
14864
- role: string().nullable()
14865
- }), _void(), {
14866
- kind: "mutation",
14867
- auth: "admin"
14868
- }), method(object({
14869
- deviceId: number(),
14870
- name: string().optional(),
14871
- location: string().nullable().optional(),
14872
- type: _enum(DeviceType).optional(),
14873
- integrationId: string().optional(),
14874
- linkDeviceId: number().nullable().optional(),
14875
- role: string().nullable().optional()
14876
- }), _void(), {
14877
- kind: "mutation",
14878
- auth: "admin"
14879
- }), method(object({
14880
- deviceId: number(),
14881
- patch: record(string(), unknown())
14882
- }), _void(), {
14883
- kind: "mutation",
14884
- auth: "admin"
14885
- }), method(_void(), array(string())), method(object({ name: string() }), _void(), {
14886
- kind: "mutation",
14887
- auth: "admin"
14888
- }), method(object({
14889
- name: string(),
14890
- cascade: boolean().optional()
14891
- }), _void(), {
14892
- kind: "mutation",
14893
- auth: "admin"
14894
- }), method(object({
14895
- deviceId: number(),
14896
- disabled: boolean()
14897
- }), _void(), {
14898
- kind: "mutation",
14899
- auth: "admin"
14900
- }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
14901
- mode: DeviceLinkModeSchema,
14902
- devices: array(LinkedDeviceSchema)
14903
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
14904
- deviceId: number(),
14905
- values: record(string(), unknown())
14906
- }), object({ success: literal(true) }), {
14907
- kind: "mutation",
14908
- auth: "admin"
14909
- }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
14910
- kind: "mutation",
14911
- auth: "admin"
14912
- }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
14913
- kind: "mutation",
14914
- auth: "admin"
14915
- }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
14916
- kind: "mutation",
14917
- auth: "admin"
14918
- }), method(object({ integrationId: string() }), object({ removed: number() }), {
14919
- kind: "mutation",
14920
- auth: "admin"
14921
- }), method(object({ deviceId: number() }), record(string(), string().nullable())), method(object({
14922
- deviceId: number(),
14923
- profileMap: record(string(), string().nullable())
14924
- }), object({ success: literal(true) }), {
14925
- kind: "mutation",
14926
- auth: "admin"
14927
- }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
14928
- kind: "mutation",
14929
- auth: "admin"
14930
- }), method(object({ deviceId: number() }), object({
14931
- deviceId: number(),
14932
- entries: array(object({
14933
- capName: string(),
14934
- kind: _enum([
14935
- "native",
14936
- "wrapped",
14937
- "linked"
14938
- ]),
14939
- providerAddonId: string(),
14940
- providerNodeId: string(),
14941
- nativeAddonId: string()
14942
- }))
14943
- })), method(object({}), array(object({
14944
- deviceId: number(),
14945
- entries: array(object({
14946
- capName: string(),
14947
- kind: _enum([
14948
- "native",
14949
- "wrapped",
14950
- "linked"
14951
- ]),
14952
- providerAddonId: string(),
14953
- providerNodeId: string(),
14954
- nativeAddonId: string()
14955
- }))
14956
- }))), method(object({
14957
- deviceId: number(),
14958
- capName: string(),
14959
- wrapperAddonId: string(),
14960
- active: boolean()
14961
- }), _void(), {
14962
- kind: "mutation",
14963
- auth: "admin"
14964
- }), method(object({ capName: string() }), array(string())), method(object({ deviceType: string() }), array(object({
14965
- capName: string(),
14966
- wrappers: array(string())
14967
- }))), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), object({
14968
- settings: SettingsSchemaWithValuesSchema.nullable(),
14969
- live: SettingsSchemaWithValuesSchema.nullable()
14970
- })), method(object({
14971
- deviceId: number().int().nonnegative(),
14972
- action: string().min(1),
14973
- input: unknown()
14974
- }), unknown(), { kind: "mutation" }), method(object({
14975
- deviceId: number(),
14976
- writerCapName: string(),
14977
- writerAddonId: string(),
14978
- key: string(),
14979
- value: unknown()
14980
- }), object({ success: literal(true) }), {
14981
- kind: "mutation",
14982
- auth: "admin"
14983
- }), method(object({
14984
- deviceId: number(),
14985
- changes: array(object({
14986
- writerCapName: string(),
14987
- writerAddonId: string(),
14988
- key: string(),
14989
- value: unknown()
14990
- }))
14991
- }), object({
14992
- success: literal(true),
14993
- failures: array(object({
14994
- writerCapName: string(),
14995
- writerAddonId: string(),
14996
- error: string()
14997
- }))
14998
- }), {
14999
- kind: "mutation",
15000
- auth: "admin"
15001
- }), method(object({ addonId: string() }), array(DiscoveryCandidateSchema), {
15002
- kind: "mutation",
15003
- auth: "admin"
15004
- }), method(object({
15005
- addonId: string(),
15006
- candidate: DiscoveryCandidateSchema,
15007
- /** Owning integration id, stamped onto the new device's meta by the
15008
- * device-manager forwarder so `removeByIntegration` can cascade it.
15009
- * Optional for back-compat (omitted = no stamp = pre-existing behavior). */
15010
- integrationId: string().optional()
15011
- }), DeviceSummarySchema, {
15012
- kind: "mutation",
15013
- auth: "admin"
15014
- }), method(object({
15015
- addonId: string(),
15016
- type: _enum(DeviceType)
15017
- }), unknown().nullable()), method(object({
15018
- addonId: string(),
15019
- type: _enum(DeviceType),
15020
- config: record(string(), unknown()),
15021
- /** Owning integration id, stamped onto the new device's meta by the
15022
- * device-manager forwarder so `removeByIntegration` can cascade it.
15023
- * Optional for back-compat (omitted = no stamp = pre-existing behavior). */
15024
- integrationId: string().optional()
15025
- }), DeviceSummarySchema, {
15026
- kind: "mutation",
15027
- auth: "admin"
15028
- }), method(object({
15029
- addonId: string(),
15030
- type: _enum(DeviceType),
15031
- key: string(),
15032
- value: unknown(),
15033
- formValues: record(string(), unknown()).optional()
15034
- }), FieldProbeResultSchema, {
15035
- kind: "mutation",
15036
- auth: "admin"
15037
- }), method(object({
15038
- addonId: string(),
15039
- integrationId: string()
15040
- }), object({ filters: array(AdoptionFilterSchema) }), { auth: "admin" }), method(ListCandidatesInputSchema.extend({ addonId: string() }), ListCandidatesOutputSchema, { auth: "admin" }), method(object({
15041
- addonId: string(),
15042
- integrationId: string()
15043
- }), AdoptionStatusSchema, {
15044
- kind: "mutation",
15045
- auth: "admin"
15046
- }), method(AdoptInputSchema.extend({ addonId: string() }), AdoptResultSchema, {
15480
+ }), method(object({
15481
+ deviceId: number(),
15482
+ location: string().nullable()
15483
+ }), _void(), {
15047
15484
  kind: "mutation",
15048
15485
  auth: "admin"
15049
- }), method(ReleaseInputSchema.extend({ addonId: string() }), _void(), {
15486
+ }), method(object({
15487
+ deviceId: number(),
15488
+ type: _enum(DeviceType)
15489
+ }), _void(), {
15050
15490
  kind: "mutation",
15051
15491
  auth: "admin"
15052
- }), method(ResyncInputSchema, ResyncResultSchema, {
15492
+ }), method(object({
15493
+ deviceId: number(),
15494
+ integrationId: string()
15495
+ }), _void(), {
15053
15496
  kind: "mutation",
15054
15497
  auth: "admin"
15055
- }), method(object({}), object({ providers: array(object({
15056
- addonId: string(),
15057
- label: string()
15058
- })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
15059
- addonId: string(),
15060
- label: string(),
15061
- candidates: array(DiscoveryCandidateSchema).readonly(),
15062
- error: string().nullable()
15063
- })).readonly() }), {
15498
+ }), method(object({
15499
+ deviceId: number(),
15500
+ linkDeviceId: number().nullable()
15501
+ }), _void(), {
15064
15502
  kind: "mutation",
15065
15503
  auth: "admin"
15066
15504
  }), method(object({
15067
- addonId: string(),
15068
- params: record(string(), unknown()).optional()
15069
- }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
15505
+ deviceId: number(),
15506
+ primaryChildEntityId: string().nullable()
15507
+ }), _void(), {
15070
15508
  kind: "mutation",
15071
15509
  auth: "admin"
15072
- }), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
15510
+ }), method(object({
15073
15511
  deviceId: number(),
15074
- key: string(),
15075
- value: unknown()
15076
- }), FieldProbeResultSchema, {
15512
+ childLayout: array(ChildLayoutEntrySchema).readonly()
15513
+ }), _void(), {
15077
15514
  kind: "mutation",
15078
15515
  auth: "admin"
15079
15516
  }), method(object({
15080
15517
  deviceId: number(),
15081
- caps: array(string()).readonly().optional()
15082
- }), record(string(), unknown().nullable()));
15083
- method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
15084
- deviceId: number(),
15085
- capName: string()
15086
- }), record(string(), unknown()).nullable()), method(object({}), record(string(), record(string(), record(string(), unknown())))), method(object({
15087
- deviceId: number(),
15088
- capName: string(),
15089
- slice: record(string(), unknown())
15090
- }), _void(), { kind: "mutation" }), object({
15518
+ deviceLinks: array(DeviceLinkSchema).readonly()
15519
+ }), _void(), {
15520
+ kind: "mutation",
15521
+ auth: "admin"
15522
+ }), method(object({
15091
15523
  deviceId: number(),
15092
- capName: string(),
15093
- slice: record(string(), unknown())
15094
- });
15095
- /**
15096
- * Embedding output. `embedding` is wire-encoded as `number[]` so the
15097
- * Zod-validated tRPC surface round-trips cleanly; consumers that need a
15098
- * `Float32Array` can wrap it on the way out (in-process, no marshalling
15099
- * is involved). `inferenceMs` mirrors the runtime field used by the
15100
- * post-analysis enrichment-engine.
15101
- */
15102
- var EmbeddingResultSchema = object({
15103
- embedding: array(number()),
15104
- inferenceMs: number()
15105
- });
15106
- var EmbeddingInfoSchema = object({
15107
- modelId: string(),
15108
- embeddingDim: number(),
15109
- ready: boolean()
15110
- });
15111
- method(object({
15112
- crop: _instanceof(Uint8Array),
15113
- width: number(),
15114
- height: number()
15115
- }), EmbeddingResultSchema), method(object({ text: string() }), EmbeddingResultSchema), method(_void(), EmbeddingInfoSchema);
15116
- /**
15117
- * filesystem-browse — per-node capability for browsing the node's local
15118
- * filesystem, sandboxed to operator-configured allowed roots. Used by the
15119
- * admin "Add filesystem location" flow to pick a node + path. `mode:'per-node'`
15120
- * (one provider per node); the hub calls it with `{nodeId}` so the codegen
15121
- * routes to that exact node (default `nodeIdMode:'routing'`).
15122
- */
15123
- var DirEntrySchema = object({
15124
- name: string(),
15125
- path: string()
15126
- });
15127
- var BrowseResultSchema = object({
15128
- path: string(),
15129
- entries: array(DirEntrySchema).readonly(),
15130
- freeBytes: number(),
15131
- totalBytes: number()
15132
- });
15133
- method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({ path: string() }), BrowseResultSchema, { auth: "admin" }), method(object({ path: string() }), object({ path: string() }), {
15524
+ display: DeviceDisplayOverrideSchema.nullable()
15525
+ }), _void(), {
15134
15526
  kind: "mutation",
15135
15527
  auth: "admin"
15136
- });
15137
- /**
15138
- * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15139
- * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15140
- * caps stay wire-compatible without a circular cap→cap import.
15141
- *
15142
- * Errors are a discriminated-union RESULT, never thrown: the shape survives
15143
- * every transport tier structurally, and failed calls still write usage rows.
15144
- * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15145
- */
15146
- var LlmUsageSchema = object({
15147
- inputTokens: number(),
15148
- outputTokens: number()
15149
- });
15150
- var LlmErrorCodeSchema = _enum([
15151
- "timeout",
15152
- "rate-limited",
15153
- "auth",
15154
- "refusal",
15155
- "bad-request",
15156
- "unavailable",
15157
- "no-profile",
15158
- "budget-exceeded",
15159
- "adapter-error"
15160
- ]);
15161
- var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15162
- ok: literal(true),
15163
- text: string(),
15164
- model: string(),
15165
- usage: LlmUsageSchema,
15166
- truncated: boolean(),
15167
- latencyMs: number()
15168
- }), object({
15169
- ok: literal(false),
15170
- code: LlmErrorCodeSchema,
15171
- message: string(),
15172
- retryAfterMs: number().optional()
15173
- })]);
15174
- /**
15175
- * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
15176
- * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15177
- * notification-output.cap.ts:27-31 precedents).
15178
- */
15179
- var LlmImageSchema = object({
15180
- bytes: _instanceof(Uint8Array),
15181
- mimeType: string()
15182
- });
15183
- var LlmGenerateBaseInputSchema = object({
15184
- /** Collection routing (the notification-output posture). */
15185
- addonId: string().optional(),
15186
- /** Explicit profile; else the resolution chain (spec §3). */
15187
- profileId: string().optional(),
15188
- /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15189
- consumer: string(),
15190
- system: string().optional(),
15191
- /** v1: single-turn. `messages[]` is a v2 additive field. */
15192
- prompt: string(),
15193
- /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15194
- jsonSchema: record(string(), unknown()).optional(),
15195
- /** Per-call override of the profile default. */
15196
- maxTokens: number().int().positive().optional(),
15197
- temperature: number().optional()
15198
- });
15199
- /**
15200
- * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
15201
- * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15202
- * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
15203
- * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15204
- * this only through the `llm` cap's methods.
15205
- *
15206
- * One running llama-server child per node in v1 (models are RAM-heavy).
15207
- * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15208
- * watchdog — operator decision #3).
15209
- */
15210
- var ManagedModelRefSchema = discriminatedUnion("kind", [
15211
- object({
15212
- kind: literal("catalog"),
15213
- catalogId: string()
15214
- }),
15215
- object({
15216
- kind: literal("url"),
15217
- url: string(),
15218
- sha256: string().optional()
15219
- }),
15220
- object({
15221
- kind: literal("path"),
15222
- path: string()
15223
- })
15224
- ]);
15225
- var ManagedRuntimeConfigSchema = object({
15226
- /** WHERE the runtime lives — hub or any agent. */
15227
- nodeId: string(),
15228
- /** Closed for v1; 'ollama' is a v2 candidate. */
15229
- engine: _enum(["llama-cpp"]),
15230
- model: ManagedModelRefSchema,
15231
- contextSize: number().int().default(4096),
15232
- /** 0 = CPU-only. */
15233
- gpuLayers: number().int().default(0),
15234
- /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15235
- threads: number().int().optional(),
15236
- /** Concurrent slots. */
15237
- parallel: number().int().default(1),
15238
- /** Else lazy: first generate boots it. */
15239
- autoStart: boolean().default(false),
15240
- /** 0 = never; frees RAM after quiet periods. */
15241
- idleStopMinutes: number().int().default(30)
15242
- });
15243
- var LlmRuntimeStatusSchema = object({
15244
- /** Status is ALWAYS node-qualified. */
15245
- nodeId: string(),
15246
- state: _enum([
15247
- "stopped",
15248
- "downloading",
15249
- "starting",
15250
- "ready",
15251
- "crashed",
15252
- "failed"
15253
- ]),
15254
- pid: number().optional(),
15255
- port: number().optional(),
15256
- modelPath: string().optional(),
15257
- modelId: string().optional(),
15258
- downloadProgress: number().min(0).max(1).optional(),
15259
- lastError: string().optional(),
15260
- crashesInWindow: number(),
15261
- /** Child RSS (sampled best-effort). */
15262
- memoryBytes: number().optional(),
15263
- vramBytes: number().optional()
15264
- });
15265
- var LlmNodeModelSchema = object({
15266
- file: string(),
15267
- sizeBytes: number(),
15268
- catalogId: string().optional(),
15269
- installedAt: number().optional()
15270
- });
15271
- var LlmRuntimeDiskUsageSchema = object({
15272
- nodeId: string(),
15273
- modelsBytes: number(),
15274
- freeBytes: number().optional()
15275
- });
15276
- method(LlmGenerateBaseInputSchema.extend({
15277
- images: array(LlmImageSchema).optional(),
15278
- runtime: ManagedRuntimeConfigSchema,
15279
- /** The managed profile's timeout, threaded by the hub provider. */
15280
- timeoutMs: number().int().positive().optional()
15281
- }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15528
+ }), method(object({}), object({ defaults: record(string(), RoleDisplayDefaultSchema) }), { kind: "query" }), method(object({ defaults: record(string(), RoleDisplayDefaultSchema) }), _void(), {
15529
+ kind: "mutation",
15530
+ auth: "admin"
15531
+ }), method(object({
15532
+ deviceId: number(),
15533
+ includeSynthesizable: boolean().optional()
15534
+ }), object({ caps: array(object({
15535
+ cap: string(),
15536
+ fields: array(object({
15537
+ path: string(),
15538
+ kind: _enum([
15539
+ "string",
15540
+ "number",
15541
+ "boolean",
15542
+ "enum"
15543
+ ]),
15544
+ enumValues: array(string()).optional(),
15545
+ item: boolean().optional()
15546
+ })).readonly(),
15547
+ itemArray: object({
15548
+ path: string(),
15549
+ keyField: string()
15550
+ }).optional()
15551
+ })).readonly() }), { kind: "query" }), method(object({
15552
+ deviceId: number(),
15553
+ role: string().nullable()
15554
+ }), _void(), {
15282
15555
  kind: "mutation",
15283
15556
  auth: "admin"
15284
- }), method(object({}), _void(), {
15557
+ }), method(object({
15558
+ deviceId: number(),
15559
+ name: string().optional(),
15560
+ location: string().nullable().optional(),
15561
+ type: _enum(DeviceType).optional(),
15562
+ integrationId: string().optional(),
15563
+ linkDeviceId: number().nullable().optional(),
15564
+ role: string().nullable().optional()
15565
+ }), _void(), {
15285
15566
  kind: "mutation",
15286
15567
  auth: "admin"
15287
- }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15568
+ }), method(object({
15569
+ deviceId: number(),
15570
+ patch: record(string(), unknown())
15571
+ }), _void(), {
15288
15572
  kind: "mutation",
15289
15573
  auth: "admin"
15290
- }), method(object({ file: string() }), _void(), {
15574
+ }), method(_void(), array(string())), method(object({ name: string() }), _void(), {
15291
15575
  kind: "mutation",
15292
15576
  auth: "admin"
15293
- }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15294
- /**
15295
- * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15296
- * methods concat-fan across providers; single-row methods route to ONE
15297
- * provider by the `addonId` in the call input (the notification-output
15298
- * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15299
- * (hub-placed); the cap stays open for future providers.
15300
- *
15301
- * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15302
- * `apiKey` is a password field — providers REDACT it on read and merge on
15303
- * write; a stored key NEVER round-trips to a client.
15304
- */
15305
- var LlmProfileKindSchema = _enum([
15306
- "openai-compatible",
15307
- "openai",
15308
- "anthropic",
15309
- "google",
15310
- "managed-local"
15311
- ]);
15312
- var LlmProfileSchema = object({
15313
- id: string(),
15577
+ }), method(object({
15314
15578
  name: string(),
15315
- kind: LlmProfileKindSchema,
15316
- /** Stamped by the provider — keeps the fanned catalog routable. */
15579
+ cascade: boolean().optional()
15580
+ }), _void(), {
15581
+ kind: "mutation",
15582
+ auth: "admin"
15583
+ }), method(object({
15584
+ deviceId: number(),
15585
+ disabled: boolean()
15586
+ }), _void(), {
15587
+ kind: "mutation",
15588
+ auth: "admin"
15589
+ }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
15590
+ mode: DeviceLinkModeSchema,
15591
+ devices: array(LinkedDeviceSchema)
15592
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
15593
+ deviceId: number(),
15594
+ values: record(string(), unknown())
15595
+ }), object({ success: literal(true) }), {
15596
+ kind: "mutation",
15597
+ auth: "admin"
15598
+ }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
15599
+ kind: "mutation",
15600
+ auth: "admin"
15601
+ }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
15602
+ kind: "mutation",
15603
+ auth: "admin"
15604
+ }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
15605
+ kind: "mutation",
15606
+ auth: "admin"
15607
+ }), method(object({ integrationId: string() }), object({ removed: number() }), {
15608
+ kind: "mutation",
15609
+ auth: "admin"
15610
+ }), method(object({ deviceId: number() }), record(string(), string().nullable())), method(object({
15611
+ deviceId: number(),
15612
+ profileMap: record(string(), string().nullable())
15613
+ }), object({ success: literal(true) }), {
15614
+ kind: "mutation",
15615
+ auth: "admin"
15616
+ }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
15617
+ kind: "mutation",
15618
+ auth: "admin"
15619
+ }), method(object({ deviceId: number() }), object({
15620
+ deviceId: number(),
15621
+ entries: array(object({
15622
+ capName: string(),
15623
+ kind: _enum([
15624
+ "native",
15625
+ "wrapped",
15626
+ "linked"
15627
+ ]),
15628
+ providerAddonId: string(),
15629
+ providerNodeId: string(),
15630
+ nativeAddonId: string()
15631
+ }))
15632
+ })), method(object({}), array(object({
15633
+ deviceId: number(),
15634
+ entries: array(object({
15635
+ capName: string(),
15636
+ kind: _enum([
15637
+ "native",
15638
+ "wrapped",
15639
+ "linked"
15640
+ ]),
15641
+ providerAddonId: string(),
15642
+ providerNodeId: string(),
15643
+ nativeAddonId: string()
15644
+ }))
15645
+ }))), method(object({
15646
+ deviceId: number(),
15647
+ capName: string(),
15648
+ wrapperAddonId: string(),
15649
+ active: boolean()
15650
+ }), _void(), {
15651
+ kind: "mutation",
15652
+ auth: "admin"
15653
+ }), method(object({ capName: string() }), array(string())), method(object({ deviceType: string() }), array(object({
15654
+ capName: string(),
15655
+ wrappers: array(string())
15656
+ }))), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), object({
15657
+ settings: SettingsSchemaWithValuesSchema.nullable(),
15658
+ live: SettingsSchemaWithValuesSchema.nullable()
15659
+ })), method(object({
15660
+ deviceId: number().int().nonnegative(),
15661
+ action: string().min(1),
15662
+ input: unknown()
15663
+ }), unknown(), { kind: "mutation" }), method(object({
15664
+ deviceId: number(),
15665
+ writerCapName: string(),
15666
+ writerAddonId: string(),
15667
+ key: string(),
15668
+ value: unknown()
15669
+ }), object({ success: literal(true) }), {
15670
+ kind: "mutation",
15671
+ auth: "admin"
15672
+ }), method(object({
15673
+ deviceId: number(),
15674
+ changes: array(object({
15675
+ writerCapName: string(),
15676
+ writerAddonId: string(),
15677
+ key: string(),
15678
+ value: unknown()
15679
+ }))
15680
+ }), object({
15681
+ success: literal(true),
15682
+ failures: array(object({
15683
+ writerCapName: string(),
15684
+ writerAddonId: string(),
15685
+ error: string()
15686
+ }))
15687
+ }), {
15688
+ kind: "mutation",
15689
+ auth: "admin"
15690
+ }), method(object({ addonId: string() }), array(DiscoveryCandidateSchema), {
15691
+ kind: "mutation",
15692
+ auth: "admin"
15693
+ }), method(object({
15317
15694
  addonId: string(),
15318
- enabled: boolean(),
15319
- /** Vendor model id, or the managed runtime's loaded model. */
15320
- model: string(),
15321
- /** Required for openai-compatible; override for cloud kinds. */
15322
- baseUrl: string().optional(),
15323
- /** ConfigUISchema type:'password' — never round-trips (spec §5). */
15324
- apiKey: string().optional(),
15325
- supportsVision: boolean(),
15326
- temperature: number().min(0).max(2).optional(),
15327
- maxTokens: number().int().positive().optional(),
15328
- timeoutMs: number().int().positive().default(6e4),
15329
- extraHeaders: record(string(), string()).optional(),
15330
- /** kind === 'managed-local' only (spec §4). */
15331
- runtime: ManagedRuntimeConfigSchema.optional()
15332
- });
15333
- /** ConfigUISchema tree passed through untyped on the wire (the
15334
- * notification-output `ConfigSchemaPassthrough` precedent at
15335
- * notification-output.cap.ts:151); the exported TS type re-tightens it. */
15336
- var ConfigSchemaPassthrough$1 = unknown();
15337
- var LlmProfileKindDescriptorSchema = object({
15338
- kind: LlmProfileKindSchema,
15339
- label: string(),
15340
- icon: string(),
15341
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
15695
+ candidate: DiscoveryCandidateSchema,
15696
+ /** Owning integration id, stamped onto the new device's meta by the
15697
+ * device-manager forwarder so `removeByIntegration` can cascade it.
15698
+ * Optional for back-compat (omitted = no stamp = pre-existing behavior). */
15699
+ integrationId: string().optional()
15700
+ }), DeviceSummarySchema, {
15701
+ kind: "mutation",
15702
+ auth: "admin"
15703
+ }), method(object({
15342
15704
  addonId: string(),
15343
- configSchema: ConfigSchemaPassthrough$1
15344
- });
15345
- var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
15346
- var LlmDefaultSchema = object({
15347
- selector: LlmDefaultSelectorSchema,
15348
- profileId: string()
15349
- });
15350
- /** Server-side rollup row getUsage never dumps raw call rows (spec §6). */
15351
- var LlmUsageRollupSchema = object({
15352
- day: string(),
15353
- consumer: string(),
15354
- profileId: string(),
15355
- calls: number(),
15356
- okCalls: number(),
15357
- errorCalls: number(),
15358
- inputTokens: number(),
15359
- outputTokens: number(),
15360
- avgLatencyMs: number()
15361
- });
15362
- /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
15363
- var ManagedModelCatalogEntrySchema = object({
15364
- id: string(),
15365
- label: string(),
15366
- family: string(),
15367
- purpose: _enum(["text", "vision"]),
15368
- url: string(),
15369
- sha256: string(),
15370
- sizeBytes: number(),
15371
- quantization: string(),
15372
- /** Load-time guidance shown in the picker. */
15373
- minRamBytes: number(),
15374
- contextSizeDefault: number().int(),
15375
- /** Vision models: companion projector file. */
15376
- mmprojUrl: string().optional()
15377
- });
15378
- var LlmRuntimeNodeSchema = object({
15379
- nodeId: string(),
15380
- reachable: boolean(),
15381
- status: LlmRuntimeStatusSchema.optional(),
15382
- disk: LlmRuntimeDiskUsageSchema.optional(),
15383
- error: string().optional()
15384
- });
15385
- var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15386
- var ProfileRefInputSchema = object({
15705
+ type: _enum(DeviceType)
15706
+ }), unknown().nullable()), method(object({
15707
+ addonId: string(),
15708
+ type: _enum(DeviceType),
15709
+ config: record(string(), unknown()),
15710
+ /** Owning integration id, stamped onto the new device's meta by the
15711
+ * device-manager forwarder so `removeByIntegration` can cascade it.
15712
+ * Optional for back-compat (omitted = no stamp = pre-existing behavior). */
15713
+ integrationId: string().optional()
15714
+ }), DeviceSummarySchema, {
15715
+ kind: "mutation",
15716
+ auth: "admin"
15717
+ }), method(object({
15387
15718
  addonId: string(),
15388
- profileId: string()
15389
- });
15390
- method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
15719
+ type: _enum(DeviceType),
15720
+ key: string(),
15721
+ value: unknown(),
15722
+ formValues: record(string(), unknown()).optional()
15723
+ }), FieldProbeResultSchema, {
15391
15724
  kind: "mutation",
15392
15725
  auth: "admin"
15393
- }), method(ProfileRefInputSchema, _void(), {
15726
+ }), method(object({
15727
+ addonId: string(),
15728
+ integrationId: string()
15729
+ }), object({ filters: array(AdoptionFilterSchema) }), { auth: "admin" }), method(ListCandidatesInputSchema.extend({ addonId: string() }), ListCandidatesOutputSchema, { auth: "admin" }), method(object({
15730
+ addonId: string(),
15731
+ integrationId: string()
15732
+ }), AdoptionStatusSchema, {
15394
15733
  kind: "mutation",
15395
15734
  auth: "admin"
15396
- }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
15735
+ }), method(AdoptInputSchema.extend({ addonId: string() }), AdoptResultSchema, {
15397
15736
  kind: "mutation",
15398
15737
  auth: "admin"
15399
- }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
15400
- selector: LlmDefaultSelectorSchema,
15401
- profileId: string().nullable()
15402
- }), _void(), {
15738
+ }), method(ReleaseInputSchema.extend({ addonId: string() }), _void(), {
15403
15739
  kind: "mutation",
15404
15740
  auth: "admin"
15405
- }), method(object({
15406
- since: number().optional(),
15407
- until: number().optional(),
15408
- consumer: string().optional(),
15409
- profileId: string().optional()
15410
- }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
15411
- nodeId: string(),
15412
- model: ManagedModelRefSchema
15413
- }), _void(), {
15741
+ }), method(ResyncInputSchema, ResyncResultSchema, {
15742
+ kind: "mutation",
15743
+ auth: "admin"
15744
+ }), method(object({}), object({ providers: array(object({
15745
+ addonId: string(),
15746
+ label: string()
15747
+ })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
15748
+ addonId: string(),
15749
+ label: string(),
15750
+ candidates: array(DiscoveryCandidateSchema).readonly(),
15751
+ error: string().nullable()
15752
+ })).readonly() }), {
15414
15753
  kind: "mutation",
15415
15754
  auth: "admin"
15416
15755
  }), method(object({
15417
- nodeId: string(),
15418
- file: string()
15419
- }), _void(), {
15756
+ addonId: string(),
15757
+ params: record(string(), unknown()).optional()
15758
+ }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
15420
15759
  kind: "mutation",
15421
15760
  auth: "admin"
15422
- }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
15761
+ }), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
15762
+ deviceId: number(),
15763
+ key: string(),
15764
+ value: unknown()
15765
+ }), FieldProbeResultSchema, {
15423
15766
  kind: "mutation",
15424
15767
  auth: "admin"
15425
- }), method(ProfileRefInputSchema, _void(), {
15768
+ }), method(object({
15769
+ deviceId: number(),
15770
+ caps: array(string()).readonly().optional()
15771
+ }), record(string(), unknown().nullable()));
15772
+ method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
15773
+ deviceId: number(),
15774
+ capName: string()
15775
+ }), record(string(), unknown()).nullable()), method(object({}), record(string(), record(string(), record(string(), unknown())))), method(object({
15776
+ deviceId: number(),
15777
+ capName: string(),
15778
+ slice: record(string(), unknown())
15779
+ }), _void(), { kind: "mutation" }), object({
15780
+ deviceId: number(),
15781
+ capName: string(),
15782
+ slice: record(string(), unknown())
15783
+ });
15784
+ /**
15785
+ * Embedding output. `embedding` is wire-encoded as `number[]` so the
15786
+ * Zod-validated tRPC surface round-trips cleanly; consumers that need a
15787
+ * `Float32Array` can wrap it on the way out (in-process, no marshalling
15788
+ * is involved). `inferenceMs` mirrors the runtime field used by the
15789
+ * post-analysis enrichment-engine.
15790
+ */
15791
+ var EmbeddingResultSchema = object({
15792
+ embedding: array(number()),
15793
+ inferenceMs: number()
15794
+ });
15795
+ var EmbeddingInfoSchema = object({
15796
+ modelId: string(),
15797
+ embeddingDim: number(),
15798
+ ready: boolean()
15799
+ });
15800
+ method(object({
15801
+ crop: _instanceof(Uint8Array),
15802
+ width: number(),
15803
+ height: number()
15804
+ }), EmbeddingResultSchema), method(object({ text: string() }), EmbeddingResultSchema), method(_void(), EmbeddingInfoSchema);
15805
+ /**
15806
+ * filesystem-browse — per-node capability for browsing the node's local
15807
+ * filesystem, sandboxed to operator-configured allowed roots. Used by the
15808
+ * admin "Add filesystem location" flow to pick a node + path. `mode:'per-node'`
15809
+ * (one provider per node); the hub calls it with `{nodeId}` so the codegen
15810
+ * routes to that exact node (default `nodeIdMode:'routing'`).
15811
+ */
15812
+ var DirEntrySchema = object({
15813
+ name: string(),
15814
+ path: string()
15815
+ });
15816
+ var BrowseResultSchema = object({
15817
+ path: string(),
15818
+ entries: array(DirEntrySchema).readonly(),
15819
+ freeBytes: number(),
15820
+ totalBytes: number()
15821
+ });
15822
+ method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({ path: string() }), BrowseResultSchema, { auth: "admin" }), method(object({ path: string() }), object({ path: string() }), {
15426
15823
  kind: "mutation",
15427
15824
  auth: "admin"
15428
15825
  });
15429
- var LogLevelSchema = _enum([
15430
- "debug",
15431
- "info",
15432
- "warn",
15433
- "error"
15826
+ /**
15827
+ * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15828
+ * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15829
+ * caps stay wire-compatible without a circular cap→cap import.
15830
+ *
15831
+ * Errors are a discriminated-union RESULT, never thrown: the shape survives
15832
+ * every transport tier structurally, and failed calls still write usage rows.
15833
+ * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15834
+ */
15835
+ var LlmUsageSchema = object({
15836
+ inputTokens: number(),
15837
+ outputTokens: number()
15838
+ });
15839
+ var LlmErrorCodeSchema = _enum([
15840
+ "timeout",
15841
+ "rate-limited",
15842
+ "auth",
15843
+ "refusal",
15844
+ "bad-request",
15845
+ "unavailable",
15846
+ "no-profile",
15847
+ "budget-exceeded",
15848
+ "adapter-error"
15434
15849
  ]);
15435
- var LogEntrySchema = object({
15436
- timestamp: date(),
15437
- level: LogLevelSchema,
15438
- scope: array(string()),
15850
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15851
+ ok: literal(true),
15852
+ text: string(),
15853
+ model: string(),
15854
+ usage: LlmUsageSchema,
15855
+ truncated: boolean(),
15856
+ latencyMs: number()
15857
+ }), object({
15858
+ ok: literal(false),
15859
+ code: LlmErrorCodeSchema,
15439
15860
  message: string(),
15440
- meta: record(string(), unknown()).optional(),
15441
- tags: record(string(), string()).optional()
15442
- });
15443
- method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
15444
- scope: array(string()).optional(),
15445
- level: LogLevelSchema.optional(),
15446
- since: date().optional(),
15447
- until: date().optional(),
15448
- limit: number().optional(),
15449
- tags: record(string(), string()).optional()
15450
- }), array(LogEntrySchema).readonly());
15861
+ retryAfterMs: number().optional()
15862
+ })]);
15451
15863
  /**
15452
- * `login-method` collection cap through which auth addons contribute
15453
- * their pre-auth login surfaces to the login page. This is the SINGLE,
15454
- * generic mechanism that supersedes the dead `auth.listProviders` reader:
15455
- * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
15456
- * `login-method` provider and the PUBLIC `auth.listLoginMethods`
15457
- * procedure aggregates them for the unauthenticated login page.
15458
- *
15459
- * A contribution is a discriminated union on `kind`:
15460
- *
15461
- * - `redirect` — a declarative button. The login page renders a generic
15462
- * button that navigates to `startUrl` (an addon-owned HTTP route).
15463
- * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
15464
- * ZERO shell-side JS. A future SSO addon plugs in the same way — the
15465
- * login page needs NO change.
15466
- *
15467
- * - `widget` — a Module-Federation widget the login page mounts (via
15468
- * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
15469
- * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
15470
- * mechanism kept for future use; no shipped addon uses it on the login
15471
- * page (the passkey ceremony below runs natively in the shell instead).
15472
- *
15473
- * - `passkey` — a declarative WebAuthn ceremony the shell renders
15474
- * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
15475
- * a remotely-loaded bundle). Carries the addon's effective `rpId` /
15476
- * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
15477
- * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
15478
- * fetching any remote code pre-auth. Contribution stays unconditional —
15479
- * enrollment state is never leaked pre-auth; visibility is a shell
15480
- * decision.
15481
- *
15482
- * Every contribution carries a `stage`:
15483
- * - `primary` — shown on the first credentials screen (OIDC /
15484
- * magic-link buttons; a future usernameless passkey).
15485
- * - `second-factor` — shown AFTER the password leg, gated on the
15486
- * returned `factors` (passkey-as-2FA today).
15487
- *
15488
- * `mount: skip` — the cap is read server-side by the core auth router
15489
- * (`registry.getCollection('login-method')`), never mounted as its own
15490
- * tRPC router.
15864
+ * `Uint8Array` is the sanctioned binary convention superjson + the UDS
15865
+ * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15866
+ * notification-output.cap.ts:27-31 precedents).
15491
15867
  */
15492
- /** When a login method renders in the two-phase login flow. */
15493
- var LoginStageEnum = _enum(["primary", "second-factor"]);
15494
- /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
15495
- var LoginMethodContributionSchema = discriminatedUnion("kind", [
15496
- object({
15497
- kind: literal("redirect"),
15498
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
15499
- id: string(),
15500
- /** Operator-facing button label. */
15501
- label: string(),
15502
- /** lucide-react icon name. */
15503
- icon: string().optional(),
15504
- /** Addon-owned HTTP route the button navigates to (GET). */
15505
- startUrl: string(),
15506
- stage: LoginStageEnum
15868
+ var LlmImageSchema = object({
15869
+ bytes: _instanceof(Uint8Array),
15870
+ mimeType: string()
15871
+ });
15872
+ var LlmGenerateBaseInputSchema = object({
15873
+ /** Collection routing (the notification-output posture). */
15874
+ addonId: string().optional(),
15875
+ /** Explicit profile; else the resolution chain (spec §3). */
15876
+ profileId: string().optional(),
15877
+ /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15878
+ consumer: string(),
15879
+ system: string().optional(),
15880
+ /** v1: single-turn. `messages[]` is a v2 additive field. */
15881
+ prompt: string(),
15882
+ /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15883
+ jsonSchema: record(string(), unknown()).optional(),
15884
+ /** Per-call override of the profile default. */
15885
+ maxTokens: number().int().positive().optional(),
15886
+ temperature: number().optional()
15887
+ });
15888
+ /**
15889
+ * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
15890
+ * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15891
+ * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
15892
+ * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15893
+ * this only through the `llm` cap's methods.
15894
+ *
15895
+ * One running llama-server child per node in v1 (models are RAM-heavy).
15896
+ * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15897
+ * watchdog — operator decision #3).
15898
+ */
15899
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
15900
+ object({
15901
+ kind: literal("catalog"),
15902
+ catalogId: string()
15507
15903
  }),
15508
15904
  object({
15509
- kind: literal("widget"),
15510
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
15511
- id: string(),
15512
- /** Owning addon id — drives the public bundle URL + the MF namespace. */
15513
- addonId: string(),
15514
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
15515
- bundle: string(),
15516
- /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
15517
- remote: WidgetRemoteSchema,
15518
- stage: LoginStageEnum
15905
+ kind: literal("url"),
15906
+ url: string(),
15907
+ sha256: string().optional()
15519
15908
  }),
15520
15909
  object({
15521
- kind: literal("passkey"),
15522
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
15523
- id: string(),
15524
- /** Operator-facing button label. */
15525
- label: string(),
15526
- stage: LoginStageEnum,
15527
- /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
15528
- rpId: string(),
15529
- /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
15530
- origin: string().nullable()
15910
+ kind: literal("path"),
15911
+ path: string()
15531
15912
  })
15532
15913
  ]);
15533
- method(_void(), array(LoginMethodContributionSchema).readonly());
15534
- var CpuBreakdownSchema = object({
15535
- total: number(),
15536
- user: number(),
15537
- system: number(),
15538
- irq: number(),
15539
- nice: number(),
15540
- loadAvg: tuple([
15541
- number(),
15542
- number(),
15543
- number()
15544
- ]),
15545
- cores: number()
15546
- });
15547
- var MemoryInfoSchema = object({
15548
- percent: number(),
15549
- totalBytes: number(),
15550
- usedBytes: number(),
15551
- availableBytes: number(),
15552
- swapUsedBytes: number(),
15553
- swapTotalBytes: number()
15554
- });
15555
- var DiskIoSnapshotSchema = object({
15556
- readBytes: number(),
15557
- writeBytes: number(),
15558
- readOps: number(),
15559
- writeOps: number(),
15560
- timestampMs: number()
15561
- });
15562
- var NetworkIoSnapshotSchema = object({
15563
- rxBytes: number(),
15564
- txBytes: number(),
15565
- rxPackets: number(),
15566
- txPackets: number(),
15567
- rxErrors: number(),
15568
- txErrors: number(),
15569
- timestampMs: number()
15570
- });
15571
- var MetricsGpuInfoSchema = object({
15572
- utilization: number(),
15573
- model: string(),
15574
- memoryUsedBytes: number(),
15575
- memoryTotalBytes: number(),
15576
- temperature: number().nullable()
15577
- });
15578
- var ProcessResourceInfoSchema = object({
15579
- openFds: number(),
15580
- threadCount: number(),
15581
- activeHandles: number(),
15582
- activeRequests: number()
15583
- });
15584
- var PressureAvgsSchema = object({
15585
- avg10: number(),
15586
- avg60: number(),
15587
- avg300: number()
15588
- });
15589
- var PressureInfoSchema = object({
15590
- some: PressureAvgsSchema,
15591
- full: PressureAvgsSchema.nullable()
15592
- });
15593
- var SystemResourceSnapshotSchema = object({
15594
- cpu: CpuBreakdownSchema,
15595
- memory: MemoryInfoSchema,
15596
- gpu: MetricsGpuInfoSchema.nullable(),
15597
- network: NetworkIoSnapshotSchema,
15598
- disk: DiskIoSnapshotSchema,
15599
- pressure: object({
15600
- cpu: PressureInfoSchema.nullable(),
15601
- memory: PressureInfoSchema.nullable(),
15602
- io: PressureInfoSchema.nullable()
15603
- }),
15604
- process: ProcessResourceInfoSchema,
15605
- cpuTemperature: number().nullable(),
15606
- timestampMs: number()
15607
- });
15608
- var DiskSpaceInfoSchema = object({
15609
- path: string(),
15610
- totalBytes: number(),
15611
- usedBytes: number(),
15612
- availableBytes: number(),
15613
- percent: number()
15614
- });
15615
- var PidResourceStatsSchema = object({
15616
- pid: number(),
15617
- cpu: number(),
15618
- memory: number(),
15619
- /**
15620
- * Private (anonymous) resident bytes — the per-process V8 heap + native
15621
- * allocations NOT shared with other processes (Linux RssAnon). This is the
15622
- * "real" per-runner cost; summing it across runners is meaningful, unlike
15623
- * `memory` (RSS), which double-counts the shared mmap'd framework code.
15624
- * Undefined where /proc is unavailable (e.g. macOS).
15625
- */
15626
- privateBytes: number().optional(),
15627
- /**
15628
- * Shared file-backed resident bytes (Linux RssFile) — mmap'd framework/lib
15629
- * code shared copy-on-write across runners. Undefined on macOS.
15630
- */
15631
- sharedBytes: number().optional()
15914
+ var ManagedRuntimeConfigSchema = object({
15915
+ /** WHERE the runtime lives — hub or any agent. */
15916
+ nodeId: string(),
15917
+ /** Closed for v1; 'ollama' is a v2 candidate. */
15918
+ engine: _enum(["llama-cpp"]),
15919
+ model: ManagedModelRefSchema,
15920
+ contextSize: number().int().default(4096),
15921
+ /** 0 = CPU-only. */
15922
+ gpuLayers: number().int().default(0),
15923
+ /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15924
+ threads: number().int().optional(),
15925
+ /** Concurrent slots. */
15926
+ parallel: number().int().default(1),
15927
+ /** Else lazy: first generate boots it. */
15928
+ autoStart: boolean().default(false),
15929
+ /** 0 = never; frees RAM after quiet periods. */
15930
+ idleStopMinutes: number().int().default(30)
15632
15931
  });
15633
- var AddonInstanceSchema = object({
15634
- addonId: string(),
15932
+ var LlmRuntimeStatusSchema = object({
15933
+ /** Status is ALWAYS node-qualified. */
15635
15934
  nodeId: string(),
15636
- role: _enum(["hub", "worker"]),
15637
- pid: number(),
15638
15935
  state: _enum([
15639
- "starting",
15640
- "running",
15641
- "stopping",
15642
15936
  "stopped",
15643
- "crashed"
15644
- ]),
15645
- uptimeSec: number()
15646
- });
15647
- var NodeProcessSchema = object({
15648
- pid: number(),
15649
- ppid: number(),
15650
- pgid: number(),
15651
- classification: _enum([
15652
- "root",
15653
- "managed",
15654
- "system",
15655
- "ghost"
15937
+ "downloading",
15938
+ "starting",
15939
+ "ready",
15940
+ "crashed",
15941
+ "failed"
15656
15942
  ]),
15657
- /** `$process` addon binding when `managed`, else null. */
15658
- addonId: string().nullable(),
15659
- /** Kernel-reported nodeId when the process is a known agent/worker. */
15660
- nodeId: string().nullable(),
15661
- /** Truncated command line. */
15662
- command: string(),
15663
- cpuPercent: number(),
15664
- memoryRssBytes: number(),
15665
- /** Wall-clock uptime (seconds). Parsed from `ps etime`. */
15666
- uptimeSec: number(),
15667
- /** True when ancestor walk reaches `ppid=1` (reparented to init/launchd). */
15668
- orphaned: boolean()
15669
- });
15670
- var KillProcessInputSchema = object({
15671
- pid: number(),
15672
- /** Force = SIGKILL. Default is SIGTERM. */
15673
- force: boolean().optional()
15674
- });
15675
- var KillProcessResultSchema = object({
15676
- success: boolean(),
15677
- reason: string().optional(),
15678
- signal: _enum(["SIGTERM", "SIGKILL"]).optional()
15679
- });
15680
- var DumpHeapSnapshotInputSchema = object({
15681
- /** The addon whose runner should dump a heap snapshot. */
15682
- addonId: string() });
15683
- var DumpHeapSnapshotResultSchema = object({
15684
- success: boolean(),
15685
- /** Path of the written .heapsnapshot inside the runner's container/host. */
15686
- path: string().optional(),
15687
- /** Process pid that was signalled. */
15688
15943
  pid: number().optional(),
15689
- reason: string().optional()
15944
+ port: number().optional(),
15945
+ modelPath: string().optional(),
15946
+ modelId: string().optional(),
15947
+ downloadProgress: number().min(0).max(1).optional(),
15948
+ lastError: string().optional(),
15949
+ crashesInWindow: number(),
15950
+ /** Child RSS (sampled best-effort). */
15951
+ memoryBytes: number().optional(),
15952
+ vramBytes: number().optional()
15690
15953
  });
15691
- var SystemMetricsSchema = object({
15692
- cpuPercent: number(),
15693
- memoryPercent: number(),
15694
- memoryUsedMB: number(),
15695
- memoryTotalMB: number(),
15696
- diskPercent: number().optional(),
15697
- temperature: number().optional(),
15698
- gpuPercent: number().optional(),
15699
- gpuMemoryPercent: number().optional()
15954
+ var LlmNodeModelSchema = object({
15955
+ file: string(),
15956
+ sizeBytes: number(),
15957
+ catalogId: string().optional(),
15958
+ installedAt: number().optional()
15700
15959
  });
15701
- method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(KillProcessInputSchema, KillProcessResultSchema, {
15960
+ var LlmRuntimeDiskUsageSchema = object({
15961
+ nodeId: string(),
15962
+ modelsBytes: number(),
15963
+ freeBytes: number().optional()
15964
+ });
15965
+ method(LlmGenerateBaseInputSchema.extend({
15966
+ images: array(LlmImageSchema).optional(),
15967
+ runtime: ManagedRuntimeConfigSchema,
15968
+ /** The managed profile's timeout, threaded by the hub provider. */
15969
+ timeoutMs: number().int().positive().optional()
15970
+ }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15702
15971
  kind: "mutation",
15703
15972
  auth: "admin"
15704
- }), method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
15973
+ }), method(object({}), _void(), {
15705
15974
  kind: "mutation",
15706
15975
  auth: "admin"
15707
- });
15708
- method(object({
15709
- sourceUrl: string(),
15710
- metadata: ModelConvertMetadataSchema,
15711
- targets: array(ConvertTargetSchema).min(1).readonly(),
15712
- calibrationRef: string().optional(),
15713
- sessionId: string().optional()
15714
- }), ConvertResultSchema, {
15976
+ }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15715
15977
  kind: "mutation",
15716
- auth: "admin",
15717
- timeoutMs: 6e5
15718
- });
15719
- method(object({
15720
- nodeId: string(),
15721
- modelId: string(),
15722
- format: _enum(MODEL_FORMATS),
15723
- entry: ModelCatalogEntrySchema
15724
- }), object({
15725
- ok: boolean(),
15726
- /** sha256 of the staged tarball (empty for a hub-local no-op). */
15727
- sha256: string(),
15728
- bytes: number(),
15729
- /** The target node's modelsDir the artifact landed in. */
15730
- path: string()
15731
- }), {
15978
+ auth: "admin"
15979
+ }), method(object({ file: string() }), _void(), {
15732
15980
  kind: "mutation",
15733
15981
  auth: "admin"
15734
- });
15735
- /**
15736
- * `mqtt-broker` — broker-registry cap.
15737
- *
15738
- * NOT a pub/sub proxy. The cap exposes (a) a registry of configured
15739
- * MQTT brokers (external + optionally an embedded `aedes`-backed one)
15740
- * and (b) the connection details a consumer addon needs to spin up
15741
- * its OWN `mqtt.js` client.
15742
- *
15743
- * Why: pub/sub routing over the system event-bus loses fidelity
15744
- * (callback shape, QoS guarantees, will/retain semantics) and adds
15745
- * refcount bookkeeping that addons would rather own themselves. The
15746
- * canonical consumer (`addon-export-ha-mqtt`) needs raw `mqtt.js`
15747
- * features anyway — give it the connection config, get out of the way.
15748
- *
15749
- * Consumer flow:
15750
- * const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
15751
- * const client = mqtt.connect(cfg.url, { username: cfg.username, … })
15752
- * client.subscribe('zigbee2mqtt/+')
15753
- *
15754
- * Collection mode: multiple brokers (e.g. one local mosquitto + one
15755
- * cloud bridge). The "embedded" entry (when present) is just another
15756
- * broker in the registry — its lifecycle is owned by the addon that
15757
- * spawned it.
15758
- */
15759
- var BrokerKindSchema = _enum(["external", "embedded"]);
15982
+ }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15760
15983
  /**
15761
- * Broker live-probe status.
15984
+ * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15985
+ * methods concat-fan across providers; single-row methods route to ONE
15986
+ * provider by the `addonId` in the call input (the notification-output
15987
+ * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15988
+ * (hub-placed); the cap stays open for future providers.
15762
15989
  *
15763
- * - `connected` last probe completed a clean CONNACK
15764
- * - `disconnected` — no probe has run yet (cold cache)
15765
- * - `auth-failed` CONNACK refused with auth error (RC 4 / 5)
15766
- * - `unreachable` — TCP connect timed out / refused
15767
- * - `tls-error` — TLS handshake failed (cert / SNI / cipher)
15990
+ * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15991
+ * `apiKey` is a password field providers REDACT it on read and merge on
15992
+ * write; a stored key NEVER round-trips to a client.
15768
15993
  */
15769
- var BrokerStatusSchema$1 = _enum([
15770
- "connected",
15771
- "disconnected",
15772
- "auth-failed",
15773
- "unreachable",
15774
- "tls-error"
15994
+ var LlmProfileKindSchema = _enum([
15995
+ "openai-compatible",
15996
+ "openai",
15997
+ "anthropic",
15998
+ "google",
15999
+ "managed-local"
15775
16000
  ]);
15776
- var BrokerInfoSchema = object({
16001
+ var LlmProfileSchema = object({
15777
16002
  id: string(),
15778
16003
  name: string(),
15779
- url: string(),
15780
- kind: BrokerKindSchema,
15781
- status: BrokerStatusSchema$1,
15782
- latencyMs: number().nullable(),
15783
- error: string().optional(),
15784
- /** Embedded brokers only: number of MQTT clients currently connected. */
15785
- connectedClients: number().int().nonnegative().optional(),
15786
- /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
15787
- lastCheckedAt: number().optional()
16004
+ kind: LlmProfileKindSchema,
16005
+ /** Stamped by the provider — keeps the fanned catalog routable. */
16006
+ addonId: string(),
16007
+ enabled: boolean(),
16008
+ /** Vendor model id, or the managed runtime's loaded model. */
16009
+ model: string(),
16010
+ /** Required for openai-compatible; override for cloud kinds. */
16011
+ baseUrl: string().optional(),
16012
+ /** ConfigUISchema type:'password' — never round-trips (spec §5). */
16013
+ apiKey: string().optional(),
16014
+ supportsVision: boolean(),
16015
+ temperature: number().min(0).max(2).optional(),
16016
+ maxTokens: number().int().positive().optional(),
16017
+ timeoutMs: number().int().positive().default(6e4),
16018
+ extraHeaders: record(string(), string()).optional(),
16019
+ /** kind === 'managed-local' only (spec §4). */
16020
+ runtime: ManagedRuntimeConfigSchema.optional()
15788
16021
  });
15789
- /**
15790
- * Connection details — what a consumer needs to call
15791
- * `mqtt.connect(url, options)`. We split URL + credentials so the
15792
- * consumer can pass them as `mqtt.connect(url, { username, password })`
15793
- * instead of stuffing creds into the URL (which leaks them into logs).
15794
- */
15795
- var BrokerConnectionDetailsSchema = object({
15796
- url: string(),
15797
- username: string().optional(),
15798
- password: string().optional(),
15799
- /**
15800
- * Suggested prefix for `clientId`. Each consumer should suffix this
15801
- * with its own discriminator (addon id, instance id) so reconnects
15802
- * don't kick each other off (MQTT spec: clientId must be unique per
15803
- * broker).
15804
- */
15805
- clientIdPrefix: string().optional()
16022
+ /** ConfigUISchema tree passed through untyped on the wire (the
16023
+ * notification-output `ConfigSchemaPassthrough` precedent at
16024
+ * notification-output.cap.ts:151); the exported TS type re-tightens it. */
16025
+ var ConfigSchemaPassthrough$1 = unknown();
16026
+ var LlmProfileKindDescriptorSchema = object({
16027
+ kind: LlmProfileKindSchema,
16028
+ label: string(),
16029
+ icon: string(),
16030
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16031
+ addonId: string(),
16032
+ configSchema: ConfigSchemaPassthrough$1
15806
16033
  });
15807
- var AddBrokerInputSchema = object({
15808
- name: string().min(1),
15809
- url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
15810
- username: string().optional(),
15811
- password: string().optional(),
15812
- clientIdPrefix: string().optional()
16034
+ var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
16035
+ var LlmDefaultSchema = object({
16036
+ selector: LlmDefaultSelectorSchema,
16037
+ profileId: string()
15813
16038
  });
15814
- var AddBrokerResultSchema = object({ id: string() });
15815
- var IdInputSchema = object({ id: string() });
15816
- var TestResultSchema$1 = discriminatedUnion("ok", [object({
15817
- ok: literal(true),
15818
- latencyMs: number()
15819
- }), object({
15820
- ok: literal(false),
15821
- error: string()
15822
- })]);
15823
- var StartEmbeddedInputSchema = object({
15824
- port: number().int().min(1).max(65535).default(1883),
15825
- /** Allow anonymous connect (no username/password). Default: false. */
15826
- allowAnonymous: boolean().default(false),
15827
- /** Optional shared username/password for clients. */
15828
- username: string().optional(),
15829
- password: string().optional()
16039
+ /** Server-side rollup row getUsage never dumps raw call rows (spec §6). */
16040
+ var LlmUsageRollupSchema = object({
16041
+ day: string(),
16042
+ consumer: string(),
16043
+ profileId: string(),
16044
+ calls: number(),
16045
+ okCalls: number(),
16046
+ errorCalls: number(),
16047
+ inputTokens: number(),
16048
+ outputTokens: number(),
16049
+ avgLatencyMs: number()
15830
16050
  });
15831
- var StartEmbeddedResultSchema = object({
16051
+ /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
16052
+ var ManagedModelCatalogEntrySchema = object({
15832
16053
  id: string(),
15833
- url: string()
15834
- });
15835
- var StatusSchema = object({
15836
- brokerCount: number(),
15837
- embeddedRunning: boolean()
15838
- });
15839
- 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);
15840
- var NetworkEndpointSchema = object({
16054
+ label: string(),
16055
+ family: string(),
16056
+ purpose: _enum(["text", "vision"]),
15841
16057
  url: string(),
15842
- hostname: string(),
15843
- port: number(),
15844
- protocol: _enum(["http", "https"])
16058
+ sha256: string(),
16059
+ sizeBytes: number(),
16060
+ quantization: string(),
16061
+ /** Load-time guidance shown in the picker. */
16062
+ minRamBytes: number(),
16063
+ contextSizeDefault: number().int(),
16064
+ /** Vision models: companion projector file. */
16065
+ mmprojUrl: string().optional()
15845
16066
  });
15846
- var NetworkAccessStatusSchema = object({
15847
- connected: boolean(),
15848
- endpoint: NetworkEndpointSchema.nullable(),
16067
+ var LlmRuntimeNodeSchema = object({
16068
+ nodeId: string(),
16069
+ reachable: boolean(),
16070
+ status: LlmRuntimeStatusSchema.optional(),
16071
+ disk: LlmRuntimeDiskUsageSchema.optional(),
15849
16072
  error: string().optional()
15850
16073
  });
15851
- /**
15852
- * Optional, richer endpoint shape returned by providers that expose
15853
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
15854
- * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
15855
- * the originating provider config (mode + sourcePort) so the
15856
- * orchestrator UI can label rows distinctly. Providers that expose only
15857
- * one endpoint just omit `listEndpoints` from their provider impl.
15858
- */
15859
- var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
15860
- /**
15861
- * Stable id within the provider — typically `<mode>-<sourcePort>` so
15862
- * the orchestrator can dedupe across `listEndpoints` polls.
15863
- */
15864
- id: string(),
15865
- /** Operator-facing label (mirrors `MeshEndpoint.label`). */
15866
- label: string(),
15867
- /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
15868
- mode: string().optional(),
15869
- /** Originating local port the ingress fronts (informational). */
15870
- sourcePort: number().optional()
16074
+ var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
16075
+ var ProfileRefInputSchema = object({
16076
+ addonId: string(),
16077
+ profileId: string()
16078
+ });
16079
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
16080
+ kind: "mutation",
16081
+ auth: "admin"
16082
+ }), method(ProfileRefInputSchema, _void(), {
16083
+ kind: "mutation",
16084
+ auth: "admin"
16085
+ }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
16086
+ kind: "mutation",
16087
+ auth: "admin"
16088
+ }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
16089
+ selector: LlmDefaultSelectorSchema,
16090
+ profileId: string().nullable()
16091
+ }), _void(), {
16092
+ kind: "mutation",
16093
+ auth: "admin"
16094
+ }), method(object({
16095
+ since: number().optional(),
16096
+ until: number().optional(),
16097
+ consumer: string().optional(),
16098
+ profileId: string().optional()
16099
+ }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
16100
+ nodeId: string(),
16101
+ model: ManagedModelRefSchema
16102
+ }), _void(), {
16103
+ kind: "mutation",
16104
+ auth: "admin"
16105
+ }), method(object({
16106
+ nodeId: string(),
16107
+ file: string()
16108
+ }), _void(), {
16109
+ kind: "mutation",
16110
+ auth: "admin"
16111
+ }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
16112
+ kind: "mutation",
16113
+ auth: "admin"
16114
+ }), method(ProfileRefInputSchema, _void(), {
16115
+ kind: "mutation",
16116
+ auth: "admin"
15871
16117
  });
15872
- var networkAccessCapability = {
15873
- name: "network-access",
15874
- scope: "system",
15875
- mode: "collection",
15876
- providerKind: "ingress",
15877
- methods: {
15878
- start: method(_void(), NetworkEndpointSchema, { kind: "mutation" }),
15879
- stop: method(_void(), _void(), { kind: "mutation" }),
15880
- getEndpoint: method(_void(), NetworkEndpointSchema.nullable()),
15881
- getStatus: method(_void(), NetworkAccessStatusSchema),
15882
- /**
15883
- * Enumerate every active ingress entry. Providers that expose only a
15884
- * single endpoint may omit this method; callers fall back to
15885
- * `getEndpoint()` in that case.
15886
- */
15887
- listEndpoints: method(_void(), array(NetworkEndpointEntrySchema).readonly())
15888
- }
15889
- };
16118
+ var LogLevelSchema = _enum([
16119
+ "debug",
16120
+ "info",
16121
+ "warn",
16122
+ "error"
16123
+ ]);
16124
+ var LogEntrySchema = object({
16125
+ timestamp: date(),
16126
+ level: LogLevelSchema,
16127
+ scope: array(string()),
16128
+ message: string(),
16129
+ meta: record(string(), unknown()).optional(),
16130
+ tags: record(string(), string()).optional()
16131
+ });
16132
+ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
16133
+ scope: array(string()).optional(),
16134
+ level: LogLevelSchema.optional(),
16135
+ since: date().optional(),
16136
+ until: date().optional(),
16137
+ limit: number().optional(),
16138
+ tags: record(string(), string()).optional()
16139
+ }), array(LogEntrySchema).readonly());
15890
16140
  /**
15891
- * notification-outputcanonical, capability-gated notification delivery.
16141
+ * `login-method`collection cap through which auth addons contribute
16142
+ * their pre-auth login surfaces to the login page. This is the SINGLE,
16143
+ * generic mechanism that supersedes the dead `auth.listProviders` reader:
16144
+ * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
16145
+ * `login-method` provider and the PUBLIC `auth.listLoginMethods`
16146
+ * procedure aggregates them for the unauthenticated login page.
15892
16147
  *
15893
- * Apprise-derived model (see
15894
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
15895
- * callers emit ONE canonical `Notification`; each provider declares a
15896
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
15897
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
15898
- * message to what the kind supports — callers never special-case a service.
16148
+ * A contribution is a discriminated union on `kind`:
15899
16149
  *
15900
- * DESIGN DECISIONS (locked):
15901
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
15902
- * `setTargetEnabled`), each provider persisting via the `settings-store`
15903
- * cap. Rationale: the admin UI needs one uniform surface across the
15904
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
15905
- * alternative would fork the UI per addon and cannot host the
15906
- * discovery→adopt flow.
15907
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
15908
- * the generated cap-mount auto-`concatCollection`-fans them across every
15909
- * registered provider (notifiers addon + HA addon) so one catalog is
15910
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
15911
- * `addonId` the generated collection router extracts from the call input.
15912
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
15913
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
15914
- * `storage` / `storage-provider` / `recording` caps over the same path. No
15915
- * base64 fallback needed.
16150
+ * - `redirect` a declarative button. The login page renders a generic
16151
+ * button that navigates to `startUrl` (an addon-owned HTTP route).
16152
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
16153
+ * ZERO shell-side JS. A future SSO addon plugs in the same way the
16154
+ * login page needs NO change.
15916
16155
  *
15917
- * TODO (deferred, closed-set change separate decision): add
15918
- * `providerKind: 'notify'` so notification providers surface on the unified
15919
- * admin "Integrations" page.
15920
- */
15921
- /**
15922
- * Zentik-derived typed-media enum — the superset across every kind. Each
15923
- * adapter picks what it supports and the degrade engine filters the rest.
15924
- */
15925
- var AttachmentMediaTypeSchema = _enum([
15926
- "image",
15927
- "video",
15928
- "gif",
15929
- "audio",
15930
- "icon"
15931
- ]);
15932
- /**
15933
- * A single attachment. Exactly one of `url` (remote source, most adapters
15934
- * prefer this) or `bytes` (inline source; required for Pushover-style
15935
- * bytes-only kinds) MUST be present the degrade engine expresses a
15936
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
16156
+ * - `widget` a Module-Federation widget the login page mounts (via
16157
+ * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
16158
+ * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
16159
+ * mechanism kept for future use; no shipped addon uses it on the login
16160
+ * page (the passkey ceremony below runs natively in the shell instead).
16161
+ *
16162
+ * - `passkey` a declarative WebAuthn ceremony the shell renders
16163
+ * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
16164
+ * a remotely-loaded bundle). Carries the addon's effective `rpId` /
16165
+ * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
16166
+ * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
16167
+ * fetching any remote code pre-auth. Contribution stays unconditional —
16168
+ * enrollment state is never leaked pre-auth; visibility is a shell
16169
+ * decision.
16170
+ *
16171
+ * Every contribution carries a `stage`:
16172
+ * - `primary` — shown on the first credentials screen (OIDC /
16173
+ * magic-link buttons; a future usernameless passkey).
16174
+ * - `second-factor` — shown AFTER the password leg, gated on the
16175
+ * returned `factors` (passkey-as-2FA today).
16176
+ *
16177
+ * `mount: skip` — the cap is read server-side by the core auth router
16178
+ * (`registry.getCollection('login-method')`), never mounted as its own
16179
+ * tRPC router.
15937
16180
  */
15938
- var AttachmentSchema = object({
15939
- mediaType: AttachmentMediaTypeSchema,
15940
- url: string().optional(),
15941
- bytes: _instanceof(Uint8Array).optional(),
15942
- mime: string().optional(),
15943
- name: string().optional()
15944
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
15945
- var NotificationFormatSchema = _enum([
15946
- "text",
15947
- "markdown",
15948
- "html"
16181
+ /** When a login method renders in the two-phase login flow. */
16182
+ var LoginStageEnum = _enum(["primary", "second-factor"]);
16183
+ /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
16184
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [
16185
+ object({
16186
+ kind: literal("redirect"),
16187
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
16188
+ id: string(),
16189
+ /** Operator-facing button label. */
16190
+ label: string(),
16191
+ /** lucide-react icon name. */
16192
+ icon: string().optional(),
16193
+ /** Addon-owned HTTP route the button navigates to (GET). */
16194
+ startUrl: string(),
16195
+ stage: LoginStageEnum
16196
+ }),
16197
+ object({
16198
+ kind: literal("widget"),
16199
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
16200
+ id: string(),
16201
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
16202
+ addonId: string(),
16203
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
16204
+ bundle: string(),
16205
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
16206
+ remote: WidgetRemoteSchema,
16207
+ stage: LoginStageEnum
16208
+ }),
16209
+ object({
16210
+ kind: literal("passkey"),
16211
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
16212
+ id: string(),
16213
+ /** Operator-facing button label. */
16214
+ label: string(),
16215
+ stage: LoginStageEnum,
16216
+ /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
16217
+ rpId: string(),
16218
+ /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
16219
+ origin: string().nullable()
16220
+ })
15949
16221
  ]);
15950
- /** A single tap-through action button. */
15951
- var NotificationActionSchema = object({
15952
- id: string(),
15953
- label: string(),
15954
- url: string().optional()
16222
+ method(_void(), array(LoginMethodContributionSchema).readonly());
16223
+ var CpuBreakdownSchema = object({
16224
+ total: number(),
16225
+ user: number(),
16226
+ system: number(),
16227
+ irq: number(),
16228
+ nice: number(),
16229
+ loadAvg: tuple([
16230
+ number(),
16231
+ number(),
16232
+ number()
16233
+ ]),
16234
+ cores: number()
15955
16235
  });
15956
- /**
15957
- * The canonical notification. `body` is the only hard field (Apprise model).
15958
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
15959
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
15960
- * the adapter maps this ordinal onto its native level. `level?` is an
15961
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
15962
- * `priority` for that one target.
15963
- */
15964
- var NotificationSchema = object({
15965
- body: string(),
15966
- title: string().optional(),
15967
- format: NotificationFormatSchema.default("text"),
15968
- priority: number().int().min(1).max(5).default(3),
15969
- level: string().optional(),
15970
- attachments: array(AttachmentSchema).optional(),
15971
- clickUrl: string().optional(),
15972
- actions: array(NotificationActionSchema).optional(),
15973
- sound: string().optional(),
15974
- ttl: number().optional(),
15975
- tag: string().optional(),
15976
- deviceId: number().optional(),
15977
- eventId: string().optional(),
15978
- metadata: record(string(), unknown()).optional()
16236
+ var MemoryInfoSchema = object({
16237
+ percent: number(),
16238
+ totalBytes: number(),
16239
+ usedBytes: number(),
16240
+ availableBytes: number(),
16241
+ swapUsedBytes: number(),
16242
+ swapTotalBytes: number()
16243
+ });
16244
+ var DiskIoSnapshotSchema = object({
16245
+ readBytes: number(),
16246
+ writeBytes: number(),
16247
+ readOps: number(),
16248
+ writeOps: number(),
16249
+ timestampMs: number()
16250
+ });
16251
+ var NetworkIoSnapshotSchema = object({
16252
+ rxBytes: number(),
16253
+ txBytes: number(),
16254
+ rxPackets: number(),
16255
+ txPackets: number(),
16256
+ rxErrors: number(),
16257
+ txErrors: number(),
16258
+ timestampMs: number()
16259
+ });
16260
+ var MetricsGpuInfoSchema = object({
16261
+ utilization: number(),
16262
+ model: string(),
16263
+ memoryUsedBytes: number(),
16264
+ memoryTotalBytes: number(),
16265
+ temperature: number().nullable()
16266
+ });
16267
+ var ProcessResourceInfoSchema = object({
16268
+ openFds: number(),
16269
+ threadCount: number(),
16270
+ activeHandles: number(),
16271
+ activeRequests: number()
16272
+ });
16273
+ var PressureAvgsSchema = object({
16274
+ avg10: number(),
16275
+ avg60: number(),
16276
+ avg300: number()
15979
16277
  });
15980
- /** One declared native severity/priority level for a kind. */
15981
- var TargetKindLevelSchema = object({
15982
- id: string(),
15983
- label: string(),
15984
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
15985
- ordinal: number().int().min(1).max(5).nullable(),
15986
- flags: object({
15987
- critical: boolean().optional(),
15988
- silent: boolean().optional(),
15989
- noPush: boolean().optional()
15990
- }).optional(),
15991
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
15992
- requires: array(string()).optional(),
15993
- description: string().optional()
16278
+ var PressureInfoSchema = object({
16279
+ some: PressureAvgsSchema,
16280
+ full: PressureAvgsSchema.nullable()
15994
16281
  });
15995
- /** The full capability block consulted before dispatch. */
15996
- var TargetKindCapsSchema = object({
15997
- attachments: object({
15998
- mediaTypes: array(AttachmentMediaTypeSchema),
15999
- mode: _enum([
16000
- "url",
16001
- "bytes",
16002
- "both"
16003
- ]),
16004
- max: number().int().nonnegative(),
16005
- maxBytes: number().int().positive().optional()
16282
+ var SystemResourceSnapshotSchema = object({
16283
+ cpu: CpuBreakdownSchema,
16284
+ memory: MemoryInfoSchema,
16285
+ gpu: MetricsGpuInfoSchema.nullable(),
16286
+ network: NetworkIoSnapshotSchema,
16287
+ disk: DiskIoSnapshotSchema,
16288
+ pressure: object({
16289
+ cpu: PressureInfoSchema.nullable(),
16290
+ memory: PressureInfoSchema.nullable(),
16291
+ io: PressureInfoSchema.nullable()
16006
16292
  }),
16007
- /** Max action buttons (0 = none). */
16008
- actions: number().int().nonnegative(),
16009
- levels: array(TargetKindLevelSchema),
16010
- format: array(NotificationFormatSchema),
16011
- clickUrl: boolean(),
16012
- sound: boolean(),
16013
- ttl: boolean(),
16014
- bodyMaxLen: number().int().positive()
16293
+ process: ProcessResourceInfoSchema,
16294
+ cpuTemperature: number().nullable(),
16295
+ timestampMs: number()
16015
16296
  });
16016
- /**
16017
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16018
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16019
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`)
16020
- * the union is large and not meant for runtime validation here; the exported
16021
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16022
- */
16023
- var ConfigSchemaPassthrough = unknown();
16024
- var TargetKindSchema = object({
16025
- kind: string(),
16026
- label: string(),
16027
- icon: string(),
16028
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
16029
- addonId: string(),
16030
- configSchema: ConfigSchemaPassthrough,
16031
- supportsDiscovery: boolean(),
16032
- caps: TargetKindCapsSchema
16297
+ var DiskSpaceInfoSchema = object({
16298
+ path: string(),
16299
+ totalBytes: number(),
16300
+ usedBytes: number(),
16301
+ availableBytes: number(),
16302
+ percent: number()
16033
16303
  });
16034
- /**
16035
- * A persisted target. `config` holds secrets; providers REDACT secret fields
16036
- * (return a presence marker only) when serving `listTargets` — never
16037
- * round-trip a stored secret to the UI.
16038
- */
16039
- var TargetSchema = object({
16040
- id: string(),
16041
- name: string(),
16042
- kind: string(),
16304
+ var PidResourceStatsSchema = object({
16305
+ pid: number(),
16306
+ cpu: number(),
16307
+ memory: number(),
16308
+ /**
16309
+ * Private (anonymous) resident bytes — the per-process V8 heap + native
16310
+ * allocations NOT shared with other processes (Linux RssAnon). This is the
16311
+ * "real" per-runner cost; summing it across runners is meaningful, unlike
16312
+ * `memory` (RSS), which double-counts the shared mmap'd framework code.
16313
+ * Undefined where /proc is unavailable (e.g. macOS).
16314
+ */
16315
+ privateBytes: number().optional(),
16316
+ /**
16317
+ * Shared file-backed resident bytes (Linux RssFile) — mmap'd framework/lib
16318
+ * code shared copy-on-write across runners. Undefined on macOS.
16319
+ */
16320
+ sharedBytes: number().optional()
16321
+ });
16322
+ var AddonInstanceSchema = object({
16043
16323
  addonId: string(),
16044
- enabled: boolean(),
16045
- config: record(string(), unknown())
16324
+ nodeId: string(),
16325
+ role: _enum(["hub", "worker"]),
16326
+ pid: number(),
16327
+ state: _enum([
16328
+ "starting",
16329
+ "running",
16330
+ "stopping",
16331
+ "stopped",
16332
+ "crashed"
16333
+ ]),
16334
+ uptimeSec: number()
16046
16335
  });
16047
- /** A discovery-surfaced candidate (config is partial + non-secret). */
16048
- var DiscoveredTargetSchema = object({
16049
- kind: string(),
16050
- suggestedName: string(),
16051
- config: record(string(), unknown())
16336
+ var NodeProcessSchema = object({
16337
+ pid: number(),
16338
+ ppid: number(),
16339
+ pgid: number(),
16340
+ classification: _enum([
16341
+ "root",
16342
+ "managed",
16343
+ "system",
16344
+ "ghost"
16345
+ ]),
16346
+ /** `$process` addon binding when `managed`, else null. */
16347
+ addonId: string().nullable(),
16348
+ /** Kernel-reported nodeId when the process is a known agent/worker. */
16349
+ nodeId: string().nullable(),
16350
+ /** Truncated command line. */
16351
+ command: string(),
16352
+ cpuPercent: number(),
16353
+ memoryRssBytes: number(),
16354
+ /** Wall-clock uptime (seconds). Parsed from `ps etime`. */
16355
+ uptimeSec: number(),
16356
+ /** True when ancestor walk reaches `ppid=1` (reparented to init/launchd). */
16357
+ orphaned: boolean()
16052
16358
  });
16053
- /** The degrade engine's report — what was resolved / dropped / degraded. */
16054
- var RenderedAsSchema = object({
16055
- level: string(),
16056
- format: NotificationFormatSchema,
16057
- attachmentsSent: number().int().nonnegative(),
16058
- actionsSent: number().int().nonnegative(),
16059
- truncated: boolean(),
16060
- dropped: array(string())
16359
+ var KillProcessInputSchema = object({
16360
+ pid: number(),
16361
+ /** Force = SIGKILL. Default is SIGTERM. */
16362
+ force: boolean().optional()
16061
16363
  });
16062
- var SendResultSchema = object({
16364
+ var KillProcessResultSchema = object({
16063
16365
  success: boolean(),
16064
- error: string().optional(),
16065
- renderedAs: RenderedAsSchema.optional()
16366
+ reason: string().optional(),
16367
+ signal: _enum(["SIGTERM", "SIGKILL"]).optional()
16368
+ });
16369
+ var DumpHeapSnapshotInputSchema = object({
16370
+ /** The addon whose runner should dump a heap snapshot. */
16371
+ addonId: string() });
16372
+ var DumpHeapSnapshotResultSchema = object({
16373
+ success: boolean(),
16374
+ /** Path of the written .heapsnapshot inside the runner's container/host. */
16375
+ path: string().optional(),
16376
+ /** Process pid that was signalled. */
16377
+ pid: number().optional(),
16378
+ reason: string().optional()
16379
+ });
16380
+ var SystemMetricsSchema = object({
16381
+ cpuPercent: number(),
16382
+ memoryPercent: number(),
16383
+ memoryUsedMB: number(),
16384
+ memoryTotalMB: number(),
16385
+ diskPercent: number().optional(),
16386
+ temperature: number().optional(),
16387
+ gpuPercent: number().optional(),
16388
+ gpuMemoryPercent: number().optional()
16389
+ });
16390
+ method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(KillProcessInputSchema, KillProcessResultSchema, {
16391
+ kind: "mutation",
16392
+ auth: "admin"
16393
+ }), method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
16394
+ kind: "mutation",
16395
+ auth: "admin"
16396
+ });
16397
+ method(object({
16398
+ sourceUrl: string(),
16399
+ metadata: ModelConvertMetadataSchema,
16400
+ targets: array(ConvertTargetSchema).min(1).readonly(),
16401
+ calibrationRef: string().optional(),
16402
+ sessionId: string().optional()
16403
+ }), ConvertResultSchema, {
16404
+ kind: "mutation",
16405
+ auth: "admin",
16406
+ timeoutMs: 6e5
16407
+ });
16408
+ method(object({
16409
+ nodeId: string(),
16410
+ modelId: string(),
16411
+ format: _enum(MODEL_FORMATS),
16412
+ entry: ModelCatalogEntrySchema
16413
+ }), object({
16414
+ ok: boolean(),
16415
+ /** sha256 of the staged tarball (empty for a hub-local no-op). */
16416
+ sha256: string(),
16417
+ bytes: number(),
16418
+ /** The target node's modelsDir the artifact landed in. */
16419
+ path: string()
16420
+ }), {
16421
+ kind: "mutation",
16422
+ auth: "admin"
16066
16423
  });
16067
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
16068
- var TestResultSchema = SendResultSchema;
16069
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
16070
- kind: string(),
16071
- config: record(string(), unknown()).optional()
16072
- }), array(DiscoveredTargetSchema)), method(object({
16073
- targetId: string(),
16074
- notification: NotificationSchema
16075
- }), SendResultSchema, { kind: "mutation" }), method(object({
16076
- targetId: string(),
16077
- sample: NotificationSchema.optional()
16078
- }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
16079
- targetId: string(),
16080
- enabled: boolean()
16081
- }), _void(), { kind: "mutation" });
16082
16424
  /**
16083
- * notification-rulesthe Notification Center rule surface (P1 core).
16084
- *
16085
- * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
16086
- * (operator decisions D-1/D-2/D-3 are binding):
16425
+ * `mqtt-broker`broker-registry cap.
16087
16426
  *
16088
- * - D-2: rule EVALUATION lives in `addon-post-analysis` (the
16089
- * `notification-center` module), hooked on the durable persistence
16090
- * moments (object-event insert, TrackCloser.closeExpired) with a
16091
- * persisted outbox + retry — never the lossy telemetry bus (D8).
16092
- * - D-3: urgency belongs to the RULE. `delivery: 'immediate'` fires on the
16093
- * FIRST persisted detection matching the conditions (per-track dedup,
16094
- * `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
16095
- * `delivery: 'track-end'` evaluates the finalized track record at close.
16096
- * - DISPATCH stays behind `notification-output` (rules reference targets
16097
- * by id; per-backend params are a passthrough blob capped by the
16098
- * target kind's own caps/degrade engine).
16427
+ * NOT a pub/sub proxy. The cap exposes (a) a registry of configured
16428
+ * MQTT brokers (external + optionally an embedded `aedes`-backed one)
16429
+ * and (b) the connection details a consumer addon needs to spin up
16430
+ * its OWN `mqtt.js` client.
16099
16431
  *
16100
- * P1 scope: admin-authored rules only (`createdBy` stamped from the
16101
- * server-injected caller identity the first `caller: 'required'`
16102
- * adopter). The P1 condition subset is: devices, classes(+exclude),
16103
- * minConfidence, admin zones (any/all + exclude), weekly schedule
16104
- * windows, and the optional label/identity/plate matchers. User rules,
16105
- * private zones, per-recipient fan-out and the wider condition table are
16106
- * P2+ (see spec §7).
16432
+ * Why: pub/sub routing over the system event-bus loses fidelity
16433
+ * (callback shape, QoS guarantees, will/retain semantics) and adds
16434
+ * refcount bookkeeping that addons would rather own themselves. The
16435
+ * canonical consumer (`addon-export-ha-mqtt`) needs raw `mqtt.js`
16436
+ * features anyway — give it the connection config, get out of the way.
16107
16437
  *
16108
- * All schemas here are the single source of truth — `NcRule` etc. are
16109
- * `z.infer` exports; no duplicate interfaces (the advanced-notifier
16110
- * schema/interface drift is explicitly not repeated).
16438
+ * Consumer flow:
16439
+ * const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
16440
+ * const client = mqtt.connect(cfg.url, { username: cfg.username, … })
16441
+ * client.subscribe('zigbee2mqtt/+')
16442
+ *
16443
+ * Collection mode: multiple brokers (e.g. one local mosquitto + one
16444
+ * cloud bridge). The "embedded" entry (when present) is just another
16445
+ * broker in the registry — its lifecycle is owned by the addon that
16446
+ * spawned it.
16111
16447
  */
16448
+ var BrokerKindSchema = _enum(["external", "embedded"]);
16112
16449
  /**
16113
- * D-3: the trigger/urgency of a rule — which persistence moment evaluates it.
16114
- * The value maps 1:1 onto the evaluated record kind:
16115
- * - `immediate` ↔ object-event persist (lowest-latency detection burst)
16116
- * - `track-end` ↔ TrackCloser.closeExpired (finalized track record)
16117
- * - `device-event` ↔ SensorEventStore insert (doorbell press / sensor state
16118
- * change of a LINKED device, one row per linked camera)
16119
- * - `package-event` ↔ PackageDropDetector object-event insert (a `package`
16120
- * delivery / pick-up)
16450
+ * Broker live-probe status.
16121
16451
  *
16122
- * `immediate`/`track-end` carry the D-3 urgency semantics; `device-event`/
16123
- * `package-event` are pure trigger kinds (no urgency dimension). Extending
16124
- * this one field keeps the schema additive a rule still declares exactly
16125
- * one trigger.
16452
+ * - `connected` last probe completed a clean CONNACK
16453
+ * - `disconnected` no probe has run yet (cold cache)
16454
+ * - `auth-failed` CONNACK refused with auth error (RC 4 / 5)
16455
+ * - `unreachable` — TCP connect timed out / refused
16456
+ * - `tls-error` — TLS handshake failed (cert / SNI / cipher)
16126
16457
  */
16127
- var NcDeliverySchema = _enum([
16128
- "immediate",
16129
- "track-end",
16130
- "device-event",
16131
- "package-event"
16458
+ var BrokerStatusSchema$1 = _enum([
16459
+ "connected",
16460
+ "disconnected",
16461
+ "auth-failed",
16462
+ "unreachable",
16463
+ "tls-error"
16132
16464
  ]);
16133
- /** Weekly schedule — OR of windows; absence on the rule = always active. */
16134
- var NcScheduleSchema = object({
16135
- windows: array(object({
16136
- /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
16137
- days: array(number().int().min(0).max(6)).min(1),
16138
- startMinute: number().int().min(0).max(1439),
16139
- endMinute: number().int().min(0).max(1439)
16140
- })).min(1),
16141
- /** IANA timezone; default = hub host timezone. */
16142
- timezone: string().optional(),
16143
- /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
16144
- invert: boolean().optional()
16145
- });
16146
- /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
16147
- var NcPlateMatcherSchema = object({
16148
- values: array(string().min(1)).min(1),
16149
- /** Max Levenshtein distance after normalization (uppercase alphanumeric). */
16150
- maxDistance: number().int().min(0).max(3).default(1)
16151
- });
16152
- /**
16153
- * Occupancy condition (DEVICE-EVENT trigger). Fires on a ZoneAnalytics
16154
- * occupancy edge for a device — optionally narrowed to a single admin
16155
- * `zoneId` and/or object `className`. `op` selects the edge/threshold:
16156
- * - `became-occupied` (default) — count crossed 0 → ≥ `count`
16157
- * - `became-free` — count crossed ≥ `count` → below it
16158
- * - `>=` / `<=` — count is at/over or at/under `count`
16159
- * `sustainSeconds` requires the condition hold continuously that long
16160
- * before firing (debounces flicker; 0 = fire on the first matching edge).
16161
- * Fail-closed: no ZoneAnalytics snapshot / missing zone / null snapshot ⇒
16162
- * the condition never matches. Confirmed edge-state survives addon restarts
16163
- * (declared SQLite collection, reseeded on boot).
16164
- */
16165
- var NcOccupancyConditionSchema = object({
16166
- /** Admin zone id to scope the count to; absent = whole-frame occupancy. */
16167
- zoneId: string().optional(),
16168
- /** Object class to count; absent = any class. */
16169
- className: string().optional(),
16170
- op: _enum([
16171
- "became-occupied",
16172
- "became-free",
16173
- ">=",
16174
- "<="
16175
- ]).default("became-occupied"),
16176
- count: number().int().min(0).default(1),
16177
- sustainSeconds: number().int().min(0).max(3600).default(15)
16178
- });
16179
- /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
16180
- var NcZoneConditionSchema = object({
16181
- ids: array(string().min(1)).min(1),
16182
- /** Quantifier over `ids` — at least one / every one visited. */
16183
- match: _enum(["any", "all"]).default("any")
16465
+ var BrokerInfoSchema = object({
16466
+ id: string(),
16467
+ name: string(),
16468
+ url: string(),
16469
+ kind: BrokerKindSchema,
16470
+ status: BrokerStatusSchema$1,
16471
+ latencyMs: number().nullable(),
16472
+ error: string().optional(),
16473
+ /** Embedded brokers only: number of MQTT clients currently connected. */
16474
+ connectedClients: number().int().nonnegative().optional(),
16475
+ /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
16476
+ lastCheckedAt: number().optional()
16184
16477
  });
16185
16478
  /**
16186
- * The P1 condition set a flat AND of groups; absent group = pass;
16187
- * membership lists are OR within the list (spec §2.3).
16479
+ * Connection details what a consumer needs to call
16480
+ * `mqtt.connect(url, options)`. We split URL + credentials so the
16481
+ * consumer can pass them as `mqtt.connect(url, { username, password })`
16482
+ * instead of stuffing creds into the URL (which leaks them into logs).
16188
16483
  */
16189
- var NcConditionsSchema = object({
16190
- /** Device scope — absent = all devices. */
16191
- devices: array(number()).optional(),
16192
- /** Detector class names (any overlap with the record's class set). */
16193
- classes: array(string().min(1)).optional(),
16194
- /** Veto classes — any overlap fails the rule. */
16195
- classesExclude: array(string().min(1)).optional(),
16196
- /** Minimum detection confidence 0–1 (fails when the record has none). */
16197
- minConfidence: number().min(0).max(1).optional(),
16198
- /** Admin zone membership over event `zones` / track `zonesVisited`. */
16199
- zones: NcZoneConditionSchema.optional(),
16200
- /** Veto zones — any hit fails the rule. */
16201
- zonesExclude: array(string().min(1)).optional(),
16202
- /**
16203
- * Exact (case-insensitive) match on the record's collapsed `label`
16204
- * (identity name / plate text / subclass).
16205
- */
16206
- labelEquals: array(string().min(1)).optional(),
16207
- /**
16208
- * Identity matcher. P1 boundary: matched against the record's collapsed
16209
- * `label` (the identity display name propagated by the face pipeline) —
16210
- * identity-ID matching rides in P2 when identity ids reach the record.
16211
- */
16212
- identities: array(string().min(1)).optional(),
16213
- /** Fuzzy plate matcher against the record's `label` (plate text). */
16214
- plates: NcPlateMatcherSchema.optional(),
16215
- /**
16216
- * Identity EXCLUDE — mirror of {@link identities} with `notIn` semantics.
16217
- * Same P1 boundary: matched against the record's collapsed `label` (the
16218
- * identity display name). A record with NO label passes (nothing to
16219
- * exclude), unlike the include variant which fails on an absent label.
16220
- */
16221
- identitiesExclude: array(string().min(1)).optional(),
16222
- /**
16223
- * Minimum server-computed key-event importance in [0,1] (`Track.importance`).
16224
- * TRACK-END only: importance is scored at track close, so it does not exist
16225
- * at immediate / object-event evaluation time (see catalog `appliesTo`). At
16226
- * close the value is threaded via the close-time info (the `Track` clone is
16227
- * captured before the DB row is updated, so it would otherwise read stale).
16228
- * Fails when the record carries no importance (never guess quality — the
16229
- * `minConfidence` precedent). MVP cut: a single scalar threshold.
16230
- */
16231
- minImportance: number().min(0).max(1).optional(),
16232
- /**
16233
- * Minimum track dwell in SECONDS — `(lastSeen − firstSeen) / 1000`.
16234
- * TRACK-END only: an `immediate` / object-event subject has no closed
16235
- * lifespan, so a dwell condition never matches immediate delivery
16236
- * (documented choice — the object-event record carries no `firstSeen`,
16237
- * so dwell cannot be computed from what the subject actually carries).
16238
- */
16239
- minDwellSeconds: number().min(0).optional(),
16240
- /**
16241
- * Detection provenance filter. `any` (default / absent) matches every
16242
- * source; otherwise the subject's source must equal it. Legacy records
16243
- * with no stamped source are treated as `pipeline`. The union spans both
16244
- * record kinds — object events carry `pipeline` | `onboard`, synthetic
16245
- * tracks carry `sensor`.
16246
- */
16247
- source: _enum([
16248
- "pipeline",
16249
- "onboard",
16250
- "sensor",
16251
- "any"
16252
- ]).optional(),
16253
- /**
16254
- * Minimum identity / plate MATCH confidence in [0,1] — DISTINCT from the
16255
- * detector `minConfidence` (that gates the object-detection score; this
16256
- * gates the recognition/OCR match score). Fails when the subject carries
16257
- * no label-match confidence (never guess). TRACK-END only: the confidence
16258
- * lives on the recognition result and reaches the subject at track close.
16259
- *
16260
- * What it measures precisely (plumbed at track close — the closer threads
16261
- * the value into `NcTrackClosedInfo.labelConfidence`, the same seam as
16262
- * `importance`): the BEST recognition match confidence observed for the
16263
- * label the track carries at close — for a face, the peak cosine similarity
16264
- * of the ASSIGNED identity (`FaceMatch.score`, reset on an identity switch);
16265
- * for a plate, the peak OCR read score of the best-held plate
16266
- * (`plateText.confidence`). When BOTH a face and a plate were recognized on
16267
- * one track the higher of the two is used. A track that ended with no
16268
- * confident identity/plate match carries no value, so the condition fails
16269
- * closed for it (an un-recognized subject).
16270
- */
16271
- minLabelConfidence: number().min(0).max(1).optional(),
16272
- /**
16273
- * DEVICE-EVENT only. Raw device event-type tokens (`EventFire.eventType`,
16274
- * e.g. a doorbell `press` / `press_long`) — matched case-insensitively
16275
- * against the token carried on the device-event subject (extracted from the
16276
- * event-emitter runtime slice's `lastEvent.eventType`). Fails when the
16277
- * subject carries no token. Doorbell-pulse / passive-sensor kinds emit no
16278
- * eventType, so gate those with {@link sensorKinds} instead.
16279
- */
16280
- eventTypeTokens: array(string().min(1)).optional(),
16281
- /**
16282
- * DEVICE-EVENT only. Sensor/control taxonomy kinds (e.g. `doorbell`,
16283
- * `contact`, `button`, `device-event`) — matched against the persisted
16284
- * `SensorEvent.kind` (see `sensor-event-kinds.ts`). Membership is OR.
16285
- */
16286
- sensorKinds: array(string().min(1)).optional(),
16287
- /**
16288
- * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
16289
- * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
16290
- * when the subject's phase does not match (a subject always carries a phase
16291
- * on the package-event trigger).
16292
- */
16293
- packagePhase: _enum([
16294
- "delivered",
16295
- "picked-up",
16296
- "both"
16297
- ]).optional(),
16298
- /**
16299
- * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
16300
- * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
16301
- * listed polygon (ZoneEngine membership semantics). Evaluated only when
16302
- * the subject carries a bbox; absent bbox ⇒ the condition FAILS.
16303
- */
16304
- customZones: array(MaskPolygonShapeSchema).optional(),
16484
+ var BrokerConnectionDetailsSchema = object({
16485
+ url: string(),
16486
+ username: string().optional(),
16487
+ password: string().optional(),
16305
16488
  /**
16306
- * DEVICE-EVENT only. ZoneAnalytics occupancy edge fires when a device's
16307
- * (optionally zone/class-scoped) occupancy count crosses the configured
16308
- * threshold and holds for `sustainSeconds`. Fail-closed on missing
16309
- * substrate (no snapshot / missing zone). See {@link NcOccupancyCondition}.
16489
+ * Suggested prefix for `clientId`. Each consumer should suffix this
16490
+ * with its own discriminator (addon id, instance id) so reconnects
16491
+ * don't kick each other off (MQTT spec: clientId must be unique per
16492
+ * broker).
16310
16493
  */
16311
- occupancy: NcOccupancyConditionSchema.optional()
16494
+ clientIdPrefix: string().optional()
16495
+ });
16496
+ var AddBrokerInputSchema = object({
16497
+ name: string().min(1),
16498
+ url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
16499
+ username: string().optional(),
16500
+ password: string().optional(),
16501
+ clientIdPrefix: string().optional()
16502
+ });
16503
+ var AddBrokerResultSchema = object({ id: string() });
16504
+ var IdInputSchema = object({ id: string() });
16505
+ var TestResultSchema$1 = discriminatedUnion("ok", [object({
16506
+ ok: literal(true),
16507
+ latencyMs: number()
16508
+ }), object({
16509
+ ok: literal(false),
16510
+ error: string()
16511
+ })]);
16512
+ var StartEmbeddedInputSchema = object({
16513
+ port: number().int().min(1).max(65535).default(1883),
16514
+ /** Allow anonymous connect (no username/password). Default: false. */
16515
+ allowAnonymous: boolean().default(false),
16516
+ /** Optional shared username/password for clients. */
16517
+ username: string().optional(),
16518
+ password: string().optional()
16312
16519
  });
16313
- /** One delivery target: a `notification-output` Target ref + passthrough params. */
16314
- var NcRuleTargetSchema = object({
16315
- /** `notification-output` Target id. */
16316
- targetId: string().min(1),
16317
- /**
16318
- * Per-backend passthrough. Recognized keys are mapped onto the canonical
16319
- * Notification (`priority`, `level`, `sound`, `clickUrl`, `ttl`); the
16320
- * degrade engine drops what the backend can't render.
16321
- */
16322
- params: record(string(), unknown()).optional()
16520
+ var StartEmbeddedResultSchema = object({
16521
+ id: string(),
16522
+ url: string()
16523
+ });
16524
+ var StatusSchema = object({
16525
+ brokerCount: number(),
16526
+ embeddedRunning: boolean()
16527
+ });
16528
+ 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);
16529
+ var NetworkEndpointSchema = object({
16530
+ url: string(),
16531
+ hostname: string(),
16532
+ port: number(),
16533
+ protocol: _enum(["http", "https"])
16534
+ });
16535
+ var NetworkAccessStatusSchema = object({
16536
+ connected: boolean(),
16537
+ endpoint: NetworkEndpointSchema.nullable(),
16538
+ error: string().optional()
16323
16539
  });
16324
16540
  /**
16325
- * Media attachment policy (P1 still-image subset).
16326
- * - `best` the best AVAILABLE subject image at dispatch time (D-3).
16327
- * - `best-matching` the media that explains WHY the rule fired: a rule
16328
- * matched on identities attaches the subject's `faceCrop`, one matched on
16329
- * plates attaches the `plateCrop`; a rule with no identity/plate condition
16330
- * (or when the specific crop is missing) degrades to `best`, then
16331
- * `keyFrame`, then no attachment — never delaying the send. The matched
16332
- * condition summary is frozen on the outbox row at enqueue (like the rule
16333
- * name), so the choice never drifts from the record that fired it.
16334
- * - `keyFrame` — the clean scene frame (no subject box).
16335
- * - `none` — no attachment.
16541
+ * Optional, richer endpoint shape returned by providers that expose
16542
+ * MORE than one ingress concurrently (Tailscale Ingress with mixed
16543
+ * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
16544
+ * the originating provider config (mode + sourcePort) so the
16545
+ * orchestrator UI can label rows distinctly. Providers that expose only
16546
+ * one endpoint just omit `listEndpoints` from their provider impl.
16336
16547
  */
16337
- var NcMediaPolicySchema = object({ attach: _enum([
16338
- "best",
16339
- "best-matching",
16340
- "keyFrame",
16341
- "none"
16342
- ]).default("best") });
16343
- /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
16344
- var NcThrottleSchema = object({
16345
- cooldownSec: number().int().min(0).max(86400).default(60),
16346
- /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
16347
- scope: _enum(["rule", "rule-device"]).default("rule-device")
16348
- });
16349
- /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
16350
- var NcRuleInputSchema = object({
16351
- name: string().min(1).max(200),
16352
- enabled: boolean().default(true),
16353
- delivery: NcDeliverySchema,
16354
- conditions: NcConditionsSchema.default({}),
16355
- schedule: NcScheduleSchema.optional(),
16356
- targets: array(NcRuleTargetSchema).min(1),
16357
- media: NcMediaPolicySchema.default({ attach: "best" }),
16358
- throttle: NcThrottleSchema.default({
16359
- cooldownSec: 60,
16360
- scope: "rule-device"
16361
- }),
16362
- /** `{{var}}` templating over camera/class/label/zones/confidence/time. */
16363
- template: object({
16364
- title: string().max(500).optional(),
16365
- body: string().max(2e3).optional()
16366
- }).optional(),
16367
- /** Canonical notification priority ordinal (1..5); per-target overridable. */
16368
- priority: number().int().min(1).max(5).default(3),
16548
+ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
16369
16549
  /**
16370
- * Ownership/visibility key. Absent = admin/global rule (unchanged legacy
16371
- * behaviour, visible to all, read-only in the viewer). Present = personal
16372
- * rule owned by this userId. Server-stamped; never trusted from a client.
16550
+ * Stable id within the provider typically `<mode>-<sourcePort>` so
16551
+ * the orchestrator can dedupe across `listEndpoints` polls.
16373
16552
  */
16374
- ownerUserId: string().optional()
16553
+ id: string(),
16554
+ /** Operator-facing label (mirrors `MeshEndpoint.label`). */
16555
+ label: string(),
16556
+ /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
16557
+ mode: string().optional(),
16558
+ /** Originating local port the ingress fronts (informational). */
16559
+ sourcePort: number().optional()
16375
16560
  });
16561
+ var networkAccessCapability = {
16562
+ name: "network-access",
16563
+ scope: "system",
16564
+ mode: "collection",
16565
+ providerKind: "ingress",
16566
+ methods: {
16567
+ start: method(_void(), NetworkEndpointSchema, { kind: "mutation" }),
16568
+ stop: method(_void(), _void(), { kind: "mutation" }),
16569
+ getEndpoint: method(_void(), NetworkEndpointSchema.nullable()),
16570
+ getStatus: method(_void(), NetworkAccessStatusSchema),
16571
+ /**
16572
+ * Enumerate every active ingress entry. Providers that expose only a
16573
+ * single endpoint may omit this method; callers fall back to
16574
+ * `getEndpoint()` in that case.
16575
+ */
16576
+ listEndpoints: method(_void(), array(NetworkEndpointEntrySchema).readonly())
16577
+ }
16578
+ };
16376
16579
  /**
16377
- * Partial patch for `updateRule` any subset of the input fields, plus the
16378
- * persisted-only {@link NcRuleSchema} `disabledTargetIds` set. The latter is
16379
- * NOT a client-authored input field (it lives on the persisted rule, not the
16380
- * input), so it is added here explicitly to let the store's per-target opt-out
16381
- * toggle round-trip through the shared `update` path. Viewer opt-out mutations
16382
- * still flow through `nc.setRuleTargetEnabled` (owner-checked), never a raw
16383
- * `updateRule` patch.
16580
+ * notification-outputcanonical, capability-gated notification delivery.
16581
+ *
16582
+ * Apprise-derived model (see
16583
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16584
+ * callers emit ONE canonical `Notification`; each provider declares a
16585
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16586
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16587
+ * message to what the kind supports — callers never special-case a service.
16588
+ *
16589
+ * DESIGN DECISIONS (locked):
16590
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16591
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16592
+ * cap. Rationale: the admin UI needs one uniform surface across the
16593
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16594
+ * alternative would fork the UI per addon and cannot host the
16595
+ * discovery→adopt flow.
16596
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16597
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16598
+ * registered provider (notifiers addon + HA addon) so one catalog is
16599
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16600
+ * `addonId` the generated collection router extracts from the call input.
16601
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16602
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16603
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16604
+ * base64 fallback needed.
16605
+ *
16606
+ * TODO (deferred, closed-set change — separate decision): add
16607
+ * `providerKind: 'notify'` so notification providers surface on the unified
16608
+ * admin "Integrations" page.
16384
16609
  */
16385
- var NcRulePatchSchema = NcRuleInputSchema.partial().extend({ disabledTargetIds: array(string()).optional() });
16386
- /** A persisted rule. */
16387
- var NcRuleSchema = NcRuleInputSchema.extend({
16610
+ /**
16611
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16612
+ * adapter picks what it supports and the degrade engine filters the rest.
16613
+ */
16614
+ var AttachmentMediaTypeSchema = _enum([
16615
+ "image",
16616
+ "video",
16617
+ "gif",
16618
+ "audio",
16619
+ "icon"
16620
+ ]);
16621
+ /**
16622
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16623
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16624
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16625
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16626
+ */
16627
+ var AttachmentSchema = object({
16628
+ mediaType: AttachmentMediaTypeSchema,
16629
+ url: string().optional(),
16630
+ bytes: _instanceof(Uint8Array).optional(),
16631
+ mime: string().optional(),
16632
+ name: string().optional()
16633
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16634
+ var NotificationFormatSchema = _enum([
16635
+ "text",
16636
+ "markdown",
16637
+ "html"
16638
+ ]);
16639
+ /** A single tap-through action button. */
16640
+ var NotificationActionSchema = object({
16388
16641
  id: string(),
16389
- /** userId of the admin who created the rule (server-stamped caller). */
16390
- createdBy: string(),
16391
- createdAt: number(),
16392
- updatedAt: number(),
16393
- /**
16394
- * Per-target opt-out set. A targetId here is suppressed for THIS rule at
16395
- * send time. Only a target's OWNER may add/remove its id (server-checked
16396
- * in `nc.setRuleTargetEnabled`). Defaults to empty.
16397
- */
16398
- disabledTargetIds: array(string()).default([])
16399
- });
16400
- var NcTestResultSchema = object({
16401
- recordId: string(),
16402
- recordKind: _enum([
16403
- "object-event",
16404
- "track",
16405
- "device-event",
16406
- "package-event"
16407
- ]),
16408
- deviceId: number(),
16409
- timestamp: number(),
16410
- wouldFire: boolean(),
16411
- /** Condition id that failed (first failing group), when `wouldFire` is false. */
16412
- failedCondition: string().optional(),
16413
- className: string().optional(),
16414
- label: string().optional()
16642
+ label: string(),
16643
+ url: string().optional()
16415
16644
  });
16416
- var NcConditionDescriptorSchema = object({
16417
- /** Field id inside `NcConditions` (or `'schedule'` for the rule-level group). */
16418
- id: string(),
16419
- group: _enum([
16420
- "scope",
16421
- "class",
16422
- "zones",
16423
- "quality",
16424
- "label",
16425
- "schedule",
16426
- "device",
16427
- "package",
16428
- "occupancy"
16429
- ]),
16645
+ /**
16646
+ * The canonical notification. `body` is the only hard field (Apprise model).
16647
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16648
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16649
+ * the adapter maps this ordinal onto its native level. `level?` is an
16650
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16651
+ * `priority` for that one target.
16652
+ */
16653
+ var NotificationSchema = object({
16654
+ body: string(),
16655
+ title: string().optional(),
16656
+ format: NotificationFormatSchema.default("text"),
16657
+ priority: number().int().min(1).max(5).default(3),
16658
+ level: string().optional(),
16659
+ attachments: array(AttachmentSchema).optional(),
16660
+ clickUrl: string().optional(),
16661
+ actions: array(NotificationActionSchema).optional(),
16662
+ sound: string().optional(),
16663
+ ttl: number().optional(),
16664
+ tag: string().optional(),
16665
+ deviceId: number().optional(),
16666
+ eventId: string().optional(),
16667
+ metadata: record(string(), unknown()).optional()
16668
+ });
16669
+ /** One declared native severity/priority level for a kind. */
16670
+ var TargetKindLevelSchema = object({
16671
+ id: string(),
16430
16672
  label: string(),
16431
- /** Editor widget the UI renders never hardcode per-condition forms. */
16432
- valueType: _enum([
16433
- "deviceIdList",
16434
- "stringList",
16435
- "number01",
16436
- "number",
16437
- "sourceSelect",
16438
- "zoneSelection",
16439
- "zoneIdList",
16440
- "schedule",
16441
- "plateMatcher",
16442
- "packagePhase",
16443
- "polygonDraw",
16444
- "occupancy"
16445
- ]),
16446
- operator: _enum([
16447
- "in",
16448
- "notIn",
16449
- "anyOf",
16450
- "allOf",
16451
- "gte",
16452
- "fuzzyIn",
16453
- "withinSchedule"
16454
- ]),
16455
- /** Which delivery kinds the condition applies to. */
16456
- appliesTo: array(NcDeliverySchema),
16457
- phase: string(),
16673
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16674
+ ordinal: number().int().min(1).max(5).nullable(),
16675
+ flags: object({
16676
+ critical: boolean().optional(),
16677
+ silent: boolean().optional(),
16678
+ noPush: boolean().optional()
16679
+ }).optional(),
16680
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16681
+ requires: array(string()).optional(),
16458
16682
  description: string().optional()
16459
16683
  });
16684
+ /** The full capability block consulted before dispatch. */
16685
+ var TargetKindCapsSchema = object({
16686
+ attachments: object({
16687
+ mediaTypes: array(AttachmentMediaTypeSchema),
16688
+ mode: _enum([
16689
+ "url",
16690
+ "bytes",
16691
+ "both"
16692
+ ]),
16693
+ max: number().int().nonnegative(),
16694
+ maxBytes: number().int().positive().optional()
16695
+ }),
16696
+ /** Max action buttons (0 = none). */
16697
+ actions: number().int().nonnegative(),
16698
+ levels: array(TargetKindLevelSchema),
16699
+ format: array(NotificationFormatSchema),
16700
+ clickUrl: boolean(),
16701
+ sound: boolean(),
16702
+ ttl: boolean(),
16703
+ bodyMaxLen: number().int().positive()
16704
+ });
16460
16705
  /**
16461
- * The delivery lifecycle status of a history row a straight read of the
16462
- * durable outbox row's own status (single source of truth):
16463
- * - `pending` — enqueued, in-flight or retrying with backoff
16464
- * - `sent` — delivered (terminal)
16465
- * - `dead` dead-lettered after exhausting retries / a permanent
16466
- * backend rejection / a deleted target (terminal; carries
16467
- * the failure `error`)
16468
- *
16469
- * P1 has no `suppressed-quiet-hours` / `snoozed` states — those ride the P2
16470
- * user dimension (quiet hours / snooze) and are additive when they land.
16706
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16707
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16708
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`)
16709
+ * the union is large and not meant for runtime validation here; the exported
16710
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16471
16711
  */
16472
- var NcHistoryStatusSchema = _enum([
16473
- "pending",
16474
- "sent",
16475
- "dead"
16476
- ]);
16477
- /** The evaluated record kind a history row descends from (one per trigger). */
16478
- var NcHistoryRecordKindSchema = _enum([
16479
- "object-event",
16480
- "track-end",
16481
- "device-event",
16482
- "package-event"
16483
- ]);
16484
- /** Subject summary frozen on the row at fire time (survives rule/record edits). */
16485
- var NcHistorySubjectSchema = object({
16486
- className: string(),
16487
- label: string().optional(),
16488
- confidence: number().optional(),
16489
- zones: array(string()),
16490
- timestamp: number()
16712
+ var ConfigSchemaPassthrough = unknown();
16713
+ var TargetKindSchema = object({
16714
+ kind: string(),
16715
+ label: string(),
16716
+ icon: string(),
16717
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16718
+ addonId: string(),
16719
+ configSchema: ConfigSchemaPassthrough,
16720
+ supportsDiscovery: boolean(),
16721
+ caps: TargetKindCapsSchema
16491
16722
  });
16492
16723
  /**
16493
- * One delivery-history row. This is a read-only VIEW over the durable
16494
- * outbox row (single source of truth the same row the drain loop drives;
16495
- * NO second write path, so history can never drift from delivery state).
16496
- * The §3.2 fields map directly: `ruleId`/`targetId`/`deviceId` are columns,
16497
- * `eventRef` is `recordKind`+`recordId`, `timestamps` are `createdAt`
16498
- * (fire) / `updatedAt` (last transition), `status` + `error` are the
16499
- * lifecycle. `ruleName` + `subject` are the intent snapshot frozen at
16500
- * enqueue. `userId?` (per-recipient history) is P2 — no user dimension in
16501
- * P1 (admin scope only).
16724
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16725
+ * (return a presence marker only) when serving `listTargets` never
16726
+ * round-trip a stored secret to the UI.
16502
16727
  */
16503
- var NcHistoryEntrySchema = object({
16504
- /** Outbox row id — the stable dedup id `ruleId:dedupRef:targetId`. */
16728
+ var TargetSchema = object({
16505
16729
  id: string(),
16506
- ruleId: string(),
16507
- /** Rule name frozen at fire time (outlives a later rename / delete). */
16508
- ruleName: string(),
16509
- /** The rule urgency/trigger that produced this delivery. */
16510
- delivery: NcDeliverySchema,
16511
- targetId: string(),
16512
- deviceId: number(),
16513
- recordKind: NcHistoryRecordKindSchema,
16514
- /** Event / track ref of the evaluated record (§3.2 `eventRef`). */
16515
- recordId: string(),
16516
- /** Present for track-scoped deliveries (object-event / track-end). */
16517
- trackId: string().optional(),
16518
- status: NcHistoryStatusSchema,
16519
- /** Delivery attempts made so far. */
16520
- attempts: number().int(),
16521
- /** Fire time (outbox enqueue). */
16522
- createdAt: number(),
16523
- /** Last transition time (terminal for sent / dead). */
16524
- updatedAt: number(),
16525
- /** Failure detail — present on a `dead` row. */
16526
- error: string().optional(),
16527
- subject: NcHistorySubjectSchema
16730
+ name: string(),
16731
+ kind: string(),
16732
+ addonId: string(),
16733
+ enabled: boolean(),
16734
+ config: record(string(), unknown())
16528
16735
  });
16529
- /**
16530
- * Query filter for `getHistory` (spec §4.2). Every field is a narrowing
16531
- * AND; absent = unbounded on that axis. `since`/`until` bound the fire time
16532
- * (`createdAt`, epoch ms, inclusive). `limit` is clamped to
16533
- * {@link NC_HISTORY_LIMIT_MAX}. `userId` (per-recipient filtering) is P2.
16534
- */
16535
- var NcHistoryFilterSchema = object({
16536
- ruleId: string().optional(),
16537
- deviceId: number().optional(),
16538
- status: NcHistoryStatusSchema.optional(),
16539
- since: number().optional(),
16540
- until: number().optional(),
16541
- limit: number().int().min(1).max(500).default(100)
16736
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16737
+ var DiscoveredTargetSchema = object({
16738
+ kind: string(),
16739
+ suggestedName: string(),
16740
+ config: record(string(), unknown())
16542
16741
  });
16543
- 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 }), {
16544
- kind: "mutation",
16545
- auth: "admin",
16546
- caller: "required"
16547
- }), method(object({
16548
- ruleId: string(),
16549
- patch: NcRulePatchSchema
16550
- }), object({ rule: NcRuleSchema }), {
16551
- kind: "mutation",
16552
- auth: "admin",
16553
- caller: "required"
16554
- }), method(object({ ruleId: string() }), object({ success: literal(true) }), {
16555
- kind: "mutation",
16556
- auth: "admin"
16557
- }), method(object({
16558
- ruleId: string(),
16742
+ /** The degrade engine's report what was resolved / dropped / degraded. */
16743
+ var RenderedAsSchema = object({
16744
+ level: string(),
16745
+ format: NotificationFormatSchema,
16746
+ attachmentsSent: number().int().nonnegative(),
16747
+ actionsSent: number().int().nonnegative(),
16748
+ truncated: boolean(),
16749
+ dropped: array(string())
16750
+ });
16751
+ var SendResultSchema = object({
16752
+ success: boolean(),
16753
+ error: string().optional(),
16754
+ renderedAs: RenderedAsSchema.optional()
16755
+ });
16756
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16757
+ var TestResultSchema = SendResultSchema;
16758
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
16759
+ kind: string(),
16760
+ config: record(string(), unknown()).optional()
16761
+ }), array(DiscoveredTargetSchema)), method(object({
16762
+ targetId: string(),
16763
+ notification: NotificationSchema
16764
+ }), SendResultSchema, { kind: "mutation" }), method(object({
16765
+ targetId: string(),
16766
+ sample: NotificationSchema.optional()
16767
+ }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
16768
+ targetId: string(),
16559
16769
  enabled: boolean()
16560
- }), object({ success: literal(true) }), {
16561
- kind: "mutation",
16562
- auth: "admin"
16563
- }), method(object({
16564
- rule: NcRuleInputSchema,
16565
- lookbackMinutes: number().int().min(1).max(1440).default(60)
16566
- }), object({ results: array(NcTestResultSchema) }), {
16567
- kind: "mutation",
16568
- auth: "admin"
16569
- }), method(object({}), object({ catalog: array(NcConditionDescriptorSchema) })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
16770
+ }), _void(), { kind: "mutation" });
16570
16771
  /**
16571
16772
  * Zod schemas for persisted record types.
16572
16773
  *
@@ -21594,6 +21795,12 @@ Object.freeze({
21594
21795
  addonId: null,
21595
21796
  access: "delete"
21596
21797
  },
21798
+ "backup.deleteSchedule": {
21799
+ capName: "backup",
21800
+ capScope: "system",
21801
+ addonId: null,
21802
+ access: "delete"
21803
+ },
21597
21804
  "backup.getEntries": {
21598
21805
  capName: "backup",
21599
21806
  capScope: "system",
@@ -21624,6 +21831,12 @@ Object.freeze({
21624
21831
  addonId: null,
21625
21832
  access: "view"
21626
21833
  },
21834
+ "backup.listSchedules": {
21835
+ capName: "backup",
21836
+ capScope: "system",
21837
+ addonId: null,
21838
+ access: "view"
21839
+ },
21627
21840
  "backup.previewSchedule": {
21628
21841
  capName: "backup",
21629
21842
  capScope: "system",
@@ -21648,6 +21861,12 @@ Object.freeze({
21648
21861
  addonId: null,
21649
21862
  access: "create"
21650
21863
  },
21864
+ "backup.upsertSchedule": {
21865
+ capName: "backup",
21866
+ capScope: "system",
21867
+ addonId: null,
21868
+ access: "create"
21869
+ },
21651
21870
  "battery.wakeForStream": {
21652
21871
  capName: "battery",
21653
21872
  capScope: "device",
@@ -25482,6 +25701,36 @@ Object.freeze({
25482
25701
  addonId: null,
25483
25702
  access: "create"
25484
25703
  },
25704
+ "terminalSession.close": {
25705
+ capName: "terminal-session",
25706
+ capScope: "system",
25707
+ addonId: null,
25708
+ access: "create"
25709
+ },
25710
+ "terminalSession.listProfiles": {
25711
+ capName: "terminal-session",
25712
+ capScope: "system",
25713
+ addonId: null,
25714
+ access: "view"
25715
+ },
25716
+ "terminalSession.listSessions": {
25717
+ capName: "terminal-session",
25718
+ capScope: "system",
25719
+ addonId: null,
25720
+ access: "view"
25721
+ },
25722
+ "terminalSession.openSession": {
25723
+ capName: "terminal-session",
25724
+ capScope: "system",
25725
+ addonId: null,
25726
+ access: "create"
25727
+ },
25728
+ "terminalSession.resize": {
25729
+ capName: "terminal-session",
25730
+ capScope: "system",
25731
+ addonId: null,
25732
+ access: "create"
25733
+ },
25485
25734
  "toast.onToast": {
25486
25735
  capName: "toast",
25487
25736
  capScope: "system",