mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-13 17:07:40 +00:00
refactor(agents): centralize delivery fallback
This commit is contained in:
@@ -25,7 +25,6 @@ import {
|
||||
isMessagingToolSendAction,
|
||||
normalizeHeartbeatToolResponse,
|
||||
projectRuntimeToolInputSchema,
|
||||
createDeliveredMessagingResultReconciler,
|
||||
runAgentHarnessAfterToolCallHook,
|
||||
sanitizeToolResult,
|
||||
setBeforeToolCallDiagnosticsEnabled,
|
||||
@@ -289,12 +288,6 @@ export function createCodexDynamicToolBridge(params: {
|
||||
);
|
||||
const telemetryRawResult = sanitizeToolResult(rawResult);
|
||||
const rawIsError = isCodexToolResultError(rawResult);
|
||||
const reconcileMiddlewareResult = createDeliveredMessagingResultReconciler({
|
||||
toolName,
|
||||
args: executedArgs,
|
||||
rawResult,
|
||||
rawIsError,
|
||||
});
|
||||
const middlewareResult = await middlewareRunner.applyToolResultMiddleware({
|
||||
threadId: call.threadId,
|
||||
turnId: call.turnId,
|
||||
@@ -304,14 +297,13 @@ export function createCodexDynamicToolBridge(params: {
|
||||
isError: rawIsError,
|
||||
result: rawResult,
|
||||
});
|
||||
const reconciledMiddlewareResult = reconcileMiddlewareResult(middlewareResult);
|
||||
const result = await legacyExtensionRunner.applyToolResultExtensions({
|
||||
threadId: call.threadId,
|
||||
turnId: call.turnId,
|
||||
toolCallId: call.callId,
|
||||
toolName,
|
||||
args: structuredClone(executedArgs),
|
||||
result: reconciledMiddlewareResult,
|
||||
result: middlewareResult,
|
||||
});
|
||||
const resultIsError = rawIsError || isCodexToolResultError(result);
|
||||
notifyAgentToolResult(options?.onAgentToolResult, toolName, result, resultIsError);
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
import { resolveContextWindowInfo } from "../context-window-guard.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
|
||||
import { createAgentToolResultMiddlewareRunner } from "../harness/tool-result-middleware.js";
|
||||
import { createDeliveredMessagingResultReconciler } from "../messaging-tool-result-fallback.js";
|
||||
import type { AgentToolResult } from "../runtime/index.js";
|
||||
import type { ExtensionFactory, SessionManager } from "../sessions/index.js";
|
||||
import { isToolResultError } from "../tool-result-error.js";
|
||||
@@ -80,24 +79,16 @@ function buildAgentToolResultMiddlewareFactory(
|
||||
const adjustedInput = eventToolCallId
|
||||
? peekAdjustedParamsForToolCall(eventToolCallId, runId)
|
||||
: undefined;
|
||||
const args = recordFromUnknown(adjustedInput ?? event.input);
|
||||
const reconcileMiddlewareResult = createDeliveredMessagingResultReconciler({
|
||||
toolName: event.toolName,
|
||||
args,
|
||||
rawResult: current,
|
||||
rawIsError: event.isError === true || inputHadErrorStatus,
|
||||
});
|
||||
const middlewareResult = await runner.applyToolResultMiddleware({
|
||||
const result = await runner.applyToolResultMiddleware({
|
||||
threadId: event.threadId,
|
||||
turnId: event.turnId,
|
||||
toolCallId,
|
||||
toolName: event.toolName,
|
||||
args,
|
||||
args: recordFromUnknown(adjustedInput ?? event.input),
|
||||
cwd: ctx.cwd,
|
||||
isError: event.isError,
|
||||
result: current,
|
||||
});
|
||||
const result = reconcileMiddlewareResult(middlewareResult);
|
||||
const isError = event.isError === true || inputHadErrorStatus || isToolResultError(result);
|
||||
if (eventToolCallId) {
|
||||
finalizeToolTerminalPresentation({
|
||||
|
||||
@@ -550,6 +550,70 @@ describe("createAgentToolResultMiddlewareRunner", () => {
|
||||
expect(sanitized.originalSizeBytes ?? 0).toBeGreaterThan(100_000);
|
||||
});
|
||||
|
||||
it("snapshots confirmed delivery before oversized details are collapsed", async () => {
|
||||
const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [
|
||||
() => {
|
||||
throw new Error("post-processing failed");
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await runner.applyToolResultMiddleware({
|
||||
toolCallId: "call-1",
|
||||
toolName: "message",
|
||||
args: { action: "send", target: "C123" },
|
||||
result: {
|
||||
content: [{ type: "text", text: "raw result must stay private" }],
|
||||
details: {
|
||||
ok: true,
|
||||
result: { messageId: "1700000000.000100", channelId: "C123" },
|
||||
raw: "x".repeat(200_000),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
content: [{ type: "text", text: "Message delivered, but result post-processing failed." }],
|
||||
details: {
|
||||
ok: true,
|
||||
deliveryStatus: "sent",
|
||||
middlewareWarning: "post-processing failed",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves confirmed delivery when middleware returns an explicit failure", async () => {
|
||||
const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [
|
||||
() => ({
|
||||
result: {
|
||||
content: [{ type: "text", text: "post-processing failed" }],
|
||||
details: { status: "error", middlewareError: true },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const result = await runner.applyToolResultMiddleware({
|
||||
toolCallId: "call-1",
|
||||
toolName: "message",
|
||||
args: { action: "send", target: "C123" },
|
||||
result: {
|
||||
content: [{ type: "text", text: "raw result must stay private" }],
|
||||
details: {
|
||||
ok: true,
|
||||
result: { messageId: "1700000000.000100", channelId: "C123" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
content: [{ type: "text", text: "Message delivered, but result post-processing failed." }],
|
||||
details: {
|
||||
ok: true,
|
||||
deliveryStatus: "sent",
|
||||
middlewareWarning: "post-processing failed",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts well-formed middleware results", async () => {
|
||||
const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [
|
||||
(eventValue, ctx) => ({
|
||||
|
||||
@@ -11,6 +11,12 @@ import type {
|
||||
} from "../../plugins/agent-tool-result-middleware-types.js";
|
||||
import { createLazyPromiseLoader } from "../../shared/lazy-promise.js";
|
||||
import { truncateUtf16Safe } from "../../utils.js";
|
||||
import {
|
||||
hasMessagingDeliveryReceipt,
|
||||
isDeliveredMessagingToolResult,
|
||||
} from "../embedded-agent-message-tool-source-reply.js";
|
||||
import { isMessagingToolSendAction } from "../embedded-agent-messaging.js";
|
||||
import { isToolResultError } from "../tool-result-error.js";
|
||||
|
||||
const log = createSubsystemLogger("agents/harness");
|
||||
const MAX_MIDDLEWARE_CONTENT_BLOCKS = 200;
|
||||
@@ -429,6 +435,42 @@ function buildMiddlewareFailureResult(): OpenClawAgentToolResult {
|
||||
};
|
||||
}
|
||||
|
||||
function buildDeliveredMessagingFailureFallback(
|
||||
event: AgentToolResultMiddlewareEvent,
|
||||
result: OpenClawAgentToolResult,
|
||||
): OpenClawAgentToolResult | undefined {
|
||||
if (
|
||||
event.isError === true ||
|
||||
isToolResultError(result) ||
|
||||
!isMessagingToolSendAction(event.toolName, event.args) ||
|
||||
!isDeliveredMessagingToolResult({
|
||||
toolName: event.toolName,
|
||||
args: event.args,
|
||||
result,
|
||||
}) ||
|
||||
!hasMessagingDeliveryReceipt(result)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: "Message delivered, but result post-processing failed." }],
|
||||
details: {
|
||||
ok: true,
|
||||
deliveryStatus: "sent",
|
||||
middlewareWarning: "post-processing failed",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function reconcileDeliveredMessagingFailure(
|
||||
result: OpenClawAgentToolResult,
|
||||
fallback: OpenClawAgentToolResult | undefined,
|
||||
): OpenClawAgentToolResult {
|
||||
return fallback && isRecord(result.details) && result.details.middlewareError === true
|
||||
? fallback
|
||||
: result;
|
||||
}
|
||||
|
||||
export function createAgentToolResultMiddlewareRunner(
|
||||
ctx: AgentToolResultMiddlewareContext,
|
||||
handlers?: AgentToolResultMiddleware[],
|
||||
@@ -461,6 +503,12 @@ export function createAgentToolResultMiddlewareRunner(
|
||||
if (handlersForRun.length === 0) {
|
||||
return event.result;
|
||||
}
|
||||
// Snapshot the confirmed side effect before legacy middleware can mutate
|
||||
// or sanitization can collapse the receipt; never expose the raw result.
|
||||
const deliveredMessagingFallback = buildDeliveredMessagingFailureFallback(
|
||||
event,
|
||||
event.result,
|
||||
);
|
||||
let current = sanitizeToolResultForMiddleware(event.result);
|
||||
for (const handler of handlersForRun) {
|
||||
try {
|
||||
@@ -479,7 +527,10 @@ export function createAgentToolResultMiddlewareRunner(
|
||||
120,
|
||||
)}`,
|
||||
);
|
||||
return buildMiddlewareFailureResult();
|
||||
return reconcileDeliveredMessagingFailure(
|
||||
buildMiddlewareFailureResult(),
|
||||
deliveredMessagingFallback,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
log.warn(
|
||||
@@ -488,10 +539,13 @@ export function createAgentToolResultMiddlewareRunner(
|
||||
120,
|
||||
)}`,
|
||||
);
|
||||
return buildMiddlewareFailureResult();
|
||||
return reconcileDeliveredMessagingFailure(
|
||||
buildMiddlewareFailureResult(),
|
||||
deliveredMessagingFallback,
|
||||
);
|
||||
}
|
||||
}
|
||||
return current;
|
||||
return reconcileDeliveredMessagingFailure(current, deliveredMessagingFallback);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import type { OpenClawAgentToolResult } from "../plugins/agent-tool-result-middleware-types.js";
|
||||
import {
|
||||
hasMessagingDeliveryReceipt,
|
||||
isDeliveredMessagingToolResult,
|
||||
} from "./embedded-agent-message-tool-source-reply.js";
|
||||
import { isMessagingToolSendAction } from "./embedded-agent-messaging.js";
|
||||
import { readToolResultDetails } from "./tool-result-error.js";
|
||||
|
||||
/** Snapshots confirmed delivery before middleware can mutate the raw result in place. */
|
||||
export function createDeliveredMessagingResultReconciler(params: {
|
||||
toolName: string;
|
||||
args: Record<string, unknown>;
|
||||
rawResult: OpenClawAgentToolResult;
|
||||
rawIsError: boolean;
|
||||
}): (middlewareResult: OpenClawAgentToolResult) => OpenClawAgentToolResult {
|
||||
const confirmedDelivery =
|
||||
!params.rawIsError &&
|
||||
isMessagingToolSendAction(params.toolName, params.args) &&
|
||||
isDeliveredMessagingToolResult({
|
||||
toolName: params.toolName,
|
||||
args: params.args,
|
||||
result: params.rawResult,
|
||||
}) &&
|
||||
hasMessagingDeliveryReceipt(params.rawResult);
|
||||
const fallback: OpenClawAgentToolResult = {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Message delivered, but result post-processing failed.",
|
||||
},
|
||||
],
|
||||
details: {
|
||||
ok: true,
|
||||
deliveryStatus: "sent",
|
||||
middlewareWarning: "post-processing failed",
|
||||
},
|
||||
};
|
||||
return (middlewareResult) =>
|
||||
confirmedDelivery && readToolResultDetails(middlewareResult)?.middlewareError === true
|
||||
? fallback
|
||||
: middlewareResult;
|
||||
}
|
||||
@@ -289,7 +289,6 @@ export {
|
||||
} from "../agents/harness/prompt-compaction-hook-helpers.js";
|
||||
export { createCodexAppServerToolResultExtensionRunner } from "../agents/harness/codex-app-server-extensions.js";
|
||||
export { createAgentToolResultMiddlewareRunner } from "../agents/harness/tool-result-middleware.js";
|
||||
export { createDeliveredMessagingResultReconciler } from "../agents/messaging-tool-result-fallback.js";
|
||||
export {
|
||||
assertContextEngineHostSupport,
|
||||
CODEX_APP_SERVER_CONTEXT_ENGINE_HOST,
|
||||
|
||||
Reference in New Issue
Block a user