@camstack/addon-post-analysis 1.2.341 → 1.2.343

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 (15) hide show
  1. package/dist/{clip-model-registry-BOAYW16o.js → clip-model-registry-Cd_y9S4U.js} +382 -8
  2. package/dist/{clip-model-registry-BP-BF3CK.mjs → clip-model-registry-DZz3e781.mjs} +371 -9
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B2hR0cv6.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DrdO3Pxp.mjs} +3 -3
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DEq-B6oq.mjs +26 -0
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CiTUMvVf.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-BgudaQ7W.mjs → hostInit-DPNnWr-m.mjs} +3 -3
  10. package/dist/pipeline-analytics/index.js +876 -239
  11. package/dist/pipeline-analytics/index.mjs +868 -231
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +2 -2
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B9vPD32p.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CY24cHgW.mjs +0 -26
@@ -5473,7 +5473,7 @@ var ZodIssueCode = {
5473
5473
  var ZodFirstPartyTypeKind;
5474
5474
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5475
5475
  //#endregion
5476
- //#region ../types/dist/sleep-BNeXenvr.mjs
5476
+ //#region ../types/dist/sleep-B0JG7-_F.mjs
5477
5477
  /**
5478
5478
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5479
5479
  * window to float samples (D455).
@@ -17437,6 +17437,10 @@ var DeviceInfoSchema = object({
17437
17437
  isCamera: boolean(),
17438
17438
  /** Current config values — serializable snapshot */
17439
17439
  config: record(string(), unknown()),
17440
+ /** Paths inside `config` the device's schema declares secret (D783) —
17441
+ * what the non-admin redaction withholds. Paths, never values. Absent
17442
+ * when the owning runner has not reported them. */
17443
+ secretConfigPaths: array(string()).optional(),
17440
17444
  /** Hardware + identity blob (manufacturer / model / firmware / sn /
17441
17445
  * uid / mac / …). Populated by drivers; editable via setMetadata. */
17442
17446
  metadata: record(string(), unknown()).nullable().optional(),
@@ -17657,7 +17661,13 @@ var DeviceRegisterPayloadSchema = object({
17657
17661
  * call (workers re-register on init), so probe-driven updates
17658
17662
  * (e.g. PTZ flag flipping after the abilities probe) propagate. */
17659
17663
  features: array(string()).optional(),
17660
- config: record(string(), unknown())
17664
+ config: record(string(), unknown()),
17665
+ /** Config paths the device's own schema DECLARES secret
17666
+ * (`.meta({ secret: true })` — dotted, `[]` for array items, e.g.
17667
+ * `connection.password`). Re-published on every register; the non-admin
17668
+ * redaction withholds them at any depth (D783). Optional: an older runner
17669
+ * omits it and the keyword backstop applies. */
17670
+ secretConfigPaths: array(string()).optional()
17661
17671
  });
17662
17672
  var DeviceRemovePayloadSchema = object({ deviceId: number() });
17663
17673
  var DevicePersistConfigPayloadSchema = object({
@@ -20401,7 +20411,14 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
20401
20411
  icon: string().optional(),
20402
20412
  /** Addon-owned HTTP route the button navigates to (GET). */
20403
20413
  startUrl: string(),
20404
- stage: LoginStageEnum
20414
+ stage: LoginStageEnum,
20415
+ /**
20416
+ * The provider names an external identity a signed-in user can link to
20417
+ * their own account (D782): `auth.listLinkableProviders` /
20418
+ * `beginExternalLink` offer exactly the redirect methods that set this,
20419
+ * whatever their id. Absent / false — a login button only (magic-link).
20420
+ */
20421
+ linkable: boolean().optional()
20405
20422
  }),
20406
20423
  object({
20407
20424
  kind: literal("widget"),
@@ -26986,7 +27003,13 @@ var RetrainFrameSchema = object({
26986
27003
  sizeBytes: number().int(),
26987
27004
  width: number().int(),
26988
27005
  height: number().int(),
26989
- copiedAt: number()
27006
+ copiedAt: number(),
27007
+ /** When the CAMERA captured the frame. Absent on a copy taken before D785. */
27008
+ capturedAt: number().optional(),
27009
+ /** The operator's "every subject in this frame is boxed". Detector
27010
+ * evaluation keeps only complete frames: an unboxed subject would count as
27011
+ * a false positive. */
27012
+ complete: boolean()
26990
27013
  });
26991
27014
  /** Why a copy-on-select could not be honoured — named, never a silent skip. */
26992
27015
  var RetrainCopyRefusalSchema = _enum([
@@ -27007,6 +27030,51 @@ var RetrainFrameListSchema = object({
27007
27030
  /** What the page pre-selects — the native key frame when one survives. */
27008
27031
  autoPickMediaId: number().int().optional()
27009
27032
  });
27033
+ /**
27034
+ * Which frames an annotated export carries. Every field narrows; an empty
27035
+ * filter is every annotated copy. A dataset VERSION is a frozen `frameIds`
27036
+ * list owned by whoever trains (the training orchestrator), so `frameIds` is
27037
+ * the primitive a version export uses; `batchId` re-selects what an earlier
27038
+ * export carried (`exportedInBatch`).
27039
+ */
27040
+ var RetrainExportFilterSchema = object({
27041
+ deviceIds: array(number()).optional(),
27042
+ frameIds: array(string()).max(2e4).optional(),
27043
+ batchId: string().optional(),
27044
+ /** Only frames the operator marked complete (all subjects boxed). */
27045
+ completeOnly: boolean().optional()
27046
+ });
27047
+ /**
27048
+ * `frames`: the full frame copies, boxes in `annotations.json`.
27049
+ * `crops`: additionally one JPEG per subject, cut with the runtime detail-crop
27050
+ * convention — the classifier layout.
27051
+ */
27052
+ var RetrainExportLayoutSchema = _enum(["frames", "crops"]);
27053
+ /**
27054
+ * Where `writeRetrainArchive` streams the tar: one HTTP request whose body is
27055
+ * the archive, sent chunk by chunk. Covers a presigned PUT and a provider
27056
+ * addon's data-plane upload route alike. Headers are sent verbatim and never
27057
+ * logged.
27058
+ */
27059
+ var RetrainArchiveTargetSchema = object({
27060
+ kind: literal("http"),
27061
+ method: _enum(["PUT", "POST"]),
27062
+ url: string().url(),
27063
+ headers: record(string(), string()).optional()
27064
+ });
27065
+ var RetrainArchiveResultSchema = object({
27066
+ batchId: string(),
27067
+ frames: number().int(),
27068
+ crops: number().int(),
27069
+ annotations: number().int(),
27070
+ /** Frames planned but not shipped (unreadable copy, path too long). */
27071
+ missing: number().int(),
27072
+ bytes: number().int(),
27073
+ /** sha256 of the exact bytes sent. */
27074
+ sha256: string(),
27075
+ /** The target's HTTP status. */
27076
+ status: number().int()
27077
+ });
27010
27078
  /** What the operator asked the assist to look for. */
27011
27079
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
27012
27080
  kind: literal("package"),
@@ -28376,11 +28444,41 @@ var pipelineAnalyticsCapability = {
28376
28444
  * out, so two crops of the same subject can never end up in one feature
28377
28445
  * space (D52).
28378
28446
  */
28379
- getRetrainExportUrl: method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
28447
+ getRetrainExportUrl: method(RetrainExportFilterSchema.extend({ layout: RetrainExportLayoutSchema.optional() }), object({ url: string() }), {
28380
28448
  kind: "query",
28381
28449
  auth: "admin"
28382
28450
  }),
28383
28451
  /**
28452
+ * The operator's "every subject in this frame is boxed" — set and cleared
28453
+ * by hand, never derived: a frame with no box may be a complete hard
28454
+ * negative, and a frame with three boxes may still miss a fourth person.
28455
+ */
28456
+ setRetrainFrameComplete: method(object({
28457
+ deviceId: number(),
28458
+ trackId: string(),
28459
+ frameId: string(),
28460
+ complete: boolean()
28461
+ }), RetrainFrameSchema, {
28462
+ kind: "mutation",
28463
+ auth: "admin"
28464
+ }),
28465
+ /**
28466
+ * Stream the annotated archive to a TARGET instead of a browser — the
28467
+ * same tar `getRetrainExportUrl` serves (manifest first, one JPEG in
28468
+ * memory at a time, paced), written chunk by chunk into one HTTP request.
28469
+ * The bytes never cross this cap and never land on the hub's disk; the
28470
+ * answer is what was sent, with its sha256.
28471
+ */
28472
+ writeRetrainArchive: method(object({
28473
+ filter: RetrainExportFilterSchema,
28474
+ layout: RetrainExportLayoutSchema,
28475
+ target: RetrainArchiveTargetSchema
28476
+ }), RetrainArchiveResultSchema, {
28477
+ kind: "mutation",
28478
+ auth: "admin",
28479
+ timeoutMs: 60 * 6e4
28480
+ }),
28481
+ /**
28384
28482
  * The media an EVENT owns.
28385
28483
  *
28386
28484
  * `ownerType` is REQUIRED, and that is the whole correction (D482). An
@@ -32785,7 +32883,19 @@ var SsoBridgeClaimsSchema = object({
32785
32883
  * editing a descriptor must not retroactively extend or shorten a link the
32786
32884
  * operator already consented to.
32787
32885
  */
32788
- refreshTtl: union([number().int().positive(), literal("never")]).optional()
32886
+ refreshTtl: union([number().int().positive(), literal("never")]).optional(),
32887
+ /**
32888
+ * Opaque handle of a hub-side LINK intent (D782). Set by an OIDC callback
32889
+ * that was started from a signed-in user's "Link" button: the finish then
32890
+ * links this identity to the intent's user instead of signing anyone in.
32891
+ * The handle carries no user id — the hub resolves it, single-use.
32892
+ */
32893
+ linkIntent: string().optional(),
32894
+ /**
32895
+ * Verified `iss` of the id_token behind an OIDC login bridge (D782). A
32896
+ * subject is unique only per issuer, so a link is bound to it.
32897
+ */
32898
+ issuer: string().optional()
32789
32899
  });
32790
32900
  var ssoBridgeCapability = {
32791
32901
  name: "sso-bridge",
@@ -35328,7 +35438,22 @@ var webrtcSessionCapability = {
35328
35438
  * field their hints were silently stripped by the schema and the
35329
35439
  * adaptive ladder started blind on the client's display size.
35330
35440
  */
35331
- hints: webrtcClientHintsSchema.optional()
35441
+ hints: webrtcClientHintsSchema.optional(),
35442
+ /**
35443
+ * Talk-back (D771): relay the PEER's audio to the device's speaker
35444
+ * through the `intercom` cap's push-talk triple. When `true` and the
35445
+ * offer lets the peer send audio, the answer's audio is `sendrecv`
35446
+ * and every inbound frame goes to `intercom.pushTalkAudio` for this
35447
+ * `deviceId`; absent/false answers `sendonly`, as for every viewer.
35448
+ *
35449
+ * TRUSTED-CALLER ONLY. Set by the Alexa export for an Echo on a camera
35450
+ * with `intercom`, when the linked user may control that camera. The
35451
+ * hub STRIPS it from every request-bound call (a browser), exactly as
35452
+ * it overwrites `relayOnly` — a viewer's talk permission is the
35453
+ * intercom cap's own surface (share tokens gate it there), and this
35454
+ * flag must not become a second door to the camera's speaker.
35455
+ */
35456
+ talkback: boolean().optional()
35332
35457
  }), object({
35333
35458
  sessionId: string(),
35334
35459
  sdpAnswer: string()
@@ -35768,6 +35893,59 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
35768
35893
  var RestartAddonResultSchema = unknown();
35769
35894
  var InstallPackageResultSchema = unknown();
35770
35895
  var ReloadPackagesResultSchema = unknown();
35896
+ /**
35897
+ * What an uninstall did on ONE node.
35898
+ * - `uninstalled` — removed: runner(s) stopped, caps gone, code deleted.
35899
+ * - `not-installed` — the node did not have it (nothing to do).
35900
+ * - `refused` — protected / bootstrap package; nothing touched.
35901
+ * - `failed` — attempted and did not complete; `error` says why.
35902
+ * - `pending-reconnect` — the node is OFFLINE: its record was pruned so the
35903
+ * back-fill never returns the package, and the stale-addon sweep removes the
35904
+ * copy when the node registers again (only once the hub no longer has it).
35905
+ */
35906
+ var UninstallNodeOutcomeSchema = _enum([
35907
+ "uninstalled",
35908
+ "not-installed",
35909
+ "refused",
35910
+ "failed",
35911
+ "pending-reconnect"
35912
+ ]);
35913
+ var UninstallNodeResultSchema = object({
35914
+ nodeId: string(),
35915
+ outcome: UninstallNodeOutcomeSchema,
35916
+ error: string().optional(),
35917
+ addonIds: array(string()).readonly(),
35918
+ runnersStopped: array(string()).readonly(),
35919
+ warnings: array(string()).readonly()
35920
+ });
35921
+ var UninstallPackageResultSchema = object({
35922
+ /** Every targeted node ended `uninstalled`, `not-installed` or `pending-reconnect`. */
35923
+ success: boolean(),
35924
+ packageName: string(),
35925
+ results: array(UninstallNodeResultSchema).readonly()
35926
+ });
35927
+ /**
35928
+ * `unreachable` lists online agents whose status read FAILED — absence from
35929
+ * `installations` is only "not installed" for a node that answered (D315).
35930
+ */
35931
+ var PackageInstallationsSchema = object({
35932
+ installations: array(object({
35933
+ packageName: string(),
35934
+ nodeId: string(),
35935
+ nodeName: string(),
35936
+ isHub: boolean(),
35937
+ isOnline: boolean(),
35938
+ version: string().nullable(),
35939
+ addonIds: array(string()).readonly(),
35940
+ source: _enum(["live", "recorded"]),
35941
+ /** The hub installs this package too. `false` on an agent row = an orphan. */
35942
+ onHub: boolean()
35943
+ })).readonly(),
35944
+ unreachable: array(object({
35945
+ nodeId: string(),
35946
+ error: string()
35947
+ })).readonly()
35948
+ });
35771
35949
  var BulkUpdateItemStatusSchema = _enum([
35772
35950
  "queued",
35773
35951
  "updating",
@@ -35876,10 +36054,31 @@ var addonsCapability = {
35876
36054
  }),
35877
36055
  isWorkspaceAvailable: method(_void(), boolean()),
35878
36056
  listWorkspacePackages: method(_void(), array(unknown()).readonly()),
35879
- uninstallPackage: method(object({ packageName: string() }), unknown(), {
36057
+ /**
36058
+ * Uninstall a package. `nodeId` omitted (or `'hub'`) uninstalls on the hub
36059
+ * — the original behaviour, which still THROWS on failure. An agent
36060
+ * `nodeId` removes it from that agent through `$agent.undeploy`
36061
+ * (runners stopped, caps deregistered, code deleted) and prunes the node's
36062
+ * declared roster so the back-fill never returns it. `everywhere: true`
36063
+ * does both for every node that has it (agents first, hub last); offline
36064
+ * nodes answer `pending-reconnect`. Node-level failures in those two modes
36065
+ * are REPORTED per node, never thrown. Protected packages are refused.
36066
+ */
36067
+ uninstallPackage: method(object({
36068
+ packageName: string().min(1),
36069
+ nodeId: string().optional(),
36070
+ everywhere: boolean().optional()
36071
+ }), UninstallPackageResultSchema, {
35880
36072
  kind: "mutation",
35881
36073
  auth: "admin"
35882
36074
  }),
36075
+ /**
36076
+ * Which nodes have a package (or, with no `packageName`, every package on
36077
+ * every node). Hub from its installer, online agents from `$agent.status`
36078
+ * (in parallel, bounded), offline agents from their declared roster.
36079
+ * Drives the uninstall confirm and the Addons page's "not on the hub" list.
36080
+ */
36081
+ listInstallations: method(object({ packageName: string().optional() }), PackageInstallationsSchema, { auth: "admin" }),
35883
36082
  reloadPackages: method(_void(), ReloadPackagesResultSchema, {
35884
36083
  kind: "mutation",
35885
36084
  auth: "admin"
@@ -47178,6 +47377,22 @@ var TotpStatusSchema = object({
47178
47377
  * delegation, structural caller-scope subset check goes inside the
47179
47378
  * provider.
47180
47379
  */
47380
+ var ExternalIdentitySchema = object({
47381
+ /** Login-method id of the provider instance, e.g. `auth-oidc/authentik`. */
47382
+ provider: string(),
47383
+ /** The IdP's stable subject (`sub`) for this account. */
47384
+ subject: string(),
47385
+ /** The verified `iss` of the IdP the identity was linked under — `sub` is unique only per issuer. */
47386
+ issuer: string(),
47387
+ userId: string(),
47388
+ /** What the IdP called the account at link time (username / email) — display only. */
47389
+ label: string().optional(),
47390
+ linkedAt: number()
47391
+ });
47392
+ var ExternalIdentityKeySchema = object({
47393
+ provider: string().min(1),
47394
+ subject: string().min(1)
47395
+ });
47181
47396
  var userManagementCapability = {
47182
47397
  name: "user-management",
47183
47398
  scope: "system",
@@ -47320,6 +47535,32 @@ var userManagementCapability = {
47320
47535
  username: string(),
47321
47536
  scopes: array(TokenScopeSchema)
47322
47537
  }).nullable(), { access: "view" }),
47538
+ listExternalIdentities: method(object({ userId: string() }), array(ExternalIdentitySchema), { auth: "admin" }),
47539
+ /** The user an identity is linked to, or `null`. */
47540
+ findUserByExternalIdentity: method(ExternalIdentityKeySchema, object({
47541
+ userId: string(),
47542
+ issuer: string()
47543
+ }).nullable(), { auth: "admin" }),
47544
+ /**
47545
+ * Link `(provider, subject)` to `userId`. Idempotent for the same user;
47546
+ * REFUSES (throws) an identity already linked to another user, and a
47547
+ * user that does not exist.
47548
+ */
47549
+ linkExternalIdentity: method(ExternalIdentityKeySchema.extend({
47550
+ userId: string(),
47551
+ issuer: string().min(1),
47552
+ label: string().optional()
47553
+ }), object({ success: literal(true) }), {
47554
+ kind: "mutation",
47555
+ auth: "admin",
47556
+ access: "create"
47557
+ }),
47558
+ /** Remove one link of `userId`. `success: false` when it was not linked to that user. */
47559
+ unlinkExternalIdentity: method(ExternalIdentityKeySchema.extend({ userId: string() }), object({ success: boolean() }), {
47560
+ kind: "mutation",
47561
+ auth: "admin",
47562
+ access: "delete"
47563
+ }),
47323
47564
  listOauthSessions: method(_void(), array(OauthSessionSummarySchema), { auth: "admin" }),
47324
47565
  revokeOauthSession: method(object({ id: string() }), object({ success: boolean() }), {
47325
47566
  kind: "mutation",
@@ -49309,6 +49550,12 @@ Object.freeze({
49309
49550
  addonId: null,
49310
49551
  access: "view"
49311
49552
  },
49553
+ "addons.listInstallations": {
49554
+ capName: "addons",
49555
+ capScope: "system",
49556
+ addonId: null,
49557
+ access: "view"
49558
+ },
49312
49559
  "addons.listJobs": {
49313
49560
  capName: "addons",
49314
49561
  capScope: "system",
@@ -53533,6 +53780,12 @@ Object.freeze({
53533
53780
  addonId: null,
53534
53781
  access: "create"
53535
53782
  },
53783
+ "pipelineAnalytics.setRetrainFrameComplete": {
53784
+ capName: "pipeline-analytics",
53785
+ capScope: "device",
53786
+ addonId: null,
53787
+ access: "create"
53788
+ },
53536
53789
  "pipelineAnalytics.setTrackFlags": {
53537
53790
  capName: "pipeline-analytics",
53538
53791
  capScope: "device",
@@ -53569,6 +53822,12 @@ Object.freeze({
53569
53822
  addonId: null,
53570
53823
  access: "delete"
53571
53824
  },
53825
+ "pipelineAnalytics.writeRetrainArchive": {
53826
+ capName: "pipeline-analytics",
53827
+ capScope: "device",
53828
+ addonId: null,
53829
+ access: "create"
53830
+ },
53572
53831
  "pipelineExecutor.cacheFrameInPool": {
53573
53832
  capName: "pipeline-executor",
53574
53833
  capScope: "system",
@@ -55921,18 +56180,36 @@ Object.freeze({
55921
56180
  addonId: null,
55922
56181
  access: "delete"
55923
56182
  },
56183
+ "userManagement.findUserByExternalIdentity": {
56184
+ capName: "user-management",
56185
+ capScope: "system",
56186
+ addonId: null,
56187
+ access: "view"
56188
+ },
55924
56189
  "userManagement.getTotpStatus": {
55925
56190
  capName: "user-management",
55926
56191
  capScope: "system",
55927
56192
  addonId: null,
55928
56193
  access: "view"
55929
56194
  },
56195
+ "userManagement.linkExternalIdentity": {
56196
+ capName: "user-management",
56197
+ capScope: "system",
56198
+ addonId: null,
56199
+ access: "create"
56200
+ },
55930
56201
  "userManagement.listApiKeys": {
55931
56202
  capName: "user-management",
55932
56203
  capScope: "system",
55933
56204
  addonId: null,
55934
56205
  access: "view"
55935
56206
  },
56207
+ "userManagement.listExternalIdentities": {
56208
+ capName: "user-management",
56209
+ capScope: "system",
56210
+ addonId: null,
56211
+ access: "view"
56212
+ },
55936
56213
  "userManagement.listOauthSessions": {
55937
56214
  capName: "user-management",
55938
56215
  capScope: "system",
@@ -56011,6 +56288,12 @@ Object.freeze({
56011
56288
  addonId: null,
56012
56289
  access: "create"
56013
56290
  },
56291
+ "userManagement.unlinkExternalIdentity": {
56292
+ capName: "user-management",
56293
+ capScope: "system",
56294
+ addonId: null,
56295
+ access: "delete"
56296
+ },
56014
56297
  "userManagement.updateUser": {
56015
56298
  capName: "user-management",
56016
56299
  capScope: "system",
@@ -59526,6 +59809,11 @@ Object.freeze({
59526
59809
  form: "single",
59527
59810
  optional: false
59528
59811
  }],
59812
+ "pipelineAnalytics.setRetrainFrameComplete": [{
59813
+ name: "deviceId",
59814
+ form: "single",
59815
+ optional: false
59816
+ }],
59529
59817
  "pipelineAnalytics.setTrackFlags": [{
59530
59818
  name: "deviceId",
59531
59819
  form: "single",
@@ -63569,6 +63857,80 @@ var FULL_IMAGE_BBOX = Object.freeze({
63569
63857
  });
63570
63858
  DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
63571
63859
  /**
63860
+ * Derive the crop rectangle for one parent detection.
63861
+ *
63862
+ * Order: pad by `paddingRatio` of the box's own size → optionally square in
63863
+ * pixel space around the padded centre → keep it inside the frame. Pure:
63864
+ * always returns a new rect and never mutates `bbox`.
63865
+ *
63866
+ * Edge handling differs by mode, on purpose:
63867
+ *
63868
+ * - **unsquared** — TRUNCATED at the frame border, byte-for-byte what the live
63869
+ * path has always done (`padAndClampFrameBbox`). A subject against the edge
63870
+ * gets a slightly smaller window. Changing this would silently reinterpret
63871
+ * every edge-touching vector already in the index.
63872
+ * - **squared** — SLID inward instead, because a truncated square is not
63873
+ * square and squaring exists precisely to preserve the aspect the model
63874
+ * sees. It only shrinks when the square is larger than the frame itself.
63875
+ */
63876
+ function deriveDetailCropRect(bbox, frameWidth, frameHeight, convention) {
63877
+ const padX = convention.paddingRatio * bbox.w;
63878
+ const padY = convention.paddingRatio * bbox.h;
63879
+ const padded = {
63880
+ x: bbox.x - padX,
63881
+ y: bbox.y - padY,
63882
+ w: bbox.w + 2 * padX,
63883
+ h: bbox.h + 2 * padY
63884
+ };
63885
+ return convention.square ? slideInsideFrame(squareInPixels(padded, frameWidth, frameHeight), frameWidth, frameHeight) : truncateToFrame(padded, frameWidth, frameHeight);
63886
+ }
63887
+ /**
63888
+ * Grow the shorter side to the longer one around the window's centre, bounded
63889
+ * by the frame's shorter side — a square larger than the frame cannot exist,
63890
+ * and collapsing to the frame's short side is the most that does.
63891
+ */
63892
+ function squareInPixels(rect, frameWidth, frameHeight) {
63893
+ const side = Math.min(Math.max(rect.w, rect.h), Math.min(frameWidth, frameHeight));
63894
+ const cx = rect.x + rect.w / 2;
63895
+ const cy = rect.y + rect.h / 2;
63896
+ return {
63897
+ x: cx - side / 2,
63898
+ y: cy - side / 2,
63899
+ w: side,
63900
+ h: side
63901
+ };
63902
+ }
63903
+ /**
63904
+ * Cut the window at the frame border — the pre-unification live behaviour,
63905
+ * preserved exactly so unsquared crops keep matching the stored index.
63906
+ */
63907
+ function truncateToFrame(rect, frameWidth, frameHeight) {
63908
+ const x1 = Math.max(0, rect.x);
63909
+ const y1 = Math.max(0, rect.y);
63910
+ const x2 = Math.min(frameWidth, rect.x + rect.w);
63911
+ const y2 = Math.min(frameHeight, rect.y + rect.h);
63912
+ return {
63913
+ x: x1,
63914
+ y: y1,
63915
+ w: Math.max(0, x2 - x1),
63916
+ h: Math.max(0, y2 - y1)
63917
+ };
63918
+ }
63919
+ /**
63920
+ * Move the window inside the frame keeping its extent — used only for squared
63921
+ * windows, where truncating would destroy the squareness that is the point.
63922
+ */
63923
+ function slideInsideFrame(rect, frameWidth, frameHeight) {
63924
+ const w = Math.max(0, Math.min(rect.w, frameWidth));
63925
+ const h = Math.max(0, Math.min(rect.h, frameHeight));
63926
+ return {
63927
+ x: Math.min(Math.max(0, rect.x), Math.max(0, frameWidth - w)),
63928
+ y: Math.min(Math.max(0, rect.y), Math.max(0, frameHeight - h)),
63929
+ w,
63930
+ h
63931
+ };
63932
+ }
63933
+ /**
63572
63934
  * WHICH delivered frames the decode worker retains a native copy of.
63573
63935
  *
63574
63936
  * - `all` — every frame the worker delivered to the runner. The shipped
@@ -64483,6 +64845,12 @@ Object.defineProperty(exports, "DEFAULT_CLIP_MODEL", {
64483
64845
  return DEFAULT_CLIP_MODEL;
64484
64846
  }
64485
64847
  });
64848
+ Object.defineProperty(exports, "DEFAULT_DETAIL_CROP_CONVENTION", {
64849
+ enumerable: true,
64850
+ get: function() {
64851
+ return DEFAULT_DETAIL_CROP_CONVENTION;
64852
+ }
64853
+ });
64486
64854
  Object.defineProperty(exports, "DEFAULT_EVENT_COLOR", {
64487
64855
  enumerable: true,
64488
64856
  get: function() {
@@ -65335,6 +65703,12 @@ Object.defineProperty(exports, "deleteAlarmProfile", {
65335
65703
  return deleteAlarmProfile;
65336
65704
  }
65337
65705
  });
65706
+ Object.defineProperty(exports, "deriveDetailCropRect", {
65707
+ enumerable: true,
65708
+ get: function() {
65709
+ return deriveDetailCropRect;
65710
+ }
65711
+ });
65338
65712
  Object.defineProperty(exports, "derivePostTriggeredAction", {
65339
65713
  enumerable: true,
65340
65714
  get: function() {