mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 07:53:20 +08:00
Merge remote-tracking branch 'upstream/main' into feat/per-bot-agent-preset
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # lib/index.js
This commit is contained in:
commit
a1bdf2c1f8
17 changed files with 438 additions and 144 deletions
|
|
@ -108,7 +108,7 @@ export class DiscordApi {
|
|||
headers: {
|
||||
authorization: `Bot ${this.#token}`,
|
||||
'content-type': 'application/json',
|
||||
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.17.0)',
|
||||
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.17.1)',
|
||||
},
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
signal: requestSignal(signal, timeoutMs),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,18 @@ export class ImagePromptError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
const HOST_ATTACHMENT_USER_MESSAGES = Object.freeze({
|
||||
MODEL_DOES_NOT_SUPPORT_IMAGES:
|
||||
'当前模型不支持图片,请用 /models 查看可用模型,再用 /model <序号> 切换后重发。',
|
||||
IMAGE_TOO_LARGE: '图片超过宿主允许的大小,请压缩后重试。',
|
||||
IMAGE_TOO_MANY_PIXELS: '图片分辨率过高,请压缩后重试。',
|
||||
INVALID_IMAGE: '图片内容无效或格式不受支持,请重新发送。',
|
||||
INVALID_IMAGE_BASE64: '未能读取图片内容,请重新发送。',
|
||||
IMAGE_TYPE_MISMATCH: '图片格式与实际内容不一致,请重新发送。',
|
||||
TOO_MANY_IMAGES: '一次发送的图片数量超过宿主限制,请减少后重试。',
|
||||
IMAGES_TOO_LARGE: '图片总大小超过宿主限制,请减少图片或压缩后重试。',
|
||||
});
|
||||
|
||||
function requestSignal(signal, timeoutMs) {
|
||||
const timeout = AbortSignal.timeout(timeoutMs);
|
||||
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
||||
|
|
@ -263,6 +275,25 @@ export async function promptContentForMessage(message, {
|
|||
return content;
|
||||
}
|
||||
|
||||
export function imagePromptUserMessage(error) {
|
||||
return error instanceof ImagePromptError ? error.userMessage : null;
|
||||
/** Return only allowlisted, user-safe image failure details. */
|
||||
export function imagePromptDiagnostic(error) {
|
||||
if (error instanceof ImagePromptError) {
|
||||
return {
|
||||
code: 'image-prompt-error',
|
||||
reason: error.code,
|
||||
userMessage: error.userMessage,
|
||||
};
|
||||
}
|
||||
if (error?.code !== 'attachment-error' || typeof error?.details?.reason !== 'string') {
|
||||
return null;
|
||||
}
|
||||
const reason = error.details.reason;
|
||||
const userMessage = Object.hasOwn(HOST_ATTACHMENT_USER_MESSAGES, reason)
|
||||
? HOST_ATTACHMENT_USER_MESSAGES[reason]
|
||||
: null;
|
||||
return userMessage ? { code: 'attachment-error', reason, userMessage } : null;
|
||||
}
|
||||
|
||||
export function imagePromptUserMessage(error) {
|
||||
return imagePromptDiagnostic(error)?.userMessage ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ function authenticatedHeaders(token) {
|
|||
function baseInfo() {
|
||||
return {
|
||||
channel_version: WEIXIN_PROTOCOL_VERSION,
|
||||
bot_agent: 'DeepSeekHarness/0.17.0',
|
||||
bot_agent: 'DeepSeekHarness/0.17.1',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|||
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
||||
import {
|
||||
hasInboundImages,
|
||||
imagePromptDiagnostic,
|
||||
imagePromptUserMessage,
|
||||
promptContentForMessage,
|
||||
} from '../shared/image-prompt.mjs';
|
||||
import { rememberConnectionTestTarget } from '../shared/connection-test.mjs';
|
||||
|
||||
const INTERACTION_RESOLVED_TEXT = '这个问题已在其他客户端处理,无需再次回答。';
|
||||
const GENERIC_PROCESSING_ERROR = '消息处理失败,请稍后重试。';
|
||||
|
||||
const HELP_TEXT = [
|
||||
'微信已连接 DeepSeek Harness。',
|
||||
|
|
@ -69,6 +71,16 @@ function canClaimInteractionReply(message, pending) {
|
|||
&& nonEmptyString(extractWeixinText(message));
|
||||
}
|
||||
|
||||
function safeMessageError(error, userMessage = GENERIC_PROCESSING_ERROR) {
|
||||
const diagnostic = imagePromptDiagnostic(error);
|
||||
return {
|
||||
code: diagnostic?.code ?? 'message-processing-failed',
|
||||
reason: diagnostic?.reason ?? 'UNKNOWN',
|
||||
message: diagnostic?.userMessage ?? userMessage,
|
||||
at: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
export function createWeixinBridgeStatus() {
|
||||
return {
|
||||
messagesReceived: 0,
|
||||
|
|
@ -78,6 +90,7 @@ export function createWeixinBridgeStatus() {
|
|||
lastReplyAt: null,
|
||||
lastRejectedAt: null,
|
||||
lastError: null,
|
||||
lastMessageError: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -168,8 +181,9 @@ export class WeixinHarnessBridge {
|
|||
).catch((error) => {
|
||||
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
||||
this.#status.lastError = error?.message ?? String(error);
|
||||
this.#status.lastMessageError = safeMessageError(error);
|
||||
this.#logger.error?.('[dsh-weixin] failed to process a command:', error);
|
||||
return this.#send(sender, '消息处理失败,请稍后重试。', contextToken, runId)
|
||||
return this.#send(sender, GENERIC_PROCESSING_ERROR, contextToken, runId)
|
||||
.catch(() => undefined);
|
||||
}).finally(() => {
|
||||
this.#acceptedMessageIds.delete(messageId);
|
||||
|
|
@ -289,6 +303,7 @@ export class WeixinHarnessBridge {
|
|||
if (reply) await this.#send(sender, reply, contextToken, runId);
|
||||
}
|
||||
this.#status.lastError = null;
|
||||
this.#status.lastMessageError = null;
|
||||
}
|
||||
|
||||
async #process(message, key, { alreadyRecorded = false } = {}) {
|
||||
|
|
@ -401,6 +416,7 @@ export class WeixinHarnessBridge {
|
|||
this.#status.messagesReplied += 1;
|
||||
this.#status.lastReplyAt = new Date().toISOString();
|
||||
this.#status.lastError = null;
|
||||
this.#status.lastMessageError = null;
|
||||
} catch (error) {
|
||||
if (error?.code === 'turn-stopped') {
|
||||
await this.#state.markSeen(messageId);
|
||||
|
|
@ -408,11 +424,13 @@ export class WeixinHarnessBridge {
|
|||
}
|
||||
if (this.#signal?.aborted) return;
|
||||
this.#status.lastError = error?.message ?? String(error);
|
||||
const userMessage = imagePromptUserMessage(error) ?? GENERIC_PROCESSING_ERROR;
|
||||
this.#status.lastMessageError = safeMessageError(error, userMessage);
|
||||
this.#logger.error?.('[dsh-weixin] failed to process an inbound message:', error);
|
||||
try {
|
||||
await this.#send(
|
||||
sender,
|
||||
imagePromptUserMessage(error) ?? '消息处理失败,请稍后重试。',
|
||||
userMessage,
|
||||
contextToken,
|
||||
runId,
|
||||
);
|
||||
|
|
@ -526,6 +544,7 @@ export class WeixinHarnessBridge {
|
|||
});
|
||||
this.#clearPendingInteraction(key, pending.interactionId);
|
||||
this.#status.lastError = null;
|
||||
this.#status.lastMessageError = null;
|
||||
} catch (error) {
|
||||
if (this.#signal?.aborted) return;
|
||||
if (error?.code === 'interaction-not-pending') {
|
||||
|
|
@ -719,13 +738,14 @@ export class WeixinHarnessBridge {
|
|||
async #handleInteractionFailure(message, messageId, error) {
|
||||
if (this.#signal?.aborted) return;
|
||||
this.#status.lastError = error?.message ?? String(error);
|
||||
this.#status.lastMessageError = safeMessageError(error);
|
||||
this.#logger.error?.('[dsh-weixin] failed to process an interaction reply:', error);
|
||||
if (!this.#state.hasSeen(messageId)) {
|
||||
await this.#state.markSeen(messageId).catch(() => undefined);
|
||||
}
|
||||
await this.#send(
|
||||
nonEmptyString(message?.from_user_id),
|
||||
'消息处理失败,请稍后重试。',
|
||||
GENERIC_PROCESSING_ERROR,
|
||||
nonEmptyString(message?.context_token) ?? undefined,
|
||||
nonEmptyString(message?.run_id) ?? undefined,
|
||||
).catch(() => undefined);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,20 @@ function safeAccountError(code, message) {
|
|||
return Object.freeze({ code, message });
|
||||
}
|
||||
|
||||
function publicMessageError(value) {
|
||||
if (!value || typeof value !== 'object'
|
||||
|| typeof value.code !== 'string' || !value.code
|
||||
|| typeof value.reason !== 'string' || !value.reason
|
||||
|| typeof value.message !== 'string' || !value.message
|
||||
|| !Number.isFinite(value.at)) return null;
|
||||
return {
|
||||
code: value.code.slice(0, 64),
|
||||
reason: value.reason.slice(0, 128),
|
||||
message: value.message.slice(0, 500),
|
||||
at: value.at,
|
||||
};
|
||||
}
|
||||
|
||||
function activationStageError(code, cause) {
|
||||
const error = new Error(`Weixin activation failed during ${code}`, { cause });
|
||||
error.name = 'WeixinActivationStageError';
|
||||
|
|
@ -351,6 +365,7 @@ export class WeixinController {
|
|||
messagesReceived: runtimeStatus?.messagesReceived ?? 0,
|
||||
messagesReplied: runtimeStatus?.messagesReplied ?? 0,
|
||||
},
|
||||
lastMessageError: publicMessageError(runtimeStatus?.lastMessageError),
|
||||
error: error ? structuredClone(error) : null,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue