mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 16:34:59 +00:00
fix(hooks): consolidate after_tool_call context + single-fire behavior (#32201)
* fix(hooks): deduplicate after_tool_call hook in embedded runs (cherry picked from commitc129a1a74b) * fix(hooks): propagate sessionKey in after_tool_call context The after_tool_call hook in handleToolExecutionEnd was passing `sessionKey: undefined` in the ToolContext, even though the value is available on ctx.params. This broke plugins that need session context in after_tool_call handlers (e.g., for per-session audit trails or security logging). - Add `sessionKey` to the `ToolHandlerParams` Pick type - Pass `ctx.params.sessionKey` through to the hook context - Add test assertion to prevent regression Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> (cherry picked from commitb7117384fc) * fix(hooks): thread agentId through to after_tool_call hook context Follow-up to #30511 — the after_tool_call hook context was passing `agentId: undefined` because SubscribeEmbeddedPiSessionParams did not carry the agent identity. This threads sessionAgentId (resolved in attempt.ts) through the session params into the tool handler context, giving plugins accurate agent-scoped context for both before_tool_call and after_tool_call hooks. Changes: - Add `agentId?: string` to SubscribeEmbeddedPiSessionParams - Add "agentId" to ToolHandlerParams Pick type - Pass `agentId: sessionAgentId` at the subscribeEmbeddedPiSession() call site in attempt.ts - Wire ctx.params.agentId into the after_tool_call hook context - Update tests to assert agentId propagation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> (cherry picked from commitaad01edd3e) * changelog: credit after_tool_call hook contributors * Update CHANGELOG.md * agents: preserve adjusted params until tool end * agents: emit after_tool_call with adjusted args * tests: cover adjusted after_tool_call params * tests: align adapter after_tool_call expectation --------- Co-authored-by: jbeno <jim@jimbeno.net> Co-authored-by: scoootscooob <zhentongfan@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,12 +5,10 @@ import type {
|
||||
} from "@mariozechner/pi-agent-core";
|
||||
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
||||
import { logDebug, logError } from "../logger.js";
|
||||
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
||||
import { isPlainObject } from "../utils.js";
|
||||
import type { ClientToolDefinition } from "./pi-embedded-runner/run/params.js";
|
||||
import type { HookContext } from "./pi-tools.before-tool-call.js";
|
||||
import {
|
||||
consumeAdjustedParamsForToolCall,
|
||||
isToolWrappedWithBeforeToolCallHook,
|
||||
runBeforeToolCallHook,
|
||||
} from "./pi-tools.before-tool-call.js";
|
||||
@@ -166,29 +164,6 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||
toolName: normalizedName,
|
||||
result: rawResult,
|
||||
});
|
||||
const afterParams = beforeHookWrapped
|
||||
? (consumeAdjustedParamsForToolCall(toolCallId) ?? executeParams)
|
||||
: executeParams;
|
||||
|
||||
// Call after_tool_call hook
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
if (hookRunner?.hasHooks("after_tool_call")) {
|
||||
try {
|
||||
await hookRunner.runAfterToolCall(
|
||||
{
|
||||
toolName: name,
|
||||
params: isPlainObject(afterParams) ? afterParams : {},
|
||||
result,
|
||||
},
|
||||
{ toolName: name },
|
||||
);
|
||||
} catch (hookErr) {
|
||||
logDebug(
|
||||
`after_tool_call hook failed: tool=${normalizedName} error=${String(hookErr)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (signal?.aborted) {
|
||||
@@ -201,41 +176,17 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||
if (name === "AbortError") {
|
||||
throw err;
|
||||
}
|
||||
if (beforeHookWrapped) {
|
||||
consumeAdjustedParamsForToolCall(toolCallId);
|
||||
}
|
||||
const described = describeToolExecutionError(err);
|
||||
if (described.stack && described.stack !== described.message) {
|
||||
logDebug(`tools: ${normalizedName} failed stack:\n${described.stack}`);
|
||||
}
|
||||
logError(`[tools] ${normalizedName} failed: ${described.message}`);
|
||||
|
||||
const errorResult = jsonResult({
|
||||
return jsonResult({
|
||||
status: "error",
|
||||
tool: normalizedName,
|
||||
error: described.message,
|
||||
});
|
||||
|
||||
// Call after_tool_call hook for errors too
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
if (hookRunner?.hasHooks("after_tool_call")) {
|
||||
try {
|
||||
await hookRunner.runAfterToolCall(
|
||||
{
|
||||
toolName: normalizedName,
|
||||
params: isPlainObject(params) ? params : {},
|
||||
error: described.message,
|
||||
},
|
||||
{ toolName: normalizedName },
|
||||
);
|
||||
} catch (hookErr) {
|
||||
logDebug(
|
||||
`after_tool_call hook failed: tool=${normalizedName} error=${String(hookErr)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return errorResult;
|
||||
}
|
||||
},
|
||||
} satisfies ToolDefinition;
|
||||
|
||||
Reference in New Issue
Block a user