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 +67 -4
- package/package.json +1 -1
- package/skills/antenna/SKILL.md +7 -6
package/index.ts
CHANGED
|
@@ -331,22 +331,85 @@ export default function register(api: any) {
|
|
|
331
331
|
});
|
|
332
332
|
|
|
333
333
|
// ═══════════════════════════════════════════════════════════════════
|
|
334
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
package/skills/antenna/SKILL.md
CHANGED
|
@@ -170,10 +170,11 @@ Everything is ephemeral:
|
|
|
170
170
|
|
|
171
171
|
### Heartbeat — 自动查匹配
|
|
172
172
|
|
|
173
|
-
|
|
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
|
-
|
|
175
|
+
当你看到 `[Antenna] 🎉 有 X 个新的匹配通知` 时:
|
|
176
|
+
1. 调 `antenna_check_matches` 拿详情
|
|
177
|
+
2. 告诉用户:"有人想认识你!" + 展示对方名片
|
|
178
|
+
3. 如果对方分享了联系方式,一并展示
|
|
179
|
+
|
|
180
|
+
用户不需要主动问,agent 会自动收到通知。
|