@camstack/addon-post-analysis 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 (48) hide show
  1. package/dist/embedding-encoder/index.js +1 -1
  2. package/dist/embedding-encoder/index.mjs +1 -1
  3. package/dist/enrichment-engine/index.js +75 -7
  4. package/dist/enrichment-engine/index.js.map +1 -1
  5. package/dist/enrichment-engine/index.mjs +75 -7
  6. package/dist/enrichment-engine/index.mjs.map +1 -1
  7. package/dist/{index-tm6O4bWa.mjs → index-CGAj-pkn.mjs} +531 -97
  8. package/dist/index-CGAj-pkn.mjs.map +1 -0
  9. package/dist/{index-DNpNyDJi.js → index-Dbx13pc7.js} +531 -97
  10. package/dist/index-Dbx13pc7.js.map +1 -0
  11. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/LiveStatsTab.d.ts +5 -0
  12. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/index.d.ts +2 -0
  13. package/dist/pipeline-analytics/@mf-types.zip +0 -0
  14. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +19 -0
  15. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BcWYbuKp.mjs +18 -0
  16. package/dist/pipeline-analytics/{__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-DuO9h7li.mjs → __mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-D-USVuHq.mjs} +4 -2
  17. package/dist/pipeline-analytics/{__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CmqNjq44.mjs → __mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-qQCPW8pT.mjs} +1 -1
  18. package/dist/pipeline-analytics/{__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CA8cCIEl.mjs → __mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-Bv9bYz9E.mjs} +1 -1
  19. package/dist/pipeline-analytics/_stub.js +497 -431
  20. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-C7e1vVcD.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D_pYr5G6.mjs} +6 -6
  21. package/dist/pipeline-analytics/{client-DdXDZxzK.mjs → client-DHmQcIWy.mjs} +2990 -3217
  22. package/dist/pipeline-analytics/{hostInit-TFbPssJX.mjs → hostInit-DXmzhNK7.mjs} +12 -12
  23. package/dist/pipeline-analytics/{index-DHiJ7A5x.mjs → index-BA65ZJOW.mjs} +1 -1
  24. package/dist/pipeline-analytics/{index-BP0-1QYT.mjs → index-BCEx31Mh.mjs} +3808 -3100
  25. package/dist/pipeline-analytics/{index-kIgjN-uq.mjs → index-CHnXxMRA.mjs} +1 -1
  26. package/dist/pipeline-analytics/index-CWkKuNLr.mjs +232 -0
  27. package/dist/pipeline-analytics/{index-B4OKsa9p.mjs → index-Crs1D0Uu.mjs} +1 -1
  28. package/dist/pipeline-analytics/{index-k0CA0h_r.mjs → index-DicaGC31.mjs} +1 -1
  29. package/dist/pipeline-analytics/index-gbflFMEY.mjs +36403 -0
  30. package/dist/pipeline-analytics/{index-DyYvUfc7.mjs → index-gpelkpEE.mjs} +1 -1
  31. package/dist/pipeline-analytics/index.js +73 -22
  32. package/dist/pipeline-analytics/index.js.map +1 -1
  33. package/dist/pipeline-analytics/index.mjs +73 -22
  34. package/dist/pipeline-analytics/index.mjs.map +1 -1
  35. package/dist/pipeline-analytics/{jsx-runtime-4ro1c69i.mjs → jsx-runtime-Wcfyyyt4.mjs} +1 -1
  36. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  37. package/dist/recording/index.js +2 -2
  38. package/dist/recording/index.mjs +2 -2
  39. package/dist/{recording-coordinator-BVFZsuQg.mjs → recording-coordinator-BjWd7HjD.mjs} +2 -2
  40. package/dist/{recording-coordinator-BVFZsuQg.mjs.map → recording-coordinator-BjWd7HjD.mjs.map} +1 -1
  41. package/dist/{recording-coordinator-CL3NPb1p.js → recording-coordinator-b-7Ast8s.js} +2 -2
  42. package/dist/{recording-coordinator-CL3NPb1p.js.map → recording-coordinator-b-7Ast8s.js.map} +1 -1
  43. package/package.json +5 -1
  44. package/dist/index-DNpNyDJi.js.map +0 -1
  45. package/dist/index-tm6O4bWa.mjs.map +0 -1
  46. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-CpCK52pE.mjs +0 -19
  47. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-DEj77u_A.mjs +0 -15
  48. package/dist/pipeline-analytics/index-BDPtaPA2.mjs +0 -20855
@@ -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;
@@ -5960,6 +5976,53 @@ const DecodedFrameSchema = object({
5960
5976
  format: _enum(["jpeg", "rgb", "bgr", "yuv420", "gray"]),
5961
5977
  timestamp: number()
5962
5978
  });
