@camstack/addon-pipeline-orchestrator 0.1.13 → 0.1.15

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 (27) hide show
  1. package/dist/@mf-types/compiled-types/widgets/MotionZonesEditor.d.ts +19 -0
  2. package/dist/@mf-types/compiled-types/widgets/MotionZonesEditor.d.ts.map +1 -0
  3. package/dist/@mf-types/compiled-types/widgets/index.d.ts +2 -0
  4. package/dist/@mf-types/compiled-types/widgets/index.d.ts.map +1 -1
  5. package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionGridCanvas.d.ts +20 -0
  6. package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionGridCanvas.d.ts.map +1 -0
  7. package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesOverlay.d.ts +14 -0
  8. package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesOverlay.d.ts.map +1 -0
  9. package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesTab.d.ts +5 -0
  10. package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesTab.d.ts.map +1 -0
  11. package/dist/@mf-types.zip +0 -0
  12. package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-UNj4rttw.mjs +20 -0
  13. package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-Ue-jHwF0.mjs +35 -0
  14. package/dist/_stub.js +4592 -4319
  15. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-CPFxFaeP.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-sOGV7J8L.mjs} +6 -6
  16. package/dist/{hostInit-C2nlXV13.mjs → hostInit-DMdjwivI.mjs} +6 -6
  17. package/dist/{index-DfLih_hW.mjs → index-BIlr4dIX.mjs} +1 -1
  18. package/dist/{index-COebxMhm.mjs → index-BK5-EWzN.mjs} +2488 -2460
  19. package/dist/{index-CIjsCPwy.mjs → index-BUn7hM0v.mjs} +2871 -2758
  20. package/dist/index.js +362 -309
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +362 -309
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/remoteEntry.js +1 -1
  25. package/package.json +1 -1
  26. package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-B47e6UPj.mjs +0 -20
  27. package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BErlAIMU.mjs +0 -34
package/dist/index.js CHANGED
@@ -5136,6 +5136,9 @@ function hydrateField(field, values) {
5136
5136
  return { ...field, value: items };
5137
5137
  }
5138
5138
  const rawValue = storedValue !== void 0 ? storedValue : defaultValue !== void 0 ? defaultValue : null;
5139
+ if (field.type === "password") {
5140
+ return { ...field, value: "" };
5141
+ }
5139
5142
  const value = field.type === "textarea" && field.isJson && rawValue !== null && typeof rawValue === "object" ? JSON.stringify(rawValue, null, 2) : rawValue;
5140
5143
  const hydrated = { ...field, value };
5141
5144
  return hydrated;
@@ -7345,6 +7348,34 @@ MotionTriggerStatusSchema.extend({
7345
7348
  }) }
7346
7349
  }
7347
7350
  });
7351
+ object({
7352
+ enabled: boolean(),
7353
+ sensitivity: number(),
7354
+ /** Row-major active-cell grid. Length = gridWidth*gridHeight (see getOptions). */
7355
+ cells: array(boolean()),
7356
+ lastFetchedAt: number()
7357
+ });
7358
+ const MotionZoneOptionsSchema = object({
7359
+ gridWidth: number(),
7360
+ gridHeight: number(),
7361
+ sensitivity: object({ min: number(), max: number(), step: number() })
7362
+ });
7363
+ const MotionZonePatchSchema = object({
7364
+ enabled: boolean().optional(),
7365
+ sensitivity: number().optional(),
7366
+ cells: array(boolean()).optional()
7367
+ });
7368
+ ({
7369
+ deviceTypes: [DeviceType.Camera],
7370
+ methods: {
7371
+ getOptions: method(object({ deviceId: number() }), MotionZoneOptionsSchema),
7372
+ setZone: method(
7373
+ object({ deviceId: number(), patch: MotionZonePatchSchema }),
7374
+ _void(),
7375
+ { kind: "mutation", auth: "admin" }
7376
+ )
7377
+ }
7378
+ });
7348
7379
  const AutotrackTargetTypeSchema = string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
7349
7380
  const PtzAutotrackSettingsSchema = object({
7350
7381
  targetType: AutotrackTargetTypeSchema,
@@ -7433,6 +7464,72 @@ PtzAutotrackStatusSchema.extend({
7433
7464
  }) }
7434
7465
  }
7435
7466
  });
