openclaw-xiaoyou 1.3.2 → 1.3.3
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/package.json +1 -1
- package/src/channel.ts +69 -28
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -16,6 +16,25 @@ let _runtime: any = null;
|
|
|
16
16
|
export function setRuntime(rt: any) { _runtime = rt; }
|
|
17
17
|
export function getRuntime() { return _runtime; }
|
|
18
18
|
|
|
19
|
+
// ─── 按标点/换行拆分文本 ─────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 将文本按中英文标点符号和换行符拆分为多个片段。
|
|
23
|
+
* 每个片段以标点或换行结尾(保留标点在片段内)。
|
|
24
|
+
*/
|
|
25
|
+
function splitBySentence(text: string): string[] {
|
|
26
|
+
// 匹配:中文标点(。!?;)、英文标点(.!?;)后跟空格或结尾、换行符
|
|
27
|
+
const parts = text.split(/(?<=[。!?;\n])|(?<=[.!?;]\s)/);
|
|
28
|
+
const result: string[] = [];
|
|
29
|
+
for (const part of parts) {
|
|
30
|
+
const trimmed = part;
|
|
31
|
+
if (trimmed.length > 0) {
|
|
32
|
+
result.push(trimmed);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result.length > 0 ? result : [text];
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
// ─── Config Adapter ──────────────────────────────────
|
|
20
39
|
|
|
21
40
|
function getChannelConfig(cfg: any): any {
|
|
@@ -180,14 +199,15 @@ export const xiayouPlugin = {
|
|
|
180
199
|
Timestamp: Date.now(),
|
|
181
200
|
});
|
|
182
201
|
|
|
183
|
-
// 4.
|
|
202
|
+
// 4. 分发并获取回复(流式 — 按标点拆分)
|
|
184
203
|
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// Gateway 会在 LLM 生成过程中通过 outbound.send 逐块推送消息,
|
|
188
|
-
// 不经过 deliver 回调。deliver 仅在非流式场景下被调用(作为 fallback)。
|
|
204
|
+
// Gateway 的 block streaming 会多次调用 deliver,每次一个 block。
|
|
205
|
+
// 我们在 deliver 内部进一步按标点/换行拆分,实现更细粒度的流式推送。
|
|
189
206
|
//
|
|
190
207
|
const replyMessageId = `xiaoyou-${Date.now()}`;
|
|
208
|
+
let chunkSeq = 0;
|
|
209
|
+
let fullText = "";
|
|
210
|
+
let replyEndSent = false;
|
|
191
211
|
|
|
192
212
|
await rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
193
213
|
ctx: inboundCtx,
|
|
@@ -195,24 +215,62 @@ export const xiayouPlugin = {
|
|
|
195
215
|
dispatcherOptions: {
|
|
196
216
|
responsePrefix: "",
|
|
197
217
|
deliver: async (payload: any) => {
|
|
198
|
-
// Fallback: 当 block streaming 未生效时,deliver 被调用
|
|
199
218
|
const textToSend = payload.markdown || payload.text;
|
|
200
219
|
if (!textToSend) return;
|
|
201
220
|
|
|
221
|
+
// 按标点/换行拆分为更细的片段
|
|
222
|
+
const sentences = splitBySentence(textToSend);
|
|
223
|
+
|
|
224
|
+
for (const sentence of sentences) {
|
|
225
|
+
fullText += sentence;
|
|
226
|
+
if (_client && _client.isConnected()) {
|
|
227
|
+
_client.sendReply({
|
|
228
|
+
type: "reply",
|
|
229
|
+
conversationId,
|
|
230
|
+
messageId: replyMessageId,
|
|
231
|
+
replyToMessageId: inboundMessageId,
|
|
232
|
+
text: sentence,
|
|
233
|
+
streamStatus: "chunk",
|
|
234
|
+
seq: chunkSeq++,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
logger.info(`[xiaoyou] ${sentences.length} chunks sent to ${conversationId} (total seq=${chunkSeq})`);
|
|
240
|
+
},
|
|
241
|
+
onComplete: async () => {
|
|
242
|
+
replyEndSent = true;
|
|
202
243
|
if (_client && _client.isConnected()) {
|
|
203
244
|
_client.sendReply({
|
|
204
245
|
type: "reply",
|
|
205
246
|
conversationId,
|
|
206
247
|
messageId: replyMessageId,
|
|
207
248
|
replyToMessageId: inboundMessageId,
|
|
208
|
-
text:
|
|
249
|
+
text: fullText,
|
|
250
|
+
streamStatus: "end",
|
|
209
251
|
timestamp: Date.now(),
|
|
210
252
|
});
|
|
211
|
-
logger.info(`[xiaoyou]
|
|
253
|
+
logger.info(`[xiaoyou] stream end sent to ${conversationId} (${chunkSeq} chunks)${inboundMessageId ? ` (replyTo=${inboundMessageId})` : ""}`);
|
|
212
254
|
}
|
|
213
255
|
},
|
|
214
256
|
},
|
|
215
257
|
});
|
|
258
|
+
|
|
259
|
+
// dispatch resolve 后,如果 onComplete 未被调用(兼容),手动发 end
|
|
260
|
+
if (!replyEndSent && chunkSeq > 0 && _client && _client.isConnected()) {
|
|
261
|
+
_client.sendReply({
|
|
262
|
+
type: "reply",
|
|
263
|
+
conversationId,
|
|
264
|
+
messageId: replyMessageId,
|
|
265
|
+
replyToMessageId: inboundMessageId,
|
|
266
|
+
text: fullText,
|
|
267
|
+
streamStatus: "end",
|
|
268
|
+
timestamp: Date.now(),
|
|
269
|
+
});
|
|
270
|
+
logger.info(`[xiaoyou] stream end (fallback) sent to ${conversationId} (${chunkSeq} chunks)${inboundMessageId ? ` (replyTo=${inboundMessageId})` : ""}`);
|
|
271
|
+
} else if (chunkSeq === 0) {
|
|
272
|
+
logger.warn(`[xiaoyou] no reply generated for ${conversationId}`);
|
|
273
|
+
}
|
|
216
274
|
},
|
|
217
275
|
});
|
|
218
276
|
|
|
@@ -240,34 +298,17 @@ export const xiayouPlugin = {
|
|
|
240
298
|
|
|
241
299
|
// ── Outbound 出站 ──────────────────────────────────
|
|
242
300
|
outbound: {
|
|
243
|
-
send: async ({ to, payload
|
|
301
|
+
send: async ({ to, payload }: any) => {
|
|
244
302
|
if (!_client || !_client.isConnected()) {
|
|
245
303
|
return { ok: false, error: "xiaoyou: not connected" };
|
|
246
304
|
}
|
|
247
305
|
|
|
248
|
-
|
|
249
|
-
const rt = getRuntime();
|
|
250
|
-
const logger = rt?.log || console;
|
|
251
|
-
logger.info(`[xiaoyou] outbound.send called: to=${to}, payload=${JSON.stringify(payload)}, meta=${JSON.stringify(meta)}, rest=${JSON.stringify(rest)}`);
|
|
252
|
-
|
|
253
|
-
// 推断 streamStatus:
|
|
254
|
-
// Gateway block streaming 可能通过 meta 或 payload 传递流式信息
|
|
255
|
-
const streamMeta = meta?.blockStream || payload?.blockStream || meta?.stream;
|
|
256
|
-
const isFinal = streamMeta?.final ?? meta?.final ?? payload?.final;
|
|
257
|
-
const seq = streamMeta?.seq ?? meta?.seq ?? payload?.seq;
|
|
258
|
-
const streamStatus = streamMeta
|
|
259
|
-
? (isFinal ? "end" : "chunk")
|
|
260
|
-
: (meta?.isBlock ? "chunk" : undefined);
|
|
261
|
-
|
|
262
|
-
const baseReply: any = {
|
|
306
|
+
const baseReply = {
|
|
263
307
|
type: "reply" as const,
|
|
264
308
|
conversationId: to,
|
|
265
|
-
messageId:
|
|
266
|
-
replyToMessageId: payload.replyToMessageId || meta?.replyToMessageId,
|
|
309
|
+
messageId: `xiaoyou-${Date.now()}`,
|
|
267
310
|
agentId: payload.agentId,
|
|
268
311
|
timestamp: Date.now(),
|
|
269
|
-
...(streamStatus && { streamStatus }),
|
|
270
|
-
...(seq !== undefined && { seq }),
|
|
271
312
|
};
|
|
272
313
|
|
|
273
314
|
if (payload.kind === "text") {
|