
























@@ -372,6 +372,116 @@ describe("createCodexDynamicToolBridge", () => {
372372expect(badExecute).not.toHaveBeenCalled();
373373});
374374375+it("quarantines unreadable dynamic tool descriptors without dropping healthy siblings", () => {
376+const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
377+const poisonedName = createTool({
378+name: "fuzzplugin_unreadable_name",
379+execute: vi.fn(),
380+});
381+Object.defineProperty(poisonedName, "name", {
382+enumerable: true,
383+get() {
384+throw new Error("fuzzplugin dynamic tool name getter exploded");
385+},
386+});
387+const poisonedSchema = createTool({
388+name: "fuzzplugin_unreadable_schema",
389+execute: vi.fn(),
390+});
391+Object.defineProperty(poisonedSchema, "parameters", {
392+enumerable: true,
393+get() {
394+throw new Error("fuzzplugin dynamic tool schema getter exploded");
395+},
396+});
397+const invalidName = createTool({
398+name: "",
399+execute: vi.fn(),
400+});
401+const poisonedExecute = createTool({
402+name: "fuzzplugin_unreadable_execute",
403+});
404+Object.defineProperty(poisonedExecute, "execute", {
405+enumerable: true,
406+get() {
407+throw new Error("fuzzplugin dynamic tool execute getter exploded");
408+},
409+});
410+411+const bridge = createCodexDynamicToolBridge({
412+tools: [
413+poisonedName,
414+poisonedSchema,
415+invalidName,
416+poisonedExecute,
417+createTool({ name: "message" }),
418+],
419+signal: new AbortController().signal,
420+});
421+422+expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
423+expect(bridge.specs.map((tool) => tool.name)).toEqual(["message"]);
424+expect(bridge.telemetry.quarantinedTools).toEqual([
425+{
426+tool: "tool[0]",
427+violations: ["tool[0].name is unreadable"],
428+},
429+{
430+tool: "fuzzplugin_unreadable_schema",
431+violations: ["fuzzplugin_unreadable_schema.inputSchema is unreadable"],
432+},
433+{
434+tool: "tool[2]",
435+violations: ["tool[2].name must be a non-empty string"],
436+},
437+{
438+tool: "fuzzplugin_unreadable_execute",
439+violations: [
440+"fuzzplugin_unreadable_execute could not be wrapped for before-tool-call hooks",
441+],
442+},
443+]);
444+expect(warn).toHaveBeenCalledWith(
445+expect.stringContaining(
446+"tool[0], fuzzplugin_unreadable_schema, tool[2], fuzzplugin_unreadable_execute",
447+),
448+expect.objectContaining({
449+tools: [
450+{
451+tool: "tool[0]",
452+violations: ["tool[0].name is unreadable"],
453+},
454+{
455+tool: "fuzzplugin_unreadable_schema",
456+violations: ["fuzzplugin_unreadable_schema.inputSchema is unreadable"],
457+},
458+{
459+tool: "tool[2]",
460+violations: ["tool[2].name must be a non-empty string"],
461+},
462+{
463+tool: "fuzzplugin_unreadable_execute",
464+violations: [
465+"fuzzplugin_unreadable_execute could not be wrapped for before-tool-call hooks",
466+],
467+},
468+],
469+}),
470+);
471+472+const registeredBridge = createCodexDynamicToolBridge({
473+tools: [poisonedExecute, createTool({ name: "message" })],
474+registeredTools: [
475+createTool({ name: "fuzzplugin_unreadable_execute" }),
476+createTool({ name: "message" }),
477+],
478+signal: new AbortController().signal,
479+});
480+481+expect(registeredBridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
482+expect(registeredBridge.specs.map((tool) => tool.name)).toEqual(["message"]);
483+});
484+375485it("can expose all dynamic tools directly for compatibility", () => {
376486const bridge = createCodexDynamicToolBridge({
377487tools: [createTool({ name: "web_search" }), createTool({ name: "message" })],
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。