gewe-openclaw 2026.3.24 → 2026.3.26
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 +84 -4
- package/index.ts +2 -0
- package/package.json +1 -1
- package/skills/gewe-agent-tools/SKILL.md +40 -1
- package/src/api-tools.ts +15 -10
- package/src/config-schema.ts +12 -1
- package/src/delivery.ts +13 -7
- package/src/group-allowlist-tool.ts +11 -12
- package/src/group-binding-tool.ts +10 -4
- package/src/group-claim-tool.ts +103 -0
- package/src/inbound.ts +189 -21
- package/src/openclaw-compat.ts +17 -6
- package/src/pairing-store.ts +281 -0
- package/src/policy.ts +15 -6
- package/src/reply-options.ts +46 -6
- package/src/send.ts +2 -0
- package/src/tool-visibility.ts +27 -0
- package/src/types.ts +5 -2
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ openclaw onboard
|
|
|
95
95
|
- `s3PublicBaseUrl`:`public` 模式下用于拼接可访问 URL(必填)。
|
|
96
96
|
- `s3PresignExpiresSec`:`presigned` 模式签名有效期(默认 3600 秒)。
|
|
97
97
|
- `s3KeyPrefix`:对象 key 前缀(默认 `gewe-openclaw/outbound`)。
|
|
98
|
-
- `allowFrom`:允许私聊触发的微信 ID
|
|
98
|
+
- `allowFrom`:允许私聊触发的微信 ID;不再隐式用于群聊权限。
|
|
99
99
|
- `voiceAutoConvert`:自动将音频转为 silk(默认开启;设为 `false` 可关闭)。
|
|
100
100
|
- `silkAutoDownload`:自动下载 `rust-silk`(默认开启;可关闭后自行配置 `voiceSilkPath` / `voiceDecodePath`)。
|
|
101
101
|
- `silkVersion`:自动下载的 `rust-silk` 版本(`latest` 会自动清理旧版本)。
|
|
@@ -141,7 +141,12 @@ openclaw onboard
|
|
|
141
141
|
- `plain`:普通回复
|
|
142
142
|
- `quote_source`:首条回复自动引用当前入站消息
|
|
143
143
|
- `at_sender`:首条文本回复自动 `@` 发送者
|
|
144
|
-
|
|
144
|
+
|
|
145
|
+
兼容说明:
|
|
146
|
+
|
|
147
|
+
- `quote_and_at` 已下线,不再建议继续配置
|
|
148
|
+
- 历史配置里的 `quote_and_at` 仍会被兼容读取,但会自动降级为 `quote_source + 可见 @昵称前缀`
|
|
149
|
+
- 这个兼容降级不是微信原生 `@`,只是为了避免旧配置直接失效
|
|
145
150
|
|
|
146
151
|
群聊默认值会跟随 `autoQuoteReply`:
|
|
147
152
|
|
|
@@ -167,6 +172,7 @@ openclaw onboard
|
|
|
167
172
|
- `requireMention: true/false` 仍然可用,会分别映射到群聊 `trigger.mode = "at"` / `"any_message"`
|
|
168
173
|
- 新的 `trigger` / `reply` 配置优先级更高
|
|
169
174
|
- `autoQuoteReply` 现在主要用于“未显式配置 `reply.mode` 时”的默认值回退
|
|
175
|
+
- 历史 `reply.mode = "quote_and_at"` 会自动降级为 `quote_source + 可见 @昵称前缀`
|
|
170
176
|
|
|
171
177
|
示例:
|
|
172
178
|
|
|
@@ -182,7 +188,7 @@ openclaw onboard
|
|
|
182
188
|
},
|
|
183
189
|
"project-room@chatroom": {
|
|
184
190
|
"trigger": { "mode": "at_or_quote" },
|
|
185
|
-
"reply": { "mode": "
|
|
191
|
+
"reply": { "mode": "at_sender" },
|
|
186
192
|
"skills": ["project-skill"]
|
|
187
193
|
},
|
|
188
194
|
"ops-room@chatroom": {
|
|
@@ -232,6 +238,11 @@ GeWe 现在补齐了目录、标准 allowlist 适配和状态摘要。
|
|
|
232
238
|
- 私聊:`allowFrom`
|
|
233
239
|
- 群发言人:`groupAllowFrom`
|
|
234
240
|
|
|
241
|
+
注意:
|
|
242
|
+
|
|
243
|
+
- 私聊 `pairing` 只会把对方加入私聊 allowlist,不会自动给任何群开权限。
|
|
244
|
+
- 群聊权限只看 `groupAllowFrom` 和 `groups.<groupId>.allowFrom`。
|
|
245
|
+
|
|
235
246
|
如果你要管理“某一个群自己的 `groups.<groupId>.allowFrom` 覆盖”,请用插件工具:
|
|
236
247
|
|
|
237
248
|
- `gewe_manage_group_allowlist`
|
|
@@ -254,6 +265,75 @@ GeWe 现在补齐了目录、标准 allowlist 适配和状态摘要。
|
|
|
254
265
|
}
|
|
255
266
|
```
|
|
256
267
|
|
|
268
|
+
### 认证方式
|
|
269
|
+
|
|
270
|
+
如果你是第一次用,可以把它理解成两步:
|
|
271
|
+
|
|
272
|
+
1. 先在私聊里完成配对
|
|
273
|
+
2. 再在要使用的群里完成认领
|
|
274
|
+
|
|
275
|
+
为什么要分成两步:
|
|
276
|
+
|
|
277
|
+
- 私聊配对成功,只表示“你可以在私聊里跟机器人说话了”
|
|
278
|
+
- 这一步不会让机器人自动在所有群里生效
|
|
279
|
+
- 群认领是第二次确认,表示“这个群也允许你使用机器人”
|
|
280
|
+
- 这样做是为了避免机器人一进公开群就被任何人直接触发
|
|
281
|
+
|
|
282
|
+
你实际要做的事很简单:
|
|
283
|
+
|
|
284
|
+
1. 先和机器人私聊,完成配对
|
|
285
|
+
2. 再在私聊里让机器人给你一个 8 位认领码
|
|
286
|
+
3. 把机器人拉进目标群
|
|
287
|
+
4. 在目标群里直接发这 8 位认领码
|
|
288
|
+
5. 收到成功提示后,这个群就接入完成了
|
|
289
|
+
|
|
290
|
+
举个例子:
|
|
291
|
+
|
|
292
|
+
- 机器人在私聊里给你:`MJSQ2G9H`
|
|
293
|
+
- 你把机器人拉进目标群
|
|
294
|
+
- 你在群里直接发:`MJSQ2G9H`
|
|
295
|
+
|
|
296
|
+
几个容易搞混的点:
|
|
297
|
+
|
|
298
|
+
- 群里不需要 `@机器人`
|
|
299
|
+
- 最推荐的发法就是只发这 8 位码
|
|
300
|
+
- `认领码: MJSQ2G9H` 这种写法也能识别,但默认不推荐
|
|
301
|
+
- 认领码有时效,而且只能用一次
|
|
302
|
+
- 这个码是谁拿到的,就只能由谁来完成这次认领
|
|
303
|
+
|
|
304
|
+
认领成功后会发生什么:
|
|
305
|
+
|
|
306
|
+
- 机器人会记住“你可以在这个群里使用它了”
|
|
307
|
+
- 只影响当前这个群
|
|
308
|
+
- 不会顺手把别的群也打开
|
|
309
|
+
- 不会自动帮你创建额外的绑定配置
|
|
310
|
+
|
|
311
|
+
如果你会自己改配置文件:
|
|
312
|
+
|
|
313
|
+
- 认领成功后,插件会把当前发码者写入 `groups.<groupId>.allowFrom`
|
|
314
|
+
|
|
315
|
+
### 群认领
|
|
316
|
+
|
|
317
|
+
如果是一个还没放行的新群,推荐流程是:
|
|
318
|
+
|
|
319
|
+
1. 先在已配对的私聊里调用 `gewe_issue_group_claim_code`
|
|
320
|
+
2. 把机器人拉进目标群
|
|
321
|
+
3. 在目标群里只发送这 8 位认领码:`XXXXXXXX`
|
|
322
|
+
|
|
323
|
+
推荐默认话术:
|
|
324
|
+
|
|
325
|
+
- 私聊里直接把 8 位码发给用户,不要包装成 `认领码: XXXXXXXX`
|
|
326
|
+
- 群里也只发这 8 位码,不要加 `认领码:` 前缀
|
|
327
|
+
|
|
328
|
+
认领成功后,插件会把“当前发码者”写入该群的 `groups.<groupId>.allowFrom`。
|
|
329
|
+
|
|
330
|
+
默认特性:
|
|
331
|
+
|
|
332
|
+
- 认领码短时有效、单次使用
|
|
333
|
+
- 只能由签发它的那个发码者在群里使用
|
|
334
|
+
- 只给当前群开权限,不会改顶层 `groupAllowFrom`
|
|
335
|
+
- 不会自动创建 `bindings[]`
|
|
336
|
+
|
|
257
337
|
如果你就在目标群里调用,`groupId` 可以省略;工具会自动用当前群。
|
|
258
338
|
|
|
259
339
|
### 状态
|
|
@@ -303,7 +383,7 @@ GeWe 的状态页现在会额外显示:
|
|
|
303
383
|
"groups": {
|
|
304
384
|
"ops-room@chatroom": {
|
|
305
385
|
"trigger": { "mode": "at_or_quote" },
|
|
306
|
-
"reply": { "mode": "
|
|
386
|
+
"reply": { "mode": "quote_source" }
|
|
307
387
|
}
|
|
308
388
|
}
|
|
309
389
|
}
|
package/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createGeweApiTools } from "./src/api-tools.js";
|
|
|
4
4
|
import { gewePlugin } from "./src/channel.js";
|
|
5
5
|
import { createGeweManageGroupAllowlistTool } from "./src/group-allowlist-tool.js";
|
|
6
6
|
import { createGeweSyncGroupBindingTool } from "./src/group-binding-tool.js";
|
|
7
|
+
import { createGeweIssueGroupClaimCodeTool } from "./src/group-claim-tool.js";
|
|
7
8
|
import { setGeweRuntime } from "./src/runtime.js";
|
|
8
9
|
|
|
9
10
|
function emptyPluginConfigSchema() {
|
|
@@ -44,6 +45,7 @@ const plugin = {
|
|
|
44
45
|
api.registerChannel({ plugin: gewePlugin });
|
|
45
46
|
api.registerTool((ctx) => createGeweApiTools(ctx));
|
|
46
47
|
api.registerTool((ctx) => createGeweSyncGroupBindingTool(ctx));
|
|
48
|
+
api.registerTool((ctx) => createGeweIssueGroupClaimCodeTool(ctx));
|
|
47
49
|
api.registerTool((ctx) =>
|
|
48
50
|
createGeweManageGroupAllowlistTool(ctx, {
|
|
49
51
|
readConfig: () => api.runtime.config.loadConfig() as never,
|
package/package.json
CHANGED
|
@@ -14,12 +14,13 @@ metadata:
|
|
|
14
14
|
|
|
15
15
|
# GeWe Agent Tools
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
优先把这五个工具当成 GeWe 的正式操作面:
|
|
18
18
|
|
|
19
19
|
- `gewe_contacts`
|
|
20
20
|
- `gewe_groups`
|
|
21
21
|
- `gewe_moments`
|
|
22
22
|
- `gewe_personal`
|
|
23
|
+
- `gewe_issue_group_claim_code`
|
|
23
24
|
|
|
24
25
|
## 什么时候用哪个
|
|
25
26
|
|
|
@@ -27,10 +28,48 @@ metadata:
|
|
|
27
28
|
- 处理群资料、群成员、群公告、群管理:用 `gewe_groups`
|
|
28
29
|
- 处理朋友圈浏览、点赞、评论、发布、转发:用 `gewe_moments`
|
|
29
30
|
- 处理当前登录微信自己的资料、二维码、安全信息、隐私设置:用 `gewe_personal`
|
|
31
|
+
- 处理新群接入、给当前已配对私聊签发群认领码:用 `gewe_issue_group_claim_code`
|
|
30
32
|
|
|
31
33
|
如果只是想知道“现在这个私聊对象是谁”,优先用 `gewe_contacts`。
|
|
32
34
|
如果只是想知道“现在这个群是谁、有哪些成员”,优先用 `gewe_groups`。
|
|
33
35
|
|
|
36
|
+
## 新群接入
|
|
37
|
+
|
|
38
|
+
当用户在 GeWe 当前私聊会话里表达这些意思时,优先进入“群认领”流程,而不是旧的手工绑群流程:
|
|
39
|
+
|
|
40
|
+
- “我要新绑定一个群”
|
|
41
|
+
- “给这个新群开权限”
|
|
42
|
+
- “把机器人接入一个新群”
|
|
43
|
+
|
|
44
|
+
这时优先调用 `gewe_issue_group_claim_code`。
|
|
45
|
+
|
|
46
|
+
关键原则:
|
|
47
|
+
|
|
48
|
+
- 不要先让用户手填群 id
|
|
49
|
+
- 不要先让用户自己改配置
|
|
50
|
+
- 不要把“私聊已经 pairing 成功”理解成“群已经自动放行”
|
|
51
|
+
|
|
52
|
+
标准动作:
|
|
53
|
+
|
|
54
|
+
1. 在当前私聊会话里调用 `gewe_issue_group_claim_code`
|
|
55
|
+
2. 把返回的 8 位认领码原样发给用户
|
|
56
|
+
3. 明确告诉用户:先把机器人拉进目标新群
|
|
57
|
+
4. 再让用户在目标群里只发送这 8 位认领码,例如:`XXXXXXXX`
|
|
58
|
+
|
|
59
|
+
重要话术约束:
|
|
60
|
+
|
|
61
|
+
- 默认只把 8 位码发给用户,不要包装成 `认领码: XXXXXXXX`
|
|
62
|
+
- 默认明确提醒:群里也只发这 8 位码,不要加 `认领码:` 前缀
|
|
63
|
+
|
|
64
|
+
推荐话术要点:
|
|
65
|
+
|
|
66
|
+
- 这是短时有效、单次使用的认领码
|
|
67
|
+
- 认领成功后,只会授权当前发码者在这个新群里触发机器人
|
|
68
|
+
- 如果只是想查新群资料,再在当前群会话里用 `gewe_groups`
|
|
69
|
+
|
|
70
|
+
只有在用户已经明确处在目标群里,且需求是“查当前群信息 / 查成员 / 查群 id”时,才优先使用 `gewe_groups`。
|
|
71
|
+
如果需求是“接入一个还没放行的新群”,先发认领码,再谈群信息。
|
|
72
|
+
|
|
34
73
|
## 当前会话推断
|
|
35
74
|
|
|
36
75
|
有些 action 可以少填参数,优先利用当前会话:
|
package/src/api-tools.ts
CHANGED
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
uploadSnsVideoGewe,
|
|
66
66
|
} from "./moments-api.js";
|
|
67
67
|
import { normalizeGeweMessagingTarget } from "./normalize.js";
|
|
68
|
-
import { normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
|
|
68
|
+
import { buildJsonSchema, normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
|
|
69
69
|
import {
|
|
70
70
|
getProfileGewe,
|
|
71
71
|
getQrCodeGewe,
|
|
@@ -74,6 +74,7 @@ import {
|
|
|
74
74
|
updateHeadImgGewe,
|
|
75
75
|
updateProfileGewe,
|
|
76
76
|
} from "./personal-api.js";
|
|
77
|
+
import { shouldExposeGeweAgentTool } from "./tool-visibility.js";
|
|
77
78
|
import type { CoreConfig, ResolvedGeweAccount } from "./types.js";
|
|
78
79
|
|
|
79
80
|
const ContactsActionSchema = z.enum([
|
|
@@ -243,6 +244,11 @@ const PersonalToolSchema = z
|
|
|
243
244
|
})
|
|
244
245
|
.strict();
|
|
245
246
|
|
|
247
|
+
const ContactsToolParameters = buildJsonSchema(ContactsToolSchema) ?? { type: "object" };
|
|
248
|
+
const GroupsToolParameters = buildJsonSchema(GroupsToolSchema) ?? { type: "object" };
|
|
249
|
+
const MomentsToolParameters = buildJsonSchema(MomentsToolSchema) ?? { type: "object" };
|
|
250
|
+
const PersonalToolParameters = buildJsonSchema(PersonalToolSchema) ?? { type: "object" };
|
|
251
|
+
|
|
246
252
|
type ContactsToolParams = z.infer<typeof ContactsToolSchema>;
|
|
247
253
|
type GroupsToolParams = z.infer<typeof GroupsToolSchema>;
|
|
248
254
|
type MomentsToolParams = z.infer<typeof MomentsToolSchema>;
|
|
@@ -1150,15 +1156,17 @@ async function executePersonalTool(
|
|
|
1150
1156
|
}
|
|
1151
1157
|
}
|
|
1152
1158
|
|
|
1153
|
-
export function createGeweApiTools(ctx: OpenClawPluginToolContext): AnyAgentTool[] {
|
|
1159
|
+
export function createGeweApiTools(ctx: OpenClawPluginToolContext): AnyAgentTool[] | null {
|
|
1160
|
+
if (!shouldExposeGeweAgentTool(ctx)) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1154
1163
|
return [
|
|
1155
1164
|
{
|
|
1156
1165
|
name: "gewe_contacts",
|
|
1157
1166
|
label: "GeWe Contacts",
|
|
1158
1167
|
description:
|
|
1159
1168
|
"GeWe contacts operations. Actions: list, list_cache, brief, detail, search, search_im, im_detail, check_relation, set_remark, set_only_chat, delete, add, add_im, sync_im, phones_get, phones_upload.",
|
|
1160
|
-
|
|
1161
|
-
parameters: ContactsToolSchema,
|
|
1169
|
+
parameters: ContactsToolParameters,
|
|
1162
1170
|
execute: async (_toolCallId, rawParams) => {
|
|
1163
1171
|
const params = ContactsToolSchema.parse(rawParams ?? {});
|
|
1164
1172
|
try {
|
|
@@ -1184,8 +1192,7 @@ export function createGeweApiTools(ctx: OpenClawPluginToolContext): AnyAgentTool
|
|
|
1184
1192
|
label: "GeWe Groups",
|
|
1185
1193
|
description:
|
|
1186
1194
|
"GeWe group operations. Actions: info, announcement, members, member_detail, qr_code, set_self_nickname, rename, set_remark, create, remove_members, agree_join, join_via_qr, add_member_as_friend, approve_join_request, admin_operate, save_to_contacts, pin, disband, set_silence, set_announcement, quit, invite.",
|
|
1187
|
-
|
|
1188
|
-
parameters: GroupsToolSchema,
|
|
1195
|
+
parameters: GroupsToolParameters,
|
|
1189
1196
|
execute: async (_toolCallId, rawParams) => {
|
|
1190
1197
|
const params = GroupsToolSchema.parse(rawParams ?? {});
|
|
1191
1198
|
try {
|
|
@@ -1211,8 +1218,7 @@ export function createGeweApiTools(ctx: OpenClawPluginToolContext): AnyAgentTool
|
|
|
1211
1218
|
label: "GeWe Moments",
|
|
1212
1219
|
description:
|
|
1213
1220
|
"GeWe Moments operations. Actions: list_self, list_contact, detail, download_video, upload_image, upload_video, delete, post_text, post_image, post_video, post_link, set_stranger_visibility, set_visible_scope, set_privacy, like, comment, forward.",
|
|
1214
|
-
|
|
1215
|
-
parameters: MomentsToolSchema,
|
|
1221
|
+
parameters: MomentsToolParameters,
|
|
1216
1222
|
execute: async (_toolCallId, rawParams) => {
|
|
1217
1223
|
const params = MomentsToolSchema.parse(rawParams ?? {});
|
|
1218
1224
|
try {
|
|
@@ -1238,8 +1244,7 @@ export function createGeweApiTools(ctx: OpenClawPluginToolContext): AnyAgentTool
|
|
|
1238
1244
|
label: "GeWe Personal",
|
|
1239
1245
|
description:
|
|
1240
1246
|
"GeWe personal-account operations. Actions: profile, qrcode, safety_info, update_profile, update_avatar, privacy.",
|
|
1241
|
-
|
|
1242
|
-
parameters: PersonalToolSchema,
|
|
1247
|
+
parameters: PersonalToolParameters,
|
|
1243
1248
|
execute: async (_toolCallId, rawParams) => {
|
|
1244
1249
|
const params = PersonalToolSchema.parse(rawParams ?? {});
|
|
1245
1250
|
try {
|
package/src/config-schema.ts
CHANGED
|
@@ -7,8 +7,11 @@ import {
|
|
|
7
7
|
ToolPolicySchema,
|
|
8
8
|
requireOpenAllowFrom,
|
|
9
9
|
} from "./openclaw-compat.js";
|
|
10
|
+
import type { GeweGroupReplyModeInput } from "./types.js";
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
|
|
13
|
+
const GEWE_GROUP_REPLY_MODES = ["plain", "quote_source", "at_sender", "quote_and_at"] as const;
|
|
14
|
+
|
|
12
15
|
const GeweGroupTriggerSchema = z
|
|
13
16
|
.object({
|
|
14
17
|
mode: z.enum(["at", "quote", "at_or_quote", "any_message"]).optional(),
|
|
@@ -23,7 +26,15 @@ const GeweDmTriggerSchema = z
|
|
|
23
26
|
|
|
24
27
|
const GeweGroupReplySchema = z
|
|
25
28
|
.object({
|
|
26
|
-
mode: z
|
|
29
|
+
mode: z
|
|
30
|
+
.custom<GeweGroupReplyModeInput>(
|
|
31
|
+
(value) => typeof value === "string" && GEWE_GROUP_REPLY_MODES.includes(value as GeweGroupReplyModeInput),
|
|
32
|
+
{
|
|
33
|
+
message:
|
|
34
|
+
"invalid GeWe group reply mode; supported values are plain, quote_source, at_sender (quote_and_at is accepted only for compatibility)",
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
.optional(),
|
|
27
38
|
})
|
|
28
39
|
.strict();
|
|
29
40
|
|
package/src/delivery.ts
CHANGED
|
@@ -770,7 +770,6 @@ function buildPartialQuoteXml(params?: {
|
|
|
770
770
|
function buildQuoteReplyAppMsg(params: {
|
|
771
771
|
title: string;
|
|
772
772
|
svrid: string;
|
|
773
|
-
atWxid?: string;
|
|
774
773
|
partialText?: {
|
|
775
774
|
text?: string;
|
|
776
775
|
start?: string;
|
|
@@ -782,12 +781,12 @@ function buildQuoteReplyAppMsg(params: {
|
|
|
782
781
|
}): string {
|
|
783
782
|
const safeTitle = escapeXmlText(params.title.trim() || "引用回复");
|
|
784
783
|
const safeSvrid = escapeXmlText(params.svrid.trim());
|
|
785
|
-
const safeAtWxid = params.atWxid?.trim() ? escapeXmlText(params.atWxid.trim()) : undefined;
|
|
786
|
-
const encodedMsgSource = safeAtWxid
|
|
787
|
-
? `<msgsource><atuserlist>${safeAtWxid}</atuserlist></msgsource>`
|
|
788
|
-
: "";
|
|
789
784
|
const partialTextXml = buildPartialQuoteXml(params.partialText);
|
|
790
|
-
return `<appmsg><title>${safeTitle}</title><type>57</type><refermsg>${partialTextXml}<svrid>${safeSvrid}</svrid
|
|
785
|
+
return `<appmsg><title>${safeTitle}</title><type>57</type><refermsg>${partialTextXml}<svrid>${safeSvrid}</svrid></refermsg></appmsg>`;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function summarizeOutboundText(value: string): string {
|
|
789
|
+
return JSON.stringify(value.replace(/\s+/g, " ").trim().slice(0, 120));
|
|
791
790
|
}
|
|
792
791
|
|
|
793
792
|
async function stageMedia(params: {
|
|
@@ -983,15 +982,18 @@ export async function deliverGewePayload(params: {
|
|
|
983
982
|
: payload.replyToId?.trim() || "";
|
|
984
983
|
const quoteReplyTitle = geweData?.quoteReply?.title?.trim() || trimmedText;
|
|
985
984
|
if (quoteReplySvrid && quoteReplyTitle && geweData?.quoteReply) {
|
|
985
|
+
core.log?.(
|
|
986
|
+
`gewe: outbound quoteReply explicit to=${toWxid} ats=${JSON.stringify(geweData.quoteReply.atWxid?.trim() || "")} title=${summarizeOutboundText(quoteReplyTitle)}`,
|
|
987
|
+
);
|
|
986
988
|
const result = await sendAppMsgGewe({
|
|
987
989
|
account,
|
|
988
990
|
toWxid,
|
|
989
991
|
appmsg: buildQuoteReplyAppMsg({
|
|
990
992
|
svrid: quoteReplySvrid,
|
|
991
993
|
title: quoteReplyTitle,
|
|
992
|
-
atWxid: geweData.quoteReply.atWxid?.trim(),
|
|
993
994
|
partialText: geweData.quoteReply.partialText,
|
|
994
995
|
}),
|
|
996
|
+
ats: geweData.quoteReply.atWxid?.trim(),
|
|
995
997
|
});
|
|
996
998
|
core.channel.activity.record({
|
|
997
999
|
channel: CHANNEL_ID,
|
|
@@ -1160,6 +1162,9 @@ export async function deliverGewePayload(params: {
|
|
|
1160
1162
|
}
|
|
1161
1163
|
|
|
1162
1164
|
if (autoQuoteReplyEnabled && trimmedText && payload.replyToId?.trim() && !mediaUrl) {
|
|
1165
|
+
core.log?.(
|
|
1166
|
+
`gewe: outbound quoteReply auto to=${toWxid} ats=${JSON.stringify(geweData?.ats?.trim() || "")} title=${summarizeOutboundText(trimmedText)}`,
|
|
1167
|
+
);
|
|
1163
1168
|
const result = await sendAppMsgGewe({
|
|
1164
1169
|
account,
|
|
1165
1170
|
toWxid,
|
|
@@ -1168,6 +1173,7 @@ export async function deliverGewePayload(params: {
|
|
|
1168
1173
|
title: trimmedText,
|
|
1169
1174
|
partialText: autoQuoteContext?.partialText,
|
|
1170
1175
|
}),
|
|
1176
|
+
ats: geweData?.ats,
|
|
1171
1177
|
});
|
|
1172
1178
|
core.channel.activity.record({
|
|
1173
1179
|
channel: CHANNEL_ID,
|
|
@@ -4,8 +4,8 @@ import { z } from "zod";
|
|
|
4
4
|
import { resolveGeweAccount } from "./accounts.js";
|
|
5
5
|
import { ensureGeweWriteSection } from "./config-edit.js";
|
|
6
6
|
import { normalizeGeweBindingConversationId, inferCurrentGeweGroupId } from "./group-binding.js";
|
|
7
|
-
import { normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
|
|
8
|
-
import {
|
|
7
|
+
import { buildJsonSchema, normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
|
|
8
|
+
import { shouldExposeGeweAgentTool } from "./tool-visibility.js";
|
|
9
9
|
import type { GeweGroupConfig } from "./types.js";
|
|
10
10
|
|
|
11
11
|
const GeweManageGroupAllowlistSchema = z
|
|
@@ -17,6 +17,9 @@ const GeweManageGroupAllowlistSchema = z
|
|
|
17
17
|
})
|
|
18
18
|
.strict();
|
|
19
19
|
|
|
20
|
+
const GeweManageGroupAllowlistParameters =
|
|
21
|
+
buildJsonSchema(GeweManageGroupAllowlistSchema) ?? { type: "object" };
|
|
22
|
+
|
|
20
23
|
function jsonResult(details: Record<string, unknown>) {
|
|
21
24
|
return {
|
|
22
25
|
content: [{ type: "text" as const, text: JSON.stringify(details, null, 2) }],
|
|
@@ -80,9 +83,7 @@ function readOverrideEntries(accountConfig: Record<string, unknown>, groupId: st
|
|
|
80
83
|
|
|
81
84
|
function resolveEffectiveEntries(params: {
|
|
82
85
|
accountConfig: Record<string, unknown>;
|
|
83
|
-
accountId: string;
|
|
84
86
|
groupId: string;
|
|
85
|
-
pairingEntries: string[];
|
|
86
87
|
}): {
|
|
87
88
|
baseEntries: string[];
|
|
88
89
|
overrideEntries: string[];
|
|
@@ -93,7 +94,7 @@ function resolveEffectiveEntries(params: {
|
|
|
93
94
|
return {
|
|
94
95
|
baseEntries,
|
|
95
96
|
overrideEntries,
|
|
96
|
-
effectiveEntries: dedupeEntries([...baseEntries, ...
|
|
97
|
+
effectiveEntries: dedupeEntries([...baseEntries, ...overrideEntries]),
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -160,14 +161,16 @@ export function createGeweManageGroupAllowlistTool(
|
|
|
160
161
|
readConfig?: () => OpenClawConfig;
|
|
161
162
|
writeConfigFile?: (next: OpenClawConfig) => Promise<void>;
|
|
162
163
|
},
|
|
163
|
-
): AnyAgentTool {
|
|
164
|
+
): AnyAgentTool | null {
|
|
165
|
+
if (!shouldExposeGeweAgentTool(ctx)) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
164
168
|
return {
|
|
165
169
|
name: "gewe_manage_group_allowlist",
|
|
166
170
|
label: "GeWe Manage Group Allowlist",
|
|
167
171
|
description:
|
|
168
172
|
"Inspect or edit a GeWe group's allowFrom override. Modes: inspect, add, remove, replace, clear.",
|
|
169
|
-
|
|
170
|
-
parameters: GeweManageGroupAllowlistSchema,
|
|
173
|
+
parameters: GeweManageGroupAllowlistParameters,
|
|
171
174
|
execute: async (_toolCallId, rawParams) => {
|
|
172
175
|
const params = GeweManageGroupAllowlistSchema.parse(rawParams ?? {});
|
|
173
176
|
const cfg = resolveToolConfig(ctx, deps?.readConfig);
|
|
@@ -189,12 +192,9 @@ export function createGeweManageGroupAllowlistTool(
|
|
|
189
192
|
?.accounts?.[accountId] ?? {})
|
|
190
193
|
) as Record<string, unknown>;
|
|
191
194
|
|
|
192
|
-
const pairingEntries = await readGeweAllowFromStore({ accountId });
|
|
193
195
|
const current = resolveEffectiveEntries({
|
|
194
196
|
accountConfig: resolvedAccount.config as Record<string, unknown>,
|
|
195
|
-
accountId,
|
|
196
197
|
groupId,
|
|
197
|
-
pairingEntries,
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
if (params.mode === "inspect") {
|
|
@@ -205,7 +205,6 @@ export function createGeweManageGroupAllowlistTool(
|
|
|
205
205
|
groupId,
|
|
206
206
|
groupPolicy: resolvedAccount.config.groupPolicy ?? "allowlist",
|
|
207
207
|
baseEntries: current.baseEntries,
|
|
208
|
-
pairingEntries,
|
|
209
208
|
overrideEntries: current.overrideEntries,
|
|
210
209
|
effectiveEntries: current.effectiveEntries,
|
|
211
210
|
});
|
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
resolveGeweBindingIdentityConfigForGroup,
|
|
16
16
|
resolveGeweCurrentSelfNickname,
|
|
17
17
|
} from "./group-binding.js";
|
|
18
|
-
import { normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
|
|
18
|
+
import { buildJsonSchema, normalizeAccountId, type OpenClawConfig } from "./openclaw-compat.js";
|
|
19
|
+
import { shouldExposeGeweAgentTool } from "./tool-visibility.js";
|
|
19
20
|
|
|
20
21
|
const GeweSyncGroupBindingToolSchema = z
|
|
21
22
|
.object({
|
|
@@ -27,6 +28,9 @@ const GeweSyncGroupBindingToolSchema = z
|
|
|
27
28
|
})
|
|
28
29
|
.strict();
|
|
29
30
|
|
|
31
|
+
const GeweSyncGroupBindingToolParameters =
|
|
32
|
+
buildJsonSchema(GeweSyncGroupBindingToolSchema) ?? { type: "object" };
|
|
33
|
+
|
|
30
34
|
function jsonResult(details: Record<string, unknown>) {
|
|
31
35
|
return {
|
|
32
36
|
content: [{ type: "text" as const, text: JSON.stringify(details, null, 2) }],
|
|
@@ -34,14 +38,16 @@ function jsonResult(details: Record<string, unknown>) {
|
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
export function createGeweSyncGroupBindingTool(ctx: OpenClawPluginToolContext): AnyAgentTool {
|
|
41
|
+
export function createGeweSyncGroupBindingTool(ctx: OpenClawPluginToolContext): AnyAgentTool | null {
|
|
42
|
+
if (!shouldExposeGeweAgentTool(ctx)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
38
45
|
return {
|
|
39
46
|
name: "gewe_sync_group_binding",
|
|
40
47
|
label: "GeWe Sync Group Binding",
|
|
41
48
|
description:
|
|
42
49
|
"Inspect or manually sync a GeWe group binding identity. Modes: inspect, dry_run, apply.",
|
|
43
|
-
|
|
44
|
-
parameters: GeweSyncGroupBindingToolSchema,
|
|
50
|
+
parameters: GeweSyncGroupBindingToolParameters,
|
|
45
51
|
execute: async (_toolCallId, rawParams) => {
|
|
46
52
|
const params = GeweSyncGroupBindingToolSchema.parse(rawParams ?? {});
|
|
47
53
|
const cfg = (ctx.config ?? {}) as OpenClawConfig;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { AnyAgentTool, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { normalizeGeweMessagingTarget } from "./normalize.js";
|
|
5
|
+
import { buildJsonSchema, normalizeAccountId } from "./openclaw-compat.js";
|
|
6
|
+
import { issueGeweGroupClaimCode } from "./pairing-store.js";
|
|
7
|
+
import { shouldExposeGeweAgentTool } from "./tool-visibility.js";
|
|
8
|
+
|
|
9
|
+
const GeweIssueGroupClaimCodeSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
accountId: z.string().optional(),
|
|
12
|
+
})
|
|
13
|
+
.strict();
|
|
14
|
+
|
|
15
|
+
const GeweIssueGroupClaimCodeParameters =
|
|
16
|
+
buildJsonSchema(GeweIssueGroupClaimCodeSchema) ?? { type: "object" };
|
|
17
|
+
|
|
18
|
+
function extractSessionScopedTarget(
|
|
19
|
+
sessionKey: string | undefined,
|
|
20
|
+
markers: string[],
|
|
21
|
+
): string | undefined {
|
|
22
|
+
const raw = sessionKey?.trim();
|
|
23
|
+
if (!raw) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const lowered = raw.toLowerCase();
|
|
27
|
+
for (const marker of markers) {
|
|
28
|
+
const index = lowered.indexOf(marker);
|
|
29
|
+
if (index === -1) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const value = raw.slice(index + marker.length).trim();
|
|
33
|
+
if (value) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function inferCurrentGeweDirectWxid(ctx: OpenClawPluginToolContext): string | undefined {
|
|
41
|
+
const fromSession = extractSessionScopedTarget(ctx.sessionKey, [
|
|
42
|
+
":gewe-openclaw:direct:",
|
|
43
|
+
":gewe-openclaw:dm:",
|
|
44
|
+
":gewe:direct:",
|
|
45
|
+
":gewe:dm:",
|
|
46
|
+
]);
|
|
47
|
+
const normalizedSession = normalizeGeweMessagingTarget(fromSession ?? "");
|
|
48
|
+
if (normalizedSession && !normalizedSession.endsWith("@chatroom")) {
|
|
49
|
+
return normalizedSession;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalizedRequester = normalizeGeweMessagingTarget(ctx.requesterSenderId ?? "");
|
|
53
|
+
if (normalizedRequester && !normalizedRequester.endsWith("@chatroom")) {
|
|
54
|
+
return normalizedRequester;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function jsonResult(details: Record<string, unknown>) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text" as const, text: JSON.stringify(details, null, 2) }],
|
|
62
|
+
details,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createGeweIssueGroupClaimCodeTool(ctx: OpenClawPluginToolContext): AnyAgentTool | null {
|
|
67
|
+
if (!shouldExposeGeweAgentTool(ctx)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
name: "gewe_issue_group_claim_code",
|
|
72
|
+
label: "GeWe Issue Group Claim Code",
|
|
73
|
+
description:
|
|
74
|
+
"Issue a short-lived single-use group claim code for the current GeWe direct-message session.",
|
|
75
|
+
parameters: GeweIssueGroupClaimCodeParameters,
|
|
76
|
+
execute: async (_toolCallId, rawParams) => {
|
|
77
|
+
const params = GeweIssueGroupClaimCodeSchema.parse(rawParams ?? {});
|
|
78
|
+
const accountId = normalizeAccountId(params.accountId ?? ctx.agentAccountId ?? "default");
|
|
79
|
+
const issuerId = inferCurrentGeweDirectWxid(ctx);
|
|
80
|
+
if (!issuerId) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
"GeWe group claim code issuance requires a current GeWe direct-message session to infer the owner wxid.",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const issued = await issueGeweGroupClaimCode({
|
|
87
|
+
accountId,
|
|
88
|
+
issuerId,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return jsonResult({
|
|
92
|
+
ok: true,
|
|
93
|
+
accountId: issued.accountId,
|
|
94
|
+
issuerId: issued.issuerId,
|
|
95
|
+
code: issued.code,
|
|
96
|
+
recommendedGroupMessage: issued.code,
|
|
97
|
+
createdAt: issued.createdAt,
|
|
98
|
+
expiresAt: issued.expiresAt,
|
|
99
|
+
usageHint: `把机器人拉进目标群后,在群里只发送这 8 位认领码:${issued.code}(不要加“认领码:”前缀)`,
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|