@blueharford/scrypted-spatial-awareness 0.6.30 → 0.6.32

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.
package/dist/plugin.zip CHANGED
Binary file
@@ -34457,6 +34457,12 @@ class AlertManager {
34457
34457
  const notifierIds = rule.notifiers.length > 0
34458
34458
  ? rule.notifiers
34459
34459
  : this.getDefaultNotifiers();
34460
+ // Debug: log which notifiers we're using
34461
+ this.console.log(`[Notification] Rule ${rule.id} has ${rule.notifiers.length} notifiers, using ${notifierIds.length} notifier(s): ${notifierIds.join(', ') || 'NONE'}`);
34462
+ if (notifierIds.length === 0) {
34463
+ this.console.warn(`[Notification] No notifiers configured! Configure a notifier in plugin settings.`);
34464
+ return;
34465
+ }
34460
34466
  // Try to get a thumbnail from the camera
34461
34467
  let mediaObject;
34462
34468
  const cameraId = alert.details.toCameraId || alert.details.cameraId;
@@ -34551,27 +34557,34 @@ class AlertManager {
34551
34557
  try {
34552
34558
  // Try new multiple notifiers setting first
34553
34559
  const notifiers = this.storage.getItem('defaultNotifiers');
34560
+ this.console.log(`[Notifiers] Raw storage value: ${notifiers}`);
34554
34561
  if (notifiers) {
34555
34562
  // Could be JSON array or comma-separated string
34556
34563
  try {
34557
34564
  const parsed = JSON.parse(notifiers);
34558
34565
  if (Array.isArray(parsed)) {
34566
+ this.console.log(`[Notifiers] Parsed JSON array: ${parsed.join(', ')}`);
34559
34567
  return parsed;
34560
34568
  }
34561
34569
  }
34562
34570
  catch {
34563
34571
  // Not JSON, might be comma-separated or single value
34564
34572
  if (notifiers.includes(',')) {
34565
- return notifiers.split(',').map(s => s.trim()).filter(Boolean);
34573
+ const result = notifiers.split(',').map(s => s.trim()).filter(Boolean);
34574
+ this.console.log(`[Notifiers] Parsed comma-separated: ${result.join(', ')}`);
34575
+ return result;
34566
34576
  }
34577
+ this.console.log(`[Notifiers] Single value: ${notifiers}`);
34567
34578
  return [notifiers];
34568
34579
  }
34569
34580
  }
34570
34581
  // Fallback to old single notifier setting
34571
34582
  const defaultNotifier = this.storage.getItem('defaultNotifier');
34583
+ this.console.log(`[Notifiers] Fallback single notifier: ${defaultNotifier || 'NONE'}`);
34572
34584
  return defaultNotifier ? [defaultNotifier] : [];
34573
34585
  }
34574
- catch {
34586
+ catch (e) {
34587
+ this.console.error(`[Notifiers] Error reading notifiers:`, e);
34575
34588
  return [];
34576
34589
  }
34577
34590
  }
@@ -35427,7 +35440,7 @@ class SpatialReasoningEngine {
35427
35440
  id: deviceId,
35428
35441
  name: device.name || deviceId,
35429
35442
  providerType: providerTypeEnum,
35430
- lastUsed: 0,
35443
+ lastUsed: Date.now() - 60000, // Initialize to 1 minute ago so all LLMs start equal
35431
35444
  errorCount: 0,
35432
35445
  });
35433
35446
  this.console.log(`[LLM] Using configured LLM: ${device.name}`);
@@ -35455,7 +35468,7 @@ class SpatialReasoningEngine {
35455
35468
  id,
35456
35469
  name: device.name || id,
35457
35470
  providerType: providerTypeEnum,
35458
- lastUsed: 0,
35471
+ lastUsed: Date.now() - 60000, // Initialize to 1 minute ago so all LLMs start equal
35459
35472
  errorCount: 0,
35460
35473
  });
35461
35474
  this.console.log(`[LLM] Auto-discovered: ${device.name}`);
@@ -37489,14 +37502,22 @@ class TrackingEngine {
37489
37502
  this.lastLlmCallTime = Date.now();
37490
37503
  }
37491
37504
  /** Check and record LLM call - returns false if rate limited */
