@openclaw-china/dingtalk 0.1.26 → 0.1.27
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/clawdbot.plugin.json +34 -0
- package/dist/index.d.ts +23 -3
- package/dist/index.js +145 -288
- package/dist/index.js.map +1 -1
- package/moltbot.plugin.json +34 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "dingtalk",
|
|
3
|
+
"name": "DingTalk",
|
|
4
|
+
"description": "钉钉消息渠道插件",
|
|
5
|
+
"version": "0.1.1",
|
|
6
|
+
"channels": ["dingtalk"],
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"enabled": { "type": "boolean" },
|
|
12
|
+
"clientId": { "type": "string" },
|
|
13
|
+
"clientSecret": { "type": "string" },
|
|
14
|
+
"connectionMode": { "type": "string", "enum": ["stream", "webhook"] },
|
|
15
|
+
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"] },
|
|
16
|
+
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"] },
|
|
17
|
+
"requireMention": { "type": "boolean" },
|
|
18
|
+
"allowFrom": { "type": "array", "items": { "type": "string" } },
|
|
19
|
+
"groupAllowFrom": { "type": "array", "items": { "type": "string" } },
|
|
20
|
+
"historyLimit": { "type": "integer", "minimum": 0 },
|
|
21
|
+
"textChunkLimit": { "type": "integer", "minimum": 1 },
|
|
22
|
+
"enableAICard": { "type": "boolean" },
|
|
23
|
+
"gatewayToken": { "type": "string" },
|
|
24
|
+
"gatewayPassword": { "type": "string" },
|
|
25
|
+
"maxFileSizeMB": { "type": "number", "minimum": 1 }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"uiHints": {
|
|
29
|
+
"clientId": { "label": "Client ID (AppKey)" },
|
|
30
|
+
"clientSecret": { "label": "Client Secret (AppSecret)", "sensitive": true },
|
|
31
|
+
"gatewayToken": { "label": "Gateway Token", "sensitive": true },
|
|
32
|
+
"gatewayPassword": { "label": "Gateway Password", "sensitive": true }
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -184,6 +184,9 @@ interface WizardPrompter {
|
|
|
184
184
|
}) => Promise<T | symbol>;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
declare function normalizeDingtalkMessagingTarget(raw: string): string | undefined;
|
|
188
|
+
declare function looksLikeDingtalkTargetId(raw: string): boolean;
|
|
189
|
+
|
|
187
190
|
/** 默认账户 ID */
|
|
188
191
|
declare const DEFAULT_ACCOUNT_ID = "default";
|
|
189
192
|
/**
|
|
@@ -419,14 +422,31 @@ declare const dingtalkPlugin: {
|
|
|
419
422
|
cfg: OutboundConfig;
|
|
420
423
|
to: string;
|
|
421
424
|
text: string;
|
|
425
|
+
accountId?: string;
|
|
426
|
+
replyToId?: string | null;
|
|
427
|
+
threadId?: string | number | null;
|
|
428
|
+
deps?: unknown;
|
|
429
|
+
gifPlayback?: boolean;
|
|
422
430
|
}) => Promise<SendResult>;
|
|
423
431
|
sendMedia: (params: {
|
|
424
432
|
cfg: OutboundConfig;
|
|
425
433
|
to: string;
|
|
426
434
|
text?: string;
|
|
427
435
|
mediaUrl?: string;
|
|
436
|
+
accountId?: string;
|
|
437
|
+
replyToId?: string | null;
|
|
438
|
+
threadId?: string | number | null;
|
|
439
|
+
deps?: unknown;
|
|
440
|
+
gifPlayback?: boolean;
|
|
428
441
|
}) => Promise<SendResult>;
|
|
429
442
|
};
|
|
443
|
+
/**
|
|
444
|
+
* Messaging target normalization
|
|
445
|
+
*/
|
|
446
|
+
messagingAdapter: {
|
|
447
|
+
normalizeTarget: typeof normalizeDingtalkMessagingTarget;
|
|
448
|
+
looksLikeId: typeof looksLikeDingtalkTargetId;
|
|
449
|
+
};
|
|
430
450
|
/**
|
|
431
451
|
* Gateway 连接管理适配器
|
|
432
452
|
* Requirements: 3.1
|
|
@@ -533,9 +553,9 @@ interface PluginRuntime {
|
|
|
533
553
|
};
|
|
534
554
|
};
|
|
535
555
|
session?: {
|
|
536
|
-
resolveStorePath?: (store: unknown,
|
|
556
|
+
resolveStorePath?: (store: unknown, opts: {
|
|
537
557
|
agentId?: string;
|
|
538
|
-
}) => string
|
|
558
|
+
}) => string;
|
|
539
559
|
recordInboundSession?: (params: {
|
|
540
560
|
storePath: string;
|
|
541
561
|
sessionKey: string;
|
|
@@ -547,7 +567,7 @@ interface PluginRuntime {
|
|
|
547
567
|
accountId?: string;
|
|
548
568
|
threadId?: string | number;
|
|
549
569
|
};
|
|
550
|
-
onRecordError
|
|
570
|
+
onRecordError: (err: unknown) => void;
|
|
551
571
|
}) => Promise<void>;
|
|
552
572
|
};
|
|
553
573
|
reply?: {
|
package/dist/index.js
CHANGED
|
@@ -4088,7 +4088,7 @@ var DingtalkConfigSchema = external_exports.object({
|
|
|
4088
4088
|
/** 媒体文件大小限制 (MB),默认 100MB */
|
|
4089
4089
|
maxFileSizeMB: external_exports.number().positive().optional().default(100),
|
|
4090
4090
|
/** 仅发送最终回复(非流式) */
|
|
4091
|
-
replyFinalOnly: external_exports.boolean().optional().default(
|
|
4091
|
+
replyFinalOnly: external_exports.boolean().optional().default(true)
|
|
4092
4092
|
});
|
|
4093
4093
|
function isConfigured(config) {
|
|
4094
4094
|
return Boolean(config?.clientId && config?.clientSecret);
|
|
@@ -4110,8 +4110,7 @@ function createDingtalkClient(opts) {
|
|
|
4110
4110
|
}
|
|
4111
4111
|
const client = new DWClient({
|
|
4112
4112
|
clientId: opts.clientId,
|
|
4113
|
-
clientSecret: opts.clientSecret
|
|
4114
|
-
...opts.ua ? { ua: opts.ua } : {}
|
|
4113
|
+
clientSecret: opts.clientSecret
|
|
4115
4114
|
});
|
|
4116
4115
|
cachedClient = client;
|
|
4117
4116
|
cachedConfig = {
|
|
@@ -4125,10 +4124,7 @@ function createDingtalkClientFromConfig(cfg) {
|
|
|
4125
4124
|
if (!creds) {
|
|
4126
4125
|
throw new Error("DingTalk credentials not configured (clientId, clientSecret required)");
|
|
4127
4126
|
}
|
|
4128
|
-
return createDingtalkClient(
|
|
4129
|
-
...creds,
|
|
4130
|
-
ua: "openclaw-dingtalk"
|
|
4131
|
-
});
|
|
4127
|
+
return createDingtalkClient(creds);
|
|
4132
4128
|
}
|
|
4133
4129
|
var DINGTALK_OAUTH_URL = "https://api.dingtalk.com/v1.0/oauth2/accessToken";
|
|
4134
4130
|
var TOKEN_REQUEST_TIMEOUT = 1e4;
|
|
@@ -4958,96 +4954,6 @@ function extractMediaFromText(text, options = {}) {
|
|
|
4958
4954
|
all: [...images, ...files]
|
|
4959
4955
|
};
|
|
4960
4956
|
}
|
|
4961
|
-
|
|
4962
|
-
// ../../packages/shared/src/cron/index.ts
|
|
4963
|
-
var CRON_HIDDEN_PROMPT = `
|
|
4964
|
-
When creating or updating a cron task, use the main session's last route as the delivery target.
|
|
4965
|
-
- Do not ask the user for userId/chatId
|
|
4966
|
-
- sessionTarget="isolated"
|
|
4967
|
-
- payload.kind="agentTurn"
|
|
4968
|
-
- payload.deliver=true
|
|
4969
|
-
- Do not set payload.channel/to (fall back to last route automatically)
|
|
4970
|
-
- Do not call the message tool to send`;
|
|
4971
|
-
var CRON_TRIGGER_KEYWORDS = [
|
|
4972
|
-
"\u5B9A\u65F6",
|
|
4973
|
-
"\u63D0\u9192",
|
|
4974
|
-
"\u6BCF\u5206\u949F",
|
|
4975
|
-
"\u6BCF\u5C0F\u65F6",
|
|
4976
|
-
"\u6BCF\u5929",
|
|
4977
|
-
"\u6BCF\u5468",
|
|
4978
|
-
"\u51E0\u70B9",
|
|
4979
|
-
"\u65E9\u4E0A",
|
|
4980
|
-
"\u665A\u4E0A",
|
|
4981
|
-
"\u5DE5\u4F5C\u65E5",
|
|
4982
|
-
"cron",
|
|
4983
|
-
"remind",
|
|
4984
|
-
"reminder",
|
|
4985
|
-
"schedule",
|
|
4986
|
-
"scheduled",
|
|
4987
|
-
"every minute",
|
|
4988
|
-
"every hour",
|
|
4989
|
-
"every day",
|
|
4990
|
-
"daily",
|
|
4991
|
-
"every week",
|
|
4992
|
-
"weekly",
|
|
4993
|
-
"weekday",
|
|
4994
|
-
"workday",
|
|
4995
|
-
"morning",
|
|
4996
|
-
"evening"
|
|
4997
|
-
];
|
|
4998
|
-
var CRON_TRIGGER_PATTERNS = [
|
|
4999
|
-
/提醒我/u,
|
|
5000
|
-
/帮我定时/u,
|
|
5001
|
-
/每.+提醒/u,
|
|
5002
|
-
/每天.+发/u,
|
|
5003
|
-
/remind me/iu,
|
|
5004
|
-
/set (a )?reminder/iu,
|
|
5005
|
-
/every .+ remind/iu,
|
|
5006
|
-
/every day .+ (send|post|notify)/iu,
|
|
5007
|
-
/schedule .+ (reminder|message|notification)/iu
|
|
5008
|
-
];
|
|
5009
|
-
var CRON_EXCLUDE_PATTERNS = [
|
|
5010
|
-
/是什么意思/u,
|
|
5011
|
-
/区别/u,
|
|
5012
|
-
/为什么/u,
|
|
5013
|
-
/\bhelp\b/iu,
|
|
5014
|
-
/文档/u,
|
|
5015
|
-
/怎么用/u,
|
|
5016
|
-
/what does|what's|meaning of/iu,
|
|
5017
|
-
/difference/iu,
|
|
5018
|
-
/why/iu,
|
|
5019
|
-
/\bdocs?\b/iu,
|
|
5020
|
-
/documentation/iu,
|
|
5021
|
-
/how to/iu,
|
|
5022
|
-
/usage/iu
|
|
5023
|
-
];
|
|
5024
|
-
function shouldInjectCronHiddenPrompt(text) {
|
|
5025
|
-
const normalized = text.trim();
|
|
5026
|
-
if (!normalized) return false;
|
|
5027
|
-
const lowered = normalized.toLowerCase();
|
|
5028
|
-
for (const pattern of CRON_EXCLUDE_PATTERNS) {
|
|
5029
|
-
if (pattern.test(lowered)) return false;
|
|
5030
|
-
}
|
|
5031
|
-
for (const keyword of CRON_TRIGGER_KEYWORDS) {
|
|
5032
|
-
if (lowered.includes(keyword.toLowerCase())) return true;
|
|
5033
|
-
}
|
|
5034
|
-
return CRON_TRIGGER_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
5035
|
-
}
|
|
5036
|
-
function splitCronHiddenPrompt(text) {
|
|
5037
|
-
const idx = text.indexOf(CRON_HIDDEN_PROMPT);
|
|
5038
|
-
if (idx === -1) {
|
|
5039
|
-
return { base: text };
|
|
5040
|
-
}
|
|
5041
|
-
const base = text.slice(0, idx).trimEnd();
|
|
5042
|
-
return { base, prompt: CRON_HIDDEN_PROMPT };
|
|
5043
|
-
}
|
|
5044
|
-
function appendCronHiddenPrompt(text) {
|
|
5045
|
-
if (!shouldInjectCronHiddenPrompt(text)) return text;
|
|
5046
|
-
if (text.includes(CRON_HIDDEN_PROMPT)) return text;
|
|
5047
|
-
return `${text}
|
|
5048
|
-
|
|
5049
|
-
${CRON_HIDDEN_PROMPT}`;
|
|
5050
|
-
}
|
|
5051
4957
|
var FileSizeLimitError2 = class _FileSizeLimitError extends Error {
|
|
5052
4958
|
/** Actual file size in bytes */
|
|
5053
4959
|
actualSize;
|
|
@@ -5751,13 +5657,33 @@ function isDingtalkRuntimeInitialized() {
|
|
|
5751
5657
|
|
|
5752
5658
|
// src/outbound.ts
|
|
5753
5659
|
function parseTarget(to) {
|
|
5754
|
-
|
|
5755
|
-
|
|
5660
|
+
const raw = to.trim();
|
|
5661
|
+
if (!raw) {
|
|
5662
|
+
return { targetId: "", chatType: "direct" };
|
|
5663
|
+
}
|
|
5664
|
+
let normalized = raw;
|
|
5665
|
+
if (normalized.startsWith("dingtalk:")) {
|
|
5666
|
+
normalized = normalized.slice("dingtalk:".length).trim();
|
|
5667
|
+
} else if (normalized.startsWith("ding:")) {
|
|
5668
|
+
normalized = normalized.slice("ding:".length).trim();
|
|
5669
|
+
} else if (normalized.startsWith("channel:")) {
|
|
5670
|
+
normalized = normalized.slice("channel:".length).trim();
|
|
5671
|
+
if (normalized.startsWith("dingtalk:")) {
|
|
5672
|
+
normalized = normalized.slice("dingtalk:".length).trim();
|
|
5673
|
+
} else if (normalized.startsWith("ding:")) {
|
|
5674
|
+
normalized = normalized.slice("ding:".length).trim();
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
if (normalized.startsWith("chat:") || normalized.startsWith("group:")) {
|
|
5678
|
+
return { targetId: normalized.slice(normalized.indexOf(":") + 1), chatType: "group" };
|
|
5756
5679
|
}
|
|
5757
|
-
if (
|
|
5758
|
-
return { targetId:
|
|
5680
|
+
if (normalized.startsWith("user:") || normalized.startsWith("dm:")) {
|
|
5681
|
+
return { targetId: normalized.slice(normalized.indexOf(":") + 1), chatType: "direct" };
|
|
5759
5682
|
}
|
|
5760
|
-
|
|
5683
|
+
if (normalized.startsWith("group:")) {
|
|
5684
|
+
return { targetId: normalized.slice("group:".length), chatType: "group" };
|
|
5685
|
+
}
|
|
5686
|
+
return { targetId: normalized, chatType: "direct" };
|
|
5761
5687
|
}
|
|
5762
5688
|
var dingtalkOutbound = {
|
|
5763
5689
|
/** 投递模式: direct (直接发送) */
|
|
@@ -6069,8 +5995,7 @@ async function finishAICard(card, content, log) {
|
|
|
6069
5995
|
|
|
6070
5996
|
// src/bot.ts
|
|
6071
5997
|
function buildGatewayUserContent(inboundCtx, logger) {
|
|
6072
|
-
const base = inboundCtx.
|
|
6073
|
-
const { base: baseText, prompt } = splitCronHiddenPrompt(base);
|
|
5998
|
+
const base = inboundCtx.Body ?? "";
|
|
6074
5999
|
const rawPaths = [];
|
|
6075
6000
|
if (typeof inboundCtx.MediaPath === "string") {
|
|
6076
6001
|
rawPaths.push(inboundCtx.MediaPath);
|
|
@@ -6090,18 +6015,13 @@ function buildGatewayUserContent(inboundCtx, logger) {
|
|
|
6090
6015
|
files.add(localPath);
|
|
6091
6016
|
}
|
|
6092
6017
|
if (files.size === 0) {
|
|
6093
|
-
return
|
|
6094
|
-
|
|
6095
|
-
${prompt}` : baseText;
|
|
6018
|
+
return base;
|
|
6096
6019
|
}
|
|
6097
6020
|
const list = Array.from(files).map((p) => `- ${p}`).join("\n");
|
|
6098
|
-
|
|
6021
|
+
return `${base}
|
|
6099
6022
|
|
|
6100
6023
|
[local files]
|
|
6101
6024
|
${list}`;
|
|
6102
|
-
return prompt ? `${content}
|
|
6103
|
-
|
|
6104
|
-
${prompt}` : content;
|
|
6105
6025
|
}
|
|
6106
6026
|
function extractLocalMediaFromText(params) {
|
|
6107
6027
|
const { text, logger } = params;
|
|
@@ -6146,22 +6066,6 @@ function extractMediaLinesFromText(params) {
|
|
|
6146
6066
|
const mediaUrls = result.all.map((m) => m.isLocal ? m.localPath ?? m.source : m.source).filter((m) => typeof m === "string" && m.trim().length > 0);
|
|
6147
6067
|
return { text: result.text, mediaUrls };
|
|
6148
6068
|
}
|
|
6149
|
-
function resolveAudioRecognition(raw) {
|
|
6150
|
-
if (raw.msgtype !== "audio") return void 0;
|
|
6151
|
-
if (!raw.content) return void 0;
|
|
6152
|
-
const contentObj = typeof raw.content === "string" ? (() => {
|
|
6153
|
-
try {
|
|
6154
|
-
return JSON.parse(raw.content);
|
|
6155
|
-
} catch {
|
|
6156
|
-
return null;
|
|
6157
|
-
}
|
|
6158
|
-
})() : raw.content;
|
|
6159
|
-
if (!contentObj || typeof contentObj !== "object") return void 0;
|
|
6160
|
-
const recognition = contentObj.recognition;
|
|
6161
|
-
if (typeof recognition !== "string") return void 0;
|
|
6162
|
-
const trimmed = recognition.trim();
|
|
6163
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
6164
|
-
}
|
|
6165
6069
|
function resolveGatewayAuthFromConfigFile(logger) {
|
|
6166
6070
|
try {
|
|
6167
6071
|
const fs5 = __require("fs");
|
|
@@ -6268,10 +6172,16 @@ function parseDingtalkMessage(raw) {
|
|
|
6268
6172
|
let content = "";
|
|
6269
6173
|
if (raw.msgtype === "text" && raw.text?.content) {
|
|
6270
6174
|
content = raw.text.content.trim();
|
|
6271
|
-
} else if (raw.msgtype === "audio") {
|
|
6272
|
-
const
|
|
6273
|
-
|
|
6274
|
-
|
|
6175
|
+
} else if (raw.msgtype === "audio" && raw.content) {
|
|
6176
|
+
const contentObj = typeof raw.content === "string" ? (() => {
|
|
6177
|
+
try {
|
|
6178
|
+
return JSON.parse(raw.content);
|
|
6179
|
+
} catch {
|
|
6180
|
+
return null;
|
|
6181
|
+
}
|
|
6182
|
+
})() : raw.content;
|
|
6183
|
+
if (contentObj && typeof contentObj === "object" && "recognition" in contentObj && typeof contentObj.recognition === "string") {
|
|
6184
|
+
content = contentObj.recognition.trim();
|
|
6275
6185
|
}
|
|
6276
6186
|
}
|
|
6277
6187
|
const mentionedBot = resolveMentionedBot(raw);
|
|
@@ -6493,7 +6403,6 @@ async function handleDingtalkMessage(params) {
|
|
|
6493
6403
|
});
|
|
6494
6404
|
const ctx = parseDingtalkMessage(raw);
|
|
6495
6405
|
const isGroup = ctx.chatType === "group";
|
|
6496
|
-
const audioRecognition = resolveAudioRecognition(raw);
|
|
6497
6406
|
logger.debug(`raw message: msgtype=${raw.msgtype}, hasText=${!!raw.text?.content}, hasContent=${!!raw.content}, textContent="${raw.text?.content ?? ""}"`);
|
|
6498
6407
|
if (raw.msgtype === "richText") {
|
|
6499
6408
|
try {
|
|
@@ -6583,34 +6492,30 @@ async function handleDingtalkMessage(params) {
|
|
|
6583
6492
|
let richTextParseResult = null;
|
|
6584
6493
|
const mediaTypes = ["picture", "video", "audio", "file"];
|
|
6585
6494
|
if (mediaTypes.includes(raw.msgtype)) {
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
extractedFileInfo.msgType,
|
|
6605
|
-
extractedFileInfo.fileName
|
|
6606
|
-
);
|
|
6607
|
-
}
|
|
6608
|
-
} catch (err) {
|
|
6609
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
6610
|
-
logger.warn(`media download failed, continuing with text: ${errorMessage}`);
|
|
6611
|
-
downloadedMedia = null;
|
|
6612
|
-
extractedFileInfo = null;
|
|
6495
|
+
try {
|
|
6496
|
+
extractedFileInfo = extractFileFromMessage(raw);
|
|
6497
|
+
if (extractedFileInfo && channelCfg?.clientId && channelCfg?.clientSecret) {
|
|
6498
|
+
const accessToken = await getAccessToken(channelCfg.clientId, channelCfg.clientSecret);
|
|
6499
|
+
downloadedMedia = await downloadDingTalkFile({
|
|
6500
|
+
downloadCode: extractedFileInfo.downloadCode,
|
|
6501
|
+
robotCode: channelCfg.clientId,
|
|
6502
|
+
accessToken,
|
|
6503
|
+
fileName: extractedFileInfo.fileName,
|
|
6504
|
+
msgType: extractedFileInfo.msgType,
|
|
6505
|
+
log: logger,
|
|
6506
|
+
maxFileSizeMB: channelCfg.maxFileSizeMB
|
|
6507
|
+
});
|
|
6508
|
+
logger.debug(`downloaded media file: ${downloadedMedia.path} (${downloadedMedia.size} bytes)`);
|
|
6509
|
+
mediaBody = buildFileContextMessage(
|
|
6510
|
+
extractedFileInfo.msgType,
|
|
6511
|
+
extractedFileInfo.fileName
|
|
6512
|
+
);
|
|
6613
6513
|
}
|
|
6514
|
+
} catch (err) {
|
|
6515
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
6516
|
+
logger.warn(`media download failed, continuing with text: ${errorMessage}`);
|
|
6517
|
+
downloadedMedia = null;
|
|
6518
|
+
extractedFileInfo = null;
|
|
6614
6519
|
}
|
|
6615
6520
|
}
|
|
6616
6521
|
if (raw.msgtype === "richText") {
|
|
@@ -6662,9 +6567,6 @@ async function handleDingtalkMessage(params) {
|
|
|
6662
6567
|
}
|
|
6663
6568
|
}
|
|
6664
6569
|
const inboundCtx = buildInboundContext(ctx, route?.sessionKey, route?.accountId);
|
|
6665
|
-
if (audioRecognition) {
|
|
6666
|
-
inboundCtx.Transcript = audioRecognition;
|
|
6667
|
-
}
|
|
6668
6570
|
if (downloadedMedia) {
|
|
6669
6571
|
inboundCtx.MediaPath = downloadedMedia.path;
|
|
6670
6572
|
inboundCtx.MediaType = downloadedMedia.contentType;
|
|
@@ -6701,50 +6603,35 @@ async function handleDingtalkMessage(params) {
|
|
|
6701
6603
|
}
|
|
6702
6604
|
const finalizeInboundContext = replyApi?.finalizeInboundContext;
|
|
6703
6605
|
const finalCtx = finalizeInboundContext ? finalizeInboundContext(inboundCtx) : inboundCtx;
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
cronBase = finalCtx.Body;
|
|
6712
|
-
} else if (typeof finalCtx.CommandBody === "string" && finalCtx.CommandBody) {
|
|
6713
|
-
cronSource = "CommandBody";
|
|
6714
|
-
cronBase = finalCtx.CommandBody;
|
|
6715
|
-
}
|
|
6716
|
-
if (cronBase) {
|
|
6717
|
-
const nextCron = appendCronHiddenPrompt(cronBase);
|
|
6718
|
-
const injected = nextCron !== cronBase;
|
|
6719
|
-
if (injected) {
|
|
6720
|
-
finalCtx.BodyForAgent = nextCron;
|
|
6721
|
-
}
|
|
6722
|
-
}
|
|
6723
|
-
const channelSession = coreChannel?.session;
|
|
6724
|
-
const storePath = channelSession?.resolveStorePath?.(
|
|
6725
|
-
cfg?.session?.store,
|
|
6726
|
-
{ agentId: route?.agentId }
|
|
6727
|
-
);
|
|
6728
|
-
if (channelSession?.recordInboundSession && storePath) {
|
|
6729
|
-
const mainSessionKeyRaw = route?.mainSessionKey;
|
|
6730
|
-
const mainSessionKey = typeof mainSessionKeyRaw === "string" && mainSessionKeyRaw.trim() ? mainSessionKeyRaw : void 0;
|
|
6731
|
-
const updateLastRoute = !isGroup && mainSessionKey ? {
|
|
6732
|
-
sessionKey: mainSessionKey,
|
|
6733
|
-
channel: "dingtalk",
|
|
6734
|
-
to: finalCtx.OriginatingTo ?? finalCtx.To ?? `user:${ctx.senderId}`,
|
|
6735
|
-
accountId: route?.accountId
|
|
6736
|
-
} : void 0;
|
|
6737
|
-
const recordSessionKeyRaw = finalCtx.SessionKey ?? route.sessionKey;
|
|
6738
|
-
const recordSessionKey = typeof recordSessionKeyRaw === "string" && recordSessionKeyRaw.trim() ? recordSessionKeyRaw : String(recordSessionKeyRaw ?? "");
|
|
6739
|
-
await channelSession.recordInboundSession({
|
|
6740
|
-
storePath,
|
|
6741
|
-
sessionKey: recordSessionKey,
|
|
6742
|
-
ctx: finalCtx,
|
|
6743
|
-
updateLastRoute,
|
|
6744
|
-
onRecordError: (err) => {
|
|
6745
|
-
logger.error(`dingtalk: failed updating session meta: ${String(err)}`);
|
|
6746
|
-
}
|
|
6606
|
+
const sessionApi = coreChannel?.session;
|
|
6607
|
+
const resolveStorePath = sessionApi?.resolveStorePath;
|
|
6608
|
+
const recordInboundSession = sessionApi?.recordInboundSession;
|
|
6609
|
+
if (resolveStorePath && recordInboundSession) {
|
|
6610
|
+
const agentId = route?.agentId;
|
|
6611
|
+
const storePath = resolveStorePath(cfg?.session?.store, {
|
|
6612
|
+
agentId
|
|
6747
6613
|
});
|
|
6614
|
+
const sessionKey = route?.sessionKey;
|
|
6615
|
+
if (typeof sessionKey !== "string" || !sessionKey.trim()) {
|
|
6616
|
+
logger.debug("recordInboundSession skipped: missing sessionKey");
|
|
6617
|
+
} else {
|
|
6618
|
+
const routeSessionKey = route?.mainSessionKey ?? route?.sessionKey;
|
|
6619
|
+
const updateLastRoute = !isGroup && routeSessionKey && typeof finalCtx.To === "string" ? {
|
|
6620
|
+
sessionKey: routeSessionKey,
|
|
6621
|
+
channel: "dingtalk",
|
|
6622
|
+
to: finalCtx.To,
|
|
6623
|
+
accountId: route?.accountId
|
|
6624
|
+
} : void 0;
|
|
6625
|
+
await recordInboundSession({
|
|
6626
|
+
storePath,
|
|
6627
|
+
sessionKey,
|
|
6628
|
+
ctx: finalCtx,
|
|
6629
|
+
updateLastRoute,
|
|
6630
|
+
onRecordError: (err) => {
|
|
6631
|
+
logger.debug(`recordInboundSession failed: ${String(err)}`);
|
|
6632
|
+
}
|
|
6633
|
+
});
|
|
6634
|
+
}
|
|
6748
6635
|
}
|
|
6749
6636
|
const dingtalkCfgResolved = channelCfg;
|
|
6750
6637
|
if (!dingtalkCfgResolved) {
|
|
@@ -7098,14 +6985,6 @@ var currentAccountId = null;
|
|
|
7098
6985
|
var currentPromise = null;
|
|
7099
6986
|
var processedMessages = /* @__PURE__ */ new Map();
|
|
7100
6987
|
var MESSAGE_DEDUP_TTL_MS = 6e4;
|
|
7101
|
-
var WATCHDOG_INTERVAL_MS = 1e4;
|
|
7102
|
-
var CONNECT_TIMEOUT_MS = 3e4;
|
|
7103
|
-
var REGISTER_TIMEOUT_MS = 3e4;
|
|
7104
|
-
var DISCONNECT_GRACE_MS = 15e3;
|
|
7105
|
-
var MIN_RECONNECT_INTERVAL_MS = 1e4;
|
|
7106
|
-
function getClientState(client) {
|
|
7107
|
-
return client;
|
|
7108
|
-
}
|
|
7109
6988
|
async function monitorDingtalkProvider(opts = {}) {
|
|
7110
6989
|
const { config, runtime: runtime2, abortSignal, accountId = "default" } = opts;
|
|
7111
6990
|
const logger = createLogger("dingtalk", {
|
|
@@ -7139,47 +7018,7 @@ async function monitorDingtalkProvider(opts = {}) {
|
|
|
7139
7018
|
logger.info(`starting Stream connection for account ${accountId}...`);
|
|
7140
7019
|
currentPromise = new Promise((resolve2, reject) => {
|
|
7141
7020
|
let stopped = false;
|
|
7142
|
-
let watchdogId = null;
|
|
7143
|
-
let lastSocket = null;
|
|
7144
|
-
let connectStartedAt = Date.now();
|
|
7145
|
-
let lastConnectedAt = null;
|
|
7146
|
-
let lastReconnectAt = 0;
|
|
7147
|
-
const attachSocketListeners = () => {
|
|
7148
|
-
const { socket } = getClientState(client);
|
|
7149
|
-
if (!socket || socket === lastSocket) return;
|
|
7150
|
-
lastSocket = socket;
|
|
7151
|
-
socket.once("open", () => {
|
|
7152
|
-
const now = Date.now();
|
|
7153
|
-
connectStartedAt = now;
|
|
7154
|
-
lastConnectedAt = now;
|
|
7155
|
-
logger.info("Stream socket opened");
|
|
7156
|
-
});
|
|
7157
|
-
socket.once("close", () => {
|
|
7158
|
-
logger.warn("Stream socket closed");
|
|
7159
|
-
});
|
|
7160
|
-
socket.once("error", (err) => {
|
|
7161
|
-
logger.warn(`Stream socket error: ${String(err)}`);
|
|
7162
|
-
});
|
|
7163
|
-
};
|
|
7164
|
-
const forceReconnect = (reason) => {
|
|
7165
|
-
const now = Date.now();
|
|
7166
|
-
if (now - lastReconnectAt < MIN_RECONNECT_INTERVAL_MS) {
|
|
7167
|
-
return;
|
|
7168
|
-
}
|
|
7169
|
-
lastReconnectAt = now;
|
|
7170
|
-
logger.warn(`[reconnect] forcing reconnect: ${reason}`);
|
|
7171
|
-
try {
|
|
7172
|
-
const { socket } = getClientState(client);
|
|
7173
|
-
socket?.terminate?.();
|
|
7174
|
-
} catch (err) {
|
|
7175
|
-
logger.error(`failed to terminate socket: ${String(err)}`);
|
|
7176
|
-
}
|
|
7177
|
-
};
|
|
7178
7021
|
const cleanup = () => {
|
|
7179
|
-
if (watchdogId) {
|
|
7180
|
-
clearInterval(watchdogId);
|
|
7181
|
-
watchdogId = null;
|
|
7182
|
-
}
|
|
7183
7022
|
if (currentClient === client) {
|
|
7184
7023
|
currentClient = null;
|
|
7185
7024
|
currentAccountId = null;
|
|
@@ -7277,42 +7116,8 @@ async function monitorDingtalkProvider(opts = {}) {
|
|
|
7277
7116
|
logger.error(`error parsing message: ${String(err)}`);
|
|
7278
7117
|
}
|
|
7279
7118
|
});
|
|
7280
|
-
watchdogId = setInterval(() => {
|
|
7281
|
-
if (stopped) return;
|
|
7282
|
-
attachSocketListeners();
|
|
7283
|
-
const now = Date.now();
|
|
7284
|
-
const state = getClientState(client);
|
|
7285
|
-
const connected = state.connected === true;
|
|
7286
|
-
const registered = state.registered === true;
|
|
7287
|
-
if (connected) {
|
|
7288
|
-
lastConnectedAt = now;
|
|
7289
|
-
}
|
|
7290
|
-
if (!connected && now - connectStartedAt > CONNECT_TIMEOUT_MS) {
|
|
7291
|
-
forceReconnect("connect timeout");
|
|
7292
|
-
connectStartedAt = now;
|
|
7293
|
-
lastConnectedAt = null;
|
|
7294
|
-
return;
|
|
7295
|
-
}
|
|
7296
|
-
if (connected && !registered && now - connectStartedAt > REGISTER_TIMEOUT_MS) {
|
|
7297
|
-
forceReconnect("register timeout");
|
|
7298
|
-
connectStartedAt = now;
|
|
7299
|
-
lastConnectedAt = null;
|
|
7300
|
-
return;
|
|
7301
|
-
}
|
|
7302
|
-
if (!connected) {
|
|
7303
|
-
const lastSeen = lastConnectedAt ?? connectStartedAt;
|
|
7304
|
-
if (now - lastSeen > DISCONNECT_GRACE_MS) {
|
|
7305
|
-
forceReconnect("client marked disconnected");
|
|
7306
|
-
connectStartedAt = now;
|
|
7307
|
-
lastConnectedAt = null;
|
|
7308
|
-
}
|
|
7309
|
-
}
|
|
7310
|
-
}, WATCHDOG_INTERVAL_MS);
|
|
7311
|
-
connectStartedAt = Date.now();
|
|
7312
|
-
lastConnectedAt = null;
|
|
7313
|
-
attachSocketListeners();
|
|
7314
7119
|
client.connect();
|
|
7315
|
-
logger.info("Stream client
|
|
7120
|
+
logger.info("Stream client connected");
|
|
7316
7121
|
} catch (err) {
|
|
7317
7122
|
logger.error(`failed to start Stream connection: ${String(err)}`);
|
|
7318
7123
|
finalizeReject(err);
|
|
@@ -7574,6 +7379,51 @@ var dingtalkOnboardingAdapter = {
|
|
|
7574
7379
|
})
|
|
7575
7380
|
};
|
|
7576
7381
|
|
|
7382
|
+
// src/normalize.ts
|
|
7383
|
+
function normalizeDingtalkMessagingTarget(raw) {
|
|
7384
|
+
const trimmed = raw.trim();
|
|
7385
|
+
if (!trimmed) {
|
|
7386
|
+
return void 0;
|
|
7387
|
+
}
|
|
7388
|
+
let normalized = trimmed;
|
|
7389
|
+
if (normalized.startsWith("channel:")) {
|
|
7390
|
+
normalized = normalized.slice("channel:".length).trim();
|
|
7391
|
+
}
|
|
7392
|
+
if (normalized.startsWith("dingtalk:")) {
|
|
7393
|
+
normalized = normalized.slice("dingtalk:".length).trim();
|
|
7394
|
+
} else if (normalized.startsWith("ding:")) {
|
|
7395
|
+
normalized = normalized.slice("ding:".length).trim();
|
|
7396
|
+
}
|
|
7397
|
+
if (!normalized) {
|
|
7398
|
+
return void 0;
|
|
7399
|
+
}
|
|
7400
|
+
const lower = normalized.toLowerCase();
|
|
7401
|
+
if (lower.startsWith("chat:") || lower.startsWith("group:")) {
|
|
7402
|
+
const targetId = normalized.slice(normalized.indexOf(":") + 1).trim();
|
|
7403
|
+
if (!targetId) return void 0;
|
|
7404
|
+
return `dingtalk:group:${targetId}`;
|
|
7405
|
+
}
|
|
7406
|
+
if (lower.startsWith("user:") || lower.startsWith("dm:")) {
|
|
7407
|
+
const targetId = normalized.slice(normalized.indexOf(":") + 1).trim();
|
|
7408
|
+
if (!targetId) return void 0;
|
|
7409
|
+
return `dingtalk:user:${targetId}`;
|
|
7410
|
+
}
|
|
7411
|
+
return `dingtalk:user:${normalized}`;
|
|
7412
|
+
}
|
|
7413
|
+
function looksLikeDingtalkTargetId(raw) {
|
|
7414
|
+
const trimmed = raw.trim();
|
|
7415
|
+
if (!trimmed) {
|
|
7416
|
+
return false;
|
|
7417
|
+
}
|
|
7418
|
+
if (/^(dingtalk|ding|channel):/i.test(trimmed)) {
|
|
7419
|
+
return true;
|
|
7420
|
+
}
|
|
7421
|
+
if (/^(user|dm|chat|group):/i.test(trimmed)) {
|
|
7422
|
+
return true;
|
|
7423
|
+
}
|
|
7424
|
+
return /^[A-Za-z0-9:_-]{6,}$/.test(trimmed);
|
|
7425
|
+
}
|
|
7426
|
+
|
|
7577
7427
|
// src/channel.ts
|
|
7578
7428
|
var DEFAULT_ACCOUNT_ID = "default";
|
|
7579
7429
|
var meta = {
|
|
@@ -7764,6 +7614,13 @@ var dingtalkPlugin = {
|
|
|
7764
7614
|
* Requirements: 7.1, 7.6
|
|
7765
7615
|
*/
|
|
7766
7616
|
outbound: dingtalkOutbound,
|
|
7617
|
+
/**
|
|
7618
|
+
* Messaging target normalization
|
|
7619
|
+
*/
|
|
7620
|
+
messagingAdapter: {
|
|
7621
|
+
normalizeTarget: normalizeDingtalkMessagingTarget,
|
|
7622
|
+
looksLikeId: looksLikeDingtalkTargetId
|
|
7623
|
+
},
|
|
7767
7624
|
/**
|
|
7768
7625
|
* Gateway 连接管理适配器
|
|
7769
7626
|
* Requirements: 3.1
|