import { afterEach, describe, expect, it } from "vitest" ;
import {
createAgentToolResultMiddlewareRunner,
createCodexAppServerToolResultExtensionRunner,
} from "../plugin-sdk/agent-harness.js" ;
import { listAgentToolResultMiddlewares } from "../plugins/agent-tool-result-middleware.js" ;
import { listCodexAppServerExtensionFactories } from "../plugins/codex-app-server-extension-factory.js" ;
import { loadOpenClawPlugins } from "../plugins/loader.js" ;
import {
cleanupTempPluginTestEnvironment,
createTempPluginDir,
resetActivePluginRegistryForTest,
writeTempPlugin,
} from "./test-helpers/temp-plugin-extension-fixtures.js" ;
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const tempDirs: string[] = [];
function createTempDir(): string {
return createTempPluginDir(tempDirs, "openclaw-codex-ext-" );
}
afterEach(() => {
cleanupTempPluginTestEnvironment(tempDirs, originalBundledPluginsDir);
});
describe("agent tool result middleware" , () => {
it("includes plugin-registered middleware and restores it from cache" , async () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
writeTempPlugin({
dir: tmp,
id: "tool-result-middleware" ,
filename: "index.mjs" ,
manifest: {
contracts: {
agentToolResultMiddleware: ["codex" ],
},
},
body: `export default { id: "tool-result-middleware" , register(api) {
api.registerAgentToolResultMiddleware(async (event) => ({
result: { ...event.result, content: [{ type: "text" , text: event.toolName + " compacted" }] }
}), { runtimes: ["codex" ] });
} };`,
});
const options = {
config: {
plugins: {
entries: {
"tool-result-middleware" : {
enabled: true ,
},
},
},
},
};
loadOpenClawPlugins(options);
expect(listAgentToolResultMiddlewares("codex" )).toHaveLength(1 );
expect(listAgentToolResultMiddlewares("pi" )).toHaveLength(0 );
resetActivePluginRegistryForTest();
expect(listAgentToolResultMiddlewares("codex" )).toHaveLength(0 );
loadOpenClawPlugins(options);
const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" });
const result = await runner.applyToolResultMiddleware({
threadId: "thread-1" ,
turnId: "turn-1" ,
toolCallId: "call-1" ,
toolName: "exec" ,
args: { command: "git status" },
result: { content: [{ type: "text" , text: "raw" }], details: {} },
});
expect(result.content).toEqual([{ type: "text" , text: "exec compacted" }]);
});
it("rejects middleware when the manifest omits the runtime contract" , () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
writeTempPlugin({
dir: tmp,
id: "tool-result-middleware" ,
filename: "index.mjs" ,
manifest: {
contracts: {
agentToolResultMiddleware: ["pi" ],
},
},
body: `export default { id: "tool-result-middleware" , register(api) {
api.registerAgentToolResultMiddleware(() => undefined, { runtimes: ["codex" ] });
} };`,
});
const registry = loadOpenClawPlugins({
config: {
plugins: {
entries: {
"tool-result-middleware" : {
enabled: true ,
},
},
},
},
});
expect(registry.diagnostics).toContainEqual(
expect.objectContaining({
level: "error" ,
pluginId: "tool-result-middleware" ,
message: "plugin must declare contracts.agentToolResultMiddleware for: codex" ,
}),
);
expect(listAgentToolResultMiddlewares("codex" )).toHaveLength(0 );
});
it("rejects middleware from non-bundled plugins even when they declare the contract" , () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins" ;
const pluginFile = writeTempPlugin({
dir: tmp,
id: "tool-result-middleware" ,
manifest: {
contracts: {
agentToolResultMiddleware: ["codex" ],
},
},
body: `export default { id: "tool-result-middleware" , register(api) {
api.registerAgentToolResultMiddleware(() => undefined, { runtimes: ["codex" ] });
} };`,
});
const registry = loadOpenClawPlugins({
workspaceDir: tmp,
config: {
plugins: {
load: { paths: [pluginFile] },
allow: ["tool-result-middleware" ],
},
},
});
expect(registry.diagnostics).toContainEqual(
expect.objectContaining({
level: "error" ,
pluginId: "tool-result-middleware" ,
message: "only bundled plugins can register agent tool result middleware" ,
}),
);
expect(listAgentToolResultMiddlewares("codex" )).toHaveLength(0 );
});
it("merges runtimes when a plugin registers the same middleware function twice" , () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
writeTempPlugin({
dir: tmp,
id: "tool-result-middleware" ,
filename: "index.mjs" ,
manifest: {
contracts: {
agentToolResultMiddleware: ["pi" , "codex" ],
},
},
body: `const middleware = () => undefined;
export default { id: "tool-result-middleware" , register(api) {
api.registerAgentToolResultMiddleware(middleware, { runtimes: ["pi" ] });
api.registerAgentToolResultMiddleware(middleware, { runtimes: ["codex" ] });
} };`,
});
loadOpenClawPlugins({
config: {
plugins: {
entries: {
"tool-result-middleware" : {
enabled: true ,
},
},
},
},
});
expect(listAgentToolResultMiddlewares("pi" )).toHaveLength(1 );
expect(listAgentToolResultMiddlewares("codex" )).toHaveLength(1 );
});
});
describe("Codex app-server extension factories" , () => {
it("includes plugin-registered Codex app-server extension factories and restores them from cache" , async () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
writeTempPlugin({
dir: tmp,
id: "codex-ext" ,
filename: "index.mjs" ,
manifest: {
contracts: {
embeddedExtensionFactories: ["codex-app-server" ],
},
},
body: `export default { id: "codex-ext" , register(api) {
api.registerCodexAppServerExtensionFactory((codex) => {
codex.on("tool_result" , async (event) => ({
result: { ...event.result, content: [{ type: "text" , text: "compacted" }] }
}));
});
} };`,
});
const options = {
config: {
plugins: {
entries: {
"codex-ext" : {
enabled: true ,
},
},
},
},
};
loadOpenClawPlugins(options);
expect(listCodexAppServerExtensionFactories()).toHaveLength(1 );
resetActivePluginRegistryForTest();
expect(listCodexAppServerExtensionFactories()).toHaveLength(0 );
loadOpenClawPlugins(options);
const runner = createCodexAppServerToolResultExtensionRunner({});
const result = await runner.applyToolResultExtensions({
threadId: "thread-1" ,
turnId: "turn-1" ,
toolCallId: "call-1" ,
toolName: "exec" ,
args: { command: "git status" },
result: { content: [{ type: "text" , text: "raw" }], details: {} },
});
expect(result.content).toEqual([{ type: "text" , text: "compacted" }]);
});
it("rejects Codex app-server extension factories from non-bundled plugins even when they declare the contract" , () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins" ;
const pluginFile = writeTempPlugin({
dir: tmp,
id: "codex-ext" ,
manifest: {
contracts: {
embeddedExtensionFactories: ["codex-app-server" ],
},
},
body: `export default { id: "codex-ext" , register(api) {
api.registerCodexAppServerExtensionFactory(() => undefined);
} };`,
});
const registry = loadOpenClawPlugins({
workspaceDir: tmp,
config: {
plugins: {
load: { paths: [pluginFile] },
allow: ["codex-ext" ],
},
},
});
expect(registry.diagnostics).toContainEqual(
expect.objectContaining({
level: "error" ,
pluginId: "codex-ext" ,
message: "only bundled plugins can register Codex app-server extension factories" ,
}),
);
expect(listCodexAppServerExtensionFactories()).toHaveLength(0 );
});
it("rejects bundled plugins that omit the Codex app-server extension contract" , () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
writeTempPlugin({
dir: tmp,
id: "codex-ext" ,
filename: "index.mjs" ,
body: `export default { id: "codex-ext" , register(api) {
api.registerCodexAppServerExtensionFactory(() => undefined);
} };`,
});
const registry = loadOpenClawPlugins({
config: {
plugins: {
entries: {
"codex-ext" : {
enabled: true ,
},
},
},
},
});
expect(registry.diagnostics).toContainEqual(
expect.objectContaining({
level: "error" ,
pluginId: "codex-ext" ,
message:
'plugin must declare contracts.embeddedExtensionFactories: ["codex-app-server"] to register Codex app-server extension factories' ,
}),
);
expect(listCodexAppServerExtensionFactories()).toHaveLength(0 );
});
it("rejects non-function Codex app-server extension factories from bundled plugins" , () => {
const tmp = createTempDir();
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
writeTempPlugin({
dir: tmp,
id: "codex-ext" ,
filename: "index.mjs" ,
manifest: {
contracts: {
embeddedExtensionFactories: ["codex-app-server" ],
},
},
body: `export default { id: "codex-ext" , register(api) {
api.registerCodexAppServerExtensionFactory("not-a-function" );
} };`,
});
const registry = loadOpenClawPlugins({
config: {
plugins: {
entries: {
"codex-ext" : {
enabled: true ,
},
},
},
},
});
expect(registry.diagnostics).toContainEqual(
expect.objectContaining({
level: "error" ,
pluginId: "codex-ext" ,
message: "codex app-server extension factory must be a function" ,
}),
);
expect(listCodexAppServerExtensionFactories()).toHaveLength(0 );
});
it("initializes async Codex app-server extension factories in registration order" , async () => {
const steps: string[] = [];
const runner = createCodexAppServerToolResultExtensionRunner({}, [
async (codex) => {
await new Promise((resolve) => setTimeout(resolve, 10 ));
codex.on("tool_result" , async ({ result }) => {
steps.push("first" );
return {
result: {
...result,
content: [{ type: "text" , text: `${result.content[0 ]?.type}:${steps.length}` }],
},
};
});
},
async (codex) => {
codex.on("tool_result" , async ({ result }) => {
steps.push("second" );
return { result };
});
},
]);
await runner.applyToolResultExtensions({
threadId: "thread-1" ,
turnId: "turn-1" ,
toolCallId: "call-1" ,
toolName: "exec" ,
args: { command: "git status" },
result: { content: [{ type: "text" , text: "raw" }], details: {} },
});
expect(steps).toEqual(["first" , "second" ]);
});
});
Messung V0.5 in Prozent C=100 H=97 G=98
¤ Dauer der Verarbeitung: 0.5 Sekunden
¤
*© Formatika GbR, Deutschland