@camstack/addon-post-analysis 1.2.74 → 1.2.76

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.
@@ -12669,6 +12669,17 @@ var LlmImageSchema = object({
12669
12669
  bytes: _instanceof(Uint8Array),
12670
12670
  mimeType: string()
12671
12671
  });
12672
+ /**
12673
+ * Retry policy. `enabled: false` is NOT the same as `maxAttempts: 1` in intent —
12674
+ * the flag is what a consumer table flips, the count is what the operator tunes.
12675
+ * A retry doubles the wall time of a call, so the two gates that run inside a
12676
+ * notification's budget keep it off (see `CONSUMER_RETRY_POLICY` in addon-ai).
12677
+ */
12678
+ var LlmRetryPolicySchema = object({
12679
+ enabled: boolean().default(false),
12680
+ /** Total attempts INCLUDING the first. 1 = no retry. */
12681
+ maxAttempts: number().int().min(1).max(5).default(1)
12682
+ });
12672
12683
  var LlmGenerateBaseInputSchema = object({
12673
12684
  /** Collection routing (the notification-output posture). */
12674
12685
  addonId: string().optional(),
@@ -12683,7 +12694,28 @@ var LlmGenerateBaseInputSchema = object({
12683
12694
  jsonSchema: record(string(), unknown()).optional(),
12684
12695
  /** Per-call override of the profile default. */
12685
12696
  maxTokens: number().int().positive().optional(),
12686
- temperature: number().optional()
12697
+ temperature: number().optional(),
12698
+ /** Per-call override of the profile default (nucleus sampling). */
12699
+ topP: number().min(0).max(1).optional(),
12700
+ /** Per-call override of the profile default (top-k sampling). */
12701
+ topK: number().int().positive().optional(),
12702
+ /** Per-call override of `profile.timeoutMs` — the total generation bound. */
12703
+ timeoutMs: number().int().positive().optional(),
12704
+ /** Per-call override; beats both the consumer table and the profile. */
12705
+ retry: LlmRetryPolicySchema.optional(),
12706
+ /**
12707
+ * Caller-minted id that makes this generation CANCELLABLE.
12708
+ *
12709
+ * Without it a caller that stops waiting cannot stop the work: the gates race
12710
+ * the call against 8 s and free their own slot when the timer wins, while the
12711
+ * generation upstream keeps running to `profile.timeoutMs` — 60 s by default,
12712
+ * on a single-threaded local model. The per-camera bound then counts WAITS,
12713
+ * not generations, and the real load is unbounded.
12714
+ *
12715
+ * `AbortSignal` cannot cross a process boundary; an id can. Pass one here and
12716
+ * `llm.cancel({ requestId })` tears the socket down.
12717
+ */
12718
+ requestId: string().optional()
12687
12719
  });
12688
12720
  /**
12689
12721
  * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
@@ -12722,8 +12754,49 @@ var ManagedRuntimeConfigSchema = object({
12722
12754
  gpuLayers: number().int().default(0),
12723
12755
  /** Default: cpus-2, clamped ≥1 (resolved node-side). */
12724
12756
  threads: number().int().optional(),
12725
- /** Concurrent slots. */
12757
+ /** Concurrent slots (`--parallel`). */
12726
12758
  parallel: number().int().default(1),
12759
+ /** Logical batch size (`-b`). Larger = faster prompt ingest, more RAM. */
12760
+ batchSize: number().int().positive().optional(),
12761
+ /** Physical batch / micro-batch (`-ub`). */
12762
+ ubatchSize: number().int().positive().optional(),
12763
+ /**
12764
+ * `--flash-attn`. Cuts KV-cache memory on the backends that implement it and
12765
+ * is a no-op elsewhere, so it is offered rather than assumed.
12766
+ */
12767
+ flashAttention: boolean().default(false),
12768
+ /**
12769
+ * `--mlock`. Pins the weights in RAM so the OS cannot page them out mid
12770
+ * inference. Costs the full model size in resident memory — which is exactly
12771
+ * what the RAM budget is counting.
12772
+ */
12773
+ mlock: boolean().default(false),
12774
+ /**
12775
+ * `--no-mmap`. Reads the whole GGUF up front instead of mapping it. Slower to
12776
+ * start, but avoids the page-fault stalls a network or spinning-disk model
12777
+ * store produces on every first token.
12778
+ */
12779
+ noMmap: boolean().default(false),
12780
+ /** `--cache-type-k` / `--cache-type-v` — quantising the KV cache is the
12781
+ * cheapest way to fit a longer context in the same RAM. */
12782
+ cacheTypeK: _enum([
12783
+ "f32",
12784
+ "f16",
12785
+ "q8_0",
12786
+ "q5_1",
12787
+ "q5_0",
12788
+ "q4_1",
12789
+ "q4_0"
12790
+ ]).optional(),
12791
+ cacheTypeV: _enum([
12792
+ "f32",
12793
+ "f16",
12794
+ "q8_0",
12795
+ "q5_1",
12796
+ "q5_0",
12797
+ "q4_1",
12798
+ "q4_0"
12799
+ ]).optional(),
12727
12800
  /** Else lazy: first generate boots it. */
12728
12801
  autoStart: boolean().default(false),
12729
12802
  /** 0 = never; frees RAM after quiet periods. */
@@ -12811,10 +12884,44 @@ var LlmProfileSchema = object({
12811
12884
  baseUrl: string().optional(),
12812
12885
  /** ConfigUISchema type:'password' — never round-trips (spec §5). */
12813
12886
  apiKey: string().optional(),
12887
+ /** Vision on/off. A vision call against a `false` profile is REFUSED, never
12888
+ * degraded to text — that shipped once and produced a confident answer to a
12889
+ * question about a picture nobody sent. */
12814
12890
  supportsVision: boolean(),
12815
12891
  temperature: number().min(0).max(2).optional(),
12892
+ /** Nucleus sampling. Every wire we speak has it. */
12893
+ topP: number().min(0).max(1).optional(),
12894
+ /** Top-k sampling. Carried only by the wires that have it — NEITHER OpenAI
12895
+ * wire does, and the client drops it there (measured: the request body gets
12896
+ * `top_p` and no `top_k`). The profile editor hides the field wherever it
12897
+ * would change nothing; `KINDS_WITH_TOP_K` is the single owner of that list. */
12898
+ topK: number().int().positive().optional(),
12816
12899
  maxTokens: number().int().positive().optional(),
12900
+ /** Prompt context window. Advisory for cloud kinds (they enforce their own);
12901
+ * for `managed-local` it is the llama.cpp `--ctx-size` the runtime starts
12902
+ * the model with, so it is the one field that changes a PROCESS. */
12903
+ contextLength: number().int().positive().optional(),
12904
+ /** Default system prompt. A caller's `system` REPLACES it (never appends —
12905
+ * two system prompts fighting is worse than either alone). */
12906
+ systemPrompt: string().optional(),
12907
+ /** Total generation bound — the only one a unary call has. */
12817
12908
  timeoutMs: number().int().positive().default(6e4),
12909
+ /** Wait for response headers only. */
12910
+ connectTimeoutMs: number().int().positive().default(1e4),
12911
+ /** Accepted, but no output yet — a cold GPU load lives here. */
12912
+ firstTokenTimeoutMs: number().int().positive().default(12e4),
12913
+ /** Output started then stopped. */
12914
+ idleTimeoutMs: number().int().positive().default(6e4),
12915
+ /** Profile-level default. The per-consumer table and a per-call override
12916
+ * both beat it — see `resolveRetryPolicy`. */
12917
+ retry: LlmRetryPolicySchema.default({
12918
+ enabled: false,
12919
+ maxAttempts: 1
12920
+ }),
12921
+ /** Whether this profile may use tools. The tool-call plumbing rides the
12922
+ * library; the REGISTRY of callable tools is ours and is empty in v1, so a
12923
+ * `true` here buys the wiring, not behaviour, until tools are registered. */
12924
+ toolsEnabled: boolean().default(false),
12818
12925
  extraHeaders: record(string(), string()).optional(),
12819
12926
  /** kind === 'managed-local' only (spec §4). */
12820
12927
  runtime: ManagedRuntimeConfigSchema.optional()
@@ -12876,7 +12983,10 @@ var ProfileRefInputSchema = object({
12876
12983
  addonId: string(),
12877
12984
  profileId: string()
12878
12985
  });
12879
- method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
12986
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({
12987
+ addonId: string().optional(),
12988
+ requestId: string()
12989
+ }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
12880
12990
  kind: "mutation",
12881
12991
  auth: "admin"
12882
12992
  }), method(ProfileRefInputSchema, _void(), {
@@ -14718,28 +14828,36 @@ var NcOccupancyConditionSchema = object({
14718
14828
  /**
14719
14829
  * Audio condition (IMMEDIATE trigger) — a rule on SOUND, not on a picture.
14720
14830
  *
14721
- * Operator-approved vocabulary (2026-08-12, option A — the same one the
14722
- * reference notifier uses, so an operator moving between them re-uses what
14723
- * they already know): a rule matches when, over a sampling window of
14724
- * `samplingSeconds`, at least `hitPercent`% of the audio samples in that
14725
- * window are HITS. A sample is a hit when it satisfies BOTH present filters:
14726
- *
14727
- * - `dbThreshold` — its level is at or above this many dBFS (see
14728
- * {@link NC_AUDIO_DBFS_FLOOR}: negative-going, `0` = full scale);
14729
- * - `labels` — the classifier put at least one of these labels on it.
14730
- *
14731
- * Both are OPTIONAL and independent, which is the point of the shape: a
14732
- * loudness rule ("something loud at 3am") needs no model to be right, and a
14733
- * label rule ("a dog barked") needs no threshold. **Fail-closed when NEITHER
14734
- * is given** — a window in which every sample is trivially a hit would fire on
14735
- * silence, so the engine refuses such a condition rather than notifying on
14736
- * nothing (the schema cannot express "at least one of" without becoming a
14737
- * ZodEffects the cap path would have to special-case).
14738
- *
14739
- * `hitPercent` is over the samples the window actually HOLDS, and the window
14740
- * must be FULL before it can match — a window that has been open for two
14741
- * seconds of its ten is 100% of nothing, and firing on it would make
14742
- * `samplingSeconds` decorative.
14831
+ * **TWO EXCLUSIVE MODES** (operator decision 2026-08-14, D157). Which one a
14832
+ * rule is in is not a stored field — it is WHICH FILTER the rule carries, so
14833
+ * there is no second switch that can disagree with the first and every rule
14834
+ * authored before the decision migrates for free (`audioModeOf`):
14835
+ *
14836
+ * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14837
+ * classifier labels with one of them. No window, no percentage:
14838
+ * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14839
+ * `throttle` cooldown is the only brake. The per-label confidence floor is
14840
+ * the analyzer's (`classificationMinScore`, per device) — a label only
14841
+ * reaches this condition if the classifier was already confident enough.
14842
+ * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14843
+ * the condition: at least `hitPercent`% of the samples over
14844
+ * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
14845
+ * {@link NC_AUDIO_DBFS_FLOOR}: negative-going, `0` = full scale). The window
14846
+ * must be FULL before it can match — a window open for two of its ten
14847
+ * seconds is 100% of nothing.
14848
+ *
14849
+ * **Why label mode has no window.** It had one, and it never fired: the
14850
+ * analyzer emits ~1 audio frame per second but YAMNet only LABELS one to three
14851
+ * of them per episode, even through continuous crying. The measured maximum
14852
+ * `hitPercent` over the whole live history was 40 — under the shipped default
14853
+ * of 60, so a label rule could not fire at all, ever. A percentage of frames is
14854
+ * the wrong question to ask of a sparse classifier.
14855
+ *
14856
+ * **Fail-closed when NEITHER is given** — every sample would be a trivial hit
14857
+ * and the rule would fire on silence. The schema cannot express "exactly one
14858
+ * of" without becoming a ZodEffects the cap path would have to special-case, so
14859
+ * the exclusivity is enforced where every editor writes (`patchAudio`) and a
14860
+ * legacy rule carrying both resolves to LABEL (the mode that fires).
14743
14861
  *
14744
14862
  * Labels are the audio macro classes (`AUDIO_MACRO_LABELS` / the NC taxonomy's
14745
14863
  * `audio-*` ids). Both spellings are accepted — the matcher normalizes the
@@ -14747,13 +14865,13 @@ var NcOccupancyConditionSchema = object({
14747
14865
  * an operator who typed `dog` mean the same thing.
14748
14866
  */
14749
14867
  var NcAudioConditionSchema = object({
14750
- /** Audio macro labels; absent = any sound (level-only rule). */
14868
+ /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14751
14869
  labels: array(string().min(1)).min(1).optional(),
14752
- /** Level floor in dBFS (negative-going, `0` = full scale); absent = any level. */
14870
+ /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14753
14871
  dbThreshold: number().min(-96).max(0).optional(),
14754
- /** Percentage of the window's samples that must be hits (1–100). */
14872
+ /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14755
14873
  hitPercent: number().int().min(1).max(100).default(60),
14756
- /** Length of the sampling window in seconds. */
14874
+ /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14757
14875
  samplingSeconds: number().int().min(1).max(300).default(10)
14758
14876
  });
14759
14877
  /**
@@ -27468,14 +27586,55 @@ method(object({
27468
27586
  * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
27469
27587
  * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
27470
27588
  *
27471
- * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) — the framework
27472
- * derives the device-detail contribution; the provider carries NO hand-written
27473
- * settings-contribution methods. `status.kind:'push'` — the engine pushes on
27474
- * every hysteresis flip / availability change; consumers never poll.
27475
- */
27476
- /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
27477
- * be added without a wire break (matching falls back to any-condition refs). */
27589
+ * **No `deviceConfig`, deliberately.** This shipped as the D14 widget archetype,
27590
+ * which put a "Scenes" tab on one camera's detail page. That is the wrong shape
27591
+ * for the thing: a scene is a standing question about the property ("is the bin
27592
+ * still out"), and the operator's question is "which of my scenes have tripped",
27593
+ * across every camera at once — not "what does camera 617 think". Buried one
27594
+ * camera deep it also could not be found. The surface is now a top-level admin
27595
+ * page (`/scenes`, `pages/Scenes.tsx`) that lists every scene on every camera and
27596
+ * picks the camera inside the create flow, the same shape Events and Faces have.
27597
+ *
27598
+ * The consequence to keep in mind: `host/scene-monitor-editor` is gone from
27599
+ * `HOST_WIDGETS` too. `scripts/check-host-widget-resolves.ts` asserts BOTH
27600
+ * directions, so a registration nobody declares fails exactly as loudly as a
27601
+ * declaration nobody registers. The editor is imported directly by the page.
27602
+ *
27603
+ * `status.kind:'push'` — the engine pushes on every hysteresis flip /
27604
+ * availability change; consumers never poll.
27605
+ */
27606
+ /** Extensible condition tag. Seeded 'day' | 'ir' (the two variants the operator
27607
+ * captures) plus 'night' | 'dawn' | 'dusk' from the resolver's sun-times band.
27608
+ * Open by design so more can be added without a wire break.
27609
+ *
27610
+ * Matching does NOT fall back across conditions: cross-condition cosines are
27611
+ * not comparable, so "I have never seen this scene in this light" is reported
27612
+ * as `unknown`, never guessed. A day reference scored against an IR frame
27613
+ * collapses the cosine and would latch a false alarm every single night. */
27478
27614
  var SceneConditionSchema = string();
27615
+ /** Reserved, engine-owned state id standing for "the region no longer looks
27616
+ * like ANY captured reference". Never authored by an operator, never stored in
27617
+ * `states[]` — it is the synthetic candidate that makes divergence a
27618
+ * first-class hysteresis input rather than a bare `return`. */
27619
+ var SCENE_DIVERGED = "__diverged__";
27620
+ /** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
27621
+ * `unknown` = we cannot judge (no reference for this condition, encoder model
27622
+ * changed, view shifted, no snapshot). `unknown` is a real value, not a null,
27623
+ * and never counts toward hysteresis in either direction. */
27624
+ var SceneVerdictSchema = _enum([
27625
+ "matched",
27626
+ "diverged",
27627
+ "unknown"
27628
+ ]);
27629
+ /** Why a scene cannot judge. Named, because this feature's failure mode is
27630
+ * silence that reads as "nothing has happened". */
27631
+ var SceneUnavailableSchema = _enum([
27632
+ "no-reference-for-condition",
27633
+ "view-shifted",
27634
+ "no-vision-profile",
27635
+ "encoder-model-changed",
27636
+ "no-snapshot"
27637
+ ]);
27479
27638
  /** One captured reference — condition-tagged, model-version-gated. `embedding`
27480
27639
  * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
27481
27640
  var SceneReferenceSchema = object({
@@ -27483,7 +27642,14 @@ var SceneReferenceSchema = object({
27483
27642
  modelId: string(),
27484
27643
  condition: SceneConditionSchema,
27485
27644
  capturedAt: number(),
27486
- thumbnailMediaId: string().optional()
27645
+ thumbnailMediaId: string().optional(),
27646
+ /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
27647
+ * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
27648
+ * normalized rect frame a different piece of world, and the scene would
27649
+ * diverge forever with a perfectly plausible cosine. Checked LAZILY, only
27650
+ * when hysteresis is about to flip — one extra encode per candidate
27651
+ * transition, not per poll. */
27652
+ anchorEmbedding: array(number()).optional()
27487
27653
  });
27488
27654
  var SceneMonitorStateSchema = object({
27489
27655
  id: string(),
@@ -27505,6 +27671,26 @@ var SceneCheckSchema = discriminatedUnion("mode", [object({
27505
27671
  profileId: string().optional(),
27506
27672
  hysteresisCount: number().int().positive()
27507
27673
  })]);
27674
+ var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
27675
+ var SCENE_CONFIRM_DEFAULT_TIMEOUT_MS = 8e3;
27676
+ /**
27677
+ * Vision-model adjudication of a candidate flip. Field names deliberately
27678
+ * mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
27679
+ *
27680
+ * `onTimeout` defaults to **'hold'**, the OPPOSITE of `NcConfirmGate`'s
27681
+ * fail-open: a notification suppressed is the worse error there, but a vision
27682
+ * model that timed out has not told us the bin is gone, and a latch is a
27683
+ * stateful claim that costs the operator a trip to reset.
27684
+ */
27685
+ var SceneConfirmSchema = object({
27686
+ enabled: boolean().default(false),
27687
+ prompt: string().min(1).max(1e3),
27688
+ profileId: string().optional(),
27689
+ timeoutMs: number().int().min(1e3).max(2e4).default(SCENE_CONFIRM_DEFAULT_TIMEOUT_MS),
27690
+ maxImagePx: number().int().min(64).max(2048).default(448),
27691
+ /** What a timeout / unavailable model means for the PENDING flip. */
27692
+ onTimeout: _enum(["flip", "hold"]).default("hold")
27693
+ });
27508
27694
  var SceneMonitorSchema = object({
27509
27695
  id: string(),
27510
27696
  label: string(),
@@ -27523,7 +27709,41 @@ var SceneMonitorSchema = object({
27523
27709
  lastConfidence: number().nullable(),
27524
27710
  currentCondition: SceneConditionSchema.nullable(),
27525
27711
  availability: _enum(["ok", "unavailable"]),
27526
- unavailableReason: string().nullable()
27712
+ unavailableReason: string().nullable(),
27713
+ /** Which state is "the initial screen". `null` until the first capture. */
27714
+ baselineStateId: string().nullable(),
27715
+ /** Which boolean drives notification rules and any export. */
27716
+ emit: _enum(["latched", "live"]).default("latched"),
27717
+ /** Live: does the region match the baseline RIGHT NOW. */
27718
+ verdict: SceneVerdictSchema,
27719
+ /** Has it been `diverged` at least once since `armedAt` — the operator's boolean. */
27720
+ latched: boolean(),
27721
+ /** Last reset (or creation). */
27722
+ armedAt: number(),
27723
+ divergedAt: number().nullable(),
27724
+ restoredAt: number().nullable(),
27725
+ /** A check is only COUNTED when the device has been quiet this long. Motion
27726
+ * during the window DISCARDS the observation — a car pulling up in front of
27727
+ * the bin must not be able to spend hysteresis credit. */
27728
+ quietSeconds: number().int().min(0).max(3600).default(60),
27729
+ /** An observation only advances the pending count when it is at least this
27730
+ * far from the previously counted one, so N agreeing checks span real time
27731
+ * rather than N adjacent polls inside one occlusion. */
27732
+ minObservationSpacingSec: number().int().min(0).max(3600).default(120),
27733
+ /** Vision-model adjudication of a candidate flip. Similarity primary only. */
27734
+ confirm: SceneConfirmSchema.optional(),
27735
+ /** Whole-frame anchor cosine below which a flip is REFUSED as `view-shifted`. */
27736
+ anchorThreshold: number().min(0).max(1).default(SCENE_DEFAULT_ANCHOR_THRESHOLD),
27737
+ /** Clear the latch on its own when the scene matches again? Default false —
27738
+ * `restoredAt` and the `scene-restored` edge are recorded regardless, so an
27739
+ * automation can react to the bin coming back without the operator's own
27740
+ * alarm silently clearing itself. */
27741
+ autoRestore: boolean().default(false),
27742
+ /** Named cause when `verdict === 'unknown'`. */
27743
+ unavailable: SceneUnavailableSchema.nullable(),
27744
+ /** Conditions that have at least one comparable reference — the coverage line
27745
+ * ("day ✓ · ir ✓ · dusk ✗") that turns a silent fallback into a visible fact. */
27746
+ coveredConditions: array(SceneConditionSchema)
27527
27747
  });
27528
27748
  var SceneMonitorStatusSchema = object({
27529
27749
  monitors: array(SceneMonitorSchema),
@@ -27536,12 +27756,6 @@ var sceneMonitorCapability = {
27536
27756
  kind: "wrapper",
27537
27757
  defaultActive: true,
27538
27758
  deviceTypes: [DeviceType.Camera],
27539
- deviceConfig: { ui: {
27540
- kind: "widget",
27541
- widgetId: "host/scene-monitor-editor",
27542
- tab: "scenes",
27543
- label: "Scenes"
27544
- } },
27545
27759
  methods: {
27546
27760
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
27547
27761
  createScene: method(object({
@@ -27572,7 +27786,14 @@ var sceneMonitorCapability = {
27572
27786
  "both"
27573
27787
  ]).optional(),
27574
27788
  checkIntervalSec: number().optional(),
27575
- check: SceneCheckSchema.optional()
27789
+ check: SceneCheckSchema.optional(),
27790
+ emit: _enum(["latched", "live"]).optional(),
27791
+ quietSeconds: number().int().min(0).max(3600).optional(),
27792
+ minObservationSpacingSec: number().int().min(0).max(3600).optional(),
27793
+ anchorThreshold: number().min(0).max(1).optional(),
27794
+ autoRestore: boolean().optional(),
27795
+ /** `null` clears the vision-model adjudicator. */
27796
+ confirm: SceneConfirmSchema.nullable().optional()
27576
27797
  })
27577
27798
  }), _void(), {
27578
27799
  kind: "mutation",
@@ -27613,6 +27834,26 @@ var sceneMonitorCapability = {
27613
27834
  }), _void(), {
27614
27835
  kind: "mutation",
27615
27836
  auth: "admin"
27837
+ }),
27838
+ /**
27839
+ * Clear the latch, re-arm, and — by default — RE-CAPTURE the baseline for
27840
+ * the CURRENT condition. The bin never goes back in exactly the same spot;
27841
+ * "reset" in the operator's head means *this is the new normal*, and
27842
+ * re-capture is what makes the feature self-healing against slow drift
27843
+ * instead of failing silently weeks later.
27844
+ *
27845
+ * Reachable from three surfaces on this one mutation: the scene card, a
27846
+ * notification button (an `onTrigger` sequence with a `kind:'cap'` step —
27847
+ * no new Notification-Center code at all), and tRPC for scripts.
27848
+ */
27849
+ resetScene: method(object({
27850
+ deviceId: number(),
27851
+ monitorId: string(),
27852
+ /** Defaults to TRUE at the provider seam — see `SCENE_RESET_RECAPTURES`. */
27853
+ recapture: boolean().optional()
27854
+ }), _void(), {
27855
+ kind: "mutation",
27856
+ auth: "admin"
27616
27857
  })
27617
27858
  },
27618
27859
  status: {
@@ -28103,12 +28344,64 @@ var NetworkAddressSchema = object({
28103
28344
  family: string(),
28104
28345
  internal: boolean()
28105
28346
  });
28347
+ /**
28348
+ * Provenance of the site coordinates, and the whole reason this is not just two
28349
+ * numbers.
28350
+ *
28351
+ * - `operator-set` — a human typed it, or accepted a detection. Authoritative;
28352
+ * nothing overwrites it.
28353
+ * - `derived-from-ip` — the hub geolocated its own public IP once, because a
28354
+ * default that is right to a few kilometres beats the coarse UTC clock split
28355
+ * the sun-times consumers otherwise fall back to.
28356
+ *
28357
+ * The UI shows which one it is. An operator who cannot tell a guess from their
28358
+ * own input will eventually trust the guess.
28359
+ */
28360
+ var SiteLocationSourceSchema = _enum(["operator-set", "derived-from-ip"]);
28361
+ /**
28362
+ * The read shape: the location plus the honest state of the one-shot derivation.
28363
+ *
28364
+ * `derivationAttemptedAt` is what makes the "one call, ever" contract
28365
+ * inspectable. When it is set and `location` is null, the geo-IP lookup ran and
28366
+ * failed; the hub will NOT try again on its own — the fallback is declared
28367
+ * (consumers degrade to their own last resort) and the operator either types the
28368
+ * coordinates or presses detect.
28369
+ */
28370
+ var SiteLocationStatusSchema = object({
28371
+ location: object({
28372
+ /** WGS84 decimal degrees. */
28373
+ latitude: number().min(-90).max(90),
28374
+ longitude: number().min(-180).max(180),
28375
+ source: SiteLocationSourceSchema,
28376
+ /** Epoch ms the value was last written. */
28377
+ updatedAt: number(),
28378
+ /**
28379
+ * Human-readable place the geo-IP service reported ("Napoli, IT"). Display
28380
+ * only — never parsed, never matched on. Absent for an operator-typed value.
28381
+ */
28382
+ label: string().optional()
28383
+ }).nullable(),
28384
+ derivationAttemptedAt: number().nullable(),
28385
+ /** Why the last derivation failed, for the UI to show instead of a shrug. */
28386
+ derivationError: string().nullable()
28387
+ });
28388
+ /** `null` clears the location and re-arms nothing — the derivation stays spent. */
28389
+ var SetSiteLocationInputSchema = object({
28390
+ latitude: number().min(-90).max(90),
28391
+ longitude: number().min(-180).max(180)
28392
+ }).nullable();
28106
28393
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
28107
28394
  kind: "mutation",
28108
28395
  auth: "admin"
28109
28396
  }), method(_void(), _void(), {
28110
28397
  kind: "mutation",
28111
28398
  auth: "admin"
28399
+ }), method(_void(), SiteLocationStatusSchema), method(SetSiteLocationInputSchema, SiteLocationStatusSchema, {
28400
+ kind: "mutation",
28401
+ auth: "admin"
28402
+ }), method(_void(), SiteLocationStatusSchema, {
28403
+ kind: "mutation",
28404
+ auth: "admin"
28112
28405
  });
28113
28406
  /**
28114
28407
  * Tamper / case-open detection sensor. Drives Home Assistant
@@ -32297,6 +32590,12 @@ Object.freeze({
32297
32590
  addonId: null,
32298
32591
  access: "create"
32299
32592
  },
32593
+ "llm.cancel": {
32594
+ capName: "llm",
32595
+ capScope: "system",
32596
+ addonId: null,
32597
+ access: "create"
32598
+ },
32300
32599
  "llm.deleteModel": {
32301
32600
  capName: "llm",
32302
32601
  capScope: "system",
@@ -34547,6 +34846,12 @@ Object.freeze({
34547
34846
  addonId: null,
34548
34847
  access: "create"
34549
34848
  },
34849
+ "sceneMonitor.resetScene": {
34850
+ capName: "scene-monitor",
34851
+ capScope: "device",
34852
+ addonId: null,
34853
+ access: "delete"
34854
+ },
34550
34855
  "sceneMonitor.updateScene": {
34551
34856
  capName: "scene-monitor",
34552
34857
  capScope: "device",
@@ -35225,6 +35530,12 @@ Object.freeze({
35225
35530
  addonId: null,
35226
35531
  access: "create"
35227
35532
  },
35533
+ "system.detectSiteLocation": {
35534
+ capName: "system",
35535
+ capScope: "system",
35536
+ addonId: null,
35537
+ access: "create"
35538
+ },
35228
35539
  "system.featureFlags": {
35229
35540
  capName: "system",
35230
35541
  capScope: "system",
@@ -35243,6 +35554,12 @@ Object.freeze({
35243
35554
  addonId: null,
35244
35555
  access: "view"
35245
35556
  },
35557
+ "system.getSiteLocation": {
35558
+ capName: "system",
35559
+ capScope: "system",
35560
+ addonId: null,
35561
+ access: "view"
35562
+ },
35246
35563
  "system.health": {
35247
35564
  capName: "system",
35248
35565
  capScope: "system",
@@ -35267,6 +35584,12 @@ Object.freeze({
35267
35584
  addonId: null,
35268
35585
  access: "create"
35269
35586
  },
35587
+ "system.setSiteLocation": {
35588
+ capName: "system",
35589
+ capScope: "system",
35590
+ addonId: null,
35591
+ access: "create"
35592
+ },
35270
35593
  "terminalSession.adoptLegacyMonitor": {
35271
35594
  capName: "terminal-session",
35272
35595
  capScope: "system",
@@ -37229,6 +37552,11 @@ Object.freeze({
37229
37552
  form: "single",
37230
37553
  optional: false
37231
37554
  }],
37555
+ "sceneMonitor.resetScene": [{
37556
+ name: "deviceId",
37557
+ form: "single",
37558
+ optional: false
37559
+ }],
37232
37560
  "sceneMonitor.updateScene": [{
37233
37561
  name: "deviceId",
37234
37562
  form: "single",
@@ -37519,6 +37847,26 @@ Object.freeze({
37519
37847
  "network-access": "ingress",
37520
37848
  "smtp-provider": "email"
37521
37849
  });
37850
+ /**
37851
+ * Which mode this condition is in — the ONE place that question is answered.
37852
+ *
37853
+ * The mode is NOT a stored field, deliberately. It is which filter the rule
37854
+ * carries, so every rule authored before the modes existed migrates for free
37855
+ * and there is no second switch that can disagree with the first (the failure
37856
+ * this repo has shipped twice). `null` = neither filter, which the engine
37857
+ * refuses (see {@link audioIsFailClosed}).
37858
+ *
37859
+ * A LEGACY rule carrying BOTH resolves to `label`: it is the mode that fires,
37860
+ * and the alternative is silently keeping a window the operator can no longer
37861
+ * see in the editor. Nothing new can reach this branch — {@link patchAudio}
37862
+ * clears the other filter on every write.
37863
+ */
37864
+ function audioModeOf(value) {
37865
+ if (value === void 0) return null;
37866
+ if (value.labels !== void 0 && value.labels.length > 0) return "label";
37867
+ if (value.dbThreshold !== void 0) return "level";
37868
+ return null;
37869
+ }
37522
37870
  new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37523
37871
  new Set(["devices", "classes"]);
37524
37872
  var WEEKDAY_TO_DAY = {
@@ -38063,4 +38411,4 @@ function vectorDimFromBase64(encoded) {
38063
38411
  return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
38064
38412
  }
38065
38413
  //#endregion
38066
- export { plateGalleryCapability as $, TIMELAPSE_DENSE_FLOOR_SEC as A, cosineSimilarity as B, NcSnoozeSchema as C, string as Ct, OpsLogEntrySchema as D, NcTaxonomySchema as E, addonWidgetsSourceCapability as F, encodeVectorBase64 as G, defineCustomActions as H, alarmPanelCapability as I, isDetectionMacroClass as J, faceGalleryCapability as K, assertTimelapseCadences as L, TimelapseRulePatchSchema as M, TimelapseRuleSchema as N, RECORDING_EXPORT_MAX_READ_BYTES as O, TrackSourceSchema as P, pipelineAnalyticsCapability as Q, audioMetricsCapability as R, NcSnoozeInputSchema as S, record as St, NcSystemEventKindSchema as T, EventCategory as Tt, deriveRecordingMode as U, customAction as V, embeddingEncoderCapability as W, kebabToCamel as X, isScheduleActive as Y, notificationRulesCapability as Z, NcRuleInputSchema as _, discriminatedUnion as _t, DETECTION_PIPELINE_CAP_NAME as a, zoneAnalyticsCapability as at, NcRuleTargetSchema as b, object as bt, EVENT_PAD_MS as c, DeviceType as ct, NC_ALARM_SYSTEM_EVENT_KINDS as d, isDeviceScopedCap as dt, readDeviceStateFrom as et, NC_CONDITION_CATALOG as f, nodePin as ft, NcConditionDescriptorSchema as g, boolean as gt, NC_TAXONOMY as h, array as ht, DETECTION_MACRO_CLASSES as i, videoclipsCapability as it, TimelapseRuleInputSchema as j, RetrainStatusSchema as k, LabelAttributionSchema as l, createEvent as lt, NC_SNOOZE_MAX_MINUTES as m, _enum as mt, DEFAULT_EVENT_COLOR as n, subKindsOf as nt, DeclaredDevices as o, errMsg as ot, NC_DEFAULT_SNOOZE_MINUTES as p, sleep as pt, hfModelUrl as q, DEFAULT_TIMELAPSE_PREVIEW_TEXT as r, vectorDimFromBase64 as rt, EVENT_KIND_BY_CAP as s, BaseAddon as st, BaseDevice as t, readTimelapseGeneratedAt as tt, MACRO_LABELS as u, hydrateSchema as ut, NcRulePatchSchema as v, literal as vt, NcSnoozeSuppressedSchema as w, unknown as wt, NcScheduleSchema as x, partialRecord as xt, NcRuleSchema as y, number as yt, buildEventKindDescriptor as z };
38414
+ export { isDetectionMacroClass as $, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as A, unknown as At, alarmPanelCapability as B, NcSnoozeSchema as C, discriminatedUnion as Ct, OpsLogEntrySchema as D, partialRecord as Dt, NcTaxonomySchema as E, object as Et, TimelapseRuleInputSchema as F, cosineSimilarity as G, audioMetricsCapability as H, TimelapseRulePatchSchema as I, deriveRecordingMode as J, customAction as K, TimelapseRuleSchema as L, SCENE_DIVERGED as M, SceneMonitorSchema as N, RECORDING_EXPORT_MAX_READ_BYTES as O, record as Ot, TIMELAPSE_DENSE_FLOOR_SEC as P, hfModelUrl as Q, TrackSourceSchema as R, NcSnoozeInputSchema as S, boolean as St, NcSystemEventKindSchema as T, number as Tt, audioModeOf as U, assertTimelapseCadences as V, buildEventKindDescriptor as W, encodeVectorBase64 as X, embeddingEncoderCapability as Y, faceGalleryCapability as Z, NcRuleInputSchema as _, isDeviceScopedCap as _t, DETECTION_PIPELINE_CAP_NAME as a, readDeviceStateFrom as at, NcRuleTargetSchema as b, _enum as bt, EVENT_PAD_MS as c, subKindsOf as ct, NC_ALARM_SYSTEM_EVENT_KINDS as d, zoneAnalyticsCapability as dt, isScheduleActive as et, NC_CONDITION_CATALOG as f, errMsg as ft, NcConditionDescriptorSchema as g, hydrateSchema as gt, NC_TAXONOMY as h, createEvent as ht, DETECTION_MACRO_CLASSES as i, plateGalleryCapability as it, SCENE_DEFAULT_ANCHOR_THRESHOLD as j, EventCategory as jt, RetrainStatusSchema as k, string as kt, LabelAttributionSchema as l, vectorDimFromBase64 as lt, NC_SNOOZE_MAX_MINUTES as m, DeviceType as mt, DEFAULT_EVENT_COLOR as n, notificationRulesCapability as nt, DeclaredDevices as o, readTimelapseGeneratedAt as ot, NC_DEFAULT_SNOOZE_MINUTES as p, BaseAddon as pt, defineCustomActions as q, DEFAULT_TIMELAPSE_PREVIEW_TEXT as r, pipelineAnalyticsCapability as rt, EVENT_KIND_BY_CAP as s, sceneMonitorCapability as st, BaseDevice as t, kebabToCamel as tt, MACRO_LABELS as u, videoclipsCapability as ut, NcRulePatchSchema as v, nodePin as vt, NcSnoozeSuppressedSchema as w, literal as wt, NcScheduleSchema as x, array as xt, NcRuleSchema as y, sleep as yt, addonWidgetsSourceCapability as z };
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-Cy8lyywE.js");
5
+ const require_dist = require("../dist-BsBjSNIo.js");
6
6
  let node_fs = require("node:fs");
7
7
  let node_fs$1 = require_dist.__toESM(node_fs, 1);
8
8
  node_fs = require_dist.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { W as embeddingEncoderCapability, q as hfModelUrl, st as BaseAddon } from "../dist-CvWsGsxP.mjs";
1
+ import { Q as hfModelUrl, Y as embeddingEncoderCapability, pt as BaseAddon } from "../dist-CYfG8ulU.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import "node:fs";
@@ -1,6 +1,6 @@
1
1
  import { a as e, i as t, n, o as r, r as i, t as a } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.js-C0AuF9av.mjs";
2
2
  import { t as o } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-B3Wx5J80.mjs";
3
- import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DE456p1Q.mjs";
3
+ import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DFckpC3K.mjs";
4
4
  import { n as m, r as h, t as g } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-Bm-iyjmq.mjs";
5
5
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
6
6
  var _ = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), v = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), y = (e) => {