mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 05:10:23 +00:00
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import { resolveExternalBestEffortDeliveryTarget } from "../infra/outbound/best-effort-delivery.js";
|
|
import { sendMessage } from "../infra/outbound/message.js";
|
|
import { parseAgentSessionKey } from "../routing/session-key.js";
|
|
import { isGatewayMessageChannel, normalizeMessageChannel } from "../utils/message-channel.js";
|
|
import { callGatewayTool } from "./tools/gateway.js";
|
|
|
|
type ExecApprovalFollowupParams = {
|
|
approvalId: string;
|
|
sessionKey?: string;
|
|
turnSourceChannel?: string;
|
|
turnSourceTo?: string;
|
|
turnSourceAccountId?: string;
|
|
turnSourceThreadId?: string | number;
|
|
resultText: string;
|
|
};
|
|
|
|
function buildExecDeniedFollowupPrompt(resultText: string): string {
|
|
return [
|
|
"An async command did not run.",
|
|
"Do not run the command again.",
|
|
"There is no new command output.",
|
|
"Do not mention, summarize, or reuse output from any earlier run in this session.",
|
|
"",
|
|
"Exact completion details:",
|
|
resultText.trim(),
|
|
"",
|
|
"Reply to the user in a helpful way.",
|
|
"Explain that the command did not run and why.",
|
|
"Do not claim there is new command output.",
|
|
].join("\n");
|
|
}
|
|
|
|
export function buildExecApprovalFollowupPrompt(resultText: string): string {
|
|
const trimmed = resultText.trim();
|
|
if (trimmed.startsWith("Exec denied (")) {
|
|
return buildExecDeniedFollowupPrompt(trimmed);
|
|
}
|
|
return [
|
|
"An async command the user already approved has completed.",
|
|
"Do not run the command again.",
|
|
"",
|
|
"Exact completion details:",
|
|
trimmed,
|
|
"",
|
|
"Reply to the user in a helpful way.",
|
|
"If it succeeded, share the relevant output.",
|
|
"If it failed, explain what went wrong.",
|
|
].join("\n");
|
|
}
|
|
|
|
export async function sendExecApprovalFollowup(
|
|
params: ExecApprovalFollowupParams,
|
|
): Promise<boolean> {
|
|
const sessionKey = params.sessionKey?.trim();
|
|
const resultText = params.resultText.trim();
|
|
if (!resultText) {
|
|
return false;
|
|
}
|
|
|
|
const deliveryTarget = resolveExternalBestEffortDeliveryTarget({
|
|
channel: params.turnSourceChannel,
|
|
to: params.turnSourceTo,
|
|
accountId: params.turnSourceAccountId,
|
|
threadId: params.turnSourceThreadId,
|
|
});
|
|
const normalizedTurnSourceChannel = normalizeMessageChannel(params.turnSourceChannel);
|
|
const sessionOnlyOriginChannel =
|
|
normalizedTurnSourceChannel && isGatewayMessageChannel(normalizedTurnSourceChannel)
|
|
? normalizedTurnSourceChannel
|
|
: undefined;
|
|
|
|
if (deliveryTarget.deliver) {
|
|
const requesterAgentId = sessionKey ? parseAgentSessionKey(sessionKey)?.agentId : undefined;
|
|
await sendMessage({
|
|
channel: deliveryTarget.channel,
|
|
to: deliveryTarget.to ?? "",
|
|
accountId: deliveryTarget.accountId,
|
|
threadId: deliveryTarget.threadId,
|
|
content: resultText,
|
|
agentId: requesterAgentId,
|
|
idempotencyKey: `exec-approval-followup:${params.approvalId}`,
|
|
mirror: sessionKey
|
|
? {
|
|
sessionKey,
|
|
agentId: requesterAgentId,
|
|
idempotencyKey: `exec-approval-followup:${params.approvalId}`,
|
|
}
|
|
: undefined,
|
|
});
|
|
return true;
|
|
}
|
|
|
|
if (!sessionKey) {
|
|
throw new Error("Session key or deliverable origin route is required");
|
|
}
|
|
|
|
await callGatewayTool(
|
|
"agent",
|
|
{ timeoutMs: 60_000 },
|
|
{
|
|
sessionKey,
|
|
message: buildExecApprovalFollowupPrompt(resultText),
|
|
deliver: deliveryTarget.deliver,
|
|
...(deliveryTarget.deliver ? { bestEffortDeliver: true as const } : {}),
|
|
channel: deliveryTarget.deliver ? deliveryTarget.channel : sessionOnlyOriginChannel,
|
|
to: deliveryTarget.deliver
|
|
? deliveryTarget.to
|
|
: sessionOnlyOriginChannel
|
|
? params.turnSourceTo
|
|
: undefined,
|
|
accountId: deliveryTarget.deliver
|
|
? deliveryTarget.accountId
|
|
: sessionOnlyOriginChannel
|
|
? params.turnSourceAccountId
|
|
: undefined,
|
|
threadId: deliveryTarget.deliver
|
|
? deliveryTarget.threadId
|
|
: sessionOnlyOriginChannel
|
|
? params.turnSourceThreadId
|
|
: undefined,
|
|
idempotencyKey: `exec-approval-followup:${params.approvalId}`,
|
|
},
|
|
{ expectFinal: true },
|
|
);
|
|
|
|
return true;
|
|
}
|