antenna-openclaw-plugin 1.3.0 → 1.3.2
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 +53 -67
- package/package.json +1 -1
- package/skills/antenna/SKILL.md +53 -20
- package/skills/antenna/EVENTS.md +0 -112
package/index.ts
CHANGED
|
@@ -268,7 +268,7 @@ export default function register(api: any) {
|
|
|
268
268
|
channel: { type: "string", description: "The channel name" },
|
|
269
269
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
270
270
|
},
|
|
271
|
-
required: ["sender_id", "channel"],
|
|
271
|
+
required: ["sender_id", "channel", "chat_id"],
|
|
272
272
|
},
|
|
273
273
|
async execute(_id: string, params: any) {
|
|
274
274
|
const cfg = getConfig(api);
|
|
@@ -277,7 +277,7 @@ export default function register(api: any) {
|
|
|
277
277
|
const radius = params.radius_m ?? cfg.defaultRadiusM ?? 500;
|
|
278
278
|
|
|
279
279
|
if (isRateLimited(deviceId)) {
|
|
280
|
-
return ok({
|
|
280
|
+
return ok({ profiles: [], message: "刚刚才扫描过,稍等一会儿再试。", rate_limited: true });
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
let lat = params.lat;
|
|
@@ -290,7 +290,7 @@ export default function register(api: any) {
|
|
|
290
290
|
lat = loc.lat;
|
|
291
291
|
lng = loc.lng;
|
|
292
292
|
} else {
|
|
293
|
-
return ok({
|
|
293
|
+
return ok({ profiles: [], message: "还没有位置信息。请先通过链接分享位置,或者发送位置消息。" });
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -323,11 +323,11 @@ export default function register(api: any) {
|
|
|
323
323
|
try { await supabase.rpc("log_recommendation", { p_device_id: deviceId, p_recommended_id: p.device_id }); } catch {}
|
|
324
324
|
}
|
|
325
325
|
return ok({
|
|
326
|
-
|
|
326
|
+
profiles: gProfiles, count: gProfiles.length, radius_m: radius, global: true,
|
|
327
327
|
message: `附近 ${radius}m 暂时没人。今天的全球推荐——这个人跟你可能聊得来。(每天 1 次)`,
|
|
328
328
|
});
|
|
329
329
|
}
|
|
330
|
-
return ok({
|
|
330
|
+
return ok({ profiles: [], message: `附近暂时没人,今天的全球推荐已经用完了。明天再来!` });
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
// Build ref mapping — never expose device_id
|
|
@@ -353,8 +353,8 @@ export default function register(api: any) {
|
|
|
353
353
|
} catch { /* best effort */ }
|
|
354
354
|
|
|
355
355
|
return ok({
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
profiles: profiles,
|
|
357
|
+
count: others.length,
|
|
358
358
|
radius_m: radius,
|
|
359
359
|
instruction: "根据你对用户的了解,判断哪些人值得推荐,用 ref 编号引用。不要显示 device_id。",
|
|
360
360
|
});
|
|
@@ -381,8 +381,9 @@ export default function register(api: any) {
|
|
|
381
381
|
line2: { type: "string", description: "Second line (what you're into)" },
|
|
382
382
|
line3: { type: "string", description: "Third line (what you're looking for)" },
|
|
383
383
|
visible: { type: "boolean", description: "Whether to be visible to others" },
|
|
384
|
+
matching_context: { type: "string", description: "Free-form context for AI matching (interests, goals, etc.)" },
|
|
384
385
|
},
|
|
385
|
-
required: ["action", "sender_id", "channel"],
|
|
386
|
+
required: ["action", "sender_id", "channel", "chat_id"],
|
|
386
387
|
},
|
|
387
388
|
async execute(_id: string, params: any) {
|
|
388
389
|
const cfg = getConfig(api);
|
|
@@ -406,6 +407,7 @@ export default function register(api: any) {
|
|
|
406
407
|
p_display_name: params.display_name ?? null, p_emoji: params.emoji ?? null,
|
|
407
408
|
p_line1: params.line1 ?? null, p_line2: params.line2 ?? null,
|
|
408
409
|
p_line3: params.line3 ?? null, p_visible: params.visible ?? true,
|
|
410
|
+
...(params.matching_context != null ? { p_matching_context: params.matching_context } : {}),
|
|
409
411
|
});
|
|
410
412
|
|
|
411
413
|
if (error) return ok({ error: error.message });
|
|
@@ -436,7 +438,7 @@ export default function register(api: any) {
|
|
|
436
438
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
437
439
|
place_name: { type: "string", description: "Optional: name of the place (for confirmation message)" },
|
|
438
440
|
},
|
|
439
|
-
required: ["lat", "lng", "sender_id", "channel"],
|
|
441
|
+
required: ["lat", "lng", "sender_id", "channel", "chat_id"],
|
|
440
442
|
},
|
|
441
443
|
async execute(_id: string, params: any) {
|
|
442
444
|
const cfg = getConfig(api);
|
|
@@ -484,7 +486,7 @@ export default function register(api: any) {
|
|
|
484
486
|
target_device_id: { type: "string", description: "Device ID (use ref instead when possible)" },
|
|
485
487
|
contact_info: { type: "string", description: "Optional contact info to share" },
|
|
486
488
|
},
|
|
487
|
-
required: ["sender_id", "channel"],
|
|
489
|
+
required: ["sender_id", "channel", "chat_id"],
|
|
488
490
|
},
|
|
489
491
|
async execute(_id: string, params: any) {
|
|
490
492
|
const cfg = getConfig(api);
|
|
@@ -560,7 +562,7 @@ export default function register(api: any) {
|
|
|
560
562
|
purpose: { type: "string", description: "'profile' (default) or 'event'" },
|
|
561
563
|
event_code: { type: "string", description: "Event code (required when purpose=event)" },
|
|
562
564
|
},
|
|
563
|
-
required: ["sender_id", "channel"],
|
|
565
|
+
required: ["sender_id", "channel", "chat_id"],
|
|
564
566
|
},
|
|
565
567
|
async execute(_id: string, params: any) {
|
|
566
568
|
const cfg = getConfig(api);
|
|
@@ -601,7 +603,7 @@ export default function register(api: any) {
|
|
|
601
603
|
channel: { type: "string", description: "The channel name" },
|
|
602
604
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
603
605
|
},
|
|
604
|
-
required: ["sender_id", "channel"],
|
|
606
|
+
required: ["sender_id", "channel", "chat_id"],
|
|
605
607
|
},
|
|
606
608
|
async execute(_id: string, params: any) {
|
|
607
609
|
const cfg = getConfig(api);
|
|
@@ -690,7 +692,7 @@ export default function register(api: any) {
|
|
|
690
692
|
requires_approval: { type: "boolean", description: "Require host approval to join (default false)" },
|
|
691
693
|
screening_questions: { type: "array", items: { type: "string" }, description: "Screening questions for applicants" },
|
|
692
694
|
},
|
|
693
|
-
required: ["name", "sender_id", "channel", "starts_at", "ends_at"],
|
|
695
|
+
required: ["name", "sender_id", "channel", "starts_at", "ends_at", "chat_id"],
|
|
694
696
|
},
|
|
695
697
|
async execute(_id: string, params: any) {
|
|
696
698
|
const cfg = getConfig(api);
|
|
@@ -727,7 +729,7 @@ export default function register(api: any) {
|
|
|
727
729
|
channel: { type: "string" },
|
|
728
730
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
729
731
|
},
|
|
730
|
-
required: ["code", "sender_id", "channel"],
|
|
732
|
+
required: ["code", "sender_id", "channel", "chat_id"],
|
|
731
733
|
},
|
|
732
734
|
async execute(_id: string, params: any) {
|
|
733
735
|
const cfg = getConfig(api);
|
|
@@ -759,7 +761,7 @@ export default function register(api: any) {
|
|
|
759
761
|
lng: { type: "number", description: "Longitude (optional, for auto-checkin)" },
|
|
760
762
|
application_context: { type: "string", description: "Application context from screening conversation" },
|
|
761
763
|
},
|
|
762
|
-
required: ["code", "sender_id", "channel"],
|
|
764
|
+
required: ["code", "sender_id", "channel", "chat_id"],
|
|
763
765
|
},
|
|
764
766
|
async execute(_id: string, params: any) {
|
|
765
767
|
const cfg = getConfig(api);
|
|
@@ -783,7 +785,7 @@ export default function register(api: any) {
|
|
|
783
785
|
} catch {}
|
|
784
786
|
}
|
|
785
787
|
|
|
786
|
-
const { data, error } = await supabase.rpc("join_event", { p_code: params.code, p_device_id: deviceId, p_application_context: params.application_context || null });
|
|
788
|
+
const { data, error } = await supabase.rpc("join_event", { p_code: params.code, p_device_id: deviceId, p_lat: lat || null, p_lng: lng || null, p_application_context: params.application_context || null });
|
|
787
789
|
if (error) return ok({ error: error.message });
|
|
788
790
|
if (!data?.joined) return ok(data);
|
|
789
791
|
|
|
@@ -841,7 +843,7 @@ export default function register(api: any) {
|
|
|
841
843
|
channel: { type: "string" },
|
|
842
844
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
843
845
|
},
|
|
844
|
-
required: ["code", "sender_id", "channel"],
|
|
846
|
+
required: ["code", "sender_id", "channel", "chat_id"],
|
|
845
847
|
},
|
|
846
848
|
async execute(_id: string, params: any) {
|
|
847
849
|
const cfg = getConfig(api);
|
|
@@ -881,7 +883,7 @@ export default function register(api: any) {
|
|
|
881
883
|
ref: { type: "string", description: "Ref number from scan/discover results" },
|
|
882
884
|
target_device_id: { type: "string", description: "Device ID (use ref instead when possible)" },
|
|
883
885
|
},
|
|
884
|
-
required: ["sender_id", "channel"],
|
|
886
|
+
required: ["sender_id", "channel", "chat_id"],
|
|
885
887
|
},
|
|
886
888
|
async execute(_id: string, params: any) {
|
|
887
889
|
const cfg = getConfig(api);
|
|
@@ -918,7 +920,7 @@ export default function register(api: any) {
|
|
|
918
920
|
lat: { type: "number", description: "Latitude (optional)" },
|
|
919
921
|
lng: { type: "number", description: "Longitude (optional)" },
|
|
920
922
|
},
|
|
921
|
-
required: ["code", "sender_id", "channel"],
|
|
923
|
+
required: ["code", "sender_id", "channel", "chat_id"],
|
|
922
924
|
},
|
|
923
925
|
async execute(_id: string, params: any) {
|
|
924
926
|
const cfg = getConfig(api);
|
|
@@ -977,60 +979,44 @@ export default function register(api: any) {
|
|
|
977
979
|
channel: { type: "string" },
|
|
978
980
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
979
981
|
},
|
|
980
|
-
required: ["sender_id", "channel"],
|
|
982
|
+
required: ["sender_id", "channel", "chat_id"],
|
|
981
983
|
},
|
|
982
984
|
async execute(_id: string, params: any) {
|
|
983
985
|
const cfg = getConfig(api);
|
|
984
986
|
const supabase = getSupabase(cfg);
|
|
985
987
|
const deviceId = deriveDeviceId(params.sender_id, params.channel, params.chat_id);
|
|
986
988
|
|
|
987
|
-
const { data:
|
|
989
|
+
const { data: result } = await supabase.rpc("get_my_matches_with_profiles", { p_device_id: deviceId });
|
|
988
990
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
991
|
+
const rawMutual = result?.mutual_matches || [];
|
|
992
|
+
const rawIncoming = result?.incoming_accepts || [];
|
|
992
993
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
// Matches where someone else accepted me
|
|
996
|
-
const incomingMatches = allMatches.filter((m: any) => m.device_id_b === deviceId);
|
|
997
|
-
|
|
998
|
-
// --- Mutual matches (both sides accepted) ---
|
|
999
|
-
const mutualMatches = [];
|
|
1000
|
-
for (const match of myMatches) {
|
|
1001
|
-
const reverse = incomingMatches.find(
|
|
1002
|
-
(m: any) => m.device_id_a === match.device_id_b
|
|
1003
|
-
);
|
|
1004
|
-
if (reverse) {
|
|
1005
|
-
// Clean up follow-up crons for this mutual pair
|
|
1006
|
-
stopFollowUpCron(deviceId, match.device_id_b, logger);
|
|
1007
|
-
stopFollowUpCron(match.device_id_b, deviceId, logger);
|
|
1008
|
-
|
|
1009
|
-
const { data: profile } = await supabase.rpc("get_profile", { p_device_id: match.device_id_b });
|
|
1010
|
-
mutualMatches.push({
|
|
1011
|
-
device_id: match.device_id_b,
|
|
1012
|
-
name: profile?.display_name || "匿名", emoji: profile?.emoji || "👤",
|
|
1013
|
-
line1: profile?.line1, line2: profile?.line2, line3: profile?.line3,
|
|
1014
|
-
their_contact: reverse.contact_info_a || null, you_shared: match.contact_info_a || null,
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
994
|
+
if (!rawMutual.length && !rawIncoming.length) {
|
|
995
|
+
return ok({ mutual_matches: [], incoming_accepts: [], message: "目前没有进行中的匹配。" });
|
|
1017
996
|
}
|
|
1018
997
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
998
|
+
const mutualMatches = rawMutual.map((m: any, i: number) => ({
|
|
999
|
+
ref: String(i + 1),
|
|
1000
|
+
_device_id: m.target_id,
|
|
1001
|
+
name: m.name || "匿名",
|
|
1002
|
+
emoji: m.emoji || "👤",
|
|
1003
|
+
line1: m.line1, line2: m.line2, line3: m.line3,
|
|
1004
|
+
their_contact: m.their_contact || null,
|
|
1005
|
+
you_shared: m.you_shared || null,
|
|
1006
|
+
}));
|
|
1007
|
+
|
|
1008
|
+
const incomingAccepts = rawIncoming.map((m: any, i: number) => ({
|
|
1009
|
+
ref: String(i + 1),
|
|
1010
|
+
_device_id: m.target_id,
|
|
1011
|
+
name: m.name || "匿名",
|
|
1012
|
+
emoji: m.emoji || "👤",
|
|
1013
|
+
line1: m.line1, line2: m.line2, line3: m.line3,
|
|
1014
|
+
}));
|
|
1015
|
+
|
|
1016
|
+
// Clean up follow-up crons for mutual matches
|
|
1017
|
+
for (const m of mutualMatches) {
|
|
1018
|
+
stopFollowUpCron(deviceId, m._device_id, logger);
|
|
1019
|
+
stopFollowUpCron(m._device_id, deviceId, logger);
|
|
1034
1020
|
}
|
|
1035
1021
|
|
|
1036
1022
|
const messages = [];
|
|
@@ -1067,7 +1053,7 @@ export default function register(api: any) {
|
|
|
1067
1053
|
starts_at: { type: "string", description: "New start time ISO" },
|
|
1068
1054
|
ends_at: { type: "string", description: "New end time ISO" },
|
|
1069
1055
|
},
|
|
1070
|
-
required: ["code", "sender_id", "channel"],
|
|
1056
|
+
required: ["code", "sender_id", "channel", "chat_id"],
|
|
1071
1057
|
},
|
|
1072
1058
|
async execute(_id: string, params: any) {
|
|
1073
1059
|
const cfg = getConfig(api);
|
|
@@ -1099,7 +1085,7 @@ export default function register(api: any) {
|
|
|
1099
1085
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
1100
1086
|
ref: { type: "string", description: "Ref number of the participant to approve" },
|
|
1101
1087
|
},
|
|
1102
|
-
required: ["code", "sender_id", "channel", "ref"],
|
|
1088
|
+
required: ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
1103
1089
|
},
|
|
1104
1090
|
async execute(_id: string, params: any) {
|
|
1105
1091
|
const cfg = getConfig(api);
|
|
@@ -1128,7 +1114,7 @@ export default function register(api: any) {
|
|
|
1128
1114
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
1129
1115
|
ref: { type: "string", description: "Ref number of the participant to reject" },
|
|
1130
1116
|
},
|
|
1131
|
-
required: ["code", "sender_id", "channel", "ref"],
|
|
1117
|
+
required: ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
1132
1118
|
},
|
|
1133
1119
|
async execute(_id: string, params: any) {
|
|
1134
1120
|
const cfg = getConfig(api);
|
|
@@ -1157,7 +1143,7 @@ export default function register(api: any) {
|
|
|
1157
1143
|
chat_id: { type: "string", description: "REQUIRED for notifications. Pass the chat/channel ID from your message context so Antenna can send you match and event notifications." },
|
|
1158
1144
|
ref: { type: "string", description: "Ref number of the participant to make co-host" },
|
|
1159
1145
|
},
|
|
1160
|
-
required: ["code", "sender_id", "channel", "ref"],
|
|
1146
|
+
required: ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
1161
1147
|
},
|
|
1162
1148
|
async execute(_id: string, params: any) {
|
|
1163
1149
|
const cfg = getConfig(api);
|
package/package.json
CHANGED
package/skills/antenna/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ You have access to Antenna for location-based social discovery.
|
|
|
10
10
|
|
|
11
11
|
**How to call Antenna:**
|
|
12
12
|
- If you have Antenna plugin tools registered (antenna_scan, antenna_profile, etc.) — use them directly.
|
|
13
|
-
- If not, use CLI: `antenna <command> [flags]`. Example: `antenna scan --id
|
|
13
|
+
- If not, use CLI: `antenna <command> [flags]`. Example: `antenna scan --id <platform>:<user_id>`. The CLI supports all the same features.
|
|
14
14
|
- All tool names map to CLI commands: `antenna_scan` → `antenna scan`, `antenna_event_create` → `antenna event --create`, etc.
|
|
15
15
|
- **Always pass `chat_id`** when calling any Antenna tool. This is the chat/channel ID from your message context (e.g. Discord channel ID, Telegram chat ID). Antenna uses it to send you notifications about matches and event approvals.
|
|
16
16
|
|
|
@@ -62,15 +62,24 @@ cron 设完后,问用户一句:
|
|
|
62
62
|
|
|
63
63
|
**第六步:启动实时通知**
|
|
64
64
|
|
|
65
|
-
onboarding 完成后,启动 `antenna watch`
|
|
65
|
+
onboarding 完成后,启动 `antenna watch` 后台进程,实时接收匹配通知和活动审批通知。
|
|
66
|
+
|
|
67
|
+
**判断方法(不要猜):**
|
|
68
|
+
1. 先试 `antenna status --id <device_id>` — 如果输出 "Watch: ✅ running",已经在跑,不需要再启动
|
|
69
|
+
2. 如果不在跑,试 `antenna watch --id <device_id> --push hermes,openclaw &` 启动后台进程
|
|
70
|
+
3. 如果 exec 报错(不支持后台进程),用 cron 替代:
|
|
71
|
+
|
|
72
|
+
Hermes cron:
|
|
66
73
|
```
|
|
67
|
-
|
|
74
|
+
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."
|
|
68
75
|
```
|
|
69
|
-
这会保持一个 WebSocket 连接,有新匹配或活动审批变更时立即通知。比 cron 更实时。
|
|
70
76
|
|
|
71
|
-
|
|
77
|
+
OpenClaw cron:
|
|
78
|
+
```
|
|
79
|
+
openclaw 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."
|
|
80
|
+
```
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
**优先级:** watch > cron。watch 是 2 分钟延迟,cron 是 1 小时。
|
|
74
83
|
|
|
75
84
|
## When to use
|
|
76
85
|
|
|
@@ -85,10 +94,11 @@ antenna watch --id <device_id> &
|
|
|
85
94
|
|
|
86
95
|
### `antenna_scan`
|
|
87
96
|
Scan for nearby people **and events**. Returns raw profile cards + active events within 5km.
|
|
97
|
+
**Read-only — does NOT update your location.** To update location, use `antenna_checkin` or `antenna_bind`.
|
|
88
98
|
- `lat`, `lng`: coordinates (from `LocationLat`/`LocationLon` context, or geocoded from user input)
|
|
89
99
|
- `radius_m`: search radius in meters (default 500, max 1000) for people; events search uses 5km
|
|
90
100
|
- `sender_id`: the user's id from message context
|
|
91
|
-
- `channel`: the channel name (telegram, whatsapp,
|
|
101
|
+
- `channel`: the platform/channel name (any platform works: telegram, discord, whatsapp, webchat, signal, slack, matrix, clawx, etc.)
|
|
92
102
|
- Returns `profiles` (nearby people) + `nearby_events` (active events with name, participants count, code)
|
|
93
103
|
|
|
94
104
|
**Location staleness:** Before scanning, check if the user's GPS is recent. If `last_seen_at` is older than 2 hours, prompt the user to update their location (`antenna_bind` or `antenna_checkin`). Stale GPS = wrong results.
|
|
@@ -132,8 +142,6 @@ The name card has:
|
|
|
132
142
|
|
|
133
143
|
**After saving the profile, generate `matching_context` automatically** based on your knowledge of the user (memory, conversations, context). Don't ask the user to write it — you write it. Example:
|
|
134
144
|
> "Product designer at a tech company in Beijing, focusing on AI search experience. Interested in music (Sakamoto), swimming, cooking, language learning. Recently exploring AI agent ecosystems and social discovery. Looking to connect with AI builders, indie hackers, and creative technologists."
|
|
135
|
-
- **line2**: what they're into
|
|
136
|
-
- **line3**: what they're looking for right now
|
|
137
145
|
|
|
138
146
|
### `antenna_accept`
|
|
139
147
|
Accept a match after the user sees results. Can optionally include contact info to share.
|
|
@@ -222,7 +230,7 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
222
230
|
> "最后——你想被叫什么?再选个 emoji 代表你自己。"
|
|
223
231
|
|
|
224
232
|
**第六步:确认**
|
|
225
|
-
|
|
233
|
+
把名片组装好,**确认三行都有内容后**展示给用户确认:
|
|
226
234
|
> 你的名片:
|
|
227
235
|
>
|
|
228
236
|
> 🎸 **小林**
|
|
@@ -232,7 +240,8 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
232
240
|
>
|
|
233
241
|
> 看看有没有要改的?OK 的话我就存了。
|
|
234
242
|
|
|
235
|
-
用户说 OK → `antenna_profile` action="set" 保存。
|
|
243
|
+
用户说 OK → **检查 line1、line2、line3 都不为空后** 调 `antenna_profile` action="set" 保存。
|
|
244
|
+
如果某一行为空,先问用户补完再保存。后端会拒绝没有 line1 的新 profile,并对缺失的 line2/line3 返回 warning。
|
|
236
245
|
用户说要改 → 改完再确认。
|
|
237
246
|
|
|
238
247
|
**关键原则:**
|
|
@@ -245,6 +254,10 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
245
254
|
|
|
246
255
|
### Showing results — 你来判断,不是服务器
|
|
247
256
|
|
|
257
|
+
**第一次 scan 的新用户:** 简短一句解释:"这是附近的人。Antenna 基于 AI 匹配,看到感兴趣的人 accept,双向匹配后交换联系方式。"
|
|
258
|
+
|
|
259
|
+
**Profile 不完整时:** 如果用户的 profile 只有 1 行,提示:"你的名片只填了一行,补完后匹配质量会更好。要现在补吗?"
|
|
260
|
+
|
|
248
261
|
`antenna_scan` 返回的是附近所有人的名片,**没有打分、没有预匹配**。你需要:
|
|
249
262
|
|
|
250
263
|
**全球推荐 fallback:** 如果 scan 结果里有 `global: true`,说明附近没人,这些是全球推荐。告诉用户“附近暂时没人,但全球有这几个有意思的人”,然后正常推荐。用户仍然可以 accept。
|
|
@@ -253,7 +266,7 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
253
266
|
2. 结合你对用户的全部了解,判断谁值得推荐
|
|
254
267
|
3. 为每个推荐的人写一句**个性化的理由**——不是"你们都提到了 X",而是真正有洞察的话
|
|
255
268
|
|
|
256
|
-
**⚠️ 隐私规则:展示结果时绝对不要显示 device_id。** `device_id`(如 `
|
|
269
|
+
**⚠️ 隐私规则:展示结果时绝对不要显示 device_id。** `device_id`(如 `platform:user123`)是内部标识符,包含用户的平台和 ID,属于隐私信息。只显示 emoji + 名字 + 三句话 + 你写的匹配理由。`device_id` 只在内部调 `antenna_accept` 时用,不要展示给用户。
|
|
257
270
|
|
|
258
271
|
比如你知道用户最近在学吉他,看到附近有人写"组乐队找吉他手":
|
|
259
272
|
> 🎸 **小林** — 在组后摇乐队,找吉他手
|
|
@@ -288,7 +301,7 @@ Use `antenna_check_matches` when:
|
|
|
288
301
|
|
|
289
302
|
### Privacy
|
|
290
303
|
- Never reveal exact coordinates to other users
|
|
291
|
-
- **Never show device_id to users** (e.g. `telegram:
|
|
304
|
+
- **Never show device_id to users** (e.g. `telegram:12345`, `discord:67890`) — this is internal only
|
|
292
305
|
- Never share someone's platform or username with another user
|
|
293
306
|
- Only show the profile info (name, emoji, three lines)
|
|
294
307
|
- Contact info is only shared when the user explicitly agrees
|
|
@@ -314,10 +327,11 @@ Plugin 自带后台服务,每 10 分钟轮询一次 Supabase 查新的 mutual
|
|
|
314
327
|
|
|
315
328
|
### `antenna_event_create`
|
|
316
329
|
Create an event. Returns a shareable link (antenna.fyi/events/CODE).
|
|
317
|
-
- `name`: event name
|
|
318
|
-
- `sender_id`, `channel`: from context
|
|
319
|
-
- `
|
|
320
|
-
- `starts_at`, `ends_at`:
|
|
330
|
+
- `name`: event name (required)
|
|
331
|
+
- `sender_id`, `channel`: from context (required)
|
|
332
|
+
- `chat_id`: REQUIRED for notifications
|
|
333
|
+
- `starts_at`, `ends_at`: ISO time strings (required — no default, must be provided)
|
|
334
|
+
- `lat`, `lng`: optional event location (needed for GPS check-in)
|
|
321
335
|
- `description`: optional event description
|
|
322
336
|
- `og_image`: optional OG image URL for social sharing preview
|
|
323
337
|
- `requires_approval`: boolean, default false. If true, participants need organizer approval.
|
|
@@ -331,23 +345,30 @@ Create an event. Returns a shareable link (antenna.fyi/events/CODE).
|
|
|
331
345
|
End an event. Only the creator can end it.
|
|
332
346
|
- `code`: event code
|
|
333
347
|
- `sender_id`, `channel`: from context
|
|
348
|
+
- `chat_id`: REQUIRED for notifications
|
|
334
349
|
|
|
335
350
|
### `antenna_event_join`
|
|
336
351
|
Join an event by code.
|
|
337
352
|
- `code`: from the event URL (antenna.fyi/events/CODE)
|
|
338
353
|
- `sender_id`, `channel`: from context
|
|
354
|
+
- `chat_id`: REQUIRED for notifications
|
|
339
355
|
|
|
340
356
|
### `antenna_event_scan`
|
|
341
357
|
Scan people in an event. No distance limit — returns all participants.
|
|
342
358
|
- `code`: event code
|
|
343
359
|
- `sender_id`, `channel`: from context
|
|
360
|
+
- `chat_id`: REQUIRED for notifications
|
|
344
361
|
- Returns profiles with `source: "event"` tag
|
|
345
362
|
|
|
346
363
|
### `antenna_event_checkin`
|
|
347
364
|
Check in at an event — marks you as present at the event location. Optionally updates GPS.
|
|
348
365
|
- `code`: event code
|
|
349
366
|
- `sender_id`, `channel`: from context
|
|
367
|
+
- `chat_id`: REQUIRED for notifications
|
|
350
368
|
- `lat`, `lng`: optional GPS coordinates
|
|
369
|
+
- **Event must have started** (`starts_at <= now`). Cannot check in before start time.
|
|
370
|
+
- **Must be within 1km** of event location.
|
|
371
|
+
- **Must have `status: active`** (approved participants only, not pending).
|
|
351
372
|
|
|
352
373
|
### `antenna_event_upload_image`
|
|
353
374
|
Upload an image for an event OG preview. Returns a public URL.
|
|
@@ -359,35 +380,41 @@ Upload an image for an event OG preview. Returns a public URL.
|
|
|
359
380
|
Update event info. Only creator or co-host can update.
|
|
360
381
|
- `code`: event code
|
|
361
382
|
- `sender_id`, `channel`: from context
|
|
362
|
-
- `
|
|
383
|
+
- `chat_id`: REQUIRED for notifications
|
|
384
|
+
- `name`, `description`, `og_image`, `lat`, `lng`, `starts_at`, `ends_at`: all optional for update (only provided fields change, others stay as-is)
|
|
363
385
|
|
|
364
386
|
### `antenna_event_approve`
|
|
365
387
|
Approve a pending participant. Only creator or co-host.
|
|
366
388
|
- `code`: event code
|
|
367
389
|
- `sender_id`, `channel`: from context
|
|
390
|
+
- `chat_id`: REQUIRED for notifications
|
|
368
391
|
- `ref`: participant ref number from scan
|
|
369
392
|
|
|
370
393
|
### `antenna_event_reject`
|
|
371
394
|
Reject a pending participant. Only creator or co-host.
|
|
372
395
|
- `code`: event code
|
|
373
396
|
- `sender_id`, `channel`: from context
|
|
397
|
+
- `chat_id`: REQUIRED for notifications
|
|
374
398
|
- `ref`: participant ref number from scan
|
|
375
399
|
|
|
376
400
|
### `antenna_event_add_host`
|
|
377
401
|
Add a co-host to the event. Only creator can add.
|
|
378
402
|
- `code`: event code
|
|
379
403
|
- `sender_id`, `channel`: from context
|
|
404
|
+
- `chat_id`: REQUIRED for notifications
|
|
380
405
|
- `ref`: participant ref number to promote to co-host
|
|
381
406
|
|
|
382
407
|
---
|
|
383
408
|
|
|
384
|
-
##
|
|
409
|
+
## Event Behavior Guide
|
|
410
|
+
|
|
411
|
+
> This section is the single source of truth for event behavior. Tool descriptions above define parameters; this section defines agent behavior.
|
|
385
412
|
|
|
386
413
|
### Creating an event
|
|
387
414
|
Collect info through conversation (ask one by one, don't dump all at once):
|
|
388
415
|
1. **Event name** (required) — "活动叫什么名字?"
|
|
389
416
|
2. **Description** — "简单描述一下这个活动?"
|
|
390
|
-
3. **Time** — "什么时候开始?大概多长?" (convert to starts_at / ends_at ISO strings)
|
|
417
|
+
3. **Time** (required) — "什么时候开始?大概多长?" (convert to `starts_at` / `ends_at` ISO strings. **Must provide both — no defaults.**)
|
|
391
418
|
4. **Location** — "活动在哪里?" If user gives an address, geocode it. If vague, generate a bind link after creation.
|
|
392
419
|
5. **Approval** — "需要审批参与者吗?" If yes:
|
|
393
420
|
6. **Screening questions** — "你想问报名者什么问题?" Collect as a list.
|
|
@@ -416,3 +443,9 @@ Only creator or co-host can approve/reject:
|
|
|
416
443
|
- `antenna_event_approve(code, ref)` → participant becomes active
|
|
417
444
|
- `antenna_event_reject(code, ref)` → participant is rejected
|
|
418
445
|
- Notifications are sent automatically to the applicant
|
|
446
|
+
|
|
447
|
+
### Key differences from regular scan
|
|
448
|
+
- `antenna_scan` = nearby discovery, read-only, does NOT write location
|
|
449
|
+
- `antenna_event_scan` = event participants, no distance limit
|
|
450
|
+
- `antenna_checkin` = update YOUR location (not event-related)
|
|
451
|
+
- `antenna_event_checkin` = mark presence at an EVENT (GPS verified, event must have started)
|
package/skills/antenna/EVENTS.md
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: antenna-events
|
|
3
|
-
description: "Antenna Event Mode — create events, manage participants, GPS check-in. Use when a user wants to create an event, join an event, check in, scan event participants, or manage event settings."
|
|
4
|
-
tools:
|
|
5
|
-
- antenna_event_create
|
|
6
|
-
- antenna_event_join
|
|
7
|
-
- antenna_event_scan
|
|
8
|
-
- antenna_event_end
|
|
9
|
-
- antenna_event_checkin
|
|
10
|
-
- antenna_event_upload_image
|
|
11
|
-
- antenna_bind
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Antenna Event Mode
|
|
15
|
-
|
|
16
|
-
Create real-world events where everyone's AI agent handles the networking.
|
|
17
|
-
|
|
18
|
-
## Quick Start
|
|
19
|
-
|
|
20
|
-
1. **Create**: `antenna_event_create(name, description)` → get shareable link
|
|
21
|
-
2. **Share**: Send `antenna.fyi/events/CODE` to attendees
|
|
22
|
-
3. **Join**: Attendees' agents call `antenna_event_join(code)`
|
|
23
|
-
4. **Check in**: At the venue, `antenna_event_checkin(code)` — GPS verified ≤1km
|
|
24
|
-
5. **Discover**: `antenna_event_scan(code)` — no distance limit inside events
|
|
25
|
-
|
|
26
|
-
## Tools
|
|
27
|
-
|
|
28
|
-
### `antenna_event_create`
|
|
29
|
-
Create an event. Returns a shareable link (antenna.fyi/events/CODE).
|
|
30
|
-
- `name`: event name
|
|
31
|
-
- `sender_id`, `channel`: from context
|
|
32
|
-
- `lat`, `lng`: optional event location
|
|
33
|
-
- `starts_at`, `ends_at`: optional time range (default: now to +12h)
|
|
34
|
-
- `description`: optional event description
|
|
35
|
-
- `og_image`: optional OG image URL for social sharing preview
|
|
36
|
-
- `requires_approval`: boolean, default false. If true, participants must be approved by the organizer before they become visible.
|
|
37
|
-
- `screening_questions`: string array. Questions to ask applicants. Agent should collect answers via conversation and submit as `application_context` when joining.
|
|
38
|
-
|
|
39
|
-
**When the user says anything about "审批" / "approval" / "筛选" / "报名表"**, set `requires_approval: true` and ask what screening questions they want.
|
|
40
|
-
|
|
41
|
-
**GPS flow for events:** If the user doesn't provide coordinates, generate a bind link (`antenna_bind` with `purpose="event"` and `event_code`) and ask them to open it at the event location. The GPS will update the event's coordinates, NOT the user's profile.
|
|
42
|
-
|
|
43
|
-
### `antenna_event_join`
|
|
44
|
-
Join an event by code. Auto-checks in if event already started and user GPS is within 1km.
|
|
45
|
-
- `code`: from the event URL (antenna.fyi/events/CODE)
|
|
46
|
-
- `sender_id`, `channel`: from context
|
|
47
|
-
- **Requires profile** — user must have a profile before joining
|
|
48
|
-
- If event has started + user has GPS + within 1km → auto check-in
|
|
49
|
-
|
|
50
|
-
### `antenna_event_scan`
|
|
51
|
-
Scan people in an event. No distance limit — returns all participants.
|
|
52
|
-
- `code`: event code
|
|
53
|
-
- `sender_id`, `channel`: from context
|
|
54
|
-
- Returns profiles with `checked_in` status and `role` (creator/participant)
|
|
55
|
-
- Header shows "X joined, Y checked in"
|
|
56
|
-
|
|
57
|
-
### `antenna_event_end`
|
|
58
|
-
End an event. Only the creator can end it.
|
|
59
|
-
- `code`: event code
|
|
60
|
-
- `sender_id`, `channel`: from context
|
|
61
|
-
|
|
62
|
-
### `antenna_event_checkin`
|
|
63
|
-
Check in at an event — marks you as present at the event location.
|
|
64
|
-
- `code`: event code
|
|
65
|
-
- `sender_id`, `channel`: from context
|
|
66
|
-
- `lat`, `lng`: optional GPS (auto-reads profile location if not provided)
|
|
67
|
-
- GPS verified: must be within 1km of event location
|
|
68
|
-
- Event must have GPS set for check-in to work
|
|
69
|
-
|
|
70
|
-
### `antenna_event_upload_image`
|
|
71
|
-
Upload an image for an event OG preview. Returns a public URL.
|
|
72
|
-
- `image_base64`: base64-encoded image data
|
|
73
|
-
- `content_type`: MIME type (default image/png)
|
|
74
|
-
- `event_code`: event code
|
|
75
|
-
|
|
76
|
-
### `antenna_bind` (for events)
|
|
77
|
-
Generate a GPS link for setting event location.
|
|
78
|
-
- `purpose`: set to `"event"`
|
|
79
|
-
- `event_code`: the event code
|
|
80
|
-
- GPS from this link writes to the event, not the user's profile
|
|
81
|
-
|
|
82
|
-
## Agent Behavior
|
|
83
|
-
|
|
84
|
-
### When someone says "create an event"
|
|
85
|
-
Collect the following info through conversation (ask one by one, don't dump all at once):
|
|
86
|
-
1. **Event name** (required) — "活动叫什么名字?"
|
|
87
|
-
2. **Description** — "简单描述一下这个活动?"
|
|
88
|
-
3. **Time** — "什么时候开始?大概多长?" (convert to starts_at / ends_at ISO strings)
|
|
89
|
-
4. **Location** — "活动在哪里?" If user gives an address, geocode it. If vague, generate a bind link after creation.
|
|
90
|
-
5. **Approval** — "需要审批参与者吗?" If yes:
|
|
91
|
-
6. **Screening questions** — "你想问报名者什么问题?" Collect as a list.
|
|
92
|
-
|
|
93
|
-
Then call `antenna_event_create` with all collected info.
|
|
94
|
-
If no GPS, call `antenna_bind(purpose="event", event_code=CODE)` and send the link.
|
|
95
|
-
Share the event URL with the user.
|
|
96
|
-
|
|
97
|
-
### When someone shares an event link
|
|
98
|
-
1. Extract the code from `antenna.fyi/events/CODE`
|
|
99
|
-
2. Call `antenna_event_join(code)` — this checks everything:
|
|
100
|
-
- If no profile → "Create a profile first"
|
|
101
|
-
- If event requires approval and no `application_context` provided → returns `needs_screening: true` + `screening_questions` array
|
|
102
|
-
- If screening questions returned: **ask the user each question**, collect answers, then call `antenna_event_join(code, application_context="collected answers")` again
|
|
103
|
-
- If join succeeds with `status: pending` → tell user "waiting for organizer approval"
|
|
104
|
-
- If join succeeds with `status: active` → user is in!
|
|
105
|
-
3. Auto check-in happens automatically if event has started + GPS within 1km
|
|
106
|
-
|
|
107
|
-
### When someone says "who's here" at an event
|
|
108
|
-
1. Call `antenna_event_scan(code)`
|
|
109
|
-
2. Analyze profiles against what you know about the user
|
|
110
|
-
3. Recommend who they should meet and why
|
|
111
|
-
4. Creator appears with organizer badge
|
|
112
|
-
|