@camstack/addon-pipeline-orchestrator 1.2.637 → 1.2.639

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/assets/viewer/.viewer-current-files.json +2 -2
  3. package/assets/viewer/.viewer-version +1 -1
  4. package/assets/viewer/_expo/static/js/web/{__common-6206f99c82ca608b8620797fed9685e4.js → __common-987325b06b59d95030dfaa48ed6559b0.js} +43 -43
  5. package/assets/viewer/_expo/static/js/web/{index-7e427fd375381d1d43da28a24697dd03.js → index-0134483fbf5fd90d22b3febd6bd0cc64.js} +2 -2
  6. package/assets/viewer/index.html +1 -1
  7. package/dist/_stub.js +2 -2
  8. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-3S_aIDgV.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-CpDS4Ilu.mjs} +2 -2
  9. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CYNe1S9x.mjs +26 -0
  10. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CMKrpGjd.mjs +26 -0
  11. package/dist/{hostInit-DgrSAcMC.mjs → hostInit-DY3NVnRS.mjs} +2 -2
  12. package/dist/index.js +521 -26
  13. package/dist/index.mjs +521 -26
  14. package/dist/remoteEntry.js +1 -1
  15. package/package.json +1 -1
  16. package/assets/viewer/_expo/static/js/web/[id]-d3b1d7a725fa2a1af0839ac46985eb3d.js +0 -7
  17. package/assets/viewer/_expo/static/js/web/[id]-ed4f5ea7c5329aa67a79c94da84ba024.js +0 -5
  18. package/assets/viewer/_expo/static/js/web/[section]-0eef3af480ef1910eed719cf82530d92.js +0 -31
  19. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BlL4i123.mjs +0 -26
  20. package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DW9I9T44.mjs +0 -26