7467
+ const StreamProfileSchema = _enum(["main", "sub", "ext"]);
7468
+ const StreamProfileConfigSchema = object({
7469
+ width: number(),
7470
+ height: number(),
7471
+ codec: _enum(["h264", "h265"]),
7472
+ framerate: number(),
7473
+ bitrate: number(),
7474
+ // kbps
7475
+ bitrateMode: _enum(["vbr", "cbr"]).optional(),
7476
+ encoderProfile: _enum(["high", "main", "baseline"]).optional(),
7477
+ gop: number().optional(),
7478
+ audio: boolean().optional()
7479
+ });
7480
+ object({
7481
+ /** Per-profile current config. A profile absent = the camera doesn't have it. */
7482
+ main: StreamProfileConfigSchema.optional(),
7483
+ sub: StreamProfileConfigSchema.optional(),
7484
+ ext: StreamProfileConfigSchema.optional(),
7485
+ lastFetchedAt: number()
7486
+ });
7487
+ const StreamProfileOptionsSchema = object({
7488
+ resolutions: array(object({ width: number(), height: number() })),
7489
+ codecs: array(_enum(["h264", "h265"])),
7490
+ framerates: array(number()),
7491
+ /** Allowed bitrate values (kbps). Empty if the camera takes a free range. */
7492
+ bitrates: array(number()),
7493
+ /** Optional [min,max] kbps when the camera accepts a continuous range. */
7494
+ bitrateRange: tuple([number(), number()]).optional(),
7495
+ supportsBitrateMode: boolean(),
7496
+ supportsEncoderProfile: boolean(),
7497
+ supportsGop: boolean()
7498
+ });
7499
+ const StreamParamsOptionsSchema = object({
7500
+ main: StreamProfileOptionsSchema.optional(),
7501
+ sub: StreamProfileOptionsSchema.optional(),
7502
+ ext: StreamProfileOptionsSchema.optional()
7503
+ });
7504
+ const StreamProfilePatchSchema = object({
7505
+ width: number().optional(),
7506
+ height: number().optional(),
7507
+ codec: _enum(["h264", "h265"]).optional(),
7508
+ framerate: number().optional(),
7509
+ bitrate: number().optional(),
7510
+ bitrateMode: _enum(["vbr", "cbr"]).optional(),
7511
+ encoderProfile: _enum(["high", "main", "baseline"]).optional(),
7512
+ gop: number().optional(),
7513
+ audio: boolean().optional()
7514
+ });
7515
+ ({
7516
+ deviceTypes: [DeviceType.Camera],
7517
+ methods: {
7518
+ getOptions: method(
7519
+ object({ deviceId: number() }),
7520
+ StreamParamsOptionsSchema
7521
+ ),
7522
+ setProfile: method(
7523
+ object({
7524
+ deviceId: number(),
7525
+ profile: StreamProfileSchema,
7526
+ patch: StreamProfilePatchSchema
7527
+ }),
7528
+ _void(),
7529
+ { kind: "mutation", auth: "admin" }
7530
+ )
7531
+ }
7532
+ });
7436
7533
  object({
7437
7534
  on: boolean(),
7438
7535
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
@@ -8373,6 +8470,83 @@ const VersionOutputSchema = object({ version: string() });
8373
8470
  getVersion: method(_void(), VersionOutputSchema)
8374
8471
  }
8375
8472
  });
