Compare commits
2 Commits
feat/volce
...
feat/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
949c09f408 | ||
|
|
59f9da02b4 |
@@ -454,6 +454,9 @@ export async function runEmbeddedAttempt(
|
||||
model: params.model,
|
||||
});
|
||||
|
||||
// Get hook runner early so it's available when creating tools
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
tools,
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
@@ -631,6 +634,7 @@ export async function runEmbeddedAttempt(
|
||||
const subscription = subscribeEmbeddedPiSession({
|
||||
session: activeSession,
|
||||
runId: params.runId,
|
||||
hookRunner: getGlobalHookRunner() ?? undefined,
|
||||
verboseLevel: params.verboseLevel,
|
||||
reasoningMode: params.reasoningLevel ?? "off",
|
||||
toolResultFormat: params.toolResultFormat,
|
||||
@@ -714,8 +718,7 @@ export async function runEmbeddedAttempt(
|
||||
}
|
||||
}
|
||||
|
||||
// Get hook runner once for both before_agent_start and agent_end hooks
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
// Hook runner was already obtained earlier before tool creation
|
||||
const hookAgentId =
|
||||
typeof params.agentId === "string" && params.agentId.trim()
|
||||
? normalizeAgentId(params.agentId)
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { AgentEvent } from "@mariozechner/pi-agent-core";
|
||||
import type { HookRunner } from "../plugins/hooks.js";
|
||||
import type {
|
||||
PluginHookAfterToolCallEvent,
|
||||
PluginHookBeforeToolCallEvent,
|
||||
} from "../plugins/types.js";
|
||||
import type { EmbeddedPiSubscribeContext } from "./pi-embedded-subscribe.handlers.types.js";
|
||||
import { emitAgentEvent } from "../infra/agent-events.js";
|
||||
import { normalizeTextForComparison } from "./pi-embedded-helpers.js";
|
||||
@@ -13,6 +18,10 @@ import {
|
||||
import { inferToolMetaFromArgs } from "./pi-embedded-utils.js";
|
||||
import { normalizeToolName } from "./tool-policy.js";
|
||||
|
||||
type OpenClawGlobal = typeof globalThis & {
|
||||
__openclawHookRunner?: HookRunner;
|
||||
};
|
||||
|
||||
function extendExecMeta(toolName: string, args: unknown, meta?: string): string | undefined {
|
||||
const normalized = toolName.trim().toLowerCase();
|
||||
if (normalized !== "exec" && normalized !== "bash") {
|
||||
@@ -51,6 +60,20 @@ export async function handleToolExecutionStart(
|
||||
const toolCallId = String(evt.toolCallId);
|
||||
const args = evt.args;
|
||||
|
||||
// Call before_tool_call hook
|
||||
const hookRunner = ctx.hookRunner ?? (globalThis as OpenClawGlobal).__openclawHookRunner;
|
||||
if (hookRunner?.hasHooks?.("before_tool_call")) {
|
||||
try {
|
||||
const hookEvent: PluginHookBeforeToolCallEvent = {
|
||||
toolName,
|
||||
params: args && typeof args === "object" ? (args as Record<string, unknown>) : {},
|
||||
};
|
||||
await hookRunner.runBeforeToolCall(hookEvent, { toolName });
|
||||
} catch (err) {
|
||||
ctx.log.debug(`before_tool_call hook failed: tool=${toolName} error=${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (toolName === "read") {
|
||||
const record = args && typeof args === "object" ? (args as Record<string, unknown>) : {};
|
||||
const filePath = typeof record.path === "string" ? record.path.trim() : "";
|
||||
@@ -145,7 +168,7 @@ export function handleToolExecutionUpdate(
|
||||
});
|
||||
}
|
||||
|
||||
export function handleToolExecutionEnd(
|
||||
export async function handleToolExecutionEnd(
|
||||
ctx: EmbeddedPiSubscribeContext,
|
||||
evt: AgentEvent & {
|
||||
toolName: string;
|
||||
@@ -220,6 +243,22 @@ export function handleToolExecutionEnd(
|
||||
`embedded run tool end: runId=${ctx.params.runId} tool=${toolName} toolCallId=${toolCallId}`,
|
||||
);
|
||||
|
||||
// Call after_tool_call hook
|
||||
const hookRunnerAfter = ctx.hookRunner ?? (globalThis as OpenClawGlobal).__openclawHookRunner;
|
||||
if (hookRunnerAfter?.hasHooks?.("after_tool_call")) {
|
||||
try {
|
||||
const hookEvent: PluginHookAfterToolCallEvent = {
|
||||
toolName,
|
||||
params: {}, // Input params not available in end handler; hook context has toolName
|
||||
result: sanitizedResult,
|
||||
error: isToolError ? extractToolErrorMessage(sanitizedResult) : undefined,
|
||||
};
|
||||
await hookRunnerAfter.runAfterToolCall(hookEvent, { toolName });
|
||||
} catch (err) {
|
||||
ctx.log.debug(`after_tool_call hook failed: tool=${toolName} error=${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.params.onToolResult && ctx.shouldEmitToolOutput()) {
|
||||
const outputText = extractToolResultText(sanitizedResult);
|
||||
if (outputText) {
|
||||
|
||||
@@ -42,7 +42,10 @@ export function createEmbeddedPiSessionEventHandler(ctx: EmbeddedPiSubscribeCont
|
||||
handleToolExecutionUpdate(ctx, evt as never);
|
||||
return;
|
||||
case "tool_execution_end":
|
||||
handleToolExecutionEnd(ctx, evt as never);
|
||||
// Async handler - best-effort, non-blocking
|
||||
handleToolExecutionEnd(ctx, evt as never).catch((err) => {
|
||||
ctx.log.debug(`tool_execution_end handler failed: ${String(err)}`);
|
||||
});
|
||||
return;
|
||||
case "agent_start":
|
||||
handleAgentStart(ctx);
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { AgentEvent, AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { ReplyDirectiveParseResult } from "../auto-reply/reply/reply-directives.js";
|
||||
import type { ReasoningLevel } from "../auto-reply/thinking.js";
|
||||
import type { InlineCodeState } from "../markdown/code-spans.js";
|
||||
import type { HookRunner } from "../plugins/hooks.js";
|
||||
import type { EmbeddedBlockChunker } from "./pi-embedded-block-chunker.js";
|
||||
import type { MessagingToolSend } from "./pi-embedded-messaging.js";
|
||||
import type {
|
||||
@@ -69,6 +70,7 @@ export type EmbeddedPiSubscribeContext = {
|
||||
log: EmbeddedSubscribeLogger;
|
||||
blockChunking?: BlockReplyChunking;
|
||||
blockChunker: EmbeddedBlockChunker | null;
|
||||
hookRunner?: HookRunner;
|
||||
|
||||
shouldEmitToolResult: () => boolean;
|
||||
shouldEmitToolOutput: () => boolean;
|
||||
|
||||
@@ -575,6 +575,7 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
||||
log,
|
||||
blockChunking,
|
||||
blockChunker,
|
||||
hookRunner: params.hookRunner,
|
||||
shouldEmitToolResult,
|
||||
shouldEmitToolOutput,
|
||||
emitToolSummary,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
||||
import type { ReasoningLevel, VerboseLevel } from "../auto-reply/thinking.js";
|
||||
import type { HookRunner } from "../plugins/hooks.js";
|
||||
import type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";
|
||||
|
||||
export type ToolResultFormat = "markdown" | "plain";
|
||||
@@ -7,6 +8,7 @@ export type ToolResultFormat = "markdown" | "plain";
|
||||
export type SubscribeEmbeddedPiSessionParams = {
|
||||
session: AgentSession;
|
||||
runId: string;
|
||||
hookRunner?: HookRunner;
|
||||
verboseLevel?: VerboseLevel;
|
||||
reasoningMode?: ReasoningLevel;
|
||||
toolResultFormat?: ToolResultFormat;
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
||||
import type { ClientToolDefinition } from "./pi-embedded-runner/run/params.js";
|
||||
import { logDebug, logError } from "../logger.js";
|
||||
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
||||
import { isPlainObject } from "../utils.js";
|
||||
import { runBeforeToolCallHook } from "./pi-tools.before-tool-call.js";
|
||||
import { normalizeToolName } from "./tool-policy.js";
|
||||
@@ -90,7 +91,38 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||
execute: async (...args: ToolExecuteArgs): Promise<AgentToolResult<unknown>> => {
|
||||
const { toolCallId, params, onUpdate, signal } = splitToolExecuteArgs(args);
|
||||
try {
|
||||
return await tool.execute(toolCallId, params, signal, onUpdate);
|
||||
// Call before_tool_call hook
|
||||
const hookOutcome = await runBeforeToolCallHook({
|
||||
toolName: name,
|
||||
params,
|
||||
toolCallId,
|
||||
});
|
||||
if (hookOutcome.blocked) {
|
||||
throw new Error(hookOutcome.reason);
|
||||
}
|
||||
const adjustedParams = hookOutcome.params;
|
||||
const result = await tool.execute(toolCallId, adjustedParams, signal, onUpdate);
|
||||
|
||||
// Call after_tool_call hook
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
if (hookRunner?.hasHooks("after_tool_call")) {
|
||||
try {
|
||||
await hookRunner.runAfterToolCall(
|
||||
{
|
||||
toolName: name,
|
||||
params: isPlainObject(adjustedParams) ? adjustedParams : {},
|
||||
result,
|
||||
},
|
||||
{ toolName: name },
|
||||
);
|
||||
} catch (hookErr) {
|
||||
logDebug(
|
||||
`after_tool_call hook failed: tool=${normalizedName} error=${String(hookErr)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (signal?.aborted) {
|
||||
throw err;
|
||||
@@ -107,11 +139,33 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||
logDebug(`tools: ${normalizedName} failed stack:\n${described.stack}`);
|
||||
}
|
||||
logError(`[tools] ${normalizedName} failed: ${described.message}`);
|
||||
return jsonResult({
|
||||
|
||||
const errorResult = 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