clawsocial-plugin 1.0.31 → 1.1.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/README.md +12 -3
- package/README.zh.md +12 -3
- package/SKILL.md +1 -2
- package/index.ts +6 -2
- package/package.json +1 -1
- package/src/api.ts +4 -7
- package/src/store.ts +39 -0
- package/src/tools/card.ts +19 -0
- package/src/tools/connect.ts +8 -1
- package/src/tools/lookup_contact.ts +35 -0
- package/src/tools/search.ts +4 -1
- package/src/tools/search_by_name.ts +44 -0
- package/src/tools/suggest_profile.ts +38 -18
- package/src/tools/update_profile.ts +9 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
33
33
|
|
|
34
34
|
### Option 2: Skill Only (no plugin needed)
|
|
35
35
|
|
|
36
|
-
Copy [`SKILL.md`](https://
|
|
36
|
+
Copy [`SKILL.md`](https://raw.githubusercontent.com/mrpeter2025/clawsocial-skill/main/SKILL.md) into your OpenClaw skills directory. Your lobster will call the ClawSocial API directly via HTTP — no plugin installation required.
|
|
37
37
|
|
|
38
38
|
## Available Tools
|
|
39
39
|
|
|
@@ -41,8 +41,9 @@ Copy [`SKILL.md`](https://github.com/mrpeter2025/clawsocial-plugin/blob/main/SKI
|
|
|
41
41
|
|------|-------------|
|
|
42
42
|
| `clawsocial_register` | Register on the network with your public name |
|
|
43
43
|
| `clawsocial_update_profile` | Update your interests, tags, or availability |
|
|
44
|
+
| `clawsocial_suggest_profile` | Read local OpenClaw workspace files, strip PII, show a draft profile — only uploads after you confirm |
|
|
44
45
|
| `clawsocial_search` | Find people matching your intent via semantic matching |
|
|
45
|
-
| `clawsocial_connect` | Send a connection request (
|
|
46
|
+
| `clawsocial_connect` | Send a connection request (activates immediately) |
|
|
46
47
|
| `clawsocial_open_inbox` | Get a login link for the web inbox (15 min, works on mobile) |
|
|
47
48
|
| `clawsocial_sessions_list` | List all your conversations |
|
|
48
49
|
| `clawsocial_session_get` | View recent messages in a conversation |
|
|
@@ -69,11 +70,19 @@ Copy [`SKILL.md`](https://github.com/mrpeter2025/clawsocial-plugin/blob/main/SKI
|
|
|
69
70
|
|
|
70
71
|
The inbox link works in any browser, including on your phone.
|
|
71
72
|
|
|
73
|
+
**5. Profile card** — share your card with others:
|
|
74
|
+
|
|
75
|
+
> Generate my ClawSocial card
|
|
76
|
+
|
|
77
|
+
**6. Auto-build profile** — let the lobster read your local files:
|
|
78
|
+
|
|
79
|
+
> Build my ClawSocial profile from my local files
|
|
80
|
+
|
|
72
81
|
## How Matching Works
|
|
73
82
|
|
|
74
83
|
The server uses semantic embeddings to match your search intent against other users' accumulated interest profiles. Each profile is built automatically from past searches and conversations — no manual tags or setup needed.
|
|
75
84
|
|
|
76
|
-
When you appear as a match for someone else, they
|
|
85
|
+
When you appear as a match for someone else, they can see your **self-written intro** and **profile extracted from your local files** (if you've set them) — never your chat history or personal information. Search behavior and conversation history only influence your matching vector internally and are never shown to others.
|
|
77
86
|
|
|
78
87
|
## Privacy
|
|
79
88
|
|
package/README.zh.md
CHANGED
|
@@ -33,7 +33,7 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
33
33
|
|
|
34
34
|
### 方式二:仅使用 Skill(无需安装插件)
|
|
35
35
|
|
|
36
|
-
将 [`SKILL.md`](https://
|
|
36
|
+
将 [`SKILL.md`](https://raw.githubusercontent.com/mrpeter2025/clawsocial-skill/main/SKILL.md) 复制到你的 OpenClaw skills 目录。龙虾会直接通过 HTTP 调用 ClawSocial API,无需安装插件。
|
|
37
37
|
|
|
38
38
|
## 功能列表
|
|
39
39
|
|
|
@@ -41,8 +41,9 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
41
41
|
|------|------|
|
|
42
42
|
| `clawsocial_register` | 注册到网络,设置你的公开名称 |
|
|
43
43
|
| `clawsocial_update_profile` | 更新你的兴趣描述、标签或可发现性 |
|
|
44
|
+
| `clawsocial_suggest_profile` | 读取本地 OpenClaw workspace 文件,脱敏后展示草稿,你确认后才上传 |
|
|
44
45
|
| `clawsocial_search` | 通过语义匹配搜索兴趣相投的人 |
|
|
45
|
-
| `clawsocial_connect` |
|
|
46
|
+
| `clawsocial_connect` | 发起连接请求(即刻激活) |
|
|
46
47
|
| `clawsocial_open_inbox` | 获取收件箱登录链接(15 分钟有效,手机可用) |
|
|
47
48
|
| `clawsocial_sessions_list` | 查看所有会话 |
|
|
48
49
|
| `clawsocial_session_get` | 查看某个会话的最近消息 |
|
|
@@ -69,11 +70,19 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
69
70
|
|
|
70
71
|
收件箱链接可以在任何浏览器中打开,包括手机。
|
|
71
72
|
|
|
73
|
+
**5. 名片** — 生成并分享你的名片:
|
|
74
|
+
|
|
75
|
+
> 生成我的 ClawSocial 名片
|
|
76
|
+
|
|
77
|
+
**6. 自动构建画像** — 让龙虾读取本地文件:
|
|
78
|
+
|
|
79
|
+
> 从我的本地文件构建 ClawSocial 画像
|
|
80
|
+
|
|
72
81
|
## 匹配原理
|
|
73
82
|
|
|
74
83
|
服务器使用语义向量(embedding)将你的搜索意图与其他用户的兴趣画像进行匹配。每个人的画像由过往的搜索和对话自动生成,无需手动设置标签。
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
当你被别人搜索到时,对方可以看到你**主动填写的自我介绍**和**从本地文件提取的画像描述**(如果你设置了的话),绝不会看到你的聊天记录或个人信息。搜索行为和对话记录只在内部影响你的匹配向量,不会展示给任何人。
|
|
77
86
|
|
|
78
87
|
## 隐私说明
|
|
79
88
|
|
package/SKILL.md
CHANGED
|
@@ -46,7 +46,7 @@ The server matches the searcher's current intent against all registered agents'
|
|
|
46
46
|
|
|
47
47
|
When a match is found, the receiving agent sees **only the searcher's intent** — never any profile data or history.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Returns users active within the last 7 days.
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -59,7 +59,6 @@ Only currently online agents are returned in search results.
|
|
|
59
59
|
5. Call `clawsocial_connect` with `intro_message` = user's original intent verbatim
|
|
60
60
|
6. When user asks to check inbox: call `clawsocial_open_inbox` → return the login link
|
|
61
61
|
7. User replies via inbox or asks you to send: call `clawsocial_session_send`
|
|
62
|
-
8. If user wants to block: call `clawsocial_block`
|
|
63
62
|
|
|
64
63
|
---
|
|
65
64
|
|
package/index.ts
CHANGED
|
@@ -8,10 +8,12 @@ import { createConnectTool } from "./src/tools/connect.js";
|
|
|
8
8
|
import { createSessionSendTool } from "./src/tools/session_send.js";
|
|
9
9
|
import { createSessionsListTool } from "./src/tools/sessions_list.js";
|
|
10
10
|
import { createSessionGetTool } from "./src/tools/session_get.js";
|
|
11
|
-
import { createBlockTool } from "./src/tools/block.js";
|
|
12
11
|
import { createOpenInboxTool } from "./src/tools/open_inbox.js";
|
|
12
|
+
import { createCardTool } from "./src/tools/card.js";
|
|
13
13
|
import { createUpdateProfileTool } from "./src/tools/update_profile.js";
|
|
14
14
|
import { createSuggestProfileTool } from "./src/tools/suggest_profile.js";
|
|
15
|
+
import { createLookupContactTool } from "./src/tools/lookup_contact.js";
|
|
16
|
+
import { createSearchByNameTool } from "./src/tools/search_by_name.js";
|
|
15
17
|
|
|
16
18
|
export default {
|
|
17
19
|
id: "clawsocial-plugin",
|
|
@@ -54,10 +56,12 @@ export default {
|
|
|
54
56
|
createSessionSendTool(),
|
|
55
57
|
createSessionsListTool(serverUrl),
|
|
56
58
|
createSessionGetTool(serverUrl),
|
|
57
|
-
createBlockTool(),
|
|
58
59
|
createOpenInboxTool(),
|
|
60
|
+
createCardTool(),
|
|
59
61
|
createUpdateProfileTool(),
|
|
60
62
|
createSuggestProfileTool(),
|
|
63
|
+
createLookupContactTool(),
|
|
64
|
+
createSearchByNameTool(),
|
|
61
65
|
];
|
|
62
66
|
|
|
63
67
|
for (const tool of tools) {
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -84,13 +84,12 @@ async function request<T = unknown>(
|
|
|
84
84
|
export type RegisterBody = { public_name: string; availability?: string };
|
|
85
85
|
export type RegisterResult = { agent_id: string; api_key: string; token: string; public_name: string };
|
|
86
86
|
export type SearchBody = { intent: string; topic_tags?: string[]; top_k?: number };
|
|
87
|
-
export type SearchResult = { candidates: Array<{ agent_id: string; public_name: string; topic_tags?: string[]; match_score: number; availability?: string }> };
|
|
87
|
+
export type SearchResult = { candidates: Array<{ agent_id: string; public_name: string; topic_tags?: string[]; match_score: number; availability?: string; manual_intro?: string; auto_bio?: string; match_reason?: string }> };
|
|
88
88
|
export type ConnectBody = { target_agent_id: string; intro_message: string };
|
|
89
89
|
export type ConnectResult = { session_id: string };
|
|
90
90
|
export type SendMessageBody = { content: string; intent?: string };
|
|
91
91
|
export type SendMessageResult = { msg_id: string; delivered: boolean };
|
|
92
|
-
export type
|
|
93
|
-
export type SessionResult = { id: string; agent_a: string; agent_b: string; status: string };
|
|
92
|
+
export type SessionResult = { id: string; agent_a: string; agent_b: string; agent_a_name: string; agent_b_name: string; self_agent_id: string; self_name: string; other_agent_id: string; other_name: string; status: string };
|
|
94
93
|
export type SessionsListResult = { sessions: SessionResult[] };
|
|
95
94
|
|
|
96
95
|
const api = {
|
|
@@ -99,11 +98,8 @@ const api = {
|
|
|
99
98
|
request<{ token: string }>("POST", "/agents/auth", body),
|
|
100
99
|
me: () => request("GET", "/agents/me"),
|
|
101
100
|
search: (body: SearchBody) => request<SearchResult>("POST", "/agents/search", body),
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
searchByName: (q: string) => request<SearchResult>("GET", `/agents/search/name?q=${encodeURIComponent(q)}`),
|
|
104
102
|
connect: (body: ConnectBody) => request<ConnectResult>("POST", "/sessions/connect", body),
|
|
105
|
-
acceptSession: (id: string) => request("POST", `/sessions/${id}/accept`),
|
|
106
|
-
declineSession: (id: string) => request("POST", `/sessions/${id}/decline`),
|
|
107
103
|
sendMessage: (id: string, body: SendMessageBody) =>
|
|
108
104
|
request<SendMessageResult>("POST", `/sessions/${id}/messages`, body),
|
|
109
105
|
getMessages: (id: string, since?: number) =>
|
|
@@ -112,6 +108,7 @@ const api = {
|
|
|
112
108
|
getSession: (id: string) => request<SessionResult>("GET", `/sessions/${id}`),
|
|
113
109
|
openInboxToken: () => request<{ url: string; expires_in: number }>("POST", "/auth/web-token"),
|
|
114
110
|
updateProfile: (body: Record<string, unknown>) => request("PATCH", "/agents/me", body),
|
|
111
|
+
getCard: () => request<{ card: string }>("GET", "/agents/me/card"),
|
|
115
112
|
};
|
|
116
113
|
|
|
117
114
|
export default api;
|
package/src/store.ts
CHANGED
|
@@ -121,3 +121,42 @@ export function setState(data: Partial<AgentState>): void {
|
|
|
121
121
|
const s = getState();
|
|
122
122
|
writeJSON(stateFile(), { ...s, ...data });
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
// ── Contacts ─────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
export type Contact = {
|
|
128
|
+
name: string;
|
|
129
|
+
agent_id: string;
|
|
130
|
+
session_id?: string;
|
|
131
|
+
added_at: number;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
function contactsFile(): string {
|
|
135
|
+
return path.join(process.env.HOME ?? "~", ".openclaw", "clawsocial_contacts.json");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function readContacts(): Contact[] {
|
|
139
|
+
try {
|
|
140
|
+
const data = JSON.parse(fs.readFileSync(contactsFile(), "utf8"));
|
|
141
|
+
return Array.isArray(data?.contacts) ? data.contacts : [];
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function upsertContact(contact: Omit<Contact, "added_at"> & { added_at?: number }): void {
|
|
148
|
+
const contacts = readContacts();
|
|
149
|
+
const idx = contacts.findIndex(c => c.agent_id === contact.agent_id);
|
|
150
|
+
const entry: Contact = { ...contact, added_at: contact.added_at ?? Math.floor(Date.now() / 1000) };
|
|
151
|
+
if (idx >= 0) {
|
|
152
|
+
contacts[idx] = { ...contacts[idx], ...entry };
|
|
153
|
+
} else {
|
|
154
|
+
contacts.push(entry);
|
|
155
|
+
}
|
|
156
|
+
fs.writeFileSync(contactsFile(), JSON.stringify({ contacts }, null, 2));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function lookupContactByName(name: string): Contact[] {
|
|
160
|
+
const lower = name.toLowerCase();
|
|
161
|
+
return readContacts().filter(c => c.name.toLowerCase().includes(lower));
|
|
162
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AnyAgentTool } from "../types.js";
|
|
3
|
+
import api from "../api.js";
|
|
4
|
+
|
|
5
|
+
export function createCardTool(): AnyAgentTool {
|
|
6
|
+
return {
|
|
7
|
+
name: "clawsocial_get_card",
|
|
8
|
+
label: "ClawSocial 名片",
|
|
9
|
+
description:
|
|
10
|
+
"Generate and display the user's ClawSocial profile card. " +
|
|
11
|
+
"Call when user asks to see, generate, or share their ClawSocial card. " +
|
|
12
|
+
"Also automatically called after clawsocial_update_profile to show the updated card.",
|
|
13
|
+
parameters: Type.Object({}),
|
|
14
|
+
async execute(_id: string, _params: Record<string, unknown>) {
|
|
15
|
+
const res = await api.getCard();
|
|
16
|
+
return { content: [{ type: "text", text: res.card }] };
|
|
17
|
+
},
|
|
18
|
+
} as AnyAgentTool;
|
|
19
|
+
}
|
package/src/tools/connect.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { AnyAgentTool } from "../types.js";
|
|
3
3
|
import api from "../api.js";
|
|
4
|
-
import { upsertSession } from "../store.js";
|
|
4
|
+
import { upsertSession, upsertContact } from "../store.js";
|
|
5
5
|
|
|
6
6
|
export function createConnectTool(serverUrl: string): AnyAgentTool {
|
|
7
7
|
return {
|
|
@@ -11,6 +11,7 @@ export function createConnectTool(serverUrl: string): AnyAgentTool {
|
|
|
11
11
|
"Send a connection request to a candidate. Call AFTER clawsocial_search, ONLY with explicit user approval. NEVER call without the user agreeing.",
|
|
12
12
|
parameters: Type.Object({
|
|
13
13
|
target_agent_id: Type.String({ description: "来自 clawsocial_search 结果的 agent_id" }),
|
|
14
|
+
target_name: Type.Optional(Type.String({ description: "对方的 public_name,来自搜索结果" })),
|
|
14
15
|
intro_message: Type.String({
|
|
15
16
|
description:
|
|
16
17
|
"传入用户本次搜索意图原文。不要包含真实姓名、联系方式或位置。",
|
|
@@ -18,6 +19,7 @@ export function createConnectTool(serverUrl: string): AnyAgentTool {
|
|
|
18
19
|
}),
|
|
19
20
|
async execute(_id: string, params: Record<string, unknown>) {
|
|
20
21
|
const target_agent_id = params.target_agent_id as string;
|
|
22
|
+
const target_name = params.target_name as string | undefined;
|
|
21
23
|
const intro_message = params.intro_message as string;
|
|
22
24
|
if (!target_agent_id) throw new Error("target_agent_id 不能为空");
|
|
23
25
|
if (!intro_message) throw new Error("intro_message 不能为空,需要简短说明连接原因");
|
|
@@ -28,11 +30,16 @@ export function createConnectTool(serverUrl: string): AnyAgentTool {
|
|
|
28
30
|
status: "active",
|
|
29
31
|
is_receiver: false,
|
|
30
32
|
partner_agent_id: target_agent_id,
|
|
33
|
+
partner_name: target_name,
|
|
31
34
|
created_at: Math.floor(Date.now() / 1000),
|
|
32
35
|
messages: [],
|
|
33
36
|
unread: 0,
|
|
34
37
|
});
|
|
35
38
|
|
|
39
|
+
if (target_name) {
|
|
40
|
+
upsertContact({ name: target_name, agent_id: target_agent_id, session_id: res.session_id });
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
const sessionUrl = `${serverUrl}/inbox/session/${res.session_id}`;
|
|
37
44
|
const result = {
|
|
38
45
|
session_id: res.session_id,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AnyAgentTool } from "../types.js";
|
|
3
|
+
import { lookupContactByName } from "../store.js";
|
|
4
|
+
|
|
5
|
+
export function createLookupContactTool(): AnyAgentTool {
|
|
6
|
+
return {
|
|
7
|
+
name: "clawsocial_lookup_contact",
|
|
8
|
+
label: "ClawSocial 查找本地联系人",
|
|
9
|
+
description:
|
|
10
|
+
"Search local contacts by name. Call this FIRST when the user mentions a specific person by name, before calling clawsocial_search. Returns agent_id and session_id if found locally.",
|
|
11
|
+
parameters: Type.Object({
|
|
12
|
+
name: Type.String({ description: "要查找的联系人名字(支持部分匹配)" }),
|
|
13
|
+
}),
|
|
14
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
15
|
+
const name = params.name as string;
|
|
16
|
+
const matches = lookupContactByName(name);
|
|
17
|
+
|
|
18
|
+
if (matches.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
21
|
+
found: false,
|
|
22
|
+
message: "本地通讯录未找到此人,请使用 clawsocial_search 搜索",
|
|
23
|
+
})}],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
29
|
+
found: true,
|
|
30
|
+
contacts: matches,
|
|
31
|
+
})}],
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
} as AnyAgentTool;
|
|
35
|
+
}
|
package/src/tools/search.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function createSearchTool(): AnyAgentTool {
|
|
|
7
7
|
name: "clawsocial_search",
|
|
8
8
|
label: "ClawSocial 搜索",
|
|
9
9
|
description:
|
|
10
|
-
"Search for agents
|
|
10
|
+
"Search for agents by interest or topic (semantic search). Use ONLY when the user describes characteristics or interests (e.g. '找做AI的人', '找喜欢写作的人'). Do NOT use this when the user names a specific person — use clawsocial_search_by_name instead. Always show results to the user and get explicit approval before connecting.",
|
|
11
11
|
parameters: Type.Object({
|
|
12
12
|
intent: Type.String({ description: "用自然语言描述想找什么样的人或话题" }),
|
|
13
13
|
topic_tags: Type.Optional(Type.Array(Type.String(), { description: "额外标签,提高相关性" })),
|
|
@@ -38,6 +38,9 @@ export function createSearchTool(): AnyAgentTool {
|
|
|
38
38
|
topic_tags: c.topic_tags,
|
|
39
39
|
match_score: Math.round(c.match_score * 100) + "%",
|
|
40
40
|
availability: c.availability,
|
|
41
|
+
...(c.manual_intro ? { manual_intro: c.manual_intro } : {}),
|
|
42
|
+
...(c.auto_bio ? { auto_bio: c.auto_bio } : {}),
|
|
43
|
+
...(c.match_reason ? { match_reason: c.match_reason } : {}),
|
|
41
44
|
})),
|
|
42
45
|
total: res.candidates.length,
|
|
43
46
|
query_intent: intent,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AnyAgentTool } from "../types.js";
|
|
3
|
+
import api from "../api.js";
|
|
4
|
+
|
|
5
|
+
export function createSearchByNameTool(): AnyAgentTool {
|
|
6
|
+
return {
|
|
7
|
+
name: "clawsocial_search_by_name",
|
|
8
|
+
label: "ClawSocial 按名字搜索",
|
|
9
|
+
description:
|
|
10
|
+
"Search for a specific person by name. Use this when the user mentions someone by name (e.g. '找虾杰伦', '联系小明'). Do NOT use clawsocial_search (interest search) for this case. Check local contacts first via clawsocial_lookup_contact before calling this.",
|
|
11
|
+
parameters: Type.Object({
|
|
12
|
+
name: Type.String({ description: "要搜索的名字(支持部分匹配)" }),
|
|
13
|
+
}),
|
|
14
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
15
|
+
const name = params.name as string;
|
|
16
|
+
if (!name) throw new Error("name 不能为空");
|
|
17
|
+
|
|
18
|
+
const res = await api.searchByName(name);
|
|
19
|
+
|
|
20
|
+
if (!res.candidates || res.candidates.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
23
|
+
candidates: [],
|
|
24
|
+
message: `未找到名字包含"${name}"的用户。`,
|
|
25
|
+
})}],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result = {
|
|
30
|
+
candidates: res.candidates.map((c) => ({
|
|
31
|
+
agent_id: c.agent_id,
|
|
32
|
+
public_name: c.public_name,
|
|
33
|
+
topic_tags: c.topic_tags,
|
|
34
|
+
availability: c.availability,
|
|
35
|
+
...(c.manual_intro ? { manual_intro: c.manual_intro } : {}),
|
|
36
|
+
...(c.auto_bio ? { auto_bio: c.auto_bio } : {}),
|
|
37
|
+
match_reason: c.match_reason,
|
|
38
|
+
})),
|
|
39
|
+
total: res.candidates.length,
|
|
40
|
+
};
|
|
41
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
42
|
+
},
|
|
43
|
+
} as AnyAgentTool;
|
|
44
|
+
}
|
|
@@ -4,19 +4,30 @@ import path from "node:path";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import type { AnyAgentTool } from "../types.js";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type LocalFiles = { soul: string; memory: string; user: string };
|
|
8
|
+
|
|
9
|
+
function readLocalFiles(): LocalFiles {
|
|
8
10
|
const home = os.homedir();
|
|
9
|
-
const
|
|
10
|
-
path.join(home, ".openclaw", "workspace"
|
|
11
|
-
path.join(home, ".clawdbot", "workspace"
|
|
11
|
+
const bases = [
|
|
12
|
+
path.join(home, ".openclaw", "workspace"),
|
|
13
|
+
path.join(home, ".clawdbot", "workspace"),
|
|
12
14
|
];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
function readFirst(relPath: string): string {
|
|
17
|
+
for (const base of bases) {
|
|
18
|
+
try {
|
|
19
|
+
const content = fs.readFileSync(path.join(base, relPath), "utf8");
|
|
20
|
+
if (content.trim()) return content;
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
return "";
|
|
18
24
|
}
|
|
19
|
-
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
soul: readFirst("SOUL.md"),
|
|
28
|
+
memory: readFirst("memory/MEMORY.md"),
|
|
29
|
+
user: readFirst("USER.md"),
|
|
30
|
+
};
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
export function createSuggestProfileTool(): AnyAgentTool {
|
|
@@ -31,30 +42,39 @@ export function createSuggestProfileTool(): AnyAgentTool {
|
|
|
31
42
|
"NEVER update the profile silently.",
|
|
32
43
|
parameters: Type.Object({}),
|
|
33
44
|
async execute(_id: string, _params: Record<string, unknown>) {
|
|
34
|
-
const
|
|
35
|
-
|
|
45
|
+
const files = readLocalFiles();
|
|
46
|
+
const found = [files.soul, files.memory, files.user].filter(Boolean);
|
|
47
|
+
const completeness = [0.1, 0.4, 0.7, 1.0][found.length];
|
|
48
|
+
|
|
49
|
+
if (found.length === 0) {
|
|
36
50
|
return {
|
|
37
51
|
content: [
|
|
38
52
|
{
|
|
39
53
|
type: "text",
|
|
40
54
|
text: JSON.stringify({
|
|
41
|
-
|
|
42
|
-
message:
|
|
43
|
-
"No memory file found. Please ask the user to describe their interests directly.",
|
|
55
|
+
files_found: 0,
|
|
56
|
+
message: "No local OpenClaw files found. Please ask the user to describe their interests directly.",
|
|
44
57
|
}),
|
|
45
58
|
},
|
|
46
59
|
],
|
|
47
60
|
};
|
|
48
61
|
}
|
|
62
|
+
|
|
49
63
|
return {
|
|
50
64
|
content: [
|
|
51
65
|
{
|
|
52
66
|
type: "text",
|
|
53
67
|
text: JSON.stringify({
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
files_found: found.length,
|
|
69
|
+
completeness_score: completeness,
|
|
70
|
+
soul: files.soul || null,
|
|
71
|
+
memory: files.memory || null,
|
|
72
|
+
user: files.user || null,
|
|
56
73
|
instruction:
|
|
57
|
-
"
|
|
74
|
+
"Extract interest topics, personality traits, work style, and focus areas from these files. " +
|
|
75
|
+
"Strip all names, companies, locations, and credentials. " +
|
|
76
|
+
"Draft a 2-3 sentence description. Show it to the user and ask for confirmation. " +
|
|
77
|
+
"Only call clawsocial_update_profile after explicit user approval. Pass: auto_bio (the drafted description) and topic_tags (array of extracted interest keywords, e.g. [\"AI\", \"Web3\", \"product design\"]). Do NOT use interest_text. Completeness is calculated server-side — do not pass completeness_score.",
|
|
58
78
|
}),
|
|
59
79
|
},
|
|
60
80
|
],
|
|
@@ -14,10 +14,17 @@ export function createUpdateProfileTool(): AnyAgentTool {
|
|
|
14
14
|
interest_text: Type.Optional(
|
|
15
15
|
Type.String({
|
|
16
16
|
description:
|
|
17
|
-
"
|
|
17
|
+
"User's own typed description of themselves — shown to others as self-intro. " +
|
|
18
18
|
"E.g. 'I'm a designer interested in AI art, generative music, and creative coding.'",
|
|
19
19
|
}),
|
|
20
20
|
),
|
|
21
|
+
auto_bio: Type.Optional(
|
|
22
|
+
Type.String({
|
|
23
|
+
description:
|
|
24
|
+
"Interest description extracted from local OpenClaw files (not typed by user directly). " +
|
|
25
|
+
"Use this instead of interest_text when the content comes from SOUL.md / MEMORY.md / USER.md.",
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
21
28
|
topic_tags: Type.Optional(
|
|
22
29
|
Type.Array(Type.String(), {
|
|
23
30
|
description:
|
|
@@ -52,6 +59,7 @@ export function createUpdateProfileTool(): AnyAgentTool {
|
|
|
52
59
|
|
|
53
60
|
const body: Record<string, unknown> = {};
|
|
54
61
|
if (params.interest_text) body.interest_text = params.interest_text;
|
|
62
|
+
if (params.auto_bio) body.auto_bio = params.auto_bio;
|
|
55
63
|
if (params.topic_tags) body.topic_tags = params.topic_tags;
|
|
56
64
|
if (params.public_name) body.public_name = params.public_name;
|
|
57
65
|
if (params.availability) body.availability = params.availability;
|