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 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(或在群里走 allowlist 规则)。
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
- - `quote_and_at`:首条文本回复同时引用并 `@`;非文本回复会自动退化为 `quote_source`
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": "quote_and_at" },
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": "quote_and_at" }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gewe-openclaw",
3
- "version": "2026.3.24",
3
+ "version": "2026.3.26",
4
4
  "type": "module",
5
5
  "description": "OpenClaw GeWe channel plugin",
6
6
  "license": "MIT",
@@ -14,12 +14,13 @@ metadata:
14
14
 
15
15
  # GeWe Agent Tools
16
16
 
17
- 优先把这四个工具当成 GeWe 的正式操作面:
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
- ownerOnly: true,
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
- ownerOnly: true,
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
- ownerOnly: true,
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
- ownerOnly: true,
1242
- parameters: PersonalToolSchema,
1247
+ parameters: PersonalToolParameters,
1243
1248
  execute: async (_toolCallId, rawParams) => {
1244
1249
  const params = PersonalToolSchema.parse(rawParams ?? {});
1245
1250
  try {
@@ -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.enum(["plain", "quote_source", "at_sender", "quote_and_at"]).optional(),
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
- ? `&lt;msgsource&gt;&lt;atuserlist&gt;${safeAtWxid}&lt;/atuserlist&gt;&lt;/msgsource&gt;`
788
- : "";
789
784
  const partialTextXml = buildPartialQuoteXml(params.partialText);
790
- return `<appmsg><title>${safeTitle}</title><type>57</type><refermsg>${partialTextXml}<svrid>${safeSvrid}</svrid>${safeAtWxid ? `<msgsource>${encodedMsgSource}</msgsource>` : ""}</refermsg></appmsg>`;
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 { readGeweAllowFromStore } from "./pairing-store.js";
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, ...params.pairingEntries, ...overrideEntries]),
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
- ownerOnly: true,
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
- ownerOnly: true,
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
+ }