mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
0e792b6de3
* refactor(channels): centralize inbound turn orchestration * refactor(runtime): remove stale compatibility paths * chore(guards): reject internal deprecated API use * refactor(channels): simplify core turn planning * chore(guards): keep deprecated checks boundary-focused * refactor(memory): keep modern config off compat barrel * fix(msteams): preserve feedback learning * test(channels): align modern inbound fixtures * refactor(channels): finish modern inbound migration * refactor(channels): tighten core inbound kernel * fix(channels): preserve turn assembly narrowing * test(sdk): keep runtime mock binding immutable * test(matrix): isolate read policy runtime * test(msteams): mock canonical reply factory * test(slack): mock core inbound turn dispatch * test(telegram): inject core session recorder * test(signal): inject core session recorder * test(googlechat): assert canonical inbound routing * test(synology-chat): align core turn fixture * fix(sdk): preserve direct DM runtime compat * refactor(channels): own inbound envelope compat in core * refactor(channels): trim inbound dispatch seams * refactor(channels): remove redundant async wrappers * test(synology-chat): type canonical dispatcher mock * refactor(channels): remove remaining dead compat seams * chore(sdk): refresh API baseline after rebase * fix(channels): preserve direct DM identity metadata
111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
// Feishu plugin module implements comment dispatcher behavior.
|
|
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
import { createReplyDispatcherWithTyping } from "openclaw/plugin-sdk/reply-runtime";
|
|
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
|
import { createFeishuClient } from "./client.js";
|
|
import {
|
|
createReplyPrefixContext,
|
|
type ClawdbotConfig,
|
|
type ReplyPayload,
|
|
type RuntimeEnv,
|
|
} from "./comment-dispatcher-runtime-api.js";
|
|
import { createCommentTypingReactionLifecycle } from "./comment-reaction.js";
|
|
import type { CommentFileType } from "./comment-target.js";
|
|
import { deliverCommentThreadText } from "./drive.js";
|
|
import { getFeishuRuntime } from "./runtime.js";
|
|
|
|
type CreateFeishuCommentReplyDispatcherParams = {
|
|
cfg: ClawdbotConfig;
|
|
agentId: string;
|
|
runtime: RuntimeEnv;
|
|
accountId?: string;
|
|
fileToken: string;
|
|
fileType: CommentFileType;
|
|
commentId: string;
|
|
replyId?: string;
|
|
isWholeComment?: boolean;
|
|
};
|
|
|
|
export function createFeishuCommentReplyDispatcher(
|
|
params: CreateFeishuCommentReplyDispatcherParams,
|
|
) {
|
|
const core = getFeishuRuntime();
|
|
const prefixContext = createReplyPrefixContext({
|
|
cfg: params.cfg,
|
|
agentId: params.agentId,
|
|
channel: "feishu",
|
|
accountId: params.accountId,
|
|
});
|
|
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
const client = createFeishuClient(account);
|
|
const textChunkLimit = core.channel.text.resolveTextChunkLimit(
|
|
params.cfg,
|
|
"feishu",
|
|
params.accountId,
|
|
{
|
|
fallbackLimit: 4000,
|
|
},
|
|
);
|
|
const chunkMode = core.channel.text.resolveChunkMode(params.cfg, "feishu", params.accountId);
|
|
const typingReaction = createCommentTypingReactionLifecycle({
|
|
cfg: params.cfg,
|
|
fileToken: params.fileToken,
|
|
fileType: params.fileType,
|
|
replyId: params.replyId,
|
|
accountId: params.accountId,
|
|
runtime: params.runtime,
|
|
});
|
|
|
|
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } =
|
|
createReplyDispatcherWithTyping({
|
|
responsePrefix: prefixContext.responsePrefix,
|
|
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
humanDelay: resolveHumanDelayConfig(params.cfg, params.agentId),
|
|
onReplyStart: async () => {
|
|
await typingReaction.start();
|
|
},
|
|
deliver: async (payload: ReplyPayload, info) => {
|
|
if (info.kind !== "final") {
|
|
return;
|
|
}
|
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
if (!reply.hasText) {
|
|
if (reply.hasMedia) {
|
|
params.runtime.log?.(
|
|
`feishu[${params.accountId ?? "default"}]: comment reply ignored media-only payload for comment=${params.commentId}`,
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
const chunks = core.channel.text.chunkTextWithMode(reply.text, textChunkLimit, chunkMode);
|
|
for (const chunk of chunks) {
|
|
await deliverCommentThreadText(client, {
|
|
file_token: params.fileToken,
|
|
file_type: params.fileType,
|
|
comment_id: params.commentId,
|
|
content: chunk,
|
|
is_whole_comment: params.isWholeComment,
|
|
});
|
|
}
|
|
},
|
|
onError: (err, info) => {
|
|
params.runtime.error?.(
|
|
`feishu[${params.accountId ?? "default"}]: comment dispatcher failed kind=${info.kind} comment=${params.commentId}: ${String(err)}`,
|
|
);
|
|
},
|
|
onCleanup: () => {
|
|
void typingReaction.cleanup();
|
|
},
|
|
});
|
|
|
|
return {
|
|
dispatcher,
|
|
replyOptions,
|
|
markDispatchIdle,
|
|
markRunComplete,
|
|
startTypingReaction: typingReaction.start,
|
|
cleanupTypingReaction: typingReaction.cleanup,
|
|
};
|
|
}
|