openclaw-xiaoyou 1.3.0 → 1.3.2
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 +30 -50
- package/src/enterprise-client.ts +1 -23
- package/src/types.ts +12 -29
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -180,18 +180,14 @@ export const xiayouPlugin = {
|
|
|
180
180
|
Timestamp: Date.now(),
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
-
// 4.
|
|
183
|
+
// 4. 分发消息给 Agent
|
|
184
184
|
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// dispatch promise resolve 后发送 reply_end 标记。
|
|
185
|
+
// Block streaming 机制说明:
|
|
186
|
+
// 当 blockStreaming=true 且 Gateway 配置了 blockStreamingDefault="on" 时,
|
|
187
|
+
// Gateway 会在 LLM 生成过程中通过 outbound.send 逐块推送消息,
|
|
188
|
+
// 不经过 deliver 回调。deliver 仅在非流式场景下被调用(作为 fallback)。
|
|
190
189
|
//
|
|
191
190
|
const replyMessageId = `xiaoyou-${Date.now()}`;
|
|
192
|
-
let chunkSeq = 0;
|
|
193
|
-
let fullText = "";
|
|
194
|
-
let replyEndSent = false;
|
|
195
191
|
|
|
196
192
|
await rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
197
193
|
ctx: inboundCtx,
|
|
@@ -199,57 +195,24 @@ export const xiayouPlugin = {
|
|
|
199
195
|
dispatcherOptions: {
|
|
200
196
|
responsePrefix: "",
|
|
201
197
|
deliver: async (payload: any) => {
|
|
198
|
+
// Fallback: 当 block streaming 未生效时,deliver 被调用
|
|
202
199
|
const textToSend = payload.markdown || payload.text;
|
|
203
200
|
if (!textToSend) return;
|
|
204
201
|
|
|
205
|
-
fullText += (fullText ? "\n" : "") + textToSend;
|
|
206
|
-
|
|
207
202
|
if (_client && _client.isConnected()) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
type: "reply_chunk",
|
|
203
|
+
_client.sendReply({
|
|
204
|
+
type: "reply",
|
|
211
205
|
conversationId,
|
|
212
206
|
messageId: replyMessageId,
|
|
213
207
|
replyToMessageId: inboundMessageId,
|
|
214
|
-
|
|
215
|
-
seq: chunkSeq++,
|
|
208
|
+
text: textToSend,
|
|
216
209
|
timestamp: Date.now(),
|
|
217
210
|
});
|
|
218
|
-
logger.info(`[xiaoyou]
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
onComplete: async () => {
|
|
222
|
-
// Gateway 调用 onComplete 表示所有 block 已推送完毕
|
|
223
|
-
replyEndSent = true;
|
|
224
|
-
if (_client && _client.isConnected()) {
|
|
225
|
-
_client.sendReplyEnd({
|
|
226
|
-
type: "reply_end",
|
|
227
|
-
conversationId,
|
|
228
|
-
messageId: replyMessageId,
|
|
229
|
-
replyToMessageId: inboundMessageId,
|
|
230
|
-
fullText,
|
|
231
|
-
timestamp: Date.now(),
|
|
232
|
-
});
|
|
233
|
-
logger.info(`[xiaoyou] streaming completed to ${conversationId} (${chunkSeq} blocks)${inboundMessageId ? ` (replyTo=${inboundMessageId})` : ""}`);
|
|
211
|
+
logger.info(`[xiaoyou] reply sent to ${conversationId}${inboundMessageId ? ` (replyTo=${inboundMessageId})` : ""}`);
|
|
234
212
|
}
|
|
235
213
|
},
|
|
236
214
|
},
|
|
237
215
|
});
|
|
238
|
-
|
|
239
|
-
// dispatch resolve 后,如果 onComplete 未被调用(旧版本兼容),手动发 reply_end
|
|
240
|
-
if (!replyEndSent && chunkSeq > 0 && _client && _client.isConnected()) {
|
|
241
|
-
_client.sendReplyEnd({
|
|
242
|
-
type: "reply_end",
|
|
243
|
-
conversationId,
|
|
244
|
-
messageId: replyMessageId,
|
|
245
|
-
replyToMessageId: inboundMessageId,
|
|
246
|
-
fullText,
|
|
247
|
-
timestamp: Date.now(),
|
|
248
|
-
});
|
|
249
|
-
logger.info(`[xiaoyou] streaming completed (fallback) to ${conversationId} (${chunkSeq} blocks)${inboundMessageId ? ` (replyTo=${inboundMessageId})` : ""}`);
|
|
250
|
-
} else if (chunkSeq === 0) {
|
|
251
|
-
logger.warn(`[xiaoyou] no reply generated for ${conversationId}`);
|
|
252
|
-
}
|
|
253
216
|
},
|
|
254
217
|
});
|
|
255
218
|
|
|
@@ -277,17 +240,34 @@ export const xiayouPlugin = {
|
|
|
277
240
|
|
|
278
241
|
// ── Outbound 出站 ──────────────────────────────────
|
|
279
242
|
outbound: {
|
|
280
|
-
send: async ({ to, payload }: any) => {
|
|
243
|
+
send: async ({ to, payload, meta, ...rest }: any) => {
|
|
281
244
|
if (!_client || !_client.isConnected()) {
|
|
282
245
|
return { ok: false, error: "xiaoyou: not connected" };
|
|
283
246
|
}
|
|
284
247
|
|
|
285
|
-
|
|
248
|
+
// 调试日志:观察 Gateway block streaming 时传入的完整参数
|
|
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 = {
|
|
286
263
|
type: "reply" as const,
|
|
287
264
|
conversationId: to,
|
|
288
|
-
messageId: `xiaoyou-${Date.now()}`,
|
|
265
|
+
messageId: payload.messageId || meta?.messageId || `xiaoyou-${Date.now()}`,
|
|
266
|
+
replyToMessageId: payload.replyToMessageId || meta?.replyToMessageId,
|
|
289
267
|
agentId: payload.agentId,
|
|
290
268
|
timestamp: Date.now(),
|
|
269
|
+
...(streamStatus && { streamStatus }),
|
|
270
|
+
...(seq !== undefined && { seq }),
|
|
291
271
|
};
|
|
292
272
|
|
|
293
273
|
if (payload.kind === "text") {
|
package/src/enterprise-client.ts
CHANGED
|
@@ -11,8 +11,6 @@ import type {
|
|
|
11
11
|
Frame,
|
|
12
12
|
InboundMessage,
|
|
13
13
|
OutboundReply,
|
|
14
|
-
OutboundReplyChunk,
|
|
15
|
-
OutboundReplyEnd,
|
|
16
14
|
PongFrame,
|
|
17
15
|
} from "./types.js";
|
|
18
16
|
|
|
@@ -33,8 +31,6 @@ export type EnterpriseClient = {
|
|
|
33
31
|
connect: () => void;
|
|
34
32
|
disconnect: () => void;
|
|
35
33
|
sendReply: (reply: OutboundReply) => boolean;
|
|
36
|
-
sendChunk: (chunk: OutboundReplyChunk) => boolean;
|
|
37
|
-
sendReplyEnd: (end: OutboundReplyEnd) => boolean;
|
|
38
34
|
isConnected: () => boolean;
|
|
39
35
|
};
|
|
40
36
|
|
|
@@ -186,27 +182,9 @@ export function createEnterpriseClient(opts: EnterpriseClientOptions): Enterpris
|
|
|
186
182
|
return true;
|
|
187
183
|
}
|
|
188
184
|
|
|
189
|
-
function sendChunk(chunk: OutboundReplyChunk): boolean {
|
|
190
|
-
if (!ws || ws.readyState !== WebSocket.OPEN || !authenticated) {
|
|
191
|
-
logger.warn("[xiaoyou] cannot send chunk: not connected");
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
ws.send(JSON.stringify(chunk));
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function sendReplyEnd(end: OutboundReplyEnd): boolean {
|
|
199
|
-
if (!ws || ws.readyState !== WebSocket.OPEN || !authenticated) {
|
|
200
|
-
logger.warn("[xiaoyou] cannot send reply_end: not connected");
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
ws.send(JSON.stringify(end));
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
185
|
function isConnected(): boolean {
|
|
208
186
|
return ws !== null && ws.readyState === WebSocket.OPEN && authenticated;
|
|
209
187
|
}
|
|
210
188
|
|
|
211
|
-
return { connect, disconnect, sendReply,
|
|
189
|
+
return { connect, disconnect, sendReply, isConnected };
|
|
212
190
|
}
|
package/src/types.ts
CHANGED
|
@@ -47,6 +47,14 @@ export type Attachment = {
|
|
|
47
47
|
|
|
48
48
|
// ─── 出站回复(插件 → 企业服务)─────────────────────────
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 流式状态标记:
|
|
52
|
+
* - "chunk" — 流式片段,text 为增量内容
|
|
53
|
+
* - "end" — 流结束,text 为完整文本
|
|
54
|
+
* - undefined — 非流式,一次性完整回复(向后兼容)
|
|
55
|
+
*/
|
|
56
|
+
export type StreamStatus = "chunk" | "end";
|
|
57
|
+
|
|
50
58
|
export type OutboundReply = {
|
|
51
59
|
type: "reply";
|
|
52
60
|
conversationId: string;
|
|
@@ -56,33 +64,10 @@ export type OutboundReply = {
|
|
|
56
64
|
mediaUrls?: string[];
|
|
57
65
|
agentId?: string;
|
|
58
66
|
timestamp: number;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
type: "reply_chunk";
|
|
64
|
-
conversationId: string;
|
|
65
|
-
messageId: string;
|
|
66
|
-
replyToMessageId?: string;
|
|
67
|
-
/** 本次增量文本 */
|
|
68
|
-
delta: string;
|
|
69
|
-
/** 该 chunk 在整条回复中的序号(从 0 开始) */
|
|
70
|
-
seq: number;
|
|
71
|
-
agentId?: string;
|
|
72
|
-
timestamp: number;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/** 流式回复结束标记(插件 → 企业服务) */
|
|
76
|
-
export type OutboundReplyEnd = {
|
|
77
|
-
type: "reply_end";
|
|
78
|
-
conversationId: string;
|
|
79
|
-
messageId: string;
|
|
80
|
-
replyToMessageId?: string;
|
|
81
|
-
/** 完整文本(可选,方便企业侧校验拼接结果) */
|
|
82
|
-
fullText?: string;
|
|
83
|
-
mediaUrls?: string[];
|
|
84
|
-
agentId?: string;
|
|
85
|
-
timestamp: number;
|
|
67
|
+
/** 流式标记:chunk=增量片段, end=流结束, 不传=一次性完整回复 */
|
|
68
|
+
streamStatus?: StreamStatus;
|
|
69
|
+
/** 流式片段序号(仅 streamStatus="chunk" 时有值,从 0 开始) */
|
|
70
|
+
seq?: number;
|
|
86
71
|
};
|
|
87
72
|
|
|
88
73
|
// ─── 控制帧 ─────────────────────────────────────────────
|
|
@@ -108,8 +93,6 @@ export type PongFrame = { type: "pong"; seq: number; ts: number };
|
|
|
108
93
|
export type Frame =
|
|
109
94
|
| InboundMessage
|
|
110
95
|
| OutboundReply
|
|
111
|
-
| OutboundReplyChunk
|
|
112
|
-
| OutboundReplyEnd
|
|
113
96
|
| AuthFrame
|
|
114
97
|
| AuthResultFrame
|
|
115
98
|
| PingFrame
|