From 8ff1d3e67bbc0b71e425e8aad46bdb64ee1351ba Mon Sep 17 00:00:00 2001 From: Vincent Koc <25068+vincentkoc@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:49:43 +0800 Subject: [PATCH] fix(harness): surface returned tool errors --- extensions/copilot/src/tool-bridge.test.ts | 11 ++++++++++- extensions/copilot/src/tool-bridge.ts | 9 +++++---- src/plugin-sdk/agent-harness-runtime.ts | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/extensions/copilot/src/tool-bridge.test.ts b/extensions/copilot/src/tool-bridge.test.ts index 17044ba7fcd6..7048541b1ec0 100644 --- a/extensions/copilot/src/tool-bridge.test.ts +++ b/extensions/copilot/src/tool-bridge.test.ts @@ -1314,17 +1314,20 @@ describe("convertOpenClawToolToSdkTool", () => { ); }); - it("reports returned OpenClaw error results as observer failures", async () => { + it("reports returned OpenClaw error results to both tool observers", async () => { const onAgentToolResult = vi.fn(); + const onToolCompleted = vi.fn(); const sourceResult = { content: [{ text: '{"status":"error","error":"backend unavailable"}', type: "text" }], details: { status: "error", error: "backend unavailable" }, }; const sdkTool = convertOpenClawToolToSdkTool(makeTool({}, sourceResult), { onAgentToolResult, + onToolCompleted, }); const result = await runSdkTool(sdkTool, {}); + await flushAsync(); expect(result).toMatchObject({ resultType: "success" }); expect(onAgentToolResult).toHaveBeenCalledWith({ @@ -1332,6 +1335,12 @@ describe("convertOpenClawToolToSdkTool", () => { result: sourceResult, isError: true, }); + expect(onToolCompleted).toHaveBeenCalledWith( + expect.objectContaining({ + error: "backend unavailable", + result: sourceResult, + }), + ); }); it("joins multiple text blocks with newlines", async () => { diff --git a/extensions/copilot/src/tool-bridge.ts b/extensions/copilot/src/tool-bridge.ts index c5a2ab1f4f24..d959238d24d0 100644 --- a/extensions/copilot/src/tool-bridge.ts +++ b/extensions/copilot/src/tool-bridge.ts @@ -8,6 +8,7 @@ import type { import { applyEmbeddedAttemptToolsAllow, buildEmbeddedAttemptToolRunContext, + extractToolErrorMessage, getPluginToolMeta, isSubagentSessionKey, isToolResultError, @@ -515,15 +516,15 @@ export function convertOpenClawToolToSdkTool( const sdkResult = agentToolResultToSdk(result); const sanitizedResult = sanitizeToolResult(result); - notifyToolResult( - sanitizedResult, - sdkResult.resultType === "failure" || isToolResultError(sanitizedResult), - ); + const resultIsError = sdkResult.resultType === "failure" || isToolResultError(sanitizedResult); + const resultError = resultIsError ? extractToolErrorMessage(sanitizedResult) : undefined; + notifyToolResult(sanitizedResult, resultIsError); notifyToolCompleted({ toolName: sourceTool.name, toolCallId: invocation.toolCallId, args: toToolStartArgs(preparedArgs), result: sanitizedResult, + ...(resultError ? { error: resultError } : {}), startedAt, }); return sdkResult; diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index bb89fd905521..a5c23eb36dfa 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -131,6 +131,7 @@ export { isMessagingTool, isMessagingToolSendAction } from "../agents/embedded-a export { extractMessagingToolSend, extractMessagingToolSendResult, + extractToolErrorMessage, extractToolResultMediaArtifact, filterToolResultMediaUrls, isToolResultError,