feat: send test messages during bot connection checks

This commit is contained in:
xmanrui 2026-08-19 03:52:52 +08:00
parent 94da24538f
commit fb61c4235f
78 changed files with 3460 additions and 210 deletions

View file

@ -9,6 +9,7 @@ import {
} from 'node:fs/promises';
import { dirname, isAbsolute, resolve } from 'node:path';
import { CONNECTION_TEST_STATE_IDENTITY } from './connection-test.mjs';
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
const EMPTY_DOCUMENT = Object.freeze({ version: 1, workspaces: Object.freeze({}) });
@ -687,6 +688,7 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
});
const scopedState = new Proxy(state, {
get(target, property) {
if (property === CONNECTION_TEST_STATE_IDENTITY) return target;
if (property === 'sessionFor') {
return (key, ...args) => {
if (!isCurrentScope()) return null;

View file

@ -0,0 +1,54 @@
const targets = new WeakMap();
export const CONNECTION_TEST_STATE_IDENTITY = Symbol('dsh-im.connection-test-state-identity');
function stateIdentity(state) {
return state?.[CONNECTION_TEST_STATE_IDENTITY] ?? state;
}
function cleanText(value) {
return typeof value === 'string' && value.trim() ? value.trim() : null;
}
export function rememberConnectionTestTarget(state, target) {
if (!state || !target || typeof target !== 'object') return false;
try {
targets.set(stateIdentity(state), structuredClone(target));
return true;
} catch {
return false;
}
}
export function connectionTestTarget(state) {
const target = state ? targets.get(stateIdentity(state)) : null;
return target ? structuredClone(target) : null;
}
export function connectionTestMessage(botName, channelLabel = '机器人') {
const name = cleanText(botName) ?? channelLabel;
return `✅ DeepSeek Harness 连接测试成功\n这条消息由插件页面中的“${name}”机器人卡片发出。`;
}
export function connectionTestTargetUnavailable(channelLabel = '机器人') {
const error = new Error(`请先私聊${channelLabel}发送 /status,再点击“检查连接”。`);
error.code = 'test-target-unavailable';
return error;
}
export async function sendRememberedConnectionTest({ state, send, text, channelLabel }) {
const target = connectionTestTarget(state);
if (!target) throw connectionTestTargetUnavailable(channelLabel);
await send(target, text);
return { sent: true };
}
export function publicConnectionTestResult(error) {
if (!error) return Object.freeze({ sent: true });
return Object.freeze({
sent: false,
code: error?.code === 'test-target-unavailable'
? 'test-target-unavailable'
: 'test-message-failed',
});
}

View file

@ -1,5 +1,9 @@
import { runWorkspaceCommand } from './workspace-command.mjs';
import { runCompactCommand } from './compact-command.mjs';
import {
rememberConnectionTestTarget,
sendRememberedConnectionTest,
} from './connection-test.mjs';
import { askInWorkspaceSession } from './workspace-session.mjs';
import { HarnessApprovalQueue } from './harness-approval.mjs';
import {
@ -194,6 +198,15 @@ export class TextHarnessBridge {
]);
}
sendConnectionTest(text) {
return sendRememberedConnectionTest({
state: this.#state,
text,
channelLabel: `${this.#descriptor.label}机器人`,
send: (target, message) => this.#bot.sendText(target, message),
});
}
async #process(message, messageId, senderId, conversationKey, {
alreadyRecorded = false,
} = {}) {
@ -239,6 +252,9 @@ 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

@ -1,3 +1,5 @@
import { connectionTestMessage } from './connection-test.mjs';
function cleanString(value) {
return typeof value === 'string' && value.trim() ? value.trim() : null;
}
@ -161,6 +163,25 @@ export class TokenBotController {
return this.status();
}
async sendConnectionTest(botId) {
const config = this.#configStore.get(botId);
if (!config) throw new Error(`Unknown ${this.#descriptor.label} bot`);
return this.#withBotTransition(botId, async () => {
const runtime = this.#runtimes.get(botId);
if (!runtime?.status?.ready || typeof runtime.sendConnectionTest !== 'function') {
const error = new Error(`${this.#descriptor.label}机器人尚未连接`);
error.code = 'test-target-unavailable';
throw error;
}
const cardLabel = `${config.name}(${this.#maskPlatformId(config.platformId)})`;
await runtime.sendConnectionTest(connectionTestMessage(
cardLabel,
`${this.#descriptor.label}机器人`,
));
return { sent: true };
});
}
async deleteBot(botId) {
const config = this.#configStore.get(botId);
if (!config) throw new Error(`Unknown ${this.#descriptor.label} bot`);