antenna-openclaw-plugin 1.3.1 → 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 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({ nearby: [], message: "刚刚才扫描过,稍等一会儿再试。", rate_limited: true });
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({ nearby: [], message: "还没有位置信息。请先通过链接分享位置,或者发送位置消息。" });
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
- nearby: gProfiles, total: gProfiles.length, radius_m: radius, global: true,
326
+ profiles: gProfiles, count: gProfiles.length, radius_m: radius, global: true,
327
327
  message: `附近 ${radius}m 暂时没人。今天的全球推荐——这个人跟你可能聊得来。(每天 1 次)`,
328
328
  });
329
329
  }
330
- return ok({ nearby: [], message: `附近暂时没人,今天的全球推荐已经用完了。明天再来!` });
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
- nearby: profiles,
357
- total: others.length,
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: allMatches } = await supabase.rpc("get_my_matches", { p_device_id: deviceId });
989
+ const { data: result } = await supabase.rpc("get_my_matches_with_profiles", { p_device_id: deviceId });
988
990
 
989
- if (!allMatches?.length) {
990
- return ok({ mutual_matches: [], incoming_accepts: [], message: "目前没有进行中的匹配。" });
991
- }
991
+ const rawMutual = result?.mutual_matches || [];
992
+ const rawIncoming = result?.incoming_accepts || [];
992
993
 
993
- // Matches I initiated
994
- const myMatches = allMatches.filter((m: any) => m.device_id_a === deviceId);
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
- // --- Incoming accepts (someone accepted me but I haven't accepted them yet) ---
1020
- const incomingAccepts = [];
1021
- for (const match of incomingMatches) {
1022
- const iAccepted = myMatches.find(
1023
- (m: any) => m.device_id_b === match.device_id_a
1024
- );
1025
- if (!iAccepted) {
1026
- // They accepted me but I haven't responded
1027
- const { data: profile } = await supabase.rpc("get_profile", { p_device_id: match.device_id_a });
1028
- incomingAccepts.push({
1029
- device_id: match.device_id_a,
1030
- name: profile?.display_name || "匿名", emoji: profile?.emoji || "👤",
1031
- line1: profile?.line1, line2: profile?.line2, line3: profile?.line3,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-openclaw-plugin",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Antenna — agent-mediated nearby people discovery for OpenClaw",
5
5
  "openclaw": {
6
6
  "extensions": ["./index.ts"]
@@ -230,7 +230,7 @@ Source code is open: https://github.com/H1an1/Antenna
230
230
  > "最后——你想被叫什么?再选个 emoji 代表你自己。"
231
231
 
232
232
  **第六步:确认**
233
- 把名片组装好,展示给用户确认:
233
+ 把名片组装好,**确认三行都有内容后**展示给用户确认:
234
234
  > 你的名片:
235
235
  >
236
236
  > 🎸 **小林**
@@ -240,7 +240,8 @@ Source code is open: https://github.com/H1an1/Antenna
240
240
  >
241
241
  > 看看有没有要改的?OK 的话我就存了。
242
242
 
243
- 用户说 OK → `antenna_profile` action="set" 保存。
243
+ 用户说 OK → **检查 line1、line2、line3 都不为空后** 调 `antenna_profile` action="set" 保存。
244
+ 如果某一行为空,先问用户补完再保存。后端会拒绝没有 line1 的新 profile,并对缺失的 line2/line3 返回 warning。
244
245
  用户说要改 → 改完再确认。
245
246
 
246
247
  **关键原则:**
@@ -253,6 +254,10 @@ Source code is open: https://github.com/H1an1/Antenna
253
254
 
254
255
  ### Showing results — 你来判断,不是服务器
255
256
 
257
+ **第一次 scan 的新用户:** 简短一句解释:"这是附近的人。Antenna 基于 AI 匹配,看到感兴趣的人 accept,双向匹配后交换联系方式。"
258
+
259
+ **Profile 不完整时:** 如果用户的 profile 只有 1 行,提示:"你的名片只填了一行,补完后匹配质量会更好。要现在补吗?"
260
+
256
261
  `antenna_scan` 返回的是附近所有人的名片,**没有打分、没有预匹配**。你需要:
257
262
 
258
263
  **全球推荐 fallback:** 如果 scan 结果里有 `global: true`,说明附近没人,这些是全球推荐。告诉用户“附近暂时没人,但全球有这几个有意思的人”,然后正常推荐。用户仍然可以 accept。