Files
openclaw/extensions/feishu/src/comment-dispatcher.ts
T
Peter Steinberger 0e792b6de3 refactor(channels): centralize inbound orchestration and remove internal compat (#109716)
* 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
2026-07-17 00:56:46 -07:00

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,
};
}