mirror of https://github.com/openclaw/openclaw.git
msteams: add message edit and delete support (#49925)
- Add edit/delete action handlers with toolContext.currentChannelId fallback for in-thread edits/deletes without explicit target - Add editMessageMSTeams/deleteMessageMSTeams to channel runtime - Add updateActivity/deleteActivity to SendContext and MSTeamsTurnContext - Extend content param with text/content/message fallback chain - Update test mocks for new SendContext shape Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9f5d286caf
commit
6e970010f7
|
|
@ -5,10 +5,14 @@ import {
|
|||
import { msteamsOutbound as msteamsOutboundImpl } from "./outbound.js";
|
||||
import { probeMSTeams as probeMSTeamsImpl } from "./probe.js";
|
||||
import {
|
||||
deleteMessageMSTeams as deleteMessageMSTeamsImpl,
|
||||
editMessageMSTeams as editMessageMSTeamsImpl,
|
||||
sendAdaptiveCardMSTeams as sendAdaptiveCardMSTeamsImpl,
|
||||
sendMessageMSTeams as sendMessageMSTeamsImpl,
|
||||
} from "./send.js";
|
||||
export const msTeamsChannelRuntime = {
|
||||
deleteMessageMSTeams: deleteMessageMSTeamsImpl,
|
||||
editMessageMSTeams: editMessageMSTeamsImpl,
|
||||
listMSTeamsDirectoryGroupsLive: listMSTeamsDirectoryGroupsLiveImpl,
|
||||
listMSTeamsDirectoryPeersLive: listMSTeamsDirectoryPeersLiveImpl,
|
||||
msteamsOutbound: { ...msteamsOutboundImpl },
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ function describeMSTeamsMessageTool({
|
|||
cfg.channels?.msteams?.enabled !== false &&
|
||||
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams));
|
||||
return {
|
||||
actions: enabled ? (["poll"] satisfies ChannelMessageActionName[]) : [],
|
||||
actions: enabled ? (["poll", "edit", "delete"] satisfies ChannelMessageActionName[]) : [],
|
||||
capabilities: enabled ? ["cards"] : [],
|
||||
schema: enabled
|
||||
? {
|
||||
|
|
@ -405,6 +405,106 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount, ProbeMSTeamsRe
|
|||
details: { ok: true, channel: "msteams", messageId: result.messageId },
|
||||
};
|
||||
}
|
||||
if (ctx.action === "edit") {
|
||||
const to =
|
||||
typeof ctx.params.to === "string"
|
||||
? ctx.params.to.trim()
|
||||
: typeof ctx.params.target === "string"
|
||||
? ctx.params.target.trim()
|
||||
: (ctx.toolContext?.currentChannelId?.trim() ?? "");
|
||||
const messageId =
|
||||
typeof ctx.params.messageId === "string" ? ctx.params.messageId.trim() : "";
|
||||
const content =
|
||||
typeof ctx.params.text === "string"
|
||||
? ctx.params.text
|
||||
: typeof ctx.params.content === "string"
|
||||
? ctx.params.content
|
||||
: typeof ctx.params.message === "string"
|
||||
? ctx.params.message
|
||||
: "";
|
||||
if (!to || !messageId) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "Edit requires a target (to) and messageId.",
|
||||
},
|
||||
],
|
||||
details: { error: "Edit requires a target (to) and messageId." },
|
||||
};
|
||||
}
|
||||
if (!content) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{ type: "text" as const, text: "Edit requires content." }],
|
||||
details: { error: "Edit requires content." },
|
||||
};
|
||||
}
|
||||
const { editMessageMSTeams } = await loadMSTeamsChannelRuntime();
|
||||
const result = await editMessageMSTeams({
|
||||
cfg: ctx.cfg,
|
||||
to,
|
||||
activityId: messageId,
|
||||
text: content,
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
ok: true,
|
||||
channel: "msteams",
|
||||
conversationId: result.conversationId,
|
||||
}),
|
||||
},
|
||||
],
|
||||
details: { ok: true, channel: "msteams" },
|
||||
};
|
||||
}
|
||||
|
||||
if (ctx.action === "delete") {
|
||||
const to =
|
||||
typeof ctx.params.to === "string"
|
||||
? ctx.params.to.trim()
|
||||
: typeof ctx.params.target === "string"
|
||||
? ctx.params.target.trim()
|
||||
: (ctx.toolContext?.currentChannelId?.trim() ?? "");
|
||||
const messageId =
|
||||
typeof ctx.params.messageId === "string" ? ctx.params.messageId.trim() : "";
|
||||
if (!to || !messageId) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "Delete requires a target (to) and messageId.",
|
||||
},
|
||||
],
|
||||
details: { error: "Delete requires a target (to) and messageId." },
|
||||
};
|
||||
}
|
||||
const { deleteMessageMSTeams } = await loadMSTeamsChannelRuntime();
|
||||
const result = await deleteMessageMSTeams({
|
||||
cfg: ctx.cfg,
|
||||
to,
|
||||
activityId: messageId,
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
ok: true,
|
||||
channel: "msteams",
|
||||
conversationId: result.conversationId,
|
||||
}),
|
||||
},
|
||||
],
|
||||
details: { ok: true, channel: "msteams" },
|
||||
};
|
||||
}
|
||||
|
||||
// Return null to fall through to default handler
|
||||
return null as never;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ const createFallbackAdapter = (proactiveSent: string[]): MSTeamsAdapter => ({
|
|||
continueConversation: async (_appId, _reference, logic) => {
|
||||
await logic({
|
||||
sendActivity: createRecordedSendActivity(proactiveSent),
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
});
|
||||
},
|
||||
process: async () => {},
|
||||
|
|
@ -175,6 +177,8 @@ describe("msteams messenger", () => {
|
|||
}
|
||||
throw new TypeError(REVOCATION_ERROR);
|
||||
},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +195,8 @@ describe("msteams messenger", () => {
|
|||
const sent: string[] = [];
|
||||
const ctx = {
|
||||
sendActivity: createRecordedSendActivity(sent),
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
};
|
||||
const adapter = createNoopAdapter();
|
||||
|
||||
|
|
@ -215,6 +221,8 @@ describe("msteams messenger", () => {
|
|||
seen.reference = reference;
|
||||
await logic({
|
||||
sendActivity: createRecordedSendActivity(seen.texts),
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
});
|
||||
},
|
||||
process: async () => {},
|
||||
|
|
@ -253,6 +261,8 @@ describe("msteams messenger", () => {
|
|||
sent.push(activity as { text?: string; entities?: unknown[] });
|
||||
return { id: "id:one" };
|
||||
},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
};
|
||||
|
||||
const adapter = createNoopAdapter();
|
||||
|
|
@ -304,6 +314,8 @@ describe("msteams messenger", () => {
|
|||
|
||||
const ctx = {
|
||||
sendActivity: createRecordedSendActivity(attempts, 429),
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
};
|
||||
const adapter = createNoopAdapter();
|
||||
|
||||
|
|
@ -328,6 +340,8 @@ describe("msteams messenger", () => {
|
|||
sendActivity: async () => {
|
||||
throw Object.assign(new Error("bad request"), { statusCode: 400 });
|
||||
},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
};
|
||||
|
||||
const adapter = createNoopAdapter();
|
||||
|
|
@ -389,7 +403,11 @@ describe("msteams messenger", () => {
|
|||
|
||||
const adapter: MSTeamsAdapter = {
|
||||
continueConversation: async (_appId, _reference, logic) => {
|
||||
await logic({ sendActivity: createRecordedSendActivity(attempts, 503) });
|
||||
await logic({
|
||||
sendActivity: createRecordedSendActivity(attempts, 503),
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
});
|
||||
},
|
||||
process: async () => {},
|
||||
updateActivity: noopUpdateActivity,
|
||||
|
|
@ -425,6 +443,8 @@ describe("msteams messenger", () => {
|
|||
batchTexts.push(text ?? "");
|
||||
return { id: `id:${text ?? ""}` };
|
||||
},
|
||||
updateActivity: noopUpdateActivity,
|
||||
deleteActivity: noopDeleteActivity,
|
||||
});
|
||||
conversationCallTexts.push(batchTexts);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ const FILE_CONSENT_THRESHOLD_BYTES = 4 * 1024 * 1024;
|
|||
|
||||
type SendContext = {
|
||||
sendActivity: (textOrActivity: string | object) => Promise<unknown>;
|
||||
updateActivity: (activity: object) => Promise<{ id?: string } | void>;
|
||||
deleteActivity: (activityId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export type MSTeamsConversationReference = {
|
||||
|
|
|
|||
|
|
@ -16,4 +16,6 @@ export type MSTeamsTurnContext = {
|
|||
sendActivities: (
|
||||
activities: Array<{ type: string } & Record<string, unknown>>,
|
||||
) => Promise<unknown>;
|
||||
updateActivity: (activity: object) => Promise<{ id?: string } | void>;
|
||||
deleteActivity: (activityId: string) => Promise<void>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { sendMessageMSTeams } from "./send.js";
|
||||
import { deleteMessageMSTeams, editMessageMSTeams, sendMessageMSTeams } from "./send.js";
|
||||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
loadOutboundMediaFromUrl: vi.fn(),
|
||||
|
|
@ -258,3 +258,197 @@ describe("sendMessageMSTeams", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("editMessageMSTeams", () => {
|
||||
beforeEach(() => {
|
||||
mockState.resolveMSTeamsSendContext.mockReset();
|
||||
});
|
||||
|
||||
it("calls continueConversation and updateActivity with correct params", async () => {
|
||||
const mockUpdateActivity = vi.fn();
|
||||
const mockContinueConversation = vi.fn(
|
||||
async (_appId: string, _ref: unknown, logic: (ctx: unknown) => Promise<void>) => {
|
||||
await logic({
|
||||
sendActivity: vi.fn(),
|
||||
updateActivity: mockUpdateActivity,
|
||||
deleteActivity: vi.fn(),
|
||||
});
|
||||
},
|
||||
);
|
||||
mockState.resolveMSTeamsSendContext.mockResolvedValue({
|
||||
adapter: { continueConversation: mockContinueConversation },
|
||||
appId: "app-id",
|
||||
conversationId: "19:conversation@thread.tacv2",
|
||||
ref: {
|
||||
user: { id: "user-1" },
|
||||
agent: { id: "agent-1" },
|
||||
conversation: { id: "19:conversation@thread.tacv2", conversationType: "personal" },
|
||||
channelId: "msteams",
|
||||
},
|
||||
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
conversationType: "personal",
|
||||
tokenProvider: {},
|
||||
});
|
||||
|
||||
const result = await editMessageMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
to: "conversation:19:conversation@thread.tacv2",
|
||||
activityId: "activity-123",
|
||||
text: "Updated message text",
|
||||
});
|
||||
|
||||
expect(result.conversationId).toBe("19:conversation@thread.tacv2");
|
||||
expect(mockContinueConversation).toHaveBeenCalledTimes(1);
|
||||
expect(mockContinueConversation).toHaveBeenCalledWith(
|
||||
"app-id",
|
||||
expect.objectContaining({ activityId: undefined }),
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockUpdateActivity).toHaveBeenCalledWith({
|
||||
type: "message",
|
||||
id: "activity-123",
|
||||
text: "Updated message text",
|
||||
});
|
||||
});
|
||||
|
||||
it("throws a descriptive error when continueConversation fails", async () => {
|
||||
const mockContinueConversation = vi.fn().mockRejectedValue(new Error("Service unavailable"));
|
||||
mockState.resolveMSTeamsSendContext.mockResolvedValue({
|
||||
adapter: { continueConversation: mockContinueConversation },
|
||||
appId: "app-id",
|
||||
conversationId: "19:conversation@thread.tacv2",
|
||||
ref: {
|
||||
user: { id: "user-1" },
|
||||
agent: { id: "agent-1" },
|
||||
conversation: { id: "19:conversation@thread.tacv2" },
|
||||
channelId: "msteams",
|
||||
},
|
||||
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
conversationType: "personal",
|
||||
tokenProvider: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
editMessageMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
to: "conversation:19:conversation@thread.tacv2",
|
||||
activityId: "activity-123",
|
||||
text: "Updated text",
|
||||
}),
|
||||
).rejects.toThrow("msteams edit failed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteMessageMSTeams", () => {
|
||||
beforeEach(() => {
|
||||
mockState.resolveMSTeamsSendContext.mockReset();
|
||||
});
|
||||
|
||||
it("calls continueConversation and deleteActivity with correct activityId", async () => {
|
||||
const mockDeleteActivity = vi.fn();
|
||||
const mockContinueConversation = vi.fn(
|
||||
async (_appId: string, _ref: unknown, logic: (ctx: unknown) => Promise<void>) => {
|
||||
await logic({
|
||||
sendActivity: vi.fn(),
|
||||
updateActivity: vi.fn(),
|
||||
deleteActivity: mockDeleteActivity,
|
||||
});
|
||||
},
|
||||
);
|
||||
mockState.resolveMSTeamsSendContext.mockResolvedValue({
|
||||
adapter: { continueConversation: mockContinueConversation },
|
||||
appId: "app-id",
|
||||
conversationId: "19:conversation@thread.tacv2",
|
||||
ref: {
|
||||
user: { id: "user-1" },
|
||||
agent: { id: "agent-1" },
|
||||
conversation: { id: "19:conversation@thread.tacv2", conversationType: "groupChat" },
|
||||
channelId: "msteams",
|
||||
},
|
||||
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
conversationType: "groupChat",
|
||||
tokenProvider: {},
|
||||
});
|
||||
|
||||
const result = await deleteMessageMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
to: "conversation:19:conversation@thread.tacv2",
|
||||
activityId: "activity-456",
|
||||
});
|
||||
|
||||
expect(result.conversationId).toBe("19:conversation@thread.tacv2");
|
||||
expect(mockContinueConversation).toHaveBeenCalledTimes(1);
|
||||
expect(mockContinueConversation).toHaveBeenCalledWith(
|
||||
"app-id",
|
||||
expect.objectContaining({ activityId: undefined }),
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockDeleteActivity).toHaveBeenCalledWith("activity-456");
|
||||
});
|
||||
|
||||
it("throws a descriptive error when continueConversation fails", async () => {
|
||||
const mockContinueConversation = vi.fn().mockRejectedValue(new Error("Not found"));
|
||||
mockState.resolveMSTeamsSendContext.mockResolvedValue({
|
||||
adapter: { continueConversation: mockContinueConversation },
|
||||
appId: "app-id",
|
||||
conversationId: "19:conversation@thread.tacv2",
|
||||
ref: {
|
||||
user: { id: "user-1" },
|
||||
agent: { id: "agent-1" },
|
||||
conversation: { id: "19:conversation@thread.tacv2" },
|
||||
channelId: "msteams",
|
||||
},
|
||||
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
conversationType: "personal",
|
||||
tokenProvider: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
deleteMessageMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
to: "conversation:19:conversation@thread.tacv2",
|
||||
activityId: "activity-456",
|
||||
}),
|
||||
).rejects.toThrow("msteams delete failed");
|
||||
});
|
||||
|
||||
it("passes the appId and proactive ref to continueConversation", async () => {
|
||||
const mockContinueConversation = vi.fn(
|
||||
async (_appId: string, _ref: unknown, logic: (ctx: unknown) => Promise<void>) => {
|
||||
await logic({
|
||||
sendActivity: vi.fn(),
|
||||
updateActivity: vi.fn(),
|
||||
deleteActivity: vi.fn(),
|
||||
});
|
||||
},
|
||||
);
|
||||
mockState.resolveMSTeamsSendContext.mockResolvedValue({
|
||||
adapter: { continueConversation: mockContinueConversation },
|
||||
appId: "my-app-id",
|
||||
conversationId: "19:conv@thread.tacv2",
|
||||
ref: {
|
||||
activityId: "original-activity",
|
||||
user: { id: "user-1" },
|
||||
agent: { id: "agent-1" },
|
||||
conversation: { id: "19:conv@thread.tacv2" },
|
||||
channelId: "msteams",
|
||||
},
|
||||
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
conversationType: "personal",
|
||||
tokenProvider: {},
|
||||
});
|
||||
|
||||
await deleteMessageMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
to: "conversation:19:conv@thread.tacv2",
|
||||
activityId: "activity-789",
|
||||
});
|
||||
|
||||
// appId should be forwarded correctly
|
||||
expect(mockContinueConversation.mock.calls[0]?.[0]).toBe("my-app-id");
|
||||
// activityId on the proactive ref should be cleared (undefined) — proactive pattern
|
||||
expect(mockContinueConversation.mock.calls[0]?.[1]).toMatchObject({
|
||||
activityId: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -511,6 +511,116 @@ export async function sendAdaptiveCardMSTeams(
|
|||
};
|
||||
}
|
||||
|
||||
export type EditMSTeamsMessageParams = {
|
||||
/** Full config (for credentials) */
|
||||
cfg: OpenClawConfig;
|
||||
/** Conversation ID or user ID */
|
||||
to: string;
|
||||
/** Activity ID of the message to edit */
|
||||
activityId: string;
|
||||
/** New message text */
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type EditMSTeamsMessageResult = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
export type DeleteMSTeamsMessageParams = {
|
||||
/** Full config (for credentials) */
|
||||
cfg: OpenClawConfig;
|
||||
/** Conversation ID or user ID */
|
||||
to: string;
|
||||
/** Activity ID of the message to delete */
|
||||
activityId: string;
|
||||
};
|
||||
|
||||
export type DeleteMSTeamsMessageResult = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Edit (update) a previously sent message in a Teams conversation.
|
||||
*
|
||||
* Uses the Bot Framework `continueConversation` → `updateActivity` flow
|
||||
* for proactive edits outside of the original turn context.
|
||||
*/
|
||||
export async function editMessageMSTeams(
|
||||
params: EditMSTeamsMessageParams,
|
||||
): Promise<EditMSTeamsMessageResult> {
|
||||
const { cfg, to, activityId, text } = params;
|
||||
const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
|
||||
cfg,
|
||||
to,
|
||||
});
|
||||
|
||||
log.debug?.("editing proactive message", { conversationId, activityId, textLength: text.length });
|
||||
|
||||
const baseRef = buildConversationReference(ref);
|
||||
const proactiveRef = { ...baseRef, activityId: undefined };
|
||||
|
||||
try {
|
||||
await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
|
||||
await ctx.updateActivity({
|
||||
type: "message",
|
||||
id: activityId,
|
||||
text,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
const classification = classifyMSTeamsSendError(err);
|
||||
const hint = formatMSTeamsSendErrorHint(classification);
|
||||
const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
|
||||
throw new Error(
|
||||
`msteams edit failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
|
||||
{ cause: err },
|
||||
);
|
||||
}
|
||||
|
||||
log.info("edited proactive message", { conversationId, activityId });
|
||||
|
||||
return { conversationId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a previously sent message in a Teams conversation.
|
||||
*
|
||||
* Uses the Bot Framework `continueConversation` → `deleteActivity` flow
|
||||
* for proactive deletes outside of the original turn context.
|
||||
*/
|
||||
export async function deleteMessageMSTeams(
|
||||
params: DeleteMSTeamsMessageParams,
|
||||
): Promise<DeleteMSTeamsMessageResult> {
|
||||
const { cfg, to, activityId } = params;
|
||||
const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
|
||||
cfg,
|
||||
to,
|
||||
});
|
||||
|
||||
log.debug?.("deleting proactive message", { conversationId, activityId });
|
||||
|
||||
const baseRef = buildConversationReference(ref);
|
||||
const proactiveRef = { ...baseRef, activityId: undefined };
|
||||
|
||||
try {
|
||||
await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
|
||||
await ctx.deleteActivity(activityId);
|
||||
});
|
||||
} catch (err) {
|
||||
const classification = classifyMSTeamsSendError(err);
|
||||
const hint = formatMSTeamsSendErrorHint(classification);
|
||||
const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
|
||||
throw new Error(
|
||||
`msteams delete failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
|
||||
{ cause: err },
|
||||
);
|
||||
}
|
||||
|
||||
log.info("deleted proactive message", { conversationId, activityId });
|
||||
|
||||
return { conversationId };
|
||||
}
|
||||
|
||||
/**
|
||||
* List all known conversation references (for debugging/CLI).
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue