lightclawbot 1.0.6 → 1.0.7
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/dist/public/data/scripts/manifest.json +5 -5
- package/dist/public/data/scripts/upgrade.211d7e4c.sh +266 -0
- package/dist/public/data/scripts/upgrade.sh +266 -0
- package/dist/src/config.d.ts +3 -3
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +4 -4
- package/dist/src/config.js.map +1 -1
- package/dist/src/dedup.d.ts +6 -1
- package/dist/src/dedup.d.ts.map +1 -1
- package/dist/src/dedup.js +27 -14
- package/dist/src/dedup.js.map +1 -1
- package/dist/src/gateway.d.ts.map +1 -1
- package/dist/src/gateway.js +9 -6
- package/dist/src/gateway.js.map +1 -1
- package/dist/src/history/text-processing.d.ts.map +1 -1
- package/dist/src/history/text-processing.js +2 -0
- package/dist/src/history/text-processing.js.map +1 -1
- package/dist/src/inbound.js +2 -2
- package/dist/src/inbound.js.map +1 -1
- package/dist/src/socket-handlers.d.ts.map +1 -1
- package/dist/src/socket-handlers.js +45 -45
- package/dist/src/socket-handlers.js.map +1 -1
- package/dist/src/streaming/delta-tracker.d.ts +34 -0
- package/dist/src/streaming/delta-tracker.d.ts.map +1 -0
- package/dist/src/streaming/delta-tracker.js +145 -0
- package/dist/src/streaming/delta-tracker.js.map +1 -0
- package/dist/src/streaming/index.d.ts +12 -0
- package/dist/src/streaming/index.d.ts.map +1 -0
- package/dist/src/streaming/index.js +13 -0
- package/dist/src/streaming/index.js.map +1 -0
- package/dist/src/streaming/stream-reply-sink.d.ts +59 -0
- package/dist/src/streaming/stream-reply-sink.d.ts.map +1 -0
- package/dist/src/streaming/stream-reply-sink.js +293 -0
- package/dist/src/streaming/stream-reply-sink.js.map +1 -0
- package/dist/src/streaming/types.d.ts +45 -0
- package/dist/src/streaming/types.d.ts.map +1 -0
- package/dist/src/streaming/types.js +7 -0
- package/dist/src/streaming/types.js.map +1 -0
- package/package.json +1 -1
- package/dist/public/data/scripts/preflight.78097a58.sh +0 -94
- package/dist/public/data/scripts/preflight.sh +0 -94
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightClaw — 流式输出模块
|
|
3
|
+
*
|
|
4
|
+
* 统一导出流式输出相关的类型、工具和分发器。
|
|
5
|
+
*
|
|
6
|
+
* 使用方式:
|
|
7
|
+
* import { createStreamReplyConfig } from "./streaming/index.js";
|
|
8
|
+
*/
|
|
9
|
+
export type { StreamFrameKind, DeltaTrackerState, StreamReplySinkOptions } from "./types.js";
|
|
10
|
+
export { createDeltaTrackerState, toStreamDeltaText } from "./delta-tracker.js";
|
|
11
|
+
export { createStreamReplyConfig } from "./stream-reply-sink.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/streaming/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAG7F,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGhF,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightClaw — 流式输出模块
|
|
3
|
+
*
|
|
4
|
+
* 统一导出流式输出相关的类型、工具和分发器。
|
|
5
|
+
*
|
|
6
|
+
* 使用方式:
|
|
7
|
+
* import { createStreamReplyConfig } from "./streaming/index.js";
|
|
8
|
+
*/
|
|
9
|
+
// 增量计算器
|
|
10
|
+
export { createDeltaTrackerState, toStreamDeltaText } from "./delta-tracker.js";
|
|
11
|
+
// 真流式回复配置(dispatcher + replyOptions,用于 dispatchReplyFromConfig)
|
|
12
|
+
export { createStreamReplyConfig } from "./stream-reply-sink.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/streaming/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,QAAQ;AACR,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,gEAAgE;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightClaw — 真流式回复分发 (StreamReplySink)
|
|
3
|
+
*
|
|
4
|
+
* 参考 kimi-claw 的 `handleSessionPrompt` (ACP 通路) 中的流式分发架构。
|
|
5
|
+
*
|
|
6
|
+
* 核心改造:
|
|
7
|
+
* 从 `dispatchReplyWithBufferedBlockDispatcher` (Buffered Block 模式) 切换到
|
|
8
|
+
* `dispatchReplyFromConfig` (直接回调模式),实现逐 token 的真流式输出。
|
|
9
|
+
*
|
|
10
|
+
* 原理对比:
|
|
11
|
+
* - 旧模式(Buffered Block):框架内部缓冲 → 累积一个 block → deliver(payload, {kind})
|
|
12
|
+
* 用户体验:等一会儿突然蹦出一大段文字
|
|
13
|
+
* - 新模式(dispatchReplyFromConfig):AI 每个 token → onPartialReply 实时回调
|
|
14
|
+
* 用户体验:逐字/逐词流畅输出
|
|
15
|
+
*
|
|
16
|
+
* dispatchReplyFromConfig 需要两个参数:
|
|
17
|
+
* 1. dispatcher: ReplyDispatcher — 包含 sendToolResult / sendBlockReply / sendFinalReply 等
|
|
18
|
+
* 2. replyOptions: GetReplyOptions — 包含 onPartialReply / onReasoningStream / onToolStart 等
|
|
19
|
+
*/
|
|
20
|
+
import type { StreamReplySinkOptions } from "./types.js";
|
|
21
|
+
import type { SignalContext } from "../types.js";
|
|
22
|
+
/**
|
|
23
|
+
* 创建真流式回复的 dispatcher 和 replyOptions。
|
|
24
|
+
*
|
|
25
|
+
* 返回 `{ dispatcher, replyOptions }` 对象,直接用于
|
|
26
|
+
* `dispatchReplyFromConfig` 的参数。
|
|
27
|
+
*
|
|
28
|
+
* 对齐 kimi-claw `handleSessionPrompt` 中的实现:
|
|
29
|
+
* - onPartialReply: 每个 token 增量 → 实时 emit stream_chunk
|
|
30
|
+
* - onReasoningStream: 思考过程增量 → 实时 emit reasoning_chunk
|
|
31
|
+
* - onToolStart: 工具调用开始通知
|
|
32
|
+
* - sendFinalReply: 去重后发送最终增量
|
|
33
|
+
* - sendBlockReply: 不处理(已通过 onPartialReply 实时推送)
|
|
34
|
+
*/
|
|
35
|
+
export declare function createStreamReplyConfig(opts: StreamReplySinkOptions, prefixOptions: Record<string, unknown>, signalCtx: SignalContext): {
|
|
36
|
+
dispatcher: {
|
|
37
|
+
sendToolResult: (payload: {
|
|
38
|
+
text?: string;
|
|
39
|
+
mediaUrls?: string[];
|
|
40
|
+
mediaUrl?: string;
|
|
41
|
+
isError?: boolean;
|
|
42
|
+
}) => boolean;
|
|
43
|
+
sendBlockReply: (payload: {
|
|
44
|
+
text?: string;
|
|
45
|
+
mediaUrls?: string[];
|
|
46
|
+
mediaUrl?: string;
|
|
47
|
+
}) => boolean;
|
|
48
|
+
sendFinalReply: (payload: {
|
|
49
|
+
text?: string;
|
|
50
|
+
mediaUrls?: string[];
|
|
51
|
+
mediaUrl?: string;
|
|
52
|
+
}) => boolean;
|
|
53
|
+
waitForIdle: () => Promise<void>;
|
|
54
|
+
getQueuedCounts: () => Record<string, number>;
|
|
55
|
+
markComplete: () => void;
|
|
56
|
+
};
|
|
57
|
+
replyOptions: Record<string, unknown>;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=stream-reply-sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-reply-sink.d.ts","sourceRoot":"","sources":["../../../src/streaming/stream-reply-sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,sBAAsB,EAC5B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,SAAS,EAAE,aAAa,GACvB;IACD,UAAU,EAAE;QACV,cAAc,EAAE,CAAC,OAAO,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC;QACpH,cAAc,EAAE,CAAC,OAAO,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC;QACjG,cAAc,EAAE,CAAC,OAAO,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC;QACjG,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QACjC,eAAe,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9C,YAAY,EAAE,MAAM,IAAI,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC,CAiSA"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightClaw — 真流式回复分发 (StreamReplySink)
|
|
3
|
+
*
|
|
4
|
+
* 参考 kimi-claw 的 `handleSessionPrompt` (ACP 通路) 中的流式分发架构。
|
|
5
|
+
*
|
|
6
|
+
* 核心改造:
|
|
7
|
+
* 从 `dispatchReplyWithBufferedBlockDispatcher` (Buffered Block 模式) 切换到
|
|
8
|
+
* `dispatchReplyFromConfig` (直接回调模式),实现逐 token 的真流式输出。
|
|
9
|
+
*
|
|
10
|
+
* 原理对比:
|
|
11
|
+
* - 旧模式(Buffered Block):框架内部缓冲 → 累积一个 block → deliver(payload, {kind})
|
|
12
|
+
* 用户体验:等一会儿突然蹦出一大段文字
|
|
13
|
+
* - 新模式(dispatchReplyFromConfig):AI 每个 token → onPartialReply 实时回调
|
|
14
|
+
* 用户体验:逐字/逐词流畅输出
|
|
15
|
+
*
|
|
16
|
+
* dispatchReplyFromConfig 需要两个参数:
|
|
17
|
+
* 1. dispatcher: ReplyDispatcher — 包含 sendToolResult / sendBlockReply / sendFinalReply 等
|
|
18
|
+
* 2. replyOptions: GetReplyOptions — 包含 onPartialReply / onReasoningStream / onToolStart 等
|
|
19
|
+
*/
|
|
20
|
+
import { CHANNEL_KEY } from "../config.js";
|
|
21
|
+
import { uploadFileToCos } from "../file-storage.js";
|
|
22
|
+
import { mediaUrlsToFiles } from "../media.js";
|
|
23
|
+
import { createDeltaTrackerState, toStreamDeltaText } from "./delta-tracker.js";
|
|
24
|
+
import { emitSignal } from "../types.js";
|
|
25
|
+
/**
|
|
26
|
+
* 创建真流式回复的 dispatcher 和 replyOptions。
|
|
27
|
+
*
|
|
28
|
+
* 返回 `{ dispatcher, replyOptions }` 对象,直接用于
|
|
29
|
+
* `dispatchReplyFromConfig` 的参数。
|
|
30
|
+
*
|
|
31
|
+
* 对齐 kimi-claw `handleSessionPrompt` 中的实现:
|
|
32
|
+
* - onPartialReply: 每个 token 增量 → 实时 emit stream_chunk
|
|
33
|
+
* - onReasoningStream: 思考过程增量 → 实时 emit reasoning_chunk
|
|
34
|
+
* - onToolStart: 工具调用开始通知
|
|
35
|
+
* - sendFinalReply: 去重后发送最终增量
|
|
36
|
+
* - sendBlockReply: 不处理(已通过 onPartialReply 实时推送)
|
|
37
|
+
*/
|
|
38
|
+
export function createStreamReplyConfig(opts, prefixOptions, signalCtx) {
|
|
39
|
+
const { emitter, targetId, originalMsgId, log, effectiveApiKey } = opts;
|
|
40
|
+
// ── 增量追踪器:追踪 AI 输出的累计文本快照 ──
|
|
41
|
+
// 参考 kimi-claw 中 const I = { latest: "" } 和 _ = { latest: "" }
|
|
42
|
+
const partialReplyState = createDeltaTrackerState();
|
|
43
|
+
const reasoningState = createDeltaTrackerState();
|
|
44
|
+
// ── 已通过流式推送的累计文本(用于 sendFinalReply 时去重) ──
|
|
45
|
+
// 参考 kimi-claw 中 let y = ""
|
|
46
|
+
let streamedText = "";
|
|
47
|
+
// ── 分发计数器(对齐 kimi-claw 中的 w = { tool: 0, block: 0, final: 0 }) ──
|
|
48
|
+
const counts = { tool: 0, block: 0, final: 0 };
|
|
49
|
+
// ── typing 状态管理 ──
|
|
50
|
+
let typingStartSent = false;
|
|
51
|
+
let idleSent = false;
|
|
52
|
+
/**
|
|
53
|
+
* 确保 typing_start 只发一次。
|
|
54
|
+
* 在 onPartialReply / onReasoningStream 首次触发时自动发送。
|
|
55
|
+
*/
|
|
56
|
+
const ensureTypingStart = () => {
|
|
57
|
+
if (!typingStartSent) {
|
|
58
|
+
typingStartSent = true;
|
|
59
|
+
emitSignal(signalCtx, "typing_start");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* 发送 typing_stop(幂等)
|
|
64
|
+
*/
|
|
65
|
+
const sendTypingStop = () => {
|
|
66
|
+
if (idleSent)
|
|
67
|
+
return;
|
|
68
|
+
idleSent = true;
|
|
69
|
+
emitSignal(signalCtx, "typing_stop");
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* 处理最终回复中的媒体文件(COS 上传 + 发送)
|
|
73
|
+
*/
|
|
74
|
+
const handleMediaFinal = async (replyText, mediaList) => {
|
|
75
|
+
if (mediaList.length === 0)
|
|
76
|
+
return false;
|
|
77
|
+
const files = await mediaUrlsToFiles(mediaList, log);
|
|
78
|
+
const publicUrls = [];
|
|
79
|
+
const storageConfig = {
|
|
80
|
+
apiKey: effectiveApiKey,
|
|
81
|
+
};
|
|
82
|
+
for (const mediaUrl of mediaList) {
|
|
83
|
+
try {
|
|
84
|
+
const localPath = mediaUrl.startsWith("file://")
|
|
85
|
+
? mediaUrl.slice(7)
|
|
86
|
+
: mediaUrl;
|
|
87
|
+
if (localPath.startsWith("/") || localPath.match(/^[A-Za-z]:\\/)) {
|
|
88
|
+
const { existsSync } = await import("node:fs");
|
|
89
|
+
if (existsSync(localPath)) {
|
|
90
|
+
const result = await uploadFileToCos(localPath, storageConfig);
|
|
91
|
+
publicUrls.push(result.url || "");
|
|
92
|
+
log?.info(`[${CHANNEL_KEY}] [stream] Uploaded to COS: ${localPath} → ${result.url}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (uploadErr) {
|
|
97
|
+
log?.warn(`[${CHANNEL_KEY}] [stream] COS upload failed for ${mediaUrl}: ${uploadErr}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
let enrichedText = replyText;
|
|
101
|
+
if (publicUrls.length > 0) {
|
|
102
|
+
const urlSection = publicUrls
|
|
103
|
+
.map((url, i) => {
|
|
104
|
+
const match = url.match(/filePath=([^&]+)/);
|
|
105
|
+
const filePath = match ? decodeURIComponent(match[1]) : "";
|
|
106
|
+
const fileName = filePath.split("/").pop() || `file${publicUrls.length > 1 ? ` (${i + 1})` : ""}`;
|
|
107
|
+
return `📎 [${fileName}](${url})`;
|
|
108
|
+
})
|
|
109
|
+
.join("\n");
|
|
110
|
+
enrichedText = enrichedText
|
|
111
|
+
? `${enrichedText}\n\n${urlSection}`
|
|
112
|
+
: urlSection;
|
|
113
|
+
}
|
|
114
|
+
if (files.length > 0) {
|
|
115
|
+
emitter.sendFiles(targetId, enrichedText, files, originalMsgId);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (enrichedText.trim()) {
|
|
119
|
+
emitter.sendReply(targetId, enrichedText, originalMsgId);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
};
|
|
124
|
+
// ════════════════════════════════════════════════════════════
|
|
125
|
+
// dispatcher — 对齐 kimi-claw 中的 R.dispatcher
|
|
126
|
+
// ════════════════════════════════════════════════════════════
|
|
127
|
+
const dispatcher = {
|
|
128
|
+
/**
|
|
129
|
+
* sendToolResult — 工具调用结果
|
|
130
|
+
*
|
|
131
|
+
* 参考 kimi-claw: 发送 tool_call_update session update
|
|
132
|
+
* lightclaw 场景下,工具结果通常由框架内部消费,不需要额外推送给用户
|
|
133
|
+
*/
|
|
134
|
+
sendToolResult: (payload) => {
|
|
135
|
+
counts.tool++;
|
|
136
|
+
log?.info(`[${CHANNEL_KEY}] [stream] sendToolResult: textLen=${payload.text?.length ?? 0} isError=${payload.isError ?? false}`);
|
|
137
|
+
return true;
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* sendBlockReply — 块回复
|
|
141
|
+
*
|
|
142
|
+
* 参考 kimi-claw: sendBlockReply 不处理,因为已通过 onPartialReply 实时推送
|
|
143
|
+
* 这是关键差异:旧模式中 deliver(block) 是主要输出渠道,
|
|
144
|
+
* 新模式中 onPartialReply 才是主输出渠道,sendBlockReply 可以忽略。
|
|
145
|
+
*/
|
|
146
|
+
sendBlockReply: (_payload) => {
|
|
147
|
+
counts.block++;
|
|
148
|
+
log?.debug?.(`[${CHANNEL_KEY}] [stream] sendBlockReply: ignored (already streamed via onPartialReply)`);
|
|
149
|
+
return true;
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* sendFinalReply — 最终回复
|
|
153
|
+
*
|
|
154
|
+
* 参考 kimi-claw sendFinalReply 的去重逻辑:
|
|
155
|
+
* 1. 如果最终文本与已流式推送的文本完全一致 → 不再发送
|
|
156
|
+
* 2. 如果最终文本以已推送文本为前缀 → 只发送增量部分
|
|
157
|
+
* 3. 如果完全不同 → 发送完整文本
|
|
158
|
+
*/
|
|
159
|
+
sendFinalReply: (payload) => {
|
|
160
|
+
counts.final++;
|
|
161
|
+
const replyText = payload.text ?? "";
|
|
162
|
+
const mediaList = payload.mediaUrls?.length
|
|
163
|
+
? payload.mediaUrls
|
|
164
|
+
: payload.mediaUrl ? [payload.mediaUrl] : [];
|
|
165
|
+
log?.info(`[${CHANNEL_KEY}] [stream] sendFinalReply: textLen=${replyText.length} mediaCount=${mediaList.length} streamedLen=${streamedText.length}`);
|
|
166
|
+
// 处理媒体文件(异步,但 dispatcher 是同步返回的)
|
|
167
|
+
if (mediaList.length > 0) {
|
|
168
|
+
handleMediaFinal(replyText, mediaList).catch((err) => {
|
|
169
|
+
log?.error(`[${CHANNEL_KEY}] [stream] sendFinalReply media handling error: ${err}`);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// 文本去重逻辑 — 参考 kimi-claw sendFinalReply
|
|
173
|
+
if (replyText.length > 0) {
|
|
174
|
+
if (streamedText) {
|
|
175
|
+
if (replyText === streamedText) {
|
|
176
|
+
// 完全一致,无需再发
|
|
177
|
+
log?.debug?.(`[${CHANNEL_KEY}] [stream] Final text identical to streamed, skipping`);
|
|
178
|
+
}
|
|
179
|
+
else if (replyText.startsWith(streamedText)) {
|
|
180
|
+
// 最终文本以已推送文本为前缀,只发送剩余增量
|
|
181
|
+
const remaining = replyText.slice(streamedText.length);
|
|
182
|
+
if (remaining.length > 0) {
|
|
183
|
+
streamedText += remaining;
|
|
184
|
+
partialReplyState.latest = replyText;
|
|
185
|
+
emitSignal(signalCtx, "stream_chunk", remaining);
|
|
186
|
+
log?.debug?.(`[${CHANNEL_KEY}] [stream] Final delta: ${remaining.length} chars`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// 最终文本与流式文本不匹配,发送完整回复
|
|
191
|
+
log?.warn(`[${CHANNEL_KEY}] [stream] Final text mismatch with streamed text, sending full reply`);
|
|
192
|
+
emitter.sendReply(targetId, replyText, originalMsgId);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// 没有流式推送过(可能 onPartialReply 未触发),直接发送完整回复
|
|
197
|
+
emitter.sendReply(targetId, replyText, originalMsgId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
},
|
|
202
|
+
/**
|
|
203
|
+
* waitForIdle — 等待空闲
|
|
204
|
+
* 参考 kimi-claw: async () => {}
|
|
205
|
+
*/
|
|
206
|
+
waitForIdle: async () => { },
|
|
207
|
+
/**
|
|
208
|
+
* getQueuedCounts — 获取排队计数
|
|
209
|
+
* 参考 kimi-claw: () => ({ ...w })
|
|
210
|
+
*/
|
|
211
|
+
getQueuedCounts: () => ({ ...counts }),
|
|
212
|
+
/**
|
|
213
|
+
* markComplete — 标记完成
|
|
214
|
+
* 参考 kimi-claw: () => {}
|
|
215
|
+
*/
|
|
216
|
+
markComplete: () => {
|
|
217
|
+
sendTypingStop();
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
// ════════════════════════════════════════════════════════════
|
|
221
|
+
// replyOptions — 对齐 kimi-claw 中的 E (replyOptions)
|
|
222
|
+
// ════════════════════════════════════════════════════════════
|
|
223
|
+
const replyOptions = {
|
|
224
|
+
// ── 来自 prefixOptions 的配置(responsePrefix、onModelSelected 等) ──
|
|
225
|
+
...prefixOptions,
|
|
226
|
+
/**
|
|
227
|
+
* onPartialReply — 核心!每个 token 增量回调
|
|
228
|
+
*
|
|
229
|
+
* 参考 kimi-claw:
|
|
230
|
+
* onPartialReply: (e) => {
|
|
231
|
+
* const o = toStreamDeltaText(I, e.text);
|
|
232
|
+
* o && (y += o, t.sendSessionUpdate(r, { sessionUpdate: "agent_message_chunk", content: { type: "text", text: o } }, n));
|
|
233
|
+
* }
|
|
234
|
+
*
|
|
235
|
+
* 这是实现真流式的关键:AI 引擎每产出一个 token 就触发此回调。
|
|
236
|
+
*/
|
|
237
|
+
onPartialReply: (payload) => {
|
|
238
|
+
if (!payload.text)
|
|
239
|
+
return;
|
|
240
|
+
ensureTypingStart();
|
|
241
|
+
const delta = toStreamDeltaText(partialReplyState, payload.text);
|
|
242
|
+
if (delta && delta.length > 0) {
|
|
243
|
+
streamedText += delta;
|
|
244
|
+
emitSignal(signalCtx, "stream_chunk", delta);
|
|
245
|
+
log?.debug?.(`[${CHANNEL_KEY}] [stream] onPartialReply delta: ${delta.length} chars`);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
/**
|
|
249
|
+
* onReasoningStream — 思考过程增量回调
|
|
250
|
+
*
|
|
251
|
+
* 参考 kimi-claw:
|
|
252
|
+
* onReasoningStream: (e) => {
|
|
253
|
+
* const o = toStreamDeltaText(_, e.text);
|
|
254
|
+
* o && t.sendSessionUpdate(r, { sessionUpdate: "agent_thought_chunk", content: { type: "text", text: o } }, n);
|
|
255
|
+
* }
|
|
256
|
+
*/
|
|
257
|
+
onReasoningStream: (payload) => {
|
|
258
|
+
if (!payload.text)
|
|
259
|
+
return;
|
|
260
|
+
ensureTypingStart();
|
|
261
|
+
const delta = toStreamDeltaText(reasoningState, payload.text);
|
|
262
|
+
if (delta && delta.length > 0) {
|
|
263
|
+
// 推送思考过程增量(使用 stream_chunk + 特殊前缀,或直接忽略)
|
|
264
|
+
// lightclaw 客户端目前不支持独立的 reasoning 通道,暂不推送
|
|
265
|
+
log?.debug?.(`[${CHANNEL_KEY}] [stream] onReasoningStream delta: ${delta.length} chars`);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
/**
|
|
269
|
+
* onToolStart — 工具调用开始
|
|
270
|
+
*
|
|
271
|
+
* 参考 kimi-claw: 发送 tool_call session update
|
|
272
|
+
*/
|
|
273
|
+
onToolStart: (payload) => {
|
|
274
|
+
log?.info(`[${CHANNEL_KEY}] [stream] onToolStart: name=${payload.name ?? "unknown"} phase=${payload.phase ?? ""}`);
|
|
275
|
+
},
|
|
276
|
+
/**
|
|
277
|
+
* onReplyStart — AI 开始生成回复
|
|
278
|
+
*/
|
|
279
|
+
onReplyStart: () => {
|
|
280
|
+
ensureTypingStart();
|
|
281
|
+
},
|
|
282
|
+
/**
|
|
283
|
+
* onAgentRunStart — Agent 运行开始
|
|
284
|
+
*/
|
|
285
|
+
onAgentRunStart: (runId) => {
|
|
286
|
+
log?.info(`[${CHANNEL_KEY}] [stream] Agent run started: runId=${runId}`);
|
|
287
|
+
},
|
|
288
|
+
// 禁用 block streaming(我们通过 onPartialReply 直接处理)
|
|
289
|
+
disableBlockStreaming: false,
|
|
290
|
+
};
|
|
291
|
+
return { dispatcher, replyOptions };
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=stream-reply-sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-reply-sink.js","sourceRoot":"","sources":["../../../src/streaming/stream-reply-sink.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightClaw — 流式输出类型定义
|
|
3
|
+
*
|
|
4
|
+
* 参考 kimi-claw 的 ImSendMessageStream 和 ACP handlers 中的流式回复模式。
|
|
5
|
+
*/
|
|
6
|
+
import type { SocketEmitter } from "../types.js";
|
|
7
|
+
/** 流式输出的消息子类型 */
|
|
8
|
+
export type StreamFrameKind = "typing_start" | "stream_chunk" | "reasoning_chunk" | "tool_start" | "tool_result" | "stream_end" | "typing_stop";
|
|
9
|
+
/**
|
|
10
|
+
* 增量追踪器内部状态。
|
|
11
|
+
*
|
|
12
|
+
* 参考 kimi-claw `toStreamDeltaText` 中的 `{ latest, mode, lastEmitted, localSnapshot }` 结构。
|
|
13
|
+
* 用于将 AI 引擎输出的文本(可能是快照/增量混合模式)转换为纯增量。
|
|
14
|
+
*/
|
|
15
|
+
export interface DeltaTrackerState {
|
|
16
|
+
/** 目前已知的最新完整文本(快照) */
|
|
17
|
+
latest: string;
|
|
18
|
+
/** 当前检测到的模式:snapshot = AI 输出全量快照;delta = AI 输出增量 */
|
|
19
|
+
mode: "snapshot" | "delta" | "unknown";
|
|
20
|
+
/** 上一次实际发出的增量文本 */
|
|
21
|
+
lastEmitted: string;
|
|
22
|
+
/** 用于 delta 模式下追踪本地快照 */
|
|
23
|
+
localSnapshot: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** 创建 StreamReplySink 所需的参数 */
|
|
26
|
+
export interface StreamReplySinkOptions {
|
|
27
|
+
/** Socket 事件发射器(复用 inbound 中的 emitter) */
|
|
28
|
+
emitter: SocketEmitter;
|
|
29
|
+
/** 发送目标用户 ID */
|
|
30
|
+
targetId: string;
|
|
31
|
+
/** 回复消息 ID(整个流共用同一个) */
|
|
32
|
+
replyMsgId: string;
|
|
33
|
+
/** 原始用户消息 ID(用于 replyToMsgId) */
|
|
34
|
+
originalMsgId: string;
|
|
35
|
+
/** 日志器 */
|
|
36
|
+
log?: {
|
|
37
|
+
info: (msg: string) => void;
|
|
38
|
+
warn: (msg: string) => void;
|
|
39
|
+
error: (msg: string) => void;
|
|
40
|
+
debug?: (msg: string) => void;
|
|
41
|
+
};
|
|
42
|
+
/** 当前有效的 API Key(用于 COS 上传) */
|
|
43
|
+
effectiveApiKey: string;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/streaming/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,iBAAiB;AACjB,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,cAAc,GACd,iBAAiB,GACjB,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,aAAa,CAAC;AAMlB;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;IACvC,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAMD,+BAA+B;AAC/B,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU;IACV,GAAG,CAAC,EAAE;QACJ,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC7B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;KAC/B,CAAC;IACF,+BAA+B;IAC/B,eAAe,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/streaming/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/package.json
CHANGED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# ========== lightclawbot 插件前置检查脚本 ==========
|
|
4
|
-
# 用途:快速判断 lightclawbot 插件是否可用
|
|
5
|
-
# 输出:最后一行固定为 RESULT:{...} JSON,供前端解析
|
|
6
|
-
# - 可用: RESULT:{"status":"ok","version":"x.y.z"}
|
|
7
|
-
# - 缺 openclaw: RESULT:{"status":"need_openclaw","reason":"..."}
|
|
8
|
-
# - 缺 lightclawbot:RESULT:{"status":"need_install","reason":"..."}
|
|
9
|
-
# - 缺 apiKey: RESULT:{"status":"need_apikey","reason":"apikeys_empty"}
|
|
10
|
-
#
|
|
11
|
-
# 使用方式:
|
|
12
|
-
# 本地执行: bash scripts/preflight.sh
|
|
13
|
-
# CDN 执行: bash <(curl -fsSL https://your-cdn.com/preflight.sh)
|
|
14
|
-
|
|
15
|
-
id="lightclawbot"
|
|
16
|
-
cfg="$HOME/.openclaw/openclaw.json"
|
|
17
|
-
plugin_dir="$HOME/.openclaw/extensions/${id}"
|
|
18
|
-
|
|
19
|
-
# ---------- 1. 检查 openclaw 是否可用 ----------
|
|
20
|
-
|
|
21
|
-
# 加载 nvm 环境(openclaw 可能依赖 nvm 管理的 node)
|
|
22
|
-
export NVM_DIR="$HOME/.nvm"
|
|
23
|
-
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
24
|
-
|
|
25
|
-
if ! command -v openclaw &>/dev/null; then
|
|
26
|
-
echo "RESULT:{\"status\":\"need_openclaw\",\"reason\":\"openclaw_not_found\"}"
|
|
27
|
-
exit 1
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
# 验证 openclaw 可执行
|
|
31
|
-
# 已知输出格式:
|
|
32
|
-
# - OpenClaw 2026.3.8 (3caab92) (新版本,带前缀 + commit hash)
|
|
33
|
-
# - OpenClaw 2026.3.2 (带前缀,无 commit hash)
|
|
34
|
-
# - 2026.3.2 (旧版本,直接输出版本号,无前缀)
|
|
35
|
-
openclaw_output=$(openclaw -v 2>/dev/null || true)
|
|
36
|
-
if ! echo "$openclaw_output" | grep -qiE '(^OpenClaw[/ ])?[0-9]+\.[0-9]+'; then
|
|
37
|
-
echo "RESULT:{\"status\":\"need_openclaw\",\"reason\":\"openclaw_not_working\"}"
|
|
38
|
-
exit 1
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
# ---------- 2. 检查配置文件是否存在 ----------
|
|
42
|
-
|
|
43
|
-
if [ ! -f "$cfg" ]; then
|
|
44
|
-
echo "RESULT:{\"status\":\"need_install\",\"reason\":\"config_not_found\"}"
|
|
45
|
-
exit 1
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
# ---------- 3. 检查插件是否已安装(目录 + package.json) ----------
|
|
49
|
-
|
|
50
|
-
if [ ! -d "$plugin_dir" ] || [ ! -f "$plugin_dir/package.json" ]; then
|
|
51
|
-
echo "RESULT:{\"status\":\"need_install\",\"reason\":\"plugin_not_installed\"}"
|
|
52
|
-
exit 1
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# ---------- 4. 检查配置文件中插件注册是否完整 ----------
|
|
56
|
-
|
|
57
|
-
# 需要 jq 来解析 JSON
|
|
58
|
-
if ! command -v jq &>/dev/null; then
|
|
59
|
-
echo "RESULT:{\"status\":\"need_install\",\"reason\":\"jq_not_found\"}"
|
|
60
|
-
exit 1
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# 4a. plugins.entries 中是否存在且 enabled
|
|
64
|
-
plugin_enabled=$(jq -r ".plugins.entries.\"${id}\".enabled // false" "$cfg" 2>/dev/null)
|
|
65
|
-
if [ "$plugin_enabled" != "true" ]; then
|
|
66
|
-
echo "RESULT:{\"status\":\"need_install\",\"reason\":\"plugin_not_enabled\"}"
|
|
67
|
-
exit 1
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
# 4b. plugins.installs 中是否有安装记录
|
|
71
|
-
if ! jq -e ".plugins.installs.\"${id}\"" "$cfg" > /dev/null 2>&1; then
|
|
72
|
-
echo "RESULT:{\"status\":\"need_install\",\"reason\":\"plugin_install_record_missing\"}"
|
|
73
|
-
exit 1
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
# 4c. channels 中是否配置且 enabled
|
|
77
|
-
channel_enabled=$(jq -r ".channels.\"${id}\".enabled // false" "$cfg" 2>/dev/null)
|
|
78
|
-
if [ "$channel_enabled" != "true" ]; then
|
|
79
|
-
echo "RESULT:{\"status\":\"need_install\",\"reason\":\"channel_not_enabled\"}"
|
|
80
|
-
exit 1
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
# 4d. channels 中 apiKeys 是否配置且非空
|
|
84
|
-
apikeys_count=$(jq -r ".channels.\"${id}\".apiKeys // [] | length" "$cfg" 2>/dev/null)
|
|
85
|
-
if [ "$apikeys_count" = "0" ] || [ -z "$apikeys_count" ]; then
|
|
86
|
-
echo "RESULT:{\"status\":\"need_install\",\"reason\":\"apikeys_empty\"}"
|
|
87
|
-
exit 1
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# ---------- 5. 全部通过,插件可用 ----------
|
|
91
|
-
|
|
92
|
-
version=$(jq -r '.version // "unknown"' "$plugin_dir/package.json")
|
|
93
|
-
echo "RESULT:{\"status\":\"ok\",\"version\":\"${version}\"}"
|
|
94
|
-
exit 0
|