mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 21:10:42 +00:00
* fix(feishu): use msg_type media for mp4 video (fixes #33674) * Feishu: harden streaming merge semantics and final reply dedupe Use explicit streaming update semantics in the Feishu reply dispatcher: treat onPartialReply payloads as snapshot updates and block fallback payloads as delta chunks, then merge final text with the shared overlap-aware mergeStreamingText helper before closing the stream. Prevent duplicate final text delivery within the same dispatch cycle, and add regression tests covering overlap snapshot merge, duplicate final suppression, and block-as-delta behavior to guard against repeated/truncated output. * fix(feishu): prefer message.reply for streaming cards in topic threads * fix: reduce Feishu streaming card print_step to avoid duplicate rendering Fixes openclaw/openclaw#33751 * Feishu: preserve media sends on duplicate finals and add media synthesis changelog * Feishu: only dedupe exact duplicate final replies * Feishu: use scoped plugin-sdk import in streaming-card tests --------- Co-authored-by: 倪汉杰0668001185 <ni.hanjie@xydigit.com> Co-authored-by: zhengquanliu <zhengquanliu@bytedance.com> Co-authored-by: nick <nickzj@qq.com> Co-authored-by: linhey <linhey@mini.local> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -94,7 +94,25 @@ export function mergeStreamingText(
|
||||
if (!next) {
|
||||
return previous;
|
||||
}
|
||||
if (!previous || next === previous || next.includes(previous)) {
|
||||
if (!previous || next === previous) {
|
||||
return next;
|
||||
}
|
||||
if (next.startsWith(previous)) {
|
||||
return next;
|
||||
}
|
||||
if (previous.startsWith(next)) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
// Merge partial overlaps, e.g. "这" + "这是" => "这是".
|
||||
const maxOverlap = Math.min(previous.length, next.length);
|
||||
for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
|
||||
if (previous.slice(-overlap) === next.slice(0, overlap)) {
|
||||
return `${previous}${next.slice(overlap)}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (next.includes(previous)) {
|
||||
return next;
|
||||
}
|
||||
if (previous.includes(next)) {
|
||||
@@ -142,7 +160,7 @@ export class FeishuStreamingSession {
|
||||
config: {
|
||||
streaming_mode: true,
|
||||
summary: { content: "[Generating...]" },
|
||||
streaming_config: { print_frequency_ms: { default: 50 }, print_step: { default: 2 } },
|
||||
streaming_config: { print_frequency_ms: { default: 50 }, print_step: { default: 1 } },
|
||||
},
|
||||
body: {
|
||||
elements: [{ tag: "markdown", content: "⏳ Thinking...", element_id: "content" }],
|
||||
@@ -181,20 +199,12 @@ export class FeishuStreamingSession {
|
||||
const cardId = createData.data.card_id;
|
||||
const cardContent = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
||||
|
||||
// Topic-group replies require root_id routing. Prefer create+root_id when available.
|
||||
// Prefer message.reply when we have a reply target — reply_in_thread
|
||||
// reliably routes streaming cards into Feishu topics, whereas
|
||||
// message.create with root_id may silently ignore root_id for card
|
||||
// references (card_id format).
|
||||
let sendRes;
|
||||
if (options?.rootId) {
|
||||
const createData = {
|
||||
receive_id: receiveId,
|
||||
msg_type: "interactive",
|
||||
content: cardContent,
|
||||
root_id: options.rootId,
|
||||
};
|
||||
sendRes = await this.client.im.message.create({
|
||||
params: { receive_id_type: receiveIdType },
|
||||
data: createData,
|
||||
});
|
||||
} else if (options?.replyToMessageId) {
|
||||
if (options?.replyToMessageId) {
|
||||
sendRes = await this.client.im.message.reply({
|
||||
path: { message_id: options.replyToMessageId },
|
||||
data: {
|
||||
@@ -203,6 +213,15 @@ export class FeishuStreamingSession {
|
||||
...(options.replyInThread ? { reply_in_thread: true } : {}),
|
||||
},
|
||||
});
|
||||
} else if (options?.rootId) {
|
||||
// root_id is undeclared in the SDK types but accepted at runtime
|
||||
sendRes = await this.client.im.message.create({
|
||||
params: { receive_id_type: receiveIdType },
|
||||
data: Object.assign(
|
||||
{ receive_id: receiveId, msg_type: "interactive", content: cardContent },
|
||||
{ root_id: options.rootId },
|
||||
),
|
||||
});
|
||||
} else {
|
||||
sendRes = await this.client.im.message.create({
|
||||
params: { receive_id_type: receiveIdType },
|
||||
|
||||
Reference in New Issue
Block a user