@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 +3 -2
- package/src/channel.ts +47 -9
- package/src/types.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xgjktech/xg_cwork_im",
|
|
3
|
-
"version": "1.
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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 ─────────────────────────────────────────────
|