antenna-openclaw-plugin 0.9.0 → 0.11.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 +85 -6
- package/package.json +1 -1
- package/skills/antenna/SKILL.md +16 -1
package/index.ts
CHANGED
|
@@ -275,7 +275,29 @@ export default function register(api: any) {
|
|
|
275
275
|
const others = (nearby ?? []).filter((p: Profile) => p.device_id !== deviceId);
|
|
276
276
|
|
|
277
277
|
if (others.length === 0) {
|
|
278
|
-
|
|
278
|
+
// Fallback to global discover
|
|
279
|
+
const { data: globalData } = await supabase.rpc("global_discover", {
|
|
280
|
+
p_device_id: deviceId, p_limit: 1,
|
|
281
|
+
});
|
|
282
|
+
const globalOthers = globalData || [];
|
|
283
|
+
if (globalOthers.length > 0) {
|
|
284
|
+
const gRefMap: Record<string, string> = {};
|
|
285
|
+
const gProfiles = globalOthers.map((p: any, i: number) => {
|
|
286
|
+
const ref = String(i + 1);
|
|
287
|
+
gRefMap[ref] = p.device_id;
|
|
288
|
+
return { ref, emoji: p.emoji || "👤", name: p.display_name || "匿名", line1: p.line1, line2: p.line2, line3: p.line3 };
|
|
289
|
+
});
|
|
290
|
+
(api as any)._antennaRefMap = gRefMap;
|
|
291
|
+
try { await supabase.rpc("save_scan_refs", { p_owner: deviceId, p_refs: JSON.stringify(gRefMap) }); } catch {}
|
|
292
|
+
for (const p of globalOthers) {
|
|
293
|
+
try { await supabase.rpc("log_recommendation", { p_device_id: deviceId, p_recommended_id: p.device_id }); } catch {}
|
|
294
|
+
}
|
|
295
|
+
return ok({
|
|
296
|
+
nearby: gProfiles, total: gProfiles.length, radius_m: radius, global: true,
|
|
297
|
+
message: `附近 ${radius}m 暂时没人。今天的全球推荐——这个人跟你可能聊得来。(每天 1 次)`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return ok({ nearby: [], message: `附近暂时没人,今天的全球推荐已经用完了。明天再来!` });
|
|
279
301
|
}
|
|
280
302
|
|
|
281
303
|
// Build ref mapping — never expose device_id
|
|
@@ -293,8 +315,11 @@ export default function register(api: any) {
|
|
|
293
315
|
};
|
|
294
316
|
});
|
|
295
317
|
|
|
296
|
-
// Store ref map for accept
|
|
318
|
+
// Store ref map for accept — memory + DB
|
|
297
319
|
(api as any)._antennaRefMap = _refMap;
|
|
320
|
+
try {
|
|
321
|
+
await supabase.rpc("save_scan_refs", { p_owner: deviceId, p_refs: JSON.stringify(_refMap) });
|
|
322
|
+
} catch { /* best effort */ }
|
|
298
323
|
|
|
299
324
|
return ok({
|
|
300
325
|
nearby: profiles,
|
|
@@ -432,13 +457,15 @@ export default function register(api: any) {
|
|
|
432
457
|
const supabase = getSupabase(cfg);
|
|
433
458
|
const deviceId = deriveDeviceId(params.sender_id, params.channel);
|
|
434
459
|
|
|
435
|
-
// Resolve ref to device_id
|
|
460
|
+
// Resolve ref to device_id — try DB first, then memory fallback
|
|
436
461
|
let targetId = params.target_device_id;
|
|
437
|
-
if (
|
|
438
|
-
|
|
462
|
+
if (!targetId && params.ref) {
|
|
463
|
+
// Try DB
|
|
464
|
+
const { data: resolved } = await supabase.rpc("resolve_ref", { p_owner: deviceId, p_ref: params.ref });
|
|
465
|
+
targetId = resolved || (api as any)._antennaRefMap?.[params.ref];
|
|
439
466
|
}
|
|
440
467
|
if (!targetId) {
|
|
441
|
-
return ok({ error: "No target.
|
|
468
|
+
return ok({ error: "No target. Ref may have expired — try scanning again." });
|
|
442
469
|
}
|
|
443
470
|
|
|
444
471
|
const { error } = await supabase.rpc("upsert_match", {
|
|
@@ -516,6 +543,58 @@ export default function register(api: any) {
|
|
|
516
543
|
},
|
|
517
544
|
});
|
|
518
545
|
|
|
546
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
547
|
+
// Tool: antenna_discover
|
|
548
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
549
|
+
api.registerTool({
|
|
550
|
+
name: "antenna_discover",
|
|
551
|
+
description:
|
|
552
|
+
"Get today's global recommendation — the person most similar to you worldwide. 1 per day, no repeats.",
|
|
553
|
+
parameters: {
|
|
554
|
+
type: "object",
|
|
555
|
+
properties: {
|
|
556
|
+
sender_id: { type: "string", description: "The sender's user ID" },
|
|
557
|
+
channel: { type: "string", description: "The channel name" },
|
|
558
|
+
},
|
|
559
|
+
required: ["sender_id", "channel"],
|
|
560
|
+
},
|
|
561
|
+
async execute(_id: string, params: any) {
|
|
562
|
+
const cfg = getConfig(api);
|
|
563
|
+
const supabase = getSupabase(cfg);
|
|
564
|
+
const deviceId = deriveDeviceId(params.sender_id, params.channel);
|
|
565
|
+
|
|
566
|
+
const { data: globalData } = await supabase.rpc("global_discover", {
|
|
567
|
+
p_device_id: deviceId, p_limit: 1,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const results = globalData || [];
|
|
571
|
+
if (results.length === 0) {
|
|
572
|
+
return ok({ count: 0, profiles: [], message: "今天的全球推荐已用完,或者你已经看过所有人了。等新人加入!" });
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const _refMap: Record<string, string> = {};
|
|
576
|
+
const profiles = results.map((p: any, i: number) => {
|
|
577
|
+
const ref = String(i + 1);
|
|
578
|
+
_refMap[ref] = p.device_id;
|
|
579
|
+
return { ref, emoji: p.emoji || "👤", name: p.display_name || "匿名", line1: p.line1, line2: p.line2, line3: p.line3 };
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// Persist refs + log recommendation
|
|
583
|
+
(api as any)._antennaRefMap = { ...(api as any)._antennaRefMap, ..._refMap };
|
|
584
|
+
try {
|
|
585
|
+
await supabase.rpc("save_scan_refs", { p_owner: deviceId, p_refs: JSON.stringify(_refMap) });
|
|
586
|
+
} catch { /* best effort */ }
|
|
587
|
+
for (const p of results) {
|
|
588
|
+
await supabase.rpc("log_recommendation", { p_device_id: deviceId, p_recommended_id: p.device_id });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return ok({
|
|
592
|
+
count: profiles.length, profiles, global: true,
|
|
593
|
+
message: "🌍 今天的全球推荐——这个人跟你可能聊得来。",
|
|
594
|
+
});
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
|
|
519
598
|
// ═══════════════════════════════════════════════════════════════════
|
|
520
599
|
// Tool: antenna_check_matches
|
|
521
600
|
// ═══════════════════════════════════════════════════════════════════
|
package/package.json
CHANGED
package/skills/antenna/SKILL.md
CHANGED
|
@@ -43,9 +43,17 @@ onboarding 完成后,帮用户设置一个每小时自动检查匹配的 cron
|
|
|
43
43
|
|
|
44
44
|
示例(Hermes):
|
|
45
45
|
```
|
|
46
|
-
hermes cron add --every 1h --message "Check antenna matches: call antenna_check_matches. If there are new mutual matches or incoming accepts, tell the user. If nothing new, reply HEARTBEAT_OK."
|
|
46
|
+
hermes cron add --every 1h --message "Check antenna matches: call antenna_check_matches. If there are new mutual matches or incoming accepts, tell the user. Also call antenna_discover once per day to get a global recommendation. If nothing new, reply HEARTBEAT_OK."
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
**第五步:问用户要不要全球推荐**
|
|
50
|
+
|
|
51
|
+
cron 设完后,问用户一句:
|
|
52
|
+
> "要我每天帮你找一个全球最像你的人吗?"
|
|
53
|
+
|
|
54
|
+
用户说要 → cron 每天调 `antenna_discover` 推送。
|
|
55
|
+
用户说不要 → 只在 scan 没人时 fallback。
|
|
56
|
+
|
|
49
57
|
## When to use
|
|
50
58
|
|
|
51
59
|
- **首次安装后**: 主动开始 onboarding(名片 → 位置)
|
|
@@ -101,6 +109,13 @@ Generate a GPS binding link. **You MUST call this immediately after saving a pro
|
|
|
101
109
|
- Send this link to the user — they open it on their phone, allow GPS, and their location is automatically shared
|
|
102
110
|
- **MANDATORY after profile save. Do not wait for user to ask.**
|
|
103
111
|
|
|
112
|
+
### `antenna_discover`
|
|
113
|
+
Get today's global recommendation — the person most similar to you worldwide. 1 per day, no repeats.
|
|
114
|
+
- `sender_id`, `channel`: from context
|
|
115
|
+
- Returns 1 profile (embedding similarity match) that hasn't been recommended before
|
|
116
|
+
- If all users have been recommended, returns a message saying "wait for new people"
|
|
117
|
+
- Use this in the daily cron job, or when user asks "find someone interesting globally"
|
|
118
|
+
|
|
104
119
|
## Behavior guidelines
|
|
105
120
|
|
|
106
121
|
### First-time user — 聊天式引导(不要让用户填表)
|