@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.mjs CHANGED
@@ -5380,7 +5380,7 @@ var ZodIssueCode = {
5380
5380
  var ZodFirstPartyTypeKind;
5381
5381
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5382
5382
  //#endregion
5383
- //#region ../types/dist/sleep-r3RWkGVx.mjs
5383
+ //#region ../types/dist/sleep-DwAQ9sqX.mjs
5384
5384
  /**
5385
5385
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5386
5386
  * window to float samples (D455).
@@ -9331,6 +9331,16 @@ var CompositionSchema = object({
9331
9331
  seen.add(feature.cap);
9332
9332
  });
9333
9333
  });
9334
+ var NodeColorIdSchema = _enum([
9335
+ "node-a",
9336
+ "node-b",
9337
+ "node-c",
9338
+ "node-d",
9339
+ "node-e",
9340
+ "node-f",
9341
+ "node-g",
9342
+ "node-h"
9343
+ ]);
9334
9344
  new Set([
9335
9345
  "track",
9336
9346
  "face",
@@ -13677,7 +13687,14 @@ var BrokerProviderInfoSchema = object({
13677
13687
  /** Broker kinds this provider can create, with a display label. */
13678
13688
  kinds: array(object({
13679
13689
  kind: string(),
13680
- label: string()
13690
+ label: string(),
13691
+ /**
13692
+ * Relative hub path to start an interactive account link (OAuth popup).
13693
+ * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
13694
+ */
13695
+ interactiveConnectPath: string().min(1).optional(),
13696
+ /** Relative hub path to disconnect the linked account (same popup pattern). */
13697
+ interactiveDisconnectPath: string().min(1).optional()
13681
13698
  }))
13682
13699
  });
13683
13700
  var ListInputSchema = object({
@@ -18206,14 +18223,14 @@ var LlmProfileSchema = object({
18206
18223
  /** ConfigUISchema tree passed through untyped on the wire (the
18207
18224
  * notification-output `ConfigSchemaPassthrough` precedent at
18208
18225
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
18209
- var ConfigSchemaPassthrough$1 = unknown();
18226
+ var ConfigSchemaPassthrough$3 = unknown();
18210
18227
  var LlmProfileKindDescriptorSchema = object({
18211
18228
  kind: LlmProfileKindSchema,
18212
18229
  label: string(),
18213
18230
  icon: string(),
18214
18231
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
18215
18232
  addonId: string(),
18216
- configSchema: ConfigSchemaPassthrough$1
18233
+ configSchema: ConfigSchemaPassthrough$3
18217
18234
  });
18218
18235
  var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
18219
18236
  var LlmDefaultSchema = object({
@@ -19334,7 +19351,7 @@ var TargetKindCapsSchema = object({
19334
19351
  * the union is large and not meant for runtime validation here; the exported
19335
19352
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
19336
19353
  */
19337
- var ConfigSchemaPassthrough = unknown();
19354
+ var ConfigSchemaPassthrough$2 = unknown();
19338
19355
  var TargetKindSchema = object({
19339
19356
  kind: string(),
19340
19357
  label: string(),
@@ -19371,7 +19388,7 @@ var TargetKindSchema = object({
19371
19388
  * its raster path, which is the safe default for an unknown image.
19372
19389
  */
19373
19390
  iconMediaType: string().optional(),
19374
- configSchema: ConfigSchemaPassthrough,
19391
+ configSchema: ConfigSchemaPassthrough$2,
19375
19392
  /**
19376
19393
  * The config keys that decide WHERE this kind's secrets are sent (server,
19377
19394
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -22692,6 +22709,61 @@ var OauthIntegrationDescriptorSchema = object({
22692
22709
  });
22693
22710
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
22694
22711
  /**
22712
+ * `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
22713
+ *
22714
+ * One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
22715
+ * ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
22716
+ * Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
22717
+ * immutable after create.
22718
+ */
22719
+ var ConfigSchemaPassthrough$1 = unknown();
22720
+ var OidcConnectionSchema = object({
22721
+ id: string().min(1),
22722
+ displayName: string(),
22723
+ icon: string(),
22724
+ issuerUrl: string(),
22725
+ clientId: string(),
22726
+ /** Client id + secret present — never the secret value. */
22727
+ configured: boolean(),
22728
+ /**
22729
+ * Values for the connection form schema. `clientSecret` is a keychain
22730
+ * {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
22731
+ */
22732
+ formValues: record(string(), unknown()).optional()
22733
+ });
22734
+ /**
22735
+ * Patch one connection. Omitted or empty `clientSecret` keeps the stored
22736
+ * secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
22737
+ */
22738
+ var OidcConnectionUpdateInputSchema = object({
22739
+ id: string().min(1),
22740
+ displayName: string().min(1),
22741
+ icon: string().optional(),
22742
+ issuerUrl: string().optional(),
22743
+ clientId: string().optional(),
22744
+ clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
22745
+ redirectUri: string().optional(),
22746
+ scopes: string().optional(),
22747
+ usernameClaim: _enum([
22748
+ "preferred_username",
22749
+ "email",
22750
+ "sub"
22751
+ ]).optional()
22752
+ });
22753
+ method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
22754
+ id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
22755
+ displayName: string().min(1)
22756
+ }), object({ id: string() }), {
22757
+ kind: "mutation",
22758
+ auth: "admin"
22759
+ }), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
22760
+ kind: "mutation",
22761
+ auth: "admin"
22762
+ }), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
22763
+ kind: "mutation",
22764
+ auth: "admin"
22765
+ });
22766
+ /**
22695
22767
  * What started a camera's onboard motion (D672). `camera` is the camera's own
22696
22768
  * firmware; `sensor` is a device the operator ATTACHED to it. Absent on the
22697
22769
  * wire means the camera: only the camera-sensors bridge ever says `sensor`.
@@ -24048,24 +24120,69 @@ var RetrainFrameListSchema = object({
24048
24120
  /** What the page pre-selects — the native key frame when one survives. */
24049
24121
  autoPickMediaId: number().int().optional()
24050
24122
  });
