@camstack/addon-provider-rtsp 1.2.25 → 1.2.27

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 (3) hide show
  1. package/dist/addon.js +258 -17
  2. package/dist/addon.mjs +258 -17
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let node_net = require("node:net");
25
25
  node_net = __toESM(node_net);
26
- //#region ../types/dist/event-category-XfKNtfCc.mjs
26
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
27
27
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
28
28
  EventCategory["SystemBoot"] = "system.boot";
29
29
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -39,6 +39,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
39
39
  */
40
40
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
41
41
  /**
42
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
43
+ * Emitted only when the material on disk actually changed, so an
44
+ * operator who trusted the old certificate by hand is told rather than
45
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
46
+ *
47
+ * Rule: docs/decisions/adr-0227-*.md
48
+ */
49
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
50
+ /**
42
51
  * A newer addon or server-root package version was found by the
43
52
  * authoritative registry check. Emitted once when any observed
44
53
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8067,6 +8076,15 @@ var LabelDefinitionSchema = object({
8067
8076
  description: string().optional(),
8068
8077
  icon: string().optional()
8069
8078
  });
8079
+ var ClassMapDefinitionSchema = object({
8080
+ mapping: record(string(), _enum([
8081
+ "person",
8082
+ "vehicle",
8083
+ "animal",
8084
+ "package"
8085
+ ])),
8086
+ preserveOriginal: boolean()
8087
+ });
8070
8088
  var MODEL_FORMATS = [
8071
8089
  "onnx",
8072
8090
  "coreml",
@@ -8245,7 +8263,13 @@ var ModelCatalogEntrySchema = object({
8245
8263
  * `id` stays the source of truth for resolution/download/persistence; grouping
8246
8264
  * is a presentation overlay resolved back to an `id`.
8247
8265
  */
8248
- group: ModelVariantGroupSchema.optional()
8266
+ group: ModelVariantGroupSchema.optional(),
8267
+ /**
8268
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8269
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8270
+ * labels already ARE the CamStack macros (Scrypted identity map).
8271
+ */
8272
+ classMap: ClassMapDefinitionSchema.optional()
8249
8273
  });
8250
8274
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8251
8275
  format: literal("openvino"),
@@ -8274,7 +8298,8 @@ var ModelConvertMetadataSchema = object({
8274
8298
  "ocr",
8275
8299
  "segmentation"
8276
8300
  ]),
8277
- faceAlignment: boolean().optional()
8301
+ faceAlignment: boolean().optional(),
8302
+ classMap: ClassMapDefinitionSchema.optional()
8278
8303
  });
8279
8304
  var ConvertResultSchema = object({
8280
8305
  entry: ModelCatalogEntrySchema,
@@ -17567,6 +17592,33 @@ var NativeCropRefSchema = object({
17567
17592
  h: number()
17568
17593
  })
17569
17594
  });
