Compare commits
2 Commits
main
...
feat/kitch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73393b8088 | ||
|
|
ed106ac203 |
10
README.md
10
README.md
@ -17,6 +17,16 @@ detached-task, and text-provider catalog surfaces.
|
||||
It should not call external services, read secrets, spawn processes, or require
|
||||
live credentials.
|
||||
|
||||
The plugin exposes three test personalities through
|
||||
`plugins.entries.openclaw-kitchen-sink-fixture.config.personality`:
|
||||
|
||||
- `full` is the default compatibility mode and keeps both generated probe
|
||||
registrations and the hand-owned runtime.
|
||||
- `conformance` loads only the valid runtime surfaces and skips intentionally
|
||||
invalid probes so OpenClaw can assert a clean external-plugin install.
|
||||
- `adversarial` loads only generated invalid probes so OpenClaw can assert
|
||||
expected diagnostics without mixing them with a live runtime smoke.
|
||||
|
||||
## Kitchen Runtime
|
||||
|
||||
The fixture can be used dry, without an LLM:
|
||||
|
||||
@ -197,6 +197,15 @@
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"personality": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"full",
|
||||
"conformance",
|
||||
"adversarial"
|
||||
],
|
||||
"default": "full"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
"./personality": "./src/personality.js",
|
||||
"./runtime": "./src/kitchen-runtime.js",
|
||||
"./scenarios": "./src/scenarios.js",
|
||||
"./setup": "./src/setup.js"
|
||||
|
||||
@ -387,8 +387,88 @@ assert.equal(cliRegistration[1].descriptors[0].name, "kitchen-sink");
|
||||
const imageTool = findRegistration("registerTool", "kitchen_sink_image_job");
|
||||
assert.equal(typeof imageTool.execute, "function");
|
||||
|
||||
const { KITCHEN_SINK_EXPECTED_DIAGNOSTICS } = await import("../src/personality.js");
|
||||
assert.deepEqual(KITCHEN_SINK_EXPECTED_DIAGNOSTICS.conformance, []);
|
||||
assert.ok(
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS.adversarial.includes(
|
||||
'channel "kitchen-sink-channel-probe" registration missing required config helpers',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS.full.includes(
|
||||
"only bundled plugins can register agent tool result middleware",
|
||||
),
|
||||
);
|
||||
|
||||
const conformance = capturePluginRegistration({ personality: "conformance" });
|
||||
assert.ok(
|
||||
conformance.registerCommand?.some(([command]) => command.name === "kitchen"),
|
||||
"conformance registers usable commands",
|
||||
);
|
||||
assert.ok(
|
||||
conformance.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"),
|
||||
"conformance registers the usable channel",
|
||||
);
|
||||
assert.equal(conformance.registerAgentToolResultMiddleware, undefined);
|
||||
assert.equal(
|
||||
conformance.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel-probe"),
|
||||
false,
|
||||
);
|
||||
|
||||
const adversarial = capturePluginRegistration({ personality: "adversarial" });
|
||||
assert.equal(
|
||||
adversarial.registerCommand?.some(([command]) => command.name === "kitchen"),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
adversarial.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"),
|
||||
false,
|
||||
);
|
||||
assert.ok(
|
||||
adversarial.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel-probe"),
|
||||
"adversarial registers generated invalid channel probe",
|
||||
);
|
||||
assert.ok(
|
||||
adversarial.registerCompactionProvider?.some(([provider]) => provider.id === "kitchen-sink-compaction-provider"),
|
||||
"adversarial registers generated invalid compaction probe",
|
||||
);
|
||||
|
||||
console.log("Kitchen runtime OK");
|
||||
|
||||
function capturePluginRegistration(config) {
|
||||
const captured = {};
|
||||
const captureApi = new Proxy(
|
||||
{
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
registrationMode: "full",
|
||||
config,
|
||||
logger: console,
|
||||
},
|
||||
{
|
||||
get(target, property) {
|
||||
if (property in target) {
|
||||
return target[property];
|
||||
}
|
||||
if (property === "on") {
|
||||
return (...args) => {
|
||||
captured.on ??= [];
|
||||
captured.on.push(args);
|
||||
};
|
||||
}
|
||||
if (typeof property !== "string" || !property.startsWith("register")) {
|
||||
return undefined;
|
||||
}
|
||||
return (...args) => {
|
||||
captured[property] ??= [];
|
||||
captured[property].push(args);
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
plugin.register(captureApi);
|
||||
return captured;
|
||||
}
|
||||
|
||||
function findRegistration(method, id) {
|
||||
const entry = registrations[method]?.map(([value]) => value).find((value) => value?.id === id);
|
||||
assert.ok(entry, `${method} ${id} registered`);
|
||||
|
||||
@ -33,6 +33,7 @@ const requiredFiles = [
|
||||
"src/index.js",
|
||||
"src/assets/kitchen_sink_office.png",
|
||||
"src/kitchen-runtime.js",
|
||||
"src/personality.js",
|
||||
"src/scenarios.js",
|
||||
"src/setup.js",
|
||||
"src/generated-hooks.js",
|
||||
|
||||
@ -130,16 +130,28 @@ function renderRuntimeIndex() {
|
||||
return `import { registerAllHooks } from "./generated-hooks.js";
|
||||
import { registerAllRegistrars } from "./generated-registrars.js";
|
||||
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
|
||||
import {
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
resolveKitchenSinkPersonality,
|
||||
} from "./personality.js";
|
||||
|
||||
export const plugin = {
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
name: "OpenClaw Kitchen Sink",
|
||||
version: "${packageJson.version}",
|
||||
description: "Credential-free fixture covering OpenClaw plugin API seams.",
|
||||
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
register(api) {
|
||||
const personality = resolveKitchenSinkPersonality(api);
|
||||
registerAllHooks(api);
|
||||
registerAllRegistrars(api);
|
||||
registerKitchenSinkRuntime(api);
|
||||
if (personality !== "conformance") {
|
||||
registerAllRegistrars(api);
|
||||
}
|
||||
if (personality !== "adversarial") {
|
||||
registerKitchenSinkRuntime(api, {
|
||||
includeAgentToolResultMiddleware: personality !== "conformance",
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -223,6 +235,7 @@ function renderManifest({ manifestContracts, packageVersion }) {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
enabled: { type: "boolean", default: false },
|
||||
personality: { type: "string", enum: ["full", "conformance", "adversarial"], default: "full" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
16
src/index.js
16
src/index.js
@ -1,16 +1,28 @@
|
||||
import { registerAllHooks } from "./generated-hooks.js";
|
||||
import { registerAllRegistrars } from "./generated-registrars.js";
|
||||
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
|
||||
import {
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
resolveKitchenSinkPersonality,
|
||||
} from "./personality.js";
|
||||
|
||||
export const plugin = {
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
name: "OpenClaw Kitchen Sink",
|
||||
version: "0.2.1",
|
||||
description: "Credential-free fixture covering OpenClaw plugin API seams.",
|
||||
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
register(api) {
|
||||
const personality = resolveKitchenSinkPersonality(api);
|
||||
registerAllHooks(api);
|
||||
registerAllRegistrars(api);
|
||||
registerKitchenSinkRuntime(api);
|
||||
if (personality !== "conformance") {
|
||||
registerAllRegistrars(api);
|
||||
}
|
||||
if (personality !== "adversarial") {
|
||||
registerKitchenSinkRuntime(api, {
|
||||
includeAgentToolResultMiddleware: personality !== "conformance",
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -53,6 +53,7 @@ export { createKitchenSinkImageAsset, kitchenPromptGuidance, shouldHandleKitchen
|
||||
|
||||
export function registerKitchenSinkRuntime(api, options = {}) {
|
||||
const runtime = createKitchenSinkRuntime(options);
|
||||
const includeAgentToolResultMiddleware = options.includeAgentToolResultMiddleware !== false;
|
||||
|
||||
optionalRegister(api, "registerCommand", () => api.registerCommand(buildKitchenCommand(runtime)));
|
||||
optionalRegister(api, "registerCommand", () => api.registerCommand(buildKitchenSinkCommand(runtime)));
|
||||
@ -101,11 +102,13 @@ export function registerKitchenSinkRuntime(api, options = {}) {
|
||||
optionalRegister(api, "registerCompactionProvider", () =>
|
||||
api.registerCompactionProvider(buildKitchenCompactionProvider()),
|
||||
);
|
||||
optionalRegister(api, "registerAgentToolResultMiddleware", () =>
|
||||
api.registerAgentToolResultMiddleware(buildKitchenToolResultMiddleware(), {
|
||||
runtimes: ["pi", "codex", "cli"],
|
||||
}),
|
||||
);
|
||||
if (includeAgentToolResultMiddleware) {
|
||||
optionalRegister(api, "registerAgentToolResultMiddleware", () =>
|
||||
api.registerAgentToolResultMiddleware(buildKitchenToolResultMiddleware(), {
|
||||
runtimes: ["pi", "codex", "cli"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
optionalRegister(api, "registerService", () => api.registerService(buildKitchenService()));
|
||||
optionalRegister(api, "registerHttpRoute", () => api.registerHttpRoute(buildKitchenHttpRoute()));
|
||||
optionalRegister(api, "registerGatewayMethod", () =>
|
||||
|
||||
36
src/personality.js
Normal file
36
src/personality.js
Normal file
@ -0,0 +1,36 @@
|
||||
export const KITCHEN_SINK_PERSONALITIES = ["full", "conformance", "adversarial"];
|
||||
|
||||
export const DEFAULT_KITCHEN_SINK_PERSONALITY = "full";
|
||||
|
||||
export const KITCHEN_SINK_EXPECTED_DIAGNOSTICS = {
|
||||
full: [
|
||||
"only bundled plugins can register agent tool result middleware",
|
||||
'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods',
|
||||
'channel "kitchen-sink-channel-probe" registration missing required config helpers',
|
||||
"cli registration missing explicit commands metadata",
|
||||
"only bundled plugins can register Codex app-server extension factories",
|
||||
'compaction provider "kitchen-sink-compaction-provider" registration missing summarize',
|
||||
"context engine registration missing id",
|
||||
"http route registration missing or invalid auth: /kitchen-sink/http-route",
|
||||
"plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider",
|
||||
"memory prompt supplement registration missing builder",
|
||||
],
|
||||
conformance: [],
|
||||
adversarial: [
|
||||
"only bundled plugins can register agent tool result middleware",
|
||||
'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods',
|
||||
'channel "kitchen-sink-channel-probe" registration missing required config helpers',
|
||||
"cli registration missing explicit commands metadata",
|
||||
"only bundled plugins can register Codex app-server extension factories",
|
||||
'compaction provider "kitchen-sink-compaction-provider" registration missing summarize',
|
||||
"context engine registration missing id",
|
||||
"http route registration missing or invalid auth: /kitchen-sink/http-route",
|
||||
"plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider",
|
||||
"memory prompt supplement registration missing builder",
|
||||
],
|
||||
};
|
||||
|
||||
export function resolveKitchenSinkPersonality(api) {
|
||||
const configured = api?.config?.personality || process.env.OPENCLAW_KITCHEN_SINK_PERSONALITY;
|
||||
return KITCHEN_SINK_PERSONALITIES.includes(configured) ? configured : DEFAULT_KITCHEN_SINK_PERSONALITY;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user