@xmanrui/dsh-im 3.0.4 → 3.0.6
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/lib/client.js +73 -6
- package/lib/index.js +188 -187
- package/package.json +1 -1
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/i18n.js +5 -0
- package/plugin-src/client/styles.js +15 -1
- package/plugin-src/client/workspace-directory-picker.js +42 -3
- package/src/channels/shared/i18n-en/weixin.mjs +2 -0
- package/src/channels/weixin/weixin-api.mjs +9 -3
- package/src/channels/weixin/weixin-bridge.mjs +111 -14
package/package.json
CHANGED
|
@@ -74,7 +74,8 @@ export function safeVerificationUrl(value) {
|
|
|
74
74
|
const url = new URL(value);
|
|
75
75
|
const host = url.hostname.toLowerCase();
|
|
76
76
|
return url.protocol === 'https:'
|
|
77
|
-
&& (host === 'weixin.qq.com' || host.endsWith('.weixin.qq.com')
|
|
77
|
+
&& (host === 'weixin.qq.com' || host.endsWith('.weixin.qq.com')
|
|
78
|
+
|| host === 'wechat.com' || host.endsWith('.wechat.com'))
|
|
78
79
|
? url.toString()
|
|
79
80
|
: undefined;
|
|
80
81
|
} catch {
|
|
@@ -122,6 +122,11 @@ const EN = Object.freeze({
|
|
|
122
122
|
'选择机器人工作区目录': 'Select bot workspace folder',
|
|
123
123
|
'当前目录': 'Current folder',
|
|
124
124
|
'主目录': 'Home',
|
|
125
|
+
'直接输入路径': 'Enter a path directly',
|
|
126
|
+
'支持 Windows 盘符、UNC 与 POSIX 绝对路径。': 'Supports Windows drives, UNC paths, and POSIX absolute paths.',
|
|
127
|
+
'输入 Host 上的完整绝对路径': 'Enter a full absolute path on the Host',
|
|
128
|
+
'前往': 'Go',
|
|
129
|
+
'读取中…': 'Loading…',
|
|
125
130
|
'正在准备目录选择器…': 'Preparing folder picker…',
|
|
126
131
|
'正在读取目录…': 'Loading folders…',
|
|
127
132
|
'这个目录中没有子文件夹。': 'This folder has no subfolders.',
|
|
@@ -184,8 +184,21 @@ const CSS = String.raw`
|
|
|
184
184
|
.dim-directoryCrumbs button { max-width: 210px; overflow: hidden; padding: 3px 5px; border: 0; border-radius: 6px; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
|
|
185
185
|
.dim-directoryCrumbs button:hover:not(:disabled) { color: var(--dim-blue); background: var(--dim-blue-soft); }
|
|
186
186
|
.dim-directoryCrumbs button[aria-current="page"] { color: var(--dsw-alias-label-primary, #1f2329); font-weight: 650; }
|
|
187
|
-
.dim-directoryCrumbs button:focus-visible, .dim-directoryList button:focus-visible, .dim-directoryPickerActions button:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 65%, white); outline-offset: 1px; }
|
|
187
|
+
.dim-directoryCrumbs button:focus-visible, .dim-directoryPathInput:focus-visible, .dim-directoryPathControl button:focus-visible, .dim-directoryList button:focus-visible, .dim-directoryPickerActions button:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 65%, white); outline-offset: 1px; }
|
|
188
188
|
.dim-directoryCrumbSeparator { flex: none; font-size: 12px; }
|
|
189
|
+
.dim-directoryPathForm { display: grid; gap: 7px; margin-top: 14px; }
|
|
190
|
+
.dim-directoryPathMeta { min-width: 0; display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
|
191
|
+
.dim-directoryPathMeta label { flex: none; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; font-weight: 650; }
|
|
192
|
+
.dim-directoryPathMeta span { min-width: 0; overflow: hidden; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
|
|
193
|
+
.dim-directoryPathControl { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 8px; }
|
|
194
|
+
.dim-directoryPathInput { min-width: 0; height: 38px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: 12px ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace; line-height: 38px; }
|
|
195
|
+
.dim-directoryPathInput::placeholder { color: var(--dsw-alias-label-tertiary, #8f959e); }
|
|
196
|
+
.dim-directoryPathInput:hover:not(:disabled) { border-color: var(--dsw-alias-border-l3, #bbbfc4); }
|
|
197
|
+
.dim-directoryPathInput:focus { border-color: var(--dim-blue); }
|
|
198
|
+
.dim-directoryPathInput[aria-invalid="true"] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 62%, var(--dsw-alias-border-l2, #dfe1e5)); }
|
|
199
|
+
.dim-directoryPathControl button { min-height: 38px; padding: 0 14px; border: 1px solid color-mix(in srgb, var(--dim-blue) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 8px; color: var(--dim-blue); background: var(--dim-blue-soft); font: inherit; font-size: 13px; font-weight: 650; cursor: pointer; }
|
|
200
|
+
.dim-directoryPathControl button:hover:not(:disabled) { border-color: color-mix(in srgb, var(--dim-blue) 48%, var(--dsw-alias-border-l2, #dfe1e5)); background: color-mix(in srgb, var(--dim-blue) 13%, transparent); }
|
|
201
|
+
.dim-directoryPathInput:disabled, .dim-directoryPathControl button:disabled { cursor: not-allowed; opacity: .55; }
|
|
189
202
|
.dim-directoryPickerBody { min-height: 0; overflow-y: auto; padding: 14px 16px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l2, #dfe1e5) transparent; }
|
|
190
203
|
.dim-directoryList { display: grid; gap: 3px; margin: 0; padding: 0; list-style: none; }
|
|
191
204
|
.dim-directoryList button { width: 100%; min-height: 46px; display: grid; grid-template-columns: 24px minmax(0, 1fr) 18px; align-items: center; gap: 10px; padding: 7px 11px; border: 0; border-radius: 9px; color: var(--dsw-alias-label-primary, #1f2329); background: transparent; font: inherit; text-align: left; cursor: pointer; }
|
|
@@ -279,6 +292,7 @@ const CSS = String.raw`
|
|
|
279
292
|
.dim-directoryPicker { height: calc(100vh - 20px); min-height: 0; border-radius: 14px; }
|
|
280
293
|
.dim-directoryPickerHeader { padding: 18px 17px 14px; }
|
|
281
294
|
.dim-directoryPickerHeader h3 { font-size: 18px; }
|
|
295
|
+
.dim-directoryPathMeta span { display: none; }
|
|
282
296
|
.dim-directoryPickerBody { padding: 10px; }
|
|
283
297
|
.dim-directoryPickerFooter { grid-template-columns: minmax(0, 1fr) max-content; gap: 10px; padding: 13px 14px; }
|
|
284
298
|
.dim-directoryPickerNotice { grid-column: 1 / -1; grid-row: 1; text-align: left; }
|
|
@@ -48,6 +48,7 @@ export function WorkspaceDirectoryPicker({
|
|
|
48
48
|
const [listing, setListing] = React.useState(null);
|
|
49
49
|
const [loading, setLoading] = React.useState(false);
|
|
50
50
|
const [error, setError] = React.useState(null);
|
|
51
|
+
const [pathDraft, setPathDraft] = React.useState(startPath ?? '');
|
|
51
52
|
const [showHidden, setShowHidden] = React.useState(false);
|
|
52
53
|
const [retryKey, setRetryKey] = React.useState(0);
|
|
53
54
|
const requestRef = React.useRef(0);
|
|
@@ -56,6 +57,8 @@ export function WorkspaceDirectoryPicker({
|
|
|
56
57
|
const bodyRef = React.useRef(null);
|
|
57
58
|
const titleId = React.useId();
|
|
58
59
|
const noticeId = React.useId();
|
|
60
|
+
const pathInputId = React.useId();
|
|
61
|
+
const errorId = React.useId();
|
|
59
62
|
const initialPathRef = React.useRef(startPath);
|
|
60
63
|
const onPickedRef = React.useRef(onPicked);
|
|
61
64
|
const onCancelRef = React.useRef(onCancel);
|
|
@@ -78,6 +81,7 @@ export function WorkspaceDirectoryPicker({
|
|
|
78
81
|
if (request !== requestRef.current || controller.signal.aborted) return { aborted: true };
|
|
79
82
|
if (bodyRef.current) bodyRef.current.scrollTop = 0;
|
|
80
83
|
setListing(next);
|
|
84
|
+
if (typeof next?.path === 'string') setPathDraft(next.path);
|
|
81
85
|
setError(null);
|
|
82
86
|
return { value: next };
|
|
83
87
|
} catch (cause) {
|
|
@@ -94,6 +98,7 @@ export function WorkspaceDirectoryPicker({
|
|
|
94
98
|
let active = true;
|
|
95
99
|
setListing(null);
|
|
96
100
|
setError(null);
|
|
101
|
+
setPathDraft(initialPathRef.current ?? '');
|
|
97
102
|
setShowHidden(false);
|
|
98
103
|
dialogRef.current?.focus?.();
|
|
99
104
|
const handleKeyDown = (event) => {
|
|
@@ -145,6 +150,7 @@ export function WorkspaceDirectoryPicker({
|
|
|
145
150
|
const entries = (listing?.entries ?? []).filter((entry) => showHidden || !entry.hidden);
|
|
146
151
|
const crumbs = listing ? displayCrumbs(listing) : [];
|
|
147
152
|
const presentedError = saveError ?? error;
|
|
153
|
+
const pathReady = listing !== null && pathDraft === listing.path;
|
|
148
154
|
|
|
149
155
|
const content = h('div', {
|
|
150
156
|
className: 'dim-directoryPickerBackdrop',
|
|
@@ -177,7 +183,40 @@ export function WorkspaceDirectoryPicker({
|
|
|
177
183
|
? h('span', null, '主目录')
|
|
178
184
|
: (crumb.name || crumb.path)))),
|
|
179
185
|
)
|
|
180
|
-
: h('p', null, '正在准备目录选择器…')
|
|
186
|
+
: h('p', null, '正在准备目录选择器…'),
|
|
187
|
+
h('form', {
|
|
188
|
+
className: 'dim-directoryPathForm',
|
|
189
|
+
onSubmit: (event) => {
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
if (!busy && !loading && pathDraft.trim()) void loadDirectory(pathDraft);
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
h('div', { className: 'dim-directoryPathMeta' },
|
|
195
|
+
h('label', { htmlFor: pathInputId }, '直接输入路径'),
|
|
196
|
+
h('span', null, '支持 Windows 盘符、UNC 与 POSIX 绝对路径。')),
|
|
197
|
+
h('div', { className: 'dim-directoryPathControl' },
|
|
198
|
+
h('input', {
|
|
199
|
+
id: pathInputId,
|
|
200
|
+
className: 'dim-directoryPathInput',
|
|
201
|
+
value: pathDraft,
|
|
202
|
+
placeholder: '输入 Host 上的完整绝对路径',
|
|
203
|
+
'aria-label': '工作区绝对路径',
|
|
204
|
+
'aria-describedby': presentedError ? errorId : undefined,
|
|
205
|
+
'aria-invalid': presentedError ? 'true' : undefined,
|
|
206
|
+
autoCapitalize: 'none',
|
|
207
|
+
autoCorrect: 'off',
|
|
208
|
+
spellCheck: false,
|
|
209
|
+
maxLength: 4_096,
|
|
210
|
+
disabled: busy || loading,
|
|
211
|
+
onChange: (event) => {
|
|
212
|
+
setPathDraft(event.target.value);
|
|
213
|
+
setError(null);
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
h('button', {
|
|
217
|
+
type: 'submit',
|
|
218
|
+
disabled: busy || loading || !pathDraft.trim(),
|
|
219
|
+
}, loading ? '读取中…' : '前往')))),
|
|
181
220
|
h('div', { ref: bodyRef, className: 'dim-directoryPickerBody', 'aria-busy': loading },
|
|
182
221
|
loading && !listing
|
|
183
222
|
? h('div', { className: 'dim-directoryPickerState' },
|
|
@@ -201,7 +240,7 @@ export function WorkspaceDirectoryPicker({
|
|
|
201
240
|
listing?.truncated
|
|
202
241
|
? h('p', { className: 'dim-directoryPickerTruncated' }, '此目录的子文件夹过多,仅显示前一部分。')
|
|
203
242
|
: null,
|
|
204
|
-
presentedError ? h('div', { className: 'dim-directoryPickerError', role: 'alert' },
|
|
243
|
+
presentedError ? h('div', { id: errorId, className: 'dim-directoryPickerError', role: 'alert' },
|
|
205
244
|
h('span', null, presentedError),
|
|
206
245
|
!listing && !busy ? h('button', {
|
|
207
246
|
type: 'button', onClick: () => setRetryKey((value) => value + 1),
|
|
@@ -222,7 +261,7 @@ export function WorkspaceDirectoryPicker({
|
|
|
222
261
|
h('button', {
|
|
223
262
|
type: 'button',
|
|
224
263
|
className: 'dim-directoryPickerPrimary',
|
|
225
|
-
disabled: busy || loading || !
|
|
264
|
+
disabled: busy || loading || !pathReady,
|
|
226
265
|
onClick: () => listing && void onPicked(listing.path),
|
|
227
266
|
}, busy ? '切换中…' : '选择此目录')))));
|
|
228
267
|
|
|
@@ -10,6 +10,8 @@ export default {
|
|
|
10
10
|
'目前支持文字、图片,以及微信已转成文字的语音消息。': 'Currently supports text, images, and voice messages that WeChat has converted to text.',
|
|
11
11
|
'微信与 DeepSeek Harness 连接正常。': 'WeChat is connected to DeepSeek Harness normally.',
|
|
12
12
|
'微信交互问题发送失败。': 'Failed to send the WeChat interaction question.',
|
|
13
|
+
'回复已经生成,但微信发送失败,可能只收到部分内容。请将下面的诊断信息完整反馈给管理员。': 'The reply was generated, but WeChat delivery failed and only part of it may have arrived. Please send the complete diagnostic below to the administrator.',
|
|
14
|
+
'微信发送诊断:{diagnostic}': 'WeChat delivery diagnostic: {diagnostic}',
|
|
13
15
|
|
|
14
16
|
// weixin-controller.mjs
|
|
15
17
|
'微信已授权,但无法读取现有登录凭据。请检查 DSH 凭据存储。': 'WeChat was authorized, but the existing login credential could not be read. Please check the DSH credential store.',
|
|
@@ -240,7 +240,8 @@ export function extractWeixinFiles(message, { fetchImpl = fetch } = {}) {
|
|
|
240
240
|
|
|
241
241
|
function isWeixinHost(hostname) {
|
|
242
242
|
const normalized = hostname.toLowerCase().replace(/\.$/, '');
|
|
243
|
-
return normalized === 'weixin.qq.com' || normalized.endsWith('.weixin.qq.com')
|
|
243
|
+
return normalized === 'weixin.qq.com' || normalized.endsWith('.weixin.qq.com')
|
|
244
|
+
|| normalized === 'wechat.com' || normalized.endsWith('.wechat.com');
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
export function normalizeWeixinApiBaseUrl(value) {
|
|
@@ -708,8 +709,13 @@ export function createWeixinApi({ fetchImpl = fetch } = {}) {
|
|
|
708
709
|
base_info: baseInfo(),
|
|
709
710
|
},
|
|
710
711
|
});
|
|
711
|
-
|
|
712
|
-
|
|
712
|
+
const sendRejection = rejectedProviderResponse(response);
|
|
713
|
+
if (sendRejection) {
|
|
714
|
+
throw new WeixinApiError(
|
|
715
|
+
'send-rejected',
|
|
716
|
+
'微信服务拒绝了回复消息。',
|
|
717
|
+
{ providerCode: sendRejection },
|
|
718
|
+
);
|
|
713
719
|
}
|
|
714
720
|
return true;
|
|
715
721
|
},
|
|
@@ -60,6 +60,7 @@ import { t } from '../shared/i18n.mjs';
|
|
|
60
60
|
const INTERACTION_RESOLVED_TEXT = () => t('这个问题已在其他客户端处理,无需再次回答。');
|
|
61
61
|
const DEFAULT_TYPING_KEEPALIVE_MS = 5_000;
|
|
62
62
|
const TYPING_RETRY_DELAY_MS = 60_000;
|
|
63
|
+
const WEIXIN_SEND_DIAGNOSTIC = Symbol('weixin-send-diagnostic');
|
|
63
64
|
|
|
64
65
|
const HELP_TEXT = () => [
|
|
65
66
|
t('微信已连接 DeepSeek Harness。'),
|
|
@@ -98,6 +99,82 @@ function nonEmptyString(value) {
|
|
|
98
99
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
function safeDiagnosticToken(value) {
|
|
103
|
+
const token = value === undefined || value === null ? '' : String(value).trim();
|
|
104
|
+
return /^-?[A-Za-z0-9_.:-]{1,160}$/u.test(token) ? token : '-';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function weixinApiHost(baseUrl) {
|
|
108
|
+
try {
|
|
109
|
+
return safeDiagnosticToken(new URL(baseUrl).hostname.toLowerCase());
|
|
110
|
+
} catch {
|
|
111
|
+
return '-';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createWeixinSendDiagnostic({
|
|
116
|
+
baseUrl,
|
|
117
|
+
text,
|
|
118
|
+
chunk,
|
|
119
|
+
chunkIndex,
|
|
120
|
+
chunkCount,
|
|
121
|
+
maxMessageChars,
|
|
122
|
+
contextToken,
|
|
123
|
+
runId,
|
|
124
|
+
error,
|
|
125
|
+
}) {
|
|
126
|
+
const status = Number(error?.status ?? error?.httpStatus);
|
|
127
|
+
const http = Number.isInteger(status)
|
|
128
|
+
? String(status)
|
|
129
|
+
: error?.code === 'send-rejected' ? '2xx' : '-';
|
|
130
|
+
return [
|
|
131
|
+
'endpoint=sendmessage',
|
|
132
|
+
`host=${weixinApiHost(baseUrl)}`,
|
|
133
|
+
`chunk=${chunkIndex + 1}/${chunkCount}`,
|
|
134
|
+
`chunkChars=${chunk.length}`,
|
|
135
|
+
`chunkUtf8Bytes=${Buffer.byteLength(chunk, 'utf8')}`,
|
|
136
|
+
`totalChars=${text.length}`,
|
|
137
|
+
`totalUtf8Bytes=${Buffer.byteLength(text, 'utf8')}`,
|
|
138
|
+
`limitChars=${maxMessageChars}`,
|
|
139
|
+
`contextToken=${contextToken ? 'yes' : 'no'}`,
|
|
140
|
+
`runId=${runId ? 'yes' : 'no'}`,
|
|
141
|
+
`http=${http}`,
|
|
142
|
+
`provider=${safeDiagnosticToken(error?.providerCode)}`,
|
|
143
|
+
`cause=${safeDiagnosticToken(error?.code ?? error?.name)}`,
|
|
144
|
+
].join(' ');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function weixinSendError(error, details) {
|
|
148
|
+
const diagnostic = createWeixinSendDiagnostic({ ...details, error });
|
|
149
|
+
const wrapped = new Error(`Weixin text delivery failed (${diagnostic})`, { cause: error });
|
|
150
|
+
const code = safeDiagnosticToken(error?.code);
|
|
151
|
+
wrapped.code = code === '-' ? 'weixin-send-failed' : code;
|
|
152
|
+
const status = Number(error?.status ?? error?.httpStatus);
|
|
153
|
+
if (Number.isInteger(status)) wrapped.status = status;
|
|
154
|
+
const providerCode = safeDiagnosticToken(error?.providerCode);
|
|
155
|
+
if (providerCode !== '-') wrapped.providerCode = providerCode;
|
|
156
|
+
wrapped[WEIXIN_SEND_DIAGNOSTIC] = diagnostic;
|
|
157
|
+
return wrapped;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function weixinSendFailureOptions(error) {
|
|
161
|
+
let current = error;
|
|
162
|
+
for (let depth = 0; current && depth < 5; depth += 1) {
|
|
163
|
+
const diagnostic = current[WEIXIN_SEND_DIAGNOSTIC];
|
|
164
|
+
if (typeof diagnostic === 'string' && diagnostic) {
|
|
165
|
+
return {
|
|
166
|
+
reason: 'weixin-send-failed',
|
|
167
|
+
userMessage: [
|
|
168
|
+
t('回复已经生成,但微信发送失败,可能只收到部分内容。请将下面的诊断信息完整反馈给管理员。'),
|
|
169
|
+
t('微信发送诊断:{diagnostic}', { diagnostic }),
|
|
170
|
+
].join('\n'),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
current = current.cause;
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
101
178
|
export function weixinInboundMessage(message, api) {
|
|
102
179
|
return {
|
|
103
180
|
content: extractWeixinText(message) ?? '',
|
|
@@ -634,7 +711,11 @@ export class WeixinHarnessBridge {
|
|
|
634
711
|
);
|
|
635
712
|
if (textDeliveryError && !delivery.userVisible) throw textDeliveryError;
|
|
636
713
|
if (textDeliveryError && delivery.artifactSendErrors === 0) {
|
|
637
|
-
setLastMessageFailure(
|
|
714
|
+
setLastMessageFailure(
|
|
715
|
+
this.#status,
|
|
716
|
+
textDeliveryError,
|
|
717
|
+
weixinSendFailureOptions(textDeliveryError),
|
|
718
|
+
);
|
|
638
719
|
}
|
|
639
720
|
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
640
721
|
this.#status.messagesReplied += 1;
|
|
@@ -663,9 +744,10 @@ export class WeixinHarnessBridge {
|
|
|
663
744
|
const userMessage = inboundFileUserMessage(error)
|
|
664
745
|
?? imagePromptUserMessage(error);
|
|
665
746
|
const imageDiagnostic = imagePromptDiagnostic(error);
|
|
747
|
+
const sendFailureOptions = weixinSendFailureOptions(error);
|
|
666
748
|
const failure = setLastMessageFailure(this.#status, error, {
|
|
667
|
-
userMessage,
|
|
668
|
-
reason: imageDiagnostic?.reason,
|
|
749
|
+
userMessage: userMessage ?? sendFailureOptions?.userMessage,
|
|
750
|
+
reason: imageDiagnostic?.reason ?? sendFailureOptions?.reason,
|
|
669
751
|
});
|
|
670
752
|
this.#logger.error?.(
|
|
671
753
|
`[dsh-weixin] failed to process an inbound message [${failure.referenceId}]:`,
|
|
@@ -1002,17 +1084,32 @@ export class WeixinHarnessBridge {
|
|
|
1002
1084
|
async #send(toUserId, text, contextToken, runId) {
|
|
1003
1085
|
await this.#stopTyping();
|
|
1004
1086
|
const providerMessageIds = [];
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1087
|
+
const chunks = splitWeixinText(text, this.#maxMessageChars);
|
|
1088
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
|
|
1089
|
+
const chunk = chunks[chunkIndex];
|
|
1090
|
+
try {
|
|
1091
|
+
const result = await this.#api.sendText({
|
|
1092
|
+
baseUrl: this.#baseUrl,
|
|
1093
|
+
token: this.#token,
|
|
1094
|
+
toUserId,
|
|
1095
|
+
text: chunk,
|
|
1096
|
+
contextToken,
|
|
1097
|
+
runId,
|
|
1098
|
+
signal: this.#signal,
|
|
1099
|
+
});
|
|
1100
|
+
providerMessageIds.push(...providerMessageIdsFor(result));
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
throw weixinSendError(error, {
|
|
1103
|
+
baseUrl: this.#baseUrl,
|
|
1104
|
+
text,
|
|
1105
|
+
chunk,
|
|
1106
|
+
chunkIndex,
|
|
1107
|
+
chunkCount: chunks.length,
|
|
1108
|
+
maxMessageChars: this.#maxMessageChars,
|
|
1109
|
+
contextToken,
|
|
1110
|
+
runId,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1016
1113
|
}
|
|
1017
1114
|
return providerMessageIds;
|
|
1018
1115
|
}
|