@camstack/addon-notifiers 1.2.129 → 1.2.131

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 +600 -151
  2. package/dist/addon.mjs +600 -151
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -5407,7 +5407,7 @@ var ZodIssueCode = {
5407
5407
  var ZodFirstPartyTypeKind;
5408
5408
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5409
5409
  //#endregion
5410
- //#region ../types/dist/sleep-r3RWkGVx.mjs
5410
+ //#region ../types/dist/sleep-DwAQ9sqX.mjs
5411
5411
  /**
5412
5412
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5413
5413
  * window to float samples (D455).
@@ -9358,6 +9358,16 @@ var CompositionSchema = object({
9358
9358
  seen.add(feature.cap);
9359
9359
  });
9360
9360
  });
9361
+ var NodeColorIdSchema = _enum([
9362
+ "node-a",
9363
+ "node-b",
9364
+ "node-c",
9365
+ "node-d",
9366
+ "node-e",
9367
+ "node-f",
9368
+ "node-g",
9369
+ "node-h"
9370
+ ]);
9361
9371
  new Set([
9362
9372
  "track",
9363
9373
  "face",
@@ -13704,7 +13714,14 @@ var BrokerProviderInfoSchema = object({
13704
13714
  /** Broker kinds this provider can create, with a display label. */
13705
13715
  kinds: array(object({
13706
13716
  kind: string(),
13707
- label: string()
13717
+ label: string(),
13718
+ /**
13719
+ * Relative hub path to start an interactive account link (OAuth popup).
13720
+ * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
13721
+ */
13722
+ interactiveConnectPath: string().min(1).optional(),
13723
+ /** Relative hub path to disconnect the linked account (same popup pattern). */
13724
+ interactiveDisconnectPath: string().min(1).optional()
13708
13725
  }))
13709
13726
  });
13710
13727
  var ListInputSchema = object({
@@ -18233,14 +18250,14 @@ var LlmProfileSchema = object({
18233
18250
  /** ConfigUISchema tree passed through untyped on the wire (the
18234
18251
  * notification-output `ConfigSchemaPassthrough` precedent at
18235
18252
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
18236
- var ConfigSchemaPassthrough$1 = unknown();
18253
+ var ConfigSchemaPassthrough$3 = unknown();
18237
18254
  var LlmProfileKindDescriptorSchema = object({
18238
18255
  kind: LlmProfileKindSchema,
18239
18256
  label: string(),
18240
18257
  icon: string(),
18241
18258
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
18242
18259
  addonId: string(),
18243
- configSchema: ConfigSchemaPassthrough$1
18260
+ configSchema: ConfigSchemaPassthrough$3
18244
18261
  });
18245
18262
  var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
18246
18263
  var LlmDefaultSchema = object({
@@ -19361,7 +19378,7 @@ var TargetKindCapsSchema = object({
19361
19378
  * the union is large and not meant for runtime validation here; the exported
19362
19379
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
19363
19380
  */
19364
- var ConfigSchemaPassthrough = unknown();
19381
+ var ConfigSchemaPassthrough$2 = unknown();
19365
19382
  var TargetKindSchema = object({
19366
19383
  kind: string(),
19367
19384
  label: string(),
@@ -19398,7 +19415,7 @@ var TargetKindSchema = object({
19398
19415
  * its raster path, which is the safe default for an unknown image.
19399
19416
  */
19400
19417
  iconMediaType: string().optional(),
19401
- configSchema: ConfigSchemaPassthrough,
19418
+ configSchema: ConfigSchemaPassthrough$2,
19402
19419
  /**
19403
19420
  * The config keys that decide WHERE this kind's secrets are sent (server,
19404
19421
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -22719,6 +22736,61 @@ var OauthIntegrationDescriptorSchema = object({
22719
22736
  });
22720
22737
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
22721
22738
  /**
22739
+ * `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
22740
+ *
22741
+ * One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
22742
+ * ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
22743
+ * Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
22744
+ * immutable after create.
22745
+ */
22746
+ var ConfigSchemaPassthrough$1 = unknown();
22747
+ var OidcConnectionSchema = object({
22748
+ id: string().min(1),
22749
+ displayName: string(),
22750
+ icon: string(),
22751
+ issuerUrl: string(),
22752
+ clientId: string(),
22753
+ /** Client id + secret present — never the secret value. */
22754
+ configured: boolean(),
22755
+ /**
22756
+ * Values for the connection form schema. `clientSecret` is a keychain
22757
+ * {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
22758
+ */
22759
+ formValues: record(string(), unknown()).optional()
22760
+ });
22761
+ /**
22762
+ * Patch one connection. Omitted or empty `clientSecret` keeps the stored
22763
+ * secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
22764
+ */
22765
+ var OidcConnectionUpdateInputSchema = object({
22766
+ id: string().min(1),
22767
+ displayName: string().min(1),
22768
+ icon: string().optional(),
22769
+ issuerUrl: string().optional(),
22770
+ clientId: string().optional(),
22771
+ clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
22772
+ redirectUri: string().optional(),
22773
+ scopes: string().optional(),
22774
+ usernameClaim: _enum([
22775
+ "preferred_username",
22776
+ "email",
22777
+ "sub"
22778
+ ]).optional()
22779
+ });
22780
+ method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
22781
+ id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
22782
+ displayName: string().min(1)
22783
+ }), object({ id: string() }), {
22784
+ kind: "mutation",
22785
+ auth: "admin"
22786
+ }), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
22787
+ kind: "mutation",
22788
+ auth: "admin"
22789
+ }), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
22790
+ kind: "mutation",
22791
+ auth: "admin"
22792
+ });
22793
+ /**
22722
22794
  * What started a camera's onboard motion (D672). `camera` is the camera's own
22723
22795
  * firmware; `sensor` is a device the operator ATTACHED to it. Absent on the
22724
22796
  * wire means the camera: only the camera-sensors bridge ever says `sensor`.
@@ -24075,24 +24147,69 @@ var RetrainFrameListSchema = object({
24075
24147
  /** What the page pre-selects — the native key frame when one survives. */
24076
24148
  autoPickMediaId: number().int().optional()
24077
24149
  });