24123
+ /** Exclusive partition of a corpus copy's annotation progress (design §4.2). */
24124
+ var RetrainFrameWorkStateSchema = _enum([
24125
+ "unannotated",
24126
+ "annotated",
24127
+ "complete"
24128
+ ]);
24129
+ /**
24130
+ * Filters for paginated corpus list, KPI, and matching export (design §4.4).
24131
+ * Every narrowing field is ANDed; omitted fields impose no constraint.
24132
+ */
24133
+ var RetrainCorpusFilterSchema = object({
24134
+ workState: RetrainFrameWorkStateSchema.optional(),
24135
+ deviceIds: array(number()).max(500).optional(),
24136
+ /** When set, only frames in this named collection. */
24137
+ collectionId: string().optional(),
24138
+ /** Frames that carry at least one saved annotation in one of these classes. */
24139
+ classes: array(RetrainMacroClassSchema).optional(),
24140
+ page: number().int().positive().default(1),
24141
+ pageSize: number().int().positive().max(100).default(50)
24142
+ });
24143
+ var RetrainCorpusPageSchema = object({
24144
+ frames: array(RetrainFrameSchema.extend({ workState: RetrainFrameWorkStateSchema })).readonly(),
24145
+ total: number().int().nonnegative(),
24146
+ page: number().int().positive(),
24147
+ pageSize: number().int().positive()
24148
+ });
24149
+ var RetrainCorpusKpiCameraRowSchema = object({
24150
+ deviceId: number(),
24151
+ unannotated: number().int().nonnegative(),
24152
+ annotated: number().int().nonnegative(),
24153
+ complete: number().int().nonnegative(),
24154
+ /** This camera's `complete` count divided by corpus `total`, in `[0,1]`.
24155
+ * `null` when the corpus is empty (no ratio). */
24156
+ pctOfCorpus: number().min(0).max(1).nullable()
24157
+ });
24158
+ var RetrainCorpusKpiSchema = object({
24159
+ total: number().int().nonnegative(),
24160
+ unannotated: number().int().nonnegative(),
24161
+ annotated: number().int().nonnegative(),
24162
+ complete: number().int().nonnegative(),
24163
+ cameras: array(RetrainCorpusKpiCameraRowSchema).readonly()
24164
+ });
24051
24165
  /**
24052
24166
  * Which frames an annotated export carries. Every field narrows; an empty
24053
- * filter is every annotated copy. A dataset VERSION is a frozen `frameIds`
24054
- * list owned by whoever trains (the training orchestrator), so `frameIds` is
24055
- * the primitive a version export uses; `batchId` re-selects what an earlier
24056
- * export carried (`exportedInBatch`).
24167
+ * filter is every annotated copy. `frameIds` is the primitive a job or
24168
+ * download uses; `batchId` re-selects what an earlier export carried
24169
+ * (`exportedInBatch`). Frozen dataset versions were removed — live selection
24170
+ * resolves to `frameIds` at plan/start.
24057
24171
  */
24058
24172
  var RetrainExportFilterSchema = object({
24059
24173
  deviceIds: array(number()).optional(),
24060
24174
  frameIds: array(string()).max(2e4).optional(),
24061
24175
  /** Only frames in at least one of these named collections. */
24062
24176
  collectionIds: array(string()).max(200).optional(),
24063
- /** Exactly the frames of this dataset version (and its completeOnly). An
24064
- * unknown id selects nothing. */
24065
- datasetVersionId: string().optional(),
24177
+ /** Single collection (Frames filter bar). ANDed with `collectionIds` when both set. */
24178
+ collectionId: string().optional(),
24066
24179
  batchId: string().optional(),
24067
24180
  /** Only frames the operator marked complete (all subjects boxed). */
24068
- completeOnly: boolean().optional()
24181
+ completeOnly: boolean().optional(),
24182
+ /** Exclusive work-state partition (`workStateOf`); same as the corpus list. */
24183
+ workState: RetrainFrameWorkStateSchema.optional(),
24184
+ /** Frames with at least one saved annotation in one of these classes. */
24185
+ classes: array(RetrainMacroClassSchema).optional()
24069
24186
  });