37492
- tryLlmCall() {
37505
+ tryLlmCall(silent = false) {
37493
37506
  if (!this.isLlmCallAllowed()) {
37494
- this.console.log('[LLM] Rate limited, skipping LLM call');
37507
+ // Only log once per rate limit window, not every call
37508
+ if (!silent && !this.rateLimitLogged) {
37509
+ const remaining = Math.ceil((this.config.llmDebounceInterval || 30000) - (Date.now() - this.lastLlmCallTime)) / 1000;
37510
+ this.console.log(`[LLM] Rate limited, ${remaining.toFixed(0)}s until next call allowed`);
37511
+ this.rateLimitLogged = true;
37512
+ }
37495
37513
  return false;
37496
37514
  }
37515
+ this.rateLimitLogged = false;
37497
37516
  this.recordLlmCall();
37498
37517
  return true;
37499
37518
  }
37519
+ /** Track if we've already logged rate limit message */
37520
+ rateLimitLogged = false;
37500
37521
  /** Get spatial reasoning result for movement (uses RAG + LLM) with debouncing and fallback */
37501
37522
  async getSpatialDescription(tracked, fromCameraId, toCameraId, transitTime, currentCameraId) {
37502
37523
  const fallbackEnabled = this.config.llmFallbackEnabled ?? true;
@@ -37706,31 +37727,31 @@ class TrackingEngine {
37706
37727
  }
37707
37728
  /** Capture and cache a snapshot for a tracked object, and start LLM analysis immediately */
37708
37729
  async captureAndCacheSnapshot(globalId, cameraId, eventType = 'entry') {
37730
+ // Skip if we already have a recent snapshot for this object (within 5 seconds)
37731
+ const existingSnapshot = this.snapshotCache.get(globalId);
37732
+ if (existingSnapshot && eventType !== 'exit') {
37733
+ // For entry/movement, we can reuse existing snapshot
37734
+ // For exit, we want a fresh snapshot while they're still visible
37735
+ return;
37736
+ }
37709
37737
  try {
37710
37738
  const camera = systemManager.getDeviceById(cameraId);
37711
37739
  if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
37712
37740
  const mediaObject = await camera.takePicture();
37713
37741
  if (mediaObject) {
37714
37742
  this.snapshotCache.set(globalId, mediaObject);
37715
- this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
37716
37743
  // Start LLM analysis immediately in parallel (don't await) - but respect rate limits
37717
37744
  const tracked = this.state.getObject(globalId);
37718
37745
  if (tracked && this.config.useLlmDescriptions && this.tryLlmCall()) {
37719
- this.console.log(`[LLM Prefetch] Starting ${eventType} analysis for ${globalId.slice(0, 8)}`);
37720
37746
  const descriptionPromise = eventType === 'exit'
37721
37747
  ? this.spatialReasoning.generateExitDescription(tracked, cameraId, mediaObject)
37722
37748
  : this.spatialReasoning.generateEntryDescription(tracked, cameraId, mediaObject);
37723
37749
  this.pendingDescriptions.set(globalId, descriptionPromise);
37724
- // Log when complete (for debugging)
37725
- descriptionPromise.then(result => {
37726
- this.console.log(`[LLM Prefetch] ${eventType} analysis ready for ${globalId.slice(0, 8)}: "${result.description.substring(0, 40)}..."`);
37727
- }).catch(e => {
37750
+ // Log when complete (but don't spam logs)
37751
+ descriptionPromise.catch(e => {
37728
37752
  this.console.warn(`[LLM Prefetch] Failed for ${globalId.slice(0, 8)}: ${e}`);
37729
37753
  });
37730
37754
  }
37731
- else if (tracked && this.config.useLlmDescriptions) {
37732
- this.console.log(`[LLM Prefetch] Skipped for ${globalId.slice(0, 8)} - rate limited`);
37733
- }
37734
37755
  }
37735
37756
  }
37736
37757
  }
@@ -39360,7 +39381,7 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
39360
39381
  llmDebounceInterval: {
39361
39382
  title: 'LLM Rate Limit (seconds)',
39362
39383
  type: 'number',
39363
- defaultValue: 30,
39384
+ defaultValue: 5,
39364
39385
  description: 'Minimum time between LLM calls to prevent API rate limiting. Increase if you get rate limit errors. (0 = no limit)',
39365
39386
  group: 'AI & Spatial Reasoning',
39366
39387
  },