fix(codex): report quarantined dynamic tools

This commit is contained in:
Vincent Koc
2026-05-28 00:44:16 +02:00
parent 40bca6d8bb
commit da5fe990d8
2 changed files with 67 additions and 11 deletions

View File

@@ -1,4 +1,9 @@
import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
import {
onInternalDiagnosticEvent,
waitForDiagnosticEventsDrained,
type DiagnosticEventPayload,
} from "openclaw/plugin-sdk/diagnostic-runtime";
import type { AnyAgentTool } from "openclaw/plugin-sdk/agent-harness";
import {
HEARTBEAT_RESPONSE_TOOL_NAME,
@@ -293,18 +298,33 @@ describe("createCodexDynamicToolBridge", () => {
it("quarantines dynamic tools with unsupported input schemas", async () => {
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
const diagnosticEvents: DiagnosticEventPayload[] = [];
const unsubscribeDiagnostics = onInternalDiagnosticEvent((event) =>
diagnosticEvents.push(event),
);
const badExecute = vi.fn();
const bridge = createCodexDynamicToolBridge({
tools: [
createTool({ name: "message" }),
createTool({
name: "dofbot_move_angles",
parameters: { type: "array", items: { type: "number" } },
execute: badExecute,
}),
],
signal: new AbortController().signal,
});
let bridge!: ReturnType<typeof createCodexDynamicToolBridge>;
try {
bridge = createCodexDynamicToolBridge({
tools: [
createTool({ name: "message" }),
createTool({
name: "dofbot_move_angles",
parameters: { type: "array", items: { type: "number" } },
execute: badExecute,
}),
],
signal: new AbortController().signal,
hookContext: {
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
},
});
await waitForDiagnosticEventsDrained();
} finally {
unsubscribeDiagnostics();
}
expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
expect(bridge.specs.map((tool) => tool.name)).toEqual(["message"]);
@@ -325,6 +345,23 @@ describe("createCodexDynamicToolBridge", () => {
],
}),
);
const blockedEvents = diagnosticEvents.filter(
(
event,
): event is Extract<DiagnosticEventPayload, { type: "tool.execution.blocked" }> =>
event.type === "tool.execution.blocked",
);
expect(blockedEvents).toContainEqual(
expect.objectContaining({
type: "tool.execution.blocked",
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
toolName: "dofbot_move_angles",
deniedReason: "unsupported_tool_schema",
reason: 'dofbot_move_angles.inputSchema.type must be "object"',
}),
);
const result = await bridge.handleToolCall({
threadId: "thread-1",

View File

@@ -1,4 +1,5 @@
import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
import {
createAgentToolResultMiddlewareRunner,
createCodexAppServerToolResultExtensionRunner,
@@ -118,6 +119,7 @@ export function createCodexDynamicToolBridge(params: {
...registeredProjection.quarantinedTools,
]);
warnQuarantinedDynamicTools(quarantinedTools);
emitQuarantinedDynamicToolDiagnostics(quarantinedTools, params.hookContext);
const telemetry: CodexDynamicToolBridge["telemetry"] = {
didSendViaMessagingTool: false,
messagingToolSentTexts: [],
@@ -337,6 +339,23 @@ function warnQuarantinedDynamicTools(tools: readonly CodexDynamicToolSchemaQuara
);
}
function emitQuarantinedDynamicToolDiagnostics(
tools: readonly CodexDynamicToolSchemaQuarantine[],
ctx: CodexDynamicToolHookContext | undefined,
): void {
for (const tool of tools) {
emitTrustedDiagnosticEvent({
type: "tool.execution.blocked",
runId: ctx?.runId,
sessionId: ctx?.sessionId,
sessionKey: ctx?.sessionKey,
toolName: tool.tool,
deniedReason: "unsupported_tool_schema",
reason: tool.violations.join(", "),
});
}
}
function dedupeQuarantinedDynamicTools(
tools: readonly CodexDynamicToolSchemaQuarantine[],
): CodexDynamicToolSchemaQuarantine[] {