17595
+ object({
17596
+ crop: object({
17597
+ left: number(),
17598
+ top: number(),
17599
+ width: number().positive(),
17600
+ height: number().positive()
17601
+ }).optional(),
17602
+ content: object({
17603
+ width: number().int().positive(),
17604
+ height: number().int().positive()
17605
+ }),
17606
+ fit: _enum(["stretch", "contain"]),
17607
+ format: _enum([
17608
+ "rgb",
17609
+ "gray",
17610
+ "jpeg"
17611
+ ])
17612
+ });
17613
+ var FrameRefSchema = object({
17614
+ registryId: string().min(1),
17615
+ id: string().min(1),
17616
+ width: number().int().positive(),
17617
+ height: number().int().positive(),
17618
+ format: _enum(["rgb", "gray"]),
17619
+ timestamp: number(),
17620
+ capturedAt: number().optional()
17621
+ });
17570
17622
  var ModelFormatSchema$1 = _enum([
17571
17623
  "onnx",
17572
17624
  "coreml",
@@ -17811,6 +17863,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17811
17863
  steps: array(PipelineStepInputSchema).min(1),
17812
17864
  frame: FrameInputSchema.optional(),
17813
17865
  /**
17866
+ * Process-local lazy frame. Valid only when caller and provider resolve
17867
+ * in the same execution-group process; split/cross-node callers use
17868
+ * `frame`/`image` inline compatibility instead.
17869
+ */
17870
+ frameRef: FrameRefSchema.optional(),
17871
+ /**
17814
17872
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17815
17873
  * the decoded pixels live in. One more member of the one-of
17816
17874
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18106,7 +18164,10 @@ var NativeCropResultSchema = object({
18106
18164
  * Which source served this crop, so a quality-sensitive consumer (the native
18107
18165
  * `keyFrame`) can reject a degraded fallback:
18108
18166
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18109
- * quality path).
18167
+ * quality path). A subject-tile serve is also native-resolution and stays
18168
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18169
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18170
+ * internal crop result (`nativeHits` vs `tileHits`).
18110
18171
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18111
18172
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18112
18173
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18597,12 +18658,41 @@ var RunnerLocalLoadSchema = object({
18597
18658
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18598
18659
  * working unchanged when they switch to reading from the runner cap.
18599
18660
  */
18661
+ var FrameLazyCountersSchema = object({
18662
+ framesDecoded: number(),
18663
+ framesAdmitted: number(),
18664
+ framesDroppedPixelFree: number(),
18665
+ viewsMaterialized: number(),
18666
+ viewsSkipped: number(),
18667
+ workerToRunnerBytes: number(),
18668
+ runnerToPoolRawBytes: number(),
18669
+ runnerToPoolJpegBytes: number(),
18670
+ onDemandFullFrameRequests: number(),
18671
+ onDemandCropRequests: number(),
18672
+ nativeHits: number(),
18673
+ nativeMisses: number(),
18674
+ tileHits: number(),
18675
+ tileMisses: number(),
18676
+ fallbackHits: number(),
18677
+ fallbackMisses: number(),
18678
+ retainedWritesAvoided: number(),
18679
+ residentRefs: number(),
18680
+ residentBytes: number(),
18681
+ releases: number(),
18682
+ evictions: number(),
18683
+ staleMisses: number()
18684
+ });
18685
+ var FrameLazyMetricsSchema = object({
18686
+ node: FrameLazyCountersSchema,
18687
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18688
+ });
18600
18689
  var RunnerLocalMetricsSchema = object({
18601
18690
  nodeId: string(),
18602
18691
  activeCameras: number(),
18603
18692
  throttledCameras: number(),
18604
18693
  avgInferenceTimeMs: number(),
18605
- queueDepth: number()
18694
+ queueDepth: number(),
18695
+ frameLazy: FrameLazyMetricsSchema.optional()
18606
18696
  });
18607
18697
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18608
18698
  handle: FrameHandleSchema,
@@ -20006,6 +20096,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
20006
20096
  location: StorageLocationSchema,
20007
20097
  relativePath: string()
20008
20098
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
20099
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20100
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20101
+ var ProfileSettingsBagSchema = record(string(), unknown());
20009
20102
  /**
20010
20103
  * A live terminal session hosted by the provider addon. Output and input do
20011
20104
  * NOT flow through the capability — they use the addon data plane
@@ -20035,7 +20128,14 @@ var TerminalSessionInfoSchema = object({
20035
20128
  var TerminalProfileInfoSchema = object({
20036
20129
  profileId: string(),
20037
20130
  label: string(),
20038
- description: string().optional()
20131
+ description: string().optional(),
20132
+ /** Spawn defaults the instance form copies on create. */
20133
+ executable: string().optional(),
20134
+ args: array(string()).readonly().optional(),
20135
+ cwd: string().optional(),
20136
+ environment: array(string()).readonly().optional(),
20137
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20138
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20039
20139
  });