5979
+ const FrameHandleSchema = object({
5980
+ shmId: string(),
5981
+ slot: number().int().nonnegative(),
5982
+ seq: number().int().nonnegative(),
5983
+ width: number().int().positive(),
5984
+ height: number().int().positive(),
5985
+ format: _enum(["jpeg", "rgb", "bgr", "yuv420", "gray"]),
5986
+ pts: number(),
5987
+ byteLength: number().int().nonnegative(),
5988
+ nodeId: string(),
5989
+ slotCount: number().int().positive()
5990
+ });
5991
+ const FrameHandleFormatSchema = _enum(["rgb", "bgr", "yuv420", "gray"]);
5992
+ const SubscribeFramesInputSchema = object({
5993
+ brokerId: string(),
5994
+ format: FrameHandleFormatSchema,
5995
+ /**
5996
+ * Optional reader-side cadence hint in frames per second. The broker does
5997
+ * NOT throttle — latest-wins ring reads drop frames implicitly for a slow
5998
+ * consumer. The value is echoed back in `SubscribeFramesResult.maxFps` so
5999
+ * the consumer can pace its own `pullFrameHandles` polling.
6000
+ */
6001
+ maxFps: number().positive().optional(),
6002
+ /** Short caller-identity tag (`motion`, `detection`, …) for diagnostics. */
6003
+ tag: string().optional()
6004
+ });
6005
+ const SubscribeFramesResultSchema = object({
6006
+ /** Opaque id the consumer passes to `pullFrameHandles` / `unsubscribeFrames`. */
6007
+ subscriptionId: string(),
6008
+ /** Reader-side cadence hint (frames/s) — echoes `SubscribeFramesInput.maxFps`. */
6009
+ maxFps: number().nonnegative()
6010
+ });
6011
+ const DecodedAudioChunkSchema = object({
6012
+ data: _instanceof(Uint8Array),
6013
+ sampleRate: number().int().positive(),
6014
+ channels: number().int().positive(),
6015
+ timestamp: number()
6016
+ });
6017
+ const SubscribeAudioChunksInputSchema = object({
6018
+ brokerId: string(),
6019
+ /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
6020
+ tag: string().optional()
6021
+ });
6022
+ const SubscribeAudioChunksResultSchema = object({
6023
+ /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
6024
+ subscriptionId: string()
6025
+ });
5963
6026
  const BrokerStatusSchema$1 = _enum(["idle", "connecting", "streaming", "error", "stopped"]);
