Skip to content

Commit da5fe99

Browse files
committed
fix(codex): report quarantined dynamic tools
1 parent 40bca6d commit da5fe99

2 files changed

Lines changed: 67 additions & 11 deletions

File tree

extensions/codex/src/app-server/dynamic-tools.test.ts

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
2+
import {
3+
onInternalDiagnosticEvent,
4+
waitForDiagnosticEventsDrained,
5+
type DiagnosticEventPayload,
6+
} from "openclaw/plugin-sdk/diagnostic-runtime";
27
import type { AnyAgentTool } from "openclaw/plugin-sdk/agent-harness";
38
import {
49
HEARTBEAT_RESPONSE_TOOL_NAME,
@@ -293,18 +298,33 @@ describe("createCodexDynamicToolBridge", () => {
293298

294299
it("quarantines dynamic tools with unsupported input schemas", async () => {
295300
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
301+
const diagnosticEvents: DiagnosticEventPayload[] = [];
302+
const unsubscribeDiagnostics = onInternalDiagnosticEvent((event) =>
303+
diagnosticEvents.push(event),
304+
);
296305
const badExecute = vi.fn();
297-
const bridge = createCodexDynamicToolBridge({
298-
tools: [
299-
createTool({ name: "message" }),
300-
createTool({
301-
name: "dofbot_move_angles",
302-
parameters: { type: "array", items: { type: "number" } },
303-
execute: badExecute,
304-
}),
305-
],
306-
signal: new AbortController().signal,
307-
});
306+
let bridge!: ReturnType<typeof createCodexDynamicToolBridge>;
307+
try {
308+
bridge = createCodexDynamicToolBridge({
309+
tools: [
310+
createTool({ name: "message" }),
311+
createTool({
312+
name: "dofbot_move_angles",
313+
parameters: { type: "array", items: { type: "number" } },
314+
execute: badExecute,
315+
}),
316+
],
317+
signal: new AbortController().signal,
318+
hookContext: {
319+
runId: "run-1",
320+
sessionId: "session-1",
321+
sessionKey: "agent:main:session-1",
322+
},
323+
});
324+
await waitForDiagnosticEventsDrained();
325+
} finally {
326+
unsubscribeDiagnostics();
327+
}
308328

309329
expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
310330
expect(bridge.specs.map((tool) => tool.name)).toEqual(["message"]);
@@ -325,6 +345,23 @@ describe("createCodexDynamicToolBridge", () => {
325345
],
326346
}),
327347
);
348+
const blockedEvents = diagnosticEvents.filter(
349+
(
350+
event,
351+
): event is Extract<DiagnosticEventPayload, { type: "tool.execution.blocked" }> =>
352+
event.type === "tool.execution.blocked",
353+
);
354+
expect(blockedEvents).toContainEqual(
355+
expect.objectContaining({
356+
type: "tool.execution.blocked",
357+
runId: "run-1",
358+
sessionId: "session-1",
359+
sessionKey: "agent:main:session-1",
360+
toolName: "dofbot_move_angles",
361+
deniedReason: "unsupported_tool_schema",
362+
reason: 'dofbot_move_angles.inputSchema.type must be "object"',
363+
}),
364+
);
328365

329366
const result = await bridge.handleToolCall({
330367
threadId: "thread-1",

extensions/codex/src/app-server/dynamic-tools.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
2+
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
23
import {
34
createAgentToolResultMiddlewareRunner,
45
createCodexAppServerToolResultExtensionRunner,
@@ -118,6 +119,7 @@ export function createCodexDynamicToolBridge(params: {
118119
...registeredProjection.quarantinedTools,
119120
]);
120121
warnQuarantinedDynamicTools(quarantinedTools);
122+
emitQuarantinedDynamicToolDiagnostics(quarantinedTools, params.hookContext);
121123
const telemetry: CodexDynamicToolBridge["telemetry"] = {
122124
didSendViaMessagingTool: false,
123125
messagingToolSentTexts: [],
@@ -337,6 +339,23 @@ function warnQuarantinedDynamicTools(tools: readonly CodexDynamicToolSchemaQuara
337339
);
338340
}
339341

342+
function emitQuarantinedDynamicToolDiagnostics(
343+
tools: readonly CodexDynamicToolSchemaQuarantine[],
344+
ctx: CodexDynamicToolHookContext | undefined,
345+
): void {
346+
for (const tool of tools) {
347+
emitTrustedDiagnosticEvent({
348+
type: "tool.execution.blocked",
349+
runId: ctx?.runId,
350+
sessionId: ctx?.sessionId,
351+
sessionKey: ctx?.sessionKey,
352+
toolName: tool.tool,
353+
deniedReason: "unsupported_tool_schema",
354+
reason: tool.violations.join(", "),
355+
});
356+
}
357+
}
358+
340359
function dedupeQuarantinedDynamicTools(
341360
tools: readonly CodexDynamicToolSchemaQuarantine[],
342361
): CodexDynamicToolSchemaQuarantine[] {

0 commit comments

Comments
 (0)