@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.
Files changed (32) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/card-content-cache-BsFPIwOr.mjs +2 -0
  3. package/dist/{runtime-CjE4Z9G5.mjs → channel-CABtqkf_.mjs} +18 -29
  4. package/dist/chunk-upload-CNlZg1SF.mjs +2 -0
  5. package/dist/{common-BfIYs1yC.mjs → common-1-GDZ2h3.mjs} +2 -2
  6. package/dist/{common-C0Oaf0z-.mjs → common-DCVdWMiZ.mjs} +1 -1
  7. package/dist/{connection-obtrPkj4.mjs → connection-Bh3U9I4x.mjs} +2 -2
  8. package/dist/dws-delivery-context-ESC7sij7.mjs +185 -0
  9. package/dist/entry-bundled.mjs +1 -1
  10. package/dist/gateway-methods-CV8y0xGw.mjs +2 -0
  11. package/dist/{gateway-methods-BdcpfxEZ.mjs → gateway-methods-Ce1FXmCx.mjs} +4 -4
  12. package/dist/index.d.mts +12 -0
  13. package/dist/index.mjs +3 -2
  14. package/dist/{message-handler-BeTQQCUe.mjs → message-handler-BoVSEDwY.mjs} +41 -14
  15. package/dist/{messaging-CK0Wz3Ze.mjs → messaging-CwQLykl6.mjs} +1 -1
  16. package/dist/runtime-DDMzayvA.mjs +26 -0
  17. package/dist/utils-legacy-Bx5izlVF.mjs +3 -0
  18. package/openclaw.plugin.json +28 -1
  19. package/package.json +1 -1
  20. package/src/config/schema.ts +11 -0
  21. package/src/core/message-handler.ts +2 -0
  22. package/src/reply-dispatcher.ts +35 -0
  23. package/src/services/dws-delivery-context.ts +317 -0
  24. package/dist/card-content-cache-C7-XHhHw.mjs +0 -2
  25. package/dist/chunk-upload-BKM1y4Ba.mjs +0 -2
  26. package/dist/gateway-methods-DQ6TwOAr.mjs +0 -2
  27. package/dist/utils-legacy-BOqY3zE7.mjs +0 -3
  28. /package/dist/{background-work-DkaSDqWk.mjs → background-work-874-aK4-.mjs} +0 -0
  29. /package/dist/{card-content-cache-DT_OYThh.mjs → card-content-cache-CGggfgCg.mjs} +0 -0
  30. /package/dist/{chunk-upload-CIU5G_ay.mjs → chunk-upload-z185bTqH.mjs} +0 -0
  31. /package/dist/{game-xiyou-uwsv23AC.mjs → game-xiyou-CKoWJobs.mjs} +0 -0
  32. /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
@@ -0,0 +1,2 @@
1
+ import { r as rememberCardContent } from "./card-content-cache-CGggfgCg.mjs";
2
+ export { rememberCardContent };
@@ -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-CK0Wz3Ze.mjs";
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-BeTQQCUe.mjs"),
960
- import("./connection-obtrPkj4.mjs")
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
- //#region src/runtime.ts
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 };
@@ -0,0 +1,2 @@
1
+ import { a as uploadLargeFileByChunks } from "./chunk-upload-z185bTqH.mjs";
2
+ export { uploadLargeFileByChunks };
@@ -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-CIU5G_ay.mjs";
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-BKM1y4Ba.mjs");
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-BfIYs1yC.mjs";
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-DQ3HeGHh.mjs";
2
- import { n as onBackgroundWorkCountChange, t as getActiveBackgroundWorkCount } from "./background-work-DkaSDqWk.mjs";
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 };
@@ -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-DQ6TwOAr.mjs");
26
+ const { registerGatewayMethods } = await import("./gateway-methods-CV8y0xGw.mjs");
27
27
  registerGatewayMethods(api);
28
28
  }
29
29
  });
@@ -0,0 +1,2 @@
1
+ import { t as registerGatewayMethods } from "./gateway-methods-Ce1FXmCx.mjs";
2
+ export { registerGatewayMethods };
@@ -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-CK0Wz3Ze.mjs";
5
- import { d as getUnionId, h as recallEmotionReply } from "./utils-legacy-DQ3HeGHh.mjs";
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-BOqY3zE7.mjs");
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-BOqY3zE7.mjs");
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 { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-CjE4Z9G5.mjs";
2
- import { t as registerGatewayMethods } from "./gateway-methods-BdcpfxEZ.mjs";
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 { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-CjE4Z9G5.mjs";
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-CK0Wz3Ze.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-CwQLykl6.mjs";
7
7
  import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
8
- 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-DQ3HeGHh.mjs";
9
- import { r as trackBackgroundWork } from "./background-work-DkaSDqWk.mjs";
10
- import "./chunk-upload-CIU5G_ay.mjs";
11
- import { a as toLocalPath, i as VIDEO_MARKER_PATTERN, o as uploadMediaToDingTalk, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-BfIYs1yC.mjs";
12
- import { n as lookupCardContent, t as collectCardLookupIds } from "./card-content-cache-DT_OYThh.mjs";
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-uwsv23AC.mjs");
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-C0Oaf0z-.mjs");
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-C0Oaf0z-.mjs");
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 dwsMatch = (payload.title || payload.name || "").match(DWS_PRODUCT_PATTERN) || payload.output?.match(DWS_PRODUCT_PATTERN);
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-uwsv23AC.mjs");
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-C7-XHhHw.mjs");
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 };
@@ -0,0 +1,3 @@
1
+ import "./session-DJ4jYqPv.mjs";
2
+ import { l as getAccessToken } from "./utils-legacy-CGEa8tg2.mjs";
3
+ export { getAccessToken };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dingtalk-connector",
3
3
  "name": "DingTalk Channel",
4
- "version": "0.8.32",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeik/dingtalk-connector",
3
- "version": "0.8.32",
3
+ "version": "0.8.33",
4
4
  "description": "OpenClaw DingTalk channel plugin (community) | 钉钉 OpenClaw 社区增强版(基于官方 0.8.24,长连接与消息体验增强)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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
 
@@ -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
+ }
@@ -1,2 +0,0 @@
1
- import { r as rememberCardContent } from "./card-content-cache-DT_OYThh.mjs";
2
- export { rememberCardContent };
@@ -1,2 +0,0 @@
1
- import { a as uploadLargeFileByChunks } from "./chunk-upload-CIU5G_ay.mjs";
2
- export { uploadLargeFileByChunks };
@@ -1,2 +0,0 @@
1
- import { t as registerGatewayMethods } from "./gateway-methods-BdcpfxEZ.mjs";
2
- export { registerGatewayMethods };
@@ -1,3 +0,0 @@
1
- import "./session-DJ4jYqPv.mjs";
2
- import { l as getAccessToken } from "./utils-legacy-DQ3HeGHh.mjs";
3
- export { getAccessToken };