5964
6027
  const BrokerStatsSchema = object({
5965
6028
  status: BrokerStatusSchema$1,
@@ -6181,9 +6244,76 @@ const RtpSourceSchema = object({
6181
6244
  object({ released: boolean(), refcount: number().int().nonnegative() }),
6182
6245
  { kind: "mutation", auth: "admin" }
6183
6246
  ),
6184
- getBroker: method(
6185
- object({ brokerId: string() }),
6186
- custom()
6247
+ /**
6248
+ * ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
6249
+ *
6250
+ * The serialisable replacement for the live-object `IStreamBroker.
6251
+ * onDecodedAudioChunk` callback path. Unlike the video frame plane,
6252
+ * audio chunks are tiny (a ~500ms PCM window is a few KB) so they ship
6253
+ * their bytes INLINE over tRPC — no shared-memory ring. A consumer:
6254
+ *
6255
+ * 1. `subscribeAudioChunks({ brokerId, tag })` — the broker registers
6256
+ * a per-subscription bounded FIFO queue and returns a
6257
+ * `subscriptionId`.
6258
+ * 2. polls `pullAudioChunks({ subscriptionId, maxCount })` — drains
6259
+ * `DecodedAudioChunk[]` in arrival order (FIFO, no latest-wins
6260
+ * drop: an audio gap is audible / breaks an analysis window). The
6261
+ * queue is generously sized; it only drops its oldest chunk if a
6262
+ * truly stalled consumer lets it overflow.
6263
+ * 3. `unsubscribeAudioChunks({ subscriptionId })` on teardown.
6264
+ */
6265
+ subscribeAudioChunks: method(
6266
+ SubscribeAudioChunksInputSchema,
6267
+ SubscribeAudioChunksResultSchema,
6268
+ { kind: "mutation" }
6269
+ ),
6270
+ pullAudioChunks: method(
6271
+ object({
6272
+ subscriptionId: string(),
6273
+ maxCount: number().int().positive().default(8)
6274
+ }),
6275
+ array(DecodedAudioChunkSchema).readonly()
6276
+ ),
6277
+ unsubscribeAudioChunks: method(
6278
+ object({ subscriptionId: string() }),
6279
+ object({ released: boolean() }),
6280
+ { kind: "mutation" }
6281
+ ),
6282
+ /**
6283
+ * ── Shared-memory frame plane (Phase 5 / D9) ──────────────────────
6284
+ *
6285
+ * The handle-based replacement for the live-object `IStreamBroker.
6286
+ * onDecodedFrame` callback path. A consumer:
6287
+ *
6288
+ * 1. `subscribeFrames({ brokerId, format })` — the broker spins up
6289
+ * (or reuses) a `frameSink: 'shm'` decoder session producing that
6290
+ * `format` and returns a `subscriptionId`.
6291
+ * 2. polls `pullFrameHandles({ subscriptionId, maxCount })` — drains
6292
+ * zero-pixel `FrameHandle[]`; each handle is fed to a
6293
+ * `FrameRingReader` that opens the named shm segment and reads the
6294
+ * pixels back zero-copy.
6295
+ * 3. `unsubscribeFrames({ subscriptionId })` on teardown.
6296
+ *
6297
+ * The broker keeps one shm ring per `(brokerId, format)` actually
6298
+ * requested — no broker-side `sharp` conversion. fps throttling is
6299
+ * implicit (latest-wins ring reads drop frames for a slow consumer).
6300
+ */
6301
+ subscribeFrames: method(
6302
+ SubscribeFramesInputSchema,
6303
+ SubscribeFramesResultSchema,
6304
+ { kind: "mutation" }
6305
+ ),
6306
+ pullFrameHandles: method(
6307
+ object({
6308
+ subscriptionId: string(),
6309
+ maxCount: number().int().positive().default(4)
6310
+ }),
6311
+ array(FrameHandleSchema).readonly()
6312
+ ),
6313
+ unsubscribeFrames: method(
6314
+ object({ subscriptionId: string() }),
6315
+ object({ released: boolean() }),
6316
+ { kind: "mutation" }
6187
6317
  ),
6188
6318
  setPreBufferDuration: method(
6189
6319
  object({ brokerId: string(), seconds: number().min(0).max(30) }),
@@ -7261,6 +7391,34 @@ MotionTriggerStatusSchema.extend({
7261
7391
  }) }
7262
7392
  }
7263
7393
  });
7394
+ object({
7395
+ enabled: boolean(),
7396
+ sensitivity: number(),
7397
+ /** Row-major active-cell grid. Length = gridWidth*gridHeight (see getOptions). */
7398
+ cells: array(boolean()),
7399
+ lastFetchedAt: number()
7400
+ });
7401
+ const MotionZoneOptionsSchema = object({
7402
+ gridWidth: number(),
7403
+ gridHeight: number(),
7404
+ sensitivity: object({ min: number(), max: number(), step: number() })
7405
+ });
7406
+ const MotionZonePatchSchema = object({
7407
+ enabled: boolean().optional(),
7408
+ sensitivity: number().optional(),
7409
+ cells: array(boolean()).optional()
7410
+ });
7411
+ ({
7412
+ deviceTypes: [DeviceType.Camera],
7413
+ methods: {
7414
+ getOptions: method(object({ deviceId: number() }), MotionZoneOptionsSchema),
7415
+ setZone: method(
7416
+ object({ deviceId: number(), patch: MotionZonePatchSchema }),
7417
+ _void(),
7418
+ { kind: "mutation", auth: "admin" }
7419
+ )
7420
+ }
7421
+ });
7264
7422
  const AutotrackTargetTypeSchema = string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
7265
7423
  const PtzAutotrackSettingsSchema = object({
7266
7424
  targetType: AutotrackTargetTypeSchema,
@@ -7349,6 +7507,100 @@ PtzAutotrackStatusSchema.extend({
7349
7507
  }) }
7350
7508
  }
7351
7509
  });
