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>
This commit is contained in:
scoootscooob 2026-03-01 01:54:41 -08:00
parent b7117384fc
commit aad01edd3e
5 changed files with 10 additions and 2 deletions

View File

@ -1186,6 +1186,7 @@ export async function runEmbeddedAttempt(
enforceFinalTag: params.enforceFinalTag,
config: params.config,
sessionKey: params.sessionKey ?? params.sessionId,
agentId: sessionAgentId,
});
const {

View File

@ -426,7 +426,7 @@ export async function handleToolExecutionEnd(
void hookRunnerAfter
.runAfterToolCall(hookEvent, {
toolName,
agentId: undefined,
agentId: ctx.params.agentId,
sessionKey: ctx.params.sessionKey,
})
.catch((err) => {

View File

@ -132,7 +132,7 @@ export type EmbeddedPiSubscribeContext = {
*/
export type ToolHandlerParams = Pick<
SubscribeEmbeddedPiSessionParams,
"runId" | "onBlockReplyFlush" | "onAgentEvent" | "onToolResult" | "sessionKey"
"runId" | "onBlockReplyFlush" | "onAgentEvent" | "onToolResult" | "sessionKey" | "agentId"
>;
export type ToolHandlerState = Pick<

View File

@ -31,6 +31,8 @@ export type SubscribeEmbeddedPiSessionParams = {
enforceFinalTag?: boolean;
config?: OpenClawConfig;
sessionKey?: string;
/** Agent identity for hook context — resolved from session config in attempt.ts. */
agentId?: string;
};
export type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";

View File

@ -127,6 +127,7 @@ describe("after_tool_call hook wiring", () => {
expect(event.error).toBeUndefined();
expect(typeof event.durationMs).toBe("number");
expect(context.toolName).toBe("read");
expect(context.agentId).toBe("main");
expect(context.sessionKey).toBe("test-session");
});
@ -166,6 +167,10 @@ describe("after_tool_call hook wiring", () => {
throw new Error("missing hook call payload");
}
expect(event.error).toBeDefined();
// agentId should be undefined when not provided
const context = firstCall?.[1] as { agentId?: string } | undefined;
expect(context?.agentId).toBeUndefined();
});
it("does not call runAfterToolCall when no hooks registered", async () => {