@camstack/addon-pipeline 0.1.14 → 0.1.16

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 (76) hide show
  1. package/dist/audio-analyzer/index.js +2 -4
  2. package/dist/audio-analyzer/index.js.map +1 -1
  3. package/dist/audio-analyzer/index.mjs +2 -4
  4. package/dist/audio-analyzer/index.mjs.map +1 -1
  5. package/dist/audio-codec-nodeav/index.js +1 -1
  6. package/dist/audio-codec-nodeav/index.mjs +1 -1
  7. package/dist/decoder-nodeav/index.js +552 -18
  8. package/dist/decoder-nodeav/index.js.map +1 -1
  9. package/dist/decoder-nodeav/index.mjs +553 -19
  10. package/dist/decoder-nodeav/index.mjs.map +1 -1
  11. package/dist/detection-pipeline/index.js +2 -4
  12. package/dist/detection-pipeline/index.js.map +1 -1
  13. package/dist/detection-pipeline/index.mjs +2 -4
  14. package/dist/detection-pipeline/index.mjs.map +1 -1
  15. package/dist/{index-DKh0uEve.mjs → index-CVzLrojg.mjs} +539 -97
  16. package/dist/index-CVzLrojg.mjs.map +1 -0
  17. package/dist/{index-CFPKrb2Y.js → index-p-6GfKOg.js} +539 -97
  18. package/dist/index-p-6GfKOg.js.map +1 -0
  19. package/dist/motion-wasm/index.js +2 -4
  20. package/dist/motion-wasm/index.js.map +1 -1
  21. package/dist/motion-wasm/index.mjs +2 -4
  22. package/dist/motion-wasm/index.mjs.map +1 -1
  23. package/dist/pipeline-runner/index.js +133 -54
  24. package/dist/pipeline-runner/index.js.map +1 -1
  25. package/dist/pipeline-runner/index.mjs +133 -54
  26. package/dist/pipeline-runner/index.mjs.map +1 -1
  27. package/dist/stream-broker/@mf-types.zip +0 -0
  28. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +19 -0
  29. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-B4l8Nb2y.mjs +20 -0
  30. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DePVYdid.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DAssX3h0.mjs} +4 -2
  31. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CBlCGyx5.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-DFoJJhpt.mjs} +1 -1
  32. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-DZchZKbW.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-x7XMEeuJ.mjs} +1 -1
  33. package/dist/stream-broker/_stub.js +2 -2
  34. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CqeKw-Ig.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-iA_8b8fz.mjs} +6 -6
  35. package/dist/stream-broker/{client-BK73l2KT.mjs → client-CZXrddDR.mjs} +2990 -3217
  36. package/dist/stream-broker/{hostInit-DkjoXTMb.mjs → hostInit-1sVsS6_a.mjs} +12 -12
  37. package/dist/stream-broker/{index-BP0-1QYT.mjs → index-BCEx31Mh.mjs} +3808 -3100
  38. package/dist/stream-broker/{index-lmXLeXy8.mjs → index-BvV3RVTZ.mjs} +1 -1
  39. package/dist/stream-broker/{index-IUYKHbxX.mjs → index-C0BzaWmB.mjs} +1 -1
  40. package/dist/stream-broker/index-CWkKuNLr.mjs +232 -0
  41. package/dist/stream-broker/{index-ns1fRD30.mjs → index-CZNxa0ad.mjs} +1 -1
  42. package/dist/stream-broker/index-Kb4xa8FX.mjs +36403 -0
  43. package/dist/stream-broker/{index-BxHaCH3N.mjs → index-KtR7Pp0O.mjs} +1 -1
  44. package/dist/stream-broker/{index-Ss9m7Jum.mjs → index-cYW01SNH.mjs} +1 -1
  45. package/dist/stream-broker/index.js +802 -541
  46. package/dist/stream-broker/index.js.map +1 -1
  47. package/dist/stream-broker/index.mjs +802 -519
  48. package/dist/stream-broker/index.mjs.map +1 -1
  49. package/dist/stream-broker/{jsx-runtime-ZdY5pIZz.mjs → jsx-runtime-B_evVsXl.mjs} +1 -1
  50. package/dist/stream-broker/remoteEntry.js +1 -1
  51. package/package.json +23 -31
  52. package/dist/index-CFPKrb2Y.js.map +0 -1
  53. package/dist/index-DKh0uEve.mjs.map +0 -1
  54. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-CpCK52pE.mjs +0 -19
  55. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BN3K4dM8.mjs +0 -20
  56. package/dist/stream-broker/index-DKercbDS.mjs +0 -20855
  57. package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
  58. package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
  59. package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
  60. package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
  61. package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
  62. package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
  63. package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
  64. package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
  65. package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
  66. package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
  67. package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
  68. package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
  69. package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
  70. package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
  71. package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
  72. package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
  73. package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
  74. package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
  75. package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
  76. package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