24070
24187
  /**
24071
24188
  * `frames`: the full frame copies, boxes in `annotations.json`.
@@ -24265,8 +24382,7 @@ var RetrainDatasetStatsSchema = object({
24265
24382
  completeFrames: number().int()
24266
24383
  })).readonly()
24267
24384
  });
24268
- /** An immutable dataset version, without its frame list. */
24269
- var RetrainDatasetVersionSchema = object({
24385
+ object({
24270
24386
  id: string(),
24271
24387
  name: string(),
24272
24388
  createdAt: number(),
@@ -24274,22 +24390,33 @@ var RetrainDatasetVersionSchema = object({
24274
24390
  layout: RetrainExportLayoutSchema,
24275
24391
  completeOnly: boolean(),
24276
24392
  frameCount: number().int(),
24277
- /** sha256 of what the frames taught when the version was frozen. */
24278
24393
  annotationHash: string(),
24279
- /** The export manifest schema the version was cut under. */
24280
24394
  manifestSchema: string(),
24281
24395
  stats: RetrainDatasetStatsSchema
24282
24396
  });
24283
- var CreateRetrainDatasetVersionResultSchema = discriminatedUnion("kind", [object({
24284
- kind: literal("created"),
24285
- version: RetrainDatasetVersionSchema
24397
+ /**
24398
+ * Live training corpus criteria — same shape as `TrainingCorpusSelection` on
24399
+ * the training cap. Resolved here so plan/start and the annotated export share
24400
+ * one definition of "the frames" (`buildRetrainExportPlan`).
24401
+ */
24402
+ var RetrainTrainingCorpusSelectionSchema = object({
24403
+ collectionIds: array(string()).min(1),
24404
+ workState: _enum([
24405
+ "complete",
24406
+ "annotated_or_complete",
24407
+ "any_annotated"
24408
+ ]).default("complete")
24409
+ });
24410
+ var RetrainTrainingCorpusResolveResultSchema = discriminatedUnion("kind", [object({
24411
+ kind: literal("resolved"),
24412
+ frameIds: array(string()).max(2e4).readonly(),
24413
+ stats: RetrainDatasetStatsSchema,
24414
+ annotationHash: string(),
24415
+ layout: RetrainExportLayoutSchema
24286
24416
  }), object({
24287
24417
  kind: literal("refused"),
24288
- reason: _enum([
24289
- "empty-name",
24290
- "duplicate-name",
24291
- "no-frames"
24292
- ])
24418
+ reason: _enum(["unknown-collection", "no-frames"]),
24419
+ detail: string()
24293
24420
  })]);
24294
24421
  /** The outcome of a lifecycle move owned by the retrain page. */
24295
24422
  var RetrainTransitionResultSchema = object({
@@ -24996,6 +25123,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
24996
25123
  }), array(RetrainTrackSchema).readonly(), {
24997
25124
  kind: "query",
24998
25125
  auth: "admin"
25126
+ }), method(RetrainCorpusFilterSchema, RetrainCorpusPageSchema, {
25127
+ kind: "query",
25128
+ auth: "admin"
25129
+ }), method(object({}), RetrainCorpusKpiSchema, {
25130
+ kind: "query",
25131
+ auth: "admin"
24999
25132
  }), method(object({ trackId: string() }), RetrainFrameListSchema, {
25000
25133
  kind: "query",
25001
25134
  auth: "admin"
@@ -25091,36 +25224,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
25091
25224
  kind: "mutation",
25092
25225
  auth: "admin",
25093
25226
  timeoutMs: 12e4
25094
- }), method(object({
25095
- selection: RetrainDatasetSelectionSchema,
25096
- completeOnly: boolean().optional()
25097
- }), RetrainDatasetStatsSchema, {
25098
- kind: "query",
25099
- auth: "admin"
25100
- }), method(object({
25101
- name: string().max(120),
25102
- selection: RetrainDatasetSelectionSchema,
25103
- layout: RetrainExportLayoutSchema,
25104
- completeOnly: boolean()
25105
- }), CreateRetrainDatasetVersionResultSchema, {
25106
- kind: "mutation",
25107
- auth: "admin",
25108
- timeoutMs: 12e4
25109
- }), method(object({}), array(RetrainDatasetVersionSchema).readonly(), {
25110
- kind: "query",
25111
- auth: "admin"
25112
- }), method(object({ id: string() }), object({ deleted: boolean() }), {
25113
- kind: "mutation",
25114
- auth: "admin"
25115
- }), method(object({ id: string() }), object({
25116
- found: boolean(),
25117
- annotationHash: string(),
25118
- matches: boolean(),
25119
- missingFrames: number().int()
25120
- }), {
25121
- kind: "query",
25122
- auth: "admin",
25123
- timeoutMs: 12e4
25124
25227
  }), method(object({}), array(RetrainAssistProfileSchema).readonly(), {
25125
25228
  kind: "query",
25126
25229
  auth: "admin"
@@ -25147,6 +25250,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
25147
25250
  }), method(object({}), array(RetrainCollectionSchema).readonly(), {
25148
25251
  kind: "query",
25149
25252
  auth: "admin"
25253
+ }), method(RetrainTrainingCorpusSelectionSchema, RetrainTrainingCorpusResolveResultSchema, {
25254
+ kind: "query",
25255
+ auth: "admin",
25256
+ timeoutMs: 6e4
25150
25257
  }), method(object({
25151
25258
  id: string(),
25152
25259
  name: string().max(120)
@@ -25763,11 +25870,14 @@ var HardwareClassIdSchema = _enum([
25763
25870
  "intel-n-series",
25764
25871
  "apple-m-base",
25765
25872
  "apple-m-pro",
25873
+ "amd-phoenix",
25766
25874
  "unknown"
25767
25875
  ]);
25768
25876
  var AcceleratorKindSchema = _enum([
25769
25877
  "intel-npu",
25770
25878
  "intel-igpu",
25879
+ "amd-igpu",
25880
+ "amd-npu",
25771
25881
  "x86-cpu",
25772
25882
  "arm-cpu",
25773
25883
  "apple-ane",
@@ -27657,6 +27767,8 @@ var AgentPipelineSettingsSchema = object({
27657
27767
  * address is wrong (multi-homed host, NAT, custom interface).
27658
27768
  */
27659
27769
  reachableHost: string().optional(),
27770
+ /** Hub-persisted UI colour (`--color-node-*` theme tokens). */
27771
+ color: NodeColorIdSchema.optional(),
27660
27772
  /**
27661
27773
  * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
27662
27774
  * weight, steps}. Absent / all-disabled ⇒ the node's single default
@@ -28206,8 +28318,9 @@ method(object({
28206
28318
  auth: "admin"
28207
28319
  }), method(object({
28208
28320
  agentNodeId: string(),
28209
- reachableHost: string().nullable()
28210
- }), object({ success: literal(true) }), {
28321
+ reachableHost: string().nullable().optional(),
28322
+ color: NodeColorIdSchema.nullable().optional()
28323
+ }).refine((v) => v.reachableHost !== void 0 || v.color !== void 0, { message: "reachableHost or color required" }), object({ success: literal(true) }), {
28211
28324
  kind: "mutation",
28212
28325
  auth: "admin"
28213
28326
  }), method(object({
@@ -31254,7 +31367,7 @@ var CustomActionInputSchema = object({
31254
31367
  action: string().min(1),
31255
31368
  input: unknown()
31256
31369
  });
31257
- method(_void(), array(AddonListItemSchema).readonly()), method(object({
31370
+ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSchema).readonly()), method(object({
31258
31371
  addonId: string(),
31259
31372
  limit: number().min(1).max(500).default(100),
31260
31373
  level: LogLevelSchema$2.optional()
@@ -34801,7 +34914,13 @@ var TopologyNodeSchema = object({
34801
34914
  })).readonly(),
34802
34915
  processes: array(TopologyProcessSchema).readonly(),
34803
34916
  categories: array(TopologyCategorySchema).readonly(),
34804
- rootPackage: TopologyRootPackageSchema.nullable()
34917
+ rootPackage: TopologyRootPackageSchema.nullable(),
34918
+ /**
34919
+ * How an online AGENT reaches the hub (mesh mTLS Release A): `tls` once it has
34920
+ * completed a mutual-TLS handshake, `clear` otherwise. Absent for the hub,
34921
+ * for offline rows and when the hub cannot tell.
34922
+ */
34923
+ meshTransport: _enum(["tls", "clear"]).optional()
34805
34924
  });
34806
34925
  var CapUsageEdgeSchema = object({
34807
34926
  callerAddonId: string(),
@@ -35141,6 +35260,7 @@ var OsdNamedTemplateSchema = object({
35141
35260
  });
35142
35261
  /** Where a slot's value comes from. */
35143
35262
  var OsdSourceSchema = discriminatedUnion("kind", [
35263
+ object({ kind: literal("none") }),
35144
35264
  object({
35145
35265
  kind: literal("static"),
35146
35266
  text: string().max(64)
@@ -35267,6 +35387,13 @@ method(object({ deviceId: number().int() }), object({
35267
35387
  }), object({ success: literal(true) }), {
35268
35388
  kind: "mutation",
35269
35389
  auth: "admin"
35390
+ }), method(object({
35391
+ deviceId: number().int(),
35392
+ slotId: string().min(1),
35393
+ enabled: boolean()
35394
+ }), object({ success: literal(true) }), {
35395
+ kind: "mutation",
35396
+ auth: "admin"
35270
35397
  }), method(object({
35271
35398
  sourceDeviceId: number().int(),
35272
35399
  targetDeviceId: number().int()
@@ -35647,7 +35774,15 @@ var GpuInfoSchema = object({
35647
35774
  name: string(),
35648
35775
  memoryMB: number().optional()
35649
35776
  });
35650
- var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
35777
+ var NpuInfoSchema = object({
35778
+ type: _enum([
35779
+ "apple-ane",
35780
+ "intel-npu",
35781
+ "amd-xdna"
35782
+ ]),
35783
+ name: string().optional(),
35784
+ generation: _enum(["xdna1", "xdna2"]).optional()
35785
+ });
35651
35786
  var CoralInfoSchema = object({
35652
35787
  type: literal("coral-edgetpu"),
35653
35788
  bus: string().optional()
@@ -39409,9 +39544,11 @@ DeviceType.Camera, method(object({
39409
39544
  * once is what makes a second provider (RunPod, a local NVIDIA node) a new
39410
39545
  * manifest entry and nothing else.
39411
39546
  *
39412
- * Collection, routed by the `addonId` in every single-row input (the `llm` /
39413
- * `notification-output` posture); `describe` concat-fans across providers.
39414
- * Every method is `admin`: these spend money.
39547
+ * Collection of provider **types** (addons). Each addon owns zero or more
39548
+ * **entries** (credentials + defaults). Concat-fans: `describe`,
39549
+ * `describeTypes`, `listEntries`, `listJobs`. Single-row work is keyed by
39550
+ * `entryId`; callers also pass the owning `addonId` as the collection
39551
+ * selector (broker posture). Every method is `admin`: these spend money.
39415
39552
  *
39416
39553
  * Every cloud object a provider creates is tagged with the orchestrator's
39417
39554
  * `jobKey`, so a retried submit finds the job it already started instead of
@@ -39435,8 +39572,8 @@ var TrainingRegionOptionSchema = object({
39435
39572
  priceMultiplier: number().positive()
39436
39573
  });
39437
39574
  /**
39438
- * The provider's per-account settings, as the orchestrator reads them. They
39439
- * live in the PROVIDER addon's settings (operator decision 2026-10-08: they
39575
+ * The provider's per-entry settings, as the orchestrator reads them. They
39576
+ * live on the PROVIDER addon's entry (operator decision 2026-10-08: they
39440
39577
  * depend on the user's account), never hard-coded in the orchestrator.
39441
39578
  */
39442
39579
  var TrainingProviderDefaultsSchema = object({
@@ -39447,9 +39584,44 @@ var TrainingProviderDefaultsSchema = object({
39447
39584
  /** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
39448
39585
  failedDatasetGraceHours: number().nonnegative()
39449
39586
  });
39587
+ /**
39588
+ * Entry form schema for a provider type — same seam as notification
39589
+ * `TargetKind.configSchema` (`z.unknown()` at the wire; callers tighten to
39590
+ * {@link ConfigUISchema}). Drives `ConfigFormBuilder` on Training → Providers.
39591
+ */
39592
+ var ConfigSchemaPassthrough = unknown();
39593
+ /** One creatable provider type, for the Add-entry dialog. */
39594
+ var TrainingProviderTypeSchema = object({
39595
+ typeId: string(),
39596
+ label: string(),
39597
+ /** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
39598
+ iconAsset: string(),
39599
+ /** Owning `training-provider` addon — collection selector for createEntry. */
39600
+ addonId: string(),
39601
+ /**
39602
+ * Fields for one entry of this type (credentials + defaults). Absent on a
39603
+ * type that only needs a name. Password fields are keychain-backed on the
39604
+ * provider addon (`secretStatePaths`), not D634 plaintext.
39605
+ */
39606
+ configSchema: ConfigSchemaPassthrough.optional()
39607
+ });
39608
+ /** One configured account / workspace under a provider type. */
39609
+ var TrainingProviderEntrySchema = object({
39610
+ entryId: string(),
39611
+ typeId: string(),
39612
+ name: string(),
39613
+ addonId: string(),
39614
+ /** Credentials are present — never the values. */
39615
+ configured: boolean()
39616
+ });
39450
39617
  var TrainingProviderDescriptorSchema = object({
39451
39618
  addonId: string(),
39452
- /** `modal`, `runpod`, `fake`, … */
39619
+ entryId: string(),
39620
+ typeId: string(),
39621
+ /** Operator-chosen entry name. */
39622
+ name: string(),
39623
+ iconAsset: string(),
39624
+ /** `modal`, `runpod`, `fake`, … — same as typeId for current providers. */
39453
39625
  kind: string(),
39454
39626
  label: string(),
39455
39627
  gpuTypes: array(TrainingGpuOptionSchema).readonly(),
@@ -39461,7 +39633,30 @@ var TrainingProviderDescriptorSchema = object({
39461
39633
  dataResidencyNote: string(),
39462
39634
  /** Credentials are present — never the values. */
39463
39635
  configured: boolean(),
39464
- defaults: TrainingProviderDefaultsSchema
39636
+ defaults: TrainingProviderDefaultsSchema,
39637
+ /**
39638
+ * Values for {@link TrainingProviderTypeSchema}'s `configSchema` — password
39639
+ * fields are keychain {@link SecretFieldView} or `null` / `''` (create),
39640
+ * never plaintext. Same hydrate contract as settings forms (D789).
39641
+ */
39642
+ formValues: record(string(), unknown()).optional()
39643
+ });
39644
+ /**
39645
+ * Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
39646
+ * An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
39647
+ * `{ $secretClear: true }` clears it.
39648
+ */
39649
+ var TrainingProviderUpdateEntryInputSchema = object({
39650
+ entryId: string().min(1),
39651
+ name: string().min(1),
39652
+ tokenId: string().optional(),
39653
+ tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
39654
+ environment: string().optional(),
39655
+ gpuType: string().optional(),
39656
+ region: string().optional(),
39657
+ costCapUsd: number().nonnegative().optional(),
39658
+ maxRuntimeSec: number().int().positive().optional(),
39659
+ failedDatasetGraceHours: number().nonnegative().optional()
39465
39660
  });
39466
39661
  var TrainingCredentialCheckSchema = object({
39467
39662
  ok: boolean(),
@@ -39513,20 +39708,33 @@ var TrainerSourceRefSchema = object({
39513
39708
  /** A commit sha (never a branch) — the job must be reproducible. */
39514
39709
  revision: string().regex(/^[0-9a-f]{7,64}$/)
39515
39710
  });
39711
+ /** Cloud job identity — route with the owning addon's `addonId` selector. */
39516
39712
  var JobRef = {
39517
- addonId: string(),
39713
+ entryId: string().min(1),
39518
39714
  jobKey: string().uuid()
39519
39715
  };
39520
39716
  var TrainingLogLineSchema = object({
39521
39717
  ts: number(),
39522
39718
  text: string()
39523
39719
  });
39524
- method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({ addonId: string() }), TrainingCredentialCheckSchema, {
39720
+ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderTypeSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderEntrySchema).readonly(), { auth: "admin" }), method(object({
39721
+ typeId: string().min(1),
39722
+ name: string().min(1)
39723
+ }), object({ entryId: string() }), {
39724
+ kind: "mutation",
39725
+ auth: "admin"
39726
+ }), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
39727
+ kind: "mutation",
39728
+ auth: "admin"
39729
+ }), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
39730
+ kind: "mutation",
39731
+ auth: "admin"
39732
+ }), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
39525
39733
  kind: "mutation",
39526
39734
  auth: "admin",
39527
39735
  timeoutMs: 3e4
39528
39736
  }), method(object({
39529
- addonId: string(),
39737
+ entryId: string().min(1),
39530
39738
  gpuType: string(),
39531
39739
  region: string(),
39532
39740
  datasetBytes: number().int().nonnegative(),
@@ -39592,7 +39800,7 @@ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "
39592
39800
  kind: "mutation",
39593
39801
  auth: "admin",
39594
39802
  timeoutMs: 5 * 6e4
39595
- }), method(object({ addonId: string() }), array(ProviderJobStatusSchema).readonly(), {
39803
+ }), method(object({ entryId: string().min(1).optional() }), array(ProviderJobStatusSchema).readonly(), {
39596
39804
  auth: "admin",
39597
39805
  timeoutMs: 2 * 6e4
39598
39806
  });
@@ -39602,20 +39810,31 @@ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "
39602
39810
  *
39603
39811
  * It owns what every provider must agree on, written once:
39604
39812
  *
39605
- * - **Dataset versions.** Taken over from pipeline-analytics (D796): the rows
39606
- * live here (D808); pipeline-analytics still CUTS a version (it owns the corpus and
39607
- * the export plan) and reaches this store through the five row methods
39608
- * below, so the Retrain page and the export did not change.
39609
39813
  * - **Jobs.** A persisted state machine, a reconcile loop against the
39610
39814
  * provider, a cost guard, a daily orphan sweep and the retention rules.
39611
39815
  * - **The return flow.** Verified artefacts → Model Studio
39612
39816
  * (`custom-model-registry.importModelBundle`) → canary on cameras →
39613
39817
  * rollback. Nothing is ever registered automatically.
39614
39818
  *
39819
+ * Plan/start takes a live {@link TrainingCorpusSelection} (collections +
39820
+ * work-state rule); frame ids are resolved via pipeline-analytics at that
39821
+ * moment and snapshotted on the job. No frozen dataset version.
39822
+ *
39615
39823
  * Every method is `admin`.
39616
39824
  */
39617
- /** A version WITH its frame list — what the export and a job read. */
39618
- var TrainingDatasetVersionRowSchema = RetrainDatasetVersionSchema.omit({ frameCount: true }).extend({ frameIds: array(string()).max(2e4).readonly() });
39825
+ /**
39826
+ * Live corpus criteria for plan/start (design §5.2). Default is complete-only.
39827
+ * Frame ids are resolved at plan/start — never a frozen version id.
39828
+ */
39829
+ var TrainingCorpusSelectionSchema = object({
39830
+ collectionIds: array(string()).min(1),
39831
+ /** Default complete-only for v1 */
39832
+ workState: _enum([
39833
+ "complete",
39834
+ "annotated_or_complete",
39835
+ "any_annotated"
39836
+ ]).default("complete")
39837
+ });
39619
39838
  /** A base model a detector job can fine-tune, and the baseline it is judged against. */
39620
39839
  var TrainingBaseModelSchema = object({
39621
39840
  modelId: string(),
@@ -39633,9 +39852,13 @@ var TrainingPresetSchema = _enum([
39633
39852
  "thorough"
39634
39853
  ]);
39635
39854
  var TrainingJobRequestSchema = object({
39636
- datasetVersionId: string().min(1),
39637
- /** The `training-provider` addon that runs it. */
39638
- addonId: string().min(1),
39855
+ /** Live collection + work-state criteria; resolved at plan/start. */
39856
+ selection: TrainingCorpusSelectionSchema,
39857
+ /**
39858
+ * The provider **entry** that runs it (credentials + defaults).
39859
+ * Resolve to an owning addon via `training-provider.describe` / `listEntries`.
39860
+ */
39861
+ entryId: string().min(1),
39639
39862
  gpuType: string().min(1),
39640
39863
  region: string(),
39641
39864
  baseModelId: string().min(1),
@@ -39694,6 +39917,7 @@ var TrainingJobPlanSchema = object({
39694
39917
  * result lives after the operator acted on it.
39695
39918
  */
39696
39919
  var TrainingJobStateSchema = _enum([
39920
+ "queued",
39697
39921
  "uploading",
39698
39922
  "submitted",
39699
39923
  "starting",
@@ -39747,6 +39971,11 @@ var TrainingJobSchema = object({
39747
39971
  createdAt: number(),
39748
39972
  updatedAt: number(),
39749
39973
  state: TrainingJobStateSchema,
39974
+ /**
39975
+ * Shared by every job enqueued together via `startJobBatch`.
39976
+ * `null` for a lone `startJob`. The engine advances queued siblings by this id.
39977
+ */
39978
+ batchId: string().nullable().default(null),
39750
39979
  stage: TrainingStageSchema.nullable(),
39751
39980
  failure: object({
39752
39981
  reason: TrainingFailureReasonSchema,
@@ -39758,6 +39987,12 @@ var TrainingJobSchema = object({
39758
39987
  maxRuntimeSec: number().int().positive(),
39759
39988
  trainer: TrainerSourceRefSchema,
39760
39989
  datasetLayout: RetrainExportLayoutSchema,
39990
+ /** Frame count snapshotted when the job started (audit; equals `frameIds.length`). */
39991
+ frameCount: number().int().nonnegative(),
39992
+ /** Resolved frame ids at start — the archive ships exactly these. */
39993
+ frameIds: array(string()).max(2e4).readonly(),
39994
+ /** Hash of what those frames taught at start (trainer `dataset.annotationHash`). */
39995
+ annotationHash: string(),
39761
39996
  dataset: object({
39762
39997
  bytes: number().int(),
39763
39998
  sha256: string(),
@@ -39799,13 +40034,7 @@ var TrainingJobSchema = object({
39799
40034
  canary: TrainingCanarySchema.nullable()
39800
40035
  });
39801
40036
  var IdInput = object({ id: string().min(1) });
39802
- method(TrainingDatasetVersionRowSchema, object({ stored: boolean() }), {
39803
- kind: "mutation",
39804
- auth: "admin"
39805
- }), 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() }), {
39806
- kind: "mutation",
39807
- auth: "admin"
39808
- }), method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" }), method(TrainingJobRequestSchema, TrainingJobPlanSchema, {
40037
+ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" }), method(TrainingJobRequestSchema, TrainingJobPlanSchema, {
39809
40038
  auth: "admin",
39810
40039
  timeoutMs: 6e4
39811
40040
  }), method(TrainingJobRequestSchema.extend({
@@ -39824,6 +40053,24 @@ method(TrainingDatasetVersionRowSchema, object({ stored: boolean() }), {
39824
40053
  kind: "mutation",
39825
40054
  auth: "admin",
39826
40055
  timeoutMs: 12e4
40056
+ }), method(TrainingJobRequestSchema.omit({ baseModelId: true }).extend({
40057
+ baseModelIds: array(string().min(1)).min(1),
40058
+ name: string().min(1).max(120),
40059
+ maxCostUsd: number().positive(),
40060
+ maxRuntimeSec: number().int().positive(),
40061
+ idempotencyKey: string().min(8).max(80)
40062
+ }), discriminatedUnion("kind", [object({
40063
+ kind: literal("started"),
40064
+ batchId: string(),
40065
+ jobs: array(TrainingJobSchema).min(1).readonly()
40066
+ }), object({
40067
+ kind: literal("refused"),
40068
+ reason: TrainingRefusalReasonSchema,
40069
+ detail: string()
40070
+ })]), {
40071
+ kind: "mutation",
40072
+ auth: "admin",
40073
+ timeoutMs: 12e4
39827
40074
  }), method(object({}), array(TrainingJobSchema).readonly(), { auth: "admin" }), method(IdInput, TrainingJobSchema.nullable(), { auth: "admin" }), method(IdInput, TrainingJobSchema, {
39828
40075
  kind: "mutation",
39829
40076
  auth: "admin",
@@ -43857,6 +44104,36 @@ Object.freeze({
43857
44104
  addonId: null,
43858
44105
  access: "view"
43859
44106
  },
44107
+ "oidcConnections.connectionFormSchema": {
44108
+ capName: "oidc-connections",
44109
+ capScope: "system",
44110
+ addonId: null,
44111
+ access: "view"
44112
+ },
44113
+ "oidcConnections.createConnection": {
44114
+ capName: "oidc-connections",
44115
+ capScope: "system",
44116
+ addonId: null,
44117
+ access: "create"
44118
+ },
44119
+ "oidcConnections.deleteConnection": {
44120
+ capName: "oidc-connections",
44121
+ capScope: "system",
44122
+ addonId: null,
44123
+ access: "delete"
44124
+ },
44125
+ "oidcConnections.listConnections": {
44126
+ capName: "oidc-connections",
44127
+ capScope: "system",
44128
+ addonId: null,
44129
+ access: "view"
44130
+ },
44131
+ "oidcConnections.updateConnection": {
44132
+ capName: "oidc-connections",
44133
+ capScope: "system",
44134
+ addonId: null,
44135
+ access: "create"
44136
+ },
43860
44137
  "osd.getStatus": {
43861
44138
  capName: "osd",
43862
44139
  capScope: "device",
@@ -43929,6 +44206,12 @@ Object.freeze({
43929
44206
  addonId: null,
43930
44207
  access: "create"
43931
44208
  },
44209
+ "osdManager.setSlotCameraEnabled": {
44210
+ capName: "osd-manager",
44211
+ capScope: "system",
44212
+ addonId: null,
44213
+ access: "create"
44214
+ },
43932
44215
  "osdManager.upsertTemplate": {
43933
44216
  capName: "osd-manager",
43934
44217
  capScope: "system",
@@ -44067,12 +44350,6 @@ Object.freeze({
44067
44350
  addonId: null,
44068
44351
  access: "create"
44069
44352
  },
44070
- "pipelineAnalytics.createRetrainDatasetVersion": {
44071
- capName: "pipeline-analytics",
44072
- capScope: "device",
44073
- addonId: null,
44074
- access: "create"
44075
- },
44076
44353
  "pipelineAnalytics.deleteDeviceEvents": {
44077
44354
  capName: "pipeline-analytics",
44078
44355
  capScope: "device",
@@ -44085,12 +44362,6 @@ Object.freeze({
44085
44362
  addonId: null,
44086
44363
  access: "delete"
44087
44364
  },
44088
- "pipelineAnalytics.deleteRetrainDatasetVersion": {
44089
- capName: "pipeline-analytics",
44090
- capScope: "device",
44091
- addonId: null,
44092
- access: "delete"
44093
- },
44094
44365
  "pipelineAnalytics.deleteTracks": {
44095
44366
  capName: "pipeline-analytics",
44096
44367
  capScope: "device",
@@ -44193,6 +44464,12 @@ Object.freeze({
44193
44464
  addonId: null,
44194
44465
  access: "view"
44195
44466
  },
44467
+ "pipelineAnalytics.getRetrainCorpusKpi": {
44468
+ capName: "pipeline-analytics",
44469
+ capScope: "device",
44470
+ addonId: null,
44471
+ access: "view"
44472
+ },
44196
44473
  "pipelineAnalytics.getRetrainExportUrl": {
44197
44474
  capName: "pipeline-analytics",
44198
44475
  capScope: "device",
@@ -44313,7 +44590,7 @@ Object.freeze({
44313
44590
  addonId: null,
44314
44591
  access: "view"
44315
44592
  },
44316
- "pipelineAnalytics.listRetrainDatasetVersions": {
44593
+ "pipelineAnalytics.listRetrainCorpusPage": {
44317
44594
  capName: "pipeline-analytics",
44318
44595
  capScope: "device",
44319
44596
  addonId: null,
@@ -44367,12 +44644,6 @@ Object.freeze({
44367
44644
  addonId: null,
44368
44645
  access: "create"
44369
44646
  },
44370
- "pipelineAnalytics.previewRetrainDataset": {
44371
- capName: "pipeline-analytics",
44372
- capScope: "device",
44373
- addonId: null,
44374
- access: "view"
44375
- },
44376
44647
  "pipelineAnalytics.proposeRetrainAnnotations": {
44377
44648
  capName: "pipeline-analytics",
44378
44649
  capScope: "device",
@@ -44451,6 +44722,12 @@ Object.freeze({
44451
44722
  addonId: null,
44452
44723
  access: "create"
44453
44724
  },
44725
+ "pipelineAnalytics.resolveRetrainTrainingCorpus": {
44726
+ capName: "pipeline-analytics",
44727
+ capScope: "device",
44728
+ addonId: null,
44729
+ access: "view"
44730
+ },
44454
44731
  "pipelineAnalytics.restageRetrainTrack": {
44455
44732
  capName: "pipeline-analytics",
44456
44733
  capScope: "device",
@@ -44511,12 +44788,6 @@ Object.freeze({
44511
44788
  addonId: null,
44512
44789
  access: "create"
44513
44790
  },
44514
- "pipelineAnalytics.verifyRetrainDatasetVersion": {
44515
- capName: "pipeline-analytics",
44516
- capScope: "device",
44517
- addonId: null,
44518
- access: "view"
44519
- },
44520
44791
  "pipelineAnalytics.wipeAllAnalytics": {
44521
44792
  capName: "pipeline-analytics",
44522
44793
  capScope: "device",
@@ -46911,18 +47182,6 @@ Object.freeze({
46911
47182
  addonId: null,
46912
47183
  access: "create"
46913
47184
  },
46914
- "training.findDatasetVersionIdByName": {
46915
- capName: "training",
46916
- capScope: "system",
46917
- addonId: null,
46918
- access: "view"
46919
- },
46920
- "training.getDatasetVersion": {
46921
- capName: "training",
46922
- capScope: "system",
46923
- addonId: null,
46924
- access: "view"
46925
- },
46926
47185
  "training.getJob": {
46927
47186
  capName: "training",
46928
47187
  capScope: "system",
@@ -46941,12 +47200,6 @@ Object.freeze({
46941
47200
  addonId: null,
46942
47201
  access: "view"
46943
47202
  },
46944
- "training.listDatasetVersions": {
46945
- capName: "training",
46946
- capScope: "system",
46947
- addonId: null,
46948
- access: "view"
46949
- },
46950
47203
  "training.listJobs": {
46951
47204
  capName: "training",
46952
47205
  capScope: "system",
@@ -46959,54 +47212,66 @@ Object.freeze({
46959
47212
  addonId: null,
46960
47213
  access: "view"
46961
47214
  },
46962
- "training.putDatasetVersion": {
47215
+ "training.registerResult": {
46963
47216
  capName: "training",
46964
47217
  capScope: "system",
46965
47218
  addonId: null,
46966
47219
  access: "create"
46967
47220
  },
46968
- "training.registerResult": {
47221
+ "training.rollbackCanary": {
46969
47222
  capName: "training",
46970
47223
  capScope: "system",
46971
47224
  addonId: null,
46972
47225
  access: "create"
46973
47226
  },
46974
- "training.removeDatasetVersion": {
47227
+ "training.startJob": {
46975
47228
  capName: "training",
46976
47229
  capScope: "system",
46977
47230
  addonId: null,
46978
- access: "delete"
47231
+ access: "create"
46979
47232
  },
46980
- "training.rollbackCanary": {
47233
+ "training.startJobBatch": {
46981
47234
  capName: "training",
46982
47235
  capScope: "system",
46983
47236
  addonId: null,
46984
47237
  access: "create"
46985
47238
  },
46986
- "training.startJob": {
46987
- capName: "training",
47239
+ "trainingProvider.cancelJob": {
47240
+ capName: "training-provider",
46988
47241
  capScope: "system",
46989
47242
  addonId: null,
46990
47243
  access: "create"
46991
47244
  },
46992
- "trainingProvider.cancelJob": {
47245
+ "trainingProvider.cleanupJob": {
46993
47246
  capName: "training-provider",
46994
47247
  capScope: "system",
46995
47248
  addonId: null,
46996
47249
  access: "create"
46997
47250
  },
46998
- "trainingProvider.cleanupJob": {
47251
+ "trainingProvider.createEntry": {
46999
47252
  capName: "training-provider",
47000
47253
  capScope: "system",
47001
47254
  addonId: null,
47002
47255
  access: "create"
47003
47256
  },
47257
+ "trainingProvider.deleteEntry": {
47258
+ capName: "training-provider",
47259
+ capScope: "system",
47260
+ addonId: null,
47261
+ access: "delete"
47262
+ },
47004
47263
  "trainingProvider.describe": {
47005
47264
  capName: "training-provider",
47006
47265
  capScope: "system",
47007
47266
  addonId: null,
47008
47267
  access: "view"
47009
47268
  },
47269
+ "trainingProvider.describeTypes": {
47270
+ capName: "training-provider",
47271
+ capScope: "system",
47272
+ addonId: null,
47273
+ access: "view"
47274
+ },
47010
47275
  "trainingProvider.estimateJob": {
47011
47276
  capName: "training-provider",
47012
47277
  capScope: "system",
@@ -47019,6 +47284,12 @@ Object.freeze({
47019
47284
  addonId: null,
47020
47285
  access: "view"
47021
47286
  },
47287
+ "trainingProvider.listEntries": {
47288
+ capName: "training-provider",
47289
+ capScope: "system",
47290
+ addonId: null,
47291
+ access: "view"
47292
+ },
47022
47293
  "trainingProvider.listJobEvents": {
47023
47294
  capName: "training-provider",
47024
47295
  capScope: "system",
@@ -47049,6 +47320,12 @@ Object.freeze({
47049
47320
  addonId: null,
47050
47321
  access: "create"
47051
47322
  },
47323
+ "trainingProvider.updateEntry": {
47324
+ capName: "training-provider",
47325
+ capScope: "system",
47326
+ addonId: null,
47327
+ access: "create"
47328
+ },
47052
47329
  "trainingProvider.uploadDataset": {
47053
47330
  capName: "training-provider",
47054
47331
  capScope: "system",
@@ -50255,6 +50532,11 @@ Object.freeze({
50255
50532
  form: "single",
50256
50533
  optional: false
50257
50534
  }],
50535
+ "osdManager.setSlotCameraEnabled": [{
50536
+ name: "deviceId",
50537
+ form: "single",
50538
+ optional: false
50539
+ }],
50258
50540
  "petFeeder.callPet": [{
50259
50541
  name: "deviceId",
50260
50542
  form: "single",
@@ -50475,6 +50757,11 @@ Object.freeze({
50475
50757
  form: "array",
50476
50758
  optional: false
50477
50759
  }],
50760
+ "pipelineAnalytics.listRetrainCorpusPage": [{
50761
+ name: "deviceIds",
50762
+ form: "array",
50763
+ optional: true
50764
+ }],
50478
50765
  "pipelineAnalytics.listRetrainStaging": [{
50479
50766
  name: "deviceIds",
50480
50767
  form: "array",
@@ -51584,6 +51871,7 @@ Object.freeze({
51584
51871
  });
51585
51872
  Object.freeze({
51586
51873
  "broker": "broker",
51874
+ "calendar-source": "calendar",
51587
51875
  "device-export": "device-export",
51588
51876
  "llm": "ai",
51589
51877
  "mesh-network": "mesh",
@@ -51668,18 +51956,18 @@ var HTML_ESCAPES = [
51668
51956
  [/"/g, "&quot;"],
51669
51957
  [/'/g, "&#39;"]
51670
51958
  ];
51671
- function escapeHtml$1(text) {
51959
+ function escapeHtml$2(text) {
51672
51960
  let out = text;
51673
51961
  for (const [re, rep] of HTML_ESCAPES) out = out.replace(re, rep);
51674
51962
  return out;
51675
51963
  }
51676
51964
  /** Plain text → HTML-lite (escape + newlines to <br>). */
51677
51965
  function textToHtml(text) {
51678
- return escapeHtml$1(text).replace(/\n/g, "<br>");
51966
+ return escapeHtml$2(text).replace(/\n/g, "<br>");
51679
51967
  }
51680
51968
  /** Markdown → HTML-lite (bold/italic/code/links; used e.g. for Pushover). */
51681
51969
  function markdownToHtmlLite(md) {
51682
- 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>");
51970
+ 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>");
51683
51971
  }
51684
51972
  /**
51685
51973
  * Preference order when the source format is NOT supported — first supported
@@ -52276,6 +52564,8 @@ var STATE_SUMMARY_VARS = [
52276
52564
  [...RULE_TEXT],
52277
52565
  [...RULE_TEXT],
52278
52566
  [...RULE_TEXT],
52567
+ [...RULE_TEXT],
52568
+ [...RULE_TEXT],
52279
52569
  ([...RULE_TEXT], [...UPDATES]),
52280
52570
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
52281
52571
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
@@ -53035,6 +53325,116 @@ var discordAdapter = {
53035
53325
  };
53036
53326
  }
53037
53327
  };
53328
+ var descriptor$2 = {
53329
+ kind: "email",
53330
+ label: "Email",
53331
+ icon: "mail",
53332
+ addonId: "notifiers",
53333
+ supportsDiscovery: false,
53334
+ configSchema: { sections: [{
53335
+ id: "email",
53336
+ title: "Email",
53337
+ description: "Send via a Connectivity → Email provider (Google Services or SMTP). Install and Verify a provider first.",
53338
+ fields: [
53339
+ {
53340
+ type: "text",
53341
+ key: "to",
53342
+ label: "To",
53343
+ description: "Recipient address, or comma-separated list.",
53344
+ required: true
53345
+ },
53346
+ {
53347
+ type: "text",
53348
+ key: "smtpAddonId",
53349
+ label: "Email provider addon id (optional)",
53350
+ description: "Leave blank to use the first ready smtp-provider. Examples: google-services, smtp-nodemailer."
53351
+ },
53352
+ {
53353
+ type: "text",
53354
+ key: "from",
53355
+ label: "From (optional)",
53356
+ description: "Override From when the relay allows it."
53357
+ }
53358
+ ]
53359
+ }] },
53360
+ caps: {
53361
+ attachments: {
53362
+ mediaTypes: ["image"],
53363
+ mode: "url",
53364
+ max: 0
53365
+ },
53366
+ actions: 0,
53367
+ levels: [{
53368
+ id: "normal",
53369
+ label: "Normal",
53370
+ ordinal: 3
53371
+ }, {
53372
+ id: "high",
53373
+ label: "High",
53374
+ ordinal: 4
53375
+ }],
53376
+ format: [
53377
+ "text",
53378
+ "html",
53379
+ "markdown"
53380
+ ],
53381
+ clickUrl: true,
53382
+ sound: false,
53383
+ ttl: false,
53384
+ bodyMaxLen: 1e5
53385
+ }
53386
+ };
53387
+ function requireTo(config) {
53388
+ const raw = config["to"];
53389
+ if (typeof raw !== "string" || raw.trim() === "") throw new Error("email: missing required config 'to'");
53390
+ const list = raw.split(/[,;\s]+/).map((s) => s.trim()).filter((s) => s.length > 0);
53391
+ if (list.length === 0) throw new Error("email: missing required config 'to'");
53392
+ return list;
53393
+ }
53394
+ function renderBodies(prepared) {
53395
+ const body = prepared.bodyParts[0] ?? prepared.body;
53396
+ const title = prepared.title?.trim() ?? "";
53397
+ const click = prepared.clickUrl !== null && prepared.clickUrl !== void 0 ? `\n\n${prepared.clickUrl}` : "";
53398
+ const text = title !== "" ? `${title}\n\n${body}${click}` : `${body}${click}`;
53399
+ if (prepared.format === "html") return {
53400
+ text,
53401
+ 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>` : ""}`
53402
+ };
53403
+ return { text };
53404
+ }
53405
+ function escapeHtml$1(value) {
53406
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53407
+ }
53408
+ /** Build the email adapter bound to a live smtp-provider client. */
53409
+ function createEmailAdapter(smtp) {
53410
+ return {
53411
+ descriptor: descriptor$2,
53412
+ destinationKeys: ["to"],
53413
+ async sendDirect(config, prepared) {
53414
+ try {
53415
+ const to = requireTo(config);
53416
+ const bodies = renderBodies(prepared);
53417
+ const addonId = (typeof config["smtpAddonId"] === "string" && config["smtpAddonId"].trim() !== "" ? config["smtpAddonId"].trim() : void 0) ?? await smtp.resolveDefaultAddonId?.();
53418
+ const subject = prepared.title?.trim() && prepared.title.trim().length > 0 ? prepared.title.trim() : "CamStack notification";
53419
+ const from = typeof config["from"] === "string" && config["from"].trim() !== "" ? config["from"].trim() : void 0;
53420
+ await smtp.sendEmail({
53421
+ to,
53422
+ subject,
53423
+ text: bodies.text,
53424
+ ...bodies.html !== void 0 ? { html: bodies.html } : {},
53425
+ ...addonId !== void 0 ? { addonId } : {},
53426
+ ...from !== void 0 ? { from } : {}
53427
+ });
53428
+ return { success: true };
53429
+ } catch (err) {
53430
+ return {
53431
+ success: false,
53432
+ error: err instanceof Error ? err.message : String(err)
53433
+ };
53434
+ }
53435
+ }
53436
+ };
53437
+ }
53038
53438
  //#endregion
53039
53439
  //#region src/http.ts
53040
53440
  /** Default per-request timeout for notification dispatch. */
@@ -54970,7 +55370,8 @@ function zentikConfigSchema() {
54970
55370
  }
54971
55371
  //#endregion
54972
55372
  //#region src/adapters/registry.ts
54973
- var NOTIFIER_ADAPTERS = [
55373
+ /** HTTP adapters that need no runtime deps. */
55374
+ var HTTP_NOTIFIER_ADAPTERS = [
54974
55375
  ntfyAdapter,
54975
55376
  pushoverAdapter,
54976
55377
  gotifyAdapter,
@@ -55122,6 +55523,17 @@ var NOTIFIER_ADAPTERS = [
55122
55523
  },
55123
55524
  whatsappAdapter
55124
55525
  ];
55526
+ /**
55527
+ * Full catalog. When `smtp` is omitted the Email kind is still listed but
55528
+ * `sendDirect` fails until the addon wires a client (tests use this).
55529
+ */
55530
+ function buildNotifierAdapters(smtp) {
55531
+ const emailSmtp = smtp ?? { async sendEmail() {
55532
+ throw new Error("email: no smtp-provider client — install Google Services or SMTP Email under Connectivity → Email and Verify it");
55533
+ } };
55534
+ return [...HTTP_NOTIFIER_ADAPTERS, createEmailAdapter(emailSmtp)];
55535
+ }
55536
+ buildNotifierAdapters();
55125
55537
  var NOTIFIER_ICONS = {
55126
55538
  telegram: {
55127
55539
  contentType: "image/svg+xml",
@@ -55963,6 +56375,32 @@ function createNotificationOutputProvider(deps) {
55963
56375
  ...prepared,
55964
56376
  attachments
55965
56377
  };
56378
+ if (adapter.sendDirect) try {
56379
+ const result = await adapter.sendDirect(target.config, readyPrepared);
56380
+ return result.error === void 0 ? {
56381
+ ...result,
56382
+ renderedAs: prepared.renderedAs
56383
+ } : {
56384
+ ...result,
56385
+ error: stripUrls(result.error),
56386
+ renderedAs: prepared.renderedAs
56387
+ };
56388
+ } catch (err) {
56389
+ logger?.warn("sendDirect failed", { meta: {
56390
+ kind: target.kind,
56391
+ error: errMsg(err)
56392
+ } });
56393
+ return {
56394
+ success: false,
56395
+ error: `sendDirect failed: ${errMsg(err)}`,
56396
+ renderedAs: prepared.renderedAs
56397
+ };
56398
+ }
56399
+ if (!adapter.buildRequest) return {
56400
+ success: false,
56401
+ error: `No adapter send path for kind '${target.kind}'`,
56402
+ renderedAs: prepared.renderedAs
56403
+ };
55966
56404
  let plan;
55967
56405
  try {
55968
56406
  plan = adapter.buildRequest(target.config, readyPrepared);
@@ -56330,11 +56768,12 @@ var TargetStore = class {
56330
56768
  * Notifiers addon — a system `notification-output` collection provider.
56331
56769
  *
56332
56770
  * It hosts per-kind `NotifierAdapter`s (ntfy, pushover, gotify, telegram,
56333
- * discord, webhook, zentik, whatsapp) behind one uniform cap surface. Targets
56334
- * persist via the `settings-store` cap; the canonical notification is degraded
56335
- * per each target's declared capabilities by the pure `prepareNotification`
56336
- * engine in `@camstack/types`. The catalog is `NOTIFIER_ADAPTERS`
56337
- * (`adapters/registry.ts`).
56771
+ * discord, webhook, zentik, whatsapp, email) behind one uniform cap surface.
56772
+ * Targets persist via the `settings-store` cap; the canonical notification is
56773
+ * degraded per each target's declared capabilities by the pure
56774
+ * `prepareNotification` engine in `@camstack/types`. The catalog is
56775
+ * `buildNotifierAdapters` (`adapters/registry.ts`); email uses `sendDirect` →
56776
+ * `smtp-provider`.
56338
56777
  */
56339
56778
  var NotifiersAddon = class extends BaseAddon {
56340
56779
  constructor() {
@@ -56346,9 +56785,19 @@ var NotifiersAddon = class extends BaseAddon {
56346
56785
  if (!api) this.ctx.logger.warn("notifiers: no ctx.api — targets persist in-memory only");
56347
56786
  const store = new TargetStore(port);
56348
56787
  await store.init();
56788
+ const adapters = buildNotifierAdapters(api !== void 0 ? { sendEmail: async (input) => {
56789
+ return { messageId: (await api.smtpProvider.sendEmail({
56790
+ to: input.to,
56791
+ subject: input.subject,
56792
+ text: input.text,
56793
+ ...input.html !== void 0 ? { html: input.html } : {},
56794
+ ...input.from !== void 0 ? { from: input.from } : {},
56795
+ ...input.addonId !== void 0 ? { addonId: input.addonId } : {}
56796
+ })).messageId };
56797
+ } } : void 0);
56349
56798
  const provider = createNotificationOutputProvider({
56350
56799
  store,
56351
- adapters: NOTIFIER_ADAPTERS,
56800
+ adapters,
56352
56801
  logger: this.ctx.logger.child("notification-output"),
56353
56802
  iconUrlFor,
56354
56803
  iconMediaTypeFor
@@ -56358,7 +56807,7 @@ var NotifiersAddon = class extends BaseAddon {
56358
56807
  provider,
56359
56808
  logger: this.ctx.logger.child("nc-target-actions")
56360
56809
  });
56361
- this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: NOTIFIER_ADAPTERS.length } });
56810
+ this.ctx.logger.info("NotifiersAddon initialized", { meta: { adapterCount: adapters.length } });
56362
56811
  return {
56363
56812
  providers: [{
56364
56813
  capability: notificationOutputCapability,