package/dist/index.mjs CHANGED
@@ -5583,7 +5583,7 @@ var ZodIssueCode = {
5583
5583
  var ZodFirstPartyTypeKind;
5584
5584
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5585
5585
  //#endregion
5586
- //#region ../types/dist/sleep-BAvUCi_2.mjs
5586
+ //#region ../types/dist/sleep-C_u6cEAJ.mjs
5587
5587
  /**
5588
5588
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5589
5589
  * window to float samples (D455).
@@ -9531,6 +9531,7 @@ var CAP_NAMES = Object.freeze([
9531
9531
  "network-link",
9532
9532
  "network-quality",
9533
9533
  "node-certificates",
9534
+ "node-removal-hook",
9534
9535
  "nodes",
9535
9536
  "notification-output",
9536
9537
  "notification-rules",
@@ -9561,6 +9562,7 @@ var CAP_NAMES = Object.freeze([
9561
9562
  "recording-onboard",
9562
9563
  "recording-signal",
9563
9564
  "records",
9565
+ "remote",
9564
9566
  "running-state",
9565
9567
  "scene-monitor",
9566
9568
  "script-runner",
@@ -9584,6 +9586,7 @@ var CAP_NAMES = Object.freeze([
9584
9586
  "system",
9585
9587
  "tamper",
9586
9588
  "temperature-sensor",
9589
+ "template",
9587
9590
  "terminal-session",
9588
9591
  "toast",
9589
9592
  "training",
@@ -10524,6 +10527,11 @@ Object.freeze({
10524
10527
  "standby"
10525
10528
  ]
10526
10529
  },
10530
+ {
10531
+ "path": "stateSource",
10532
+ "valueType": "enum",
10533
+ "enumValues": ["reported", "assumed"]
10534
+ },
10527
10535
  {
10528
10536
  "path": "volumeLevel",
10529
10537
  "valueType": "number"
@@ -10799,6 +10807,18 @@ Object.freeze({
10799
10807
  {
10800
10808
  "path": "volume",
10801
10809
  "valueType": "number"
10810
+ },
10811
+ {
10812
+ "path": "lastFeedGrams",
10813
+ "valueType": "number"
10814
+ },
10815
+ {
10816
+ "path": "lastFeedHopper1",
10817
+ "valueType": "number"
10818
+ },
10819
+ {
10820
+ "path": "lastFeedHopper2",
10821
+ "valueType": "number"
10802
10822
  }
10803
10823
  ],
10804
10824
  "power-meter": [
@@ -11724,7 +11744,7 @@ function errMsg$2(err) {
11724
11744
  return String(err);
11725
11745
  }
11726
11746
  //#endregion
11727
- //#region ../types/dist/composition-DpFv_J6D.mjs
11747
+ //#region ../types/dist/composition-bIBW5_13.mjs
11728
11748
  /**
11729
11749
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11730
11750
  *
@@ -43824,6 +43844,14 @@ method(object({
43824
43844
  kind: "mutation",
43825
43845
  auth: "admin"
43826
43846
  });
43847
+ method(object({
43848
+ nodeId: string().min(1),
43849
+ /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
43850
+ removedAt: number()
43851
+ }), object({ counts: record(string(), number().int().nonnegative()) }), {
43852
+ kind: "mutation",
43853
+ auth: "admin"
43854
+ });
43827
43855
  /**
43828
43856
  * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
43829
43857
  * so it can delete what it keeps for that device (D865).
@@ -56067,8 +56095,7 @@ var pipelineOrchestratorCapability = {
56067
56095
  * replaced the four flat orchestrator lists (`enabledNodes`,
56068
56096
  * `enabledDecoderNodes`, `enabledAudioNodes`, `remoteSourcingNodes`).
56069
56097
  * Each flag is optional in the patch: omit a flag to leave it unchanged,
56070
- * pass `null` to reset it to the node default (`hub` → capable, every
56071
- * other node → not). Detection/audio eligibility is derived from these
56098
+ * pass `null` to reset it to the default (ON for every node). Detection/audio eligibility is derived from these
56072
56099
  * flags across the cluster; changing them re-derives the enabled sets
56073
56100
  * and reconciles dispatch immediately. There is NO separate decode flag:
56074
56101
  * decode is always co-located with its frame consumer, so decode
@@ -60027,6 +60054,318 @@ DeviceType.Light, method(object({
60027
60054
  level: number().min(0).max(100),
60028
60055
  lastChangedAt: number()
60029
60056
  });
60057
+ /**
60058
+ * The role vocabulary of a remote (D963): what a button MEANS, independent of
60059
+ * how it is wired. A layout template places buttons by role; a device binds a
60060
+ * role to an action; an exporter asks for a role by name. Roles are shared so
60061
+ * one layout fits every appliance and one voice command fits every binding.
60062
+ *
60063
+ * A role outside this list is `custom:<key>` — the operator's own button (a
60064
+ * SmartIR source, a channel preset). The key is a lowercase slug.
60065
+ */
60066
+ var REMOTE_KNOWN_ROLES = [
60067
+ "power",
60068
+ "power_on",
60069
+ "power_off",
60070
+ "volume_up",
60071
+ "volume_down",
60072
+ "mute",
60073
+ "channel_up",
60074
+ "channel_down",
60075
+ "up",
60076
+ "down",
60077
+ "left",
60078
+ "right",
60079
+ "ok",
60080
+ "back",
60081
+ "home",
60082
+ "menu",
60083
+ "info",
60084
+ "guide",
60085
+ "exit",
60086
+ "input",
60087
+ "play",
60088
+ "pause",
60089
+ "play_pause",
60090
+ "stop",
60091
+ "next",
60092
+ "previous",
60093
+ "fast_forward",
60094
+ "rewind",
60095
+ "digit_0",
60096
+ "digit_1",
60097
+ "digit_2",
60098
+ "digit_3",
60099
+ "digit_4",
60100
+ "digit_5",
60101
+ "digit_6",
60102
+ "digit_7",
60103
+ "digit_8",
60104
+ "digit_9"
60105
+ ];
60106
+ var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
60107
+ var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
60108
+ function isRemoteKnownRole(value) {
60109
+ return KNOWN_ROLES.has(value);
60110
+ }
60111
+ function isRemoteCustomKey(key) {
60112
+ return CUSTOM_KEY_RE.test(key);
60113
+ }
60114
+ /** The key of a `custom:<key>` role, or null for any other string. */
60115
+ function customKeyOfRole(role) {
60116
+ if (!role.startsWith("custom:")) return null;
60117
+ const key = role.slice(7);
60118
+ return isRemoteCustomKey(key) ? key : null;
60119
+ }
60120
+ function isRemoteRole(value) {
60121
+ return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
60122
+ }
60123
+ var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
60124
+ var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
60125
+ var RemoteCustomButtonSchema = object({
60126
+ /** The slug after `custom:` in the role. */
60127
+ key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
60128
+ /** What the operator calls it, and what a voice command may name. */
60129
+ name: string().min(1).max(60)
60130
+ });
60131
+ var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
60132
+ var RemoteConfigSchema = object({
60133
+ layoutTemplateId: string().min(1).nullable(),
60134
+ /**
60135
+ * The transmitter device new bindings and an import send through. A binding
60136
+ * names its own device, so one command may override it.
60137
+ */
60138
+ defaultTransmitterId: number().int().nonnegative().nullable().default(null),
60139
+ /** Keyed by role. A role with no entry falls back to the native cap. */
60140
+ bindings: record(string(), RemoteBindingSchema),
60141
+ customButtons: array(RemoteCustomButtonSchema).max(100),
60142
+ power: object({ mode: RemotePowerModeSchema })
60143
+ }).superRefine((config, ctx) => {
60144
+ const roles = Object.keys(config.bindings);
60145
+ if (roles.length > 200) ctx.addIssue({
60146
+ code: "custom",
60147
+ message: "too many bindings",
60148
+ path: ["bindings"]
60149
+ });
60150
+ for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
60151
+ code: "custom",
60152
+ message: `"${role}" is not a role`,
60153
+ path: ["bindings", role]
60154
+ });
60155
+ const keys = /* @__PURE__ */ new Set();
60156
+ config.customButtons.forEach((button, index) => {
60157
+ if (keys.has(button.key)) ctx.addIssue({
60158
+ code: "custom",
60159
+ message: `custom button "${button.key}" is listed twice`,
60160
+ path: [
60161
+ "customButtons",
60162
+ index,
60163
+ "key"
60164
+ ]
60165
+ });
60166
+ keys.add(button.key);
60167
+ });
60168
+ for (const role of roles) {
60169
+ const key = customKeyOfRole(role);
60170
+ if (key !== null && !keys.has(key)) ctx.addIssue({
60171
+ code: "custom",
60172
+ message: `${role} is bound but has no entry in customButtons`,
60173
+ path: ["bindings", role]
60174
+ });
60175
+ }
60176
+ });
60177
+ object({
60178
+ power: object({
60179
+ state: _enum(["on", "off"]),
60180
+ at: number()
60181
+ }).nullable(),
60182
+ muted: object({
60183
+ value: boolean(),
60184
+ at: number()
60185
+ }).nullable()
60186
+ });
60187
+ var RemoteLayoutButtonSchema = object({
60188
+ id: string().min(1).max(40),
60189
+ /** 0-based grid position. */
60190
+ row: number().int().min(0).max(24),
60191
+ col: number().int().min(0).max(24),
60192
+ rowSpan: number().int().min(1).max(24),
60193
+ colSpan: number().int().min(1).max(24),
60194
+ icon: string().min(1).max(60).optional(),
60195
+ label: string().min(1).max(40).optional(),
60196
+ role: RemoteRoleSchema
60197
+ });
60198
+ object({
60199
+ columns: number().int().min(1).max(24),
60200
+ rows: number().int().min(1).max(24),
60201
+ buttons: array(RemoteLayoutButtonSchema).max(200)
60202
+ }).superRefine((grid, ctx) => {
60203
+ const ids = /* @__PURE__ */ new Set();
60204
+ const taken = /* @__PURE__ */ new Map();
60205
+ grid.buttons.forEach((button, index) => {
60206
+ if (ids.has(button.id)) ctx.addIssue({
60207
+ code: "custom",
60208
+ message: `button id "${button.id}" is used twice`,
60209
+ path: [
60210
+ "buttons",
60211
+ index,
60212
+ "id"
60213
+ ]
60214
+ });
60215
+ ids.add(button.id);
60216
+ if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
60217
+ ctx.addIssue({
60218
+ code: "custom",
60219
+ message: `button "${button.id}" leaves the grid`,
60220
+ path: ["buttons", index]
60221
+ });
60222
+ return;
60223
+ }
60224
+ for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
60225
+ const cell = `${r}:${c}`;
60226
+ const other = taken.get(cell);
60227
+ if (other !== void 0) {
60228
+ ctx.addIssue({
60229
+ code: "custom",
60230
+ message: `buttons "${other}" and "${button.id}" overlap`,
60231
+ path: ["buttons", index]
60232
+ });
60233
+ return;
60234
+ }
60235
+ taken.set(cell, button.id);
60236
+ }
60237
+ });
60238
+ });
60239
+ var RemoteBoundCommandSchema = object({
60240
+ role: RemoteRoleSchema,
60241
+ /** The operator's name for a custom button; absent for a known role. */
60242
+ label: string().optional(),
60243
+ custom: boolean()
60244
+ });
60245
+ var RemoteCommandListSchema = object({
60246
+ layoutTemplateId: string().nullable(),
60247
+ power: object({ mode: RemotePowerModeSchema }),
60248
+ /** Only the roles that are BOUND — the ones a press can reach. */
60249
+ commands: array(RemoteBoundCommandSchema)
60250
+ });
60251
+ var RemoteRefusalReasonSchema = _enum([
60252
+ "no-remote",
60253
+ "unbound-role",
60254
+ "action-failed"
60255
+ ]);
60256
+ var RemoteSendResultSchema = discriminatedUnion("status", [
60257
+ object({
60258
+ status: literal("sent"),
60259
+ /** How many times the action ran. */
60260
+ sends: number().int().min(1)
60261
+ }),
60262
+ object({
60263
+ status: literal("skipped"),
60264
+ reason: literal("already-in-state")
60265
+ }),
60266
+ object({
60267
+ status: literal("refused"),
60268
+ reason: RemoteRefusalReasonSchema,
60269
+ detail: string().optional()
60270
+ })
60271
+ ]);
60272
+ var RemoteMediaCommandSchema = _enum([
60273
+ "play",
60274
+ "pause",
60275
+ "stop",
60276
+ "next",
60277
+ "previous",
60278
+ "setMute",
60279
+ "selectSource"
60280
+ ]);
60281
+ /** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
60282
+ var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
60283
+ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
60284
+ status: literal("refused"),
60285
+ reason: _enum(["unknown-template", "not-a-media-player"]),
60286
+ detail: string()
60287
+ })]);
60288
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
60289
+ DeviceType.MediaPlayer;
60290
+ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
60291
+ kind: "mutation",
60292
+ auth: "admin"
60293
+ }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
60294
+ role: RemoteRoleSchema,
60295
+ /** Sends in a row (volume steps). */
60296
+ repeat: number().int().min(1).max(50).default(1)
60297
+ }), RemoteSendResultSchema, {
60298
+ kind: "mutation",
60299
+ auth: "protected"
60300
+ }), method(deviceInput.extend({
60301
+ command: RemoteMediaCommandSchema,
60302
+ muted: boolean().optional(),
60303
+ source: string().optional(),
60304
+ repeat: number().int().min(1).max(50).default(1)
60305
+ }), RemoteMediaCommandResultSchema, {
60306
+ kind: "mutation",
60307
+ auth: "protected"
60308
+ });
60309
+ var TemplateKindSchema = _enum(["remote"]);
60310
+ /**
60311
+ * template — the generic store of reusable, named operator data (D963). A
60312
+ * record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
60313
+ * button grid of a remote) and the schema each payload must satisfy are
60314
+ * declared by the system in `template-kinds.ts`. Nothing here is specific to a
60315
+ * kind: the engine validates a payload against its kind's schema and, on a
60316
+ * delete, asks the kind's owner whether anything still points at the record.
60317
+ *
60318
+ * Every refusal is a typed result — a UI says "unknown kind" or names the
60319
+ * devices that still use a template, without parsing text.
60320
+ */
60321
+ var TemplateRecordSchema = object({
60322
+ id: string().min(1),
60323
+ kind: TemplateKindSchema,
60324
+ name: string().min(1).max(120),
60325
+ /** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
60326
+ payload: unknown(),
60327
+ createdAt: number(),
60328
+ updatedAt: number()
60329
+ });
60330
+ var TemplateWriteResultSchema = discriminatedUnion("status", [object({
60331
+ status: literal("saved"),
60332
+ template: TemplateRecordSchema
60333
+ }), object({
60334
+ status: literal("refused"),
60335
+ reason: _enum(["invalid-payload", "not-found"]),
60336
+ detail: string()
60337
+ })]);
60338
+ /** A device still pointing at a template. */
60339
+ var TemplateUserSchema = object({
60340
+ deviceId: number().int().nonnegative(),
60341
+ name: string()
60342
+ });
60343
+ var TemplateDeleteResultSchema = discriminatedUnion("status", [
60344
+ object({ status: literal("deleted") }),
60345
+ object({ status: literal("not-found") }),
60346
+ object({
60347
+ status: literal("in-use"),
60348
+ devices: array(TemplateUserSchema).min(1)
60349
+ })
60350
+ ]);
60351
+ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
60352
+ kind: TemplateKindSchema,
60353
+ name: string().min(1).max(120),
60354
+ payload: unknown()
60355
+ }), TemplateWriteResultSchema, {
60356
+ kind: "mutation",
60357
+ auth: "admin"
60358
+ }), method(object({
60359
+ id: string().min(1),
60360
+ name: string().min(1).max(120).optional(),
60361
+ payload: unknown().optional()
60362
+ }), TemplateWriteResultSchema, {
60363
+ kind: "mutation",
60364
+ auth: "admin"
60365
+ }), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
60366
+ kind: "mutation",
60367
+ auth: "admin"
60368
+ });
60030
60369
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
60031
60370
  kind: "mutation",