20040
20140
  /**
20041
20141
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20048,7 +20148,12 @@ var TerminalInstanceInfoSchema = object({
20048
20148
  profileId: string(),
20049
20149
  profileLabel: string(),
20050
20150
  name: string(),
20051
- enabled: boolean()
20151
+ enabled: boolean(),
20152
+ executable: string(),
20153
+ args: array(string()).readonly(),
20154
+ cwd: string(),
20155
+ environment: array(string()).readonly(),
20156
+ profileSettings: ProfileSettingsBagSchema
20052
20157
  });
20053
20158
  var TerminalLegacyCameraSchema = object({
20054
20159
  stableId: string(),
@@ -20078,7 +20183,23 @@ var TerminalOutputBatchSchema = object({
20078
20183
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20079
20184
  targetNodeId: string().min(1),
20080
20185
  profileId: string().min(1),
20081
- name: string().trim().min(1).max(160).optional()
20186
+ name: string().trim().min(1).max(160).optional(),
20187
+ executable: string().max(1024).optional(),
20188
+ args: array(string().max(2048)).max(64).optional(),
20189
+ cwd: string().max(1024).optional(),
20190
+ environment: array(string().max(4096)).max(64).optional(),
20191
+ profileSettings: ProfileSettingsBagSchema.optional()
20192
+ }), TerminalInstanceInfoSchema, {
20193
+ kind: "mutation",
20194
+ auth: "admin"
20195
+ }), method(object({
20196
+ instanceId: string().min(1),
20197
+ name: string().trim().min(1).max(160).optional(),
20198
+ executable: string().max(1024).optional(),
20199
+ args: array(string().max(2048)).max(64).optional(),
20200
+ cwd: string().max(1024).optional(),
20201
+ environment: array(string().max(4096)).max(64).optional(),
20202
+ profileSettings: ProfileSettingsBagSchema.optional()
20082
20203
  }), TerminalInstanceInfoSchema, {
20083
20204
  kind: "mutation",
20084
20205
  auth: "admin"
@@ -20100,7 +20221,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20100
20221
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20101
20222
  profileId: string(),
20102
20223
  cols: number().int().positive(),
20103
- rows: number().int().positive()
20224
+ rows: number().int().positive(),
20225
+ executable: string().max(1024).optional(),
20226
+ args: array(string().max(2048)).max(64).optional(),
20227
+ cwd: string().max(1024).optional(),
20228
+ environment: array(string().max(4096)).max(64).optional()
20104
20229
  }), TerminalSessionInfoSchema, {
20105
20230
  kind: "mutation",
20106
20231
  auth: "admin"
@@ -23983,10 +24108,10 @@ var lawnMowerControlCapability = {
23983
24108
  *
23984
24109
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23985
24110
  * to receive an ordered list of candidate base URLs it should race
23986
- * on connect — LAN IPv4 first (lowest latency when on same network),
23987
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23988
- * race them with short timeouts and stick with the winner for the
23989
- * session.
24111
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24112
+ * when on the same network), then public hostname (if a tunnel is
24113
+ * up). The SDK can race them with short timeouts and stick with the
24114
+ * winner for the session.
23990
24115
  *
23991
24116
  * Why hub-only: agents are not directly addressable by the operator's
23992
24117
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24141,6 +24266,17 @@ var NotificationEndpointSchema = object({
24141
24266
  /** What the ranking currently resolves to (null when nothing is reachable). */
24142
24267
  resolved: string().nullable()
24143
24268
  });
24269
+ /**
24270
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24271
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24272
+ * currently expands to, so the UI can show the effective set either way.
24273
+ */
24274
+ var ViewerEndpointsSchema = object({
24275
+ /** The operator's explicit race set, or empty for AUTO. */
24276
+ baseUrls: array(string()).readonly(),
24277
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24278
+ resolved: array(string()).readonly()
24279
+ });
24144
24280
  var AllowedAddressesSchema = object({
24145
24281
  /**
24146
24282
  * Allowlist of interface addresses operators have explicitly opted
@@ -24149,6 +24285,20 @@ var AllowedAddressesSchema = object({
24149
24285
  * Network Addresses admin page and persisted by the addon.
24150
24286
  */
24151
24287
  addresses: array(string()).readonly() });
24288
+ var TlsStatusSchema = object({
24289
+ mode: _enum([
24290
+ "generated",
24291
+ "uploaded",
24292
+ "disabled"
24293
+ ]),
24294
+ leafFingerprintSha256: string().nullable(),
24295
+ caFingerprintSha256: string().nullable(),
24296
+ validTo: string().nullable(),
24297
+ sans: array(string()),
24298
+ caCertPem: string().nullable(),
24299
+ reissueError: string().nullable(),
24300
+ restartRequired: boolean()
24301
+ });
24152
24302
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24153
24303
  /**
24154
24304
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24158,17 +24308,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24158
24308
  */
24159
24309
  port: number().int().min(1).max(65535).optional(),
24160
24310
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24161
- * candidate. Default `true`. */
24311
+ * candidate. Default `false` — loopback is not a client route. */
24162
24312
  includeLoopback: boolean().optional(),
24163
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24164
- * Default `false`. */
24313
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24314
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24315
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24165
24316
  ipv4Only: boolean().optional(),
24166
24317
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24167
24318
  * Pass `'https'` when the caller is itself loaded over HTTPS
24168
24319
  * to avoid mixed-content blocks in the browser. The public
24169
24320
  * tunnel always emits `https://` regardless. */
24170
24321
  scheme: _enum(["http", "https"]).optional()
