@blueharford/scrypted-spatial-awareness 0.6.13 → 0.6.14

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
@@ -36946,6 +36946,8 @@ class TrackingEngine {
36946
36946
  // ==================== Snapshot Cache ====================
36947
36947
  /** Cached snapshots for tracked objects (for faster notifications) */
36948
36948
  snapshotCache = new Map();
36949
+ /** Pending LLM description promises (started when snapshot is captured) */
36950
+ pendingDescriptions = new Map();
36949
36951
  constructor(topology, state, alertManager, config, console) {
36950
36952
  this.topology = topology;
36951
36953
  this.state = state;
@@ -37256,13 +37258,28 @@ class TrackingEngine {
37256
37258
  // Check if we've already alerted for this object
37257
37259
  if (this.isInAlertCooldown(globalId))
37258
37260
  return;
37259
- // Use cached snapshot (captured when object was first detected)
37260
- let mediaObject = this.snapshotCache.get(globalId);
37261
- this.console.log(`[Entry Alert] Using cached snapshot: ${!!mediaObject}`);
37262
- // Generate spatial description (now async with LLM support)
37263
- this.console.log(`[Entry Alert] Calling generateEntryDescription with mediaObject=${!!mediaObject}`);
37264
- const spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
37265
- this.console.log(`[Entry Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
37261
+ // Use prefetched LLM result if available (started when snapshot was captured)
37262
+ let spatialResult;
37263
+ const pendingDescription = this.pendingDescriptions.get(globalId);
37264
+ if (pendingDescription) {
37265
+ this.console.log(`[Entry Alert] Using prefetched LLM result for ${globalId.slice(0, 8)}`);
37266
+ try {
37267
+ spatialResult = await pendingDescription;
37268
+ this.console.log(`[Entry Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
37269
+ }
37270
+ catch (e) {
37271
+ this.console.warn(`[Entry Alert] Prefetch failed, generating fallback: ${e}`);
37272
+ spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId);
37273
+ }
37274
+ this.pendingDescriptions.delete(globalId);
37275
+ }
37276
+ else {
37277
+ // Fallback: generate description now (slower path)
37278
+ this.console.log(`[Entry Alert] No prefetch available, generating now`);
37279
+ const mediaObject = this.snapshotCache.get(globalId);
37280
+ spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
37281
+ this.console.log(`[Entry Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
37282
+ }
37266
37283
  if (isEntryPoint) {
37267
37284
  // Entry point - generate property entry alert
37268
37285
  await this.alertManager.checkAndAlert('property_entry', tracked, {
@@ -37293,8 +37310,8 @@ class TrackingEngine {
37293
37310
  this.recordAlertTime(globalId);
37294
37311
  }, this.config.loiteringThreshold);
37295
37312
  }
37296
- /** Capture and cache a snapshot for a tracked object */
37297
- async captureAndCacheSnapshot(globalId, cameraId) {
37313
+ /** Capture and cache a snapshot for a tracked object, and start LLM analysis immediately */
37314
+ async captureAndCacheSnapshot(globalId, cameraId, eventType = 'entry') {
37298
37315
  try {
37299
37316
  const camera = systemManager.getDeviceById(cameraId);
37300
37317
  if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
@@ -37302,6 +37319,21 @@ class TrackingEngine {
37302
37319
  if (mediaObject) {
37303
37320
  this.snapshotCache.set(globalId, mediaObject);
37304
37321
  this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
37322
+ // Start LLM analysis immediately in parallel (don't await)
37323
+ const tracked = this.state.getObject(globalId);
37324
+ if (tracked && this.config.useLlmDescriptions) {
37325
+ this.console.log(`[LLM Prefetch] Starting ${eventType} analysis for ${globalId.slice(0, 8)}`);
37326
+ const descriptionPromise = eventType === 'exit'
37327
+ ? this.spatialReasoning.generateExitDescription(tracked, cameraId, mediaObject)
37328
+ : this.spatialReasoning.generateEntryDescription(tracked, cameraId, mediaObject);
37329
+ this.pendingDescriptions.set(globalId, descriptionPromise);
37330
+ // Log when complete (for debugging)
37331
+ descriptionPromise.then(result => {
37332
+ this.console.log(`[LLM Prefetch] ${eventType} analysis ready for ${globalId.slice(0, 8)}: "${result.description.substring(0, 40)}..."`);
37333
+ }).catch(e => {
37334
+ this.console.warn(`[LLM Prefetch] Failed for ${globalId.slice(0, 8)}: ${e}`);
37335
+ });
37336
+ }
37305
37337
  }
37306
37338
  }
37307
37339
  }
@@ -37349,8 +37381,9 @@ class TrackingEngine {
37349
37381
  // Mark as pending and set timer
37350
37382
  this.state.markPending(tracked.globalId);
37351
37383
  // Capture a fresh snapshot now while object is still visible (before they leave)
37384
+ // Also starts LLM analysis immediately in parallel
37352
37385
  if (this.config.useLlmDescriptions) {
37353
- this.captureAndCacheSnapshot(tracked.globalId, sighting.cameraId).catch(e => {
37386
+ this.captureAndCacheSnapshot(tracked.globalId, sighting.cameraId, 'exit').catch(e => {
37354
37387
  this.console.warn(`[Exit Snapshot] Failed to update snapshot: ${e}`);
37355
37388
  });
37356
37389
  }
@@ -37359,13 +37392,28 @@ class TrackingEngine {
37359
37392
  const current = this.state.getObject(tracked.globalId);
37360
37393
  if (current && current.state === 'pending') {
37361
37394
  this.state.markExited(tracked.globalId, sighting.cameraId, sighting.cameraName);
37362
- // Use cached snapshot (captured when exit was first detected, while object was still visible)
37363
- let mediaObject = this.snapshotCache.get(tracked.globalId);
37364
- this.console.log(`[Exit Alert] Using cached snapshot: ${!!mediaObject}`);
37365
- // Generate rich exit description using topology context (now async with LLM support)
37366
- this.console.log(`[Exit Alert] Calling generateExitDescription with mediaObject=${!!mediaObject}`);
37367
- const spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
37368
- this.console.log(`[Exit Alert] Object ${tracked.globalId.slice(0, 8)} exited: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
37395
+ // Use prefetched LLM result if available (started when exit was first detected)
37396
+ let spatialResult;
37397
+ const pendingDescription = this.pendingDescriptions.get(tracked.globalId);
37398
+ if (pendingDescription) {
37399
+ this.console.log(`[Exit Alert] Using prefetched LLM result for ${tracked.globalId.slice(0, 8)}`);
37400
+ try {
37401
+ spatialResult = await pendingDescription;
37402
+ this.console.log(`[Exit Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
37403
+ }
37404
+ catch (e) {
37405
+ this.console.warn(`[Exit Alert] Prefetch failed, generating fallback: ${e}`);
37406
+ spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId);
37407
+ }
37408
+ this.pendingDescriptions.delete(tracked.globalId);
37409
+ }
37410
+ else {
37411
+ // Fallback: generate description now (slower path)
37412
+ this.console.log(`[Exit Alert] No prefetch available, generating now`);
37413
+ const mediaObject = this.snapshotCache.get(tracked.globalId);
37414
+ spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
37415
+ this.console.log(`[Exit Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
37416
+ }
37369
37417
  await this.alertManager.checkAndAlert('property_exit', current, {
37370
37418
  cameraId: sighting.cameraId,
37371
37419
  cameraName: sighting.cameraName,
@@ -37374,8 +37422,9 @@ class TrackingEngine {
37374
37422
  involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
37375
37423
  usedLlm: spatialResult.usedLlm,
37376
37424
  });
37377
- // Clean up cached snapshot after exit alert
37425
+ // Clean up cached snapshot and pending descriptions after exit alert
37378
37426
  this.snapshotCache.delete(tracked.globalId);
37427
+ this.pendingDescriptions.delete(tracked.globalId);
37379
37428
  }
37380
37429
  this.pendingTimers.delete(tracked.globalId);
37381
37430
  }, this.config.correlationWindow);
@@ -37391,8 +37440,9 @@ class TrackingEngine {
37391
37440
  this.state.markLost(tracked.globalId);
37392
37441
  this.console.log(`Object ${tracked.globalId.slice(0, 8)} marked as lost ` +
37393
37442
  `(not seen for ${Math.round(timeSinceSeen / 1000)}s)`);
37394
- // Clean up cached snapshot
37443
+ // Clean up cached snapshot and pending descriptions
37395
37444
  this.snapshotCache.delete(tracked.globalId);
37445
+ this.pendingDescriptions.delete(tracked.globalId);
37396
37446
  this.alertManager.checkAndAlert('lost_tracking', tracked, {
37397
37447
  objectClass: tracked.className,
37398
37448
  objectLabel: tracked.label,