@@ -5067,6 +5067,7 @@ const WELL_KNOWN_TABS = [
5067
5067
  { id: "osd", label: "OSD", icon: "type", order: 18 },
5068
5068
  { id: "stream-broker", label: "Stream Broker", icon: "radio", order: 20 },
5069
5069
  { id: "streaming", label: "Streaming", icon: "video", order: 35 },
5070
+ { id: "ptz", label: "PTZ", icon: "move", order: 40 },
5070
5071
  { id: "pipeline", label: "Detection Pipeline", icon: "cpu", order: 39 },
5071
5072
  { id: "zones", label: "Detection", icon: "shapes", order: 38 },
5072
5073
  { id: "live-stats", label: "Live Stats", icon: "activity", order: 39 },
@@ -5134,6 +5135,9 @@ function hydrateField(field, values) {
5134
5135
  return { ...field, value: items };
5135
5136
  }
5136
5137
  const rawValue = storedValue !== void 0 ? storedValue : defaultValue !== void 0 ? defaultValue : null;
5138
+ if (field.type === "password") {
5139
+ return { ...field, value: "" };
5140
+ }
5137
5141
  const value = field.type === "textarea" && field.isJson && rawValue !== null && typeof rawValue === "object" ? JSON.stringify(rawValue, null, 2) : rawValue;
5138
5142
  const hydrated = { ...field, value };
5139
5143
  return hydrated;
@@ -5220,7 +5224,19 @@ const DecoderSessionConfigSchema = object({
5220
5224
  * on every line so `grep tag=broker:5/high` filters one camera
5221
5225
  * profile cleanly.
5222
5226
  */
5223
- tag: string().optional()
5227
+ tag: string().optional(),
5228
+ /**
5229
+ * Where the session delivers decoded frames (Phase 5 / D9):
5230
+ *
5231
+ * - `'callback'` (default) — the legacy pixel path: decoded frames are
5232
+ * buffered as `DecodedFrame`s and drained via `pullFrames`.
5233
+ * - `'shm'` — the shared-memory frame plane: decoded frames are written
5234
+ * into an OS shared-memory ring and drained as zero-pixel
5235
+ * `FrameHandle`s via `pullHandles`. A session is one mode or the
5236
+ * other — `pullFrames` returns nothing for an `'shm'` session and
5237
+ * `pullHandles` returns nothing for a `'callback'` session.
5238
+ */
5239
+ frameSink: _enum(["callback", "shm"]).default("callback")
5224
5240
  });
5225
5241
  function errMsg(err) {
5226
5242
  if (err instanceof Error) return err.message;
@@ -5986,6 +6002,53 @@ const DecodedFrameSchema = object({
5986
6002
  format: _enum(["jpeg", "rgb", "bgr", "yuv420", "gray"]),
5987
6003
  timestamp: number()
5988
6004
  });
6005
+ const FrameHandleSchema = object({
6006
+ shmId: string(),
6007
+ slot: number().int().nonnegative(),
6008
+ seq: number().int().nonnegative(),
6009
+ width: number().int().positive(),
6010
+ height: number().int().positive(),
6011
+ format: _enum(["jpeg", "rgb", "bgr", "yuv420", "gray"]),
6012
+ pts: number(),
6013
+ byteLength: number().int().nonnegative(),
6014
+ nodeId: string(),
6015
+ slotCount: number().int().positive()
6016
+ });
6017
+ const FrameHandleFormatSchema = _enum(["rgb", "bgr", "yuv420", "gray"]);
6018
+ const SubscribeFramesInputSchema = object({
6019
+ brokerId: string(),
6020
+ format: FrameHandleFormatSchema,
6021
+ /**
6022
+ * Optional reader-side cadence hint in frames per second. The broker does
6023
+ * NOT throttle — latest-wins ring reads drop frames implicitly for a slow
6024
+ * consumer. The value is echoed back in `SubscribeFramesResult.maxFps` so
6025
+ * the consumer can pace its own `pullFrameHandles` polling.
6026
+ */
6027
+ maxFps: number().positive().optional(),
6028
+ /** Short caller-identity tag (`motion`, `detection`, …) for diagnostics. */
6029
+ tag: string().optional()
6030
+ });
6031
+ const SubscribeFramesResultSchema = object({
6032
+ /** Opaque id the consumer passes to `pullFrameHandles` / `unsubscribeFrames`. */
6033
+ subscriptionId: string(),
6034
+ /** Reader-side cadence hint (frames/s) — echoes `SubscribeFramesInput.maxFps`. */
6035
+ maxFps: number().nonnegative()
6036
+ });
6037
+ const DecodedAudioChunkSchema = object({
6038
+ data: _instanceof(Uint8Array),
6039
+ sampleRate: number().int().positive(),
6040
+ channels: number().int().positive(),
6041
+ timestamp: number()
6042
+ });
6043
+ const SubscribeAudioChunksInputSchema = object({
6044
+ brokerId: string(),
6045
+ /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
6046
+ tag: string().optional()
6047
+ });
6048
+ const SubscribeAudioChunksResultSchema = object({
6049
+ /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
6050
+ subscriptionId: string()
6051
+ });
5989
6052
  const BrokerStatusSchema$1 = _enum(["idle", "connecting", "streaming", "error", "stopped"]);
5990
6053
  const BrokerStatsSchema = object({
5991
6054
  status: BrokerStatusSchema$1,
@@ -6211,9 +6274,76 @@ const streamBrokerCapability = {
6211
6274
  object({ released: boolean(), refcount: number().int().nonnegative() }),
6212
6275
  { kind: "mutation", auth: "admin" }
6213
6276
  ),
6214
- getBroker: method(
6215
- object({ brokerId: string() }),
6216
- custom()
6277
+ /**
6278
+ * ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
6279
+ *
6280
+ * The serialisable replacement for the live-object `IStreamBroker.
6281
+ * onDecodedAudioChunk` callback path. Unlike the video frame plane,
6282
+ * audio chunks are tiny (a ~500ms PCM window is a few KB) so they ship
6283
+ * their bytes INLINE over tRPC — no shared-memory ring. A consumer:
6284
+ *
6285
+ * 1. `subscribeAudioChunks({ brokerId, tag })` — the broker registers
6286
+ * a per-subscription bounded FIFO queue and returns a
6287
+ * `subscriptionId`.
6288
+ * 2. polls `pullAudioChunks({ subscriptionId, maxCount })` — drains
6289
+ * `DecodedAudioChunk[]` in arrival order (FIFO, no latest-wins
6290
+ * drop: an audio gap is audible / breaks an analysis window). The
6291
+ * queue is generously sized; it only drops its oldest chunk if a
6292
+ * truly stalled consumer lets it overflow.
6293
+ * 3. `unsubscribeAudioChunks({ subscriptionId })` on teardown.
6294
+ */
6295
+ subscribeAudioChunks: method(
6296
+ SubscribeAudioChunksInputSchema,
6297
+ SubscribeAudioChunksResultSchema,
6298
+ { kind: "mutation" }
6299
+ ),
6300
+ pullAudioChunks: method(
6301
+ object({
6302
+ subscriptionId: string(),
6303
+ maxCount: number().int().positive().default(8)
6304
+ }),
6305
+ array(DecodedAudioChunkSchema).readonly()
6306
+ ),
6307
+ unsubscribeAudioChunks: method(
6308
+ object({ subscriptionId: string() }),
6309
+ object({ released: boolean() }),
6310
+ { kind: "mutation" }
6311
+ ),
6312
+ /**
6313
+ * ── Shared-memory frame plane (Phase 5 / D9) ──────────────────────
6314
+ *
6315
+ * The handle-based replacement for the live-object `IStreamBroker.
6316
+ * onDecodedFrame` callback path. A consumer:
6317
+ *
6318
+ * 1. `subscribeFrames({ brokerId, format })` — the broker spins up
6319
+ * (or reuses) a `frameSink: 'shm'` decoder session producing that
6320
+ * `format` and returns a `subscriptionId`.
6321
+ * 2. polls `pullFrameHandles({ subscriptionId, maxCount })` — drains
6322
+ * zero-pixel `FrameHandle[]`; each handle is fed to a
6323
+ * `FrameRingReader` that opens the named shm segment and reads the
6324
+ * pixels back zero-copy.
6325
+ * 3. `unsubscribeFrames({ subscriptionId })` on teardown.
6326
+ *
6327
+ * The broker keeps one shm ring per `(brokerId, format)` actually
6328
+ * requested — no broker-side `sharp` conversion. fps throttling is
6329
+ * implicit (latest-wins ring reads drop frames for a slow consumer).
6330
+ */
6331
+ subscribeFrames: method(
6332
+ SubscribeFramesInputSchema,
6333
+ SubscribeFramesResultSchema,
6334
+ { kind: "mutation" }
6335
+ ),
6336
+ pullFrameHandles: method(
6337
+ object({
6338
+ subscriptionId: string(),
6339
+ maxCount: number().int().positive().default(4)
6340
+ }),
6341
+ array(FrameHandleSchema).readonly()
6342
+ ),
6343
+ unsubscribeFrames: method(
6344
+ object({ subscriptionId: string() }),
6345
+ object({ released: boolean() }),
6346
+ { kind: "mutation" }
6217
6347
  ),
6218
6348
  setPreBufferDuration: method(
6219
6349
  object({ brokerId: string(), seconds: number().min(0).max(30) }),
@@ -6269,6 +6399,8 @@ const cameraStreamsCapability = {
6269
6399
  name: "camera-streams",
6270
6400
  scope: "device",
6271
6401
  mode: "singleton",
6402
+ kind: "wrapper",
6403
+ defaultActive: true,
6272
6404
  deviceTypes: [DeviceType.Camera],
6273
6405
  methods: {
6274
6406
  getCameraStreams: method(
@@ -6534,6 +6666,8 @@ const motionDetectionCapability = {
6534
6666
  name: "motion-detection",
6535
6667
  scope: "device",
6536
6668
  mode: "singleton",
6669
+ kind: "wrapper",
6670
+ defaultActive: true,
6537
6671
  exposesDeviceSettings: true,
6538
6672
  methods: {
6539
6673
  analyze: method(
@@ -7304,6 +7438,34 @@ MotionTriggerStatusSchema.extend({
7304
7438
  }) }
7305
7439
  }
7306
7440
  });
7441
+ object({
7442
+ enabled: boolean(),
7443
+ sensitivity: number(),
7444
+ /** Row-major active-cell grid. Length = gridWidth*gridHeight (see getOptions). */
7445
+ cells: array(boolean()),
7446
+ lastFetchedAt: number()
7447
+ });
7448
+ const MotionZoneOptionsSchema = object({
7449
+ gridWidth: number(),
7450
+ gridHeight: number(),
7451
+ sensitivity: object({ min: number(), max: number(), step: number() })
7452
+ });
7453
+ const MotionZonePatchSchema = object({
7454
+ enabled: boolean().optional(),
7455
+ sensitivity: number().optional(),
7456
+ cells: array(boolean()).optional()
7457
+ });
7458
+ ({
7459
+ deviceTypes: [DeviceType.Camera],
7460
+ methods: {
7461
+ getOptions: method(object({ deviceId: number() }), MotionZoneOptionsSchema),
7462
+ setZone: method(
7463
+ object({ deviceId: number(), patch: MotionZonePatchSchema }),
7464
+ _void(),
7465
+ { kind: "mutation", auth: "admin" }
7466
+ )
7467
+ }
7468
+ });
7307
7469
  const AutotrackTargetTypeSchema = string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
7308
7470
  const PtzAutotrackSettingsSchema = object({
7309
7471
  targetType: AutotrackTargetTypeSchema,
@@ -7392,6 +7554,100 @@ PtzAutotrackStatusSchema.extend({
7392
7554
  }) }
7393
7555
  }
7394
7556
  });
7557
+ const StreamProfileSchema = _enum(["main", "sub", "ext"]);
7558
+ const StreamProfileConfigSchema = object({
7559
+ width: number(),
7560
+ height: number(),
7561
+ codec: _enum(["h264", "h265"]),
7562
+ framerate: number(),
7563
+ bitrate: number(),
7564
+ // kbps
7565
+ bitrateMode: _enum(["vbr", "cbr"]).optional(),
7566
+ encoderProfile: _enum(["high", "main", "baseline"]).optional(),
7567
+ gop: number().optional(),
7568
+ audio: boolean().optional()
7569
+ });
7570
+ object({
7571
+ /** Per-profile current config. A profile absent = the camera doesn't have it. */
7572
+ main: StreamProfileConfigSchema.optional(),
7573
+ sub: StreamProfileConfigSchema.optional(),
7574
+ ext: StreamProfileConfigSchema.optional(),
7575
+ lastFetchedAt: number()
7576
+ });
7577
+ const StreamProfileOptionsSchema = object({
7578
+ resolutions: array(object({ width: number(), height: number() })),
7579
+ codecs: array(_enum(["h264", "h265"])),
7580
+ framerates: array(number()),
7581
+ /** Allowed bitrate values (kbps). Empty if the camera takes a free range. */
7582
+ bitrates: array(number()),
7583
+ /** Optional [min,max] kbps when the camera accepts a continuous range. */
7584
+ bitrateRange: tuple([number(), number()]).optional(),
7585
+ supportsBitrateMode: boolean(),
7586
+ supportsEncoderProfile: boolean(),
7587
+ supportsGop: boolean(),
7588
+ /** Allowed GOP / keyframe-interval range, in seconds — drives the
7589
+ * I-frame-interval selector. Absent when the camera advertises GOP
7590
+ * support but no concrete range (callers then fall back to a free
7591
+ * numeric input). `{ min, max, step }` per the getOptions convention. */
7592
+ gop: object({ min: number(), max: number(), step: number() }).optional()
7593
+ });
7594
+ const StreamParamsOptionsSchema = object({
7595
+ main: StreamProfileOptionsSchema.optional(),
7596
+ sub: StreamProfileOptionsSchema.optional(),
7597
+ ext: StreamProfileOptionsSchema.optional()
7598
+ });
7599
+ const StreamProfilePatchSchema = object({
7600
+ width: number().optional(),
7601
+ height: number().optional(),
7602
+ codec: _enum(["h264", "h265"]).optional(),
7603
+ framerate: number().optional(),
7604
+ bitrate: number().optional(),
7605
+ bitrateMode: _enum(["vbr", "cbr"]).optional(),
7606
+ encoderProfile: _enum(["high", "main", "baseline"]).optional(),
7607
+ gop: number().optional(),
7608
+ audio: boolean().optional()
7609
+ });
7610
+ ({
7611
+ deviceTypes: [DeviceType.Camera],
7612
+ methods: {
7613
+ getOptions: method(
7614
+ object({ deviceId: number() }),
7615
+ StreamParamsOptionsSchema
7616
+ ),
7617
+ setProfile: method(
7618
+ object({
7619
+ deviceId: number(),
7620
+ profile: StreamProfileSchema,
7621
+ patch: StreamProfilePatchSchema
7622
+ }),
7623
+ _void(),
7624
+ { kind: "mutation", auth: "admin" }
7625
+ ),
7626
+ /**
7627
+ * Build the `ConfigUISchema` (admin-ui `ConfigFormBuilder` input
7628
+ * shape) for this camera's stream-encoder settings — one section per
7629
+ * profile (main / sub / ext) with the resolution / codec / framerate
7630
+ * / bitrate / bitrate-mode / encoder-profile / GOP controls the
7631
+ * firmware actually exposes.
7632
+ *
7633
+ * Driven by `getOptions` (camera-probed availability) + `getStatus`
7634
+ * (current per-profile config); each field's `default` is seeded
7635
+ * from the live config so the form renders the camera state in one
7636
+ * pass. Returns `null` when the camera exposes no configurable
7637
+ * stream property — the renderer then shows the unsupported message.
7638
+ *
7639
+ * Output is `z.unknown().nullable()` — the same convention every
7640
+ * other `ConfigUISchema`-returning cap method uses (`device-ops`,
7641
+ * `device-manager`); `ConfigUISchema` is a TS-only type with no
7642
+ * companion Zod schema, and a concrete object would collapse
7643
+ * unrelated AppRouter branches to `unknown` during codegen.
7644
+ */
7645
+ getConfigSchema: method(
7646
+ object({ deviceId: number() }),
7647
+ unknown().nullable()
7648
+ )
7649
+ }
7650
+ });
7395
7651
  object({
7396
7652
  on: boolean(),
7397
7653
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
@@ -8329,6 +8585,83 @@ const VersionOutputSchema = object({ version: string() });
8329
8585
  getVersion: method(_void(), VersionOutputSchema)
8330
8586
  }
8331
8587
  });
8588
+ const MethodAccessSchema = _enum(["view", "create", "delete"]);
8589
+ const AllowedProviderSchema = union([literal("*"), array(string())]);
8590
+ const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
8591
+ const CapScopeSchema = _enum(["device", "system"]);
8592
+ const TokenScopeSchema = discriminatedUnion("type", [
8593
+ object({
8594
+ type: literal("category"),
8595
+ target: CapScopeSchema,
8596
+ access: array(MethodAccessSchema).min(1)
8597
+ }),
8598
+ object({
8599
+ type: literal("capability"),
8600
+ target: string(),
8601
+ access: array(MethodAccessSchema).min(1)
8602
+ }),
8603
+ object({
8604
+ type: literal("addon"),
8605
+ target: string(),
8606
+ access: array(MethodAccessSchema).min(1)
8607
+ }),
8608
+ object({
8609
+ type: literal("device"),
8610
+ /**
8611
+ * One or more deviceIds (serialised as strings for wire-format
8612
+ * consistency with the rest of the union). Matcher accepts if
8613
+ * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
8614
+ * of one scope-per-device when granting access to a set of cameras.
8615
+ */
8616
+ targets: array(string()).min(1),
8617
+ access: array(MethodAccessSchema).min(1)
8618
+ })
8619
+ ]);
8620
+ object({
8621
+ id: string(),
8622
+ username: string(),
8623
+ passwordHash: string(),
8624
+ /**
8625
+ * Admin bypass. When true, the middleware skips the scope-access
8626
+ * check entirely. There is no other axis of privilege; the legacy
8627
+ * role enum collapsed onto this boolean in v2.
8628
+ */
8629
+ isAdmin: boolean().default(false),
8630
+ allowedProviders: AllowedProviderSchema,
8631
+ allowedDevices: AllowedDevicesSchema,
8632
+ /**
8633
+ * Scopes granted to this user. Admins bypass; their `scopes` is
8634
+ * ignored. Non-admins without scopes are locked out of every
8635
+ * protected call.
8636
+ */
8637
+ scopes: array(TokenScopeSchema).default([]),
8638
+ createdAt: number(),
8639
+ updatedAt: number()
8640
+ });
8641
+ object({
8642
+ id: string(),
8643
+ label: string(),
8644
+ isAdmin: boolean().default(false),
8645
+ allowedProviders: AllowedProviderSchema,
8646
+ allowedDevices: AllowedDevicesSchema,
8647
+ tokenHash: string(),
8648
+ tokenPrefix: string(),
8649
+ createdAt: number(),
8650
+ lastUsedAt: number().optional()
8651
+ });
8652
+ object({
8653
+ id: string(),
8654
+ userId: string(),
8655
+ name: string(),
8656
+ tokenHash: string(),
8657
+ tokenPrefix: string(),
8658
+ scopes: array(TokenScopeSchema),
8659
+ // SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
8660
+ // schema accepts both `null` (read from disk) and `undefined` (in-memory).
8661
+ expiresAt: number().nullish(),
8662
+ lastUsedAt: number().nullish(),
8663
+ createdAt: number()
8664
+ });
8332
8665
  const SsoBridgeClaimsSchema = object({
8333
8666
  userId: string(),
8334
8667
  username: string(),
@@ -8344,7 +8677,18 @@ const SsoBridgeClaimsSchema = object({
8344
8677
  * JWT WITHOUT verifying the signature — the hub re-verifies on every
8345
8678
  * inbound call so trust still rests with the signing hub.
8346
8679
  */
8347
- hubUrl: string().optional()
8680
+ hubUrl: string().optional(),
8681
+ /** Permission scopes baked into the token. Set by the OAuth
8682
+ * account-linking grant; absent on ordinary SSO-login tokens. */
8683
+ scopes: array(TokenScopeSchema).optional(),
8684
+ /** OAuth authorization-code binding — set only on `oauth-code` tokens. */
8685
+ redirectUri: string().optional(),
8686
+ integrationId: string().optional(),
8687
+ /** JWT ID — unique per issued code; consumed-set enforces single-use. */
8688
+ jti: string().optional(),
8689
+ /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
8690
+ * tokens so the verify path can check the session is not revoked. */
8691
+ sessionId: string().optional()
8348
8692
  });
8349
8693
  ({
8350
8694
  methods: {
@@ -8361,6 +8705,23 @@ const SsoBridgeClaimsSchema = object({
8361
8705
  )
8362
8706
  }
8363
8707
  });
8708
+ const OauthIntegrationDescriptorSchema = object({
8709
+ /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
8710
+ integrationId: string(),
8711
+ /** Human label rendered on the consent page. */
8712
+ displayName: string(),
8713
+ /** Scopes baked into every token issued for this integration. */
8714
+ requestedScopes: array(TokenScopeSchema),
8715
+ /** Allowed redirect_uri prefixes. /api/oauth2/authorize rejects any
8716
+ * redirect_uri that does not start with one of these. Required —
8717
+ * an empty list means the integration can never complete linking. */
8718
+ allowedRedirectPrefixes: array(string()).min(1)
8719
+ });
8720
+ ({
8721
+ methods: {
8722
+ getDescriptor: method(_void(), OauthIntegrationDescriptorSchema)
8723
+ }
8724
+ });
8364
8725
  const PasskeySummarySchema = object({
8365
8726
  credentialId: string(),
8366
8727
  label: string(),
@@ -8641,21 +9002,30 @@ const AddonPageDeclarationSchema = object({
8641
9002
  });
8642
9003
  const WidgetHostEnum = _enum(["device-tab", "dashboard", "integration-detail"]);
8643
9004
  const WidgetSizeEnum = _enum(["xs", "sm", "md", "lg", "xl"]);
9005
+ const WidgetRemoteSchema = object({
9006
+ remoteName: string(),
9007
+ exposedModule: string(),
9008
+ componentKey: string().optional()
9009
+ });
8644
9010
  const WidgetMetadataSchema = object({
8645
- /** Stable id within the addon — kebab-case. */
8646
- stableId: string(),
9011
+ // ── UiContribution core (kind:'remote') ──────────────────────────
9012
+ /** Primary host tab — `'dashboard'`, `'device-tab'`, or a device-detail tab id. */
9013
+ tab: string(),
9014
+ /** Optional sub-tab within `tab`. */
9015
+ subTab: string().optional(),
8647
9016
  /** Operator-facing label. */
8648
9017
  label: string(),
9018
+ /** Ordering within `(tab, subTab)`, ascending. */
9019
+ order: number().optional(),
9020
+ /** Always `'remote'` — a widget is a Module Federation remote. */
9021
+ kind: literal("remote"),
9022
+ /** MF remote descriptor. */
9023
+ remote: WidgetRemoteSchema,
9024
+ // ── Widget-only metadata ─────────────────────────────────────────
9025
+ /** Stable id within the addon — kebab-case. Equals `remote.componentKey`. */
9026
+ stableId: string(),
8649
9027
  description: string().optional(),
8650
9028
  icon: string().optional(),
8651
- /**
8652
- * Module Federation remote name — must match the `name` field on the
8653
- * widget addon's `federation()` plugin config. Used by the host's
8654
- * `<WidgetRegistryProvider>` to call `loadRemote('<remoteName>/widgets')`.
8655
- * Conventionally `addon_<addonid>_widgets` (snake_case; MF names
8656
- * cannot contain hyphens).
8657
- */
8658
- remoteName: string(),
8659
9029
  /**
8660
9030
  * Bundle filename inside the addon's `dist/` dir served at
8661
9031
  * `/api/addon-widgets/<addonId>/<bundle>`. With Module Federation
@@ -8664,9 +9034,9 @@ const WidgetMetadataSchema = object({
8664
9034
  * cache-buster URL without a separate filesystem stat.
8665
9035
  */
8666
9036
  bundle: string(),
8667
- /** Where the widget makes sense to render. */
9037
+ /** Every host the widget supports. The picker filters on this set. */
8668
9038
  hosts: array(WidgetHostEnum).readonly(),
8669
- /** Required props the host must supply. Validated at <WidgetSlot> mount. */
9039
+ /** Required props the host must supply. Validated at `<WidgetSlot>` mount. */
8670
9040
  requires: object({
8671
9041
  deviceContext: boolean().default(false),
8672
9042
  integrationContext: boolean().default(false)
@@ -8759,6 +9129,16 @@ const DEFAULT_DECODER_HWACCEL_CONFIG = {
8759
9129
  hwaccel: "auto",
8760
9130
  probedBestHwaccel: ""
8761
9131
  };
9132
+ const ShmRingStatsSchema = object({
9133
+ sessionId: string(),
9134
+ slotCount: number().int(),
9135
+ slotByteLength: number().int(),
9136
+ segmentBytes: number().int(),
9137
+ budgetMb: number().int(),
9138
+ framesWritten: number().int(),
9139
+ getFrameHits: number().int(),
9140
+ getFrameMisses: number().int()
9141
+ });
8762
9142
  const decoderCapability = {
8763
9143
  name: "decoder",
8764
9144
  scope: "system",
@@ -8795,10 +9175,27 @@ const decoderCapability = {
8795
9175
  url: string()
8796
9176
  }), _void()),
8797
9177
  // ── Output — polling-based frame retrieval ────────────────────
9178
+ // `pullFrames` drains the pixel `DecodedFrame[]` of a `frameSink:
9179
+ // 'callback'` session. `pullHandles` (Phase 5 / D9) drains the
9180
+ // zero-pixel `FrameHandle[]` of a `frameSink: 'shm'` session — the
9181
+ // broker hands each handle to a `FrameRingReader` that opens the
9182
+ // named segment and reads the pixels back zero-copy. A session is
9183
+ // one mode or the other; the unmatched method returns an empty
9184
+ // array.
8798
9185
  pullFrames: method(object({
8799
9186
  sessionId: string(),
8800
9187
  maxCount: number().default(1)
8801
9188
  }), array(DecodedFrameSchema)),
9189
+ pullHandles: method(object({
9190
+ sessionId: string(),
9191
+ maxCount: number().default(1)
9192
+ }), array(FrameHandleSchema)),
9193
+ // ── Frame fetch (Phase 5 / D9 downstream access) ──────────────
9194
+ // Read the pixels a FrameHandle refers to from this node's shm ring.
9195
+ // Returns null when the slot was already recycled (latest-wins).
9196
+ getFrame: method(object({ handle: FrameHandleSchema }), DecodedFrameSchema.nullable()),
9197
+ // shm ring usage stats for a session.
9198
+ getShmStats: method(object({ sessionId: string() }), ShmRingStatsSchema.nullable()),
8802
9199
  // ── Control ───────────────────────────────────────────────────
8803
9200
  updateConfig: method(object({
8804
9201
  sessionId: string(),
@@ -8943,6 +9340,8 @@ const webrtcSessionCapability = {
8943
9340
  name: "webrtc-session",
8944
9341
  scope: "device",
8945
9342
  mode: "singleton",
9343
+ kind: "wrapper",
9344
+ defaultActive: true,
8946
9345
  deviceTypes: [DeviceType.Camera],
8947
9346
  methods: {
8948
9347
  /**
@@ -9457,6 +9856,8 @@ const audioAnalysisCapability = {
9457
9856
  name: "audio-analysis",
9458
9857
  scope: "device",
9459
9858
  mode: "singleton",
9859
+ kind: "wrapper",
9860
+ defaultActive: true,
9460
9861
  deviceTypes: [DeviceType.Camera],
9461
9862
  exposesDeviceSettings: true,
9462
9863
  methods: {
@@ -9989,8 +10390,8 @@ const DevicePersistConfigPayloadSchema = object({
9989
10390
  /**
9990
10391
  * Return the addon ids that declared a wrapper provider for `capName`.
9991
10392
  * Backs the device-bindings UI's wrapper-picker dropdown. Entries are
9992
- * sourced from `CapabilityRegistry.wrapperProviders`, populated at
9993
- * `ProviderRegistration.kind === 'wrapper'` time.
10393
+ * sourced from `CapabilityRegistry.wrapperProviders`, populated when
10394
+ * the cap definition declares `kind: 'wrapper'`.
9994
10395
  */
9995
10396
  listWrappersForCap: method(
9996
10397
  object({ capName: string() }),
@@ -10695,6 +11096,8 @@ const detectionPipelineCapability = {
10695
11096
  name: "detection-pipeline",
10696
11097
  scope: "device",
10697
11098
  mode: "singleton",
11099
+ kind: "wrapper",
11100
+ defaultActive: true,
10698
11101
  deviceTypes: [DeviceType.Camera],
10699
11102
  exposesDeviceSettings: true,
10700
11103
  methods: {}
@@ -11085,6 +11488,18 @@ const PtzMoveCommandSchema = object({
11085
11488
  zoom: number().optional(),
11086
11489
  speed: number().optional()
11087
11490
  });
11491
+ PtzPositionSchema.extend({ autofocus: boolean() });
11492
+ const PtzOptionsSchema = object({
11493
+ hasPan: boolean(),
11494
+ hasTilt: boolean(),
11495
+ hasZoom: boolean(),
11496
+ supportsPresets: boolean(),
11497
+ /** Max number of named presets the camera supports, when known. */
11498
+ maxPresets: number().optional(),
11499
+ /** Whether the camera exposes a controllable autofocus toggle
11500
+ * (boolean `hasX` per the getOptions availability convention). */
11501
+ hasAutofocus: boolean()
11502
+ });
11088
11503
  ({
11089
11504
  deviceTypes: [DeviceType.Camera],
11090
11505
  methods: {
@@ -11112,6 +11527,20 @@ const PtzMoveCommandSchema = object({
11112
11527
  _void(),
11113
11528
  { kind: "mutation" }
11114
11529
  ),
11530
+ savePreset: method(
11531
+ object({ deviceId: number(), presetId: string(), name: string() }),
11532
+ _void(),
11533
+ { kind: "mutation", auth: "admin" }
11534
+ ),
11535
+ deletePreset: method(
11536
+ object({ deviceId: number(), presetId: string() }),
11537
+ _void(),
11538
+ { kind: "mutation", auth: "admin" }
11539
+ ),
11540
+ getOptions: method(
11541
+ object({ deviceId: number() }),
11542
+ PtzOptionsSchema
11543
+ ),
11115
11544
  goHome: method(
11116
11545
  object({ deviceId: number() }),
11117
11546
  _void(),
@@ -11126,6 +11555,13 @@ const PtzMoveCommandSchema = object({
11126
11555
  getPosition: method(
11127
11556
  object({ deviceId: number() }),
11128
11557
  PtzPositionSchema
11558
+ ),
11559
+ /** Toggle the camera's autofocus. Only meaningful when
11560
+ * `getOptions().hasAutofocus` is true. */
11561
+ setAutofocus: method(
11562
+ object({ deviceId: number(), enabled: boolean() }),
11563
+ _void(),
11564
+ { kind: "mutation" }
11129
11565
  )
11130
11566
  }
11131
11567
  });
@@ -12061,83 +12497,6 @@ const MeshStatusSchema = object({
12061
12497
  // tabs driven by this cap.
12062
12498
  }
12063
12499
  });
12064
- const MethodAccessSchema = _enum(["view", "create", "delete"]);
12065
- const AllowedProviderSchema = union([literal("*"), array(string())]);
12066
- const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
12067
- const CapScopeSchema = _enum(["device", "system"]);
12068
- const TokenScopeSchema = discriminatedUnion("type", [
12069
- object({
12070
- type: literal("category"),
12071
- target: CapScopeSchema,
12072
- access: array(MethodAccessSchema).min(1)
12073
- }),
12074
- object({
12075
- type: literal("capability"),
12076
- target: string(),
12077
- access: array(MethodAccessSchema).min(1)
12078
- }),
12079
- object({
12080
- type: literal("addon"),
12081
- target: string(),
12082
- access: array(MethodAccessSchema).min(1)
12083
- }),
12084
- object({
12085
- type: literal("device"),
12086
- /**
12087
- * One or more deviceIds (serialised as strings for wire-format
12088
- * consistency with the rest of the union). Matcher accepts if
12089
- * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
12090
- * of one scope-per-device when granting access to a set of cameras.
12091
- */
12092
- targets: array(string()).min(1),
12093
- access: array(MethodAccessSchema).min(1)
12094
- })
12095
- ]);
12096
- object({
12097
- id: string(),
12098
- username: string(),
12099
- passwordHash: string(),
12100
- /**
12101
- * Admin bypass. When true, the middleware skips the scope-access
12102
- * check entirely. There is no other axis of privilege; the legacy
12103
- * role enum collapsed onto this boolean in v2.
12104
- */
12105
- isAdmin: boolean().default(false),
12106
- allowedProviders: AllowedProviderSchema,
12107
- allowedDevices: AllowedDevicesSchema,
12108
- /**
12109
- * Scopes granted to this user. Admins bypass; their `scopes` is
12110
- * ignored. Non-admins without scopes are locked out of every
12111
- * protected call.
12112
- */
12113
- scopes: array(TokenScopeSchema).default([]),
12114
- createdAt: number(),
12115
- updatedAt: number()
12116
- });
12117
- object({
12118
- id: string(),
12119
- label: string(),
12120
- isAdmin: boolean().default(false),
12121
- allowedProviders: AllowedProviderSchema,
12122
- allowedDevices: AllowedDevicesSchema,
12123
- tokenHash: string(),
12124
- tokenPrefix: string(),
12125
- createdAt: number(),
12126
- lastUsedAt: number().optional()
12127
- });
12128
- object({
12129
- id: string(),
12130
- userId: string(),
12131
- name: string(),
12132
- tokenHash: string(),
12133
- tokenPrefix: string(),
12134
- scopes: array(TokenScopeSchema),
12135
- // SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
12136
- // schema accepts both `null` (read from disk) and `undefined` (in-memory).
12137
- expiresAt: number().nullish(),
12138
- lastUsedAt: number().nullish(),
12139
- createdAt: number()
12140
- });
12141
12500
  const UserSummarySchema = object({
12142
12501
  id: string(),
12143
12502
  username: string(),
@@ -12210,6 +12569,16 @@ const CreateScopedTokenResultSchema = object({
12210
12569
  token: string(),
12211
12570
  record: ScopedTokenSummarySchema
12212
12571
  });
12572
+ const OauthSessionSummarySchema = object({
12573
+ id: string(),
12574
+ userId: string(),
12575
+ username: string(),
12576
+ integrationId: string(),
12577
+ scopes: array(TokenScopeSchema),
12578
+ createdAt: number(),
12579
+ lastUsedAt: number(),
12580
+ revokedAt: number().nullable()
12581
+ });
12213
12582
  const TotpSetupResultSchema = object({
12214
12583
  secret: string(),
12215
12584
  otpauthUrl: string()
@@ -12285,6 +12654,66 @@ const TotpStatusSchema = object({
12285
12654
  object({ userId: string(), code: string() }),
12286
12655
  object({ valid: boolean() }),
12287
12656
  { kind: "mutation", access: "view" }
12657
+ ),
12658
+ // ── OAuth account-linking grant ────────────────────────────────
12659
+ //
12660
+ // Core's /oauth2/* endpoints delegate here. Tokens are sso-bridge
12661
+ // JWTs (kinds oauth-code / oauth-access / oauth-refresh) and ALWAYS
12662
+ // carry isAdmin:false — the operator login proves hub control; the
12663
+ // issued token is minimal (device scope only).
12664
+ oauthIssueCode: method(
12665
+ object({
12666
+ integrationId: string(),
12667
+ userId: string(),
12668
+ username: string(),
12669
+ scopes: array(TokenScopeSchema),
12670
+ redirectUri: string(),
12671
+ hubUrl: string()
12672
+ }),
12673
+ object({ code: string() }),
12674
+ { kind: "mutation", access: "create" }
12675
+ ),
12676
+ oauthExchangeCode: method(
12677
+ object({ code: string(), redirectUri: string() }),
12678
+ object({
12679
+ accessToken: string(),
12680
+ refreshToken: string(),
12681
+ expiresIn: number()
12682
+ }).nullable(),
12683
+ { kind: "mutation", access: "view" }
12684
+ ),
12685
+ oauthRefresh: method(
12686
+ object({ refreshToken: string() }),
12687
+ object({
12688
+ accessToken: string(),
12689
+ refreshToken: string(),
12690
+ expiresIn: number()
12691
+ }).nullable(),
12692
+ { kind: "mutation", access: "view" }
12693
+ ),
12694
+ oauthVerifyAccessToken: method(
12695
+ object({ token: string() }),
12696
+ object({
12697
+ userId: string(),
12698
+ username: string(),
12699
+ scopes: array(TokenScopeSchema)
12700
+ }).nullable(),
12701
+ { access: "view" }
12702
+ ),
12703
+ // ── OAuth linked-session management (Phase D) ──────────────────
12704
+ //
12705
+ // The admin UI lists active account-linking sessions and revokes
12706
+ // them; revocation makes the linked integration's tokens fail
12707
+ // verification immediately.
12708
+ listOauthSessions: method(
12709
+ _void(),
12710
+ array(OauthSessionSummarySchema),
12711
+ { auth: "admin" }
12712
+ ),
12713
+ revokeOauthSession: method(
12714
+ object({ id: string() }),
12715
+ object({ success: boolean() }),
12716
+ { kind: "mutation", auth: "admin", access: "delete" }
12288
12717
  )
12289
12718
  }
12290
12719
  });
@@ -12501,6 +12930,19 @@ const RenameNodeResultSchema = object({
12501
12930
  record(string(), ClusterAddonStatusEntrySchema),
12502
12931
  { auth: "admin" }
12503
12932
  ),
12933
+ getCapUsageGraph: method(
12934
+ object({
12935
+ windowSeconds: number().int().positive().max(300).default(60)
12936
+ }),
12937
+ array(object({
12938
+ callerAddonId: string(),
12939
+ providerAddonId: string(),
12940
+ capName: string(),
12941
+ callsPerMin: number(),
12942
+ lastCallAtMs: number()
12943
+ })).readonly(),
12944
+ { auth: "admin" }
12945
+ ),
12504
12946
  /**
12505
12947
  * Direct per-node addon listing — calls `$agent.status` on the target
12506
12948
  * node (or returns the hub registry for `nodeId === 'hub'`) and surfaces
@@ -13826,4 +14268,4 @@ export {
13826
14268
  DEFAULT_AUDIO_ANALYZER_CONFIG as y,
13827
14269
  AUDIO_BACKEND_CHOICES as z
13828
14270
  };
13829
- //# sourceMappingURL=index-DKh0uEve.mjs.map
14271
+ //# sourceMappingURL=index-CVzLrojg.mjs.map