mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(agents): preserve code mode hook context (#83481)
This commit is contained in:
committed by
GitHub
parent
322f0bb7bc
commit
69aec10852
@@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/code mode: preserve agent, session, run, and channel context in `before_tool_call` hooks for top-level `exec`/`wait` dispatches. Fixes #83387.
|
||||
- Replies: keep final payload delivery after live preview updates so channels can finalize or send the completed answer instead of losing preview-only drafts. (#83468)
|
||||
- Providers/Xiaomi: replay MiMo Anthropic-compatible `reasoning_content` as provider-required thinking blocks even when OpenClaw thinking is disabled, fixing follow-up tool turns for `mimo-v2-flash`. Fixes #83407. Thanks @Xgenious7.
|
||||
- Agents/exec approvals: forward approval-runtime credentials on agent-owned Gateway approval calls so approved async commands complete through the existing runtime path instead of stalling on unauthenticated follow-up calls. Thanks @IWhatsskill, @Patrick-Erichsen, and @jesse-merhi.
|
||||
|
||||
@@ -1061,6 +1061,15 @@ async function compactEmbeddedPiSessionDirectOnce(
|
||||
const { customTools } = splitSdkTools({
|
||||
tools: effectiveTools,
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
toolHookContext: {
|
||||
agentId: sessionAgentId,
|
||||
config: params.config,
|
||||
cwd: effectiveWorkspace,
|
||||
sessionKey: sandboxSessionKey,
|
||||
sessionId: params.sessionId,
|
||||
runId: params.runId,
|
||||
channelId: params.currentChannelId,
|
||||
},
|
||||
});
|
||||
// Pi treats `tools` as a name allowlist during session creation. Pass the
|
||||
// exact OpenClaw-managed registrations so custom tools survive startup.
|
||||
|
||||
@@ -2179,6 +2179,7 @@ export async function runEmbeddedAttempt(
|
||||
const { customTools } = splitSdkTools({
|
||||
tools: effectiveTools,
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
toolHookContext: catalogToolHookContext,
|
||||
});
|
||||
|
||||
// Add client tools (OpenResponses hosted tools) to customTools.
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { toToolDefinitions } from "../pi-tool-definition-adapter.js";
|
||||
import type { HookContext } from "../pi-tools.before-tool-call.js";
|
||||
|
||||
// We always pass tools via `customTools` so our policy filtering, sandbox integration,
|
||||
// and extended toolset remain consistent across providers.
|
||||
type AnyAgentTool = AgentTool;
|
||||
|
||||
export function splitSdkTools(options: { tools: AnyAgentTool[]; sandboxEnabled: boolean }): {
|
||||
export function splitSdkTools(options: {
|
||||
tools: AnyAgentTool[];
|
||||
sandboxEnabled: boolean;
|
||||
toolHookContext?: HookContext;
|
||||
}): {
|
||||
customTools: ReturnType<typeof toToolDefinitions>;
|
||||
} {
|
||||
const { tools } = options;
|
||||
const { tools, toolHookContext } = options;
|
||||
return {
|
||||
customTools: toToolDefinitions(tools),
|
||||
customTools: toToolDefinitions(tools, toolHookContext),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -223,7 +223,10 @@ export function isClientToolNameConflictError(err: unknown): err is Error {
|
||||
return err instanceof Error && err.message.startsWith(CLIENT_TOOL_NAME_CONFLICT_PREFIX);
|
||||
}
|
||||
|
||||
export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||
export function toToolDefinitions(
|
||||
tools: AnyAgentTool[],
|
||||
hookContext?: HookContext,
|
||||
): ToolDefinition[] {
|
||||
return tools.map((tool) => {
|
||||
const name = tool.name || "tool";
|
||||
const normalizedName = normalizeToolName(name);
|
||||
@@ -242,6 +245,7 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||
toolName: name,
|
||||
params,
|
||||
toolCallId,
|
||||
ctx: hookContext,
|
||||
});
|
||||
if (hookOutcome.blocked) {
|
||||
if (hookOutcome.kind === "veto") {
|
||||
|
||||
@@ -313,6 +313,46 @@ describe("before_tool_call hook deduplication (#15502)", () => {
|
||||
expect(beforeToolCallHook).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("passes hook context for unwrapped tool definitions", async () => {
|
||||
const execute = vi.fn().mockResolvedValue({ content: [], details: { ok: true } });
|
||||
const baseTool = { name: "exec", execute, description: "exec", parameters: {} } as any;
|
||||
const [def] = toToolDefinitions([baseTool], {
|
||||
agentId: "code-agent",
|
||||
sessionKey: "agent:code-agent:main",
|
||||
sessionId: "session-code",
|
||||
runId: "run-code",
|
||||
channelId: "channel-code",
|
||||
});
|
||||
const extensionContext = {} as Parameters<typeof def.execute>[4];
|
||||
|
||||
await def.execute(
|
||||
"call-code-exec",
|
||||
{ code: "echo hi" },
|
||||
undefined,
|
||||
undefined,
|
||||
extensionContext,
|
||||
);
|
||||
|
||||
expect(beforeToolCallHook).toHaveBeenCalledTimes(1);
|
||||
expect(beforeToolCallHook).toHaveBeenCalledWith(
|
||||
{
|
||||
toolName: "exec",
|
||||
params: { code: "echo hi" },
|
||||
runId: "run-code",
|
||||
toolCallId: "call-code-exec",
|
||||
},
|
||||
{
|
||||
toolName: "exec",
|
||||
agentId: "code-agent",
|
||||
sessionKey: "agent:code-agent:main",
|
||||
sessionId: "session-code",
|
||||
runId: "run-code",
|
||||
toolCallId: "call-code-exec",
|
||||
channelId: "channel-code",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves the hook marker when abort wrapping a hooked tool", () => {
|
||||
const execute = vi.fn().mockResolvedValue({ content: [], details: { ok: true } });
|
||||
const baseTool = { name: "Bash", execute, description: "bash", parameters: {} } as any;
|
||||
|
||||
Reference in New Issue
Block a user