




















@@ -22,9 +22,15 @@ import {
2222collectPresentOpenClawTools,
2323isUpdatePlanToolEnabledForOpenClawTools,
2424} from "./openclaw-tools.registration.js";
25+import {
26+type HookContext,
27+isToolWrappedWithBeforeToolCallHook,
28+wrapToolWithBeforeToolCallHook,
29+} from "./pi-tools.before-tool-call.js";
2530import type { SandboxFsBridge } from "./sandbox/fs-bridge.js";
2631import type { SpawnedToolContext } from "./spawned-context.js";
2732import type { ToolFsPolicy } from "./tool-fs-policy.js";
33+import { resolveToolLoopDetectionConfig } from "./tool-loop-detection-config.js";
2834import { createAgentsListTool } from "./tools/agents-list-tool.js";
2935import type { AnyAgentTool } from "./tools/common.js";
3036import { createCronTool } from "./tools/cron-tool.js";
@@ -117,6 +123,14 @@ export function createOpenClawTools(
117123enableHeartbeatTool?: boolean;
118124/** If true, skip plugin tool resolution and return only shipped core tools. */
119125disablePluginTools?: boolean;
126+/**
127+ * Wrap returned tools with the before_tool_call hook at construction time.
128+ * Defaults to true; callers that already enforce the hook at a later shared
129+ * boundary should opt out explicitly.
130+ */
131+wrapBeforeToolCallHook?: boolean;
132+/** Override or extend the default hook context used by construction-time wrapping. */
133+beforeToolCallHookContext?: HookContext;
120134/** Records hot-path tool-prep stages for reply startup diagnostics. */
121135recordToolPrepStage?: (name: string) => void;
122136/** Trusted sender id from inbound context (not tool args). */
@@ -413,23 +427,45 @@ export function createOpenClawTools(
413427 ...collectPresentOpenClawTools([webSearchTool, webFetchTool, imageTool, pdfTool]),
414428];
415429options?.recordToolPrepStage?.("openclaw-tools:core-tool-list");
416-417-if (options?.disablePluginTools) {
418-return tools;
430+let allTools = tools;
431+if (!options?.disablePluginTools) {
432+const existingToolNames = new Set<string>();
433+for (const tool of tools) {
434+existingToolNames.add(tool.name);
435+}
436+allTools = [
437+ ...tools,
438+ ...resolveOpenClawPluginToolsForOptions({
439+ options,
440+ resolvedConfig,
441+ existingToolNames,
442+}),
443+];
444+options?.recordToolPrepStage?.("openclaw-tools:plugin-tools");
419445}
420446421-const existingToolNames = new Set<string>();
422-for (const tool of tools) {
423-existingToolNames.add(tool.name);
447+if (options?.wrapBeforeToolCallHook === false) {
448+return allTools;
424449}
425-const wrappedPluginTools = resolveOpenClawPluginToolsForOptions({
426- options,
427- resolvedConfig,
428- existingToolNames,
429-});
430-options?.recordToolPrepStage?.("openclaw-tools:plugin-tools");
431-432-return [...tools, ...wrappedPluginTools];
450+const hookAgentId = options?.requesterAgentIdOverride ?? sessionAgentId;
451+const defaultHookContext: HookContext = {
452+ ...(hookAgentId ? { agentId: hookAgentId } : {}),
453+ ...(resolvedConfig ? { config: resolvedConfig } : {}),
454+ ...(options?.agentSessionKey ? { sessionKey: options.agentSessionKey } : {}),
455+ ...(options?.sessionId ? { sessionId: options.sessionId } : {}),
456+ ...(options?.currentChannelId ? { channelId: options.currentChannelId } : {}),
457+loopDetection: resolveToolLoopDetectionConfig({ cfg: resolvedConfig, agentId: hookAgentId }),
458+};
459+const hookContext = {
460+ ...defaultHookContext,
461+ ...options?.beforeToolCallHookContext,
462+};
463+options?.recordToolPrepStage?.("openclaw-tools:tool-hooks");
464+return allTools.map((tool) =>
465+isToolWrappedWithBeforeToolCallHook(tool)
466+ ? tool
467+ : wrapToolWithBeforeToolCallHook(tool, hookContext),
468+);
433469}
434470435471export const __testing = {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。