dingtalk add @person function

This commit is contained in:
lbq 2026-08-27 22:09:06 +08:00
parent d251bc280d
commit 7d5bc94f64
2 changed files with 33 additions and 20 deletions

View file

@ -1008,13 +1008,18 @@ export function createDingtalkApi({
failAiCard: failCard,
async sendText({ clientId, clientSecret, sessionWebhook, text, signal }) {
async sendText({ clientId, clientSecret, sessionWebhook, text, at, signal }) {
const content = nonEmptyString(text);
if (!content) throw new TypeError('text is required');
const webhook = normalizeDingtalkSessionWebhook(sessionWebhook);
const token = await accessToken({ clientId, clientSecret, signal });
const replyAt = at && typeof at === 'object'
&& ((Array.isArray(at.atUserIds) && at.atUserIds.length > 0)
|| (Array.isArray(at.atMobiles) && at.atMobiles.length > 0)
|| at.isAtAll === true)
? { at } : {};
const response = await requestJson(fetchImpl, webhook, {
body: { msgtype: 'text', text: { content } },
body: { msgtype: 'text', text: { content }, ...replyAt },
headers: { 'x-acs-dingtalk-access-token': token },
signal,
action: '消息回复',

View file

@ -548,7 +548,7 @@ export class DingtalkHarnessBridge {
`[dsh-dingtalk] failed to process a command [${failure.referenceId}]`,
safeErrorDiagnostic(error),
);
return this.#send(sessionWebhook, messageFailureText(failure)).catch(() => undefined);
return this.#send(sessionWebhook, messageFailureText(failure), this.#atUsersFor(message)).catch(() => undefined);
}).finally(() => {
this.#acceptedMessageIds.delete(messageId);
this.#commandTasks.delete(task);
@ -570,7 +570,7 @@ export class DingtalkHarnessBridge {
: null,
isQuestionPending: () => this.#pendingInteractions.has(key),
send: sessionWebhook
? (reply) => this.#send(sessionWebhook, reply)
? (reply) => this.#send(sessionWebhook, reply, this.#atUsersFor(message))
: async () => undefined,
});
if (approvalReply) {
@ -803,7 +803,7 @@ export class DingtalkHarnessBridge {
]);
}
for (const reply of result?.messages ?? [result?.message]) {
if (reply) await this.#send(sessionWebhook, reply);
if (reply) await this.#send(sessionWebhook, reply, this.#atUsersFor(message));
}
this.#status.lastError = null;
}
@ -829,7 +829,7 @@ export class DingtalkHarnessBridge {
`[dsh-dingtalk] failed to process a batch input message [${failure.referenceId}]`,
safeErrorDiagnostic(error),
);
await this.#send(sessionWebhook, messageFailureText(failure)).catch(() => undefined);
await this.#send(sessionWebhook, messageFailureText(failure), this.#atUsersFor(message)).catch(() => undefined);
}).finally(() => {
this.#acceptedMessageIds.delete(messageId);
this.#commandTasks.delete(task);
@ -881,23 +881,23 @@ export class DingtalkHarnessBridge {
let batchSettled = batchSubmission === null;
try {
if (!text && !hasImages && !hasFiles) {
await this.#send(sessionWebhook, t('目前支持文字、图片和文件消息。'));
await this.#send(sessionWebhook, t('目前支持文字、图片和文件消息。'), this.#atUsersFor(message));
return;
}
const command = text.toLowerCase();
if (isPlainText && !hasImages && !hasFiles && command === '/help') {
await this.#send(sessionWebhook, helpText());
await this.#send(sessionWebhook, helpText(), this.#atUsersFor(message));
return;
}
if (isPlainText && !hasImages && !hasFiles && command === '/status') {
await this.#harness.ensureRunning({ signal: this.#signal });
await this.#send(sessionWebhook, t('钉钉机器人与 DeepSeek Harness 连接正常。'));
await this.#send(sessionWebhook, t('钉钉机器人与 DeepSeek Harness 连接正常。'), this.#atUsersFor(message));
return;
}
if (isPlainText && !hasImages && !hasFiles && command === '/new') {
await this.#state.clearSession(key);
await this.#send(sessionWebhook, t('已开启新会话。请发送你的问题。'));
await this.#send(sessionWebhook, t('已开启新会话。请发送你的问题。'), this.#atUsersFor(message));
return;
}
const workspaceCommand = isPlainText && !hasImages && !hasFiles
@ -905,7 +905,7 @@ export class DingtalkHarnessBridge {
: null;
if (workspaceCommand) {
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
await this.#send(sessionWebhook, reply);
await this.#send(sessionWebhook, reply, this.#atUsersFor(message));
}
return;
}
@ -919,7 +919,7 @@ export class DingtalkHarnessBridge {
)
: null;
if (compactCommand) {
await this.#send(sessionWebhook, compactCommand.message);
await this.#send(sessionWebhook, compactCommand.message, this.#atUsersFor(message));
return;
}
@ -984,7 +984,7 @@ export class DingtalkHarnessBridge {
textReceipt = createDeliveryReceipt({
deliveryId: messageId,
presentation: 'dingtalk-text',
providerMessageIds: await this.#send(sessionWebhook, answerText),
providerMessageIds: await this.#send(sessionWebhook, answerText, this.#atUsersFor(message)),
});
}
} catch (error) {
@ -1045,7 +1045,7 @@ export class DingtalkHarnessBridge {
? `${errorText}\n\n${batchFailureMessage}`
: errorText;
const streamed = cardStarted && await cardStream.finish(visibleError);
if (!streamed) await this.#send(sessionWebhook, visibleError);
if (!streamed) await this.#send(sessionWebhook, visibleError, this.#atUsersFor(message));
} catch {
this.#logger.error?.('[dsh-dingtalk] failed to send the safe error reply');
}
@ -1098,7 +1098,7 @@ export class DingtalkHarnessBridge {
const text = message?.msgtype === 'text' ? nonEmptyString(message?.text?.content) : null;
if (!text) {
try {
await this.#send(sessionWebhook, t('请用文字回答当前问题。'));
await this.#send(sessionWebhook, t('请用文字回答当前问题。'), this.#atUsersFor(message));
} catch {
this.#logger.error?.('[dsh-dingtalk] failed to reject a non-text interaction reply');
}
@ -1109,7 +1109,7 @@ export class DingtalkHarnessBridge {
if (!pending || pending !== expected || pending.submitting) {
if (claimed && (!pending || pending !== expected)) {
try {
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT));
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT), this.#atUsersFor(message));
} catch {
this.#logger.error?.('[dsh-dingtalk] failed to send an expired interaction notice');
}
@ -1174,7 +1174,7 @@ export class DingtalkHarnessBridge {
if (error?.code === 'interaction-not-pending') {
this.#clearPendingInteraction(key, pending.interactionId);
try {
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT));
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT), this.#atUsersFor(message));
} catch {
this.#logger.error?.('[dsh-dingtalk] failed to send an expired interaction notice');
}
@ -1187,7 +1187,7 @@ export class DingtalkHarnessBridge {
this.#status.lastError = t('回答提交失败。');
this.#logger.error?.('[dsh-dingtalk] failed to answer a Harness interaction');
try {
await this.#send(sessionWebhook, t('回答提交失败,请重新发送当前问题的答案。'));
await this.#send(sessionWebhook, t('回答提交失败,请重新发送当前问题的答案。'), this.#atUsersFor(message));
} catch {
this.#logger.error?.('[dsh-dingtalk] failed to send an interaction retry notice');
}
@ -1319,7 +1319,7 @@ export class DingtalkHarnessBridge {
return;
}
try {
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT));
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT), this.#atUsersFor(message));
} catch {
this.#logger.error?.('[dsh-dingtalk] failed to send an expired interaction notice');
}
@ -1363,7 +1363,14 @@ export class DingtalkHarnessBridge {
}
}
async #send(sessionWebhook, text) {
#atUsersFor(message) {
const sender = senderStaffId(message);
return String(message?.conversationType) === '2' && sender
? { atUserIds: [sender] }
: undefined;
}
async #send(sessionWebhook, text, at) {
const providerMessageIds = [];
for (const chunk of splitDingtalkText(text, this.#maxMessageChars)) {
this.#signal?.throwIfAborted();
@ -1372,6 +1379,7 @@ export class DingtalkHarnessBridge {
clientSecret: this.#clientSecret,
sessionWebhook,
text: chunk,
at,
signal: this.#signal,
});
providerMessageIds.push(...providerMessageIdsFor(result));