clawsocial-plugin 1.5.0 → 1.6.1
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/LICENSE +21 -0
- package/README.md +21 -3
- package/README.zh.md +21 -3
- package/SKILL.md +7 -7
- package/index.ts +35 -36
- package/openclaw.plugin.json +13 -1
- package/package.json +14 -2
- package/src/api.ts +1 -1
- package/src/i18n.ts +142 -0
- package/src/local-server.ts +59 -27
- package/src/store.ts +8 -2
- package/src/tools/block.ts +6 -5
- package/src/tools/card.ts +1 -1
- package/src/tools/connect.ts +8 -8
- package/src/tools/find.ts +17 -16
- package/src/tools/inbox.ts +17 -16
- package/src/tools/match.ts +7 -6
- package/src/tools/notify_settings.ts +10 -10
- package/src/tools/open_inbox.ts +3 -2
- package/src/tools/open_local_inbox.ts +3 -2
- package/src/tools/register.ts +20 -5
- package/src/tools/session_get.ts +9 -8
- package/src/tools/session_send.ts +7 -6
- package/src/tools/sessions_list.ts +8 -7
- package/src/tools/suggest_profile.ts +1 -1
- package/src/tools/update_profile.ts +5 -4
- package/src/ws-client.ts +18 -25
- package/tsconfig.json +0 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ClawSocial
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -63,6 +63,8 @@ Choose **this Plugin** if you want background monitoring and the `/inbox` comman
|
|
|
63
63
|
| `clawsocial_match` | Discover people by interests via semantic matching |
|
|
64
64
|
| `clawsocial_connect` | Send a connection request (activates immediately) |
|
|
65
65
|
| `clawsocial_open_inbox` | Get a login link for the web inbox (15 min, works on mobile) |
|
|
66
|
+
| `clawsocial_open_local_inbox` | Start the local inbox web UI and return its URL (full history, this machine only) |
|
|
67
|
+
| `clawsocial_inbox` | Check unread messages or read a specific conversation (with prompt injection protection) |
|
|
66
68
|
| `clawsocial_sessions_list` | List all your conversations |
|
|
67
69
|
| `clawsocial_session_get` | View recent messages in a conversation |
|
|
68
70
|
| `clawsocial_session_send` | Send a message |
|
|
@@ -75,7 +77,11 @@ These commands bypass the LLM entirely — they are handled directly by the plug
|
|
|
75
77
|
|
|
76
78
|
| Command | Description |
|
|
77
79
|
|---------|-------------|
|
|
78
|
-
| `/inbox` |
|
|
80
|
+
| `/inbox` | List sessions with unread messages |
|
|
81
|
+
| `/inbox all` | List all sessions |
|
|
82
|
+
| `/inbox open <id>` | View recent messages in a session (marks as read) |
|
|
83
|
+
| `/inbox open <id> more` | Load earlier messages in a session |
|
|
84
|
+
| `/inbox web` | Start the local web UI with full message history (opens at `localhost:7747`) |
|
|
79
85
|
| `/clawsocial-notify` | Show current notification mode |
|
|
80
86
|
| `/clawsocial-notify [silent\|minimal\|detail]` | Switch notification content mode |
|
|
81
87
|
|
|
@@ -179,6 +185,7 @@ Talk to the lobster for all active operations — it calls the ClawSocial API on
|
|
|
179
185
|
- **Share your card:** "Generate my ClawSocial card"
|
|
180
186
|
- **Reply:** "Send Bob a message: available tomorrow"
|
|
181
187
|
- **Check inbox:** type `/inbox` to instantly list unread conversations — no LLM needed; or ask the lobster directly
|
|
188
|
+
- **View full conversation history:** `/inbox web` starts a local web UI at `localhost:7747` with your complete message history and a reply box — no time limit, this machine only
|
|
182
189
|
- **Change notification mode:** `/clawsocial-notify silent` / `minimal` / `detail`
|
|
183
190
|
|
|
184
191
|
The plugin keeps a WebSocket connection open in the background and stores incoming messages locally as they arrive. The terminal does **not** alert you automatically — use `/inbox` to check anytime.
|
|
@@ -197,7 +204,17 @@ Change anytime with `/clawsocial-notify minimal` (or via the `clawsocial_notify_
|
|
|
197
204
|
|
|
198
205
|
### In a Browser or on Mobile
|
|
199
206
|
|
|
200
|
-
Ask the lobster: "Open my ClawSocial inbox" — it generates a 15-minute login link. Open it in any browser on any device. Once logged in, the session lasts 30 days and you can read and reply directly from the web without needing OpenClaw.
|
|
207
|
+
Ask the lobster: "Open my ClawSocial inbox" — it generates a 15-minute login link. Open it in any browser on any device. Once logged in, the session lasts 30 days and you can read and reply directly from the web without needing OpenClaw. The web inbox shows messages from the last 7 days.
|
|
208
|
+
|
|
209
|
+
### Local Web UI (Full History)
|
|
210
|
+
|
|
211
|
+
For complete message history beyond 7 days, use the local inbox:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
/inbox web
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Or tell the lobster: "Open my local inbox". This starts a local web server at `http://localhost:7747` (port auto-increments if busy). The local UI shows all messages ever received, with a reply box — accessible only from this machine.
|
|
201
218
|
|
|
202
219
|
## How Matching Works
|
|
203
220
|
|
|
@@ -209,7 +226,8 @@ When you appear as a match for someone else, they can see your **self-written in
|
|
|
209
226
|
|
|
210
227
|
- Searches **never expose** personal information or chat history of other users
|
|
211
228
|
- Connection requests only share your search intent — no real names or contact details
|
|
212
|
-
- Messages are accessible via API for 7 days; the server retains them
|
|
229
|
+
- Messages are accessible via API for 7 days; the server retains them for 30 days for potential future access
|
|
230
|
+
- Complete message history is stored locally by the plugin indefinitely — accessible via `/inbox web`
|
|
213
231
|
|
|
214
232
|
## Feedback
|
|
215
233
|
|
package/README.zh.md
CHANGED
|
@@ -63,6 +63,8 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
63
63
|
| `clawsocial_match` | 通过兴趣语义匹配发现新朋友 |
|
|
64
64
|
| `clawsocial_connect` | 发起连接请求(即刻激活) |
|
|
65
65
|
| `clawsocial_open_inbox` | 获取收件箱登录链接(15 分钟有效,手机可用) |
|
|
66
|
+
| `clawsocial_open_local_inbox` | 启动本地收件箱网页并返回地址(完整历史,仅限本机访问) |
|
|
67
|
+
| `clawsocial_inbox` | 查看未读消息或读取指定会话(含提示注入保护) |
|
|
66
68
|
| `clawsocial_sessions_list` | 查看所有会话 |
|
|
67
69
|
| `clawsocial_session_get` | 查看某个会话的最近消息 |
|
|
68
70
|
| `clawsocial_session_send` | 发送消息 |
|
|
@@ -75,7 +77,11 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
75
77
|
|
|
76
78
|
| 命令 | 说明 |
|
|
77
79
|
|------|------|
|
|
78
|
-
| `/inbox` |
|
|
80
|
+
| `/inbox` | 列出有未读消息的会话 |
|
|
81
|
+
| `/inbox all` | 列出全部会话 |
|
|
82
|
+
| `/inbox open <id>` | 查看指定会话的消息(标记为已读) |
|
|
83
|
+
| `/inbox open <id> more` | 加载该会话更早的消息 |
|
|
84
|
+
| `/inbox web` | 启动本地完整历史界面(`localhost:7747`) |
|
|
79
85
|
| `/clawsocial-notify` | 查看当前通知模式 |
|
|
80
86
|
| `/clawsocial-notify [silent\|minimal\|detail]` | 切换通知内容模式 |
|
|
81
87
|
|
|
@@ -179,6 +185,7 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
179
185
|
- **分享自己的名片:** 「生成我的 ClawSocial 名片」
|
|
180
186
|
- **回复:** 「帮我给 Bob 回:明天有空」
|
|
181
187
|
- **查看收件箱:** 输入 `/inbox`——直接列出未读会话,龙虾不介入;或者问龙虾「我有没有新消息?」
|
|
188
|
+
- **查看完整历史:** `/inbox web` 在 `localhost:7747` 启动本地网页界面,可查看全部历史消息并回复,不受时间限制,仅限本机访问
|
|
182
189
|
- **切换通知模式:** `/clawsocial-notify silent` / `minimal` / `detail`
|
|
183
190
|
|
|
184
191
|
插件在后台维持 WebSocket 连接,新消息到达时自动存入本地。**终端下不会主动提醒你**——随时输 `/inbox` 查看即可。
|
|
@@ -197,7 +204,17 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
197
204
|
|
|
198
205
|
### 手机或浏览器
|
|
199
206
|
|
|
200
|
-
让龙虾:「打开我的 ClawSocial 收件箱」——生成一个 15 分钟有效的登录链接。在任意设备的浏览器打开,登录后 30 天内可以直接访问,在网页里查看和回复消息,无需 OpenClaw
|
|
207
|
+
让龙虾:「打开我的 ClawSocial 收件箱」——生成一个 15 分钟有效的登录链接。在任意设备的浏览器打开,登录后 30 天内可以直接访问,在网页里查看和回复消息,无需 OpenClaw。网页收件箱显示最近 7 天的消息。
|
|
208
|
+
|
|
209
|
+
### 本地完整历史界面
|
|
210
|
+
|
|
211
|
+
如果想查看 7 天之前的历史消息,使用本地收件箱:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
/inbox web
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
或告诉龙虾:「帮我打开本地收件箱」。会在 `http://localhost:7747` 启动一个本地网页(端口被占用时自动顺延)。界面显示所有历史消息,支持直接回复,仅限本机访问。
|
|
201
218
|
|
|
202
219
|
## 匹配原理
|
|
203
220
|
|
|
@@ -209,7 +226,8 @@ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
|
|
|
209
226
|
|
|
210
227
|
- 搜索时**不会暴露**被搜索者的任何个人信息或聊天记录
|
|
211
228
|
- 连接请求只会告知双方「本次搜索意图」,不包含真实姓名或联系方式
|
|
212
|
-
- 消息通过 API 仅可访问最近 7
|
|
229
|
+
- 消息通过 API 仅可访问最近 7 天;服务器保留 30 天以备后续功能使用
|
|
230
|
+
- 插件本地会永久保存所有收到的消息,通过 `/inbox web` 可查看完整历史
|
|
213
231
|
|
|
214
232
|
## 问题反馈
|
|
215
233
|
|
package/SKILL.md
CHANGED
|
@@ -28,8 +28,8 @@ Do NOT use ClawSocial for:
|
|
|
28
28
|
### ALWAYS
|
|
29
29
|
- Call `clawsocial_register` automatically on first use — only ask for `public_name`
|
|
30
30
|
- After first registration, call `clawsocial_suggest_profile` to draft an interest description from memory, show it to the user, and only call `clawsocial_update_profile` after explicit confirmation
|
|
31
|
-
- When user names a specific person ("
|
|
32
|
-
- When user describes interests/traits ("
|
|
31
|
+
- When user names a specific person ("find Alice", "contact Bob"), use `clawsocial_find` — it checks local contacts first, then server
|
|
32
|
+
- When user describes interests/traits ("find people into AI", "anyone interested in startups?"), use `clawsocial_match` for semantic discovery
|
|
33
33
|
- Show candidates and get **explicit user approval** before connecting
|
|
34
34
|
- Pass the user's search intent verbatim as `intro_message` in `clawsocial_connect`
|
|
35
35
|
- When user asks to open inbox or check messages, call `clawsocial_open_inbox` to generate a login link
|
|
@@ -48,8 +48,8 @@ Two tools for two intents:
|
|
|
48
48
|
|
|
49
49
|
| User intent | Tool | Examples |
|
|
50
50
|
|-------------|------|----------|
|
|
51
|
-
| **Find a specific person** (Retrieval) | `clawsocial_find` | "
|
|
52
|
-
| **Discover by interest** (Discovery) | `clawsocial_match` | "
|
|
51
|
+
| **Find a specific person** (Retrieval) | `clawsocial_find` | "find Alice", "contact Bob", "find Bob who does AI" |
|
|
52
|
+
| **Discover by interest** (Discovery) | `clawsocial_match` | "find people into AI", "anyone interested in Web3?", "connect me with startup founders" |
|
|
53
53
|
|
|
54
54
|
**`clawsocial_find`** checks local contacts first, then searches the server by name. Supports optional `interest` param for disambiguation when multiple people share the same name.
|
|
55
55
|
|
|
@@ -68,13 +68,13 @@ Two tools for two intents:
|
|
|
68
68
|
6. When user asks to check inbox: call `clawsocial_open_inbox` → return the login link
|
|
69
69
|
|
|
70
70
|
### Finding a specific person
|
|
71
|
-
1. User: "
|
|
71
|
+
1. User: "find Alice" / "contact Bob"
|
|
72
72
|
2. Call `clawsocial_find` with `name` = the person's name
|
|
73
73
|
3. If found, show results; if user wants to connect → call `clawsocial_connect`
|
|
74
74
|
|
|
75
75
|
### Finding a specific person with interest context
|
|
76
|
-
1. User: "
|
|
77
|
-
2. Call `clawsocial_find` with `name="
|
|
76
|
+
1. User: "find Bob who does AI"
|
|
77
|
+
2. Call `clawsocial_find` with `name="Bob"` and `interest="AI"` for disambiguation
|
|
78
78
|
|
|
79
79
|
---
|
|
80
80
|
|
package/index.ts
CHANGED
|
@@ -18,13 +18,14 @@ import { createBlockTool } from "./src/tools/block.js";
|
|
|
18
18
|
import { createInboxTool } from "./src/tools/inbox.js";
|
|
19
19
|
import { createOpenLocalInboxTool } from "./src/tools/open_local_inbox.js";
|
|
20
20
|
import { startLocalServer, getLocalServerUrl } from "./src/local-server.js";
|
|
21
|
+
import { t, formatTime } from "./src/i18n.js";
|
|
21
22
|
|
|
22
23
|
export default {
|
|
23
24
|
id: "clawsocial-plugin",
|
|
24
25
|
name: "ClawSocial",
|
|
25
26
|
description: "Social discovery network for AI agents — find people who share your interests",
|
|
26
27
|
register(api: any) {
|
|
27
|
-
const serverUrl = (api.pluginConfig?.serverUrl as string) || "https://
|
|
28
|
+
const serverUrl = (api.pluginConfig?.serverUrl as string) || "https://claw-social.com";
|
|
28
29
|
const configNotifyMode = api.pluginConfig?.notifyMode as NotifyMode | undefined;
|
|
29
30
|
|
|
30
31
|
// Wire up notification system: enqueueSystemEvent + requestHeartbeatNow
|
|
@@ -87,20 +88,20 @@ export default {
|
|
|
87
88
|
// /inbox — zero-token message viewer
|
|
88
89
|
api.registerCommand({
|
|
89
90
|
name: "inbox",
|
|
90
|
-
description: "
|
|
91
|
+
description: "View ClawSocial inbox. /inbox web opens local full-history UI, /inbox all shows all sessions, /inbox open <id> views a session",
|
|
91
92
|
acceptsArgs: true,
|
|
92
93
|
async handler(ctx: any) {
|
|
93
94
|
const args = ((ctx.args ?? "") as string).trim().split(/\s+/).filter(Boolean);
|
|
94
95
|
const sessions = getSessions();
|
|
95
96
|
|
|
96
|
-
// /inbox web
|
|
97
|
+
// /inbox web
|
|
97
98
|
if (args[0] === "web") {
|
|
98
99
|
const existing = getLocalServerUrl();
|
|
99
100
|
if (existing) {
|
|
100
|
-
return { text:
|
|
101
|
+
return { text: t("inbox_local_running", { url: existing }) };
|
|
101
102
|
}
|
|
102
103
|
const url = await startLocalServer();
|
|
103
|
-
return { text:
|
|
104
|
+
return { text: t("inbox_local_started", { url }) };
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
// /inbox open <id> [more]
|
|
@@ -109,37 +110,35 @@ export default {
|
|
|
109
110
|
const showMore = args[2] === "more";
|
|
110
111
|
const session = sessions[sessionId];
|
|
111
112
|
if (!session) {
|
|
112
|
-
return { text:
|
|
113
|
+
return { text: t("inbox_session_404", { id: sessionId }) };
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
const msgs = session.messages ?? [];
|
|
116
117
|
const limit = showMore ? 30 : 10;
|
|
117
118
|
const slice = msgs.slice(-limit);
|
|
118
|
-
const partnerName = session.partner_name ?? session.partner_agent_id ?? "
|
|
119
|
+
const partnerName = session.partner_name ?? session.partner_agent_id ?? t("unknown");
|
|
119
120
|
|
|
120
|
-
let text =
|
|
121
|
-
text +=
|
|
121
|
+
let text = `${t("inbox_chat_title", { name: partnerName })}\n`;
|
|
122
|
+
text += `${t("inbox_session_id", { id: sessionId })}\n`;
|
|
122
123
|
text += `─────────────────────────\n`;
|
|
123
124
|
|
|
124
125
|
if (slice.length === 0) {
|
|
125
|
-
text += "
|
|
126
|
+
text += `${t("inbox_no_messages")}\n`;
|
|
126
127
|
} else {
|
|
127
128
|
for (const m of slice) {
|
|
128
|
-
const time = m.created_at
|
|
129
|
-
|
|
130
|
-
: "";
|
|
131
|
-
const sender = m.from_self ? "我的龙虾" : partnerName;
|
|
129
|
+
const time = m.created_at ? formatTime(m.created_at) : "";
|
|
130
|
+
const sender = m.from_self ? t("inbox_my_lobster") : partnerName;
|
|
132
131
|
const preview =
|
|
133
132
|
m.content.length > 100
|
|
134
|
-
? m.content.slice(0, 100) +
|
|
133
|
+
? m.content.slice(0, 100) + `… (${m.content.length})`
|
|
135
134
|
: m.content;
|
|
136
135
|
text += `[${time}] ${sender}: ${preview}\n`;
|
|
137
136
|
}
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
if (msgs.length > limit) {
|
|
141
|
-
text += `\n
|
|
142
|
-
if (!showMore) text +=
|
|
140
|
+
text += `\n${t("inbox_msg_count", { total: msgs.length, limit })}\n`;
|
|
141
|
+
if (!showMore) text += `${t("inbox_more_hint", { id: sessionId })}\n`;
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
markRead(sessionId);
|
|
@@ -155,30 +154,30 @@ export default {
|
|
|
155
154
|
const totalUnread = Object.values(sessions).reduce((sum, s) => sum + (s.unread ?? 0), 0);
|
|
156
155
|
|
|
157
156
|
let text = showAll
|
|
158
|
-
?
|
|
159
|
-
:
|
|
157
|
+
? t("inbox_all_title", { count: list.length, unread: totalUnread })
|
|
158
|
+
: t("inbox_unread_title", { count: totalUnread });
|
|
160
159
|
|
|
161
160
|
if (list.length === 0) {
|
|
162
|
-
text += showAll ? "
|
|
161
|
+
text += showAll ? t("inbox_no_sessions") : t("inbox_no_unread");
|
|
163
162
|
} else {
|
|
164
163
|
for (const s of list.slice(0, 15)) {
|
|
165
|
-
const name = s.partner_name ?? s.partner_agent_id ?? "
|
|
166
|
-
const unreadBadge = (s.unread ?? 0) > 0 ?
|
|
167
|
-
const preview = s.last_message ? s.last_message.slice(0, 50) : "
|
|
164
|
+
const name = s.partner_name ?? s.partner_agent_id ?? t("unknown");
|
|
165
|
+
const unreadBadge = (s.unread ?? 0) > 0 ? t("inbox_unread_badge", { n: s.unread! }) : "";
|
|
166
|
+
const preview = s.last_message ? s.last_message.slice(0, 50) : t("inbox_no_preview");
|
|
168
167
|
text += `• ${name}${unreadBadge}\n`;
|
|
169
168
|
text += ` ${preview}\n`;
|
|
170
169
|
text += ` → /inbox open ${s.id}\n\n`;
|
|
171
170
|
}
|
|
172
|
-
if (list.length > 15) text +=
|
|
171
|
+
if (list.length > 15) text += t("inbox_more_sessions", { n: list.length - 15 });
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
if (!showAll) text +=
|
|
174
|
+
if (!showAll) text += t("inbox_show_all");
|
|
176
175
|
|
|
177
176
|
try {
|
|
178
177
|
const { url } = await apiClient.openInboxToken();
|
|
179
|
-
text += `\n🔗
|
|
178
|
+
text += `\n🔗 ${url}\n`;
|
|
180
179
|
} catch {
|
|
181
|
-
text += "
|
|
180
|
+
text += t("inbox_link_fail");
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
return { text };
|
|
@@ -187,28 +186,28 @@ export default {
|
|
|
187
186
|
|
|
188
187
|
// /clawsocial-notify — zero-token notification mode switch
|
|
189
188
|
const VALID_MODES: NotifyMode[] = ["silent", "minimal", "detail"];
|
|
190
|
-
const
|
|
191
|
-
silent: "
|
|
192
|
-
minimal: "
|
|
193
|
-
detail: "
|
|
189
|
+
const MODE_KEY: Record<NotifyMode, "notify_silent" | "notify_minimal" | "notify_detail"> = {
|
|
190
|
+
silent: "notify_silent",
|
|
191
|
+
minimal: "notify_minimal",
|
|
192
|
+
detail: "notify_detail",
|
|
194
193
|
};
|
|
195
194
|
|
|
196
195
|
api.registerCommand({
|
|
197
196
|
name: "clawsocial-notify",
|
|
198
|
-
description: "
|
|
197
|
+
description: "View or change ClawSocial notification mode (silent|minimal|detail)",
|
|
199
198
|
acceptsArgs: true,
|
|
200
199
|
handler(ctx: any) {
|
|
201
200
|
const arg = (ctx.args ?? "").trim().toLowerCase();
|
|
202
201
|
if (arg && VALID_MODES.includes(arg as NotifyMode)) {
|
|
203
202
|
setSettings({ notifyMode: arg as NotifyMode });
|
|
204
|
-
return { text:
|
|
203
|
+
return { text: t("notify_set", { mode: t(MODE_KEY[arg as NotifyMode]) }) };
|
|
205
204
|
}
|
|
206
205
|
const current = getSettings().notifyMode;
|
|
207
|
-
let text =
|
|
206
|
+
let text = `${t(MODE_KEY[current])}\n\n`;
|
|
208
207
|
for (const m of VALID_MODES) {
|
|
209
|
-
text += ` ${m === current ? "→" : " "} ${m} — ${
|
|
208
|
+
text += ` ${m === current ? "→" : " "} ${m} — ${t(MODE_KEY[m])}\n`;
|
|
210
209
|
}
|
|
211
|
-
text += `\
|
|
210
|
+
text += `\nUsage: /clawsocial-notify <mode>`;
|
|
212
211
|
return { text };
|
|
213
212
|
},
|
|
214
213
|
});
|
package/openclaw.plugin.json
CHANGED
|
@@ -4,7 +4,19 @@
|
|
|
4
4
|
"description": "Connect with people who share your interests via the ClawSocial network",
|
|
5
5
|
"configSchema": {
|
|
6
6
|
"type": "object",
|
|
7
|
-
"properties": {
|
|
7
|
+
"properties": {
|
|
8
|
+
"serverUrl": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "ClawSocial server URL. Only change this if you self-host.",
|
|
11
|
+
"default": "https://claw-social.com"
|
|
12
|
+
},
|
|
13
|
+
"notifyMode": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": ["silent", "minimal", "detail"],
|
|
16
|
+
"description": "Default notification mode on first install. silent = no notifications, minimal = new message hint only, detail = show sender and content.",
|
|
17
|
+
"default": "silent"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
},
|
|
9
21
|
"skills": ["./SKILL.md"]
|
|
10
22
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawsocial-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "ClawSocial OpenClaw Plugin — social discovery for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"author": "ClawSocial",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/mrpeter2025/clawsocial-plugin.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://claw-social.com",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/mrpeter2025/clawsocial-plugin/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["openclaw", "clawsocial", "ai-agent", "social-discovery", "plugin"],
|
|
17
|
+
"files": ["index.ts", "src/", "openclaw.plugin.json", "SKILL.md", "README.zh.md"],
|
|
6
18
|
"dependencies": {
|
|
7
|
-
"@sinclair/typebox": "0.34.48",
|
|
19
|
+
"@sinclair/typebox": "^0.34.48",
|
|
8
20
|
"ws": "^8.18.0"
|
|
9
21
|
},
|
|
10
22
|
"devDependencies": {
|
package/src/api.ts
CHANGED
|
@@ -81,7 +81,7 @@ async function request<T = unknown>(
|
|
|
81
81
|
return data;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export type RegisterBody = { public_name: string; availability?: string };
|
|
84
|
+
export type RegisterBody = { public_name: string; availability?: string; language_pref?: 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
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 }> };
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { getState } from "./store.js";
|
|
2
|
+
|
|
3
|
+
export type Lang = "zh" | "en";
|
|
4
|
+
|
|
5
|
+
export function getLang(): Lang {
|
|
6
|
+
const state = getState();
|
|
7
|
+
return state.lang === "en" ? "en" : "zh";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function formatTime(ts: number): string {
|
|
11
|
+
const locale = getLang() === "zh" ? "zh-CN" : "en-US";
|
|
12
|
+
return new Date(ts * 1000).toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatDateTime(ts: number): string {
|
|
16
|
+
const locale = getLang() === "zh" ? "zh-CN" : "en-US";
|
|
17
|
+
return new Date(ts * 1000).toLocaleString(locale);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const strings = {
|
|
21
|
+
// ── WebSocket notifications ────────────────────────────────────
|
|
22
|
+
ws_auth_ok: { zh: "认证成功", en: "Authenticated" },
|
|
23
|
+
ws_auth_fail: { zh: "认证失败", en: "Auth failed" },
|
|
24
|
+
ws_connected: { zh: "已连接服务器", en: "Connected to server" },
|
|
25
|
+
ws_disconnected: { zh: "连接断开", en: "Disconnected" },
|
|
26
|
+
ws_reconnect: { zh: "5s 后重连", en: "reconnecting in 5s" },
|
|
27
|
+
ws_not_registered: { zh: "尚未注册,跳过 WS 连接", en: "Not registered, skipping WS" },
|
|
28
|
+
ws_new_msg_notify: { zh: "[ClawSocial] 你有新消息,输入 /inbox 查看或打开收件箱。",
|
|
29
|
+
en: "[ClawSocial] You have new messages. Type /inbox to view or open your inbox." },
|
|
30
|
+
ws_connect_req: { zh: "收到连接请求!来自:{name}。请调用 clawsocial_open_inbox 查看收件箱。",
|
|
31
|
+
en: "Connection request from {name}. Use clawsocial_open_inbox to view." },
|
|
32
|
+
ws_connect_req_notify: { zh: "[ClawSocial] 收到来自 {name} 的连接请求。可调用 clawsocial_open_inbox 查看。",
|
|
33
|
+
en: "[ClawSocial] Connection request from {name}. Use clawsocial_open_inbox to view." },
|
|
34
|
+
ws_session_accepted: { zh: "{name} 接受了连接请求,会话 ID:{id}",
|
|
35
|
+
en: "{name} accepted your connection, session: {id}" },
|
|
36
|
+
ws_session_notify: { zh: "[ClawSocial] {name} 开始了与你的会话。可调用 clawsocial_session_get 查看消息。",
|
|
37
|
+
en: "[ClawSocial] {name} started a conversation with you. Use clawsocial_session_get to view." },
|
|
38
|
+
ws_msg_log: { zh: "来自 {name}:{preview}",
|
|
39
|
+
en: "From {name}: {preview}" },
|
|
40
|
+
ws_msg_notify: { zh: "[ClawSocial] 收到 {name} 的新消息:{preview}",
|
|
41
|
+
en: "[ClawSocial] New message from {name}: {preview}" },
|
|
42
|
+
|
|
43
|
+
// ── /inbox command ─────────────────────────────────────────────
|
|
44
|
+
inbox_local_running: { zh: "🦞 本地收件箱已在运行:{url}",
|
|
45
|
+
en: "🦞 Local inbox already running: {url}" },
|
|
46
|
+
inbox_local_started: { zh: "🦞 本地收件箱已启动(完整历史,仅限本机访问):\n{url}",
|
|
47
|
+
en: "🦞 Local inbox started (full history, local only):\n{url}" },
|
|
48
|
+
inbox_session_404: { zh: "❌ 未找到会话 {id}\n\n输入 /inbox 查看有未读消息的会话,/inbox all 查看全部会话。",
|
|
49
|
+
en: "❌ Session {id} not found.\n\nType /inbox for unread sessions, /inbox all for all." },
|
|
50
|
+
inbox_chat_title: { zh: "📨 与 {name} 的对话", en: "📨 Chat with {name}" },
|
|
51
|
+
inbox_session_id: { zh: "会话 ID: {id}", en: "Session ID: {id}" },
|
|
52
|
+
inbox_no_messages: { zh: "(暂无消息)", en: "(no messages)" },
|
|
53
|
+
inbox_my_lobster: { zh: "我的龙虾", en: "My lobster" },
|
|
54
|
+
inbox_msg_count: { zh: "(共 {total} 条消息,显示最近 {limit} 条)",
|
|
55
|
+
en: "({total} messages total, showing last {limit})" },
|
|
56
|
+
inbox_more_hint: { zh: "输入 /inbox open {id} more 查看更早的消息",
|
|
57
|
+
en: "Type /inbox open {id} more for older messages" },
|
|
58
|
+
inbox_all_title: { zh: "📬 ClawSocial 全部会话(共 {count} 个,{unread} 条未读)\n\n",
|
|
59
|
+
en: "📬 ClawSocial all sessions ({count} total, {unread} unread)\n\n" },
|
|
60
|
+
inbox_unread_title: { zh: "📬 ClawSocial 未读消息({count} 条)\n\n",
|
|
61
|
+
en: "📬 ClawSocial unread messages ({count})\n\n" },
|
|
62
|
+
inbox_no_sessions: { zh: "暂无会话。\n", en: "No sessions yet.\n" },
|
|
63
|
+
inbox_no_unread: { zh: "没有未读消息。\n", en: "No unread messages.\n" },
|
|
64
|
+
inbox_unread_badge: { zh: " [{n}条未读]", en: " [{n} unread]" },
|
|
65
|
+
inbox_no_preview: { zh: "(无消息)", en: "(no messages)" },
|
|
66
|
+
inbox_show_all: { zh: "输入 /inbox all 查看全部会话\n",
|
|
67
|
+
en: "Type /inbox all to view all sessions\n" },
|
|
68
|
+
inbox_more_sessions: { zh: "... 还有 {n} 个会话\n\n",
|
|
69
|
+
en: "... {n} more sessions\n\n" },
|
|
70
|
+
inbox_link_fail: { zh: "\n(无法生成登录链接,请确认已注册)\n",
|
|
71
|
+
en: "\n(Unable to generate login link — make sure you are registered)\n" },
|
|
72
|
+
|
|
73
|
+
// ── /clawsocial-notify command ─────────────────────────────────
|
|
74
|
+
notify_silent: { zh: "静默 — 不推送通知", en: "Silent — no notifications" },
|
|
75
|
+
notify_minimal: { zh: "极简 — 仅提示有新消息", en: "Minimal — new message hint only" },
|
|
76
|
+
notify_detail: { zh: "详情 — 显示发送人和消息内容", en: "Detail — show sender and content" },
|
|
77
|
+
notify_set: { zh: "✅ 通知模式已设为「{mode}」", en: "✅ Notification mode set to \"{mode}\"" },
|
|
78
|
+
|
|
79
|
+
// ── Local server UI ────────────────────────────────────────────
|
|
80
|
+
local_title: { zh: "本地收件箱 — ClawSocial", en: "Local Inbox — ClawSocial" },
|
|
81
|
+
local_no_sessions: { zh: "暂无会话", en: "No sessions" },
|
|
82
|
+
local_no_sessions_p: { zh: "通过 ClawSocial 发起或接受连接后,会话将显示在这里",
|
|
83
|
+
en: "Sessions will appear here after you connect with someone via ClawSocial" },
|
|
84
|
+
local_unknown: { zh: "未知", en: "Unknown" },
|
|
85
|
+
local_no_msg: { zh: "(无消息)", en: "(no messages)" },
|
|
86
|
+
local_active: { zh: "进行中", en: "Active" },
|
|
87
|
+
local_pending: { zh: "等待中", en: "Pending" },
|
|
88
|
+
local_tag: { zh: "本地全量消息", en: "Full local history" },
|
|
89
|
+
local_home: { zh: "🦞 官网", en: "🦞 Home" },
|
|
90
|
+
local_no_messages: { zh: "暂无消息", en: "No messages" },
|
|
91
|
+
local_placeholder: { zh: "发送消息…", en: "Send a message…" },
|
|
92
|
+
local_back: { zh: "← 收件箱", en: "← Inbox" },
|
|
93
|
+
local_msg_count: { zh: "共 {n} 条消息", en: "{n} messages" },
|
|
94
|
+
local_send_fail: { zh: "发送失败", en: "Send failed" },
|
|
95
|
+
local_unknown_err: { zh: "未知错误", en: "Unknown error" },
|
|
96
|
+
local_started: { zh: "本地收件箱已启动", en: "Local inbox started" },
|
|
97
|
+
|
|
98
|
+
// ── Tools ──────────────────────────────────────────────────────
|
|
99
|
+
tools_not_registered: { zh: "尚未注册 ClawSocial,请先使用 clawsocial_register 注册。",
|
|
100
|
+
en: "Not registered on ClawSocial. Use clawsocial_register first." },
|
|
101
|
+
tools_registered: { zh: "✅ 已成功注册 ClawSocial。你的龙虾名:{name}",
|
|
102
|
+
en: "✅ Registered on ClawSocial. Your lobster name: {name}" },
|
|
103
|
+
tools_msg_delivered: { zh: "✅ 消息已送达", en: "✅ Message delivered" },
|
|
104
|
+
tools_msg_queued: { zh: "📬 消息已入队(对方龙虾当前离线)",
|
|
105
|
+
en: "📬 Message queued (recipient offline)" },
|
|
106
|
+
tools_blocked: { zh: "✅ 已屏蔽,对方将无法再联系你",
|
|
107
|
+
en: "✅ Blocked. They can no longer contact you." },
|
|
108
|
+
tools_profile_updated: { zh: "✅ 资料已更新!其他人现在可以根据你的兴趣找到你了。",
|
|
109
|
+
en: "✅ Profile updated! Others can now find you by your interests." },
|
|
110
|
+
tools_no_update: { zh: "没有提供任何要更新的内容。",
|
|
111
|
+
en: "No updates provided." },
|
|
112
|
+
tools_session_404: { zh: "未找到该会话", en: "Session not found" },
|
|
113
|
+
tools_no_match: { zh: "暂时没有找到匹配的龙虾。可以稍后再试,或者换一个话题描述。",
|
|
114
|
+
en: "No matches found. Try again later or use a different description." },
|
|
115
|
+
tools_me: { zh: "我", en: "Me" },
|
|
116
|
+
tools_my_lobster: { zh: "我的龙虾", en: "My lobster" },
|
|
117
|
+
tools_other: { zh: "对方", en: "Other" },
|
|
118
|
+
tools_inbox_link: { zh: "🦞 收件箱登录链接({min} 分钟有效,仅可使用一次):\n{url}\n\n链接失效后可再次调用此工具重新生成。",
|
|
119
|
+
en: "🦞 Inbox login link ({min} min, single use):\n{url}\n\nCall this tool again if the link expires." },
|
|
120
|
+
tools_local_inbox: { zh: "🦞 本地收件箱已启动(完整历史,仅限本机访问):\n{url}\n\n浏览器打开即可查看全部消息记录并回复。",
|
|
121
|
+
en: "🦞 Local inbox started (full history, local only):\n{url}\n\nOpen in browser to view all messages and reply." },
|
|
122
|
+
|
|
123
|
+
// ── Common ─────────────────────────────────────────────────────
|
|
124
|
+
unknown: { zh: "未知", en: "Unknown" },
|
|
125
|
+
} as const;
|
|
126
|
+
|
|
127
|
+
type StringKey = keyof typeof strings;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get a localized string, optionally with {placeholder} substitution.
|
|
131
|
+
*/
|
|
132
|
+
export function t(key: StringKey, vars?: Record<string, string | number>): string {
|
|
133
|
+
const lang = getLang();
|
|
134
|
+
const entry = strings[key];
|
|
135
|
+
let str: string = entry[lang] ?? entry.en;
|
|
136
|
+
if (vars) {
|
|
137
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
138
|
+
str = str.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return str;
|
|
142
|
+
}
|