mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 09:23:58 +00:00
571 lines
18 KiB
TypeScript
571 lines
18 KiB
TypeScript
/**
|
|
* Detects message-tool sends that delivered a visible reply to the current source.
|
|
*/
|
|
import { safeParseJson } from "@openclaw/normalization-core";
|
|
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
|
import type { SourceReplyDeliveryMode } from "../auto-reply/get-reply-options.types.js";
|
|
import {
|
|
isMessageToolConversationCreateActionName,
|
|
isMessageToolSendActionName,
|
|
isMessagingToolDeliveryAction,
|
|
} from "./embedded-agent-messaging.js";
|
|
import { isToolResultError } from "./embedded-agent-subscribe.tools.js";
|
|
import { normalizeToolName } from "./tool-policy.js";
|
|
|
|
const MESSAGE_TOOL_NAME = "message";
|
|
const SESSIONS_SEND_TOOL_NAME = "sessions_send";
|
|
const EXPLICIT_MESSAGE_ROUTE_KEYS = ["channel", "target", "to", "channelId", "provider"];
|
|
const DRY_RUN_DELIVERY_STATUS = "dry_run";
|
|
const PARTIAL_FAILED_DELIVERY_STATUS = "partial_failed";
|
|
const SENT_DELIVERY_STATUS = "sent";
|
|
const NON_DELIVERY_MESSAGE_IDS = new Set(["skipped", "suppressed"]);
|
|
const RESULT_ENVELOPE_KEYS = [
|
|
"details",
|
|
"payload",
|
|
"result",
|
|
"results",
|
|
"sendResult",
|
|
"toolResult",
|
|
];
|
|
const BROADCAST_SEND_ENVELOPE_KEYS = ["payload", "result", "sendResult", "toolResult"];
|
|
const PARTIAL_DELIVERY_ENVELOPE_KEYS = [...RESULT_ENVELOPE_KEYS, "error", "cause"];
|
|
const SESSIONS_SEND_DELIVERY_STATUSES = new Set(["accepted", "ok"]);
|
|
const BARE_OK_DELIVERY_STATUS = "ok";
|
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as Record<string, unknown>)
|
|
: {};
|
|
}
|
|
|
|
function hasStringValue(value: unknown): boolean {
|
|
return typeof value === "string" && value.trim().length > 0;
|
|
}
|
|
|
|
function hasConversationIdValue(value: unknown): boolean {
|
|
return hasStringValue(value) || (typeof value === "number" && Number.isFinite(value));
|
|
}
|
|
|
|
function hasExplicitMessageRoute(args: Record<string, unknown>): boolean {
|
|
if (EXPLICIT_MESSAGE_ROUTE_KEYS.some((key) => hasStringValue(args[key]))) {
|
|
return true;
|
|
}
|
|
return Array.isArray(args.targets) && args.targets.some((value) => hasStringValue(value));
|
|
}
|
|
|
|
function isMessageToolSourceReplyActionName(action: unknown): boolean {
|
|
if (isMessageToolSendActionName(action)) {
|
|
return true;
|
|
}
|
|
return typeof action === "string" && action.trim().toLowerCase() === "reply";
|
|
}
|
|
|
|
function normalizeStatus(value: unknown): string | undefined {
|
|
return typeof value === "string" ? value.trim().toLowerCase() : undefined;
|
|
}
|
|
|
|
function isBareOkDeliveryStatus(value: unknown): boolean {
|
|
return normalizeStatus(value) === BARE_OK_DELIVERY_STATUS;
|
|
}
|
|
|
|
function isBareSentDeliveryStatus(value: unknown): boolean {
|
|
return normalizeStatus(value) === SENT_DELIVERY_STATUS;
|
|
}
|
|
|
|
function parseJsonRecord(value: string): Record<string, unknown> | undefined {
|
|
return asOptionalRecord(safeParseJson(value));
|
|
}
|
|
|
|
function recordHasDeliveredMessageId(record: Record<string, unknown>): boolean {
|
|
const hasDeliveredId = (value: unknown) => {
|
|
const normalized = normalizeStatus(value);
|
|
return Boolean(normalized && !NON_DELIVERY_MESSAGE_IDS.has(normalized));
|
|
};
|
|
if (hasDeliveredId(record.messageId) || hasDeliveredId(record.pollId)) {
|
|
return true;
|
|
}
|
|
const receipt = record.receipt;
|
|
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
|
|
return false;
|
|
}
|
|
const receiptRecord = receipt as Record<string, unknown>;
|
|
return (
|
|
hasDeliveredId(receiptRecord.primaryPlatformMessageId) ||
|
|
(Array.isArray(receiptRecord.platformMessageIds) &&
|
|
receiptRecord.platformMessageIds.some((value) => hasDeliveredId(value)))
|
|
);
|
|
}
|
|
|
|
function deliveryEnvelopeHasCreatedConversationId(value: unknown, depth = 0): boolean {
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) => deliveryEnvelopeHasCreatedConversationId(item, depth + 1));
|
|
}
|
|
|
|
const record = value as Record<string, unknown>;
|
|
if (
|
|
hasConversationIdValue(record.topicId) ||
|
|
hasConversationIdValue(record.threadId) ||
|
|
hasConversationIdValue(record.messageThreadId)
|
|
) {
|
|
return true;
|
|
}
|
|
const thread = record.thread;
|
|
if (thread && typeof thread === "object" && !Array.isArray(thread)) {
|
|
if (hasConversationIdValue((thread as Record<string, unknown>).id)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeHasCreatedConversationId(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
const content = record.content;
|
|
if (
|
|
Array.isArray(content) &&
|
|
content.some((item) => deliveryEnvelopeHasCreatedConversationId(item, depth + 1))
|
|
) {
|
|
return true;
|
|
}
|
|
return PARTIAL_DELIVERY_ENVELOPE_KEYS.some((key) =>
|
|
deliveryEnvelopeHasCreatedConversationId(record[key], depth + 1),
|
|
);
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesOk(value: unknown, depth = 0): boolean {
|
|
if (isBareOkDeliveryStatus(value)) {
|
|
return true;
|
|
}
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) => deliveryEnvelopeIndicatesOk(item, depth + 1));
|
|
}
|
|
const record = value as Record<string, unknown>;
|
|
if (record.ok === true) {
|
|
return true;
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeIndicatesOk(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
if (isBareOkDeliveryStatus(record.text)) {
|
|
return true;
|
|
}
|
|
}
|
|
const content = record.content;
|
|
if (
|
|
Array.isArray(content) &&
|
|
content.some((item) => deliveryEnvelopeIndicatesOk(item, depth + 1))
|
|
) {
|
|
return true;
|
|
}
|
|
return RESULT_ENVELOPE_KEYS.some((key) => deliveryEnvelopeIndicatesOk(record[key], depth + 1));
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesNonDelivery(value: unknown, depth = 0): boolean {
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) => deliveryEnvelopeIndicatesNonDelivery(item, depth + 1));
|
|
}
|
|
const record = value as Record<string, unknown>;
|
|
const messageId = normalizeStatus(record.messageId);
|
|
if (
|
|
(messageId && NON_DELIVERY_MESSAGE_IDS.has(messageId)) ||
|
|
normalizeStatus(record.deliveryStatus) === "suppressed" ||
|
|
normalizeStatus(record.status) === "suppressed"
|
|
) {
|
|
return true;
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeIndicatesNonDelivery(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
const content = record.content;
|
|
if (
|
|
Array.isArray(content) &&
|
|
content.some((item) => deliveryEnvelopeIndicatesNonDelivery(item, depth + 1))
|
|
) {
|
|
return true;
|
|
}
|
|
return RESULT_ENVELOPE_KEYS.some((key) =>
|
|
deliveryEnvelopeIndicatesNonDelivery(record[key], depth + 1),
|
|
);
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesNoOp(value: unknown, depth = 0): boolean {
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) => deliveryEnvelopeIndicatesNoOp(item, depth + 1));
|
|
}
|
|
const record = value as Record<string, unknown>;
|
|
const removed = record.removed;
|
|
if (
|
|
removed === null ||
|
|
removed === false ||
|
|
removed === 0 ||
|
|
(Array.isArray(removed) && removed.length === 0) ||
|
|
record.applied === false ||
|
|
record.changed === false ||
|
|
record.created === false ||
|
|
record.deleted === false ||
|
|
record.sent === false ||
|
|
record.updated === false
|
|
) {
|
|
return true;
|
|
}
|
|
const status = normalizeStatus(record.status);
|
|
if (status === "noop" || status === "no_op" || status === "not_found") {
|
|
return true;
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeIndicatesNoOp(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
const content = record.content;
|
|
if (
|
|
Array.isArray(content) &&
|
|
content.some((item) => deliveryEnvelopeIndicatesNoOp(item, depth + 1))
|
|
) {
|
|
return true;
|
|
}
|
|
return RESULT_ENVELOPE_KEYS.some((key) => deliveryEnvelopeIndicatesNoOp(record[key], depth + 1));
|
|
}
|
|
|
|
function broadcastEntryHasSuccessfulBareOkSend(
|
|
record: Record<string, unknown>,
|
|
depth: number,
|
|
): boolean {
|
|
return BROADCAST_SEND_ENVELOPE_KEYS.some((key) => {
|
|
const value = record[key];
|
|
return (
|
|
deliveryEnvelopeIndicatesOk(value, depth + 1) &&
|
|
!deliveryEnvelopeIndicatesNonDelivery(value, depth + 1) &&
|
|
!deliveryEnvelopeIndicatesNoOp(value, depth + 1)
|
|
);
|
|
});
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesSuccessfulBroadcast(value: unknown, depth = 0): boolean {
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some(
|
|
(item) =>
|
|
item !== null &&
|
|
typeof item === "object" &&
|
|
!Array.isArray(item) &&
|
|
(item as Record<string, unknown>).ok === true &&
|
|
!deliveryEnvelopeIndicatesNonDelivery(item) &&
|
|
!deliveryEnvelopeIndicatesNoOp(item) &&
|
|
(deliveryEnvelopeIndicatesDelivered(item, depth + 1) ||
|
|
broadcastEntryHasSuccessfulBareOkSend(item as Record<string, unknown>, depth + 1)),
|
|
);
|
|
}
|
|
const record = value as Record<string, unknown>;
|
|
if (deliveryEnvelopeIndicatesSuccessfulBroadcast(record.results, depth + 1)) {
|
|
return true;
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeIndicatesSuccessfulBroadcast(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
const content = record.content;
|
|
if (
|
|
Array.isArray(content) &&
|
|
content.some((item) => deliveryEnvelopeIndicatesSuccessfulBroadcast(item, depth + 1))
|
|
) {
|
|
return true;
|
|
}
|
|
return RESULT_ENVELOPE_KEYS.some((key) =>
|
|
deliveryEnvelopeIndicatesSuccessfulBroadcast(record[key], depth + 1),
|
|
);
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesDryRun(value: unknown, depth = 0): boolean {
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) => deliveryEnvelopeIndicatesDryRun(item, depth + 1));
|
|
}
|
|
|
|
const record = value as Record<string, unknown>;
|
|
if (
|
|
record.dryRun === true ||
|
|
normalizeStatus(record.deliveryStatus) === DRY_RUN_DELIVERY_STATUS ||
|
|
normalizeStatus(record.status) === DRY_RUN_DELIVERY_STATUS
|
|
) {
|
|
return true;
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeIndicatesDryRun(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const content = record.content;
|
|
if (Array.isArray(content)) {
|
|
for (const item of content) {
|
|
if (deliveryEnvelopeIndicatesDryRun(item, depth + 1)) {
|
|
return true;
|
|
}
|
|
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
const text = (item as Record<string, unknown>).text;
|
|
if (typeof text === "string") {
|
|
const parsed = parseJsonRecord(text);
|
|
if (parsed && deliveryEnvelopeIndicatesDryRun(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return RESULT_ENVELOPE_KEYS.some((key) =>
|
|
deliveryEnvelopeIndicatesDryRun(record[key], depth + 1),
|
|
);
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesDelivered(
|
|
value: unknown,
|
|
depth = 0,
|
|
requireReceipt = false,
|
|
): boolean {
|
|
if (!requireReceipt && isBareSentDeliveryStatus(value)) {
|
|
return true;
|
|
}
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) =>
|
|
deliveryEnvelopeIndicatesDelivered(item, depth + 1, requireReceipt),
|
|
);
|
|
}
|
|
|
|
const record = value as Record<string, unknown>;
|
|
if (
|
|
(!requireReceipt && normalizeStatus(record.deliveryStatus) === SENT_DELIVERY_STATUS) ||
|
|
(!requireReceipt && normalizeStatus(record.status) === SENT_DELIVERY_STATUS) ||
|
|
recordHasDeliveredMessageId(record)
|
|
) {
|
|
return true;
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeIndicatesDelivered(parsed, depth + 1, requireReceipt)) {
|
|
return true;
|
|
}
|
|
if (!requireReceipt && isBareSentDeliveryStatus(record.text)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const content = record.content;
|
|
if (Array.isArray(content)) {
|
|
for (const item of content) {
|
|
if (deliveryEnvelopeIndicatesDelivered(item, depth + 1, requireReceipt)) {
|
|
return true;
|
|
}
|
|
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
const text = (item as Record<string, unknown>).text;
|
|
if (typeof text === "string") {
|
|
const parsed = parseJsonRecord(text);
|
|
if (parsed && deliveryEnvelopeIndicatesDelivered(parsed, depth + 1, requireReceipt)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return RESULT_ENVELOPE_KEYS.some((key) =>
|
|
deliveryEnvelopeIndicatesDelivered(record[key], depth + 1, requireReceipt),
|
|
);
|
|
}
|
|
|
|
/** Return true when a result envelope carries a provider message identifier. */
|
|
export function hasMessagingDeliveryReceipt(value: unknown): boolean {
|
|
return deliveryEnvelopeIndicatesDelivered(value, 0, true);
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesSessionsSendAccepted(value: unknown, depth = 0): boolean {
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) => deliveryEnvelopeIndicatesSessionsSendAccepted(item, depth + 1));
|
|
}
|
|
const record = value as Record<string, unknown>;
|
|
if (
|
|
SESSIONS_SEND_DELIVERY_STATUSES.has(normalizeStatus(record.deliveryStatus) ?? "") ||
|
|
SESSIONS_SEND_DELIVERY_STATUSES.has(normalizeStatus(record.status) ?? "")
|
|
) {
|
|
return true;
|
|
}
|
|
if (typeof record.text === "string") {
|
|
const parsed = parseJsonRecord(record.text);
|
|
if (parsed && deliveryEnvelopeIndicatesSessionsSendAccepted(parsed, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
const content = record.content;
|
|
if (
|
|
Array.isArray(content) &&
|
|
content.some((item) => deliveryEnvelopeIndicatesSessionsSendAccepted(item, depth + 1))
|
|
) {
|
|
return true;
|
|
}
|
|
return RESULT_ENVELOPE_KEYS.some((key) =>
|
|
deliveryEnvelopeIndicatesSessionsSendAccepted(record[key], depth + 1),
|
|
);
|
|
}
|
|
|
|
function deliveryEnvelopeIndicatesPartialDelivery(value: unknown, depth = 0): boolean {
|
|
if (!value || typeof value !== "object" || depth > 4) {
|
|
return false;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.some((item) => deliveryEnvelopeIndicatesPartialDelivery(item, depth + 1));
|
|
}
|
|
|
|
const record = value as Record<string, unknown>;
|
|
if (
|
|
record.sentBeforeError === true ||
|
|
record.visibleReplySent === true ||
|
|
normalizeStatus(record.deliveryStatus) === PARTIAL_FAILED_DELIVERY_STATUS ||
|
|
normalizeStatus(record.status) === PARTIAL_FAILED_DELIVERY_STATUS
|
|
) {
|
|
return true;
|
|
}
|
|
return PARTIAL_DELIVERY_ENVELOPE_KEYS.some((key) =>
|
|
deliveryEnvelopeIndicatesPartialDelivery(record[key], depth + 1),
|
|
);
|
|
}
|
|
|
|
/** Return true only when a messaging tool result proves a real visible delivery. */
|
|
export function isDeliveredMessagingToolResult(params: {
|
|
toolName?: string;
|
|
args?: unknown;
|
|
result?: unknown;
|
|
hookResult?: unknown;
|
|
isError?: boolean;
|
|
}): boolean {
|
|
const args = asRecord(params.args);
|
|
const action = normalizeStatus(args.action);
|
|
if (
|
|
args.dryRun === true ||
|
|
deliveryEnvelopeIndicatesDryRun(params.result) ||
|
|
deliveryEnvelopeIndicatesDryRun(params.hookResult)
|
|
) {
|
|
return false;
|
|
}
|
|
if (
|
|
deliveryEnvelopeIndicatesPartialDelivery(params.result) ||
|
|
deliveryEnvelopeIndicatesPartialDelivery(params.hookResult)
|
|
) {
|
|
return true;
|
|
}
|
|
if (
|
|
action &&
|
|
isMessageToolConversationCreateActionName(action) &&
|
|
(deliveryEnvelopeHasCreatedConversationId(params.result) ||
|
|
deliveryEnvelopeHasCreatedConversationId(params.hookResult))
|
|
) {
|
|
return true;
|
|
}
|
|
if (
|
|
action === "broadcast" &&
|
|
(deliveryEnvelopeIndicatesSuccessfulBroadcast(params.result) ||
|
|
deliveryEnvelopeIndicatesSuccessfulBroadcast(params.hookResult))
|
|
) {
|
|
return true;
|
|
}
|
|
if (params.isError || isToolResultError(params.result) || isToolResultError(params.hookResult)) {
|
|
return false;
|
|
}
|
|
const normalizedToolName = normalizeToolName(params.toolName ?? MESSAGE_TOOL_NAME);
|
|
const mutationHasBareOk =
|
|
isMessagingToolDeliveryAction(normalizedToolName, args) &&
|
|
action !== "broadcast" &&
|
|
(deliveryEnvelopeIndicatesOk(params.result) || deliveryEnvelopeIndicatesOk(params.hookResult));
|
|
if (
|
|
mutationHasBareOk &&
|
|
!deliveryEnvelopeIndicatesNonDelivery(params.result) &&
|
|
!deliveryEnvelopeIndicatesNonDelivery(params.hookResult) &&
|
|
!deliveryEnvelopeIndicatesNoOp(params.result) &&
|
|
!deliveryEnvelopeIndicatesNoOp(params.hookResult)
|
|
) {
|
|
return true;
|
|
}
|
|
if (
|
|
deliveryEnvelopeIndicatesNonDelivery(params.result) ||
|
|
deliveryEnvelopeIndicatesNonDelivery(params.hookResult) ||
|
|
deliveryEnvelopeIndicatesNoOp(params.result) ||
|
|
deliveryEnvelopeIndicatesNoOp(params.hookResult)
|
|
) {
|
|
return false;
|
|
}
|
|
if (normalizedToolName === SESSIONS_SEND_TOOL_NAME) {
|
|
return (
|
|
deliveryEnvelopeIndicatesSessionsSendAccepted(params.result) ||
|
|
deliveryEnvelopeIndicatesSessionsSendAccepted(params.hookResult) ||
|
|
deliveryEnvelopeIndicatesDelivered(params.result) ||
|
|
deliveryEnvelopeIndicatesDelivered(params.hookResult)
|
|
);
|
|
}
|
|
return (
|
|
deliveryEnvelopeIndicatesDelivered(params.result) ||
|
|
deliveryEnvelopeIndicatesDelivered(params.hookResult)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Only implicit-route, non-dry-run, delivered `message.send` calls qualify.
|
|
* Explicit routes and other messaging tools are outbound side effects, not source replies.
|
|
*/
|
|
export function isDeliveredMessageToolOnlySourceReplyResult(params: {
|
|
sourceReplyDeliveryMode?: SourceReplyDeliveryMode;
|
|
toolName: string;
|
|
args?: unknown;
|
|
result?: unknown;
|
|
hookResult?: unknown;
|
|
isError?: boolean;
|
|
allowExplicitSourceRoute?: boolean;
|
|
}): boolean {
|
|
if (params.sourceReplyDeliveryMode !== "message_tool_only") {
|
|
return false;
|
|
}
|
|
if (normalizeToolName(params.toolName) !== MESSAGE_TOOL_NAME) {
|
|
return false;
|
|
}
|
|
const args = asRecord(params.args);
|
|
const sourceRouteReplyAction =
|
|
params.allowExplicitSourceRoute === true && isMessageToolSourceReplyActionName(args.action);
|
|
if (!isMessageToolSendActionName(args.action) && !sourceRouteReplyAction) {
|
|
return false;
|
|
}
|
|
if (hasExplicitMessageRoute(args) && params.allowExplicitSourceRoute !== true) {
|
|
return false;
|
|
}
|
|
return isDeliveredMessagingToolResult(params);
|
|
}
|