openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
484 lines (483 loc) • 19.6 kB
JavaScript
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { r as writeRuntimeJson } from "./runtime-B4lgFmsS.js";
import { v as resolveSessionAgentId } from "./agent-scope-MrLta7Pq.js";
import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js";
import { a as normalizeChannelId, t as getChannelPlugin } from "./registry-MkxNn1Ue.js";
import "./plugins-t2ejWcVy.js";
import { r as isInternalMessageChannel } from "./message-channel-BiOeMu0l.js";
import { t as normalizeReplyPayload } from "./normalize-reply-BJvr7poh.js";
import { c as projectOutboundPayloadPlanForOutbound, n as formatOutboundPayloadLog, o as projectOutboundPayloadPlanForJson, r as normalizeOutboundPayloadsForJson, t as createOutboundPayloadPlan } from "./payloads-B4DTFTLz.js";
import { t as sendDurableMessageBatch } from "./send-DWoTcOuO.js";
import "./runtime-BiQQNgz2.js";
import { r as resolveMessageChannelSelection } from "./channel-selection-MKj8pvsX.js";
import { n as isNestedAgentLane } from "./lanes-CI0_P-yC.js";
import { n as createReplyMediaPathNormalizer } from "./reply-media-paths.runtime-CgZsH182.js";
import { t as createReplyPrefixContext } from "./reply-prefix-0ojzXxbX.js";
import { n as resolveAgentDeliveryPlanWithSessionRoute, r as resolveAgentOutboundTarget } from "./agent-delivery-DgS5lONr.js";
import { t as createOutboundSendDeps } from "./outbound-send-deps-DKKrD6yB.js";
//#region src/infra/outbound/envelope.ts
const isOutboundPayloadJson = (payload) => "mediaUrl" in payload;
/** Builds the outbound result envelope, flattening plain delivery-only results by default. */
function buildOutboundResultEnvelope(params) {
const hasPayloads = params.payloads !== void 0;
const payloads = params.payloads === void 0 ? void 0 : params.payloads.length === 0 ? [] : isOutboundPayloadJson(params.payloads[0]) ? [...params.payloads] : normalizeOutboundPayloadsForJson(params.payloads);
if (params.flattenDelivery !== false && params.delivery && !params.meta && !hasPayloads) return params.delivery;
return {
...hasPayloads ? { payloads } : {},
...params.meta ? { meta: params.meta } : {},
...params.delivery ? { delivery: params.delivery } : {}
};
}
//#endregion
//#region src/agents/command/delivery.ts
/**
* Normalizes and delivers agent command results to outbound channels.
*/
const NESTED_LOG_PREFIX = "[agent:nested]";
function normalizeDeliverySessionId(value) {
const trimmed = value?.trim();
return trimmed ? trimmed : void 0;
}
function isFreshDeliverySessionMatch(freshSessionEntry, expectedSessionId) {
const normalizedExpected = normalizeDeliverySessionId(expectedSessionId);
return Boolean(normalizedExpected && freshSessionEntry.sessionId === normalizedExpected);
}
function formatNestedLogPrefix(opts, sessionKey) {
const parts = [NESTED_LOG_PREFIX];
const session = sessionKey ?? opts.sessionKey ?? opts.sessionId;
if (session) parts.push(`session=${session}`);
if (opts.runId) parts.push(`run=${opts.runId}`);
const channel = opts.messageChannel ?? opts.channel;
if (channel) parts.push(`channel=${channel}`);
if (opts.to) parts.push(`to=${opts.to}`);
if (opts.accountId) parts.push(`account=${opts.accountId}`);
return parts.join(" ");
}
function logNestedOutput(runtime, opts, output, sessionKey) {
const prefix = formatNestedLogPrefix(opts, sessionKey);
for (const line of output.split(/\r?\n/)) {
if (!line) continue;
runtime.log(`${prefix} ${line}`);
}
}
function mergeResultMetaOverrides(meta, overrides) {
if (!overrides) return meta;
return {
...meta,
...overrides
};
}
function hasNonEmptyString(value) {
return typeof value === "string" && value.trim().length > 0;
}
function hasNonEmptyStringArray(value) {
return Array.isArray(value) && value.some(hasNonEmptyString);
}
function hasNonEmptyArray(value) {
return Array.isArray(value) && value.length > 0;
}
function buildDeliveryResult(params) {
return {
payloads: params.payloads,
meta: params.meta,
...params.result.didSendViaMessagingTool === true ? { didSendViaMessagingTool: true } : {},
...hasNonEmptyStringArray(params.result.messagingToolSentTexts) ? { messagingToolSentTexts: params.result.messagingToolSentTexts } : {},
...hasNonEmptyStringArray(params.result.messagingToolSentMediaUrls) ? { messagingToolSentMediaUrls: params.result.messagingToolSentMediaUrls } : {},
...hasNonEmptyArray(params.result.messagingToolSentTargets) ? { messagingToolSentTargets: params.result.messagingToolSentTargets } : {},
...params.deliverySucceeded !== void 0 ? { deliverySucceeded: params.deliverySucceeded } : {},
...params.deliveryStatus ? { deliveryStatus: params.deliveryStatus } : {}
};
}
function serializeDeliveryPayloadOutcomes(outcomes) {
if (!outcomes || outcomes.length === 0) return;
return outcomes.map((outcome) => {
if (outcome.status === "sent") return {
index: outcome.index,
status: "sent",
resultCount: outcome.results.length
};
if (outcome.status === "suppressed") return {
index: outcome.index,
status: "suppressed",
reason: outcome.reason,
...outcome.hookEffect ? { hookEffect: outcome.hookEffect } : {}
};
return {
index: outcome.index,
status: "failed",
error: formatErrorMessage(outcome.error),
sentBeforeError: outcome.sentBeforeError,
stage: outcome.stage
};
});
}
function deliveryStatusFromDurableSend(send) {
const payloadOutcomes = serializeDeliveryPayloadOutcomes(send.payloadOutcomes);
switch (send.status) {
case "sent": return {
requested: true,
attempted: true,
status: "sent",
succeeded: true,
resultCount: send.results.length,
...payloadOutcomes ? { payloadOutcomes } : {}
};
case "suppressed": return {
requested: true,
attempted: true,
status: "suppressed",
succeeded: true,
reason: send.reason,
resultCount: 0,
...payloadOutcomes ? { payloadOutcomes } : {}
};
case "partial_failed": return {
requested: true,
attempted: true,
status: "partial_failed",
succeeded: "partial",
error: true,
errorMessage: formatErrorMessage(send.error),
resultCount: send.results.length,
sentBeforeError: true,
...payloadOutcomes ? { payloadOutcomes } : {}
};
case "failed": return {
requested: true,
attempted: true,
status: "failed",
succeeded: false,
error: true,
errorMessage: formatErrorMessage(send.error),
...send.stage ? { reason: send.stage } : {},
...payloadOutcomes ? { payloadOutcomes } : {}
};
}
return send;
}
function preDeliveryFailureStatus(reason) {
return {
requested: true,
attempted: false,
status: "failed",
succeeded: false,
error: true,
reason
};
}
function noVisiblePayloadStatus() {
return {
requested: true,
attempted: false,
status: "suppressed",
succeeded: true,
reason: "no_visible_payload",
resultCount: 0
};
}
async function normalizeReplyMediaPathsForDelivery(params) {
if (params.payloads.length === 0) return params.payloads;
const agentId = params.outboundSession?.agentId ?? resolveSessionAgentId({
sessionKey: params.sessionKey,
config: params.cfg
});
const workspaceDir = agentId ? resolveAgentWorkspaceDir(params.cfg, agentId) : void 0;
if (!workspaceDir) return params.payloads;
const normalizeMediaPaths = createReplyMediaPathNormalizer({
cfg: params.cfg,
sessionKey: params.sessionKey,
agentId,
workspaceDir,
messageProvider: params.deliveryChannel,
accountId: params.accountId
});
const result = [];
for (const payload of params.payloads) result.push(await normalizeMediaPaths(payload));
return result;
}
/** Normalizes reply payloads and media paths before delivery. */
function normalizeAgentCommandReplyPayloads(params) {
const payloads = params.payloads ?? [];
if (payloads.length === 0) return [];
const channel = params.deliveryChannel && !isInternalMessageChannel(params.deliveryChannel) ? normalizeChannelId(params.deliveryChannel) ?? params.deliveryChannel : void 0;
if (!channel) return payloads;
const applyChannelTransforms = params.applyChannelTransforms ?? true;
const deliveryPlugin = applyChannelTransforms ? getChannelPlugin(channel) : void 0;
const sessionKey = params.outboundSession?.key ?? params.opts.sessionKey;
const agentId = params.outboundSession?.agentId ?? resolveSessionAgentId({
sessionKey,
config: params.cfg
});
const replyPrefix = createReplyPrefixContext({
cfg: params.cfg,
agentId,
channel,
accountId: params.accountId
});
const modelUsed = params.result.meta.agentMeta?.model;
const providerUsed = params.result.meta.agentMeta?.provider;
if (providerUsed && modelUsed) replyPrefix.onModelSelected({
provider: providerUsed,
model: modelUsed,
thinkLevel: void 0
});
const responsePrefixContext = replyPrefix.responsePrefixContextProvider();
const transformReplyPayload = deliveryPlugin?.messaging?.transformReplyPayload ? (payload) => deliveryPlugin.messaging?.transformReplyPayload?.({
payload,
cfg: params.cfg,
accountId: params.accountId
}) ?? payload : void 0;
const normalizedPayloads = [];
for (const payload of payloads) {
const normalized = normalizeReplyPayload(payload, {
responsePrefix: replyPrefix.responsePrefix,
applyChannelTransforms,
responsePrefixContext,
transformReplyPayload
});
if (normalized) normalizedPayloads.push(normalized);
}
return normalizedPayloads;
}
/** Delivers an agent command result or records why delivery was skipped. */
async function deliverAgentCommandResult(params) {
const { cfg, deps, runtime, opts, outboundSession, sessionEntry, payloads, result } = params;
const effectiveSessionKey = outboundSession?.key ?? opts.sessionKey;
const deliveryAgentId = outboundSession?.agentId ?? resolveSessionAgentId({
sessionKey: effectiveSessionKey,
config: cfg
}) ?? resolveDefaultAgentId(cfg);
const deliver = opts.deliver === true;
const bestEffortDeliver = opts.bestEffortDeliver === true;
const turnSourceChannel = opts.runContext?.messageChannel ?? opts.messageChannel;
const turnSourceTo = opts.runContext?.currentChannelId ?? opts.to;
const turnSourceAccountId = opts.runContext?.accountId ?? opts.accountId;
const turnSourceThreadId = opts.runContext?.currentThreadTs ?? opts.threadId;
const explicitChannelHint = (opts.replyChannel ?? opts.channel)?.trim();
const resolveDeliveryRouting = async (candidateSessionEntry) => {
const deliveryPlan = await resolveAgentDeliveryPlanWithSessionRoute({
cfg,
agentId: deliveryAgentId,
currentSessionKey: effectiveSessionKey,
sessionEntry: candidateSessionEntry,
requestedChannel: opts.replyChannel ?? opts.channel,
explicitTo: opts.replyTo ?? opts.to,
explicitThreadId: opts.threadId,
accountId: opts.replyAccountId ?? opts.accountId,
wantsDelivery: deliver,
turnSourceChannel,
turnSourceTo,
turnSourceAccountId,
turnSourceThreadId
});
let deliveryChannel = deliveryPlan.resolvedChannel;
if (deliver && isInternalMessageChannel(deliveryChannel) && !explicitChannelHint) try {
deliveryChannel = (await resolveMessageChannelSelection({ cfg })).channel;
} catch {}
const effectiveDeliveryPlan = deliveryChannel === deliveryPlan.resolvedChannel ? deliveryPlan : {
...deliveryPlan,
resolvedChannel: deliveryChannel
};
const deliveryPlugin = deliver && !isInternalMessageChannel(deliveryChannel) ? getChannelPlugin(normalizeChannelId(deliveryChannel) ?? deliveryChannel) : void 0;
const isDeliveryChannelKnown = isInternalMessageChannel(deliveryChannel) || Boolean(deliveryPlugin);
const targetMode = opts.deliveryTargetMode ?? effectiveDeliveryPlan.deliveryTargetMode ?? (opts.to ? "explicit" : "implicit");
const resolvedAccountId = effectiveDeliveryPlan.resolvedAccountId;
const resolved = deliver && isDeliveryChannelKnown && deliveryChannel ? resolveAgentOutboundTarget({
cfg,
plan: effectiveDeliveryPlan,
targetMode,
validateExplicitTarget: true
}) : {
resolvedTarget: null,
resolvedTo: effectiveDeliveryPlan.resolvedTo,
targetMode
};
const resolvedThreadId = deliveryPlan.resolvedThreadId ?? opts.threadId;
const replyTransport = deliveryPlugin?.threading?.resolveReplyTransport?.({
cfg,
accountId: resolvedAccountId,
threadId: resolvedThreadId
}) ?? null;
return {
deliveryPlan,
deliveryChannel,
effectiveDeliveryPlan,
deliveryPlugin,
isDeliveryChannelKnown,
targetMode,
resolvedAccountId,
resolved,
resolvedTarget: resolved.resolvedTarget,
deliveryTarget: resolved.resolvedTo,
resolvedThreadId,
resolvedReplyToId: replyTransport?.replyToId ?? void 0,
resolvedThreadTarget: replyTransport && Object.hasOwn(replyTransport, "threadId") ? replyTransport.threadId ?? null : resolvedThreadId ?? null
};
};
const deliveryRoutingFailureReason = (route) => {
if (!deliver) return;
if (isInternalMessageChannel(route.deliveryChannel)) return "channel_resolved_to_internal";
if (!route.isDeliveryChannelKnown) return "unknown_channel";
if (route.resolvedTarget && !route.resolvedTarget.ok) return "invalid_delivery_target";
if (!route.deliveryTarget) return "no_delivery_target";
};
const isRetryableFreshSessionRoutingFailure = (route) => {
const reason = deliveryRoutingFailureReason(route);
if (!reason) return false;
if (reason === "unknown_channel") return false;
return true;
};
let deliveryRouting = await resolveDeliveryRouting(sessionEntry);
if (isRetryableFreshSessionRoutingFailure(deliveryRouting)) {
const freshSessionEntry = await params.resolveFreshSessionEntryForDelivery?.();
const expectedFreshSessionId = params.expectedSessionIdForFreshDelivery ?? sessionEntry?.sessionId;
if (freshSessionEntry && freshSessionEntry !== sessionEntry && isFreshDeliverySessionMatch(freshSessionEntry, expectedFreshSessionId)) {
const freshRouting = await resolveDeliveryRouting(freshSessionEntry);
if (!deliveryRoutingFailureReason(freshRouting)) {
if (!opts.json) runtime.log(`[delivery] refreshed session routing before final delivery (session=${effectiveSessionKey ?? "unknown"} channel=${freshRouting.deliveryChannel})`);
deliveryRouting = freshRouting;
}
}
}
const { deliveryChannel, isDeliveryChannelKnown, resolvedAccountId, resolvedTarget, deliveryTarget, resolvedReplyToId, resolvedThreadTarget } = deliveryRouting;
let deliveryLoggedError = false;
const logDeliveryError = (err) => {
deliveryLoggedError = true;
const message = `Delivery failed (${deliveryChannel}${deliveryTarget ? ` to ${deliveryTarget}` : ""}): ${String(err)}`;
runtime.error?.(message);
if (!runtime.error) runtime.log(message);
};
let strictPreDeliveryError;
let deliveryStatus;
const handlePreDeliveryError = (err, reason) => {
deliveryStatus = preDeliveryFailureStatus(reason);
if (!bestEffortDeliver) {
if (opts.json) {
strictPreDeliveryError = err;
return;
}
throw err;
}
logDeliveryError(err);
};
if (deliver) {
if (isInternalMessageChannel(deliveryChannel)) handlePreDeliveryError(/* @__PURE__ */ new Error("delivery channel is required: pass --channel/--reply-channel or use a main session with a previous channel"), "channel_resolved_to_internal");
else if (!isDeliveryChannelKnown) handlePreDeliveryError(/* @__PURE__ */ new Error(`Unknown channel: ${deliveryChannel}`), "unknown_channel");
else if (resolvedTarget && !resolvedTarget.ok) handlePreDeliveryError(resolvedTarget.error, "invalid_delivery_target");
}
const normalizedReplyPayloads = normalizeAgentCommandReplyPayloads({
cfg,
opts,
outboundSession,
payloads,
result,
deliveryChannel,
accountId: resolvedAccountId,
applyChannelTransforms: deliver
});
const outboundPayloadPlan = createOutboundPayloadPlan(deliver && !deliveryStatus && !isInternalMessageChannel(deliveryChannel) ? await normalizeReplyMediaPathsForDelivery({
cfg,
payloads: normalizedReplyPayloads,
sessionKey: effectiveSessionKey,
outboundSession,
deliveryChannel,
accountId: resolvedAccountId
}) : normalizedReplyPayloads);
const normalizedPayloads = projectOutboundPayloadPlanForJson(outboundPayloadPlan);
const resultMeta = mergeResultMetaOverrides(result.meta, opts.resultMetaOverrides);
const emitJsonEnvelope = (status) => {
if (!opts.json) return;
writeRuntimeJson(runtime, {
...buildOutboundResultEnvelope({
payloads: normalizedPayloads,
meta: resultMeta
}),
...status ? { deliveryStatus: status } : {}
});
};
if (strictPreDeliveryError) {
emitJsonEnvelope(deliveryStatus);
throw toLintErrorObject(strictPreDeliveryError, "Non-Error thrown");
}
const deliveryPayloads = projectOutboundPayloadPlanForOutbound(outboundPayloadPlan);
if (deliveryPayloads.length === 0) {
deliveryStatus = deliver ? deliveryStatus ?? noVisiblePayloadStatus() : void 0;
const deliverySucceeded = deliveryStatus?.succeeded === true ? true : void 0;
emitJsonEnvelope(deliveryStatus);
return buildDeliveryResult({
payloads: normalizedPayloads,
meta: resultMeta,
result,
deliverySucceeded,
deliveryStatus
});
}
let deliverySucceeded = false;
const logPayload = (payload) => {
if (opts.json) return;
const output = formatOutboundPayloadLog(payload);
if (!output) return;
if (isNestedAgentLane(opts.lane)) {
logNestedOutput(runtime, opts, output, effectiveSessionKey);
return;
}
runtime.log(output);
};
if (!deliver) {
for (const payload of deliveryPayloads) logPayload(payload);
emitJsonEnvelope();
return buildDeliveryResult({
payloads: normalizedPayloads,
meta: resultMeta,
result
});
}
if (deliver && deliveryChannel && !isInternalMessageChannel(deliveryChannel)) {
if (deliveryTarget && !deliveryStatus) {
const send = await sendDurableMessageBatch({
cfg,
channel: deliveryChannel,
to: deliveryTarget,
accountId: resolvedAccountId,
payloads: deliveryPayloads,
session: outboundSession,
replyToId: resolvedReplyToId ?? null,
threadId: resolvedThreadTarget ?? null,
bestEffort: bestEffortDeliver,
durability: bestEffortDeliver ? "best_effort" : "required",
onError: logDeliveryError,
onPayload: logPayload,
deps: createOutboundSendDeps(deps)
});
deliveryStatus = deliveryStatusFromDurableSend(send);
if (!bestEffortDeliver && (send.status === "failed" || send.status === "partial_failed")) {
emitJsonEnvelope(deliveryStatus);
throw send.error;
}
deliverySucceeded = send.status === "sent" || send.status === "suppressed";
}
}
if (deliver && !deliveryStatus) deliveryStatus = preDeliveryFailureStatus("no_delivery_target");
if (deliver && !deliverySucceeded && !opts.json && !deliveryLoggedError) {
const message = `[delivery] delivery requested but not completed: ${deliveryStatus?.status ?? "unknown"} (reason=${deliveryStatus?.reason ?? "none"} session=${effectiveSessionKey ?? "unknown"} channel=${deliveryChannel ?? "none"} target=${deliveryTarget ?? "none"} payloads=${deliveryPayloads.length})`;
runtime.error?.(message);
if (!runtime.error) runtime.log(message);
}
emitJsonEnvelope(deliveryStatus);
return buildDeliveryResult({
payloads: normalizedPayloads,
meta: resultMeta,
result,
deliverySucceeded,
deliveryStatus
});
}
function toLintErrorObject(value, fallbackMessage) {
if (value instanceof Error) return value;
if (typeof value === "string") return new Error(value);
const error = new Error(fallbackMessage, { cause: value });
if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value);
return error;
}
//#endregion
export { deliverAgentCommandResult };