@jeik/dingtalk-connector 0.8.32 → 0.8.33
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/CHANGELOG.md +14 -0
- package/dist/card-content-cache-BsFPIwOr.mjs +2 -0
- package/dist/{runtime-CjE4Z9G5.mjs → channel-CABtqkf_.mjs} +18 -29
- package/dist/chunk-upload-CNlZg1SF.mjs +2 -0
- package/dist/{common-BfIYs1yC.mjs → common-1-GDZ2h3.mjs} +2 -2
- package/dist/{common-C0Oaf0z-.mjs → common-DCVdWMiZ.mjs} +1 -1
- package/dist/{connection-obtrPkj4.mjs → connection-Bh3U9I4x.mjs} +2 -2
- package/dist/dws-delivery-context-ESC7sij7.mjs +185 -0
- package/dist/entry-bundled.mjs +1 -1
- package/dist/gateway-methods-CV8y0xGw.mjs +2 -0
- package/dist/{gateway-methods-BdcpfxEZ.mjs → gateway-methods-Ce1FXmCx.mjs} +4 -4
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +3 -2
- package/dist/{message-handler-BeTQQCUe.mjs → message-handler-BoVSEDwY.mjs} +41 -14
- package/dist/{messaging-CK0Wz3Ze.mjs → messaging-CwQLykl6.mjs} +1 -1
- package/dist/runtime-DDMzayvA.mjs +26 -0
- package/dist/utils-legacy-Bx5izlVF.mjs +3 -0
- package/openclaw.plugin.json +28 -1
- package/package.json +1 -1
- package/src/config/schema.ts +11 -0
- package/src/core/message-handler.ts +2 -0
- package/src/reply-dispatcher.ts +35 -0
- package/src/services/dws-delivery-context.ts +317 -0
- package/dist/card-content-cache-C7-XHhHw.mjs +0 -2
- package/dist/chunk-upload-BKM1y4Ba.mjs +0 -2
- package/dist/gateway-methods-DQ6TwOAr.mjs +0 -2
- package/dist/utils-legacy-BOqY3zE7.mjs +0 -3
- /package/dist/{background-work-DkaSDqWk.mjs → background-work-874-aK4-.mjs} +0 -0
- /package/dist/{card-content-cache-DT_OYThh.mjs → card-content-cache-CGggfgCg.mjs} +0 -0
- /package/dist/{chunk-upload-CIU5G_ay.mjs → chunk-upload-z185bTqH.mjs} +0 -0
- /package/dist/{game-xiyou-uwsv23AC.mjs → game-xiyou-CKoWJobs.mjs} +0 -0
- /package/dist/{utils-legacy-DQ3HeGHh.mjs → utils-legacy-CGEa8tg2.mjs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.33] - 2026-07-31
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
- **dws 发消息成功后注入 outbound_message 上下文** — 与 `message` 工具 OC-4 一致:当 agent 通过 `dws chat message send|send-by-bot|reply|send-by-webhook` 发消息成功时,自动向目标会话写入模型可见的出站上下文(来源/调用人/正文/可复制 ID)。
|
|
13
|
+
- 配置项 `channels.dingtalk-connector.dwsDeliveryContext`:`target`(默认)/ `source` / `both` / `off`
|
|
14
|
+
- **仅叠加**:不影响普通回复与 message 工具;解析失败或非发消息命令时 no-op
|
|
15
|
+
- 实现:`src/services/dws-delivery-context.ts` + `onCommandOutput` 钩子;从命令行解析 `--group`/`--user` 后经 `resolveOutboundSessionRoute` 映射 sessionKey
|
|
16
|
+
- 依赖 OpenClaw(≥ 本配套版本)`plugin-sdk/outbound-runtime` 导出 `appendOutboundMessageDeliveryContext`
|
|
17
|
+
|
|
18
|
+
### Docs
|
|
19
|
+
|
|
20
|
+
- **CHANGELOG / schema / openclaw.plugin.json** — 同步 `dwsDeliveryContext` 字段说明与 UI help。
|
|
21
|
+
|
|
8
22
|
## [0.8.32] - 2026-07-29
|
|
9
23
|
|
|
10
24
|
### Features
|
|
@@ -3,7 +3,7 @@ import { a as resolveDingtalkAccount, c as addWildcardAllowFrom, d as hasConfigu
|
|
|
3
3
|
import { t as createLogger } from "./logger-CnBTcwyq.mjs";
|
|
4
4
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
5
5
|
import "./utils-CIfI_3Jh.mjs";
|
|
6
|
-
import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-
|
|
6
|
+
import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-CwQLykl6.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { z, z as z$1 } from "zod";
|
|
9
9
|
//#region src/secret-input.ts
|
|
@@ -111,6 +111,20 @@ const DingtalkSharedConfigShape = {
|
|
|
111
111
|
*/
|
|
112
112
|
messageAnswerCard: z$1.boolean().optional(),
|
|
113
113
|
groupReplyMode: GroupReplyModeSchema,
|
|
114
|
+
/**
|
|
115
|
+
* dws CLI 发消息成功后,是否写入与 message 工具一致的 outbound_message 上下文。
|
|
116
|
+
* - target(默认):写入目标会话(群/好友),对方会话后续能看见「谁让 bot 发了什么」
|
|
117
|
+
* - source:写入发起会话
|
|
118
|
+
* - both:两边都写
|
|
119
|
+
* - off:关闭(完全不改行为)
|
|
120
|
+
* 仅叠加:不影响普通回复 / message 工具;解析失败或 dws 非发消息命令时 no-op。
|
|
121
|
+
*/
|
|
122
|
+
dwsDeliveryContext: z$1.enum([
|
|
123
|
+
"off",
|
|
124
|
+
"target",
|
|
125
|
+
"source",
|
|
126
|
+
"both"
|
|
127
|
+
]).optional(),
|
|
114
128
|
/** AI Card 模板 ID,不填则使用官方默认模板 */
|
|
115
129
|
cardTemplateId: z$1.string().optional(),
|
|
116
130
|
/** AI Card 内容变量名,对应卡片模板中的变量字段,不填默认 content(与默认增强模板一致) */
|
|
@@ -956,8 +970,8 @@ async function monitorDingtalkProvider(opts = {}) {
|
|
|
956
970
|
const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
|
|
957
971
|
const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
|
|
958
972
|
import("./accounts-BQptOmgB.mjs"),
|
|
959
|
-
import("./message-handler-
|
|
960
|
-
import("./connection-
|
|
973
|
+
import("./message-handler-BoVSEDwY.mjs"),
|
|
974
|
+
import("./connection-Bh3U9I4x.mjs")
|
|
961
975
|
]);
|
|
962
976
|
const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
|
|
963
977
|
const { handleDingTalkMessage } = monitorAccountModule;
|
|
@@ -1445,29 +1459,4 @@ function initDingtalkPluginConfigSchema() {
|
|
|
1445
1459
|
dingtalkPlugin.configSchema = buildChannelConfigSchema(DingtalkConfigBaseSchema);
|
|
1446
1460
|
}
|
|
1447
1461
|
//#endregion
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* 自实现的运行时存储工厂,避免依赖特定版本 openclaw 是否导出 createPluginRuntimeStore。
|
|
1451
|
-
* 旧版 openclaw 没有导出该函数,直接 import 会导致 TypeError,因此在此处内联实现。
|
|
1452
|
-
*/
|
|
1453
|
-
function createRuntimeStore(errorMessage) {
|
|
1454
|
-
let runtimeValue = null;
|
|
1455
|
-
return {
|
|
1456
|
-
setRuntime: (next) => {
|
|
1457
|
-
runtimeValue = next;
|
|
1458
|
-
},
|
|
1459
|
-
clearRuntime: () => {
|
|
1460
|
-
runtimeValue = null;
|
|
1461
|
-
},
|
|
1462
|
-
tryGetRuntime: () => {
|
|
1463
|
-
return runtimeValue;
|
|
1464
|
-
},
|
|
1465
|
-
getRuntime: () => {
|
|
1466
|
-
if (runtimeValue === null) throw new Error(errorMessage);
|
|
1467
|
-
return runtimeValue;
|
|
1468
|
-
}
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
const { setRuntime: setDingtalkRuntime, getRuntime: getDingtalkRuntime } = createRuntimeStore("DingTalk runtime not initialized");
|
|
1472
|
-
//#endregion
|
|
1473
|
-
export { initDingtalkPluginConfigSchema as a, dingtalkPlugin as i, setDingtalkRuntime as n, CHANNEL_ID as r, getDingtalkRuntime as t };
|
|
1462
|
+
export { dingtalkPlugin as n, initDingtalkPluginConfigSchema as r, CHANNEL_ID as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as createLogger } from "./logger-CnBTcwyq.mjs";
|
|
2
2
|
import { r as dingtalkUploadHttp } from "./http-client-DFWZgO1n.mjs";
|
|
3
|
-
import { t as CHUNK_CONFIG } from "./chunk-upload-
|
|
3
|
+
import { t as CHUNK_CONFIG } from "./chunk-upload-z185bTqH.mjs";
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import FormData from "form-data";
|
|
@@ -48,7 +48,7 @@ async function uploadMediaToDingTalk(filePath, mediaType, oapiToken, maxSize = 2
|
|
|
48
48
|
if ((mediaType === "video" || mediaType === "file") && fileSize > CHUNK_CONFIG.SIZE_THRESHOLD) {
|
|
49
49
|
log?.info?.(`文件超过 20MB,使用分块上传:${absPath} (${fileSizeMB}MB)`);
|
|
50
50
|
try {
|
|
51
|
-
const { uploadLargeFileByChunks } = await import("./chunk-upload-
|
|
51
|
+
const { uploadLargeFileByChunks } = await import("./chunk-upload-CNlZg1SF.mjs");
|
|
52
52
|
const downloadCode = await uploadLargeFileByChunks(absPath, mediaType, oapiToken, debugEnabled);
|
|
53
53
|
if (downloadCode) {
|
|
54
54
|
log?.info?.(`分块上传成功:${absPath}, download_code: ${downloadCode}`);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as VIDEO_MARKER_PATTERN, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-
|
|
1
|
+
import { i as VIDEO_MARKER_PATTERN, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-1-GDZ2h3.mjs";
|
|
2
2
|
export { AUDIO_MARKER_PATTERN, FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as checkAndMarkDingtalkMessage } from "./utils-legacy-
|
|
2
|
-
import { n as onBackgroundWorkCountChange, t as getActiveBackgroundWorkCount } from "./background-work-
|
|
1
|
+
import { i as checkAndMarkDingtalkMessage } from "./utils-legacy-CGEa8tg2.mjs";
|
|
2
|
+
import { n as onBackgroundWorkCountChange, t as getActiveBackgroundWorkCount } from "./background-work-874-aK4-.mjs";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
//#region src/core/connection.ts
|
|
5
5
|
/**
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { t as CHANNEL_ID } from "./channel-CABtqkf_.mjs";
|
|
2
|
+
//#region src/services/dws-delivery-context.ts
|
|
3
|
+
/**
|
|
4
|
+
* DWS 发消息成功后注入 outbound_message 上下文(对齐 OpenClaw message 工具 OC-4)。
|
|
5
|
+
*
|
|
6
|
+
* - 不修改 dws 本体:仅解析 OpenClaw exec/shell 回调里看到的命令行
|
|
7
|
+
* - 默认开启(target):只在 dws 发消息成功时追加上下文,不影响普通回复 / message 工具
|
|
8
|
+
* - 可配置 off | target | source | both
|
|
9
|
+
*/
|
|
10
|
+
const SEND_COMMAND_RE = /\bdws\b(?:\s+[^\s]+)*\s+chat\s+message\s+(send-by-bot|send-by-webhook|send|reply)\b/i;
|
|
11
|
+
/** 从命令行提取 --flag value / --flag=value / 布尔 --flag */
|
|
12
|
+
function extractFlag(cmd, name) {
|
|
13
|
+
const eq = new RegExp(`--${name}(?:=|\\s+)(?:"([^"]*)"|'([^']*)'|(\\S+))`, "i");
|
|
14
|
+
const m = cmd.match(eq);
|
|
15
|
+
if (!m) return void 0;
|
|
16
|
+
return (m[1] ?? m[2] ?? m[3] ?? "").trim() || void 0;
|
|
17
|
+
}
|
|
18
|
+
function hasBoolFlag(cmd, name) {
|
|
19
|
+
return new RegExp(`(?:^|\\s)--${name}(?:\\s|=|$)`, "i").test(cmd);
|
|
20
|
+
}
|
|
21
|
+
function splitCsv(raw) {
|
|
22
|
+
if (!raw) return void 0;
|
|
23
|
+
const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
24
|
+
return parts.length ? parts : void 0;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 判断是否为 dws 发消息类命令,并解析关键参数。
|
|
28
|
+
* 解析失败返回 null(调用方 no-op,不影响现有体验)。
|
|
29
|
+
*/
|
|
30
|
+
function parseDwsSendCommand(commandText) {
|
|
31
|
+
const cmd = String(commandText || "").trim();
|
|
32
|
+
if (!cmd) return null;
|
|
33
|
+
const m = cmd.match(SEND_COMMAND_RE);
|
|
34
|
+
if (!m) return null;
|
|
35
|
+
const sub = m[1].toLowerCase();
|
|
36
|
+
let kind;
|
|
37
|
+
if (sub === "send-by-bot") kind = "bot-send";
|
|
38
|
+
else if (sub === "send-by-webhook") kind = "webhook-send";
|
|
39
|
+
else if (sub === "reply") kind = "reply";
|
|
40
|
+
else kind = "user-send";
|
|
41
|
+
const group = extractFlag(cmd, "group") || extractFlag(cmd, "conversation-id") || extractFlag(cmd, "id") || extractFlag(cmd, "chat");
|
|
42
|
+
const user = extractFlag(cmd, "user") || extractFlag(cmd, "open-dingtalk-id") || splitCsv(extractFlag(cmd, "users"))?.[0] || splitCsv(extractFlag(cmd, "open-dingtalk-ids"))?.[0];
|
|
43
|
+
const text = extractFlag(cmd, "text") || extractFlag(cmd, "content") || extractFlag(cmd, "body") || extractFlag(cmd, "message") || extractFlag(cmd, "markdown");
|
|
44
|
+
let target;
|
|
45
|
+
let rawTarget;
|
|
46
|
+
let targetChatType;
|
|
47
|
+
if (group) {
|
|
48
|
+
rawTarget = group.replace(/^(group:|channel:)/i, "");
|
|
49
|
+
target = `group:${rawTarget}`;
|
|
50
|
+
targetChatType = "group";
|
|
51
|
+
} else if (user) {
|
|
52
|
+
rawTarget = user.replace(/^(user:|dm:)/i, "");
|
|
53
|
+
target = `user:${rawTarget}`;
|
|
54
|
+
targetChatType = "direct";
|
|
55
|
+
}
|
|
56
|
+
if (kind === "webhook-send" && !target) return {
|
|
57
|
+
kind,
|
|
58
|
+
message: text,
|
|
59
|
+
title: extractFlag(cmd, "title"),
|
|
60
|
+
atAll: hasBoolFlag(cmd, "at-all"),
|
|
61
|
+
atUserIds: splitCsv(extractFlag(cmd, "at-users")),
|
|
62
|
+
atMobiles: splitCsv(extractFlag(cmd, "at-mobiles")),
|
|
63
|
+
command: cmd.slice(0, 2e3)
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
kind,
|
|
67
|
+
target,
|
|
68
|
+
rawTarget,
|
|
69
|
+
targetChatType,
|
|
70
|
+
message: text,
|
|
71
|
+
title: extractFlag(cmd, "title"),
|
|
72
|
+
atAll: hasBoolFlag(cmd, "at-all"),
|
|
73
|
+
atUserIds: splitCsv(extractFlag(cmd, "at-user-ids")) || splitCsv(extractFlag(cmd, "at-users")),
|
|
74
|
+
atOpenDingTalkIds: splitCsv(extractFlag(cmd, "at-open-dingtalk-ids")),
|
|
75
|
+
atMobiles: splitCsv(extractFlag(cmd, "at-mobiles")),
|
|
76
|
+
robotCode: extractFlag(cmd, "robot-code"),
|
|
77
|
+
command: cmd.slice(0, 2e3)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function resolveDwsDeliveryContextMode(config) {
|
|
81
|
+
const raw = config?.dwsDeliveryContext;
|
|
82
|
+
if (raw === "off" || raw === false) return "off";
|
|
83
|
+
if (raw === "source" || raw === "both" || raw === "target") return raw;
|
|
84
|
+
if (raw === true || raw === void 0 || raw === null) return "target";
|
|
85
|
+
return "off";
|
|
86
|
+
}
|
|
87
|
+
let warnedMissingApi = false;
|
|
88
|
+
function resolveModeFromConfigs(cfg, accountConfig) {
|
|
89
|
+
if (accountConfig?.dwsDeliveryContext !== void 0) return resolveDwsDeliveryContextMode(accountConfig);
|
|
90
|
+
const channelCfg = cfg?.channels?.[CHANNEL_ID];
|
|
91
|
+
if (channelCfg?.dwsDeliveryContext !== void 0) return resolveDwsDeliveryContextMode(channelCfg);
|
|
92
|
+
return "target";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 在 dws 发消息命令成功结束后,向目标/来源会话写入 outbound_message。
|
|
96
|
+
* 全程 best-effort,失败只打日志。
|
|
97
|
+
*/
|
|
98
|
+
async function maybeInjectDwsOutboundContext(params) {
|
|
99
|
+
const log = params.log;
|
|
100
|
+
const effectiveMode = resolveModeFromConfigs(params.cfg, params.accountConfig);
|
|
101
|
+
if (effectiveMode === "off") return false;
|
|
102
|
+
if (params.phase && params.phase !== "end") return false;
|
|
103
|
+
if (params.exitCode !== void 0 && params.exitCode !== null && params.exitCode !== 0) return false;
|
|
104
|
+
const parsed = parseDwsSendCommand(params.commandText);
|
|
105
|
+
if (!parsed) return false;
|
|
106
|
+
if (!parsed.target && effectiveMode === "target") {
|
|
107
|
+
log?.info?.(`[DingTalk][dwsDeliveryContext] 解析到 dws ${parsed.kind} 但无 group/user 目标,跳过 target 注入`);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const oc = await import("openclaw/plugin-sdk/outbound-runtime");
|
|
112
|
+
const append = oc.appendOutboundMessageDeliveryContext;
|
|
113
|
+
const resolveRoute = oc.resolveOutboundSessionRoute;
|
|
114
|
+
const ensureEntry = oc.ensureOutboundSessionEntry;
|
|
115
|
+
if (typeof append !== "function") {
|
|
116
|
+
if (!warnedMissingApi) {
|
|
117
|
+
warnedMissingApi = true;
|
|
118
|
+
log?.warn?.("[DingTalk][dwsDeliveryContext] 当前 OpenClaw 未导出 appendOutboundMessageDeliveryContext;请升级/重建 openclaw(plugin-sdk/outbound-runtime)。本功能已跳过,不影响发消息。");
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
let targetRoute = null;
|
|
123
|
+
const targetTo = parsed.target;
|
|
124
|
+
if (targetTo && typeof resolveRoute === "function") try {
|
|
125
|
+
targetRoute = await resolveRoute({
|
|
126
|
+
cfg: params.cfg,
|
|
127
|
+
channel: CHANNEL_ID,
|
|
128
|
+
agentId: params.agentId,
|
|
129
|
+
accountId: params.accountId,
|
|
130
|
+
target: targetTo,
|
|
131
|
+
currentSessionKey: params.sourceSessionKey
|
|
132
|
+
});
|
|
133
|
+
if (targetRoute && typeof ensureEntry === "function") await ensureEntry({
|
|
134
|
+
cfg: params.cfg,
|
|
135
|
+
channel: CHANNEL_ID,
|
|
136
|
+
accountId: params.accountId,
|
|
137
|
+
route: targetRoute
|
|
138
|
+
});
|
|
139
|
+
} catch (err) {
|
|
140
|
+
log?.warn?.(`[DingTalk][dwsDeliveryContext] resolveOutboundSessionRoute 失败: ${err?.message || err}`);
|
|
141
|
+
}
|
|
142
|
+
const action = parsed.kind === "bot-send" ? "dws.chat.message.send-by-bot" : parsed.kind === "webhook-send" ? "dws.chat.message.send-by-webhook" : parsed.kind === "reply" ? "dws.chat.message.reply" : "dws.chat.message.send";
|
|
143
|
+
const actionParams = {
|
|
144
|
+
action: "send",
|
|
145
|
+
via: "dws",
|
|
146
|
+
dwsKind: parsed.kind,
|
|
147
|
+
message: parsed.message ?? "",
|
|
148
|
+
title: parsed.title,
|
|
149
|
+
target: targetTo,
|
|
150
|
+
to: parsed.rawTarget,
|
|
151
|
+
channel: CHANNEL_ID,
|
|
152
|
+
accountId: params.accountId,
|
|
153
|
+
atAll: parsed.atAll || void 0,
|
|
154
|
+
atUserIds: parsed.atUserIds,
|
|
155
|
+
atOpenDingTalkIds: parsed.atOpenDingTalkIds,
|
|
156
|
+
atMobiles: parsed.atMobiles,
|
|
157
|
+
robotCode: parsed.robotCode,
|
|
158
|
+
command: parsed.command
|
|
159
|
+
};
|
|
160
|
+
const idempotencyKey = params.toolCallId?.trim() || `dws:${params.sourceSessionKey}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
161
|
+
await append({
|
|
162
|
+
cfg: params.cfg,
|
|
163
|
+
mode: effectiveMode,
|
|
164
|
+
agentId: params.agentId,
|
|
165
|
+
sourceSessionKey: params.sourceSessionKey,
|
|
166
|
+
sourceAccountId: params.accountId,
|
|
167
|
+
invokerId: params.invokerId,
|
|
168
|
+
invokerName: params.invokerName,
|
|
169
|
+
targetTo: targetTo || params.sourceSessionKey,
|
|
170
|
+
targetChannel: CHANNEL_ID,
|
|
171
|
+
targetAccountId: params.accountId,
|
|
172
|
+
targetRoute,
|
|
173
|
+
actionParams,
|
|
174
|
+
action,
|
|
175
|
+
idempotencyKey
|
|
176
|
+
});
|
|
177
|
+
log?.info?.(`[DingTalk][dwsDeliveryContext] 已注入 outbound_message mode=${effectiveMode} kind=${parsed.kind} target=${targetTo || "-"} route=${targetRoute?.sessionKey || "-"}`);
|
|
178
|
+
return true;
|
|
179
|
+
} catch (err) {
|
|
180
|
+
log?.warn?.(`[DingTalk][dwsDeliveryContext] 注入失败(已忽略,不影响发消息): ${err?.message || err}`);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
export { maybeInjectDwsOutboundContext };
|
package/dist/entry-bundled.mjs
CHANGED
|
@@ -23,7 +23,7 @@ var entry_bundled_default = defineBundledChannelEntry({
|
|
|
23
23
|
exportName: "setDingtalkRuntime"
|
|
24
24
|
},
|
|
25
25
|
async registerFull(api) {
|
|
26
|
-
const { registerGatewayMethods } = await import("./gateway-methods-
|
|
26
|
+
const { registerGatewayMethods } = await import("./gateway-methods-CV8y0xGw.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-BAzdqkAV.mjs";
|
|
2
2
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
3
3
|
import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
4
|
-
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-
|
|
5
|
-
import { d as getUnionId, h as recallEmotionReply } from "./utils-legacy-
|
|
4
|
+
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-CwQLykl6.mjs";
|
|
5
|
+
import { d as getUnionId, h as recallEmotionReply } from "./utils-legacy-CGEa8tg2.mjs";
|
|
6
6
|
//#region src/docs.ts
|
|
7
7
|
var DingtalkDocsClient = class {
|
|
8
8
|
config;
|
|
@@ -592,7 +592,7 @@ function registerGatewayMethods(api) {
|
|
|
592
592
|
});
|
|
593
593
|
const results = {};
|
|
594
594
|
if (cardInstanceId) try {
|
|
595
|
-
const { getAccessToken } = await import("./utils-legacy-
|
|
595
|
+
const { getAccessToken } = await import("./utils-legacy-Bx5izlVF.mjs");
|
|
596
596
|
const token = await getAccessToken(account.config);
|
|
597
597
|
await finishAICard({
|
|
598
598
|
cardInstanceId: String(cardInstanceId),
|
|
@@ -752,7 +752,7 @@ function registerGatewayMethods(api) {
|
|
|
752
752
|
try {
|
|
753
753
|
const account = resolveDingtalkAccount({ cfg });
|
|
754
754
|
if (!account.config?.clientId || !account.config?.clientSecret) return respond(false, { error: "Not configured" });
|
|
755
|
-
const { getAccessToken } = await import("./utils-legacy-
|
|
755
|
+
const { getAccessToken } = await import("./utils-legacy-Bx5izlVF.mjs");
|
|
756
756
|
await getAccessToken(account.config);
|
|
757
757
|
respond(true, {
|
|
758
758
|
ok: true,
|
package/dist/index.d.mts
CHANGED
|
@@ -76,6 +76,12 @@ declare const DingtalkConfigSchema: z$1.ZodObject<{
|
|
|
76
76
|
text: "text";
|
|
77
77
|
markdown: "markdown";
|
|
78
78
|
}>>;
|
|
79
|
+
dwsDeliveryContext: z$1.ZodOptional<z$1.ZodEnum<{
|
|
80
|
+
source: "source";
|
|
81
|
+
off: "off";
|
|
82
|
+
target: "target";
|
|
83
|
+
both: "both";
|
|
84
|
+
}>>;
|
|
79
85
|
cardTemplateId: z$1.ZodOptional<z$1.ZodString>;
|
|
80
86
|
cardContentVar: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
|
|
81
87
|
answerCard: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
@@ -134,6 +140,12 @@ declare const DingtalkConfigSchema: z$1.ZodObject<{
|
|
|
134
140
|
text: "text";
|
|
135
141
|
markdown: "markdown";
|
|
136
142
|
}>>;
|
|
143
|
+
dwsDeliveryContext: z$1.ZodOptional<z$1.ZodEnum<{
|
|
144
|
+
source: "source";
|
|
145
|
+
off: "off";
|
|
146
|
+
target: "target";
|
|
147
|
+
both: "both";
|
|
148
|
+
}>>;
|
|
137
149
|
cardTemplateId: z$1.ZodOptional<z$1.ZodString>;
|
|
138
150
|
cardContentVar: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
|
|
139
151
|
answerCard: z$1.ZodOptional<z$1.ZodBoolean>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { n as dingtalkPlugin, r as initDingtalkPluginConfigSchema } from "./channel-CABtqkf_.mjs";
|
|
2
|
+
import { n as setDingtalkRuntime } from "./runtime-DDMzayvA.mjs";
|
|
3
|
+
import { t as registerGatewayMethods } from "./gateway-methods-Ce1FXmCx.mjs";
|
|
3
4
|
//#region index.ts
|
|
4
5
|
/**
|
|
5
6
|
* 检测同一 plugin id 在多个路径被加载的情况。
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount } from "./accounts-BAzdqkAV.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { t as CHANNEL_ID } from "./channel-CABtqkf_.mjs";
|
|
3
3
|
import { n as createLoggerFromConfig, r as isDingtalkDebug } from "./logger-CnBTcwyq.mjs";
|
|
4
4
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
5
5
|
import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
|
|
6
|
-
import { S as finalClean, _ as FINAL_TAG, a as sendTextMessage, b as estimateTokens, d as finishAICard, g as unregisterActiveCard, h as streamAICard, i as sendProactive, m as registerActiveCard, p as isQpsLimitError, r as sendMessage, t as sendMarkdownMessage, u as createAICardForTarget, v as PROCESS_TAG, x as extractFinal, y as displayClean } from "./messaging-
|
|
6
|
+
import { S as finalClean, _ as FINAL_TAG, a as sendTextMessage, b as estimateTokens, d as finishAICard, g as unregisterActiveCard, h as streamAICard, i as sendProactive, m as registerActiveCard, p as isQpsLimitError, r as sendMessage, t as sendMarkdownMessage, u as createAICardForTarget, v as PROCESS_TAG, x as extractFinal, y as displayClean } from "./messaging-CwQLykl6.mjs";
|
|
7
7
|
import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import { r as
|
|
10
|
-
import "./
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
8
|
+
import { t as getDingtalkRuntime } from "./runtime-DDMzayvA.mjs";
|
|
9
|
+
import { c as formatSenderRoles, f as getUserProfile, h as recallEmotionReply, l as getAccessToken, o as formatHiredDateShanghai, r as addEmotionReply, s as formatSenderDisplayLabel, t as DINGTALK_API, u as getOapiAccessToken$1 } from "./utils-legacy-CGEa8tg2.mjs";
|
|
10
|
+
import { r as trackBackgroundWork } from "./background-work-874-aK4-.mjs";
|
|
11
|
+
import "./chunk-upload-z185bTqH.mjs";
|
|
12
|
+
import { a as toLocalPath, i as VIDEO_MARKER_PATTERN, o as uploadMediaToDingTalk, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-1-GDZ2h3.mjs";
|
|
13
|
+
import { n as lookupCardContent, t as collectCardLookupIds } from "./card-content-cache-CGggfgCg.mjs";
|
|
13
14
|
import * as fs from "fs";
|
|
14
15
|
import * as path from "path";
|
|
15
16
|
import * as os from "node:os";
|
|
@@ -250,7 +251,7 @@ function readPayloadRawError(payload) {
|
|
|
250
251
|
}
|
|
251
252
|
function createDingtalkReplyDispatcher(params) {
|
|
252
253
|
const core = getDingtalkRuntime();
|
|
253
|
-
const { cfg, agentId, conversationId, senderId, isDirect, accountId, sessionWebhook, asyncMode = false, preCreatedCard } = params;
|
|
254
|
+
const { cfg, agentId, conversationId, senderId, isDirect, accountId, sessionWebhook, asyncMode = false, preCreatedCard, sessionKey: sourceSessionKey, senderName } = params;
|
|
254
255
|
const account = resolveDingtalkAccount({
|
|
255
256
|
cfg,
|
|
256
257
|
accountId
|
|
@@ -633,7 +634,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
633
634
|
}
|
|
634
635
|
} else log.info(`[DingTalk][closeStreaming] 养成系统:onCommandOutput 监听到 ${productsToProcess.size} 个 dws 产品: ${[...productsToProcess].join(", ")}`);
|
|
635
636
|
if (productsToProcess.size > 0) {
|
|
636
|
-
const { GamificationEngine } = await import("./game-xiyou-
|
|
637
|
+
const { GamificationEngine } = await import("./game-xiyou-CKoWJobs.mjs");
|
|
637
638
|
const engine = GamificationEngine.getInstanceForUser(senderId);
|
|
638
639
|
if (engine.isEnabled()) {
|
|
639
640
|
const primaryProduct = [...productsToProcess][0];
|
|
@@ -978,7 +979,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
978
979
|
currentToolLine = "";
|
|
979
980
|
await startStreaming();
|
|
980
981
|
if (currentCardTarget) try {
|
|
981
|
-
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-
|
|
982
|
+
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-DCVdWMiZ.mjs");
|
|
982
983
|
await enqueueCardStream(buildCardContent().replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim(), { force: true });
|
|
983
984
|
} catch (err) {
|
|
984
985
|
if (!isQpsLimitError(err)) log.error(`[DingTalk][onPartialReply] final-marker 刷卡失败:${err.message}`);
|
|
@@ -991,7 +992,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
991
992
|
if (payload.text?.trim()) hasModelStreamText = true;
|
|
992
993
|
currentToolLine = "";
|
|
993
994
|
try {
|
|
994
|
-
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-
|
|
995
|
+
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-DCVdWMiZ.mjs");
|
|
995
996
|
await enqueueCardStream(buildCardContent().replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim());
|
|
996
997
|
} catch (err) {
|
|
997
998
|
if (isQpsLimitError(err)) log.warn(`[DingTalk][onPartialReply] QPS 限流,已排队尾随重试 latestLen=${latestCardContent.length}`);
|
|
@@ -1019,7 +1020,8 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
1019
1020
|
}
|
|
1020
1021
|
},
|
|
1021
1022
|
onCommandOutput: (payload) => {
|
|
1022
|
-
const
|
|
1023
|
+
const commandText = payload.title || payload.name || "";
|
|
1024
|
+
const dwsMatch = commandText.match(DWS_PRODUCT_PATTERN) || payload.output?.match(DWS_PRODUCT_PATTERN);
|
|
1023
1025
|
if (dwsMatch) {
|
|
1024
1026
|
const product = dwsMatch[1];
|
|
1025
1027
|
if (!(payload.phase === "end" && payload.exitCode !== null && payload.exitCode !== 0)) {
|
|
@@ -1027,6 +1029,29 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
1027
1029
|
log.info(`[DingTalk][onCommandOutput] 检测到 dws 产品: ${product},phase=${payload.phase}, exitCode=${payload.exitCode}`);
|
|
1028
1030
|
} else log.info(`[DingTalk][onCommandOutput] dws 命令执行失败,跳过: ${product},exitCode=${payload.exitCode}`);
|
|
1029
1031
|
}
|
|
1032
|
+
if (payload.phase === "end" && sourceSessionKey) {
|
|
1033
|
+
const fullCmd = [
|
|
1034
|
+
payload.title,
|
|
1035
|
+
payload.name,
|
|
1036
|
+
payload.output
|
|
1037
|
+
].filter(Boolean).join("\n") || commandText;
|
|
1038
|
+
import("./dws-delivery-context-ESC7sij7.mjs").then(({ maybeInjectDwsOutboundContext }) => maybeInjectDwsOutboundContext({
|
|
1039
|
+
cfg,
|
|
1040
|
+
accountConfig: account.config,
|
|
1041
|
+
agentId,
|
|
1042
|
+
accountId,
|
|
1043
|
+
sourceSessionKey,
|
|
1044
|
+
invokerId: senderId,
|
|
1045
|
+
invokerName: senderName,
|
|
1046
|
+
commandText: fullCmd,
|
|
1047
|
+
exitCode: payload.exitCode,
|
|
1048
|
+
phase: payload.phase,
|
|
1049
|
+
toolCallId: payload.toolCallId,
|
|
1050
|
+
log
|
|
1051
|
+
})).catch((err) => {
|
|
1052
|
+
log.warn?.(`[DingTalk][dwsDeliveryContext] onCommandOutput 钩子异常: ${err?.message || err}`);
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1030
1055
|
}
|
|
1031
1056
|
},
|
|
1032
1057
|
markDispatchIdle,
|
|
@@ -2008,7 +2033,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2008
2033
|
const rawText = content.text || "";
|
|
2009
2034
|
let userContent = normalizeSlashCommand(rawText) || (content.imageUrls.length > 0 ? "请描述这张图片" : "");
|
|
2010
2035
|
try {
|
|
2011
|
-
const { GamificationEngine, isGamificationCommand } = await import("./game-xiyou-
|
|
2036
|
+
const { GamificationEngine, isGamificationCommand } = await import("./game-xiyou-CKoWJobs.mjs");
|
|
2012
2037
|
if (isGamificationCommand(rawText)) {
|
|
2013
2038
|
const engine = GamificationEngine.getInstanceForUser(senderId);
|
|
2014
2039
|
if (rawText.trim().startsWith("/西游") || engine.isEnabled()) {
|
|
@@ -2277,7 +2302,9 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2277
2302
|
messageCreateTimeMs: Date.now(),
|
|
2278
2303
|
sessionWebhook: data.sessionWebhook,
|
|
2279
2304
|
asyncMode,
|
|
2280
|
-
preCreatedCard: earlyCard
|
|
2305
|
+
preCreatedCard: earlyCard,
|
|
2306
|
+
sessionKey,
|
|
2307
|
+
senderName
|
|
2281
2308
|
});
|
|
2282
2309
|
finalizeDispatchError = finalizeErr;
|
|
2283
2310
|
if (config.clientId) {
|
|
@@ -371,7 +371,7 @@ async function finishAICard(card, content, config, log, contentVar, skipInputing
|
|
|
371
371
|
const fixedContent = ensureTableBlankLines(cleanContent);
|
|
372
372
|
log?.info?.(`[DingTalk][AICard] 开始 finish(一次性定稿,无流式回放),最终内容长度=${fixedContent.length}`);
|
|
373
373
|
try {
|
|
374
|
-
const { rememberCardContent } = await import("./card-content-cache-
|
|
374
|
+
const { rememberCardContent } = await import("./card-content-cache-BsFPIwOr.mjs");
|
|
375
375
|
rememberCardContent({
|
|
376
376
|
text: fixedContent,
|
|
377
377
|
outTrackId: card.cardInstanceId,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/runtime.ts
|
|
2
|
+
/**
|
|
3
|
+
* 自实现的运行时存储工厂,避免依赖特定版本 openclaw 是否导出 createPluginRuntimeStore。
|
|
4
|
+
* 旧版 openclaw 没有导出该函数,直接 import 会导致 TypeError,因此在此处内联实现。
|
|
5
|
+
*/
|
|
6
|
+
function createRuntimeStore(errorMessage) {
|
|
7
|
+
let runtimeValue = null;
|
|
8
|
+
return {
|
|
9
|
+
setRuntime: (next) => {
|
|
10
|
+
runtimeValue = next;
|
|
11
|
+
},
|
|
12
|
+
clearRuntime: () => {
|
|
13
|
+
runtimeValue = null;
|
|
14
|
+
},
|
|
15
|
+
tryGetRuntime: () => {
|
|
16
|
+
return runtimeValue;
|
|
17
|
+
},
|
|
18
|
+
getRuntime: () => {
|
|
19
|
+
if (runtimeValue === null) throw new Error(errorMessage);
|
|
20
|
+
return runtimeValue;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const { setRuntime: setDingtalkRuntime, getRuntime: getDingtalkRuntime } = createRuntimeStore("DingTalk runtime not initialized");
|
|
25
|
+
//#endregion
|
|
26
|
+
export { setDingtalkRuntime as n, getDingtalkRuntime as t };
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dingtalk-connector",
|
|
3
3
|
"name": "DingTalk Channel",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.33",
|
|
5
5
|
"description": "OpenClaw DingTalk channel plugin (community) | 钉钉 OpenClaw 社区增强版",
|
|
6
6
|
"author": "jeik",
|
|
7
7
|
"main": "index.ts",
|
|
@@ -269,6 +269,17 @@
|
|
|
269
269
|
"markdown"
|
|
270
270
|
]
|
|
271
271
|
},
|
|
272
|
+
"dwsDeliveryContext": {
|
|
273
|
+
"type": "string",
|
|
274
|
+
"enum": [
|
|
275
|
+
"off",
|
|
276
|
+
"target",
|
|
277
|
+
"source",
|
|
278
|
+
"both"
|
|
279
|
+
],
|
|
280
|
+
"default": "target",
|
|
281
|
+
"description": "dws CLI 发消息成功后写入 outbound_message 上下文(与 message 工具一致)。target=目标会话(默认);source=发起会话;both=两边;off=关闭。仅叠加,不影响普通回复/message 工具。"
|
|
282
|
+
},
|
|
272
283
|
"cardTemplateId": {
|
|
273
284
|
"type": "string"
|
|
274
285
|
},
|
|
@@ -522,6 +533,17 @@
|
|
|
522
533
|
"markdown"
|
|
523
534
|
]
|
|
524
535
|
},
|
|
536
|
+
"dwsDeliveryContext": {
|
|
537
|
+
"type": "string",
|
|
538
|
+
"enum": [
|
|
539
|
+
"off",
|
|
540
|
+
"target",
|
|
541
|
+
"source",
|
|
542
|
+
"both"
|
|
543
|
+
],
|
|
544
|
+
"default": "target",
|
|
545
|
+
"description": "dws CLI 发消息成功后写入 outbound_message 上下文。target=目标会话(默认);source=发起会话;both=两边;off=关闭。"
|
|
546
|
+
},
|
|
525
547
|
"cardTemplateId": {
|
|
526
548
|
"type": "string"
|
|
527
549
|
},
|
|
@@ -588,6 +610,11 @@
|
|
|
588
610
|
"help": "群回复形态:aicard(默认,流式 AI 卡)/ text / markdown。text、markdown 时群聊不用 AI 卡,便于多机器人互相 @。",
|
|
589
611
|
"advanced": true
|
|
590
612
|
},
|
|
613
|
+
"channels.dingtalk-connector.dwsDeliveryContext": {
|
|
614
|
+
"label": "DWS Delivery Context",
|
|
615
|
+
"help": "dws 发消息成功后注入 outbound_message(与 message 工具一致)。默认 target;设 off 关闭。仅叠加,不影响现有回复。",
|
|
616
|
+
"advanced": true
|
|
617
|
+
},
|
|
591
618
|
"channels.dingtalk-connector.cardTemplateId": {
|
|
592
619
|
"label": "AI Card Template ID",
|
|
593
620
|
"help": "会话流式 AI Card 模板 ID;不填默认 0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema。",
|
package/package.json
CHANGED
package/src/config/schema.ts
CHANGED
|
@@ -100,6 +100,17 @@ const DingtalkSharedConfigShape = {
|
|
|
100
100
|
*/
|
|
101
101
|
messageAnswerCard: z.boolean().optional(),
|
|
102
102
|
groupReplyMode: GroupReplyModeSchema,
|
|
103
|
+
/**
|
|
104
|
+
* dws CLI 发消息成功后,是否写入与 message 工具一致的 outbound_message 上下文。
|
|
105
|
+
* - target(默认):写入目标会话(群/好友),对方会话后续能看见「谁让 bot 发了什么」
|
|
106
|
+
* - source:写入发起会话
|
|
107
|
+
* - both:两边都写
|
|
108
|
+
* - off:关闭(完全不改行为)
|
|
109
|
+
* 仅叠加:不影响普通回复 / message 工具;解析失败或 dws 非发消息命令时 no-op。
|
|
110
|
+
*/
|
|
111
|
+
dwsDeliveryContext: z
|
|
112
|
+
.enum(["off", "target", "source", "both"])
|
|
113
|
+
.optional(),
|
|
103
114
|
/** AI Card 模板 ID,不填则使用官方默认模板 */
|
|
104
115
|
cardTemplateId: z.string().optional(),
|
|
105
116
|
/** AI Card 内容变量名,对应卡片模板中的变量字段,不填默认 content(与默认增强模板一致) */
|
|
@@ -1794,6 +1794,8 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
|
|
|
1794
1794
|
sessionWebhook: data.sessionWebhook,
|
|
1795
1795
|
asyncMode,
|
|
1796
1796
|
preCreatedCard: earlyCard,
|
|
1797
|
+
sessionKey, // dws 发消息成功后写 outbound_message 用
|
|
1798
|
+
senderName,
|
|
1797
1799
|
});
|
|
1798
1800
|
finalizeDispatchError = finalizeErr;
|
|
1799
1801
|
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -274,6 +274,10 @@ export type CreateDingtalkReplyDispatcherParams = {
|
|
|
274
274
|
asyncMode?: boolean;
|
|
275
275
|
/** 队列繁忙时预先创建的 AI Card,startStreaming 时直接复用而非新建 */
|
|
276
276
|
preCreatedCard?: AICardInstance;
|
|
277
|
+
/** 当前会话 sessionKey(用于 dws 发消息成功后写 outbound_message) */
|
|
278
|
+
sessionKey?: string;
|
|
279
|
+
/** 调用人昵称(dws outbound 上下文 invoker) */
|
|
280
|
+
senderName?: string;
|
|
277
281
|
};
|
|
278
282
|
|
|
279
283
|
export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatcherParams) {
|
|
@@ -288,6 +292,8 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
288
292
|
sessionWebhook,
|
|
289
293
|
asyncMode = false,
|
|
290
294
|
preCreatedCard,
|
|
295
|
+
sessionKey: sourceSessionKey,
|
|
296
|
+
senderName,
|
|
291
297
|
} = params;
|
|
292
298
|
|
|
293
299
|
const account = resolveDingtalkAccount({ cfg, accountId });
|
|
@@ -1670,6 +1676,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
1670
1676
|
}
|
|
1671
1677
|
},
|
|
1672
1678
|
// ===== 养成系统:监听 dws 命令执行 =====
|
|
1679
|
+
// ===== dws 发消息成功 → outbound_message 上下文注入(OC-4 对齐)=====
|
|
1673
1680
|
onCommandOutput: (payload: {
|
|
1674
1681
|
itemId?: string;
|
|
1675
1682
|
phase?: string;
|
|
@@ -1695,6 +1702,34 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
1695
1702
|
log.info(`[DingTalk][onCommandOutput] dws 命令执行失败,跳过: ${product},exitCode=${payload.exitCode}`);
|
|
1696
1703
|
}
|
|
1697
1704
|
}
|
|
1705
|
+
|
|
1706
|
+
// dws chat message send* 成功后注入 outbound_message(best-effort,不阻塞)
|
|
1707
|
+
if (payload.phase === 'end' && sourceSessionKey) {
|
|
1708
|
+
const fullCmd =
|
|
1709
|
+
[payload.title, payload.name, payload.output].filter(Boolean).join('\n') || commandText;
|
|
1710
|
+
void import('./services/dws-delivery-context.ts')
|
|
1711
|
+
.then(({ maybeInjectDwsOutboundContext }) =>
|
|
1712
|
+
maybeInjectDwsOutboundContext({
|
|
1713
|
+
cfg,
|
|
1714
|
+
accountConfig: account.config,
|
|
1715
|
+
agentId,
|
|
1716
|
+
accountId,
|
|
1717
|
+
sourceSessionKey,
|
|
1718
|
+
invokerId: senderId,
|
|
1719
|
+
invokerName: senderName,
|
|
1720
|
+
commandText: fullCmd,
|
|
1721
|
+
exitCode: payload.exitCode,
|
|
1722
|
+
phase: payload.phase,
|
|
1723
|
+
toolCallId: payload.toolCallId,
|
|
1724
|
+
log,
|
|
1725
|
+
}),
|
|
1726
|
+
)
|
|
1727
|
+
.catch((err: any) => {
|
|
1728
|
+
log.warn?.(
|
|
1729
|
+
`[DingTalk][dwsDeliveryContext] onCommandOutput 钩子异常: ${err?.message || err}`,
|
|
1730
|
+
);
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1698
1733
|
// 工具进度改由 onToolStart + 正文同一 cardContentVar 展示,不再写独立 cardToolVar 字段
|
|
1699
1734
|
},
|
|
1700
1735
|
},
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DWS 发消息成功后注入 outbound_message 上下文(对齐 OpenClaw message 工具 OC-4)。
|
|
3
|
+
*
|
|
4
|
+
* - 不修改 dws 本体:仅解析 OpenClaw exec/shell 回调里看到的命令行
|
|
5
|
+
* - 默认开启(target):只在 dws 发消息成功时追加上下文,不影响普通回复 / message 工具
|
|
6
|
+
* - 可配置 off | target | source | both
|
|
7
|
+
*/
|
|
8
|
+
import { CHANNEL_ID } from "../channel.ts";
|
|
9
|
+
|
|
10
|
+
export type DwsDeliveryContextMode = "off" | "target" | "source" | "both";
|
|
11
|
+
|
|
12
|
+
export type ParsedDwsSendCommand = {
|
|
13
|
+
/** dws 子命令身份 */
|
|
14
|
+
kind: "user-send" | "bot-send" | "webhook-send" | "reply";
|
|
15
|
+
/** 规范化后的 message 工具风格 target:group:… / user:… */
|
|
16
|
+
target?: string;
|
|
17
|
+
/** 原始 openConversationId / userId / openDingTalkId */
|
|
18
|
+
rawTarget?: string;
|
|
19
|
+
targetChatType?: "group" | "direct";
|
|
20
|
+
message?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
atAll?: boolean;
|
|
23
|
+
atUserIds?: string[];
|
|
24
|
+
atOpenDingTalkIds?: string[];
|
|
25
|
+
atMobiles?: string[];
|
|
26
|
+
robotCode?: string;
|
|
27
|
+
/** 原始命令文本(截断后写入 args) */
|
|
28
|
+
command: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const SEND_COMMAND_RE =
|
|
32
|
+
/\bdws\b(?:\s+[^\s]+)*\s+chat\s+message\s+(send-by-bot|send-by-webhook|send|reply)\b/i;
|
|
33
|
+
|
|
34
|
+
/** 从命令行提取 --flag value / --flag=value / 布尔 --flag */
|
|
35
|
+
function extractFlag(cmd: string, name: string): string | undefined {
|
|
36
|
+
const eq = new RegExp(`--${name}(?:=|\\s+)(?:"([^"]*)"|'([^']*)'|(\\S+))`, "i");
|
|
37
|
+
const m = cmd.match(eq);
|
|
38
|
+
if (!m) return undefined;
|
|
39
|
+
return (m[1] ?? m[2] ?? m[3] ?? "").trim() || undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hasBoolFlag(cmd: string, name: string): boolean {
|
|
43
|
+
return new RegExp(`(?:^|\\s)--${name}(?:\\s|=|$)`, "i").test(cmd);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function splitCsv(raw?: string): string[] | undefined {
|
|
47
|
+
if (!raw) return undefined;
|
|
48
|
+
const parts = raw
|
|
49
|
+
.split(",")
|
|
50
|
+
.map((s) => s.trim())
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
return parts.length ? parts : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 判断是否为 dws 发消息类命令,并解析关键参数。
|
|
57
|
+
* 解析失败返回 null(调用方 no-op,不影响现有体验)。
|
|
58
|
+
*/
|
|
59
|
+
export function parseDwsSendCommand(commandText: string): ParsedDwsSendCommand | null {
|
|
60
|
+
const cmd = String(commandText || "").trim();
|
|
61
|
+
if (!cmd) return null;
|
|
62
|
+
const m = cmd.match(SEND_COMMAND_RE);
|
|
63
|
+
if (!m) return null;
|
|
64
|
+
|
|
65
|
+
const sub = m[1].toLowerCase();
|
|
66
|
+
let kind: ParsedDwsSendCommand["kind"];
|
|
67
|
+
if (sub === "send-by-bot") kind = "bot-send";
|
|
68
|
+
else if (sub === "send-by-webhook") kind = "webhook-send";
|
|
69
|
+
else if (sub === "reply") kind = "reply";
|
|
70
|
+
else kind = "user-send";
|
|
71
|
+
|
|
72
|
+
const group =
|
|
73
|
+
extractFlag(cmd, "group") ||
|
|
74
|
+
extractFlag(cmd, "conversation-id") ||
|
|
75
|
+
extractFlag(cmd, "id") ||
|
|
76
|
+
extractFlag(cmd, "chat");
|
|
77
|
+
const user =
|
|
78
|
+
extractFlag(cmd, "user") ||
|
|
79
|
+
extractFlag(cmd, "open-dingtalk-id") ||
|
|
80
|
+
// 批量单聊取第一个,便于 session 映射
|
|
81
|
+
splitCsv(extractFlag(cmd, "users"))?.[0] ||
|
|
82
|
+
splitCsv(extractFlag(cmd, "open-dingtalk-ids"))?.[0];
|
|
83
|
+
|
|
84
|
+
const text =
|
|
85
|
+
extractFlag(cmd, "text") ||
|
|
86
|
+
extractFlag(cmd, "content") ||
|
|
87
|
+
extractFlag(cmd, "body") ||
|
|
88
|
+
extractFlag(cmd, "message") ||
|
|
89
|
+
extractFlag(cmd, "markdown");
|
|
90
|
+
|
|
91
|
+
let target: string | undefined;
|
|
92
|
+
let rawTarget: string | undefined;
|
|
93
|
+
let targetChatType: "group" | "direct" | undefined;
|
|
94
|
+
|
|
95
|
+
if (group) {
|
|
96
|
+
rawTarget = group.replace(/^(group:|channel:)/i, "");
|
|
97
|
+
target = `group:${rawTarget}`;
|
|
98
|
+
targetChatType = "group";
|
|
99
|
+
} else if (user) {
|
|
100
|
+
rawTarget = user.replace(/^(user:|dm:)/i, "");
|
|
101
|
+
target = `user:${rawTarget}`;
|
|
102
|
+
targetChatType = "direct";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// webhook 无明确会话目标时无法写 target session
|
|
106
|
+
if (kind === "webhook-send" && !target) {
|
|
107
|
+
return {
|
|
108
|
+
kind,
|
|
109
|
+
message: text,
|
|
110
|
+
title: extractFlag(cmd, "title"),
|
|
111
|
+
atAll: hasBoolFlag(cmd, "at-all"),
|
|
112
|
+
atUserIds: splitCsv(extractFlag(cmd, "at-users")),
|
|
113
|
+
atMobiles: splitCsv(extractFlag(cmd, "at-mobiles")),
|
|
114
|
+
command: cmd.slice(0, 2000),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
kind,
|
|
120
|
+
target,
|
|
121
|
+
rawTarget,
|
|
122
|
+
targetChatType,
|
|
123
|
+
message: text,
|
|
124
|
+
title: extractFlag(cmd, "title"),
|
|
125
|
+
atAll: hasBoolFlag(cmd, "at-all"),
|
|
126
|
+
atUserIds:
|
|
127
|
+
splitCsv(extractFlag(cmd, "at-user-ids")) ||
|
|
128
|
+
splitCsv(extractFlag(cmd, "at-users")),
|
|
129
|
+
atOpenDingTalkIds: splitCsv(extractFlag(cmd, "at-open-dingtalk-ids")),
|
|
130
|
+
atMobiles: splitCsv(extractFlag(cmd, "at-mobiles")),
|
|
131
|
+
robotCode: extractFlag(cmd, "robot-code"),
|
|
132
|
+
command: cmd.slice(0, 2000),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function resolveDwsDeliveryContextMode(
|
|
137
|
+
config: any,
|
|
138
|
+
): DwsDeliveryContextMode {
|
|
139
|
+
const raw = config?.dwsDeliveryContext;
|
|
140
|
+
if (raw === "off" || raw === false) return "off";
|
|
141
|
+
if (raw === "source" || raw === "both" || raw === "target") return raw;
|
|
142
|
+
// 默认 target:仅叠加,不改 message 工具 / 普通回复
|
|
143
|
+
if (raw === true || raw === undefined || raw === null) return "target";
|
|
144
|
+
return "off";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type MaybeInjectDwsOutboundContextParams = {
|
|
148
|
+
/** 完整 OpenClaw 配置(resolveOutboundSessionRoute 需要) */
|
|
149
|
+
cfg: any;
|
|
150
|
+
/** 账号合并后的钉钉配置(读取 dwsDeliveryContext) */
|
|
151
|
+
accountConfig?: any;
|
|
152
|
+
agentId: string;
|
|
153
|
+
accountId?: string;
|
|
154
|
+
/** 当前发起会话 sessionKey */
|
|
155
|
+
sourceSessionKey: string;
|
|
156
|
+
invokerId?: string;
|
|
157
|
+
invokerName?: string;
|
|
158
|
+
commandText: string;
|
|
159
|
+
exitCode?: number | null;
|
|
160
|
+
phase?: string;
|
|
161
|
+
toolCallId?: string;
|
|
162
|
+
log?: { info?: (...a: any[]) => void; warn?: (...a: any[]) => void };
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
let warnedMissingApi = false;
|
|
166
|
+
|
|
167
|
+
function resolveModeFromConfigs(cfg: any, accountConfig?: any): DwsDeliveryContextMode {
|
|
168
|
+
// 账号级 > 渠道顶层 > 默认 target
|
|
169
|
+
if (accountConfig?.dwsDeliveryContext !== undefined) {
|
|
170
|
+
return resolveDwsDeliveryContextMode(accountConfig);
|
|
171
|
+
}
|
|
172
|
+
const channelCfg = cfg?.channels?.[CHANNEL_ID];
|
|
173
|
+
if (channelCfg?.dwsDeliveryContext !== undefined) {
|
|
174
|
+
return resolveDwsDeliveryContextMode(channelCfg);
|
|
175
|
+
}
|
|
176
|
+
return "target";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 在 dws 发消息命令成功结束后,向目标/来源会话写入 outbound_message。
|
|
181
|
+
* 全程 best-effort,失败只打日志。
|
|
182
|
+
*/
|
|
183
|
+
export async function maybeInjectDwsOutboundContext(
|
|
184
|
+
params: MaybeInjectDwsOutboundContextParams,
|
|
185
|
+
): Promise<boolean> {
|
|
186
|
+
const log = params.log;
|
|
187
|
+
const effectiveMode = resolveModeFromConfigs(params.cfg, params.accountConfig);
|
|
188
|
+
|
|
189
|
+
if (effectiveMode === "off") return false;
|
|
190
|
+
|
|
191
|
+
// 仅在命令结束且成功时注入
|
|
192
|
+
if (params.phase && params.phase !== "end") return false;
|
|
193
|
+
if (params.exitCode !== undefined && params.exitCode !== null && params.exitCode !== 0) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const parsed = parseDwsSendCommand(params.commandText);
|
|
198
|
+
if (!parsed) return false;
|
|
199
|
+
|
|
200
|
+
// 无明确 target 时,仅 source 模式才写
|
|
201
|
+
if (!parsed.target && effectiveMode === "target") {
|
|
202
|
+
log?.info?.(
|
|
203
|
+
`[DingTalk][dwsDeliveryContext] 解析到 dws ${parsed.kind} 但无 group/user 目标,跳过 target 注入`,
|
|
204
|
+
);
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const oc = await import("openclaw/plugin-sdk/outbound-runtime");
|
|
210
|
+
const append = (oc as any).appendOutboundMessageDeliveryContext as
|
|
211
|
+
| ((p: any) => Promise<void>)
|
|
212
|
+
| undefined;
|
|
213
|
+
const resolveRoute = (oc as any).resolveOutboundSessionRoute as
|
|
214
|
+
| ((p: any) => Promise<any>)
|
|
215
|
+
| undefined;
|
|
216
|
+
const ensureEntry = (oc as any).ensureOutboundSessionEntry as
|
|
217
|
+
| ((p: any) => Promise<void>)
|
|
218
|
+
| undefined;
|
|
219
|
+
|
|
220
|
+
if (typeof append !== "function") {
|
|
221
|
+
if (!warnedMissingApi) {
|
|
222
|
+
warnedMissingApi = true;
|
|
223
|
+
log?.warn?.(
|
|
224
|
+
"[DingTalk][dwsDeliveryContext] 当前 OpenClaw 未导出 appendOutboundMessageDeliveryContext;" +
|
|
225
|
+
"请升级/重建 openclaw(plugin-sdk/outbound-runtime)。本功能已跳过,不影响发消息。",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let targetRoute: any = null;
|
|
232
|
+
const targetTo = parsed.target;
|
|
233
|
+
if (targetTo && typeof resolveRoute === "function") {
|
|
234
|
+
try {
|
|
235
|
+
targetRoute = await resolveRoute({
|
|
236
|
+
cfg: params.cfg,
|
|
237
|
+
channel: CHANNEL_ID,
|
|
238
|
+
agentId: params.agentId,
|
|
239
|
+
accountId: params.accountId,
|
|
240
|
+
target: targetTo,
|
|
241
|
+
currentSessionKey: params.sourceSessionKey,
|
|
242
|
+
});
|
|
243
|
+
if (targetRoute && typeof ensureEntry === "function") {
|
|
244
|
+
await ensureEntry({
|
|
245
|
+
cfg: params.cfg,
|
|
246
|
+
channel: CHANNEL_ID,
|
|
247
|
+
accountId: params.accountId,
|
|
248
|
+
route: targetRoute,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
} catch (err: any) {
|
|
252
|
+
log?.warn?.(
|
|
253
|
+
`[DingTalk][dwsDeliveryContext] resolveOutboundSessionRoute 失败: ${err?.message || err}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// target 模式但路由失败 → 仍可写 source(若 both/source)
|
|
259
|
+
const action =
|
|
260
|
+
parsed.kind === "bot-send"
|
|
261
|
+
? "dws.chat.message.send-by-bot"
|
|
262
|
+
: parsed.kind === "webhook-send"
|
|
263
|
+
? "dws.chat.message.send-by-webhook"
|
|
264
|
+
: parsed.kind === "reply"
|
|
265
|
+
? "dws.chat.message.reply"
|
|
266
|
+
: "dws.chat.message.send";
|
|
267
|
+
|
|
268
|
+
const actionParams: Record<string, unknown> = {
|
|
269
|
+
action: "send",
|
|
270
|
+
via: "dws",
|
|
271
|
+
dwsKind: parsed.kind,
|
|
272
|
+
message: parsed.message ?? "",
|
|
273
|
+
title: parsed.title,
|
|
274
|
+
target: targetTo,
|
|
275
|
+
to: parsed.rawTarget,
|
|
276
|
+
channel: CHANNEL_ID,
|
|
277
|
+
accountId: params.accountId,
|
|
278
|
+
atAll: parsed.atAll || undefined,
|
|
279
|
+
atUserIds: parsed.atUserIds,
|
|
280
|
+
atOpenDingTalkIds: parsed.atOpenDingTalkIds,
|
|
281
|
+
atMobiles: parsed.atMobiles,
|
|
282
|
+
robotCode: parsed.robotCode,
|
|
283
|
+
command: parsed.command,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const idempotencyKey =
|
|
287
|
+
params.toolCallId?.trim() ||
|
|
288
|
+
`dws:${params.sourceSessionKey}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
289
|
+
|
|
290
|
+
await append({
|
|
291
|
+
cfg: params.cfg,
|
|
292
|
+
mode: effectiveMode,
|
|
293
|
+
agentId: params.agentId,
|
|
294
|
+
sourceSessionKey: params.sourceSessionKey,
|
|
295
|
+
sourceAccountId: params.accountId,
|
|
296
|
+
invokerId: params.invokerId,
|
|
297
|
+
invokerName: params.invokerName,
|
|
298
|
+
targetTo: targetTo || params.sourceSessionKey,
|
|
299
|
+
targetChannel: CHANNEL_ID,
|
|
300
|
+
targetAccountId: params.accountId,
|
|
301
|
+
targetRoute,
|
|
302
|
+
actionParams,
|
|
303
|
+
action,
|
|
304
|
+
idempotencyKey,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
log?.info?.(
|
|
308
|
+
`[DingTalk][dwsDeliveryContext] 已注入 outbound_message mode=${effectiveMode} kind=${parsed.kind} target=${targetTo || "-"} route=${targetRoute?.sessionKey || "-"}`,
|
|
309
|
+
);
|
|
310
|
+
return true;
|
|
311
|
+
} catch (err: any) {
|
|
312
|
+
log?.warn?.(
|
|
313
|
+
`[DingTalk][dwsDeliveryContext] 注入失败(已忽略,不影响发消息): ${err?.message || err}`,
|
|
314
|
+
);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|