@xgjktech/xg_cwork_im 1.10.2 → 1.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xgjktech/xg_cwork_im",
3
- "version": "1.10.2",
3
+ "version": "1.11.0",
4
4
  "description": "XG CWork IM channel plugin for OpenClaw",
5
5
  "keywords": [
6
6
  "bot",
@@ -21,7 +21,8 @@
21
21
  "scripts": {
22
22
  "type-check": "tsc -p tsconfig.json --noEmit",
23
23
  "lint": "tsc -p tsconfig.json --noEmit",
24
- "install:prod": "npm install --omit=dev"
24
+ "install:prod": "npm install --omit=dev",
25
+ "prepublishOnly": "npm run type-check"
25
26
  },
26
27
  "dependencies": {
27
28
  "@sinclair/typebox": "^0.32.0",
package/src/channel.ts CHANGED
@@ -57,6 +57,7 @@ const XgImAccountConfigSchema = z.object({
57
57
  inboundMediaWorkspaceSubdir: z.string().optional(),
58
58
  inboundMediaOpenClawPath: z.enum(["workspaceRelative", "absoluteFileUrl"]).optional(),
59
59
  inboundMediaSandboxRootPrefix: z.string().optional(),
60
+ systemPrompt: z.string().optional(),
60
61
  });
61
62
 
62
63
  const XgImConfigSchema = z.object({
@@ -83,6 +84,8 @@ const XgImConfigSchema = z.object({
83
84
  inboundMediaOpenClawPath: z.enum(["workspaceRelative", "absoluteFileUrl"]).optional(),
84
85
  /** 沙箱内 workspace 挂载根,如 `/workspace`;与 workspaceRelative 组合生成绝对引用 */
85
86
  inboundMediaSandboxRootPrefix: z.string().optional(),
87
+ /** 群聊入站时写入 OpenClaw `GroupSystemPrompt`(与 accounts 内字段合并规则见 getXgImConfig) */
88
+ systemPrompt: z.string().optional(),
86
89
  // 多账户:对象 map(key 为 accountId)
87
90
  accounts: z.record(z.string(), XgImAccountConfigSchema).optional(),
88
91
  }).superRefine((val, ctx) => {
@@ -171,6 +174,31 @@ function getXgImConfig(cfg: OpenClawConfig, accountId?: string | null): XgImConf
171
174
  return raw;
172
175
  }
173
176
 
177
+ /**
178
+ * 合并 `channels.xg_cwork_im` 顶层、`accounts.default`、以及 `accounts.<accountId>` 后的 `systemPrompt`,
179
+ * 写入 OpenClaw 入站 `GroupSystemPrompt`(与 getXgImConfig 的非 default 账户合并方式一致;对 default 账户会合并 default 片段)。
180
+ */
181
+ function resolveXgImGroupSystemPrompt(cfg: OpenClawConfig, accountId: string): string | undefined {
182
+ try {
183
+ const raw = (cfg as Record<string, Record<string, unknown>>)?.channels?.xg_cwork_im as XgImConfig | undefined;
184
+ if (!raw) return undefined;
185
+ const accountMap =
186
+ raw.accounts && typeof raw.accounts === "object" && !Array.isArray(raw.accounts)
187
+ ? (raw.accounts as Record<string, Partial<XgImConfig> | undefined>)
188
+ : undefined;
189
+ const defaults = accountMap?.default ? { ...accountMap.default } : {};
190
+ const base: XgImConfig = { ...raw, ...defaults };
191
+ let merged: XgImConfig = base;
192
+ if (accountId && accountId !== "default" && accountMap?.[accountId]) {
193
+ merged = { ...base, ...accountMap[accountId] };
194
+ }
195
+ const t = merged.systemPrompt?.trim();
196
+ return t || undefined;
197
+ } catch {
198
+ return undefined;
199
+ }
200
+ }
201
+
174
202
  function isConfigured(cfg: OpenClawConfig): boolean {
175
203
  try {
176
204
  const c = getXgImConfig(cfg);
@@ -257,8 +285,7 @@ function buildInboundDisplayText(msgContent: WsMessageContent | undefined, fileI
257
285
  if (fileItems.length > 0) {
258
286
  const lines = fileItems.map((f) => {
259
287
  const name = f.name?.trim() || f.fileId || "未命名文件";
260
- const mime = imFormatToMimeType(f.format);
261
- return mime ? `- \`${name}\` (${mime})` : `- \`${name}\``;
288
+ return `- \`${name}\``;
262
289
  });
263
290
  const block = `**附件(${fileItems.length})**\n${lines.join("\n")}`;
264
291
  rawText = rawText.trim() ? `${rawText.trim()}\n\n${block}` : block;
@@ -275,6 +302,11 @@ function buildInboundDisplayText(msgContent: WsMessageContent | undefined, fileI
275
302
  const parsedBlock = `**附件解析内容**\n\n${parsedParts.join("\n\n")}`;
276
303
  rawText = `${rawText.trim()}\n\n${parsedBlock}`;
277
304
  }
305
+
306
+ const urlGuard =
307
+ "**附件 URL 使用约束**\n" +
308
+ "处理附件时必须逐字原样使用消息中提供的完整 URL;禁止修改、删除或重排任何 query 参数。";
309
+ rawText = `${rawText.trim()}\n\n${urlGuard}`;
278
310
  }
279
311
  return rawText;
280
312
  }
@@ -297,20 +329,15 @@ function isActuallyMentioned(
297
329
 
298
330
  function mediaFieldsFromFileItems(fileItems: MsgFileVO[]): Record<string, unknown> {
299
331
  if (fileItems.length === 0) return {};
300
- const mimes = fileItems.map(
301
- (f) => imFormatToMimeType(f.format) ?? "application/octet-stream",
302
- );
303
- // OpenClaw 入站展示与媒体拉取以 MediaPath(s) 为准;[media attached: …] 依赖 Path,仅写 MediaUrl 时该段不出现。
304
- // 不要同时写 Path 与 Url(同链接会被拼成两段,出现 `url | url`)。
332
+ // 只传 MediaPath(s),不传 MediaType(s)。
333
+ // 预签名 URL query 中常含 response-content-type;若额外给 MediaType,模型/工具更容易“改写 URL”导致签名失效。
305
334
  if (fileItems.length === 1) {
306
335
  return {
307
336
  MediaPath: fileItems[0]!.url,
308
- MediaType: mimes[0],
309
337
  };
310
338
  }
311
339
  return {
312
340
  MediaPaths: fileItems.map((f) => f.url),
313
- MediaTypes: mimes,
314
341
  };
315
342
  }
316
343
 
@@ -760,6 +787,16 @@ export const xgCworkImChannelPlugin: XgImChannelPlugin = {
760
787
  config,
761
788
  });
762
789
 
790
+ const groupSystemPromptResolved = resolveXgImGroupSystemPrompt(ctx.cfg, account.accountId);
791
+ if (config.debug) {
792
+ log.info(
793
+ `${logPrefix} [GroupSystemPrompt] ` +
794
+ (groupSystemPromptResolved
795
+ ? `active len=${groupSystemPromptResolved.length} preview=${JSON.stringify(groupSystemPromptResolved.slice(0, 96))}`
796
+ : "(empty — 若已配置 systemPrompt:检查网关是否加载了含本字段的插件构建,或把 systemPrompt 写到 accounts.<id> 再试)"),
797
+ );
798
+ }
799
+
763
800
  const inboundCtx = rt.channel.reply.finalizeInboundContext({
764
801
  Body: body,
765
802
  RawBody: text,
@@ -772,6 +809,7 @@ export const xgCworkImChannelPlugin: XgImChannelPlugin = {
772
809
  ChatType: "group",
773
810
  ConversationLabel: fromLabel,
774
811
  GroupSubject: params.groupId,
812
+ GroupSystemPrompt: groupSystemPromptResolved,
775
813
  SenderName: senderName,
776
814
  SenderId: senderId ?? "",
777
815
  Provider: "xg_cwork_im",
package/src/types.ts CHANGED
@@ -54,6 +54,11 @@ export interface XgImAccountConfig {
54
54
  * `{inboundMediaSandboxRootPrefix}/xg_im_inbound/...`,避免 `./` 被工具按 cwd 解析而报「不可访问」。
55
55
  */
56
56
  inboundMediaSandboxRootPrefix?: string;
57
+ /**
58
+ * 可选:本账户在群聊中追加给 Agent 的补充说明(映射为 OpenClaw 入站 `GroupSystemPrompt`)。
59
+ * 与顶层 `systemPrompt` 的合并规则见 `getXgImConfig`(`accounts.default` 与具体账户覆盖)。
60
+ */
61
+ systemPrompt?: string;
57
62
  }
58
63
 
59
64
  export interface XgImConfig extends OpenClawConfig {
@@ -115,6 +120,11 @@ export interface XgImConfig extends OpenClawConfig {
115
120
  inboundMediaOpenClawPath?: "workspaceRelative" | "absoluteFileUrl";
116
121
  /** 见 {@link XgImAccountConfig.inboundMediaSandboxRootPrefix} */
117
122
  inboundMediaSandboxRootPrefix?: string;
123
+ /**
124
+ * 可选:本通道在群聊中追加给 Agent 的补充说明(映射为 OpenClaw 入站 `GroupSystemPrompt`)。
125
+ * 多账户时可与 `accounts.<id>.systemPrompt` 及 `accounts.default.systemPrompt` 组合覆盖,见插件内 `getXgImConfig`。
126
+ */
127
+ systemPrompt?: string;
118
128
  }
119
129
 
120
130
  // ─── IM 接口 Request / Response ─────────────────────────────────────────────