8473
+ const MethodAccessSchema = _enum(["view", "create", "delete"]);
8474
+ const AllowedProviderSchema = union([literal("*"), array(string())]);
8475
+ const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
8476
+ const CapScopeSchema = _enum(["device", "system"]);
8477
+ const TokenScopeSchema = discriminatedUnion("type", [
8478
+ object({
8479
+ type: literal("category"),
8480
+ target: CapScopeSchema,
8481
+ access: array(MethodAccessSchema).min(1)
8482
+ }),
8483
+ object({
8484
+ type: literal("capability"),
8485
+ target: string(),
8486
+ access: array(MethodAccessSchema).min(1)
8487
+ }),
8488
+ object({
8489
+ type: literal("addon"),
8490
+ target: string(),
8491
+ access: array(MethodAccessSchema).min(1)
8492
+ }),
8493
+ object({
8494
+ type: literal("device"),
8495
+ /**
8496
+ * One or more deviceIds (serialised as strings for wire-format
8497
+ * consistency with the rest of the union). Matcher accepts if
8498
+ * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
8499
+ * of one scope-per-device when granting access to a set of cameras.
8500
+ */
8501
+ targets: array(string()).min(1),
8502
+ access: array(MethodAccessSchema).min(1)
8503
+ })
8504
+ ]);
8505
+ object({
8506
+ id: string(),
8507
+ username: string(),
8508
+ passwordHash: string(),
8509
+ /**
8510
+ * Admin bypass. When true, the middleware skips the scope-access
8511
+ * check entirely. There is no other axis of privilege; the legacy
8512
+ * role enum collapsed onto this boolean in v2.
8513
+ */
8514
+ isAdmin: boolean().default(false),
8515
+ allowedProviders: AllowedProviderSchema,
8516
+ allowedDevices: AllowedDevicesSchema,
8517
+ /**
8518
+ * Scopes granted to this user. Admins bypass; their `scopes` is
8519
+ * ignored. Non-admins without scopes are locked out of every
8520
+ * protected call.
8521
+ */
8522
+ scopes: array(TokenScopeSchema).default([]),
8523
+ createdAt: number(),
8524
+ updatedAt: number()
8525
+ });
8526
+ object({
8527
+ id: string(),
8528
+ label: string(),
8529
+ isAdmin: boolean().default(false),
8530
+ allowedProviders: AllowedProviderSchema,
8531
+ allowedDevices: AllowedDevicesSchema,
8532
+ tokenHash: string(),
8533
+ tokenPrefix: string(),
8534
+ createdAt: number(),
8535
+ lastUsedAt: number().optional()
8536
+ });
8537
+ object({
8538
+ id: string(),
8539
+ userId: string(),
8540
+ name: string(),
8541
+ tokenHash: string(),
8542
+ tokenPrefix: string(),
8543
+ scopes: array(TokenScopeSchema),
8544
+ // SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
8545
+ // schema accepts both `null` (read from disk) and `undefined` (in-memory).
8546
+ expiresAt: number().nullish(),
8547
+ lastUsedAt: number().nullish(),
8548
+ createdAt: number()
8549
+ });
8376
8550
  const SsoBridgeClaimsSchema = object({
8377
8551
  userId: string(),
8378
8552
  username: string(),
@@ -8388,7 +8562,18 @@ const SsoBridgeClaimsSchema = object({
8388
8562
  * JWT WITHOUT verifying the signature — the hub re-verifies on every
8389
8563
  * inbound call so trust still rests with the signing hub.
8390
8564
  */
8391
- hubUrl: string().optional()
8565
+ hubUrl: string().optional(),
8566
+ /** Permission scopes baked into the token. Set by the OAuth
8567
+ * account-linking grant; absent on ordinary SSO-login tokens. */
8568
+ scopes: array(TokenScopeSchema).optional(),
8569
+ /** OAuth authorization-code binding — set only on `oauth-code` tokens. */
8570
+ redirectUri: string().optional(),
8571
+ integrationId: string().optional(),
8572
+ /** JWT ID — unique per issued code; consumed-set enforces single-use. */
8573
+ jti: string().optional(),
8574
+ /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
8575
+ * tokens so the verify path can check the session is not revoked. */
8576
+ sessionId: string().optional()
8392
8577
  });
8393
8578
  ({
8394
8579
  methods: {
@@ -8405,6 +8590,23 @@ const SsoBridgeClaimsSchema = object({
8405
8590
  )
8406
8591
  }
8407
8592
  });
8593
+ const OauthIntegrationDescriptorSchema = object({
8594
+ /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
8595
+ integrationId: string(),
8596
+ /** Human label rendered on the consent page. */
8597
+ displayName: string(),
8598
+ /** Scopes baked into every token issued for this integration. */
8599
+ requestedScopes: array(TokenScopeSchema),
8600
+ /** Allowed redirect_uri prefixes. /oauth2/authorize rejects any
8601
+ * redirect_uri that does not start with one of these. Required —
8602
+ * an empty list means the integration can never complete linking. */
8603
+ allowedRedirectPrefixes: array(string()).min(1)
8604
+ });
8605
+ ({
8606
+ methods: {
8607
+ getDescriptor: method(_void(), OauthIntegrationDescriptorSchema)
8608
+ }
8609
+ });
8408
8610
  const PasskeySummarySchema = object({
8409
8611
  credentialId: string(),
8410
8612
  label: string(),
@@ -10294,51 +10496,6 @@ const AuthResultSchema = object({
10294
10496
  validateToken: method(object({ token: string() }), AuthResultSchema.nullable())
10295
10497
  }
10296
10498
  });
10297
- const AuthProviderInfoSchema = object({
10298
- /** Stable id matching the addon id (used for `getLoginUrl({addonId,…})`). */
10299
- addonId: string(),
10300
- /**
10301
- * Per-instance id when one addon registers multiple "logical"
10302
- * providers (e.g. OIDC with Google + Microsoft + custom). The login
10303
- * URL becomes `/addon/${addonId}/${instanceId}/start` — handler reads
10304
- * `:instanceId` from the route. Empty/unset means the addon is a
10305
- * single-instance provider; the URL is `/addon/${addonId}/start`.
10306
- */
10307
- instanceId: string().optional(),
10308
- /** Display label shown on the login button + admin row. */
10309
- displayName: string(),
10310
- /** Optional iconography hint (lucide-react icon name OR emoji). */
10311
- icon: string().optional(),
10312
- /** When true, the provider exposes a redirect-based login flow
10313
- * (`getLoginUrl` returns a URL the browser navigates to). */
10314
- hasRedirectFlow: boolean(),
10315
- /** When true, the provider exposes a credential-form login flow
10316
- * (`validateCredentials` accepts username + password). */
10317
- hasCredentialFlow: boolean(),
10318
- /** Provider kind, drives admin-UI hint dispatch (oidc / saml / totp / …). */
10319
- kind: string().optional(),
10320
- /** Operator-facing status string (e.g. "Connected to https://login.acme.com"). */
10321
- status: string().optional(),
10322
- /** When false, the provider is registered but disabled by config; the
10323
- * UI surfaces it as inactive without enumerating it for login. */
10324
- enabled: boolean()
10325
- });
10326
- ({
10327
- methods: {
10328
- /** All registered auth providers, both enabled and disabled. */
10329
- listProviders: method(_void(), array(AuthProviderInfoSchema).readonly()),
10330
- /**
10331
- * Toggle a provider's enabled flag. Disabled providers stay
10332
- * registered but aren't surfaced on the login page. The orchestrator
10333
- * persists the state in `addon-settings` so it survives restarts.
10334
- */
10335
- setProviderEnabled: method(
10336
- object({ addonId: string(), enabled: boolean() }),
10337
- object({ success: literal(true) }),
10338
- { kind: "mutation", auth: "admin" }
10339
- )
10340
- }
10341
- });
10342
10499
  const NetworkEndpointSchema = object({
10343
10500
  url: string(),
10344
10501
  hostname: string(),
@@ -10370,55 +10527,13 @@ const NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
10370
10527
  getEndpoint: method(_void(), NetworkEndpointSchema.nullable()),
10371
10528
  getStatus: method(_void(), NetworkAccessStatusSchema),
10372
10529
  /**
10373
- * Enumerate every active ingress entry. Default implementation (when
10374
- * the provider omits this method) is derived from `getEndpoint()` —
10375
- * see the remote-access orchestrator for the fallback path.
10530
+ * Enumerate every active ingress entry. Providers that expose only a
10531
+ * single endpoint may omit this method; callers fall back to
10532
+ * `getEndpoint()` in that case.
10376
10533
  */
10377
10534
  listEndpoints: method(_void(), array(NetworkEndpointEntrySchema).readonly())
10378
10535
  }
10379
10536
  });
10380
- const RemoteAccessEndpointSchema = object({
10381
- url: string(),
10382
- hostname: string(),
10383
- port: number(),
10384
- protocol: _enum(["http", "https"])
10385
- });
10386
- const RemoteAccessProviderInfoSchema = object({
10387
- /** Stable id matching the addon id. */
10388
- addonId: string(),
10389
- /** Display label shown on the admin row — sourced from the addon manifest. */
10390
- displayName: string(),
10391
- /** When false, the provider is registered but disabled. */
10392
- enabled: boolean(),
10393
- /** True when the underlying tunnel/connection is up. */
10394
- connected: boolean(),
10395
- /** Public-facing endpoint, when connected. Null otherwise. */
10396
- endpoint: RemoteAccessEndpointSchema.nullable(),
10397
- /** Last error message (when connected=false), if available. */
10398
- error: string().optional()
10399
- });
10400
- ({
10401
- methods: {
10402
- /** All registered remote-access providers + their live status. */
10403
- listProviders: method(_void(), array(RemoteAccessProviderInfoSchema).readonly()),
10404
- /**
10405
- * Start a specific provider's tunnel. Per-provider config still
10406
- * lives on the addon's settings panel; this is just the on/off
10407
- * trigger so the admin UI can manage the lifecycle from one place.
10408
- */
10409
- startProvider: method(
10410
- object({ addonId: string() }),
10411
- RemoteAccessEndpointSchema,
10412
- { kind: "mutation", auth: "admin" }
10413
- ),
10414
- /** Stop a specific provider's tunnel (idempotent on already-stopped). */
10415
- stopProvider: method(
10416
- object({ addonId: string() }),
10417
- object({ success: literal(true) }),
10418
- { kind: "mutation", auth: "admin" }
10419
- )
10420
- }
10421
- });
10422
10537
  const TurnServerSchema = object({
10423
10538
  /** Single URL or list of URLs (e.g. "turn:turn.example.com:3478?transport=udp"). */
10424
10539
  urls: union([string(), array(string())]),
@@ -10438,45 +10553,6 @@ const TurnServerSchema = object({
10438
10553
  )
10439
10554
  }
10440
10555
  });
10441
- const TurnProviderInfoSchema = object({
10442
- /** Stable id matching the addon id. */
10443
- addonId: string(),
10444
- /** Display label shown on the admin row — sourced from the addon manifest. */
10445
- displayName: string(),
10446
- /** When false, the provider is registered but disabled. */
10447
- enabled: boolean(),
10448
- /** Number of servers this provider is currently exposing. */
10449
- serverCount: number(),
10450
- /**
10451
- * Flat list of every TURN/STUN URL this provider currently exposes.
10452
- * One row per URL (multi-URL ICE server entries are flattened). The
10453
- * admin UI shows this in a compact per-provider list so operators
10454
- * can verify what's actually being negotiated without having to dig
10455
- * into the combined `getAllServers` output.
10456
- */
10457
- urls: array(string()).readonly(),
10458
- /** Last fetch error (when serverCount=0 due to API failure), if any. */
10459
- error: string().optional()
10460
- });
10461
- ({
10462
- methods: {
10463
- /** All registered TURN providers + per-provider stats. */
10464
- listProviders: method(_void(), array(TurnProviderInfoSchema).readonly()),
10465
- /**
10466
- * Combined list of TURN/STUN servers from all ENABLED providers.
10467
- * Consumed by the WebRTC layer at session-creation time —
10468
- * implementations may fetch fresh short-lived credentials each
10469
- * call (e.g. Cloudflare API), so consumers SHOULD call per-session.
10470
- */
10471
- getAllServers: method(_void(), array(TurnServerSchema).readonly()),
10472
- /** Toggle a provider's enabled flag. */
10473
- setProviderEnabled: method(
10474
- object({ addonId: string(), enabled: boolean() }),
10475
- object({ success: literal(true) }),
10476
- { kind: "mutation", auth: "admin" }
10477
- )
10478
- }
10479
- });
10480
10556
  const SnapshotImageSchema = object({
10481
10557
  base64: string(),
10482
10558
  contentType: string()
@@ -11204,6 +11280,14 @@ const PtzMoveCommandSchema = object({
11204
11280
  zoom: number().optional(),
11205
11281
  speed: number().optional()
11206
11282
  });
11283
+ const PtzOptionsSchema = object({
11284
+ hasPan: boolean(),
11285
+ hasTilt: boolean(),
11286
+ hasZoom: boolean(),
11287
+ supportsPresets: boolean(),
11288
+ /** Max number of named presets the camera supports, when known. */
11289
+ maxPresets: number().optional()
11290
+ });
11207
11291
  ({
11208
11292
  deviceTypes: [DeviceType.Camera],
11209
11293
  methods: {
@@ -11231,6 +11315,20 @@ const PtzMoveCommandSchema = object({
11231
11315
  _void(),
11232
11316
  { kind: "mutation" }
11233
11317
  ),
11318
+ savePreset: method(
11319
+ object({ deviceId: number(), presetId: string(), name: string() }),
11320
+ _void(),
11321
+ { kind: "mutation", auth: "admin" }
11322
+ ),
11323
+ deletePreset: method(
11324
+ object({ deviceId: number(), presetId: string() }),
11325
+ _void(),
11326
+ { kind: "mutation", auth: "admin" }
11327
+ ),
11328
+ getOptions: method(
11329
+ object({ deviceId: number() }),
11330
+ PtzOptionsSchema
11331
+ ),
11234
11332
  goHome: method(
11235
11333
  object({ deviceId: number() }),
11236
11334
  _void(),
@@ -11944,7 +12042,7 @@ const AllowedAddressesSchema = object({
11944
12042
  )
11945
12043
  }
11946
12044
  });
11947
- const MeshEndpointSchema$1 = object({
12045
+ const MeshEndpointSchema = object({
11948
12046
  /** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
11949
12047
  id: string(),
11950
12048
  /** Operator-facing label (e.g. "Mesh IPv4", "MagicDNS"). */
@@ -12017,7 +12115,7 @@ const MeshStatusSchema = object({
12017
12115
  /** Number of peers visible to this host (excluding self). */
12018
12116
  peerCount: number(),
12019
12117
  /** Every endpoint this provider exposes for the current host. */
12020
- endpoints: array(MeshEndpointSchema$1).readonly(),
12118
+ endpoints: array(MeshEndpointSchema).readonly(),
12021
12119
  /** Last error from the daemon, when not joined. */
12022
12120
  error: string().optional(),
12023
12121
  // ── Account / tenant identity (generic across providers) ────────
@@ -12180,182 +12278,6 @@ const MeshStatusSchema = object({
12180
12278
  // tabs driven by this cap.
12181
12279
  }
12182
12280
  });
12183
- const MeshEndpointSchema = object({
12184
- id: string(),
12185
- label: string(),
12186
- scope: _enum(["mesh", "public"]),
12187
- url: string(),
12188
- hostname: string(),
12189
- port: number(),
12190
- protocol: _enum(["http", "https"])
12191
- });
12192
- const MeshProviderInfoSchema = object({
12193
- /** Stable id matching the addon id. */
12194
- addonId: string(),
12195
- /** Display label shown on the admin row — sourced from the addon manifest. */
12196
- displayName: string(),
12197
- /** True when the host is joined to this provider's mesh. */
12198
- joined: boolean(),
12199
- /** Local mesh IP (empty when not joined). */
12200
- meshIp: string(),
12201
- /** MagicDNS / mesh hostname (empty when not configured). */
12202
- magicDnsHostname: string(),
12203
- /** Peer count (excluding self). */
12204
- peerCount: number(),
12205
- /** Active endpoints (mesh IP + MagicDNS + optional public Funnel). */
12206
- endpoints: array(MeshEndpointSchema).readonly(),
12207
- /** Last error reported by the provider. */
12208
- error: string().optional(),
12209
- // ── Generic identity fields mirrored from MeshStatus ─────────────
12210
- /** Tenant / tailnet / network display name. Empty pre-join. */
12211
- tenantName: string(),
12212
- /** Mesh DNS suffix (e.g. tailXXXX.ts.net). Empty when not configured. */
12213
- magicDnsSuffix: string(),
12214
- /** Authenticated user / account login. Null for token-only providers. */
12215
- userLogin: string().nullable(),
12216
- /** Provider control-plane URL. */
12217
- controlPlaneUrl: string(),
12218
- /** Machine-key expiry (epoch ms). Null when keys don't rotate. */
12219
- keyExpiry: number().nullable()
12220
- });
12221
- ({
12222
- methods: {
12223
- /** All registered mesh-network providers + live status. */
12224
- listProviders: method(_void(), array(MeshProviderInfoSchema).readonly()),
12225
- /**
12226
- * Join the mesh of a specific provider. Per-provider config still
12227
- * lives on its settings panel; the orchestrator forwards.
12228
- */
12229
- joinProvider: method(
12230
- object({
12231
- addonId: string(),
12232
- authKey: string().min(8),
12233
- hostname: string().optional()
12234
- }),
12235
- object({ joined: literal(true) }),
12236
- { kind: "mutation" }
12237
- ),
12238
- leaveProvider: method(
12239
- object({ addonId: string() }),
12240
- object({ success: literal(true) }),
12241
- { kind: "mutation" }
12242
- ),
12243
- /**
12244
- * Browser-redirect login flow. Forwards to the named provider's
12245
- * `mesh-network.startLogin` and returns the URL the daemon
12246
- * prints. UI opens it in a new tab, then polls `listProviders`
12247
- * for `joined: true`.
12248
- */
12249
- startLoginProvider: method(
12250
- object({
12251
- addonId: string(),
12252
- hostname: string().optional()
12253
- }),
12254
- object({ loginUrl: string() }),
12255
- { kind: "mutation" }
12256
- ),
12257
- /**
12258
- * Sign out of the provider's account entirely (`mesh-network.logout`).
12259
- * Distinct from `leaveProvider` which only takes the host off-mesh;
12260
- * `logoutProvider` wipes credentials so the next start requires a
12261
- * fresh login.
12262
- */
12263
- logoutProvider: method(
12264
- object({ addonId: string() }),
12265
- object({ loggedOut: literal(true) }),
12266
- { kind: "mutation" }
12267
- ),
12268
- /**
12269
- * Per-provider peer list. Forwards to `mesh-network.listPeers` on
12270
- * the addressed provider. Separate from `listProviders` because
12271
- * peer payloads can be large on a heavily-populated tailnet —
12272
- * fetch only when the operator opens the Peers tab.
12273
- */
12274
- listProviderPeers: method(
12275
- object({ addonId: string() }),
12276
- object({
12277
- peers: array(MeshPeerSchema).readonly()
12278
- })
12279
- )
12280
- }
12281
- });
12282
- const MethodAccessSchema = _enum(["view", "create", "delete"]);
12283
- const AllowedProviderSchema = union([literal("*"), array(string())]);
12284
- const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
12285
- const CapScopeSchema = _enum(["device", "system"]);
12286
- const TokenScopeSchema = discriminatedUnion("type", [
12287
- object({
12288
- type: literal("category"),
12289
- target: CapScopeSchema,
12290
- access: array(MethodAccessSchema).min(1)
12291
- }),
12292
- object({
12293
- type: literal("capability"),
12294
- target: string(),
12295
- access: array(MethodAccessSchema).min(1)
12296
- }),
12297
- object({
12298
- type: literal("addon"),
12299
- target: string(),
12300
- access: array(MethodAccessSchema).min(1)
12301
- }),
12302
- object({
12303
- type: literal("device"),
12304
- /**
12305
- * One or more deviceIds (serialised as strings for wire-format
12306
- * consistency with the rest of the union). Matcher accepts if
12307
- * `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
12308
- * of one scope-per-device when granting access to a set of cameras.
12309
- */
12310
- targets: array(string()).min(1),
12311
- access: array(MethodAccessSchema).min(1)
12312
- })
12313
- ]);
12314
- object({
12315
- id: string(),
12316
- username: string(),
12317
- passwordHash: string(),
12318
- /**
12319
- * Admin bypass. When true, the middleware skips the scope-access
12320
- * check entirely. There is no other axis of privilege; the legacy
12321
- * role enum collapsed onto this boolean in v2.
12322
- */
12323
- isAdmin: boolean().default(false),
12324
- allowedProviders: AllowedProviderSchema,
12325
- allowedDevices: AllowedDevicesSchema,
12326
- /**
12327
- * Scopes granted to this user. Admins bypass; their `scopes` is
12328
- * ignored. Non-admins without scopes are locked out of every
12329
- * protected call.
12330
- */
12331
- scopes: array(TokenScopeSchema).default([]),
12332
- createdAt: number(),
12333
- updatedAt: number()
12334
- });
12335
- object({
12336
- id: string(),
12337
- label: string(),
12338
- isAdmin: boolean().default(false),
12339
- allowedProviders: AllowedProviderSchema,
12340
- allowedDevices: AllowedDevicesSchema,
12341
- tokenHash: string(),
12342
- tokenPrefix: string(),
12343
- createdAt: number(),
12344
- lastUsedAt: number().optional()
12345
- });
12346
- object({
12347
- id: string(),
12348
- userId: string(),
12349
- name: string(),
12350
- tokenHash: string(),
12351
- tokenPrefix: string(),
12352
- scopes: array(TokenScopeSchema),
12353
- // SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
12354
- // schema accepts both `null` (read from disk) and `undefined` (in-memory).
12355
- expiresAt: number().nullish(),
12356
- lastUsedAt: number().nullish(),
12357
- createdAt: number()
12358
- });
12359
12281
  const UserSummarySchema = object({
12360
12282
  id: string(),
12361
12283
  username: string(),
@@ -12428,6 +12350,16 @@ const CreateScopedTokenResultSchema = object({
12428
12350
  token: string(),
12429
12351
  record: ScopedTokenSummarySchema
12430
12352
  });
12353
+ const OauthSessionSummarySchema = object({
12354
+ id: string(),
12355
+ userId: string(),
12356
+ username: string(),
12357
+ integrationId: string(),
12358
+ scopes: array(TokenScopeSchema),
12359
+ createdAt: number(),
12360
+ lastUsedAt: number(),
12361
+ revokedAt: number().nullable()
12362
+ });
12431
12363
  const TotpSetupResultSchema = object({
12432
12364
  secret: string(),
12433
12365
  otpauthUrl: string()
@@ -12503,6 +12435,66 @@ const TotpStatusSchema = object({
12503
12435
  object({ userId: string(), code: string() }),
12504
12436
  object({ valid: boolean() }),
12505
12437
  { kind: "mutation", access: "view" }
12438
+ ),
12439
+ // ── OAuth account-linking grant ────────────────────────────────
12440
+ //
12441
+ // Core's /oauth2/* endpoints delegate here. Tokens are sso-bridge
12442
+ // JWTs (kinds oauth-code / oauth-access / oauth-refresh) and ALWAYS
12443
+ // carry isAdmin:false — the operator login proves hub control; the
12444
+ // issued token is minimal (device scope only).
12445
+ oauthIssueCode: method(
12446
+ object({
12447
+ integrationId: string(),
12448
+ userId: string(),
12449
+ username: string(),
12450
+ scopes: array(TokenScopeSchema),
12451
+ redirectUri: string(),
12452
+ hubUrl: string()
12453
+ }),
12454
+ object({ code: string() }),
12455
+ { kind: "mutation", access: "create" }
12456
+ ),
12457
+ oauthExchangeCode: method(
12458
+ object({ code: string(), redirectUri: string() }),
12459
+ object({
12460
+ accessToken: string(),
12461
+ refreshToken: string(),
12462
+ expiresIn: number()
12463
+ }).nullable(),
12464
+ { kind: "mutation", access: "view" }
12465
+ ),
12466
+ oauthRefresh: method(
12467
+ object({ refreshToken: string() }),
12468
+ object({
12469
+ accessToken: string(),
12470
+ refreshToken: string(),
12471
+ expiresIn: number()
12472
+ }).nullable(),
12473
+ { kind: "mutation", access: "view" }
12474
+ ),
12475
+ oauthVerifyAccessToken: method(
12476
+ object({ token: string() }),
12477
+ object({
12478
+ userId: string(),
12479
+ username: string(),
12480
+ scopes: array(TokenScopeSchema)
12481
+ }).nullable(),
12482
+ { access: "view" }
12483
+ ),
12484
+ // ── OAuth linked-session management (Phase D) ──────────────────
12485
+ //
12486
+ // The admin UI lists active account-linking sessions and revokes
12487
+ // them; revocation makes the linked integration's tokens fail
12488
+ // verification immediately.
12489
+ listOauthSessions: method(
12490
+ _void(),
12491
+ array(OauthSessionSummarySchema),
12492
+ { auth: "admin" }
12493
+ ),
12494
+ revokeOauthSession: method(
12495
+ object({ id: string() }),
12496
+ object({ success: boolean() }),
12497
+ { kind: "mutation", auth: "admin", access: "delete" }
12506
12498
  )
12507
12499
  }
12508
12500
  });
@@ -13148,6 +13140,29 @@ const CustomActionInputSchema = object({
13148
13140
  isActive: boolean()
13149
13141
  })).readonly()
13150
13142
  ),
13143
+ /**
13144
+ * Toggle a single collection-cap provider on/off. Generic write-side
13145
+ * counterpart of `listCapabilityProviders` — drives the per-provider
13146
+ * Enable/Disable affordance in admin pages (TURN servers, etc.)
13147
+ * without needing a bespoke orchestrator cap.
13148
+ *
13149
+ * Reaches the hub's `CapabilityRegistry` directly:
13150
+ * `enableCollectionProvider` / `disableCollectionProvider` flip the
13151
+ * registry-level `disabledProviders` set. `getCollectionEntries`
13152
+ * already filters disabled providers out, so a disabled provider
13153
+ * drops out of every collection aggregate immediately. Only valid
13154
+ * for `mode: 'collection'` caps — the registry no-ops + warns for
13155
+ * singletons.
13156
+ */
13157
+ setCapabilityProviderEnabled: method(
13158
+ object({
13159
+ capName: string().min(1),
13160
+ addonId: string().min(1),
13161
+ enabled: boolean()
13162
+ }),
13163
+ object({ success: literal(true) }),
13164
+ { kind: "mutation", auth: "admin" }
13165
+ ),
13151
13166
  /**
13152
13167
  * Live-update one of the framework packages marked
13153
13168
  * `camstack.system: true` (`@camstack/types|kernel|core|sdk|ui-library`).
@@ -14982,6 +14997,24 @@ class PipelineOrchestratorAddon extends BaseAddon {
14982
14997
  allowedSizes: ["lg", "xl"],
14983
14998
  defaultColumns: 12,
14984
14999
  defaultRows: 4
15000
+ },
15001
+ {
15002
+ // On-camera motion-detection grid editor. The `motion-zones`
15003
+ // cap is owned by the camera provider addons; the widget only
15004
+ // talks to it over tRPC, so hosting the React surface here
15005
+ // (one bundle) avoids duplicating it per provider.
15006
+ stableId: "motion-zones-editor",
15007
+ label: "Motion Zones",
15008
+ description: "On-camera motion-detection grid editor (live-frame overlay).",
15009
+ icon: "grid-3x3",
15010
+ remoteName: "addon_pipeline_orchestrator_widgets",
15011
+ bundle: "remoteEntry.js",
15012
+ hosts: ["device-tab"],
15013
+ requires: { deviceContext: true, integrationContext: false },
15014
+ defaultSize: "lg",
15015
+ allowedSizes: ["md", "lg"],
15016
+ defaultColumns: 12,
15017
+ defaultRows: 1
14985
15018
  }
14986
15019
  ]
14987
15020
  };
@@ -17153,8 +17186,28 @@ class PipelineOrchestratorAddon extends BaseAddon {
17153
17186
  }
17154
17187
  ]
17155
17188
  };
17189
+ const motionZonesSection = {
17190
+ id: "motion-zones",
17191
+ title: "Motion Zones",
17192
+ tab: "motion",
17193
+ location: "settings",
17194
+ columns: 1,
17195
+ order: 0,
17196
+ fields: [
17197
+ {
17198
+ // Widget fields manage their own state via DeviceProxy —
17199
+ // `ConfigWidgetField` carries no `value` (unlike the legacy
17200
+ // `zone-editor` field type which still does).
17201
+ type: "widget",
17202
+ key: "motion-zones",
17203
+ label: "Motion Zones",
17204
+ widgetId: "pipeline-orchestrator/motion-zones-editor",
17205
+ span: 1
17206
+ }
17207
+ ]
17208
+ };
17156
17209
  return {
17157
- sections: [...baseSections, zonesSection]
17210
+ sections: [...baseSections, zonesSection, motionZonesSection]
17158
17211
  };
17159
17212
  }
17160
17213
  // `getCameraPipelineWithFallback` was a thin wrapper over