7510
+ const StreamProfileSchema = _enum(["main", "sub", "ext"]);
7511
+ const StreamProfileConfigSchema = object({
7512
+ width: number(),
7513
+ height: number(),
7514
+ codec: _enum(["h264", "h265"]),
7515
+ framerate: number(),
7516
+ bitrate: number(),
7517
+ // kbps
7518
+ bitrateMode: _enum(["vbr", "cbr"]).optional(),
7519
+ encoderProfile: _enum(["high", "main", "baseline"]).optional(),
7520
+ gop: number().optional(),
7521
+ audio: boolean().optional()
7522
+ });
7523
+ object({
7524
+ /** Per-profile current config. A profile absent = the camera doesn't have it. */
7525
+ main: StreamProfileConfigSchema.optional(),
7526
+ sub: StreamProfileConfigSchema.optional(),
7527
+ ext: StreamProfileConfigSchema.optional(),
7528
+ lastFetchedAt: number()
7529
+ });
7530
+ const StreamProfileOptionsSchema = object({
7531
+ resolutions: array(object({ width: number(), height: number() })),
7532
+ codecs: array(_enum(["h264", "h265"])),
7533
+ framerates: array(number()),
7534
+ /** Allowed bitrate values (kbps). Empty if the camera takes a free range. */
7535
+ bitrates: array(number()),
7536
+ /** Optional [min,max] kbps when the camera accepts a continuous range. */
7537
+ bitrateRange: tuple([number(), number()]).optional(),
7538
+ supportsBitrateMode: boolean(),
7539
+ supportsEncoderProfile: boolean(),
7540
+ supportsGop: boolean(),
7541
+ /** Allowed GOP / keyframe-interval range, in seconds — drives the
7542
+ * I-frame-interval selector. Absent when the camera advertises GOP
7543
+ * support but no concrete range (callers then fall back to a free
7544
+ * numeric input). `{ min, max, step }` per the getOptions convention. */
7545
+ gop: object({ min: number(), max: number(), step: number() }).optional()
7546
+ });
7547
+ const StreamParamsOptionsSchema = object({
7548
+ main: StreamProfileOptionsSchema.optional(),
7549
+ sub: StreamProfileOptionsSchema.optional(),
7550
+ ext: StreamProfileOptionsSchema.optional()
7551
+ });
7552
+ const StreamProfilePatchSchema = object({
7553
+ width: number().optional(),
7554
+ height: number().optional(),
7555
+ codec: _enum(["h264", "h265"]).optional(),
7556
+ framerate: number().optional(),
7557
+ bitrate: number().optional(),
7558
+ bitrateMode: _enum(["vbr", "cbr"]).optional(),
7559
+ encoderProfile: _enum(["high", "main", "baseline"]).optional(),
7560
+ gop: number().optional(),
7561
+ audio: boolean().optional()
7562
+ });
7563
+ ({
7564
+ deviceTypes: [DeviceType.Camera],
7565
+ methods: {
7566
+ getOptions: method(
7567
+ object({ deviceId: number() }),
7568
+ StreamParamsOptionsSchema
7569
+ ),
7570
+ setProfile: method(
7571
+ object({
7572
+ deviceId: number(),
7573
+ profile: StreamProfileSchema,
7574
+ patch: StreamProfilePatchSchema
7575
+ }),
7576
+ _void(),
7577
+ { kind: "mutation", auth: "admin" }
7578
+ ),
7579
+ /**
7580
+ * Build the `ConfigUISchema` (admin-ui `ConfigFormBuilder` input
7581
+ * shape) for this camera's stream-encoder settings — one section per
7582
+ * profile (main / sub / ext) with the resolution / codec / framerate
7583
+ * / bitrate / bitrate-mode / encoder-profile / GOP controls the
7584
+ * firmware actually exposes.
7585
+ *
7586
+ * Driven by `getOptions` (camera-probed availability) + `getStatus`
7587
+ * (current per-profile config); each field's `default` is seeded
7588
+ * from the live config so the form renders the camera state in one
7589
+ * pass. Returns `null` when the camera exposes no configurable
7590
+ * stream property — the renderer then shows the unsupported message.
7591
+ *
7592
+ * Output is `z.unknown().nullable()` — the same convention every
7593
+ * other `ConfigUISchema`-returning cap method uses (`device-ops`,
7594
+ * `device-manager`); `ConfigUISchema` is a TS-only type with no
7595
+ * companion Zod schema, and a concrete object would collapse
7596
+ * unrelated AppRouter branches to `unknown` during codegen.
7597
+ */
7598
+ getConfigSchema: method(
7599
+ object({ deviceId: number() }),
7600
+ unknown().nullable()
7601
+ )
7602
+ }
7603
+ });
7352
7604
  object({
7353
7605
  on: boolean(),
7354
7606
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
@@ -8297,6 +8549,83 @@ const VersionOutputSchema = object({ version: string() });
8297
8549
  getVersion: method(_void(), VersionOutputSchema)
8298
8550
  }
8299
8551
  });
