fix: remember direct recipients for connection tests

This commit is contained in:
xmanrui 2026-08-19 11:24:50 +08:00
parent fb61c4235f
commit 3ac70a7c65
34 changed files with 397 additions and 178 deletions

View file

@ -310,6 +310,9 @@ export class DingtalkHarnessBridge {
} catch {
// An unsafe reply route must never be able to submit an approval.
}
if (sessionWebhook && String(message.conversationType) !== '2') {
rememberConnectionTestTarget(this.#state, { sessionWebhook });
}
const pending = this.#pendingInteractions.get(key);
const approvalReply = this.#approvals.claimReply({
key,
@ -460,9 +463,6 @@ export class DingtalkHarnessBridge {
if (isPlainText && !hasImages && command === '/status') {
await this.#harness.ensureRunning({ signal: this.#signal });
await this.#send(sessionWebhook, '钉钉机器人与 DeepSeek Harness 连接正常。');
if (String(message.conversationType) !== '2') {
rememberConnectionTestTarget(this.#state, { sessionWebhook });
}
return;
}
if (isPlainText && !hasImages && command === '/new') {

View file

@ -18,6 +18,7 @@ import {
} from '../shared/harness-question.mjs';
import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
import { runCompactCommand } from '../shared/compact-command.mjs';
import { rememberConnectionTestTarget } from '../shared/connection-test.mjs';
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
@ -133,6 +134,11 @@ export class FeishuHarnessBridge {
return Promise.resolve();
}
if (event.message.chat_type === 'p2p') {
const chatId = nonEmptyString(event.message.chat_id);
if (chatId) rememberConnectionTestTarget(this.#state, { chatId });
}
this.#acceptedMessageIds.add(messageId);
const processingReaction = this.#addReaction(messageId, 'OnIt');
if (this.#isResolvedQuestionReply(event, key)) {

View file

@ -1,5 +1,9 @@
import { FeishuHarnessBridge } from './bridge.mjs';
import { VerifiedFeishuChannel } from './feishu-channel.mjs';
import {
connectionTestTargetUnavailable,
sendRememberedConnectionTest,
} from '../shared/connection-test.mjs';
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
@ -246,27 +250,39 @@ export class FeishuRuntime {
error.code = 'test-target-unavailable';
throw error;
}
const ownerOpenId = this.#ownerOpenIds[0];
if (!ownerOpenId || ownerOpenId === '*') {
const error = new Error('飞书机器人没有可用的测试消息接收人');
error.code = 'test-target-unavailable';
throw error;
}
if (typeof text !== 'string' || !text.trim()) {
throw new TypeError('Feishu connection test text is required');
}
const response = await this.#client.im.v1.message.create({
params: { receive_id_type: 'open_id' },
data: {
receive_id: ownerOpenId,
msg_type: 'text',
content: JSON.stringify({ text }),
const send = async (receiveIdType, receiveId, content) => {
const response = await this.#client.im.v1.message.create({
params: { receive_id_type: receiveIdType },
data: {
receive_id: receiveId,
msg_type: 'text',
content: JSON.stringify({ text: content }),
},
});
if (response?.code && response.code !== 0) {
throw new Error(`Feishu connection test failed: ${response.msg || response.code}`);
}
};
const ownerOpenId = this.#ownerOpenIds.find((value) => value !== '*');
if (ownerOpenId) {
await send('open_id', ownerOpenId, text);
return { sent: true };
}
return sendRememberedConnectionTest({
state: this.#state,
text,
channelLabel: '飞书机器人',
send: async (target, content) => {
const chatId = typeof target?.chatId === 'string' ? target.chatId.trim() : '';
if (!chatId) throw connectionTestTargetUnavailable('飞书机器人');
await send('chat_id', chatId, content);
},
});
if (response?.code && response.code !== 0) {
throw new Error(`Feishu connection test failed: ${response.msg || response.code}`);
}
return { sent: true };
}
async stop({ preserveError = false } = {}) {

View file

@ -177,6 +177,12 @@ export class QqHarnessBridge {
|| this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
const key = conversationKey(message);
this.#acceptedMessageIds.add(messageId);
if (message.kind === 'c2c'
&& (this.#ownerUserOpenid === '*' || sender === this.#ownerUserOpenid)
&& message.replyTarget?.scope === 'c2c'
&& nonEmptyString(message.replyTarget.targetId) === sender) {
rememberConnectionTestTarget(this.#state, message.replyTarget);
}
const pending = this.#pendingInteractions.get(key);
const approval = this.#approvals.claimReply({
key,
@ -296,11 +302,6 @@ export class QqHarnessBridge {
}
if (!hasImages && command === '/status') {
await this.#harness.ensureRunning({ signal: this.#signal });
if (message.kind === 'c2c'
&& target?.scope === 'c2c'
&& nonEmptyString(target.targetId) === sender) {
rememberConnectionTestTarget(this.#state, target);
}
await this.#bot.sendText(target, 'QQ 机器人与 DeepSeek Harness 连接正常。');
await this.#state.markSeen(messageId);
return;

View file

@ -31,7 +31,7 @@ export function connectionTestMessage(botName, channelLabel = '机器人') {
}
export function connectionTestTargetUnavailable(channelLabel = '机器人') {
const error = new Error(`请先私聊${channelLabel}发送 /status,再点击“检查连接”。`);
const error = new Error(`${channelLabel}尚未收到可用于测试的私聊消息。`);
error.code = 'test-target-unavailable';
return error;
}

View file

@ -101,6 +101,12 @@ export class TextHarnessBridge {
return Promise.resolve();
}
this.#acceptedMessageIds.add(messageId);
if (normalized.kind === 'direct') {
rememberConnectionTestTarget(
this.#state,
normalized.connectionTestTarget ?? normalized.replyTarget,
);
}
const key = `${kind}:${conversationId}`;
const pending = this.#pendingInteractions.get(key);
@ -252,9 +258,6 @@ export class TextHarnessBridge {
if (!hasImages && command === '/status') {
await this.#harness.ensureRunning({ signal: this.#signal });
await this.#bot.sendText(target, `${this.#descriptor.label}机器人与 DeepSeek Harness 连接正常。`);
if (message.kind === 'direct') {
rememberConnectionTestTarget(this.#state, message.connectionTestTarget ?? target);
}
return;
}
const workspaceCommand = !hasImages

View file

@ -270,6 +270,9 @@ export class WecomHarnessBridge {
const key = conversationKey(frame);
this.#acceptedMessageIds.add(messageId);
if (body.chattype === 'single') {
rememberConnectionTestTarget(this.#state, { chatId });
}
const pending = this.#pendingInteractions.get(key);
const approval = this.#approvals.claimReply({
key,
@ -431,9 +434,6 @@ export class WecomHarnessBridge {
if (!hasImages && command === '/status') {
await this.#harness.ensureRunning({ signal: this.#signal });
await this.#sendImmediate(frame, chatId, '企业微信机器人与 DeepSeek Harness 连接正常。');
if (body.chattype === 'single') {
rememberConnectionTestTarget(this.#state, { chatId });
}
await this.#state.markSeen(messageId);
return;
}

View file

@ -129,6 +129,9 @@ export class WeixinHarnessBridge {
if (!messageId || !sender || this.#state.hasSeen(messageId)
|| this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
this.#acceptedMessageIds.add(messageId);
if (sender === this.#ownerUserId) {
rememberConnectionTestTarget(this.#state, { toUserId: sender });
}
const key = conversationKey(sender);
const contextToken = nonEmptyString(message?.context_token) ?? undefined;
const runId = nonEmptyString(message?.run_id) ?? undefined;
@ -251,7 +254,6 @@ export class WeixinHarnessBridge {
if (!hasImages && command === '/status') {
await this.#harness.ensureRunning({ signal: this.#signal });
await this.#send(sender, '微信与 DeepSeek Harness 连接正常。', contextToken, runId);
rememberConnectionTestTarget(this.#state, { toUserId: sender });
await this.#state.markSeen(messageId);
return;
}