antenna-fyi 0.9.2 → 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/lib/core.js +15 -6
- package/lib/hermes-plugin/schemas.py +8 -4
- package/lib/hermes-plugin/tools.py +27 -13
- package/lib/mcp.js +22 -9
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -69,18 +69,27 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
69
69
|
? (data || []).filter((p) => p.device_id !== device_id)
|
|
70
70
|
: data || [];
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
// Build ref mapping (1-indexed) so device_id is never exposed to the agent/user
|
|
73
|
+
const _refMap = {};
|
|
74
|
+
const profiles = others.map((p, i) => {
|
|
75
|
+
const ref = String(i + 1);
|
|
76
|
+
_refMap[ref] = p.device_id;
|
|
77
|
+
return {
|
|
78
|
+
ref,
|
|
77
79
|
name: p.display_name || "匿名",
|
|
78
80
|
emoji: p.emoji || "👤",
|
|
79
81
|
line1: p.line1,
|
|
80
82
|
line2: p.line2,
|
|
81
83
|
line3: p.line3,
|
|
82
84
|
distance_m: p.distance_m ?? p.dist_meters ?? null,
|
|
83
|
-
}
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
count: others.length,
|
|
90
|
+
radius_m,
|
|
91
|
+
profiles,
|
|
92
|
+
_ref_map: _refMap,
|
|
84
93
|
};
|
|
85
94
|
}
|
|
86
95
|
|
|
@@ -58,24 +58,28 @@ PROFILE_SCHEMA = {
|
|
|
58
58
|
ACCEPT_SCHEMA = {
|
|
59
59
|
"name": "antenna_accept",
|
|
60
60
|
"description": (
|
|
61
|
-
"Accept a match.
|
|
62
|
-
"
|
|
61
|
+
"Accept a match. Use 'ref' from scan results (e.g. '1', '2') or target_device_id. "
|
|
62
|
+
"Optionally share contact info."
|
|
63
63
|
),
|
|
64
64
|
"parameters": {
|
|
65
65
|
"type": "object",
|
|
66
66
|
"properties": {
|
|
67
67
|
"sender_id": {"type": "string"},
|
|
68
68
|
"channel": {"type": "string"},
|
|
69
|
+
"ref": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "Ref number from scan results (e.g. '1')",
|
|
72
|
+
},
|
|
69
73
|
"target_device_id": {
|
|
70
74
|
"type": "string",
|
|
71
|
-
"description": "Device ID
|
|
75
|
+
"description": "Device ID (use ref instead when possible)",
|
|
72
76
|
},
|
|
73
77
|
"contact_info": {
|
|
74
78
|
"type": "string",
|
|
75
79
|
"description": "Contact info to share (e.g. 'WeChat: yi')",
|
|
76
80
|
},
|
|
77
81
|
},
|
|
78
|
-
"required": ["sender_id", "channel"
|
|
82
|
+
"required": ["sender_id", "channel"],
|
|
79
83
|
},
|
|
80
84
|
}
|
|
81
85
|
|
|
@@ -28,6 +28,7 @@ _client = None
|
|
|
28
28
|
_client_url = None
|
|
29
29
|
_last_scan: dict[str, float] = {}
|
|
30
30
|
SCAN_DEBOUNCE_S = 30
|
|
31
|
+
_last_ref_map: dict[str, str] = {} # ref → device_id from last scan
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def _get_url():
|
|
@@ -106,21 +107,27 @@ def handle_scan(params: dict) -> str:
|
|
|
106
107
|
if not others:
|
|
107
108
|
return _ok({"nearby": [], "message": f"在 {radius}m 范围内没有发现其他人。"})
|
|
108
109
|
|
|
110
|
+
# Build ref mapping — never expose device_id to agent/user
|
|
111
|
+
global _last_ref_map
|
|
112
|
+
_last_ref_map = {}
|
|
113
|
+
profiles = []
|
|
114
|
+
for i, p in enumerate(others):
|
|
115
|
+
ref = str(i + 1)
|
|
116
|
+
_last_ref_map[ref] = p.get("device_id")
|
|
117
|
+
profiles.append({
|
|
118
|
+
"ref": ref,
|
|
119
|
+
"emoji": p.get("emoji") or "👤",
|
|
120
|
+
"name": p.get("display_name") or "匿名",
|
|
121
|
+
"line1": p.get("line1"),
|
|
122
|
+
"line2": p.get("line2"),
|
|
123
|
+
"line3": p.get("line3"),
|
|
124
|
+
})
|
|
125
|
+
|
|
109
126
|
return _ok({
|
|
110
|
-
"nearby":
|
|
111
|
-
{
|
|
112
|
-
"device_id": p.get("device_id"),
|
|
113
|
-
"emoji": p.get("emoji") or "👤",
|
|
114
|
-
"name": p.get("display_name") or "匿名",
|
|
115
|
-
"line1": p.get("line1"),
|
|
116
|
-
"line2": p.get("line2"),
|
|
117
|
-
"line3": p.get("line3"),
|
|
118
|
-
}
|
|
119
|
-
for p in others
|
|
120
|
-
],
|
|
127
|
+
"nearby": profiles,
|
|
121
128
|
"total": len(others),
|
|
122
129
|
"radius_m": radius,
|
|
123
|
-
"instruction": "
|
|
130
|
+
"instruction": "根据你对用户的了解,判断哪些人值得推荐,为每个推荐写一句个性化的匹配理由。使用 ref 编号(如 '1', '2')来引用人员,不要显示 device_id。",
|
|
124
131
|
})
|
|
125
132
|
|
|
126
133
|
|
|
@@ -153,7 +160,14 @@ def handle_profile(params: dict) -> str:
|
|
|
153
160
|
def handle_accept(params: dict) -> str:
|
|
154
161
|
sb = _sb()
|
|
155
162
|
did = _device_id(params["sender_id"], params["channel"])
|
|
156
|
-
|
|
163
|
+
|
|
164
|
+
# Resolve ref to device_id
|
|
165
|
+
ref = params.get("ref")
|
|
166
|
+
target = params.get("target_device_id")
|
|
167
|
+
if ref and ref in _last_ref_map:
|
|
168
|
+
target = _last_ref_map[ref]
|
|
169
|
+
if not target:
|
|
170
|
+
return _ok({"error": "No target. Use 'ref' from scan results or 'target_device_id'."})
|
|
157
171
|
|
|
158
172
|
sb.rpc("upsert_match", {
|
|
159
173
|
"p_device_id_a": did,
|
package/lib/mcp.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
export async function startMcpServer() {
|
|
18
18
|
const server = new McpServer({
|
|
19
19
|
name: "Antenna",
|
|
20
|
-
version: "0.
|
|
20
|
+
version: "0.9.2",
|
|
21
21
|
description:
|
|
22
22
|
"Nearby people discovery — scan for people around you, set up your profile card, accept matches, and check match status.",
|
|
23
23
|
});
|
|
@@ -26,22 +26,27 @@ export async function startMcpServer() {
|
|
|
26
26
|
return { content: [{ type: "text", text: JSON.stringify(obj) }] };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Store last scan ref map for resolving refs in accept
|
|
30
|
+
let _lastRefMap = {};
|
|
31
|
+
|
|
29
32
|
// ─── antenna_scan ──────────────────────────────────────────────────
|
|
30
33
|
|
|
31
34
|
server.tool(
|
|
32
35
|
"antenna_scan",
|
|
33
|
-
"Scan for nearby people.
|
|
36
|
+
"Scan for nearby people. Returns profiles with 'ref' numbers (1, 2, 3...) for privacy. Use these ref numbers when calling antenna_accept.",
|
|
34
37
|
{
|
|
35
38
|
lat: z.number().optional().describe("Latitude (optional if location was shared via web)"),
|
|
36
39
|
lng: z.number().optional().describe("Longitude (optional if location was shared via web)"),
|
|
37
40
|
radius_m: z.number().optional().default(500).describe("Search radius in meters"),
|
|
38
41
|
sender_id: z.string().describe("The sender's user ID"),
|
|
39
|
-
channel: z.string().describe("Channel name
|
|
42
|
+
channel: z.string().describe("Channel name"),
|
|
40
43
|
},
|
|
41
44
|
async ({ lat, lng, radius_m, sender_id, channel }) => {
|
|
42
45
|
try {
|
|
43
46
|
const result = await scan({ lat, lng, radius_m, device_id: deriveDeviceId(sender_id, channel) });
|
|
44
|
-
|
|
47
|
+
_lastRefMap = result._ref_map || {};
|
|
48
|
+
const { _ref_map, ...clean } = result;
|
|
49
|
+
return jsonResult(clean);
|
|
45
50
|
} catch (e) {
|
|
46
51
|
return jsonResult({ error: e.message });
|
|
47
52
|
}
|
|
@@ -83,16 +88,24 @@ export async function startMcpServer() {
|
|
|
83
88
|
|
|
84
89
|
server.tool(
|
|
85
90
|
"antenna_accept",
|
|
86
|
-
"Accept a match
|
|
91
|
+
"Accept a match. Use 'ref' from scan results (e.g. '1', '2') OR target_device_id directly.",
|
|
87
92
|
{
|
|
88
93
|
sender_id: z.string().describe("The sender's user ID"),
|
|
89
94
|
channel: z.string().describe("Channel name"),
|
|
90
|
-
|
|
95
|
+
ref: z.string().optional().describe("Ref number from scan results (e.g. '1')"),
|
|
96
|
+
target_device_id: z.string().optional().describe("Device ID (use ref instead when possible)"),
|
|
91
97
|
contact_info: z.string().optional().describe("Contact info to share"),
|
|
92
98
|
},
|
|
93
|
-
async ({ sender_id, channel, target_device_id, contact_info }) => {
|
|
99
|
+
async ({ sender_id, channel, ref, target_device_id, contact_info }) => {
|
|
94
100
|
try {
|
|
95
|
-
|
|
101
|
+
let targetId = target_device_id;
|
|
102
|
+
if (ref && _lastRefMap[ref]) {
|
|
103
|
+
targetId = _lastRefMap[ref];
|
|
104
|
+
}
|
|
105
|
+
if (!targetId) {
|
|
106
|
+
return jsonResult({ error: "No target specified. Use 'ref' from scan results or 'target_device_id'." });
|
|
107
|
+
}
|
|
108
|
+
const result = await accept({ device_id: deriveDeviceId(sender_id, channel), target_device_id: targetId, contact_info });
|
|
96
109
|
return jsonResult(result);
|
|
97
110
|
} catch (e) {
|
|
98
111
|
return jsonResult({ error: e.message });
|
|
@@ -144,7 +157,7 @@ export async function startMcpServer() {
|
|
|
144
157
|
}
|
|
145
158
|
);
|
|
146
159
|
|
|
147
|
-
// ─── antenna_bind
|
|
160
|
+
// ─── antenna_bind ──────────────────────────────────────────────────
|
|
148
161
|
|
|
149
162
|
server.tool(
|
|
150
163
|
"antenna_bind",
|