24150
+ /** Exclusive partition of a corpus copy's annotation progress (design §4.2). */
24151
+ var RetrainFrameWorkStateSchema = _enum([
24152
+ "unannotated",
24153
+ "annotated",
24154
+ "complete"
24155
+ ]);
24156
+ /**
24157
+ * Filters for paginated corpus list, KPI, and matching export (design §4.4).
24158
+ * Every narrowing field is ANDed; omitted fields impose no constraint.
24159
+ */
24160
+ var RetrainCorpusFilterSchema = object({
24161
+ workState: RetrainFrameWorkStateSchema.optional(),
24162
+ deviceIds: array(number()).max(500).optional(),
24163
+ /** When set, only frames in this named collection. */
24164
+ collectionId: string().optional(),
24165
+ /** Frames that carry at least one saved annotation in one of these classes. */
24166
+ classes: array(RetrainMacroClassSchema).optional(),
24167
+ page: number().int().positive().default(1),
24168
+ pageSize: number().int().positive().max(100).default(50)
24169
+ });
24170
+ var RetrainCorpusPageSchema = object({
24171
+ frames: array(RetrainFrameSchema.extend({ workState: RetrainFrameWorkStateSchema })).readonly(),
24172
+ total: number().int().nonnegative(),
24173
+ page: number().int().positive(),
24174
+ pageSize: number().int().positive()
24175
+ });
24176
+ var RetrainCorpusKpiCameraRowSchema = object({
24177
+ deviceId: number(),
24178
+ unannotated: number().int().nonnegative(),
24179
+ annotated: number().int().nonnegative(),
24180
+ complete: number().int().nonnegative(),
24181
+ /** This camera's `complete` count divided by corpus `total`, in `[0,1]`.
24182
+ * `null` when the corpus is empty (no ratio). */
24183
+ pctOfCorpus: number().min(0).max(1).nullable()
24184
+ });
24185
+ var RetrainCorpusKpiSchema = object({
24186
+ total: number().int().nonnegative(),
24187
+ unannotated: number().int().nonnegative(),
24188
+ annotated: number().int().nonnegative(),
24189
+ complete: number().int().nonnegative(),
24190
+ cameras: array(RetrainCorpusKpiCameraRowSchema).readonly()
24191
+ });
24078
24192
  /**
24079
24193
  * Which frames an annotated export carries. Every field narrows; an empty
24080
- * filter is every annotated copy. A dataset VERSION is a frozen `frameIds`
24081
- * list owned by whoever trains (the training orchestrator), so `frameIds` is
24082
- * the primitive a version export uses; `batchId` re-selects what an earlier
24083
- * export carried (`exportedInBatch`).
24194
+ * filter is every annotated copy. `frameIds` is the primitive a job or
24195
+ * download uses; `batchId` re-selects what an earlier export carried
24196
+ * (`exportedInBatch`). Frozen dataset versions were removed — live selection
24197
+ * resolves to `frameIds` at plan/start.
24084
24198
  */
24085
24199
  var RetrainExportFilterSchema = object({
24086
24200
  deviceIds: array(number()).optional(),
24087
24201
  frameIds: array(string()).max(2e4).optional(),
24088
24202
  /** Only frames in at least one of these named collections. */
24089
24203
  collectionIds: array(string()).max(200).optional(),
24090
- /** Exactly the frames of this dataset version (and its completeOnly). An
24091
- * unknown id selects nothing. */
24092
- datasetVersionId: string().optional(),
24204
+ /** Single collection (Frames filter bar). ANDed with `collectionIds` when both set. */
24205
+ collectionId: string().optional(),
24093
24206
  batchId: string().optional(),
24094
24207
  /** Only frames the operator marked complete (all subjects boxed). */
24095
- completeOnly: boolean().optional()
24208
+ completeOnly: boolean().optional(),
24209
+ /** Exclusive work-state partition (`workStateOf`); same as the corpus list. */
24210
+ workState: RetrainFrameWorkStateSchema.optional(),
24211
+ /** Frames with at least one saved annotation in one of these classes. */
24212
+ classes: array(RetrainMacroClassSchema).optional()
24096
24213
  });