60032
60371
  auth: "protected"
@@ -62785,9 +63124,35 @@ var MediaInfoSchema = object({
62785
63124
  /** Optional cover-art / thumbnail URL. */
62786
63125
  imageUrl: string().optional()
62787
63126
  });
63127
+ /**
63128
+ * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
63129
+ * the device said so. `assumed`: the driver has no feedback (infrared) and
63130
+ * tracks what it last SENT; it moves only after a send was accepted, and a
63131
+ * human at the real remote makes it wrong. A UI shows an assumed value as a
63132
+ * guess, never as a reading.
63133
+ */
63134
+ var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
63135
+ /** The fields of a media-player status a driver can only guess. */
63136
+ var MediaPlayerAssumedFieldSchema = _enum([
63137
+ "state",
63138
+ "volumeLevel",
63139
+ "isMuted",
63140
+ "source"
63141
+ ]);
62788
63142
  object({
62789
63143
  /** Playback lifecycle state. */
62790
63144
  state: MediaPlayerStateSchema,
63145
+ /**
63146
+ * Whether the state fields are readings or the driver's own bookkeeping.
63147
+ * A slice written before this field existed parses as `reported`.
63148
+ */
63149
+ stateSource: MediaPlayerStateSourceSchema.default("reported"),
63150
+ /**
63151
+ * Which fields are the driver's own guess. `stateSource` is `assumed` when
63152
+ * this is not empty; a field not listed here is a reading even then (power
63153
+ * from a running-state source, mute guessed from the last send).
63154
+ */
63155
+ assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
62791
63156
  /** Volume as 0..100 inclusive. Null when the device has no volume
62792
63157
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
62793
63158
  volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
@@ -64253,6 +64618,14 @@ object({
64253
64618
  feedSound: boolean(),
64254
64619
  /** Speaker / prompt volume level (device-scaled integer). */
64255
64620
  volume: number().meta(readingMeta("none")),
64621
+ /** Portion (grams) of the last manual feed sent from a single-hopper
64622
+ * stepper — the default the next manual feed starts from. Persisted per
64623
+ * device by the provider; null until a manual feed has been sent. */
64624
+ lastFeedGrams: gramsPortion.nullable(),
64625
+ /** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
64626
+ lastFeedHopper1: gramsPortion.nullable(),
64627
+ /** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
64628
+ lastFeedHopper2: gramsPortion.nullable(),
64256
64629
  /** Ms epoch when the slice was last refreshed from the cloud. */
64257
64630
  lastFetchedAt: number()
64258
64631
  });
