mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-15 23:24:03 -06:00
d7de67ae02
* feat(ask-user): follow-up harness slice * feat(ask-user): follow-up channels slice * feat(ask-user): follow-up native slice * feat(ui): dock question panel above composer with stepper and compact stream summaries * docs: refresh follow-up integration maps * test(ui): align terminal summary proof * fix(infra): echo declared option answers in terminal status when free-text is allowed * fix(infra): keep reaction answering when display labels are formatter-adjusted * fix(agents): settle plain-text claims only after question registration commits * fix(agents,apps): commit-ordered claim persistence, claim-aware prompt delivery, non-blocking question refresh * fix(harness,infra): reaction-appropriate question copy, caller presentations honored * fix(native,infra,agents): local-expiry eviction, value-addressed reactions, reserve-before-request * fix(infra,android): dual-mode question resolver for compact callbacks; reset terminal retention on replayed pending * fix(harness,discord): claim-aware prompt delivery in run helper; escape finalization labels * fix(macos): merge transient-content visibility with question cards after main sync * fix: repair ask user follow-up CI * test: update limited bootstrap scope expectation * fix: retain shared question card API * chore: refresh native i18n inventory
87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
// Covers Slack question delivery capture and Block Kit final edit.
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
const hoisted = vi.hoisted(() => ({
|
|
update: vi.fn(),
|
|
registration: undefined as
|
|
| { finalize: (statusLine: string) => void | Promise<void>; deliveryId: string }
|
|
| undefined,
|
|
}));
|
|
vi.mock("openclaw/plugin-sdk/question-gateway-runtime", async (importOriginal) => {
|
|
const original =
|
|
await importOriginal<typeof import("openclaw/plugin-sdk/question-gateway-runtime")>();
|
|
return {
|
|
...original,
|
|
questionGatewayRuntime: {
|
|
...original.questionGatewayRuntime,
|
|
registerChannelDelivery: (registration: typeof hoisted.registration) => {
|
|
hoisted.registration = registration;
|
|
},
|
|
},
|
|
};
|
|
});
|
|
vi.mock("./send.js", () => ({ updateMessageSlack: hoisted.update }));
|
|
|
|
import { slackOutbound } from "./outbound-adapter.js";
|
|
|
|
describe("Slack question finalization", () => {
|
|
it("removes action blocks and appends terminal context", async () => {
|
|
const questionId = "ask_0123456789abcdef0123456789abcdef";
|
|
const payload = {
|
|
channelData: { askUser: { questionId } },
|
|
presentation: {
|
|
blocks: [
|
|
{ type: "text" as const, text: "Pick one" },
|
|
{
|
|
type: "buttons" as const,
|
|
buttons: ["One", "Two"].map((label) => ({
|
|
label,
|
|
action: { type: "question" as const, questionId, optionValue: label },
|
|
})),
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const rendered = await slackOutbound.renderPresentation?.({
|
|
payload,
|
|
presentation: payload.presentation,
|
|
ctx: { cfg: {}, to: "C123", text: "Pick one", payload },
|
|
});
|
|
expect(rendered).not.toBeNull();
|
|
const slackData = rendered!.channelData?.slack as {
|
|
renderedPresentationSegments: unknown[];
|
|
};
|
|
slackData.renderedPresentationSegments.unshift({
|
|
kind: "text",
|
|
text: "Preface",
|
|
mrkdwn: false,
|
|
});
|
|
await slackOutbound.afterDeliverPayload?.({
|
|
cfg: {},
|
|
target: { channel: "slack", to: "C123", accountId: "default" },
|
|
payload: rendered!,
|
|
results: [
|
|
{ channel: "slack", messageId: "44", channelId: "C123" },
|
|
{ channel: "slack", messageId: "55", channelId: "C123" },
|
|
],
|
|
});
|
|
|
|
await hoisted.registration?.finalize("Answered: <!channel>");
|
|
expect(hoisted.update).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
channelId: "C123",
|
|
messageTs: "55",
|
|
text: expect.stringContaining("Answered: <!channel>"),
|
|
blocks: expect.arrayContaining([
|
|
{
|
|
type: "context",
|
|
elements: [{ type: "mrkdwn", text: "Answered: <!channel>" }],
|
|
},
|
|
]),
|
|
}),
|
|
);
|
|
const blocks = hoisted.update.mock.calls[0]?.[0]?.blocks as Array<{ type?: string }>;
|
|
expect(blocks.some((block) => block.type === "actions")).toBe(false);
|
|
});
|
|
});
|