8552
+ const MethodAccessSchema = _enum(["view", "create", "delete"]);
8553
+ const AllowedProviderSchema = union([literal("*"), array(string())]);
8554
+ const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
8555
+ const CapScopeSchema = _enum(["device", "system"]);
8556
+ const TokenScopeSchema = discriminatedUnion("type", [
8557
+ object({
8558
+ type: literal("category"),
8559
+ target: CapScopeSchema,
8560
+ access: array(MethodAccessSchema).min(1)
8561
+ }),
8562
+ object({
8563
+ type: literal("capability"),
8564
+ target: string(),
8565
+ access: array(MethodAccessSchema).min(1)
8566
+ }),
8567
+ object({
8568
+ type: literal("addon"),
8569
+ target: string(),
8570
+ access: array(MethodAccessSchema).min(1)
8571
+ }),
8572
+ object({
8573
+ type: literal("device"),
8574
+ /**
8575
+ * One or more deviceIds (serialised as strings for wire-format
8576
+ * consistency with the rest of the union). Matcher accepts if
8577
+ * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
8578
+ * of one scope-per-device when granting access to a set of cameras.
8579
+ */
8580
+ targets: array(string()).min(1),
8581
+ access: array(MethodAccessSchema).min(1)
8582
+ })
8583
+ ]);
8584
+ object({
8585
+ id: string(),
8586
+ username: string(),
8587
+ passwordHash: string(),
8588
+ /**
8589
+ * Admin bypass. When true, the middleware skips the scope-access
8590
+ * check entirely. There is no other axis of privilege; the legacy
8591
+ * role enum collapsed onto this boolean in v2.
8592
+ */
8593
+ isAdmin: boolean().default(false),
8594
+ allowedProviders: AllowedProviderSchema,
8595
+ allowedDevices: AllowedDevicesSchema,
8596
+ /**
8597
+ * Scopes granted to this user. Admins bypass; their `scopes` is
8598
+ * ignored. Non-admins without scopes are locked out of every
8599
+ * protected call.
8600
+ */
8601
+ scopes: array(TokenScopeSchema).default([]),
8602
+ createdAt: number(),
8603
+ updatedAt: number()
8604
+ });
8605
+ object({
8606
+ id: string(),
8607
+ label: string(),
8608
+ isAdmin: boolean().default(false),
8609
+ allowedProviders: AllowedProviderSchema,
8610
+ allowedDevices: AllowedDevicesSchema,
8611
+ tokenHash: string(),
8612
+ tokenPrefix: string(),
8613
+ createdAt: number(),
8614
+ lastUsedAt: number().optional()
8615
+ });
8616
+ object({
8617
+ id: string(),
8618
+ userId: string(),
8619
+ name: string(),
8620
+ tokenHash: string(),
8621
+ tokenPrefix: string(),
8622
+ scopes: array(TokenScopeSchema),
8623
+ // SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
8624
+ // schema accepts both `null` (read from disk) and `undefined` (in-memory).
8625
+ expiresAt: number().nullish(),
8626
+ lastUsedAt: number().nullish(),
8627
+ createdAt: number()
8628
+ });
8300
8629
  const SsoBridgeClaimsSchema = object({
8301
8630
  userId: string(),
8302
8631
  username: string(),
@@ -8312,7 +8641,18 @@ const SsoBridgeClaimsSchema = object({
8312
8641
  * JWT WITHOUT verifying the signature — the hub re-verifies on every
8313
8642
  * inbound call so trust still rests with the signing hub.
8314
8643
  */
8315
- hubUrl: string().optional()
8644
+ hubUrl: string().optional(),
8645
+ /** Permission scopes baked into the token. Set by the OAuth
8646
+ * account-linking grant; absent on ordinary SSO-login tokens. */
8647
+ scopes: array(TokenScopeSchema).optional(),
8648
+ /** OAuth authorization-code binding — set only on `oauth-code` tokens. */
8649
+ redirectUri: string().optional(),
8650
+ integrationId: string().optional(),
8651
+ /** JWT ID — unique per issued code; consumed-set enforces single-use. */
8652
+ jti: string().optional(),
8653
+ /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
8654
+ * tokens so the verify path can check the session is not revoked. */
8655
+ sessionId: string().optional()
8316
8656
  });
8317
8657
  ({
8318
8658
  methods: {
@@ -8329,6 +8669,23 @@ const SsoBridgeClaimsSchema = object({
8329
8669
  )
8330
8670
  }
8331
8671
  });
8672
+ const OauthIntegrationDescriptorSchema = object({
8673
+ /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
8674
+ integrationId: string(),
8675
+ /** Human label rendered on the consent page. */
8676
+ displayName: string(),
8677
+ /** Scopes baked into every token issued for this integration. */
8678
+ requestedScopes: array(TokenScopeSchema),
8679
+ /** Allowed redirect_uri prefixes. /api/oauth2/authorize rejects any
8680
+ * redirect_uri that does not start with one of these. Required —
8681
+ * an empty list means the integration can never complete linking. */
8682
+ allowedRedirectPrefixes: array(string()).min(1)
8683
+ });
8684
+ ({
8685
+ methods: {
8686
+ getDescriptor: method(_void(), OauthIntegrationDescriptorSchema)
8687
+ }
8688
+ });
8332
8689
  const PasskeySummarySchema = object({
8333
8690
  credentialId: string(),
8334
8691
  label: string(),
@@ -8609,21 +8966,30 @@ const AddonPageDeclarationSchema = object({
8609
8966
  });
8610
8967
  const WidgetHostEnum = _enum(["device-tab", "dashboard", "integration-detail"]);
8611
8968
  const WidgetSizeEnum = _enum(["xs", "sm", "md", "lg", "xl"]);
8969
+ const WidgetRemoteSchema = object({
8970
+ remoteName: string(),
8971
+ exposedModule: string(),
8972
+ componentKey: string().optional()
8973
+ });
8612
8974
  const WidgetMetadataSchema = object({
8613
- /** Stable id within the addon — kebab-case. */
8614
- stableId: string(),
8975
+ // ── UiContribution core (kind:'remote') ──────────────────────────
8976
+ /** Primary host tab — `'dashboard'`, `'device-tab'`, or a device-detail tab id. */
8977
+ tab: string(),
8978
+ /** Optional sub-tab within `tab`. */
8979
+ subTab: string().optional(),
8615
8980
  /** Operator-facing label. */
8616
8981
  label: string(),
8982
+ /** Ordering within `(tab, subTab)`, ascending. */
8983
+ order: number().optional(),
8984
+ /** Always `'remote'` — a widget is a Module Federation remote. */
8985
+ kind: literal("remote"),
8986
+ /** MF remote descriptor. */
8987
+ remote: WidgetRemoteSchema,
8988
+ // ── Widget-only metadata ─────────────────────────────────────────
8989
+ /** Stable id within the addon — kebab-case. Equals `remote.componentKey`. */
8990
+ stableId: string(),
8617
8991
  description: string().optional(),
8618
8992
  icon: string().optional(),
8619
- /**
8620
- * Module Federation remote name — must match the `name` field on the
8621
- * widget addon's `federation()` plugin config. Used by the host's
8622
- * `<WidgetRegistryProvider>` to call `loadRemote('<remoteName>/widgets')`.
8623
- * Conventionally `addon_<addonid>_widgets` (snake_case; MF names
8624
- * cannot contain hyphens).
8625
- */
8626
- remoteName: string(),
8627
8993
  /**
8628
8994
  * Bundle filename inside the addon's `dist/` dir served at
8629
8995
  * `/api/addon-widgets/<addonId>/<bundle>`. With Module Federation
@@ -8632,9 +8998,9 @@ const WidgetMetadataSchema = object({
8632
8998
  * cache-buster URL without a separate filesystem stat.
8633
8999
  */
8634
9000
  bundle: string(),
8635
- /** Where the widget makes sense to render. */
9001
+ /** Every host the widget supports. The picker filters on this set. */
8636
9002
  hosts: array(WidgetHostEnum).readonly(),
8637
- /** Required props the host must supply. Validated at <WidgetSlot> mount. */
9003
+ /** Required props the host must supply. Validated at `<WidgetSlot>` mount. */
8638
9004
  requires: object({
8639
9005
  deviceContext: boolean().default(false),
8640
9006
  integrationContext: boolean().default(false)
@@ -8709,6 +9075,16 @@ const InvokeReplyEnvelopeSchema = object({
8709
9075
  invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
8710
9076
  }
8711
9077
  });
9078
+ const ShmRingStatsSchema = object({
9079
+ sessionId: string(),
9080
+ slotCount: number().int(),
9081
+ slotByteLength: number().int(),
9082
+ segmentBytes: number().int(),
9083
+ budgetMb: number().int(),
9084
+ framesWritten: number().int(),
9085
+ getFrameHits: number().int(),
9086
+ getFrameMisses: number().int()
9087
+ });
8712
9088
  ({
8713
9089
  methods: {
8714
9090
  // ── Discovery ─────────────────────────────────────────────────
@@ -8736,10 +9112,27 @@ const InvokeReplyEnvelopeSchema = object({
8736
9112
  url: string()
8737
9113
  }), _void()),
8738
9114
  // ── Output — polling-based frame retrieval ────────────────────
9115
+ // `pullFrames` drains the pixel `DecodedFrame[]` of a `frameSink:
9116
+ // 'callback'` session. `pullHandles` (Phase 5 / D9) drains the
9117
+ // zero-pixel `FrameHandle[]` of a `frameSink: 'shm'` session — the
9118
+ // broker hands each handle to a `FrameRingReader` that opens the
9119
+ // named segment and reads the pixels back zero-copy. A session is
9120
+ // one mode or the other; the unmatched method returns an empty
9121
+ // array.
8739
9122
  pullFrames: method(object({
8740
9123
  sessionId: string(),
8741
9124
  maxCount: number().default(1)
8742
9125
  }), array(DecodedFrameSchema)),
9126
+ pullHandles: method(object({
9127
+ sessionId: string(),
9128
+ maxCount: number().default(1)
9129
+ }), array(FrameHandleSchema)),
9130
+ // ── Frame fetch (Phase 5 / D9 downstream access) ──────────────
9131
+ // Read the pixels a FrameHandle refers to from this node's shm ring.
9132
+ // Returns null when the slot was already recycled (latest-wins).
9133
+ getFrame: method(object({ handle: FrameHandleSchema }), DecodedFrameSchema.nullable()),
9134
+ // shm ring usage stats for a session.
9135
+ getShmStats: method(object({ sessionId: string() }), ShmRingStatsSchema.nullable()),
8743
9136
  // ── Control ───────────────────────────────────────────────────
8744
9137
  updateConfig: method(object({
8745
9138
  sessionId: string(),
@@ -9911,8 +10304,8 @@ const DevicePersistConfigPayloadSchema = object({
9911
10304
  /**
9912
10305
  * Return the addon ids that declared a wrapper provider for `capName`.
9913
10306
  * Backs the device-bindings UI's wrapper-picker dropdown. Entries are
9914
- * sourced from `CapabilityRegistry.wrapperProviders`, populated at
9915
- * `ProviderRegistration.kind === 'wrapper'` time.
10307
+ * sourced from `CapabilityRegistry.wrapperProviders`, populated when
10308
+ * the cap definition declares `kind: 'wrapper'`.
9916
10309
  */
9917
10310
  listWrappersForCap: method(
9918
10311
  object({ capName: string() }),
@@ -10723,6 +11116,8 @@ const pipelineAnalyticsCapability = {
10723
11116
  name: "pipeline-analytics",
10724
11117
  scope: "device",
10725
11118
  mode: "singleton",
11119
+ kind: "wrapper",
11120
+ defaultActive: true,
10726
11121
  deviceTypes: [DeviceType.Camera],
10727
11122
  exposesDeviceSettings: true,
10728
11123
  methods: {
@@ -11009,6 +11404,18 @@ const PtzMoveCommandSchema = object({
11009
11404
  zoom: number().optional(),
11010
11405
  speed: number().optional()
11011
11406
  });
11407
+ PtzPositionSchema.extend({ autofocus: boolean() });
11408
+ const PtzOptionsSchema = object({
11409
+ hasPan: boolean(),
11410
+ hasTilt: boolean(),
11411
+ hasZoom: boolean(),
11412
+ supportsPresets: boolean(),
11413
+ /** Max number of named presets the camera supports, when known. */
11414
+ maxPresets: number().optional(),
11415
+ /** Whether the camera exposes a controllable autofocus toggle
11416
+ * (boolean `hasX` per the getOptions availability convention). */
11417
+ hasAutofocus: boolean()
11418
+ });
11012
11419
  ({
11013
11420
  deviceTypes: [DeviceType.Camera],
11014
11421
  methods: {
@@ -11036,6 +11443,20 @@ const PtzMoveCommandSchema = object({
11036
11443
  _void(),
11037
11444
  { kind: "mutation" }
11038
11445
  ),
11446
+ savePreset: method(
11447
+ object({ deviceId: number(), presetId: string(), name: string() }),
11448
+ _void(),
11449
+ { kind: "mutation", auth: "admin" }
11450
+ ),
11451
+ deletePreset: method(
11452
+ object({ deviceId: number(), presetId: string() }),
11453
+ _void(),
11454
+ { kind: "mutation", auth: "admin" }
11455
+ ),
11456
+ getOptions: method(
11457
+ object({ deviceId: number() }),
11458
+ PtzOptionsSchema
11459
+ ),
11039
11460
  goHome: method(
11040
11461
  object({ deviceId: number() }),
11041
11462
  _void(),
@@ -11050,6 +11471,13 @@ const PtzMoveCommandSchema = object({
11050
11471
  getPosition: method(
11051
11472
  object({ deviceId: number() }),
11052
11473
  PtzPositionSchema
11474
+ ),
11475
+ /** Toggle the camera's autofocus. Only meaningful when
11476
+ * `getOptions().hasAutofocus` is true. */
11477
+ setAutofocus: method(
11478
+ object({ deviceId: number(), enabled: boolean() }),
11479
+ _void(),
11480
+ { kind: "mutation" }
11053
11481
  )
11054
11482
  }
11055
11483
  });
@@ -11985,83 +12413,6 @@ const MeshStatusSchema = object({
11985
12413
  // tabs driven by this cap.
11986
12414
  }
11987
12415
  });
11988
- const MethodAccessSchema = _enum(["view", "create", "delete"]);
11989
- const AllowedProviderSchema = union([literal("*"), array(string())]);
11990
- const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
11991
- const CapScopeSchema = _enum(["device", "system"]);
11992
- const TokenScopeSchema = discriminatedUnion("type", [
11993
- object({
11994
- type: literal("category"),
11995
- target: CapScopeSchema,
11996
- access: array(MethodAccessSchema).min(1)
11997
- }),
11998
- object({
11999
- type: literal("capability"),
12000
- target: string(),
12001
- access: array(MethodAccessSchema).min(1)
12002
- }),
12003
- object({
12004
- type: literal("addon"),
12005
- target: string(),
12006
- access: array(MethodAccessSchema).min(1)
12007
- }),
12008
- object({
12009
- type: literal("device"),
12010
- /**
12011
- * One or more deviceIds (serialised as strings for wire-format
12012
- * consistency with the rest of the union). Matcher accepts if
12013
- * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
12014
- * of one scope-per-device when granting access to a set of cameras.
12015
- */
12016
- targets: array(string()).min(1),
12017
- access: array(MethodAccessSchema).min(1)
12018
- })
12019
- ]);
12020
- object({
12021
- id: string(),
12022
- username: string(),
12023
- passwordHash: string(),
12024
- /**
12025
- * Admin bypass. When true, the middleware skips the scope-access
12026
- * check entirely. There is no other axis of privilege; the legacy
12027
- * role enum collapsed onto this boolean in v2.
12028
- */
12029
- isAdmin: boolean().default(false),
12030
- allowedProviders: AllowedProviderSchema,
12031
- allowedDevices: AllowedDevicesSchema,
12032
- /**
12033
- * Scopes granted to this user. Admins bypass; their `scopes` is
12034
- * ignored. Non-admins without scopes are locked out of every
12035
- * protected call.
12036
- */
12037
- scopes: array(TokenScopeSchema).default([]),
12038
- createdAt: number(),
12039
- updatedAt: number()
12040
- });
12041
- object({
12042
- id: string(),
12043
- label: string(),
12044
- isAdmin: boolean().default(false),
12045
- allowedProviders: AllowedProviderSchema,
12046
- allowedDevices: AllowedDevicesSchema,
12047
- tokenHash: string(),
12048
- tokenPrefix: string(),
12049
- createdAt: number(),
12050
- lastUsedAt: number().optional()
12051
- });
12052
- object({
12053
- id: string(),
12054
- userId: string(),
12055
- name: string(),
12056
- tokenHash: string(),
12057
- tokenPrefix: string(),
12058
- scopes: array(TokenScopeSchema),
12059
- // SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
12060
- // schema accepts both `null` (read from disk) and `undefined` (in-memory).
12061
- expiresAt: number().nullish(),
12062
- lastUsedAt: number().nullish(),
12063
- createdAt: number()
12064
- });
12065
12416
  const UserSummarySchema = object({
12066
12417
  id: string(),
12067
12418
  username: string(),
@@ -12134,6 +12485,16 @@ const CreateScopedTokenResultSchema = object({
12134
12485
  token: string(),
12135
12486
  record: ScopedTokenSummarySchema
12136
12487
  });
12488
+ const OauthSessionSummarySchema = object({
12489
+ id: string(),
12490
+ userId: string(),
12491
+ username: string(),
12492
+ integrationId: string(),
12493
+ scopes: array(TokenScopeSchema),
12494
+ createdAt: number(),
12495
+ lastUsedAt: number(),
12496
+ revokedAt: number().nullable()
12497
+ });
12137
12498
  const TotpSetupResultSchema = object({
12138
12499
  secret: string(),
12139
12500
  otpauthUrl: string()
@@ -12209,6 +12570,66 @@ const TotpStatusSchema = object({
12209
12570
  object({ userId: string(), code: string() }),
12210
12571
  object({ valid: boolean() }),
12211
12572
  { kind: "mutation", access: "view" }
12573
+ ),
12574
+ // ── OAuth account-linking grant ────────────────────────────────
12575
+ //
12576
+ // Core's /oauth2/* endpoints delegate here. Tokens are sso-bridge
12577
+ // JWTs (kinds oauth-code / oauth-access / oauth-refresh) and ALWAYS
12578
+ // carry isAdmin:false — the operator login proves hub control; the
12579
+ // issued token is minimal (device scope only).
12580
+ oauthIssueCode: method(
12581
+ object({
12582
+ integrationId: string(),
12583
+ userId: string(),
12584
+ username: string(),
12585
+ scopes: array(TokenScopeSchema),
12586
+ redirectUri: string(),
12587
+ hubUrl: string()
12588
+ }),
12589
+ object({ code: string() }),
12590
+ { kind: "mutation", access: "create" }
12591
+ ),
12592
+ oauthExchangeCode: method(
12593
+ object({ code: string(), redirectUri: string() }),
12594
+ object({
12595
+ accessToken: string(),
12596
+ refreshToken: string(),
12597
+ expiresIn: number()
12598
+ }).nullable(),
12599
+ { kind: "mutation", access: "view" }
12600
+ ),
12601
+ oauthRefresh: method(
12602
+ object({ refreshToken: string() }),
12603
+ object({
12604
+ accessToken: string(),
12605
+ refreshToken: string(),
12606
+ expiresIn: number()
12607
+ }).nullable(),
12608
+ { kind: "mutation", access: "view" }
12609
+ ),
12610
+ oauthVerifyAccessToken: method(
12611
+ object({ token: string() }),
12612
+ object({
12613
+ userId: string(),
12614
+ username: string(),
12615
+ scopes: array(TokenScopeSchema)
12616
+ }).nullable(),
12617
+ { access: "view" }
12618
+ ),
12619
+ // ── OAuth linked-session management (Phase D) ──────────────────
12620
+ //
12621
+ // The admin UI lists active account-linking sessions and revokes
12622
+ // them; revocation makes the linked integration's tokens fail
12623
+ // verification immediately.
12624
+ listOauthSessions: method(
12625
+ _void(),
12626
+ array(OauthSessionSummarySchema),
12627
+ { auth: "admin" }
12628
+ ),
12629
+ revokeOauthSession: method(
12630
+ object({ id: string() }),
12631
+ object({ success: boolean() }),
12632
+ { kind: "mutation", auth: "admin", access: "delete" }
12212
12633
  )
12213
12634
  }
12214
12635
  });
@@ -12425,6 +12846,19 @@ const RenameNodeResultSchema = object({
12425
12846
  record(string(), ClusterAddonStatusEntrySchema),
12426
12847
  { auth: "admin" }
12427
12848
  ),
12849
+ getCapUsageGraph: method(
12850
+ object({
12851
+ windowSeconds: number().int().positive().max(300).default(60)
12852
+ }),
12853
+ array(object({
12854
+ callerAddonId: string(),
12855
+ providerAddonId: string(),
12856
+ capName: string(),
12857
+ callsPerMin: number(),
12858
+ lastCallAtMs: number()
12859
+ })).readonly(),
12860
+ { auth: "admin" }
12861
+ ),
12428
12862
  /**
12429
12863
  * Direct per-node addon listing — calls `$agent.status` on the target
12430
12864
  * node (or returns the hub registry for `nodeId === 'hub'`) and surfaces
@@ -13524,4 +13958,4 @@ export {
13524
13958
  tuple as t,
13525
13959
  zoneAnalyticsCapability as z
13526
13960
  };
13527
- //# sourceMappingURL=index-tm6O4bWa.mjs.map
13961
+ //# sourceMappingURL=index-CGAj-pkn.mjs.map