@@ -69704,9 +70077,17 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
69704
70077
  kind: "mutation",
69705
70078
  auth: "admin"
69706
70079
  });
70080
+ discriminatedUnion("kind", [object({
70081
+ kind: literal("raw"),
70082
+ timings: array(number().int()),
70083
+ settings: TransmitSettingsSchema.optional()
70084
+ }), object({
70085
+ kind: literal("pronto"),
70086
+ code: string()
70087
+ })]);
69707
70088
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
69708
70089
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
69709
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
70090
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
69710
70091
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
69711
70092
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
69712
70093
  new Set(CAP_NAMES);
@@ -74557,6 +74938,13 @@ Object.freeze({
74557
74938
  access: "create",
74558
74939
  role: "admin"
74559
74940
  },
74941
+ "nodeRemovalHook.onNodeRemoved": {
74942
+ capName: "node-removal-hook",
74943
+ capScope: "system",
74944
+ addonId: null,
74945
+ access: "create",
74946
+ role: "admin"
74947
+ },
74560
74948
  "nodes.clusterAddonStatus": {
74561
74949
  capName: "nodes",
74562
74950
  capScope: "system",
@@ -77448,6 +77836,41 @@ Object.freeze({
77448
77836
  access: "create",
77449
77837
  role: "admin"
77450
77838
  },
77839
+ "remote.getConfig": {
77840
+ capName: "remote",
77841
+ capScope: "system",
77842
+ addonId: null,
77843
+ access: "view",
77844
+ role: "admin"
77845
+ },
77846
+ "remote.listCommands": {
77847
+ capName: "remote",
77848
+ capScope: "system",
77849
+ addonId: null,
77850
+ access: "view",
77851
+ role: "admin"
77852
+ },
77853
+ "remote.sendCommand": {
77854
+ capName: "remote",
77855
+ capScope: "system",
77856
+ addonId: null,
77857
+ access: "create",
77858
+ role: "admin"
77859
+ },
77860
+ "remote.sendMediaCommand": {
77861
+ capName: "remote",
77862
+ capScope: "system",
77863
+ addonId: null,
77864
+ access: "create",
77865
+ role: "admin"
77866
+ },
77867
+ "remote.setConfig": {
77868
+ capName: "remote",
77869
+ capScope: "system",
77870
+ addonId: null,
77871
+ access: "create",
77872
+ role: "admin"
77873
+ },
77451
77874
  "runningState.getStatus": {
77452
77875
  capName: "running-state",
77453
77876
  capScope: "device",
@@ -78638,6 +79061,41 @@ Object.freeze({
78638
79061
  access: "view",
78639
79062
  role: "viewer"
78640
79063
  },
79064
+ "template.create": {
79065
+ capName: "template",
79066
+ capScope: "system",
79067
+ addonId: null,
79068
+ access: "create",
79069
+ role: "admin"
79070
+ },
79071
+ "template.delete": {
79072
+ capName: "template",
79073
+ capScope: "system",
79074
+ addonId: null,
79075
+ access: "delete",
79076
+ role: "admin"
79077
+ },
79078
+ "template.get": {
79079
+ capName: "template",
79080
+ capScope: "system",
79081
+ addonId: null,
79082
+ access: "view",
79083
+ role: "admin"
79084
+ },
79085
+ "template.list": {
79086
+ capName: "template",
79087
+ capScope: "system",
79088
+ addonId: null,
79089
+ access: "view",
79090
+ role: "admin"
79091
+ },
79092
+ "template.update": {
79093
+ capName: "template",
79094
+ capScope: "system",
79095
+ addonId: null,
79096
+ access: "create",
79097
+ role: "admin"
79098
+ },
78641
79099
  "terminalSession.adoptLegacyMonitor": {
78642
79100
  capName: "terminal-session",
78643
79101
  capScope: "system",
@@ -83430,6 +83888,7 @@ var SYSTEM_CAP_VOCABULARY = {
83430
83888
  "device-manager": system("the device registry itself — rename, move and remove are administration"),
83431
83889
  "device-provider": system(INTERNAL),
83432
83890
  "device-removal-hook": system(INTERNAL),
83891
+ "node-removal-hook": system(INTERNAL),
83433
83892
  "device-state": system("the runtime-state mirror every trigger already reads through"),
83434
83893
  "display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
83435
83894
  "embedding-encoder": system(ML),
@@ -83469,6 +83928,7 @@ var SYSTEM_CAP_VOCABULARY = {
83469
83928
  "recording-archive": system(STORAGE),
83470
83929
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
83471
83930
  records: system(INTERNAL),
83931
+ remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
83472
83932
  "server-management": system(CLUSTER),
83473
83933
  "settings-store": system(INTERNAL),
83474
83934
  "smtp-provider": system(INTERNAL),
@@ -83481,6 +83941,7 @@ var SYSTEM_CAP_VOCABULARY = {
83481
83941
  "storage-provider": system(STORAGE),
83482
83942
  "stream-broker": system(PIPELINE),
83483
83943
  system: system(CLUSTER),
83944
+ template: system("operator-authored templates — edited in their own editor, never actuated"),
83484
83945
  "terminal-session": system(CLUSTER),
83485
83946
  toast: system(UI_PLUMBING),
83486
83947
  training: system(ML),
@@ -85935,6 +86396,31 @@ Object.freeze({
85935
86396
  form: "single",
85936
86397
  optional: true
85937
86398
  }],
86399
+ "remote.getConfig": [{
86400
+ name: "deviceId",
86401
+ form: "single",
86402
+ optional: false
86403
+ }],
86404
+ "remote.listCommands": [{
86405
+ name: "deviceId",
86406
+ form: "single",
86407
+ optional: false
86408
+ }],
86409
+ "remote.sendCommand": [{
86410
+ name: "deviceId",
86411
+ form: "single",
86412
+ optional: false
86413
+ }],
86414
+ "remote.sendMediaCommand": [{
86415
+ name: "deviceId",
86416
+ form: "single",
86417
+ optional: false
86418
+ }],
86419
+ "remote.setConfig": [{
86420
+ name: "deviceId",
86421
+ form: "single",
86422
+ optional: false
86423
+ }],
85938
86424
  "runningState.getStatus": [{
85939
86425
  name: "deviceId",
85940
86426
  form: "single",
@@ -89075,14 +89561,6 @@ var DEVICE_BACKEND_TO_FORMAT = {
89075
89561
  function deviceBackendToFormat(backend) {
89076
89562
  return DEVICE_BACKEND_TO_FORMAT[backend] ?? "onnx";
89077
89563
  }
89078
- discriminatedUnion("kind", [object({
89079
- kind: literal("raw"),
89080
- timings: array(number().int()),
89081
- settings: TransmitSettingsSchema.optional()
89082
- }), object({
89083
- kind: literal("pronto"),
89084
- code: string()
89085
- })]);
89086
89564
  object({
89087
89565
  manufacturer: string().optional(),
89088
89566
  supportedModels: array(string()).optional(),
@@ -100768,12 +101246,12 @@ function computeFrameSourceNodes(input) {
100768
101246
  //#endregion
100769
101247
  //#region src/node-topology.ts
100770
101248
  /**
100771
- * Default placement capability for a node when its stored flag is unset:
100772
- * the hub is capable of every role out of the box; every other node must be
100773
- * explicitly enabled.
101249
+ * A placement flag (detect, audio, ingest) is ON unless the operator stored an
101250
+ * explicit `false`. A newly paired agent has no stored settings and must still
101251
+ * join balancing (D965).
100774
101252
  */
100775
- function nodeCapabilityDefault(nodeId) {
100776
- return nodeId === "hub";
101253
+ function isRoleOn(flag) {
101254
+ return flag !== false;
100777
101255
  }
100778
101256
  var NodeTopologyService = class {
100779
101257
  deps;
@@ -100904,17 +101382,16 @@ var NodeTopologyService = class {
100904
101382
  this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$2(err) } });
100905
101383
  }
100906
101384
  const nodeIds = new Set(["hub"]);
100907
- for (const nodeId of Object.keys(blob)) if (typeof nodeId === "string" && nodeId.length > 0 && !nodeId.includes("/")) nodeIds.add(nodeId);
101385
+ for (const nodeId of [...Object.keys(blob), ...this.knownRunnerNodes]) if (typeof nodeId === "string" && nodeId.length > 0 && !nodeId.includes("/")) nodeIds.add(nodeId);
100908
101386
  const detect = [];
100909
101387
  const audio = [];
100910
101388
  const ingest = [];
100911
101389
  const reachableHosts = /* @__PURE__ */ new Map();
100912
- const capable = (flag, nodeId) => typeof flag === "boolean" ? flag : nodeCapabilityDefault(nodeId);
100913
101390
  for (const nodeId of nodeIds) {
100914
101391
  const settings = blob[nodeId];
100915
- if (capable(settings?.detect, nodeId)) detect.push(nodeId);
100916
- if (capable(settings?.audio, nodeId)) audio.push(nodeId);
100917
- if (capable(settings?.ingest, nodeId)) ingest.push(nodeId);
101392
+ if (isRoleOn(settings?.detect)) detect.push(nodeId);
101393
+ if (isRoleOn(settings?.audio)) audio.push(nodeId);
101394
+ if (isRoleOn(settings?.ingest)) ingest.push(nodeId);
100918
101395
  const host = settings?.reachableHost?.trim();
100919
101396
  if (host !== void 0 && host.length > 0) reachableHosts.set(nodeId, host);
100920
101397
  }
@@ -102292,7 +102769,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
102292
102769
  const deviceFormat = deviceKey ? parseDeviceKey(deviceKey).format : catalog.selectedEngine.format;
102293
102770
  const jumpFormats = [...new Set(jumpDeviceKeys.map((k) => parseDeviceKey(k).format))];
102294
102771
  const { config: resolvedBase, skipped, droppedRoots } = resolvePipelineWithSkips(cam, deviceFormat, catalog, jumpFormats);
102295
- if (skipped.length > 0) this.deps.logger.warn("pipeline steps gated on but not provisioned on this node — skipped", {
102772
+ if (skipped.length > 0) this.deps.logger.warn(`pipeline steps gated on but not provisioned on this node — skipped (node ${agentNodeId}, device ${deviceId})`, {
102296
102773
  tags: {
102297
102774
  deviceId,
102298
102775
  nodeId: agentNodeId
@@ -106653,6 +107130,12 @@ async function buildOrchestratorControllers(deps) {
106653
107130
  const unsubAgentOnlineEvent = deps.ctx().eventBus.subscribe({ category: EventCategory.AgentOnline }, (event) => {
106654
107131
  const nodeId = event.data.agentId;
106655
107132
  if (!nodeId.includes("/")) topology.addKnownRunnerNode(nodeId);
107133
+ if (!nodeId.includes("/")) topology.refreshNodeCapabilities().catch((err) => {
107134
+ deps.ctx().logger.warn("refreshNodeCapabilities after agent online failed", {
107135
+ tags: { nodeId },
107136
+ meta: { error: errMsg$2(err) }
107137
+ });
107138
+ });
106656
107139
  sliceGate.forgetNode(nodeId);
106657
107140
  sliceGate.refresh("agent-online");
106658
107141
  providerGate.forgetProcess(nodeId);
@@ -106950,6 +107433,11 @@ var VERB = {
106950
107433
  };
106951
107434
  var RebalanceSuggestion = class {
106952
107435
  deps;
107436
+ /** `name (id)` when a name distinct from the id is known, else the id. */
107437
+ async label(nodeId) {
107438
+ const name = await this.deps.nodeName(nodeId);
107439
+ return name === null || name.length === 0 || name === nodeId ? nodeId : `${name} (${nodeId})`;
107440
+ }
106953
107441
  timer = null;
106954
107442
  lastNote = null;
106955
107443
  notUsedRaised = /* @__PURE__ */ new Set();
@@ -106998,7 +107486,7 @@ var RebalanceSuggestion = class {
106998
107486
  return;
106999
107487
  }
107000
107488
  const note = this.lastNote;
107001
- const cause = note === null ? "The cluster changed" : `Node ${note.nodeId} ${VERB[note.kind]}`;
107489
+ const cause = note === null ? "The cluster changed" : `Node ${await this.label(note.nodeId)} ${VERB[note.kind]}`;
107002
107490
  const count = manual.moves.length;
107003
107491
  const now = this.deps.now();
107004
107492
  logger.info("rebalance suggestion: raising the alert", { meta: {
@@ -107042,7 +107530,7 @@ var RebalanceSuggestion = class {
107042
107530
  category: ALERT_CATEGORY,
107043
107531
  severity: "info",
107044
107532
  title: "Node is not used for detection",
107045
- message: `Node ${nodeId} is online but detection is not enabled on it, so no camera will be placed there.`,
107533
+ message: `Node ${await this.label(nodeId)} is online but detection is not enabled on it, so no camera will be placed there.`,
107046
107534
  action: {
107047
107535
  label: "Open node",
107048
107536
  href: `/system/pipeline?view=nodes&node=${encodeURIComponent(nodeId)}`,
@@ -109401,6 +109889,13 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
109401
109889
  }),
109402
109890
  enabledNodes: () => this.topology?.enabledNodes ?? [],
109403
109891
  runnerNodes: () => this.topology?.collectRunnerNodeIds() ?? [],
109892
+ nodeName: async (nodeId) => {
109893
+ try {
109894
+ return (await ctx.api.nodes.directory.query()).nodes.find((n) => n.id === nodeId)?.name ?? null;
109895
+ } catch {
109896
+ return null;
109897
+ }
109898
+ },
109404
109899
  alerts: {
109405
109900
  emit: (alert) => ctx.api.alerts.emit.mutate(alert),
109406
109901
  complete: (alertId) => ctx.api.alerts.update.mutate({