antenna-openclaw-plugin 0.8.1 → 0.10.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 +36 -16
- package/package.json +1 -1
- package/skills/antenna/SKILL.md +16 -1
package/index.ts
CHANGED
|
@@ -278,19 +278,29 @@ export default function register(api: any) {
|
|
|
278
278
|
return ok({ nearby: [], message: `在 ${radius}m 范围内没有发现其他人。试试扩大范围?` });
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
281
|
+
// Build ref mapping — never expose device_id
|
|
282
|
+
const _refMap: Record<string, string> = {};
|
|
283
|
+
const profiles = others.map((p: Profile, i: number) => {
|
|
284
|
+
const ref = String(i + 1);
|
|
285
|
+
_refMap[ref] = p.device_id;
|
|
286
|
+
return {
|
|
287
|
+
ref,
|
|
285
288
|
emoji: p.emoji || "👤",
|
|
286
289
|
name: p.display_name || "匿名",
|
|
287
290
|
line1: p.line1,
|
|
288
291
|
line2: p.line2,
|
|
289
292
|
line3: p.line3,
|
|
290
|
-
}
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Store ref map for accept
|
|
297
|
+
(api as any)._antennaRefMap = _refMap;
|
|
298
|
+
|
|
299
|
+
return ok({
|
|
300
|
+
nearby: profiles,
|
|
291
301
|
total: others.length,
|
|
292
302
|
radius_m: radius,
|
|
293
|
-
instruction: "
|
|
303
|
+
instruction: "根据你对用户的了解,判断哪些人值得推荐,用 ref 编号引用。不要显示 device_id。",
|
|
294
304
|
});
|
|
295
305
|
},
|
|
296
306
|
});
|
|
@@ -405,24 +415,34 @@ export default function register(api: any) {
|
|
|
405
415
|
api.registerTool({
|
|
406
416
|
name: "antenna_accept",
|
|
407
417
|
description:
|
|
408
|
-
"Accept a match.
|
|
418
|
+
"Accept a match. Use 'ref' from scan results (e.g. '1', '2') or target_device_id. Optionally share contact info.",
|
|
409
419
|
parameters: {
|
|
410
420
|
type: "object",
|
|
411
421
|
properties: {
|
|
412
422
|
sender_id: { type: "string" },
|
|
413
423
|
channel: { type: "string" },
|
|
414
|
-
|
|
415
|
-
|
|
424
|
+
ref: { type: "string", description: "Ref number from scan results (e.g. '1')" },
|
|
425
|
+
target_device_id: { type: "string", description: "Device ID (use ref instead when possible)" },
|
|
426
|
+
contact_info: { type: "string", description: "Optional contact info to share" },
|
|
416
427
|
},
|
|
417
|
-
required: ["sender_id", "channel"
|
|
428
|
+
required: ["sender_id", "channel"],
|
|
418
429
|
},
|
|
419
430
|
async execute(_id: string, params: any) {
|
|
420
431
|
const cfg = getConfig(api);
|
|
421
432
|
const supabase = getSupabase(cfg);
|
|
422
433
|
const deviceId = deriveDeviceId(params.sender_id, params.channel);
|
|
423
434
|
|
|
435
|
+
// Resolve ref to device_id
|
|
436
|
+
let targetId = params.target_device_id;
|
|
437
|
+
if (params.ref && (api as any)._antennaRefMap?.[params.ref]) {
|
|
438
|
+
targetId = (api as any)._antennaRefMap[params.ref];
|
|
439
|
+
}
|
|
440
|
+
if (!targetId) {
|
|
441
|
+
return ok({ error: "No target. Use 'ref' from scan results or 'target_device_id'." });
|
|
442
|
+
}
|
|
443
|
+
|
|
424
444
|
const { error } = await supabase.rpc("upsert_match", {
|
|
425
|
-
p_device_id_a: deviceId, p_device_id_b:
|
|
445
|
+
p_device_id_a: deviceId, p_device_id_b: targetId,
|
|
426
446
|
p_status: "accepted", p_contact_info: params.contact_info ?? null,
|
|
427
447
|
});
|
|
428
448
|
|
|
@@ -430,13 +450,13 @@ export default function register(api: any) {
|
|
|
430
450
|
|
|
431
451
|
const { data: myMatches } = await supabase.rpc("get_my_matches", { p_device_id: deviceId });
|
|
432
452
|
const reverse = (myMatches || []).find(
|
|
433
|
-
(m: any) => m.device_id_a ===
|
|
453
|
+
(m: any) => m.device_id_a === targetId && m.device_id_b === deviceId
|
|
434
454
|
);
|
|
435
455
|
|
|
436
456
|
if (reverse) {
|
|
437
457
|
// Mutual match! Stop any follow-up cron for this pair
|
|
438
|
-
stopFollowUpCron(deviceId,
|
|
439
|
-
stopFollowUpCron(
|
|
458
|
+
stopFollowUpCron(deviceId, targetId, logger);
|
|
459
|
+
stopFollowUpCron(targetId, deviceId, logger);
|
|
440
460
|
|
|
441
461
|
return ok({
|
|
442
462
|
accepted: true, mutual: true,
|
|
@@ -448,11 +468,11 @@ export default function register(api: any) {
|
|
|
448
468
|
}
|
|
449
469
|
|
|
450
470
|
// Not mutual yet — start a follow-up cron (check every 15min for 2h)
|
|
451
|
-
const { data: targetProfile } = await supabase.rpc("get_profile", { p_device_id:
|
|
471
|
+
const { data: targetProfile } = await supabase.rpc("get_profile", { p_device_id: targetId });
|
|
452
472
|
const targetName = targetProfile?.display_name || "对方";
|
|
453
473
|
|
|
454
474
|
startFollowUpCron(
|
|
455
|
-
deviceId,
|
|
475
|
+
deviceId, targetId,
|
|
456
476
|
params.channel, params.sender_id, targetName, logger,
|
|
457
477
|
);
|
|
458
478
|
|
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 — 聊天式引导(不要让用户填表)
|