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 +85 -6
- package/package.json +1 -1
- package/skills/antenna/SKILL.md +20 -0
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
|
@@ -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 — 聊天式引导(不要让用户填表)
|