24097
24214
  /**
24098
24215
  * `frames`: the full frame copies, boxes in `annotations.json`.
@@ -24292,8 +24409,7 @@ var RetrainDatasetStatsSchema = object({
24292
24409
  completeFrames: number().int()
24293
24410
  })).readonly()
24294
24411
  });
24295
- /** An immutable dataset version, without its frame list. */
24296
- var RetrainDatasetVersionSchema = object({
24412
+ object({
24297
24413
  id: string(),
24298
24414
  name: string(),
24299
24415
  createdAt: number(),
@@ -24301,22 +24417,33 @@ var RetrainDatasetVersionSchema = object({
24301
24417
  layout: RetrainExportLayoutSchema,
24302
24418
  completeOnly: boolean(),
24303
24419
  frameCount: number().int(),
24304
- /** sha256 of what the frames taught when the version was frozen. */
24305
24420
  annotationHash: string(),
24306
- /** The export manifest schema the version was cut under. */
24307
24421
  manifestSchema: string(),
24308
24422
  stats: RetrainDatasetStatsSchema
24309
24423
  });
24310
- var CreateRetrainDatasetVersionResultSchema = discriminatedUnion("kind", [object({
24311
- kind: literal("created"),
24312
- version: RetrainDatasetVersionSchema
24424
+ /**
24425
+ * Live training corpus criteria — same shape as `TrainingCorpusSelection` on
24426
+ * the training cap. Resolved here so plan/start and the annotated export share
24427
+ * one definition of "the frames" (`buildRetrainExportPlan`).
24428
+ */
24429
+ var RetrainTrainingCorpusSelectionSchema = object({
24430
+ collectionIds: array(string()).min(1),
24431
+ workState: _enum([
24432
+ "complete",
24433
+ "annotated_or_complete",
24434
+ "any_annotated"
24435
+ ]).default("complete")
24436
+ });
24437
+ var RetrainTrainingCorpusResolveResultSchema = discriminatedUnion("kind", [object({
24438
+ kind: literal("resolved"),
24439
+ frameIds: array(string()).max(2e4).readonly(),
24440
+ stats: RetrainDatasetStatsSchema,
24441
+ annotationHash: string(),
24442
+ layout: RetrainExportLayoutSchema
24313
24443
  }), object({
24314
24444
  kind: literal("refused"),
24315
- reason: _enum([
24316
- "empty-name",
24317
- "duplicate-name",
24318
- "no-frames"
24319
- ])
24445
+ reason: _enum(["unknown-collection", "no-frames"]),
24446
+ detail: string()
24320
24447
  })]);
24321
24448
  /** The outcome of a lifecycle move owned by the retrain page. */
24322
24449
  var RetrainTransitionResultSchema = object({
@@ -25023,6 +25150,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
25023
25150
  }), array(RetrainTrackSchema).readonly(), {
25024
25151
  kind: "query",
25025
25152
  auth: "admin"
25153
+ }), method(RetrainCorpusFilterSchema, RetrainCorpusPageSchema, {
25154
+ kind: "query",
25155
+ auth: "admin"
25156
+ }), method(object({}), RetrainCorpusKpiSchema, {
25157
+ kind: "query",
25158
+ auth: "admin"
25026
25159
  }), method(object({ trackId: string() }), RetrainFrameListSchema, {
25027
25160
  kind: "query",
25028
25161
  auth: "admin"
@@ -25118,36 +25251,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
25118
25251
  kind: "mutation",
25119
25252
  auth: "admin",
25120
25253
  timeoutMs: 12e4
25121
- }), method(object({
25122
- selection: RetrainDatasetSelectionSchema,
25123
- completeOnly: boolean().optional()
25124
- }), RetrainDatasetStatsSchema, {
25125
- kind: "query",
25126
- auth: "admin"
25127
- }), method(object({
25128
- name: string().max(120),
25129
- selection: RetrainDatasetSelectionSchema,
25130
- layout: RetrainExportLayoutSchema,
25131
- completeOnly: boolean()
25132
- }), CreateRetrainDatasetVersionResultSchema, {
25133
- kind: "mutation",
25134
- auth: "admin",
25135
- timeoutMs: 12e4
25136
- }), method(object({}), array(RetrainDatasetVersionSchema).readonly(), {
25137
- kind: "query",
25138
- auth: "admin"
25139
- }), method(object({ id: string() }), object({ deleted: boolean() }), {
25140
- kind: "mutation",
25141
- auth: "admin"
25142
- }), method(object({ id: string() }), object({
25143
- found: boolean(),
25144
- annotationHash: string(),
25145
- matches: boolean(),
25146
- missingFrames: number().int()
25147
- }), {
25148
- kind: "query",
25149
- auth: "admin",
25150
- timeoutMs: 12e4
25151
25254
  }), method(object({}), array(RetrainAssistProfileSchema).readonly(), {
25152
25255
  kind: "query",
25153
25256
  auth: "admin"
@@ -25174,6 +25277,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
25174
25277
  }), method(object({}), array(RetrainCollectionSchema).readonly(), {
25175
25278
  kind: "query",
25176
25279
  auth: "admin"
25280
+ }), method(RetrainTrainingCorpusSelectionSchema, RetrainTrainingCorpusResolveResultSchema, {
25281
+ kind: "query",
25282
+ auth: "admin",
25283
+ timeoutMs: 6e4
25177
25284
  }), method(object({
25178
25285
  id: string(),
25179
25286
  name: string().max(120)
@@ -25790,11 +25897,14 @@ var HardwareClassIdSchema = _enum([
25790
25897
  "intel-n-series",
25791
25898
  "apple-m-base",
25792
25899
  "apple-m-pro",
25900
+ "amd-phoenix",
25793
25901
  "unknown"
25794
25902
  ]);
25795
25903
  var AcceleratorKindSchema = _enum([
25796
25904
  "intel-npu",
25797
25905
  "intel-igpu",
25906
+ "amd-igpu",
25907
+ "amd-npu",
25798
25908
  "x86-cpu",
25799
25909
  "arm-cpu",
25800
25910
  "apple-ane",
@@ -27684,6 +27794,8 @@ var AgentPipelineSettingsSchema = object({
27684
27794
  * address is wrong (multi-homed host, NAT, custom interface).
27685
27795
  */
27686
27796
  reachableHost: string().optional(),
27797
+ /** Hub-persisted UI colour (`--color-node-*` theme tokens). */
27798
+ color: NodeColorIdSchema.optional(),
27687
27799
  /**
27688
27800
  * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
27689
27801
  * weight, steps}. Absent / all-disabled ⇒ the node's single default
@@ -28233,8 +28345,9 @@ method(object({
28233
28345
  auth: "admin"
28234
28346
  }), method(object({
28235
28347
  agentNodeId: string(),
28236
- reachableHost: string().nullable()
28237
- }), object({ success: literal(true) }), {
28348
+ reachableHost: string().nullable().optional(),
28349
+ color: NodeColorIdSchema.nullable().optional()
28350
+ }).refine((v) => v.reachableHost !== void 0 || v.color !== void 0, { message: "reachableHost or color required" }), object({ success: literal(true) }), {
28238
28351
  kind: "mutation",
28239
28352
  auth: "admin"
28240
28353
  }), method(object({
@@ -31281,7 +31394,7 @@ var CustomActionInputSchema = object({
31281
31394
  action: string().min(1),
31282
31395
  input: unknown()
31283
31396
  });
31284
- method(_void(), array(AddonListItemSchema).readonly()), method(object({
31397
+ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSchema).readonly()), method(object({
31285
31398
  addonId: string(),
31286
31399
  limit: number().min(1).max(500).default(100),
31287
31400
  level: LogLevelSchema$2.optional()
@@ -34828,7 +34941,13 @@ var TopologyNodeSchema = object({
34828
34941
  })).readonly(),
34829
34942
  processes: array(TopologyProcessSchema).readonly(),
34830
34943
  categories: array(TopologyCategorySchema).readonly(),
34831
- rootPackage: TopologyRootPackageSchema.nullable()
34944
+ rootPackage: TopologyRootPackageSchema.nullable(),
34945
+ /**
34946
+ * How an online AGENT reaches the hub (mesh mTLS Release A): `tls` once it has
34947
+ * completed a mutual-TLS handshake, `clear` otherwise. Absent for the hub,
34948
+ * for offline rows and when the hub cannot tell.
34949
+ */
34950
+ meshTransport: _enum(["tls", "clear"]).optional()
34832
34951
  });
34833
34952
  var CapUsageEdgeSchema = object({
34834
34953
  callerAddonId: string(),
@@ -35168,6 +35287,7 @@ var OsdNamedTemplateSchema = object({
35168
35287
  });
35169
35288
  /** Where a slot's value comes from. */
35170
35289
  var OsdSourceSchema = discriminatedUnion("kind", [
35290
+ object({ kind: literal("none") }),
35171
35291
  object({
35172
35292
  kind: literal("static"),
35173
35293
  text: string().max(64)
@@ -35294,6 +35414,13 @@ method(object({ deviceId: number().int() }), object({
35294
35414
  }), object({ success: literal(true) }), {
35295
35415
  kind: "mutation",
35296
35416
  auth: "admin"
35417
+ }), method(object({
35418
+ deviceId: number().int(),
35419
+ slotId: string().min(1),
35420
+ enabled: boolean()
35421
+ }), object({ success: literal(true) }), {
35422
+ kind: "mutation",
35423
+ auth: "admin"
35297
35424
  }), method(object({
35298
35425
  sourceDeviceId: number().int(),
35299
35426
  targetDeviceId: number().int()
@@ -35674,7 +35801,15 @@ var GpuInfoSchema = object({
35674
35801
  name: string(),
35675
35802
  memoryMB: number().optional()
35676
35803
  });
35677
- var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
35804
+ var NpuInfoSchema = object({
35805
+ type: _enum([
35806
+ "apple-ane",
35807
+ "intel-npu",
35808
+ "amd-xdna"
35809
+ ]),
35810
+ name: string().optional(),
35811
+ generation: _enum(["xdna1", "xdna2"]).optional()
35812
+ });
35678
35813
  var CoralInfoSchema = object({
35679
35814
  type: literal("coral-edgetpu"),
35680
35815
  bus: string().optional()
@@ -39436,9 +39571,11 @@ DeviceType.Camera, method(object({
39436
39571
  * once is what makes a second provider (RunPod, a local NVIDIA node) a new
39437
39572
  * manifest entry and nothing else.
39438
39573
  *
39439
- * Collection, routed by the `addonId` in every single-row input (the `llm` /
39440
- * `notification-output` posture); `describe` concat-fans across providers.
39441
- * Every method is `admin`: these spend money.
39574
+ * Collection of provider **types** (addons). Each addon owns zero or more
39575
+ * **entries** (credentials + defaults). Concat-fans: `describe`,
39576
+ * `describeTypes`, `listEntries`, `listJobs`. Single-row work is keyed by
39577
+ * `entryId`; callers also pass the owning `addonId` as the collection
39578
+ * selector (broker posture). Every method is `admin`: these spend money.
39442
39579
  *
39443
39580
  * Every cloud object a provider creates is tagged with the orchestrator's
39444
39581
  * `jobKey`, so a retried submit finds the job it already started instead of
@@ -39462,8 +39599,8 @@ var TrainingRegionOptionSchema = object({
39462
39599
  priceMultiplier: number().positive()
39463
39600
  });
39464
39601
  /**
39465
- * The provider's per-account settings, as the orchestrator reads them. They
39466
- * live in the PROVIDER addon's settings (operator decision 2026-10-08: they
39602
+ * The provider's per-entry settings, as the orchestrator reads them. They
39603
+ * live on the PROVIDER addon's entry (operator decision 2026-10-08: they
39467
39604
  * depend on the user's account), never hard-coded in the orchestrator.
39468
39605
  */
39469
39606
  var TrainingProviderDefaultsSchema = object({
@@ -39474,9 +39611,44 @@ var TrainingProviderDefaultsSchema = object({
39474
39611
  /** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
39475
39612
  failedDatasetGraceHours: number().nonnegative()
39476
39613
  });
39614
+ /**
39615
+ * Entry form schema for a provider type — same seam as notification
39616
+ * `TargetKind.configSchema` (`z.unknown()` at the wire; callers tighten to
39617
+ * {@link ConfigUISchema}). Drives `ConfigFormBuilder` on Training → Providers.
39618
+ */
39619
+ var ConfigSchemaPassthrough = unknown();
39620
+ /** One creatable provider type, for the Add-entry dialog. */
39621
+ var TrainingProviderTypeSchema = object({
39622
+ typeId: string(),
39623
+ label: string(),
39624
+ /** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
39625
+ iconAsset: string(),
39626
+ /** Owning `training-provider` addon — collection selector for createEntry. */
39627
+ addonId: string(),
39628
+ /**
39629
+ * Fields for one entry of this type (credentials + defaults). Absent on a
39630
+ * type that only needs a name. Password fields are keychain-backed on the
39631
+ * provider addon (`secretStatePaths`), not D634 plaintext.
39632
+ */
39633
+ configSchema: ConfigSchemaPassthrough.optional()
39634
+ });
39635
+ /** One configured account / workspace under a provider type. */
39636
+ var TrainingProviderEntrySchema = object({
39637
+ entryId: string(),
39638
+ typeId: string(),
39639
+ name: string(),
39640
+ addonId: string(),
39641
+ /** Credentials are present — never the values. */
39642
+ configured: boolean()
39643
+ });
39477
39644
  var TrainingProviderDescriptorSchema = object({
39478
39645
  addonId: string(),
39479
- /** `modal`, `runpod`, `fake`, … */
39646
+ entryId: string(),
39647
+ typeId: string(),
39648
+ /** Operator-chosen entry name. */
39649
+ name: string(),
39650
+ iconAsset: string(),
39651
+ /** `modal`, `runpod`, `fake`, … — same as typeId for current providers. */
39480
39652
  kind: string(),
39481
39653
  label: string(),
39482
39654
  gpuTypes: array(TrainingGpuOptionSchema).readonly(),
@@ -39488,7 +39660,30 @@ var TrainingProviderDescriptorSchema = object({
39488
39660
  dataResidencyNote: string(),
39489
39661
  /** Credentials are present — never the values. */
39490
39662
  configured: boolean(),
39491
- defaults: TrainingProviderDefaultsSchema
39663
+ defaults: TrainingProviderDefaultsSchema,
39664
+ /**
39665
+ * Values for {@link TrainingProviderTypeSchema}'s `configSchema` — password
39666
+ * fields are keychain {@link SecretFieldView} or `null` / `''` (create),
39667
+ * never plaintext. Same hydrate contract as settings forms (D789).
39668
+ */
39669
+ formValues: record(string(), unknown()).optional()
39670
+ });
39671
+ /**
39672
+ * Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
39673
+ * An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
39674
+ * `{ $secretClear: true }` clears it.
39675
+ */
39676
+ var TrainingProviderUpdateEntryInputSchema = object({
39677
+ entryId: string().min(1),
39678
+ name: string().min(1),
39679
+ tokenId: string().optional(),
39680
+ tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
39681
+ environment: string().optional(),
39682
+ gpuType: string().optional(),
39683
+ region: string().optional(),
39684
+ costCapUsd: number().nonnegative().optional(),
39685
+ maxRuntimeSec: number().int().positive().optional(),
39686
+ failedDatasetGraceHours: number().nonnegative().optional()
39492
39687
  });
39493
39688
  var TrainingCredentialCheckSchema = object({
39494
39689
  ok: boolean(),
@@ -39540,20 +39735,33 @@ var TrainerSourceRefSchema = object({
39540
39735
  /** A commit sha (never a branch) — the job must be reproducible. */
39541
39736
  revision: string().regex(/^[0-9a-f]{7,64}$/)
39542
39737
  });
39738
+ /** Cloud job identity — route with the owning addon's `addonId` selector. */
39543
39739
  var JobRef = {
39544
- addonId: string(),
39740
+ entryId: string().min(1),
39545
39741
  jobKey: string().uuid()
39546
39742
  };
39547
39743
  var TrainingLogLineSchema = object({
39548
39744
  ts: number(),
39549
39745
  text: string()
39550
39746
  });
39551
- method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({ addonId: string() }), TrainingCredentialCheckSchema, {
39747
+ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderTypeSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderEntrySchema).readonly(), { auth: "admin" }), method(object({
39748
+ typeId: string().min(1),
39749
+ name: string().min(1)
39750
+ }), object({ entryId: string() }), {
39751
+ kind: "mutation",
39752
+ auth: "admin"
39753
+ }), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
39754
+ kind: "mutation",
39755
+ auth: "admin"
39756
+ }), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
39757
+ kind: "mutation",
39758
+ auth: "admin"
39759
+ }), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
39552
39760
  kind: "mutation",
39553
39761
  auth: "admin",
39554
39762
  timeoutMs: 3e4
39555
39763
  }), method(object({
39556
- addonId: string(),
39764
+ entryId: string().min(1),
39557
39765
  gpuType: string(),
39558
39766
  region: string(),
39559
39767
  datasetBytes: number().int().nonnegative(),
@@ -39619,7 +39827,7 @@ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "
39619
39827
  kind: "mutation",
39620
39828
  auth: "admin",
39621
39829
  timeoutMs: 5 * 6e4
39622
- }), method(object({ addonId: string() }), array(ProviderJobStatusSchema).readonly(), {
39830
+ }), method(object({ entryId: string().min(1).optional() }), array(ProviderJobStatusSchema).readonly(), {
39623
39831
  auth: "admin",
39624
39832
  timeoutMs: 2 * 6e4
39625
39833
  });
@@ -39629,20 +39837,31 @@ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "
39629
39837
  *
39630
39838
  * It owns what every provider must agree on, written once:
39631
39839
  *
39632
- * - **Dataset versions.** Taken over from pipeline-analytics (D796): the rows
39633
- * live here (D808); pipeline-analytics still CUTS a version (it owns the corpus and
39634
- * the export plan) and reaches this store through the five row methods
39635
- * below, so the Retrain page and the export did not change.
39636
39840
  * - **Jobs.** A persisted state machine, a reconcile loop against the
39637
39841
  * provider, a cost guard, a daily orphan sweep and the retention rules.
39638
39842
  * - **The return flow.** Verified artefacts → Model Studio
39639
39843
  * (`custom-model-registry.importModelBundle`) → canary on cameras →
39640
39844
  * rollback. Nothing is ever registered automatically.
39641
39845
  *
39846
+ * Plan/start takes a live {@link TrainingCorpusSelection} (collections +
39847
+ * work-state rule); frame ids are resolved via pipeline-analytics at that
39848
+ * moment and snapshotted on the job. No frozen dataset version.
39849
+ *
39642
39850
  * Every method is `admin`.
39643
39851
  */
39644
- /** A version WITH its frame list — what the export and a job read. */
39645
- var TrainingDatasetVersionRowSchema = RetrainDatasetVersionSchema.omit({ frameCount: true }).extend({ frameIds: array(string()).max(2e4).readonly() });
39852
+ /**
39853
+ * Live corpus criteria for plan/start (design §5.2). Default is complete-only.
39854
+ * Frame ids are resolved at plan/start — never a frozen version id.
39855
+ */
39856
+ var TrainingCorpusSelectionSchema = object({
39857
+ collectionIds: array(string()).min(1),
39858
+ /** Default complete-only for v1 */
39859
+ workState: _enum([
39860
+ "complete",
39861
+ "annotated_or_complete",
39862
+ "any_annotated"
39863
+ ]).default("complete")
39864
+ });
39646
39865
  /** A base model a detector job can fine-tune, and the baseline it is judged against. */
39647
39866
  var TrainingBaseModelSchema = object({
39648
39867
  modelId: string(),
@@ -39660,9 +39879,13 @@ var TrainingPresetSchema = _enum([
39660
39879
  "thorough"
39661
39880
  ]);
39662
39881
  var TrainingJobRequestSchema = object({
39663
- datasetVersionId: string().min(1),
39664
- /** The `training-provider` addon that runs it. */
39665
- addonId: string().min(1),
39882
+ /** Live collection + work-state criteria; resolved at plan/start. */
39883
+ selection: TrainingCorpusSelectionSchema,
39884
+ /**
39885
+ * The provider **entry** that runs it (credentials + defaults).
39886
+ * Resolve to an owning addon via `training-provider.describe` / `listEntries`.
39887
+ */
39888
+ entryId: string().min(1),
39666
39889
  gpuType: string().min(1),
39667
39890
  region: string(),
39668
39891
  baseModelId: string().min(1),
@@ -39721,6 +39944,7 @@ var TrainingJobPlanSchema = object({
39721
39944
  * result lives after the operator acted on it.
39722
39945
  */
39723
39946
  var TrainingJobStateSchema = _enum([
39947
+ "queued",
39724
39948
  "uploading",
39725
39949
  "submitted",
39726
39950
  "starting",
@@ -39774,6 +39998,11 @@ var TrainingJobSchema = object({
39774
39998
  createdAt: number(),
39775
39999
  updatedAt: number(),
39776
40000
  state: TrainingJobStateSchema,
40001
+ /**
40002
+ * Shared by every job enqueued together via `startJobBatch`.
40003
+ * `null` for a lone `startJob`. The engine advances queued siblings by this id.
40004
+ */
40005
+ batchId: string().nullable().default(null),
39777
40006
  stage: TrainingStageSchema.nullable(),
39778
40007
  failure: object({
39779
40008
  reason: TrainingFailureReasonSchema,
@@ -39785,6 +40014,12 @@ var TrainingJobSchema = object({
39785
40014
  maxRuntimeSec: number().int().positive(),
39786
40015
  trainer: TrainerSourceRefSchema,
39787
40016
  datasetLayout: RetrainExportLayoutSchema,
40017
+ /** Frame count snapshotted when the job started (audit; equals `frameIds.length`). */
40018
+ frameCount: number().int().nonnegative(),
40019
+ /** Resolved frame ids at start — the archive ships exactly these. */
40020
+ frameIds: array(string()).max(2e4).readonly(),
40021
+ /** Hash of what those frames taught at start (trainer `dataset.annotationHash`). */
40022
+ annotationHash: string(),
39788
40023
  dataset: object({
39789
40024
  bytes: number().int(),
39790
40025
  sha256: string(),
@@ -39826,13 +40061,7 @@ var TrainingJobSchema = object({
39826
40061
  canary: TrainingCanarySchema.nullable()
39827
40062
  });
39828
40063
  var IdInput = object({ id: string().min(1) });
39829
- method(TrainingDatasetVersionRowSchema, object({ stored: boolean() }), {
39830
- kind: "mutation",
39831
- auth: "admin"
39832
- }), method(IdInput, TrainingDatasetVersionRowSchema.nullable(), { auth: "admin" }), method(object({}), array(RetrainDatasetVersionSchema).readonly(), { auth: "admin" }), method(object({ name: string() }), object({ id: string().nullable() }), { auth: "admin" }), method(IdInput, object({ removed: boolean() }), {
39833
- kind: "mutation",
39834
- auth: "admin"
39835
- }), method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" }), method(TrainingJobRequestSchema, TrainingJobPlanSchema, {
40064
+ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" }), method(TrainingJobRequestSchema, TrainingJobPlanSchema, {
39836
40065
  auth: "admin",
39837
40066
  timeoutMs: 6e4
39838
40067
  }), method(TrainingJobRequestSchema.extend({
@@ -39851,6 +40080,24 @@ method(TrainingDatasetVersionRowSchema, object({ stored: boolean() }), {
39851
40080
  kind: "mutation",
39852
40081
  auth: "admin",
39853
40082
  timeoutMs: 12e4
40083
+ }), method(TrainingJobRequestSchema.omit({ baseModelId: true }).extend({
40084
+ baseModelIds: array(string().min(1)).min(1),
40085
+ name: string().min(1).max(120),
40086
+ maxCostUsd: number().positive(),
40087
+ maxRuntimeSec: number().int().positive(),
40088
+ idempotencyKey: string().min(8).max(80)
40089
+ }), discriminatedUnion("kind", [object({
40090
+ kind: literal("started"),
40091
+ batchId: string(),
40092
+ jobs: array(TrainingJobSchema).min(1).readonly()
40093
+ }), object({
40094
+ kind: literal("refused"),
40095
+ reason: TrainingRefusalReasonSchema,
40096
+ detail: string()
40097
+ })]), {
40098
+ kind: "mutation",
40099
+ auth: "admin",
40100
+ timeoutMs: 12e4
39854
40101
  }), method(object({}), array(TrainingJobSchema).readonly(), { auth: "admin" }), method(IdInput, TrainingJobSchema.nullable(), { auth: "admin" }), method(IdInput, TrainingJobSchema, {
39855
40102
  kind: "mutation",
39856
40103
  auth: "admin",
@@ -43884,6 +44131,36 @@ Object.freeze({
43884
44131
  addonId: null,
43885
44132
  access: "view"
43886
44133
  },
44134
+ "oidcConnections.connectionFormSchema": {
44135
+ capName: "oidc-connections",
44136
+ capScope: "system",
44137
+ addonId: null,
44138
+ access: "view"
44139
+ },
44140
+ "oidcConnections.createConnection": {
44141
+ capName: "oidc-connections",
44142
+ capScope: "system",
44143
+ addonId: null,
44144
+ access: "create"
44145
+ },
44146
+ "oidcConnections.deleteConnection": {
44147
+ capName: "oidc-connections",
44148
+ capScope: "system",
44149
+ addonId: null,
44150
+ access: "delete"
44151
+ },
44152
+ "oidcConnections.listConnections": {
44153
+ capName: "oidc-connections",
44154
+ capScope: "system",
44155
+ addonId: null,
44156
+ access: "view"
44157
+ },
44158
+ "oidcConnections.updateConnection": {
44159
+ capName: "oidc-connections",
44160
+ capScope: "system",
44161
+ addonId: null,
44162
+ access: "create"
44163
+ },
43887
44164
  "osd.getStatus": {
43888
44165
  capName: "osd",
43889
44166
  capScope: "device",
@@ -43956,6 +44233,12 @@ Object.freeze({
43956
44233
  addonId: null,
43957
44234
  access: "create"
43958
44235
  },
44236
+ "osdManager.setSlotCameraEnabled": {
44237
+ capName: "osd-manager",
44238
+ capScope: "system",
44239
+ addonId: null,
44240
+ access: "create"
44241
+ },
43959
44242
  "osdManager.upsertTemplate": {
43960
44243
  capName: "osd-manager",
43961
44244
  capScope: "system",
@@ -44094,12 +44377,6 @@ Object.freeze({
44094
44377
  addonId: null,
44095
44378
  access: "create"
44096
44379
  },
44097
- "pipelineAnalytics.createRetrainDatasetVersion": {
44098
- capName: "pipeline-analytics",
44099
- capScope: "device",
44100
- addonId: null,
44101
- access: "create"
44102
- },
44103
44380
  "pipelineAnalytics.deleteDeviceEvents": {
44104
44381
  capName: "pipeline-analytics",
44105
44382
  capScope: "device",
@@ -44112,12 +44389,6 @@ Object.freeze({
44112
44389
  addonId: null,
44113
44390
  access: "delete"
44114
44391
  },
44115
- "pipelineAnalytics.deleteRetrainDatasetVersion": {
44116
- capName: "pipeline-analytics",
44117
- capScope: "device",
44118
- addonId: null,
44119
- access: "delete"
44120
- },
44121
44392
  "pipelineAnalytics.deleteTracks": {
44122
44393
  capName: "pipeline-analytics",
44123
44394
  capScope: "device",
@@ -44220,6 +44491,12 @@ Object.freeze({
44220
44491
  addonId: null,
44221
44492
  access: "view"
44222
44493
  },
44494
+ "pipelineAnalytics.getRetrainCorpusKpi": {
44495
+ capName: "pipeline-analytics",
44496
+ capScope: "device",
44497
+ addonId: null,
44498
+ access: "view"
44499
+ },
44223
44500
  "pipelineAnalytics.getRetrainExportUrl": {
44224
44501
  capName: "pipeline-analytics",
44225
44502
  capScope: "device",
@@ -44340,7 +44617,7 @@ Object.freeze({
44340
44617
  addonId: null,
44341
44618
  access: "view"
44342
44619
  },
44343
- "pipelineAnalytics.listRetrainDatasetVersions": {
44620
+ "pipelineAnalytics.listRetrainCorpusPage": {
44344
44621
  capName: "pipeline-analytics",
44345
44622
  capScope: "device",
44346
44623
  addonId: null,
@@ -44394,12 +44671,6 @@ Object.freeze({
44394
44671
  addonId: null,
44395
44672
  access: "create"
44396
44673
  },
44397
- "pipelineAnalytics.previewRetrainDataset": {
44398
- capName: "pipeline-analytics",
44399
- capScope: "device",
44400
- addonId: null,
44401
- access: "view"
44402
- },
44403
44674
  "pipelineAnalytics.proposeRetrainAnnotations": {
44404
44675
  capName: "pipeline-analytics",
44405
44676
  capScope: "device",
@@ -44478,6 +44749,12 @@ Object.freeze({
44478
44749
  addonId: null,
44479
44750
  access: "create"
44480
44751
  },
44752
+ "pipelineAnalytics.resolveRetrainTrainingCorpus": {
44753
+ capName: "pipeline-analytics",
44754
+ capScope: "device",
44755
+ addonId: null,
44756
+ access: "view"
44757
+ },
44481
44758
  "pipelineAnalytics.restageRetrainTrack": {
44482
44759
  capName: "pipeline-analytics",
44483
44760
  capScope: "device",
@@ -44538,12 +44815,6 @@ Object.freeze({
44538
44815
  addonId: null,
44539
44816
  access: "create"
44540
44817
  },
44541
- "pipelineAnalytics.verifyRetrainDatasetVersion": {
44542
- capName: "pipeline-analytics",
44543
- capScope: "device",
44544
- addonId: null,
44545
- access: "view"
44546
- },
44547
44818
  "pipelineAnalytics.wipeAllAnalytics": {
44548
44819
  capName: "pipeline-analytics",
44549
44820
  capScope: "device",
@@ -46938,18 +47209,6 @@ Object.freeze({
46938
47209
  addonId: null,
46939
47210
  access: "create"
46940
47211
  },
46941
- "training.findDatasetVersionIdByName": {
46942
- capName: "training",
46943
- capScope: "system",
46944
- addonId: null,
46945
- access: "view"
46946
- },
46947
- "training.getDatasetVersion": {
46948
- capName: "training",
46949
- capScope: "system",
46950
- addonId: null,
46951
- access: "view"
46952
- },
46953
47212
  "training.getJob": {
46954
47213
  capName: "training",
46955
47214
  capScope: "system",
@@ -46968,12 +47227,6 @@ Object.freeze({
46968
47227
  addonId: null,
46969
47228
  access: "view"
46970
47229
  },
46971
- "training.listDatasetVersions": {
46972
- capName: "training",
46973
- capScope: "system",
46974
- addonId: null,
46975
- access: "view"
46976
- },
46977
47230
  "training.listJobs": {
46978
47231
  capName: "training",
46979
47232
  capScope: "system",
@@ -46986,54 +47239,66 @@ Object.freeze({
46986
47239
  addonId: null,
46987
47240
  access: "view"
46988
47241
  },
46989
- "training.putDatasetVersion": {
47242
+ "training.registerResult": {
46990
47243
  capName: "training",
46991
47244
  capScope: "system",
46992
47245
  addonId: null,
46993
47246
  access: "create"
46994
47247
  },
46995
- "training.registerResult": {
47248
+ "training.rollbackCanary": {
46996
47249
  capName: "training",
46997
47250
  capScope: "system",
46998
47251
  addonId: null,
46999
47252
  access: "create"
47000
47253
  },
47001
- "training.removeDatasetVersion": {
47254
+ "training.startJob": {
47002
47255
  capName: "training",
47003
47256
  capScope: "system",
47004
47257
  addonId: null,
47005
- access: "delete"
47258
+ access: "create"
47006
47259
  },
47007
- "training.rollbackCanary": {
47260
+ "training.startJobBatch": {
47008
47261
  capName: "training",
47009
47262
  capScope: "system",
47010
47263
  addonId: null,
47011
47264
  access: "create"
47012
47265
  },
47013
- "training.startJob": {
47014
- capName: "training",
47266
+ "trainingProvider.cancelJob": {
47267
+ capName: "training-provider",
47015
47268
  capScope: "system",
47016
47269
  addonId: null,
47017
47270
  access: "create"
47018
47271
  },
47019
- "trainingProvider.cancelJob": {
47272
+ "trainingProvider.cleanupJob": {
47020
47273
  capName: "training-provider",
47021
47274
  capScope: "system",
47022
47275
  addonId: null,
47023
47276
  access: "create"
47024
47277
  },
47025
- "trainingProvider.cleanupJob": {
47278
+ "trainingProvider.createEntry": {
47026
47279
  capName: "training-provider",
47027
47280
  capScope: "system",
47028
47281
  addonId: null,
47029
47282
  access: "create"
47030
47283
  },
47284
+ "trainingProvider.deleteEntry": {
47285
+ capName: "training-provider",
47286
+ capScope: "system",
47287
+ addonId: null,
47288
+ access: "delete"
47289
+ },
47031
47290
  "trainingProvider.describe": {
47032
47291
  capName: "training-provider",
47033
47292
  capScope: "system",
47034
47293
  addonId: null,
47035
47294
  access: "view"
47036
47295
  },
47296
+ "trainingProvider.describeTypes": {
47297
+ capName: "training-provider",
47298
+ capScope: "system",
47299
+ addonId: null,
47300
+ access: "view"
47301
+ },
47037
47302
  "trainingProvider.estimateJob": {
47038
47303
  capName: "training-provider",
47039
47304
  capScope: "system",
@@ -47046,6 +47311,12 @@ Object.freeze({
47046
47311
  addonId: null,
47047
47312
  access: "view"
47048
47313
  },
47314
+ "trainingProvider.listEntries": {
47315
+ capName: "training-provider",
47316
+ capScope: "system",
47317
+ addonId: null,
47318
+ access: "view"
47319
+ },
47049
47320
  "trainingProvider.listJobEvents": {
47050
47321
  capName: "training-provider",
47051
47322
  capScope: "system",
@@ -47076,6 +47347,12 @@ Object.freeze({
47076
47347
  addonId: null,
47077
47348
  access: "create"
47078
47349
  },
47350
+ "trainingProvider.updateEntry": {
47351
+ capName: "training-provider",
47352
+ capScope: "system",
47353
+ addonId: null,
47354
+ access: "create"
47355
+ },
47079
47356
  "trainingProvider.uploadDataset": {
47080
47357
  capName: "training-provider",
47081
47358
  capScope: "system",
@@ -50282,6 +50559,11 @@ Object.freeze({
50282
50559
  form: "single",
50283
50560
  optional: false
50284
50561
  }],
50562
+ "osdManager.setSlotCameraEnabled": [{
50563
+ name: "deviceId",
50564
+ form: "single",
50565
+ optional: false
50566
+ }],
50285
50567
  "petFeeder.callPet": [{
50286
50568
  name: "deviceId",
50287
50569
  form: "single",
@@ -50502,6 +50784,11 @@ Object.freeze({
50502
50784
  form: "array",
50503
50785
  optional: false
50504
50786
  }],
50787
+ "pipelineAnalytics.listRetrainCorpusPage": [{
50788
+ name: "deviceIds",
50789
+ form: "array",
50790
+ optional: true
50791
+ }],
50505
50792
  "pipelineAnalytics.listRetrainStaging": [{
50506
50793
  name: "deviceIds",
50507
50794
  form: "array",
@@ -51611,6 +51898,7 @@ Object.freeze({
51611
51898
  });
51612
51899
  Object.freeze({
51613
51900
  "broker": "broker",
51901
+ "calendar-source": "calendar",
51614
51902
  "device-export": "device-export",
51615
51903
  "llm": "ai",
51616
51904
  "mesh-network": "mesh",
@@ -51695,18 +51983,18 @@ var HTML_ESCAPES = [
51695
51983
  [/"/g, "&quot;"],
51696
51984
  [/'/g, "&#39;"]
51697
51985
  ];
51698
- function escapeHtml$1(text) {
51986
+ function escapeHtml$2(text) {
51699
51987
  let out = text;
51700
51988
  for (const [re, rep] of HTML_ESCAPES) out = out.replace(re, rep);
51701
51989
  return out;
51702
51990
  }
51703
51991
  /** Plain text → HTML-lite (escape + newlines to <br>). */
51704
51992
  function textToHtml(text) {
51705
- return escapeHtml$1(text).replace(/\n/g, "<br>");
51993
+ return escapeHtml$2(text).replace(/\n/g, "<br>");
51706
51994
  }
51707
51995
  /** Markdown → HTML-lite (bold/italic/code/links; used e.g. for Pushover). */
51708
51996
  function markdownToHtmlLite(md) {
51709
- return escapeHtml$1(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51997
+ return escapeHtml$2(md).replace(/(\*\*|__)(.*?)\1/g, "<b>$2</b>").replace(/(\*|_)(.*?)\1/g, "<i>$2</i>").replace(/`([^`]*)`/g, "<code>$1</code>").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "<a href=\"$2\">$1</a>").replace(/\n/g, "<br>");
51710
51998
  }
51711
51999
  /**
51712
52000
  * Preference order when the source format is NOT supported — first supported
@@ -52303,6 +52591,8 @@ var STATE_SUMMARY_VARS = [
52303
52591
  [...RULE_TEXT],
52304
52592
  [...RULE_TEXT],
52305
52593
  [...RULE_TEXT],
52594
+ [...RULE_TEXT],
52595
+ [...RULE_TEXT],
52306
52596
  ([...RULE_TEXT], [...UPDATES]),
52307
52597
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
52308
52598
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
@@ -53062,6 +53352,116 @@ var discordAdapter = {
53062
53352
  };
53063
53353
  }
53064
53354
  };
53355
+ var descriptor$2 = {
53356
+ kind: "email",
53357
+ label: "Email",
53358
+ icon: "mail",
53359
+ addonId: "notifiers",
53360
+ supportsDiscovery: false,
53361
+ configSchema: { sections: [{
53362
+ id: "email",
53363
+ title: "Email",
53364
+ description: "Send via a Connectivity → Email provider (Google Services or SMTP). Install and Verify a provider first.",
53365
+ fields: [
53366
+ {
53367
+ type: "text",
53368
+ key: "to",
53369
+ label: "To",
53370
+ description: "Recipient address, or comma-separated list.",
53371
+ required: true
53372
+ },
53373
+ {
53374
+ type: "text",
53375
+ key: "smtpAddonId",
53376
+ label: "Email provider addon id (optional)",
53377
+ description: "Leave blank to use the first ready smtp-provider. Examples: google-services, smtp-nodemailer."
53378
+ },
53379
+ {
53380
+ type: "text",
53381
+ key: "from",
53382
+ label: "From (optional)",
53383
+ description: "Override From when the relay allows it."
53384
+ }
53385
+ ]
53386
+ }] },
53387
+ caps: {
53388
+ attachments: {
53389
+ mediaTypes: ["image"],
53390
+ mode: "url",
53391
+ max: 0
53392
+ },
53393
+ actions: 0,
53394
+ levels: [{
53395
+ id: "normal",
53396
+ label: "Normal",
53397
+ ordinal: 3
53398
+ }, {
53399
+ id: "high",
53400
+ label: "High",
53401
+ ordinal: 4
53402
+ }],
53403
+ format: [
53404
+ "text",
53405
+ "html",
53406
+ "markdown"
53407
+ ],
53408
+ clickUrl: true,
53409
+ sound: false,
53410
+ ttl: false,
53411
+ bodyMaxLen: 1e5
53412
+ }
53413
+ };
53414
+ function requireTo(config) {
53415
+ const raw = config["to"];
53416
+ if (typeof raw !== "string" || raw.trim() === "") throw new Error("email: missing required config 'to'");
53417
+ const list = raw.split(/[,;\s]+/).map((s) => s.trim()).filter((s) => s.length > 0);
53418
+ if (list.length === 0) throw new Error("email: missing required config 'to'");
53419
+ return list;
53420
+ }
53421
+ function renderBodies(prepared) {
53422
+ const body = prepared.bodyParts[0] ?? prepared.body;
53423
+ const title = prepared.title?.trim() ?? "";
53424
+ const click = prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `\n\n${prepared.clickUrl}` : "";
53425
+ const text = title !== "" ? `${title}\n\n${body}${click}` : `${body}${click}`;
53426
+ if (prepared.format === "html") return {
53427
+ text,
53428
+ html: `${title !== "" ? `<h2>${escapeHtml$1(title)}</h2>` : ""}<div>${body}</div>${prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `<p><a href="${escapeHtml$1(prepared.clickUrl)}">${escapeHtml$1(prepared.clickUrl)}</a></p>` : ""}`
53429
+ };
53430
+ return { text };
53431
+ }
53432
+ function escapeHtml$1(value) {
53433
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53434
+ }
53435
+ /** Build the email adapter bound to a live smtp-provider client. */
53436
+ function createEmailAdapter(smtp) {
53437
+ return {
53438
+ descriptor: descriptor$2,
53439
+ destinationKeys: ["to"],
53440
+ async sendDirect(config, prepared) {
53441
+ try {
53442
+ const to = requireTo(config);
53443
+ const bodies = renderBodies(prepared);
53444
+ const addonId = (typeof config["smtpAddonId"] === "string" && config["smtpAddonId"].trim() !== "" ? config["smtpAddonId"].trim() : void 0) ?? await smtp.resolveDefaultAddonId?.();
53445
+ const subject = prepared.title?.trim() && prepared.title.trim().length > 0 ? prepared.title.trim() : "CamStack notification";
53446
+ const from = typeof config["from"] === "string" && config["from"].trim() !== "" ? config["from"].trim() : void 0;
53447
+ await smtp.sendEmail({
53448
+ to,
53449
+ subject,
53450
+ text: bodies.text,
53451
+ ...bodies.html !== void 0 ? { html: bodies.html } : {},
53452
+ ...addonId !== void 0 ? { addonId } : {},
53453
+ ...from !== void 0 ? { from } : {}
53454
+ });
53455
+ return { success: true };
53456
+ } catch (err) {
53457
+ return {
53458
+ success: false,
53459
+ error: err instanceof Error ? err.message : String(err)
53460
+ };
53461
+ }
53462
+ }
53463
+ };
53464
+ }
53065
53465
  //#endregion
53066
53466
  //#region src/http.ts
53067
53467
  /** Default per-request timeout for notification dispatch. */
@@ -54997,7 +55397,8 @@ function zentikConfigSchema() {
54997
55397
  }
54998
55398
  //#endregion
54999
55399
  //#region src/adapters/registry.ts
55000
- var NOTIFIER_ADAPTERS = [
55400
+ /** HTTP adapters that need no runtime deps. */
55401
+ var HTTP_NOTIFIER_ADAPTERS = [
55001
55402
  ntfyAdapter,
55002
55403
  pushoverAdapter,
55003
55404
  gotifyAdapter,
@@ -55149,6 +55550,17 @@ var NOTIFIER_ADAPTERS = [
55149
55550
  },
55150
55551
  whatsappAdapter
55151
55552
  ];
55553
+ /**
55554
+ * Full catalog. When `smtp` is omitted the Email kind is still listed but
55555
+ * `sendDirect` fails until the addon wires a client (tests use this).
55556
+ */
55557
+ function buildNotifierAdapters(smtp) {
55558
+ const emailSmtp = smtp ?? { async sendEmail() {
55559
+ throw new Error("email: no smtp-provider client — install Google Services or SMTP Email under Connectivity → Email and Verify it");
55560
+ } };
55561
+ return [...HTTP_NOTIFIER_ADAPTERS, createEmailAdapter(emailSmtp)];
55562
+ }
55563
+ buildNotifierAdapters();
55152
55564
  var NOTIFIER_ICONS = {
55153
55565
  telegram: {
55154
55566
  contentType: "image/svg+xml",
@@ -55990,6 +56402,32 @@ function createNotificationOutputProvider(deps) {
55990
56402
  ...prepared,
55991
56403
  attachments
55992
56404
  };
56405
+ if (adapter.sendDirect) try {
56406
+ const result = await adapter.sendDirect(target.config, readyPrepared);
56407
+ return result.error === void 0 ? {
56408
+ ...result,
56409
+ renderedAs: prepared.renderedAs
56410
+ } : {
56411
+ ...result,
56412
+ error: stripUrls(result.error),
56413
+ renderedAs: prepared.renderedAs
56414
+ };
56415
+ } catch (err) {
56416
+ logger?.warn("sendDirect failed", { meta: {
56417
+ kind: target.kind,
56418
+ error: errMsg(err)
56419
+ } });
56420
+ return {
56421
+ success: false,
56422
+ error: `sendDirect failed: ${errMsg(err)}`,
56423
+ renderedAs: prepared.renderedAs
56424
+ };
56425
+ }
56426
+ if (!adapter.buildRequest) return {
56427
+ success: false,
56428
+ error: `No adapter send path for kind '${target.kind}'`,
56429
+ renderedAs: prepared.renderedAs
56430
+ };
55993
56431
  let plan;
55994
56432
  try {
55995
56433
  plan = adapter.buildRequest(target.config, readyPrepared);
@@ -56357,11 +56795,12 @@ var TargetStore = class {
56357
56795
  * Notifiers addon — a system `notification-output` collection provider.
56358
56796
  *
56359
56797
  * It hosts per-kind `NotifierAdapter`s (ntfy, pushover, gotify, telegram,
56360
- * discord, webhook, zentik, whatsapp) behind one uniform cap surface. Targets
56361
- * persist via the `settings-store` cap; the canonical notification is degraded
56362
- * per each target's declared capabilities by the pure `prepareNotification`
56363
- * engine in `@camstack/types`. The catalog is `NOTIFIER_ADAPTERS`
56364
- * (`adapters/registry.ts`).
56798
+ * discord, webhook, zentik, whatsapp, email) behind one uniform cap surface.
56799
+ * Targets persist via the `settings-store` cap; the canonical notification is
56800
+ * degraded per each target's declared capabilities by the pure
56801
+ * `prepareNotification` engine in `@camstack/types`. The catalog is
56802
+ * `buildNotifierAdapters` (`adapters/registry.ts`); email uses `sendDirect` →
56803
+ * `smtp-provider`.
56365
56804
  */
56366
56805
  var NotifiersAddon = class extends BaseAddon {
56367
56806
  constructor() {
@@ -56373,9 +56812,19 @@ var NotifiersAddon = class extends BaseAddon {
56373
56812
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
56374
56813
  const store = new TargetStore(port);
56375
56814
  await store.init();
56815
+ const adapters = buildNotifierAdapters(api !== void 0 ? { sendEmail: async (input) => {
56816
+ return { messageId: (await api.smtpProvider.sendEmail({
56817
+ to: input.to,
56818
+ subject: input.subject,
56819
+ text: input.text,
56820
+ ...input.html !== void 0 ? { html: input.html } : {},
56821
+ ...input.from !== void 0 ? { from: input.from } : {},
56822
+ ...input.addonId !== void 0 ? { addonId: input.addonId } : {}
56823
+ })).messageId };
56824
+ } } : void 0);
56376
56825
  const provider = createNotificationOutputProvider({
56377
56826
  store,
56378
- adapters: NOTIFIER_ADAPTERS,
56827
+ adapters,
56379
56828
  logger: this.ctx.logger.child("notification-output"),
56380
56829
  iconUrlFor,
56381
56830
  iconMediaTypeFor
@@ -56385,7 +56834,7 @@ var NotifiersAddon = class extends BaseAddon {
56385
56834
  provider,
56386
56835
  logger: this.ctx.logger.child("nc-target-actions")
56387
56836
  });
56388
- this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: NOTIFIER_ADAPTERS.length } });
56837
+ this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: adapters.length } });
56389
56838
  return {
56390
56839
  providers: [{
56391
56840
  capability: notificationOutputCapability,