24171
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24322
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24323
+ kind: "mutation",
24324
+ auth: "admin"
24325
+ }), method(object({
24326
+ certPem: string().min(1),
24327
+ keyPem: string().min(1),
24328
+ caPem: string().optional()
24329
+ }), TlsStatusSchema, {
24330
+ kind: "mutation",
24331
+ auth: "admin"
24332
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24333
+ kind: "mutation",
24334
+ auth: "admin"
24335
+ });
24172
24336
  var LockControlStatusSchema = object({
24173
24337
  /** Lifecycle state of the lock. `jammed` means the motor reported
24174
24338
  * failure to reach the target — operator intervention required. */
@@ -32261,6 +32425,12 @@ Object.freeze({
32261
32425
  addonId: null,
32262
32426
  access: "create"
32263
32427
  },
32428
+ "localNetwork.downloadCa": {
32429
+ capName: "local-network",
32430
+ capScope: "system",
32431
+ addonId: null,
32432
+ access: "view"
32433
+ },
32264
32434
  "localNetwork.getAllowedAddresses": {
32265
32435
  capName: "local-network",
32266
32436
  capScope: "system",
@@ -32285,18 +32455,42 @@ Object.freeze({
32285
32455
  addonId: null,
32286
32456
  access: "view"
32287
32457
  },
32458
+ "localNetwork.getTlsStatus": {
32459
+ capName: "local-network",
32460
+ capScope: "system",
32461
+ addonId: null,
32462
+ access: "view"
32463
+ },
32464
+ "localNetwork.getViewerEndpoints": {
32465
+ capName: "local-network",
32466
+ capScope: "system",
32467
+ addonId: null,
32468
+ access: "view"
32469
+ },
32288
32470
  "localNetwork.list": {
32289
32471
  capName: "local-network",
32290
32472
  capScope: "system",
32291
32473
  addonId: null,
32292
32474
  access: "view"
32293
32475
  },
32476
+ "localNetwork.regenerateCertificate": {
32477
+ capName: "local-network",
32478
+ capScope: "system",
32479
+ addonId: null,
32480
+ access: "create"
32481
+ },
32294
32482
  "localNetwork.resetAllowlistToBestMatch": {
32295
32483
  capName: "local-network",
32296
32484
  capScope: "system",
32297
32485
  addonId: null,
32298
32486
  access: "delete"
32299
32487
  },
32488
+ "localNetwork.revertToGeneratedCertificate": {
32489
+ capName: "local-network",
32490
+ capScope: "system",
32491
+ addonId: null,
32492
+ access: "create"
32493
+ },
32300
32494
  "localNetwork.setAllowedAddresses": {
32301
32495
  capName: "local-network",
32302
32496
  capScope: "system",
@@ -32309,6 +32503,18 @@ Object.freeze({
32309
32503
  addonId: null,
32310
32504
  access: "create"
32311
32505
  },
32506
+ "localNetwork.setViewerEndpoints": {
32507
+ capName: "local-network",
32508
+ capScope: "system",
32509
+ addonId: null,
32510
+ access: "create"
32511
+ },
32512
+ "localNetwork.uploadCertificate": {
32513
+ capName: "local-network",
32514
+ capScope: "system",
32515
+ addonId: null,
32516
+ access: "create"
32517
+ },
32312
32518
  "lockControl.lock": {
32313
32519
  capName: "lock-control",
32314
32520
  capScope: "device",
@@ -35189,6 +35395,12 @@ Object.freeze({
35189
35395
  addonId: null,
35190
35396
  access: "create"
35191
35397
  },
35398
+ "terminalSession.updateInstance": {
35399
+ capName: "terminal-session",
35400
+ capScope: "system",
35401
+ addonId: null,
35402
+ access: "create"
35403
+ },
35192
35404
  "terminalSession.writeInput": {
35193
35405
  capName: "terminal-session",
35194
35406
  capScope: "system",
@@ -37676,6 +37888,35 @@ Object.freeze(Object.fromEntries([{
37676
37888
  }]
37677
37889
  }].map((s) => [s.stepId, s.defaultModelId])));
37678
37890
  string().min(1);
37891
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37892
+ stepId: "face-embedding",
37893
+ key: "minLandmarkFaceSize",
37894
+ label: "Min face size for recognition (detection px)",
37895
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
37896
+ type: "slider",
37897
+ min: 0,
37898
+ max: 64,
37899
+ step: 2,
37900
+ default: 24
37901
+ }];
37902
+ function clusterStepSettingKey(stepId, fieldKey) {
37903
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37904
+ }
37905
+ var ClusterSettingNumberSchema = number().finite();
37906
+ function readClusterStepSettings(config) {
37907
+ const out = {};
37908
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37909
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37910
+ const value = parsed.success ? parsed.data : field.default;
37911
+ const existing = out[field.stepId] ?? {};
37912
+ out[field.stepId] = {
37913
+ ...existing,
37914
+ [field.key]: value
37915
+ };
37916
+ }
37917
+ return out;
37918
+ }
37919
+ readClusterStepSettings({});
37679
37920
  object({
37680
37921
  /**
37681
37922
  * Fraction of the box's own size added on EACH side before cutting.
package/dist/addon.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import net from "node:net";
2
- //#region ../types/dist/event-category-XfKNtfCc.mjs
2
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -15,6 +15,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
15
15
  */
16
16
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
17
17
  /**
18
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
19
+ * Emitted only when the material on disk actually changed, so an
20
+ * operator who trusted the old certificate by hand is told rather than
21
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
22
+ *
23
+ * Rule: docs/decisions/adr-0227-*.md
24
+ */
25
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
26
+ /**
18
27
  * A newer addon or server-root package version was found by the
19
28
  * authoritative registry check. Emitted once when any observed
20
29
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8043,6 +8052,15 @@ var LabelDefinitionSchema = object({
8043
8052
  description: string().optional(),
8044
8053
  icon: string().optional()
8045
8054
  });
8055
+ var ClassMapDefinitionSchema = object({
8056
+ mapping: record(string(), _enum([
8057
+ "person",
8058
+ "vehicle",
8059
+ "animal",
8060
+ "package"
8061
+ ])),
8062
+ preserveOriginal: boolean()
8063
+ });
8046
8064
  var MODEL_FORMATS = [
8047
8065
  "onnx",
8048
8066
  "coreml",
@@ -8221,7 +8239,13 @@ var ModelCatalogEntrySchema = object({
8221
8239
  * `id` stays the source of truth for resolution/download/persistence; grouping
8222
8240
  * is a presentation overlay resolved back to an `id`.
8223
8241
  */
8224
- group: ModelVariantGroupSchema.optional()
8242
+ group: ModelVariantGroupSchema.optional(),
8243
+ /**
8244
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8245
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8246
+ * labels already ARE the CamStack macros (Scrypted identity map).
8247
+ */
8248
+ classMap: ClassMapDefinitionSchema.optional()
8225
8249
  });
8226
8250
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8227
8251
  format: literal("openvino"),
@@ -8250,7 +8274,8 @@ var ModelConvertMetadataSchema = object({
8250
8274
  "ocr",
8251
8275
  "segmentation"
8252
8276
  ]),
8253
- faceAlignment: boolean().optional()
8277
+ faceAlignment: boolean().optional(),
8278
+ classMap: ClassMapDefinitionSchema.optional()
8254
8279
  });
8255
8280
  var ConvertResultSchema = object({
8256
8281
  entry: ModelCatalogEntrySchema,
@@ -17543,6 +17568,33 @@ var NativeCropRefSchema = object({
17543
17568
  h: number()
17544
17569
  })
17545
17570
  });
17571
+ object({
17572
+ crop: object({
17573
+ left: number(),
17574
+ top: number(),
17575
+ width: number().positive(),
17576
+ height: number().positive()
17577
+ }).optional(),
17578
+ content: object({
17579
+ width: number().int().positive(),
17580
+ height: number().int().positive()
17581
+ }),
17582
+ fit: _enum(["stretch", "contain"]),
17583
+ format: _enum([
17584
+ "rgb",
17585
+ "gray",
17586
+ "jpeg"
17587
+ ])
17588
+ });
17589
+ var FrameRefSchema = object({
17590
+ registryId: string().min(1),
17591
+ id: string().min(1),
17592
+ width: number().int().positive(),
17593
+ height: number().int().positive(),
17594
+ format: _enum(["rgb", "gray"]),
17595
+ timestamp: number(),
17596
+ capturedAt: number().optional()
17597
+ });
17546
17598
  var ModelFormatSchema$1 = _enum([
17547
17599
  "onnx",
17548
17600
  "coreml",
@@ -17787,6 +17839,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17787
17839
  steps: array(PipelineStepInputSchema).min(1),
17788
17840
  frame: FrameInputSchema.optional(),
17789
17841
  /**
17842
+ * Process-local lazy frame. Valid only when caller and provider resolve
17843
+ * in the same execution-group process; split/cross-node callers use
17844
+ * `frame`/`image` inline compatibility instead.
17845
+ */
17846
+ frameRef: FrameRefSchema.optional(),
17847
+ /**
17790
17848
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17791
17849
  * the decoded pixels live in. One more member of the one-of
17792
17850
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18082,7 +18140,10 @@ var NativeCropResultSchema = object({
18082
18140
  * Which source served this crop, so a quality-sensitive consumer (the native
18083
18141
  * `keyFrame`) can reject a degraded fallback:
18084
18142
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18085
- * quality path).
18143
+ * quality path). A subject-tile serve is also native-resolution and stays
18144
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18145
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18146
+ * internal crop result (`nativeHits` vs `tileHits`).
18086
18147
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18087
18148
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18088
18149
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18573,12 +18634,41 @@ var RunnerLocalLoadSchema = object({
18573
18634
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18574
18635
  * working unchanged when they switch to reading from the runner cap.
18575
18636
  */
18637
+ var FrameLazyCountersSchema = object({
18638
+ framesDecoded: number(),
18639
+ framesAdmitted: number(),
18640
+ framesDroppedPixelFree: number(),
18641
+ viewsMaterialized: number(),
18642
+ viewsSkipped: number(),
18643
+ workerToRunnerBytes: number(),
18644
+ runnerToPoolRawBytes: number(),
18645
+ runnerToPoolJpegBytes: number(),
18646
+ onDemandFullFrameRequests: number(),
18647
+ onDemandCropRequests: number(),
18648
+ nativeHits: number(),
18649
+ nativeMisses: number(),
18650
+ tileHits: number(),
18651
+ tileMisses: number(),
18652
+ fallbackHits: number(),
18653
+ fallbackMisses: number(),
18654
+ retainedWritesAvoided: number(),
18655
+ residentRefs: number(),
18656
+ residentBytes: number(),
18657
+ releases: number(),
18658
+ evictions: number(),
18659
+ staleMisses: number()
18660
+ });
18661
+ var FrameLazyMetricsSchema = object({
18662
+ node: FrameLazyCountersSchema,
18663
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18664
+ });
18576
18665
  var RunnerLocalMetricsSchema = object({
18577
18666
  nodeId: string(),
18578
18667
  activeCameras: number(),
18579
18668
  throttledCameras: number(),
18580
18669
  avgInferenceTimeMs: number(),
18581
- queueDepth: number()
18670
+ queueDepth: number(),
18671
+ frameLazy: FrameLazyMetricsSchema.optional()
18582
18672
  });
18583
18673
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18584
18674
  handle: FrameHandleSchema,
@@ -19982,6 +20072,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19982
20072
  location: StorageLocationSchema,
19983
20073
  relativePath: string()
19984
20074
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
20075
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20076
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20077
+ var ProfileSettingsBagSchema = record(string(), unknown());
19985
20078
  /**
19986
20079
  * A live terminal session hosted by the provider addon. Output and input do
19987
20080
  * NOT flow through the capability — they use the addon data plane
@@ -20011,7 +20104,14 @@ var TerminalSessionInfoSchema = object({
20011
20104
  var TerminalProfileInfoSchema = object({
20012
20105
  profileId: string(),
20013
20106
  label: string(),
20014
- description: string().optional()
20107
+ description: string().optional(),
20108
+ /** Spawn defaults the instance form copies on create. */
20109
+ executable: string().optional(),
20110
+ args: array(string()).readonly().optional(),
20111
+ cwd: string().optional(),
20112
+ environment: array(string()).readonly().optional(),
20113
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20114
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20015
20115
  });
20016
20116
  /**
20017
20117
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20024,7 +20124,12 @@ var TerminalInstanceInfoSchema = object({
20024
20124
  profileId: string(),
20025
20125
  profileLabel: string(),
20026
20126
  name: string(),
20027
- enabled: boolean()
20127
+ enabled: boolean(),
20128
+ executable: string(),
20129
+ args: array(string()).readonly(),
20130
+ cwd: string(),
20131
+ environment: array(string()).readonly(),
20132
+ profileSettings: ProfileSettingsBagSchema
20028
20133
  });
20029
20134
  var TerminalLegacyCameraSchema = object({
20030
20135
  stableId: string(),
@@ -20054,7 +20159,23 @@ var TerminalOutputBatchSchema = object({
20054
20159
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20055
20160
  targetNodeId: string().min(1),
20056
20161
  profileId: string().min(1),
20057
- name: string().trim().min(1).max(160).optional()
20162
+ name: string().trim().min(1).max(160).optional(),
20163
+ executable: string().max(1024).optional(),
20164
+ args: array(string().max(2048)).max(64).optional(),
20165
+ cwd: string().max(1024).optional(),
20166
+ environment: array(string().max(4096)).max(64).optional(),
20167
+ profileSettings: ProfileSettingsBagSchema.optional()
20168
+ }), TerminalInstanceInfoSchema, {
20169
+ kind: "mutation",
20170
+ auth: "admin"
20171
+ }), method(object({
20172
+ instanceId: string().min(1),
20173
+ name: string().trim().min(1).max(160).optional(),
20174
+ executable: string().max(1024).optional(),
20175
+ args: array(string().max(2048)).max(64).optional(),
20176
+ cwd: string().max(1024).optional(),
20177
+ environment: array(string().max(4096)).max(64).optional(),
20178
+ profileSettings: ProfileSettingsBagSchema.optional()
20058
20179
  }), TerminalInstanceInfoSchema, {
20059
20180
  kind: "mutation",
20060
20181
  auth: "admin"
@@ -20076,7 +20197,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20076
20197
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20077
20198
  profileId: string(),
20078
20199
  cols: number().int().positive(),
20079
- rows: number().int().positive()
20200
+ rows: number().int().positive(),
20201
+ executable: string().max(1024).optional(),
20202
+ args: array(string().max(2048)).max(64).optional(),
20203
+ cwd: string().max(1024).optional(),
20204
+ environment: array(string().max(4096)).max(64).optional()
20080
20205
  }), TerminalSessionInfoSchema, {
20081
20206
  kind: "mutation",
20082
20207
  auth: "admin"
@@ -23959,10 +24084,10 @@ var lawnMowerControlCapability = {
23959
24084
  *
23960
24085
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23961
24086
  * to receive an ordered list of candidate base URLs it should race
23962
- * on connect — LAN IPv4 first (lowest latency when on same network),
23963
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23964
- * race them with short timeouts and stick with the winner for the
23965
- * session.
24087
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24088
+ * when on the same network), then public hostname (if a tunnel is
24089
+ * up). The SDK can race them with short timeouts and stick with the
24090
+ * winner for the session.
23966
24091
  *
23967
24092
  * Why hub-only: agents are not directly addressable by the operator's
23968
24093
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24117,6 +24242,17 @@ var NotificationEndpointSchema = object({
24117
24242
  /** What the ranking currently resolves to (null when nothing is reachable). */
24118
24243
  resolved: string().nullable()
24119
24244
  });
24245
+ /**
24246
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24247
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24248
+ * currently expands to, so the UI can show the effective set either way.
24249
+ */
24250
+ var ViewerEndpointsSchema = object({
24251
+ /** The operator's explicit race set, or empty for AUTO. */
24252
+ baseUrls: array(string()).readonly(),
24253
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24254
+ resolved: array(string()).readonly()
24255
+ });
24120
24256
  var AllowedAddressesSchema = object({
24121
24257
  /**
24122
24258
  * Allowlist of interface addresses operators have explicitly opted
@@ -24125,6 +24261,20 @@ var AllowedAddressesSchema = object({
24125
24261
  * Network Addresses admin page and persisted by the addon.
24126
24262
  */
24127
24263
  addresses: array(string()).readonly() });
24264
+ var TlsStatusSchema = object({
24265
+ mode: _enum([
24266
+ "generated",
24267
+ "uploaded",
24268
+ "disabled"
24269
+ ]),
24270
+ leafFingerprintSha256: string().nullable(),
24271
+ caFingerprintSha256: string().nullable(),
24272
+ validTo: string().nullable(),
24273
+ sans: array(string()),
24274
+ caCertPem: string().nullable(),
24275
+ reissueError: string().nullable(),
24276
+ restartRequired: boolean()
24277
+ });
24128
24278
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24129
24279
  /**
24130
24280
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24134,17 +24284,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24134
24284
  */
24135
24285
  port: number().int().min(1).max(65535).optional(),
24136
24286
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24137
- * candidate. Default `true`. */
24287
+ * candidate. Default `false` — loopback is not a client route. */
24138
24288
  includeLoopback: boolean().optional(),
24139
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24140
- * Default `false`. */
24289
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24290
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24291
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24141
24292
  ipv4Only: boolean().optional(),
24142
24293
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24143
24294
  * Pass `'https'` when the caller is itself loaded over HTTPS
24144
24295
  * to avoid mixed-content blocks in the browser. The public
24145
24296
  * tunnel always emits `https://` regardless. */
24146
24297
  scheme: _enum(["http", "https"]).optional()
24147
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24298
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24299
+ kind: "mutation",
24300
+ auth: "admin"
24301
+ }), method(object({
24302
+ certPem: string().min(1),
24303
+ keyPem: string().min(1),
24304
+ caPem: string().optional()
24305
+ }), TlsStatusSchema, {
24306
+ kind: "mutation",
24307
+ auth: "admin"
24308
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24309
+ kind: "mutation",
24310
+ auth: "admin"
24311
+ });
24148
24312
  var LockControlStatusSchema = object({
24149
24313
  /** Lifecycle state of the lock. `jammed` means the motor reported
24150
24314
  * failure to reach the target — operator intervention required. */
@@ -32237,6 +32401,12 @@ Object.freeze({
32237
32401
  addonId: null,
32238
32402
  access: "create"
32239
32403
  },
32404
+ "localNetwork.downloadCa": {
32405
+ capName: "local-network",
32406
+ capScope: "system",
32407
+ addonId: null,
32408
+ access: "view"
32409
+ },
32240
32410
  "localNetwork.getAllowedAddresses": {
32241
32411
  capName: "local-network",
32242
32412
  capScope: "system",
@@ -32261,18 +32431,42 @@ Object.freeze({
32261
32431
  addonId: null,
32262
32432
  access: "view"
32263
32433
  },
32434
+ "localNetwork.getTlsStatus": {
32435
+ capName: "local-network",
32436
+ capScope: "system",
32437
+ addonId: null,
32438
+ access: "view"
32439
+ },
32440
+ "localNetwork.getViewerEndpoints": {
32441
+ capName: "local-network",
32442
+ capScope: "system",
32443
+ addonId: null,
32444
+ access: "view"
32445
+ },
32264
32446
  "localNetwork.list": {
32265
32447
  capName: "local-network",
32266
32448
  capScope: "system",
32267
32449
  addonId: null,
32268
32450
  access: "view"
32269
32451
  },
32452
+ "localNetwork.regenerateCertificate": {
32453
+ capName: "local-network",
32454
+ capScope: "system",
32455
+ addonId: null,
32456
+ access: "create"
32457
+ },
32270
32458
  "localNetwork.resetAllowlistToBestMatch": {
32271
32459
  capName: "local-network",
32272
32460
  capScope: "system",
32273
32461
  addonId: null,
32274
32462
  access: "delete"
32275
32463
  },
32464
+ "localNetwork.revertToGeneratedCertificate": {
32465
+ capName: "local-network",
32466
+ capScope: "system",
32467
+ addonId: null,
32468
+ access: "create"
32469
+ },
32276
32470
  "localNetwork.setAllowedAddresses": {
32277
32471
  capName: "local-network",
32278
32472
  capScope: "system",
@@ -32285,6 +32479,18 @@ Object.freeze({
32285
32479
  addonId: null,
32286
32480
  access: "create"
32287
32481
  },
32482
+ "localNetwork.setViewerEndpoints": {
32483
+ capName: "local-network",
32484
+ capScope: "system",
32485
+ addonId: null,
32486
+ access: "create"
32487
+ },
32488
+ "localNetwork.uploadCertificate": {
32489
+ capName: "local-network",
32490
+ capScope: "system",
32491
+ addonId: null,
32492
+ access: "create"
32493
+ },
32288
32494
  "lockControl.lock": {
32289
32495
  capName: "lock-control",
32290
32496
  capScope: "device",
@@ -35165,6 +35371,12 @@ Object.freeze({
35165
35371
  addonId: null,
35166
35372
  access: "create"
35167
35373
  },
35374
+ "terminalSession.updateInstance": {
35375
+ capName: "terminal-session",
35376
+ capScope: "system",
35377
+ addonId: null,
35378
+ access: "create"
35379
+ },
35168
35380
  "terminalSession.writeInput": {
35169
35381
  capName: "terminal-session",
35170
35382
  capScope: "system",
@@ -37652,6 +37864,35 @@ Object.freeze(Object.fromEntries([{
37652
37864
  }]
37653
37865
  }].map((s) => [s.stepId, s.defaultModelId])));
37654
37866
  string().min(1);
37867
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37868
+ stepId: "face-embedding",
37869
+ key: "minLandmarkFaceSize",
37870
+ label: "Min face size for recognition (detection px)",
37871
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
37872
+ type: "slider",
37873
+ min: 0,
37874
+ max: 64,
37875
+ step: 2,
37876
+ default: 24
37877
+ }];
37878
+ function clusterStepSettingKey(stepId, fieldKey) {
37879
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37880
+ }
37881
+ var ClusterSettingNumberSchema = number().finite();
37882
+ function readClusterStepSettings(config) {
37883
+ const out = {};
37884
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37885
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37886
+ const value = parsed.success ? parsed.data : field.default;
37887
+ const existing = out[field.stepId] ?? {};
37888
+ out[field.stepId] = {
37889
+ ...existing,
37890
+ [field.key]: value
37891
+ };
37892
+ }
37893
+ return out;
37894
+ }
37895
+ readClusterStepSettings({});
37655
37896
  object({
37656
37897
  /**
37657
37898
  * Fraction of the box's own size added on EACH side before cutting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "1.2.25",
3
+ "version": "1.2.27",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",