antenna-openclaw-plugin 0.2.3 → 0.3.0

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/index.ts CHANGED
@@ -331,22 +331,85 @@ export default function register(api: any) {
331
331
  });
332
332
 
333
333
  // ═══════════════════════════════════════════════════════════════════
334
- // Hook: auto-scan when location is received
334
+ // Service: poll for new mutual matches every 10 minutes
335
+ // ═══════════════════════════════════════════════════════════════════
336
+ const _pendingNotifications: Map<string, any[]> = new Map(); // deviceId → new mutual matches
337
+
338
+ let _pollTimer: ReturnType<typeof setInterval> | null = null;
339
+
340
+ api.registerService({
341
+ id: "antenna-match-poller",
342
+ start: () => {
343
+ logger.info("Antenna: match poller started (10 min interval)");
344
+ _pollTimer = setInterval(async () => {
345
+ try {
346
+ const cfg = getConfig(api);
347
+ const supabase = getSupabase(cfg);
348
+
349
+ // Get all profiles that have been active in last 24h
350
+ const { data: activeProfiles } = await supabase
351
+ .rpc("nearby_profiles", { p_lat: 0, p_lng: 0, p_radius_m: 999999999 })
352
+ .select("device_id");
353
+
354
+ if (!activeProfiles?.length) return;
355
+
356
+ for (const profile of activeProfiles) {
357
+ const deviceId = profile.device_id;
358
+ const { data: matches } = await supabase.rpc("get_my_matches", { p_device_id: deviceId });
359
+ if (!matches?.length) continue;
360
+
361
+ // Check for matches created in last 10 min (new since last poll)
362
+ const newMatches = matches.filter((m: any) => {
363
+ const created = new Date(m.created_at).getTime();
364
+ return Date.now() - created < 10 * 60 * 1000;
365
+ });
366
+
367
+ if (newMatches.length > 0) {
368
+ _pendingNotifications.set(deviceId, newMatches);
369
+ logger.info(`Antenna: ${newMatches.length} new match(es) for ${deviceId}`);
370
+ }
371
+ }
372
+ } catch (err: any) {
373
+ logger.warn("Antenna: match poll error:", err.message);
374
+ }
375
+ }, 10 * 60 * 1000); // 10 minutes
376
+ },
377
+ stop: () => {
378
+ if (_pollTimer) clearInterval(_pollTimer);
379
+ logger.info("Antenna: match poller stopped");
380
+ },
381
+ });
382
+
383
+ // ═══════════════════════════════════════════════════════════════════
384
+ // Hook: auto-scan when location is received + inject match notifications
335
385
  // ═══════════════════════════════════════════════════════════════════
336
386
  api.on(
337
387
  "before_prompt_build",
338
388
  (event: any, ctx: any) => {
339
389
  try {
340
390
  const cfg = getConfig(api);
341
- if (cfg.autoScanOnLocation === false) return {};
391
+ let hint = "";
392
+
393
+ // --- Check for pending match notifications ---
394
+ if (ctx?.senderId && ctx?.channel) {
395
+ const deviceId = deriveDeviceId(ctx.senderId, ctx.channel);
396
+ const pending = _pendingNotifications.get(deviceId);
397
+ if (pending && pending.length > 0) {
398
+ _pendingNotifications.delete(deviceId);
399
+ hint += `\n\n[Antenna] 🎉 有 ${pending.length} 个新的匹配通知!请调用 antenna_check_matches 查看详情,并告诉用户有人想认识他们。`;
400
+ }
401
+ }
402
+
403
+ // --- Auto-scan on location ---
404
+ if (cfg.autoScanOnLocation === false) return hint ? { prependContext: hint } : {};
342
405
 
343
406
  const lat = ctx?.LocationLat;
344
407
  const lon = ctx?.LocationLon;
345
- if (lat == null || lon == null) return {};
408
+ if (lat == null || lon == null) return hint ? { prependContext: hint } : {};
346
409
 
347
410
  const isLive = ctx?.LocationIsLive ?? false;
348
411
  const locationName = ctx?.LocationName ?? "";
349
- const hint = isLive
412
+ hint += isLive
350
413
  ? `\n\n[Antenna] 📡 收到实时位置 (${lat.toFixed(4)}, ${lon.toFixed(4)})${locationName ? ` — ${locationName}` : ""}。请使用 antenna_scan 工具查看附近有谁。参数:lat=${lat}, lng=${lon}, sender_id 和 channel 从消息上下文获取。`
351
414
  : `\n\n[Antenna] 📍 收到位置 (${lat.toFixed(4)}, ${lon.toFixed(4)})${locationName ? ` — ${locationName}` : ""}。请使用 antenna_scan 工具查看附近有谁。参数:lat=${lat}, lng=${lon}, sender_id 和 channel 从消息上下文获取。`;
352
415
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-openclaw-plugin",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Antenna — agent-mediated nearby people discovery for OpenClaw",
5
5
  "openclaw": {
6
6
  "extensions": ["./index.ts"]
@@ -170,10 +170,11 @@ Everything is ephemeral:
170
170
 
171
171
  ### Heartbeat — 自动查匹配
172
172
 
173
- 如果 agent heartbeat 机制(定期唤醒),每次 heartbeat 时:
174
- 1. 检查用户是否有 Antenna profile(`antenna_profile` action="get")
175
- 2. 如果有,调 `antenna_check_matches` 查看是否有新的 mutual match
176
- 3. 如果有新匹配,主动通知用户:"有人想认识你!"并展示对方信息
177
- 4. 如果没有新匹配,不用说话(静默跳过)
173
+ Plugin 自带后台服务,每 10 分钟轮询一次 Supabase 查新的 mutual match。如果发现新匹配,会在用户下次跟 agent 说话时通过 `[Antenna] 🎉` 提示注入 context。
178
174
 
179
- 这样用户不需要主动问,agent 会自动帮忙盯着。
175
+ 当你看到 `[Antenna] 🎉 有 X 个新的匹配通知` 时:
176
+ 1. 调 `antenna_check_matches` 拿详情
177
+ 2. 告诉用户:"有人想认识你!" + 展示对方名片
178
+ 3. 如果对方分享了联系方式,一并展示
179
+
180
+ 用户不需要主动问,agent 会自动收到通知。