antenna-openclaw-plugin 0.10.0 → 1.0.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 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
- return ok({ nearby: [], message: `在 ${radius}m 范围内没有发现其他人。试试扩大范围?` });
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 (params.ref && (api as any)._antennaRefMap?.[params.ref]) {
438
- targetId = (api as any)._antennaRefMap[params.ref];
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. Use 'ref' from scan results or 'target_device_id'." });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-openclaw-plugin",
3
- "version": "0.10.0",
3
+ "version": "1.0.0",
4
4
  "description": "Antenna — agent-mediated nearby people discovery for OpenClaw",
5
5
  "openclaw": {
6
6
  "extensions": ["./index.ts"]
@@ -116,6 +116,26 @@ Get today's global recommendation — the person most similar to you worldwide.
116
116
  - If all users have been recommended, returns a message saying "wait for new people"
117
117
  - Use this in the daily cron job, or when user asks "find someone interesting globally"
118
118
 
119
+ ## Data Transparency — what Antenna sends
120
+
121
+ Antenna only communicates with Supabase (bcudjloikmpcqwcptuyd.supabase.co) via HTTPS.
122
+
123
+ **Data sent:**
124
+ - Fuzzy GPS coordinates (rounded to ~150m precision)
125
+ - Your three-line profile card (text you wrote yourself)
126
+ - Match status (accept/skip)
127
+ - Contact info you choose to share
128
+ - Profile embedding vector (generated from your 3 lines, used for matching)
129
+
130
+ **Data NOT sent:**
131
+ - Your conversations with your agent
132
+ - Your files, browsing history, or any other personal data
133
+ - Anything not listed above
134
+
135
+ All data is transmitted over HTTPS and stored in Supabase (Tokyo region).
136
+ Matches auto-delete after 24 hours. GPS is blurred client-side before transmission.
137
+ Source code is open: https://github.com/H1an1/Antenna
138
+
119
139
  ## Behavior guidelines
120
140
 
121
141
  ### First-time user — 聊天式引导(不要让用户填表)