fix: reply to Feishu trigger messages

This commit is contained in:
2026-07-09 20:04:13 +08:00
parent fc0df7b823
commit fc5a5365d8
8 changed files with 229 additions and 96 deletions
+27 -8
View File
@@ -25,6 +25,7 @@ import {
type CardActionEvent,
type FeishuRuntime,
type MessageReceiveEvent,
type SendMessageOptions,
} from "./client.js";
import { runAgent as defaultRunAgent, type ProjectContext, type RunRequest, type RunResult } from "../agent/runner.js";
import type { RuntimeSettings } from "../settings/runtime.js";
@@ -124,6 +125,7 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
options: { readonly queueIfLocked?: boolean } = {},
): Promise<StartAgentRunOutcome> {
const { msg, rt, chatId, projectId, senderOpenId, actor } = context;
const sendOptions = sendOptionsForTriggerMessage(msg);
const queueIfLocked = options.queueIfLocked ?? true;
// ADR-0002: if locked by another run, enqueue the trigger for this project.
@@ -169,7 +171,7 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
sender: await senderAuditMetadata(rt, senderOpenId),
},
});
await sendText(rt, chatId, `无权限使用角色 ${roleId}`);
await sendText(rt, chatId, `无权限使用角色 ${roleId}`, sendOptions);
return "skipped";
}
const role = models.role(roleId);
@@ -279,7 +281,14 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
// Streaming agent card: single interactive card through the full run
// lifecycle (thinking → tool calls → streaming text → complete).
// Shows tool-use trace panel + reasoning panel + answer text.
const card = new StreamingAgentCard({ runId: run.id, rt, chatId, patchIntervalMs: undefined, maxMessageLength: undefined });
const card = new StreamingAgentCard({
runId: run.id,
rt,
chatId,
sendOptions,
patchIntervalMs: undefined,
maxMessageLength: undefined,
});
const deliveredFiles: string[] = [];
const fileDeliveryMcpServer = createFileDeliveryMcpServer({
rt,
@@ -287,6 +296,7 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
projectId,
runId: run.id,
workspaceDir: project.workspaceDir,
sendOptions,
approvalManager,
onDelivered: (path) => {
deliveredFiles.push(path);
@@ -422,11 +432,16 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
});
if (position === 0) {
await reactToMessage(context.rt, context.msg.message_id, "OnIt");
await sendText(context.rt, context.chatId, "队列已满,请稍后再试");
await sendText(context.rt, context.chatId, "队列已满,请稍后再试", sendOptionsForTriggerMessage(context.msg));
return "rejected";
}
await sendText(context.rt, context.chatId, `已加入队列(第${position}位),当前处理完成后将自动开始`);
await sendText(
context.rt,
context.chatId,
`已加入队列(第${position}位),当前处理完成后将自动开始`,
sendOptionsForTriggerMessage(context.msg),
);
return "queued";
}
@@ -614,7 +629,7 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
if (senderOpenId === "") {
deps.logger.warn({ projectId, chatId }, "feishu trigger: sender has no open_id, denying");
await writeAudit(deps.prisma, { projectId, action: "trigger.denied", metadata: { reason: "missing open_id" } });
await sendText(rt, chatId, "无法识别发送者,拒绝触发。");
await sendText(rt, chatId, "无法识别发送者,拒绝触发。", sendOptionsForTriggerMessage(msg));
return;
}
const actor = { feishuOpenId: senderOpenId, chatId };
@@ -634,7 +649,7 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
sender: await senderAuditMetadata(rt, senderOpenId),
},
});
await sendText(rt, chatId, "无权限触发。");
await sendText(rt, chatId, "无权限触发。", sendOptionsForTriggerMessage(msg));
return;
}
@@ -689,14 +704,14 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
if (helpSubcommandTarget !== null) {
// Help is generated on demand because role/tool configuration is runtime data.
const models = await deps.settings.modelRegistry({ projectId });
await sendText(rt, chatId, formatSlashHelpTarget(helpSubcommandTarget, models, slashCommands));
await sendText(rt, chatId, formatSlashHelpTarget(helpSubcommandTarget, models, slashCommands), sendOptionsForTriggerMessage(msg));
return;
}
if (invocation !== null) {
const slashCommand = slashCommands.get(invocation.name);
if (slashCommand !== undefined) {
await slashCommand.run({ invocation, projectId, chatId, rt });
await slashCommand.run({ invocation, projectId, chatId, rt, sendOptions: sendOptionsForTriggerMessage(msg) });
return;
}
}
@@ -772,6 +787,10 @@ function nonEmpty(value: string | undefined): string | undefined {
return value === undefined || value === "" ? undefined : value;
}
function sendOptionsForTriggerMessage(msg: MessageReceiveEvent["message"]): SendMessageOptions {
return { replyToMessageId: msg.message_id };
}
function approvalActionFromValue(value: unknown): string | null {
if (typeof value === "string") {
try {