mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 09:33:06 +00:00
fix(exec): deliver approval followups directly to chat
This commit is contained in:
@@ -4,13 +4,19 @@ vi.mock("./tools/gateway.js", () => ({
|
||||
callGatewayTool: vi.fn(async () => ({ ok: true })),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/outbound/message.js", () => ({
|
||||
sendMessage: vi.fn(async () => ({ ok: true })),
|
||||
}));
|
||||
|
||||
let callGatewayTool: typeof import("./tools/gateway.js").callGatewayTool;
|
||||
let sendMessage: typeof import("../infra/outbound/message.js").sendMessage;
|
||||
let buildExecApprovalFollowupPrompt: typeof import("./bash-tools.exec-approval-followup.js").buildExecApprovalFollowupPrompt;
|
||||
let sendExecApprovalFollowup: typeof import("./bash-tools.exec-approval-followup.js").sendExecApprovalFollowup;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
({ callGatewayTool } = await import("./tools/gateway.js"));
|
||||
({ sendMessage } = await import("../infra/outbound/message.js"));
|
||||
({ buildExecApprovalFollowupPrompt, sendExecApprovalFollowup } =
|
||||
await import("./bash-tools.exec-approval-followup.js"));
|
||||
});
|
||||
@@ -48,32 +54,65 @@ describe("exec approval followup", () => {
|
||||
}),
|
||||
{ expectFinal: true },
|
||||
);
|
||||
expect(sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses external delivery when a deliverable route is available", async () => {
|
||||
await sendExecApprovalFollowup({
|
||||
approvalId: "req-2",
|
||||
it.each([
|
||||
{
|
||||
channel: "slack",
|
||||
sessionKey: "agent:main:slack:channel:C123",
|
||||
to: "channel:C123",
|
||||
accountId: "default",
|
||||
threadId: "1712419200.1234",
|
||||
},
|
||||
{
|
||||
channel: "discord",
|
||||
sessionKey: "agent:main:discord:channel:123",
|
||||
turnSourceChannel: "discord",
|
||||
turnSourceTo: "123",
|
||||
turnSourceAccountId: "default",
|
||||
turnSourceThreadId: "456",
|
||||
resultText: "Exec completed: echo ok",
|
||||
to: "123",
|
||||
accountId: "default",
|
||||
threadId: "456",
|
||||
},
|
||||
{
|
||||
channel: "telegram",
|
||||
sessionKey: "agent:main:telegram:-100123",
|
||||
to: "-100123",
|
||||
accountId: "default",
|
||||
threadId: "789",
|
||||
},
|
||||
])("uses direct external delivery for $channel followups", async (target) => {
|
||||
await sendExecApprovalFollowup({
|
||||
approvalId: `req-${target.channel}`,
|
||||
sessionKey: target.sessionKey,
|
||||
turnSourceChannel: target.channel,
|
||||
turnSourceTo: target.to,
|
||||
turnSourceAccountId: target.accountId,
|
||||
turnSourceThreadId: target.threadId,
|
||||
resultText: "slack exec approval smoke",
|
||||
});
|
||||
|
||||
expect(callGatewayTool).toHaveBeenCalledWith(
|
||||
"agent",
|
||||
expect.any(Object),
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: "agent:main:discord:channel:123",
|
||||
deliver: true,
|
||||
bestEffortDeliver: true,
|
||||
channel: "discord",
|
||||
to: "123",
|
||||
accountId: "default",
|
||||
threadId: "456",
|
||||
channel: target.channel,
|
||||
to: target.to,
|
||||
accountId: target.accountId,
|
||||
threadId: target.threadId,
|
||||
content: "slack exec approval smoke",
|
||||
mirror: expect.objectContaining({
|
||||
sessionKey: target.sessionKey,
|
||||
idempotencyKey: `exec-approval-followup:req-${target.channel}`,
|
||||
}),
|
||||
}),
|
||||
{ expectFinal: true },
|
||||
);
|
||||
expect(callGatewayTool).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws when neither a session nor a deliverable route is available", async () => {
|
||||
await expect(
|
||||
sendExecApprovalFollowup({
|
||||
approvalId: "req-missing",
|
||||
turnSourceChannel: "slack",
|
||||
resultText: "Exec completed: echo ok",
|
||||
}),
|
||||
).rejects.toThrow("Session key or deliverable origin route is required");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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";
|
||||
|
||||
@@ -51,7 +53,7 @@ export async function sendExecApprovalFollowup(
|
||||
): Promise<boolean> {
|
||||
const sessionKey = params.sessionKey?.trim();
|
||||
const resultText = params.resultText.trim();
|
||||
if (!sessionKey || !resultText) {
|
||||
if (!resultText) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -67,6 +69,31 @@ export async function sendExecApprovalFollowup(
|
||||
? 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 },
|
||||
|
||||
Reference in New Issue
Block a user