mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 23:56:07 +00:00
fix(agent): respect delivery status evidence
This commit is contained in:
@@ -624,6 +624,9 @@ terminal summary, and sanitized error text.
|
||||
- `agent` requests can include `deliver=true` to request outbound delivery.
|
||||
- `bestEffortDeliver=false` keeps strict behavior: unresolved or internal-only delivery targets return `INVALID_REQUEST`.
|
||||
- `bestEffortDeliver=true` allows fallback to session-only execution when no external deliverable route can be resolved (for example internal/webchat sessions or ambiguous multi-channel configs).
|
||||
- Final `agent` results may include `result.deliveryStatus` when delivery was
|
||||
requested, using the same `sent`, `suppressed`, `partial_failed`, and `failed`
|
||||
statuses documented for [`openclaw agent --json --deliver`](/cli/agent#json-delivery-status).
|
||||
|
||||
## Versioning
|
||||
|
||||
|
||||
@@ -77,6 +77,9 @@ programmatic delivery.
|
||||
preserve isolation; direct chats collapse to `main`).
|
||||
- Thinking and verbose flags persist into the session store.
|
||||
- Output: plain text by default, or `--json` for structured payload + metadata.
|
||||
- With `--json --deliver`, the JSON includes delivery status for sent,
|
||||
suppressed, partial, and failed sends. See
|
||||
[JSON delivery status](/cli/agent#json-delivery-status).
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ type AgentPayloadLike = {
|
||||
|
||||
export type AgentDeliveryEvidence = {
|
||||
payloads?: unknown;
|
||||
deliveryStatus?: {
|
||||
status?: unknown;
|
||||
errorMessage?: unknown;
|
||||
};
|
||||
didSendViaMessagingTool?: unknown;
|
||||
messagingToolSentTexts?: unknown;
|
||||
messagingToolSentMediaUrls?: unknown;
|
||||
@@ -106,3 +110,15 @@ export function hasOutboundDeliveryEvidence(result: AgentDeliveryEvidence): bool
|
||||
hasPositiveNumber(result.meta?.toolSummary?.calls)
|
||||
);
|
||||
}
|
||||
|
||||
export function getAgentCommandDeliveryFailure(result: AgentDeliveryEvidence): string | undefined {
|
||||
const status = result.deliveryStatus?.status;
|
||||
if (status !== "failed" && status !== "partial_failed") {
|
||||
return undefined;
|
||||
}
|
||||
const message = result.deliveryStatus?.errorMessage;
|
||||
if (hasNonEmptyString(message)) {
|
||||
return message;
|
||||
}
|
||||
return status === "partial_failed" ? "agent delivery partially failed" : "agent delivery failed";
|
||||
}
|
||||
|
||||
@@ -732,6 +732,48 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
|
||||
expect(sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports requester-agent delivery failure even when output stayed visible", async () => {
|
||||
const callGateway = createGatewayMock({
|
||||
result: {
|
||||
payloads: [{ text: "Tests passed and the PR is ready for review." }],
|
||||
deliveryStatus: {
|
||||
status: "failed",
|
||||
errorMessage: "Slack send failed: channel not found",
|
||||
},
|
||||
},
|
||||
});
|
||||
const sendMessage = createSendMessageMock();
|
||||
const result = await deliverSlackThreadAnnouncement({
|
||||
callGateway,
|
||||
sendMessage,
|
||||
sessionId: "requester-session-4",
|
||||
isActive: false,
|
||||
expectsCompletionMessage: true,
|
||||
directIdempotencyKey: "announce-thread-delivery-status-failed",
|
||||
internalEvents: [
|
||||
{
|
||||
type: "task_completion",
|
||||
source: "subagent",
|
||||
childSessionKey: "agent:worker:subagent:child",
|
||||
childSessionId: "child-session-id",
|
||||
announceType: "subagent task",
|
||||
taskLabel: "thread completion smoke",
|
||||
status: "ok",
|
||||
statusLabel: "completed successfully",
|
||||
result: "child completion output",
|
||||
replyInstruction: "Summarize the result.",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectRecordFields(result, {
|
||||
delivered: false,
|
||||
path: "direct",
|
||||
error: "Slack send failed: channel not found",
|
||||
});
|
||||
expect(sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not raw-send grouped child results when requester-agent output is empty", async () => {
|
||||
const callGateway = createGatewayMock({
|
||||
result: {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { buildAnnounceIdempotencyKey, resolveQueueAnnounceId } from "./announce-idempotency.js";
|
||||
import type { AgentInternalEvent } from "./internal-events.js";
|
||||
import {
|
||||
getAgentCommandDeliveryFailure,
|
||||
getGatewayAgentResult,
|
||||
hasMessagingToolDeliveryEvidence,
|
||||
hasVisibleAgentPayload,
|
||||
@@ -571,6 +572,11 @@ function hasGatewayAgentMessagingToolDelivery(response: unknown): boolean {
|
||||
return Boolean(result && hasMessagingToolDeliveryEvidence(result));
|
||||
}
|
||||
|
||||
function getGatewayAgentCommandDeliveryFailure(response: unknown): string | undefined {
|
||||
const result = getGatewayAgentResult(response);
|
||||
return result ? getAgentCommandDeliveryFailure(result) : undefined;
|
||||
}
|
||||
|
||||
function isGatewayAgentRunPending(response: unknown): boolean {
|
||||
if (!response || typeof response !== "object") {
|
||||
return false;
|
||||
@@ -850,6 +856,16 @@ async function sendSubagentAnnounceDirectly(params: {
|
||||
error: "completion agent did not deliver through the message tool",
|
||||
};
|
||||
}
|
||||
const directDeliveryFailure = shouldDeliverAgentFinal
|
||||
? getGatewayAgentCommandDeliveryFailure(directAnnounceResponse)
|
||||
: undefined;
|
||||
if (directDeliveryFailure) {
|
||||
return {
|
||||
delivered: false,
|
||||
path: "direct",
|
||||
error: directDeliveryFailure,
|
||||
};
|
||||
}
|
||||
if (
|
||||
params.expectsCompletionMessage &&
|
||||
shouldDeliverAgentFinal &&
|
||||
|
||||
@@ -23,6 +23,7 @@ type AgentGatewayResult = {
|
||||
mediaUrl?: string | null;
|
||||
mediaUrls?: string[];
|
||||
}>;
|
||||
deliveryStatus?: unknown;
|
||||
meta?: unknown;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user