Compare commits

...

26 Commits
v0.2.2 ... main

Author SHA1 Message Date
github-actions[bot]
088bb4bee4
Update OpenClaw fixture dependencies
Some checks failed
Check / check (push) Has been cancelled
Check / clawhub-dry-run (push) Has been cancelled
Automated update of openclaw@2026.5.7, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-07 22:14:49 +00:00
github-actions[bot]
95ec036d85
Update OpenClaw fixture dependencies
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
Automated update of openclaw@2026.5.6, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-06 17:40:47 +00:00
github-actions[bot]
c84b7aa983
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.5, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-06 09:38:55 +00:00
github-actions[bot]
849fe726ea
Update OpenClaw fixture dependencies
Some checks failed
Check / check (push) Has been cancelled
Check / clawhub-dry-run (push) Has been cancelled
Automated update of openclaw@2026.5.4, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-05 08:19:22 +00:00
github-actions[bot]
f57a0fc7c4
Update OpenClaw fixture dependencies
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
Automated update of openclaw@2026.5.3-1, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-04 10:00:26 +00:00
github-actions[bot]
fbae1bed22
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.3, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-04 08:10:01 +00:00
github-actions[bot]
7657ba8249
Update OpenClaw fixture dependencies
Some checks are pending
Check / clawhub-dry-run (push) Waiting to run
Check / check (push) Waiting to run
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-03 08:20:18 +00:00
github-actions[bot]
67ca4a3370
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.8, and the generated kitchen-sink API surface.
2026-05-03 07:32:12 +00:00
github-actions[bot]
c023ff6f85
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.7, and the generated kitchen-sink API surface.
2026-05-03 05:46:15 +00:00
Vincent Koc
6995e72caa
chore(deps): update plugin inspector 2026-05-02 19:10:17 -07:00
Vincent Koc
bc26de1a38
fix: pin safe uuid resolution 2026-05-02 18:50:31 -07:00
Vincent Koc
7f92e87bfc
fix: publish valid install metadata (#15) 2026-05-02 18:22:01 -07:00
github-actions[bot]
02370e2338
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-05-03 01:03:37 +00:00
Vincent Koc
2861f04237
chore(release): bump kitchen sink to 0.2.4 2026-05-02 14:40:34 -07:00
Vincent Koc
768be7da53
chore(release): bump kitchen sink to 0.2.3 2026-05-02 14:21:16 -07:00
Vincent Koc
89449910b4
fix(package): restore kitchen sink install artifacts
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
2026-05-02 08:40:15 -07:00
github-actions[bot]
87d135b61f
Update OpenClaw fixture dependencies
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
Automated update of openclaw@2026.4.29, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-04-30 21:04:52 +00:00
Vincent Koc
02421e56a6
fix(security): override anthropic sdk patched version
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
2026-04-29 16:19:27 -07:00
github-actions[bot]
4bd96b6f2d
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.4.27, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-04-29 22:41:07 +00:00
Vincent Koc
c5ffa2a3c0
docs(code): add kitchen sink wayfinding comments 2026-04-29 14:45:09 -07:00
Vincent Koc
d1daa6e3de
docs(dx): document kitchen sink source layout 2026-04-29 14:29:08 -07:00
Vincent Koc
6a2cf98404
refactor(fixtures): move image and text mocks 2026-04-29 14:29:08 -07:00
Vincent Koc
ba65248dc2
refactor(core): centralize kitchen sink constants 2026-04-29 14:29:08 -07:00
Vincent Koc
da085688fd
refactor(tests): share kitchen sink harness 2026-04-29 14:29:07 -07:00
Vincent Koc
dcf61bc18c
refactor(runtime): split kitchen sink registration builders 2026-04-29 14:29:07 -07:00
github-actions[bot]
f254b77d71
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.4.26, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-04-29 21:11:35 +00:00
29 changed files with 4282 additions and 1923 deletions

View File

@ -27,6 +27,27 @@ The plugin exposes three test personalities through
- `adversarial` loads only generated invalid probes so OpenClaw can assert - `adversarial` loads only generated invalid probes so OpenClaw can assert
expected diagnostics without mixing them with a live runtime smoke. expected diagnostics without mixing them with a live runtime smoke.
## Source Layout
The hand-owned runtime is intentionally split by plugin surface so it can be
used as reference code instead of one giant fixture file:
- `src/index.js` selects the Kitchen Sink personality and registers the runtime
plus generated probes.
- `src/kitchen-runtime.js` is the runtime registrar entrypoint. It wires
builders together but keeps the implementation in smaller modules.
- `src/runtime/commands.js`, `channel.js`, `providers.js`, `tasks.js`, and
`platform.js` hold the command/tool, channel, provider, detached-task, and
service/gateway/CLI registrations.
- `src/scenarios.js` is the deterministic scenario router shared by dry
commands, tools, providers, hooks, channel delivery, and tests.
- `src/fixtures/` holds deterministic mock payloads such as the bundled image
asset and text-provider stream fixture.
- `src/generated-*` files are diagnostic surface probes generated from the
installed OpenClaw SDK. They are not the code plugin authors should copy.
- `scripts/lib/` holds test harness code reused by runtime and contract probes;
`scripts/fixtures/` holds reviewable consumer-smoke programs.
## Kitchen Runtime ## Kitchen Runtime
The fixture can be used dry, without an LLM: The fixture can be used dry, without an LLM:
@ -56,8 +77,8 @@ It also exposes provider and tool surfaces for live model routing:
and the contract probe script also checks the approval path and conversation and the contract probe script also checks the approval path and conversation
privacy observations for `llm_input`, `llm_output`, and `agent_end`. privacy observations for `llm_input`, `llm_output`, and `agent_end`.
- `src/scenarios.js` is the shared deterministic fixture engine used by dry - `src/scenarios.js` routes deterministic user scenarios; reusable mock payloads
commands, tools, providers, hooks, channel delivery, and tests. live in `src/fixtures/`.
- `kitchen_sink_image_job` returns a deterministic image job, waits 10 seconds - `kitchen_sink_image_job` returns a deterministic image job, waits 10 seconds
in real runtime execution, then returns the bundled `kitchen_sink_office.png` in real runtime execution, then returns the bundled `kitchen_sink_office.png`
image payload with PNG dimensions, byte size, SHA-256 hash, seed, model, and image payload with PNG dimensions, byte size, SHA-256 hash, seed, model, and
@ -110,8 +131,11 @@ contract coverage.
```sh ```sh
npm install npm install
npm run sync:surface npm run sync:surface
npm test npm run check:runtime
npm run check:inspector
npm run check:install
npm run pack:check npm run pack:check
npm run pack:zip
``` ```
The `Update OpenClaw SDK Surface` workflow automatically checks The `Update OpenClaw SDK Surface` workflow automatically checks
@ -131,6 +155,11 @@ Tagged GitHub releases publish the validated package to npm through trusted
publishing. The release tag must match `package.json`, for example `v0.0.1` for publishing. The release tag must match `package.json`, for example `v0.0.1` for
version `0.0.1`. version `0.0.1`.
`npm pack` remains the canonical npm artifact. `npm run pack:zip` builds the
legacy archive artifact at `dist/openclaw-kitchen-sink-fixture-<version>.zip`
with `package.json`, `openclaw.plugin.json`, `plugin-inspector.config.json`,
`README.md`, and `src/**` at the archive root for old archive installers.
Use the `Draft Release` workflow to create the tag and generated GitHub release Use the `Draft Release` workflow to create the tag and generated GitHub release
notes. Publishing that draft release runs the npm publish workflow. `0.0.x` notes. Publishing that draft release runs the npm publish workflow. `0.0.x`
verification releases publish under the `verification` npm dist-tag so they do verification releases publish under the `verification` npm dist-tag so they do

View File

@ -1,8 +1,8 @@
{ {
"id": "openclaw-kitchen-sink-fixture", "id": "openclaw-kitchen-sink-fixture",
"name": "OpenClaw Kitchen Sink", "name": "OpenClaw Kitchen Sink",
"version": "0.2.2", "version": "0.2.5",
"description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.4.26.", "description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.5.7.",
"enabledByDefault": false, "enabledByDefault": false,
"kind": [ "kind": [
"tool", "tool",
@ -57,6 +57,42 @@
"hook" "hook"
] ]
}, },
"channelConfigs": {
"kitchen-sink-channel": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"configured": {
"type": "boolean",
"default": true
},
"disabled": {
"type": "boolean",
"default": false
},
"enabled": {
"type": "boolean",
"default": true
},
"token": {
"type": "string"
}
}
},
"uiHints": {
"token": {
"sensitive": true
}
},
"label": "Kitchen Sink",
"description": "Credential-free channel fixture for deterministic Kitchen Sink conversations.",
"commands": {
"nativeCommandsAutoEnabled": true,
"nativeSkillsAutoEnabled": true
}
}
},
"setup": { "setup": {
"providers": [ "providers": [
{ {

2513
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@openclaw/kitchen-sink", "name": "@openclaw/kitchen-sink",
"version": "0.2.2", "version": "0.2.5",
"private": false, "private": false,
"description": "Credential-free kitchen-sink OpenClaw plugin fixture covering the public plugin API surface.", "description": "Credential-free kitchen-sink OpenClaw plugin fixture covering the public plugin API surface.",
"type": "module", "type": "module",
@ -46,15 +46,29 @@
"compat": { "compat": {
"pluginApi": "2026.4" "pluginApi": "2026.4"
}, },
"install": {
"clawhubSpec": "clawhub:@openclaw/kitchen-sink",
"npmSpec": "@openclaw/kitchen-sink",
"defaultChoice": "clawhub",
"minHostVersion": ">=2026.5.7"
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
},
"build": { "build": {
"openclawVersion": "2026.4.26", "openclawVersion": "2026.5.7",
"pluginSdkVersion": "2026.4.26" "pluginSdkVersion": "2026.5.7"
} }
}, },
"scripts": { "scripts": {
"acceptance:install": "node scripts/check-installed-package.mjs", "acceptance:install": "node scripts/check-installed-package.mjs",
"check": "npm run sync:surface -- --check && node scripts/check-sdk-surface.mjs && node scripts/check-kitchen-runtime.mjs && node scripts/check-kitchen-contract-probes.mjs && npm run plugin:inspect && npm run acceptance:install", "check": "npm run sync:surface -- --check && node scripts/check-sdk-surface.mjs && npm run check:runtime && npm run plugin:inspect && npm run check:install",
"check:install": "node scripts/check-installed-package.mjs",
"check:inspector": "npm run plugin:inspect && npm run plugin:inspect:runtime",
"check:runtime": "node scripts/check-kitchen-runtime.mjs && node scripts/check-kitchen-contract-probes.mjs",
"pack:check": "node scripts/check-pack-payload.mjs", "pack:check": "node scripts/check-pack-payload.mjs",
"pack:zip": "node scripts/pack-zip.mjs",
"plugin:inspect": "plugin-inspector check --config plugin-inspector.config.json --no-openclaw", "plugin:inspect": "plugin-inspector check --config plugin-inspector.config.json --no-openclaw",
"plugin:inspect:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --config plugin-inspector.config.json --no-openclaw --runtime --mock-sdk", "plugin:inspect:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --config plugin-inspector.config.json --no-openclaw --runtime --mock-sdk",
"sdk:target-check": "node scripts/check-target-sdk-imports.mjs", "sdk:target-check": "node scripts/check-target-sdk-imports.mjs",
@ -62,10 +76,14 @@
"test": "npm run check" "test": "npm run check"
}, },
"dependencies": { "dependencies": {
"openclaw": "2026.4.26" "openclaw": "2026.5.7"
}, },
"devDependencies": { "devDependencies": {
"@openclaw/plugin-inspector": "0.3.4" "@openclaw/plugin-inspector": "0.3.10"
},
"overrides": {
"@anthropic-ai/sdk": "0.91.1",
"uuid": "14.0.0"
}, },
"engines": { "engines": {
"node": ">=22" "node": ">=22"

View File

@ -225,6 +225,12 @@
} }
}, },
"runtimeRegistrations": { "runtimeRegistrations": {
"registerAgentEventSubscription": {
"count": 1,
"ids": [
"kitchen-sink-agent-event-subscription"
]
},
"registerAgentHarness": { "registerAgentHarness": {
"count": 1, "count": 1,
"ids": [ "ids": [
@ -297,6 +303,12 @@
"kitchen-sink-context-engine" "kitchen-sink-context-engine"
] ]
}, },
"registerControlUiDescriptor": {
"count": 1,
"ids": [
"kitchen-sink-control-ui-descriptor"
]
},
"registerDetachedTaskRuntime": { "registerDetachedTaskRuntime": {
"count": 1, "count": 1,
"ids": [ "ids": [
@ -414,6 +426,12 @@
"kitchen-sink-node-host-command" "kitchen-sink-node-host-command"
] ]
}, },
"registerNodeInvokePolicy": {
"count": 1,
"ids": [
"kitchen-sink-node-invoke-policy"
]
},
"registerProvider": { "registerProvider": {
"count": 2, "count": 2,
"ids": [ "ids": [
@ -441,6 +459,12 @@
"kitchen-sink-reload" "kitchen-sink-reload"
] ]
}, },
"registerRuntimeLifecycle": {
"count": 1,
"ids": [
"kitchen-sink-runtime-lifecycle"
]
},
"registerSecurityAuditCollector": { "registerSecurityAuditCollector": {
"count": 1, "count": 1,
"ids": [ "ids": [
@ -454,6 +478,18 @@
"kitchen-sink-service" "kitchen-sink-service"
] ]
}, },
"registerSessionExtension": {
"count": 1,
"ids": [
"kitchen-sink-session-extension"
]
},
"registerSessionSchedulerJob": {
"count": 1,
"ids": [
"kitchen-sink-session-scheduler-job"
]
},
"registerSpeechProvider": { "registerSpeechProvider": {
"count": 2, "count": 2,
"ids": [ "ids": [
@ -476,6 +512,18 @@
"kitchen_sink_text" "kitchen_sink_text"
] ]
}, },
"registerToolMetadata": {
"count": 1,
"ids": [
"kitchen-sink-tool-metadata"
]
},
"registerTrustedToolPolicy": {
"count": 1,
"ids": [
"kitchen-sink-trusted-tool-policy"
]
},
"registerVideoGenerationProvider": { "registerVideoGenerationProvider": {
"count": 2, "count": 2,
"ids": [ "ids": [

View File

@ -14,6 +14,7 @@ Status: PASS
| Method | Count | IDs | | Method | Count | IDs |
| ------ | ----- | --- | | ------ | ----- | --- |
| registerAgentEventSubscription | 1 | kitchen-sink-agent-event-subscription |
| registerAgentHarness | 1 | kitchen-sink-agent-harness | | registerAgentHarness | 1 | kitchen-sink-agent-harness |
| registerAgentToolResultMiddleware | 2 | kitchen-sink-agent-tool-result-middleware, kitchen-sink-agent-tool-result-middleware | | registerAgentToolResultMiddleware | 2 | kitchen-sink-agent-tool-result-middleware, kitchen-sink-agent-tool-result-middleware |
| registerAutoEnableProbe | 1 | kitchen-sink-auto-enable-probe | | registerAutoEnableProbe | 1 | kitchen-sink-auto-enable-probe |
@ -25,6 +26,7 @@ Status: PASS
| registerCompactionProvider | 2 | kitchen-sink-compaction, kitchen-sink-compaction-provider | | registerCompactionProvider | 2 | kitchen-sink-compaction, kitchen-sink-compaction-provider |
| registerConfigMigration | 1 | kitchen-sink-config-migration | | registerConfigMigration | 1 | kitchen-sink-config-migration |
| registerContextEngine | 1 | kitchen-sink-context-engine | | registerContextEngine | 1 | kitchen-sink-context-engine |
| registerControlUiDescriptor | 1 | kitchen-sink-control-ui-descriptor |
| registerDetachedTaskRuntime | 1 | kitchen-sink-detached-task-runtime | | registerDetachedTaskRuntime | 1 | kitchen-sink-detached-task-runtime |
| registerGatewayDiscoveryService | 1 | kitchen-sink-gateway-discovery-service | | registerGatewayDiscoveryService | 1 | kitchen-sink-gateway-discovery-service |
| registerGatewayMethod | 2 | kitchen-sink-gateway-method, kitchen.status | | registerGatewayMethod | 2 | kitchen-sink-gateway-method, kitchen.status |
@ -43,15 +45,21 @@ Status: PASS
| registerMigrationProvider | 1 | kitchen-sink-migration-provider | | registerMigrationProvider | 1 | kitchen-sink-migration-provider |
| registerMusicGenerationProvider | 2 | kitchen-sink-music, kitchen-sink-music-generation-provider | | registerMusicGenerationProvider | 2 | kitchen-sink-music, kitchen-sink-music-generation-provider |
| registerNodeHostCommand | 1 | kitchen-sink-node-host-command | | registerNodeHostCommand | 1 | kitchen-sink-node-host-command |
| registerNodeInvokePolicy | 1 | kitchen-sink-node-invoke-policy |
| registerProvider | 2 | kitchen-sink-llm, kitchen-sink-provider | | registerProvider | 2 | kitchen-sink-llm, kitchen-sink-provider |
| registerRealtimeTranscriptionProvider | 2 | kitchen-sink-realtime-transcription, kitchen-sink-realtime-transcription-provider | | registerRealtimeTranscriptionProvider | 2 | kitchen-sink-realtime-transcription, kitchen-sink-realtime-transcription-provider |
| registerRealtimeVoiceProvider | 2 | kitchen-sink-realtime-voice, kitchen-sink-realtime-voice-provider | | registerRealtimeVoiceProvider | 2 | kitchen-sink-realtime-voice, kitchen-sink-realtime-voice-provider |
| registerReload | 1 | kitchen-sink-reload | | registerReload | 1 | kitchen-sink-reload |
| registerRuntimeLifecycle | 1 | kitchen-sink-runtime-lifecycle |
| registerSecurityAuditCollector | 1 | kitchen-sink-security-audit-collector | | registerSecurityAuditCollector | 1 | kitchen-sink-security-audit-collector |
| registerService | 2 | kitchen-sink-service, kitchen-sink-service | | registerService | 2 | kitchen-sink-service, kitchen-sink-service |
| registerSessionExtension | 1 | kitchen-sink-session-extension |
| registerSessionSchedulerJob | 1 | kitchen-sink-session-scheduler-job |
| registerSpeechProvider | 2 | kitchen-sink-speech, kitchen-sink-speech-provider | | registerSpeechProvider | 2 | kitchen-sink-speech, kitchen-sink-speech-provider |
| registerTextTransforms | 1 | kitchen-sink-text-transforms | | registerTextTransforms | 1 | kitchen-sink-text-transforms |
| registerTool | 4 | kitchen-sink-tool, kitchen_sink_image_job, kitchen_sink_search, kitchen_sink_text | | registerTool | 4 | kitchen-sink-tool, kitchen_sink_image_job, kitchen_sink_search, kitchen_sink_text |
| registerToolMetadata | 1 | kitchen-sink-tool-metadata |
| registerTrustedToolPolicy | 1 | kitchen-sink-trusted-tool-policy |
| registerVideoGenerationProvider | 2 | kitchen-sink-video, kitchen-sink-video-generation-provider | | registerVideoGenerationProvider | 2 | kitchen-sink-video, kitchen-sink-video-generation-provider |
| registerWebFetchProvider | 2 | kitchen-sink-fetch, kitchen-sink-web-fetch-provider | | registerWebFetchProvider | 2 | kitchen-sink-fetch, kitchen-sink-web-fetch-provider |
| registerWebSearchProvider | 2 | kitchen-sink-search, kitchen-sink-web-search-provider | | registerWebSearchProvider | 2 | kitchen-sink-search, kitchen-sink-web-search-provider |

View File

@ -31,7 +31,7 @@ try {
assert.equal(installedPackageJson.version, JSON.parse(readFileSync("package.json", "utf8")).version); assert.equal(installedPackageJson.version, JSON.parse(readFileSync("package.json", "utf8")).version);
const probeFile = path.join(projectDir, "probe.mjs"); const probeFile = path.join(projectDir, "probe.mjs");
writeFileSync(probeFile, consumerProbeSource()); writeFileSync(probeFile, readFileSync(new URL("./fixtures/installed-consumer-probe.mjs", import.meta.url), "utf8"));
run(process.execPath, [probeFile], { cwd: projectDir }); run(process.execPath, [probeFile], { cwd: projectDir });
const inspectorBin = path.join(repoRoot, "node_modules", ".bin", "plugin-inspector"); const inspectorBin = path.join(repoRoot, "node_modules", ".bin", "plugin-inspector");
@ -66,67 +66,3 @@ function run(command, args, options = {}) {
process.exit(result.status ?? 1); process.exit(result.status ?? 1);
} }
} }
function consumerProbeSource() {
return String.raw`
import assert from "node:assert/strict";
import { plugin } from "@openclaw/kitchen-sink";
import { createKitchenSinkRuntime } from "@openclaw/kitchen-sink/runtime";
import { createKitchenSinkImageAsset, kitchenPromptGuidance } from "@openclaw/kitchen-sink/scenarios";
import setup from "@openclaw/kitchen-sink/setup";
const registrations = {};
const api = new Proxy(
{ id: "consumer-install-smoke", registrationMode: "full", config: {}, logger: console },
{
get(target, property) {
if (property in target) return target[property];
if (property === "on") {
return (...args) => {
registrations.on ??= [];
registrations.on.push(args);
};
}
if (typeof property !== "string" || !property.startsWith("register")) return undefined;
return (...args) => {
registrations[property] ??= [];
registrations[property].push(args);
};
},
},
);
plugin.register(api);
assert.equal(plugin.id, "openclaw-kitchen-sink-fixture");
assert.ok(registrations.registerImageGenerationProvider?.some(([provider]) => provider.id === "kitchen-sink-image"));
assert.ok(registrations.registerProvider?.some(([provider]) => provider.id === "kitchen-sink-llm"));
assert.ok(registrations.registerWebSearchProvider?.some(([provider]) => provider.id === "kitchen-sink-search"));
assert.ok(registrations.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"));
const runtime = createKitchenSinkRuntime({
delayMs: 10_000,
sleep: async () => {},
now: (() => {
let tick = 0;
return () => new Date(Date.UTC(2026, 3, 29, 12, 0, tick++));
})(),
});
const image = await runtime.runImageJob({ prompt: "generate an image with kitchen sink" });
assert.equal(image.job.status, "completed");
assert.equal(image.image.metadata.assetName, "kitchen_sink_office.png");
assert.equal(image.image.metadata.sha256, "e126064123bb13d8ee01a22c204e079bc22397c103ed1c3a191c60d5ae3319aa");
const directImage = createKitchenSinkImageAsset({
prompt: "consumer import smoke",
jobId: "ks_consumer_install_smoke",
});
assert.equal(directImage.mimeType, "image/png");
assert.ok(directImage.dataUrl.startsWith("data:image/png;base64,"));
assert.ok(kitchenPromptGuidance().some((line) => line.includes("kitchen_sink_image_job")));
assert.equal(setup.id, "openclaw-kitchen-sink-setup");
assert.equal(typeof setup.setup, "function");
const setupResult = await setup.setup({ config: {} });
assert.equal(setupResult.configured, true);
`;
}

View File

@ -3,32 +3,14 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { mkdirSync, writeFileSync } from "node:fs"; import { mkdirSync, writeFileSync } from "node:fs";
import { plugin } from "../src/index.js"; import { plugin } from "../src/index.js";
import {
capturePluginRegistration,
createHookFinder,
registrationSummary,
} from "./lib/plugin-registration-harness.mjs";
const registrations = {}; const registrations = capturePluginRegistration(plugin);
const api = new Proxy( const findHook = createHookFinder(registrations);
{
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) => capture("on", args);
}
if (typeof property !== "string" || !property.startsWith("register")) {
return undefined;
}
return (...args) => capture(property, args);
},
},
);
plugin.register(api);
const beforeToolCall = findHook("before_tool_call"); const beforeToolCall = findHook("before_tool_call");
const llmInput = findHook("llm_input"); const llmInput = findHook("llm_input");
@ -49,7 +31,7 @@ const probes = {
end: await agentEnd(secretEvent("kitchen final answer"), secretContext()), end: await agentEnd(secretEvent("kitchen final answer"), secretContext()),
}, },
channel: await captureChannelProbe(), channel: await captureChannelProbe(),
runtimeRegistrations: registrationSummary(), runtimeRegistrations: registrationSummary(registrations),
}; };
assert.equal(probes.beforeToolCall.allow.decision, "allow"); assert.equal(probes.beforeToolCall.allow.decision, "allow");
@ -90,50 +72,6 @@ console.log(
`Kitchen contract probes OK: ${Object.keys(probes.runtimeRegistrations).length} registration methods, before_tool_call allow/block/approval, conversation privacy, channel envelope`, `Kitchen contract probes OK: ${Object.keys(probes.runtimeRegistrations).length} registration methods, before_tool_call allow/block/approval, conversation privacy, channel envelope`,
); );
function capture(method, args) {
registrations[method] ??= [];
registrations[method].push(args);
}
function findHook(name) {
const entry = registrations.on?.find(([hookName]) => hookName === name);
assert.ok(entry, `hook ${name} registered`);
return entry[1];
}
function registrationSummary() {
return Object.fromEntries(
Object.entries(registrations)
.filter(([method]) => method !== "on")
.sort(([a], [b]) => a.localeCompare(b))
.map(([method, entries]) => [
method,
{
count: entries.length,
ids: entries.map((args) => idForRegistration(method, args)).filter(Boolean).sort(),
},
]),
);
}
function idForRegistration(method, args) {
const [value, second] = args;
if (method === "registerGatewayMethod" && typeof value === "string") {
return value;
}
if (method === "registerCli" && second?.descriptors?.length > 0) {
return second.descriptors.map((descriptor) => descriptor.name).join(", ");
}
if (value?.id || value?.name) {
return value.id || value.name;
}
if (typeof second === "string") {
return second;
}
const slug = method.slice("register".length).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
return `kitchen-sink-${slug}`;
}
async function captureChannelProbe() { async function captureChannelProbe() {
const channel = registrations.registerChannel?.map(([value]) => value).find((value) => value.id === "kitchen-sink-channel"); const channel = registrations.registerChannel?.map(([value]) => value).find((value) => value.id === "kitchen-sink-channel");
assert.ok(channel, "kitchen-sink-channel registered"); assert.ok(channel, "kitchen-sink-channel registered");

View File

@ -2,38 +2,16 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { plugin } from "../src/index.js"; import { plugin } from "../src/index.js";
import {
capturePluginRegistration,
createHookFinder,
createRegistrationFinder,
fixedNow,
} from "./lib/plugin-registration-harness.mjs";
const registrations = {}; const registrations = capturePluginRegistration(plugin);
const api = new Proxy( const findRegistration = createRegistrationFinder(registrations);
{ const findHook = createHookFinder(registrations);
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) => {
registrations.on ??= [];
registrations.on.push(args);
};
}
if (typeof property !== "string" || !property.startsWith("register")) {
return undefined;
}
return (...args) => {
registrations[property] ??= [];
registrations[property].push(args);
};
},
},
);
plugin.register(api);
const commands = registrations.registerCommand?.map(([command]) => command) ?? []; const commands = registrations.registerCommand?.map(([command]) => command) ?? [];
assert.ok(commands.some((command) => command.name === "kitchen"), "registers kitchen command"); assert.ok(commands.some((command) => command.name === "kitchen"), "registers kitchen command");
@ -400,7 +378,7 @@ assert.ok(
), ),
); );
const conformance = capturePluginRegistration({ personality: "conformance" }); const conformance = capturePluginRegistration(plugin, { personality: "conformance" });
assert.ok( assert.ok(
conformance.registerCommand?.some(([command]) => command.name === "kitchen"), conformance.registerCommand?.some(([command]) => command.name === "kitchen"),
"conformance registers usable commands", "conformance registers usable commands",
@ -415,7 +393,7 @@ assert.equal(
false, false,
); );
const adversarial = capturePluginRegistration({ personality: "adversarial" }); const adversarial = capturePluginRegistration(plugin, { personality: "adversarial" });
assert.equal( assert.equal(
adversarial.registerCommand?.some(([command]) => command.name === "kitchen"), adversarial.registerCommand?.some(([command]) => command.name === "kitchen"),
false, false,
@ -434,54 +412,3 @@ assert.ok(
); );
console.log("Kitchen runtime OK"); 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`);
return entry;
}
function findHook(name) {
const entry = registrations.on?.find(([hookName]) => hookName === name);
assert.ok(entry, `hook ${name} registered`);
return entry[1];
}
function fixedNow() {
let tick = 0;
return () => new Date(Date.UTC(2026, 3, 28, 12, 0, tick++));
}

View File

@ -32,8 +32,16 @@ const requiredFiles = [
"plugin-inspector.config.json", "plugin-inspector.config.json",
"src/index.js", "src/index.js",
"src/assets/kitchen_sink_office.png", "src/assets/kitchen_sink_office.png",
"src/constants.js",
"src/fixtures/images.js",
"src/fixtures/text.js",
"src/kitchen-runtime.js", "src/kitchen-runtime.js",
"src/personality.js", "src/personality.js",
"src/runtime/channel.js",
"src/runtime/commands.js",
"src/runtime/platform.js",
"src/runtime/providers.js",
"src/runtime/tasks.js",
"src/scenarios.js", "src/scenarios.js",
"src/setup.js", "src/setup.js",
"src/generated-hooks.js", "src/generated-hooks.js",
@ -43,6 +51,7 @@ const requiredFiles = [
const missingFiles = requiredFiles.filter((file) => !files.has(file)); const missingFiles = requiredFiles.filter((file) => !files.has(file));
const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8")); const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
const pluginManifest = JSON.parse(fs.readFileSync("openclaw.plugin.json", "utf8"));
const issues = []; const issues = [];
function sameStringArray(actual, expected) { function sameStringArray(actual, expected) {
@ -92,6 +101,28 @@ if (buildPluginSdkVersion !== buildOpenClawVersion) {
if (packageJson.dependencies?.openclaw !== buildOpenClawVersion) { if (packageJson.dependencies?.openclaw !== buildOpenClawVersion) {
issues.push("dependencies.openclaw must match openclaw.build.openclawVersion"); issues.push("dependencies.openclaw must match openclaw.build.openclawVersion");
} }
if (packageJson.openclaw?.install?.clawhubSpec !== "clawhub:@openclaw/kitchen-sink") {
issues.push('openclaw.install.clawhubSpec must be "clawhub:@openclaw/kitchen-sink"');
}
if (packageJson.openclaw?.install?.npmSpec !== "@openclaw/kitchen-sink") {
issues.push('openclaw.install.npmSpec must be "@openclaw/kitchen-sink"');
}
if (packageJson.openclaw?.install?.defaultChoice !== "clawhub") {
issues.push('openclaw.install.defaultChoice must be "clawhub"');
}
if (packageJson.openclaw?.install?.minHostVersion !== `>=${buildOpenClawVersion}`) {
issues.push("openclaw.install.minHostVersion must be a semver floor for openclaw.build.openclawVersion");
}
const kitchenSinkChannelConfig = pluginManifest.channelConfigs?.["kitchen-sink-channel"];
if (!kitchenSinkChannelConfig?.schema || kitchenSinkChannelConfig.schema.type !== "object") {
issues.push("openclaw.plugin.json must declare channelConfigs.kitchen-sink-channel.schema");
}
if (packageJson.openclaw?.release?.publishToClawHub !== true) {
issues.push("openclaw.release.publishToClawHub must be true");
}
if (packageJson.openclaw?.release?.publishToNpm !== true) {
issues.push("openclaw.release.publishToNpm must be true");
}
if (!packageJson.files?.includes("src/")) { if (!packageJson.files?.includes("src/")) {
issues.push("package files must include src/"); issues.push("package files must include src/");
} }

View File

@ -0,0 +1,59 @@
import assert from "node:assert/strict";
import { plugin } from "@openclaw/kitchen-sink";
import { createKitchenSinkRuntime } from "@openclaw/kitchen-sink/runtime";
import { createKitchenSinkImageAsset, kitchenPromptGuidance } from "@openclaw/kitchen-sink/scenarios";
import setup from "@openclaw/kitchen-sink/setup";
const registrations = {};
const api = new Proxy(
{ id: "consumer-install-smoke", registrationMode: "full", config: {}, logger: console },
{
get(target, property) {
if (property in target) return target[property];
if (property === "on") {
return (...args) => {
registrations.on ??= [];
registrations.on.push(args);
};
}
if (typeof property !== "string" || !property.startsWith("register")) return undefined;
return (...args) => {
registrations[property] ??= [];
registrations[property].push(args);
};
},
},
);
plugin.register(api);
assert.equal(plugin.id, "openclaw-kitchen-sink-fixture");
assert.ok(registrations.registerImageGenerationProvider?.some(([provider]) => provider.id === "kitchen-sink-image"));
assert.ok(registrations.registerProvider?.some(([provider]) => provider.id === "kitchen-sink-llm"));
assert.ok(registrations.registerWebSearchProvider?.some(([provider]) => provider.id === "kitchen-sink-search"));
assert.ok(registrations.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"));
const runtime = createKitchenSinkRuntime({
delayMs: 10_000,
sleep: async () => {},
now: (() => {
let tick = 0;
return () => new Date(Date.UTC(2026, 3, 29, 12, 0, tick++));
})(),
});
const image = await runtime.runImageJob({ prompt: "generate an image with kitchen sink" });
assert.equal(image.job.status, "completed");
assert.equal(image.image.metadata.assetName, "kitchen_sink_office.png");
assert.equal(image.image.metadata.sha256, "e126064123bb13d8ee01a22c204e079bc22397c103ed1c3a191c60d5ae3319aa");
const directImage = createKitchenSinkImageAsset({
prompt: "consumer import smoke",
jobId: "ks_consumer_install_smoke",
});
assert.equal(directImage.mimeType, "image/png");
assert.ok(directImage.dataUrl.startsWith("data:image/png;base64,"));
assert.ok(kitchenPromptGuidance().some((line) => line.includes("kitchen_sink_image_job")));
assert.equal(setup.id, "openclaw-kitchen-sink-setup");
assert.equal(typeof setup.setup, "function");
const setupResult = await setup.setup({ config: {} });
assert.equal(setupResult.configured, true);

View File

@ -0,0 +1,90 @@
import assert from "node:assert/strict";
export function capturePluginRegistration(plugin, config = {}) {
// The harness captures every register* call through one proxy, which lets
// scripts inspect new SDK registrars without updating bespoke mocks first.
const captured = {};
const api = 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) => capture(captured, "on", args);
}
if (typeof property !== "string" || !property.startsWith("register")) {
return undefined;
}
return (...args) => capture(captured, property, args);
},
},
);
plugin.register(api);
return captured;
}
export function createRegistrationFinder(registrations) {
return (method, id) => {
const entry = registrations[method]?.map(([value]) => value).find((value) => value?.id === id);
assert.ok(entry, `${method} ${id} registered`);
return entry;
};
}
export function createHookFinder(registrations) {
return (name) => {
const entry = registrations.on?.find(([hookName]) => hookName === name);
assert.ok(entry, `hook ${name} registered`);
return entry[1];
};
}
export function registrationSummary(registrations) {
return Object.fromEntries(
Object.entries(registrations)
.filter(([method]) => method !== "on")
.sort(([a], [b]) => a.localeCompare(b))
.map(([method, entries]) => [
method,
{
count: entries.length,
ids: entries.map((args) => idForRegistration(method, args)).filter(Boolean).sort(),
},
]),
);
}
export function fixedNow(start = Date.UTC(2026, 3, 28, 12, 0, 0)) {
let tick = 0;
return () => new Date(start + tick++ * 1000);
}
function capture(registrations, method, args) {
registrations[method] ??= [];
registrations[method].push(args);
}
function idForRegistration(method, args) {
const [value, second] = args;
if (method === "registerGatewayMethod" && typeof value === "string") {
return value;
}
if (method === "registerCli" && second?.descriptors?.length > 0) {
return second.descriptors.map((descriptor) => descriptor.name).join(", ");
}
if (value?.id || value?.name) {
return value.id || value.name;
}
if (typeof second === "string") {
return second;
}
const slug = method.slice("register".length).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
return `kitchen-sink-${slug}`;
}

134
scripts/pack-zip.mjs Normal file
View File

@ -0,0 +1,134 @@
#!/usr/bin/env node
import { Buffer } from "node:buffer";
import fs from "node:fs/promises";
import path from "node:path";
const repoRoot = process.cwd();
const packageJson = JSON.parse(await fs.readFile(path.join(repoRoot, "package.json"), "utf8"));
const pluginJson = JSON.parse(await fs.readFile(path.join(repoRoot, "openclaw.plugin.json"), "utf8"));
const packageVersion = packageJson.version;
const pluginId = pluginJson.id;
const crcTable = Array.from({ length: 256 }, (_, index) => {
let value = index;
for (let bit = 0; bit < 8; bit += 1) {
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
}
return value >>> 0;
});
if (pluginId !== "openclaw-kitchen-sink-fixture") {
throw new Error(`unexpected plugin id: ${pluginId}`);
}
if (typeof packageVersion !== "string" || packageVersion.trim().length === 0) {
throw new Error("package.json version must be a non-empty string");
}
const distDir = path.join(repoRoot, "dist");
const zipName = `${pluginId}-${packageVersion}.zip`;
const zipPath = path.join(distDir, zipName);
const entries = [
"package.json",
"openclaw.plugin.json",
"plugin-inspector.config.json",
"README.md",
...(await listFiles("src")),
];
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(zipPath, await createZip(entries));
console.log(`Wrote ${path.relative(repoRoot, zipPath)} (${entries.length} files)`);
async function listFiles(root) {
const files = [];
const stack = [root];
while (stack.length > 0) {
const relativeDir = stack.pop();
const dirents = await fs.readdir(path.join(repoRoot, relativeDir), { withFileTypes: true });
for (const dirent of dirents.sort((left, right) => left.name.localeCompare(right.name))) {
const relativePath = path.posix.join(relativeDir, dirent.name);
if (dirent.isDirectory()) {
stack.push(relativePath);
} else if (dirent.isFile()) {
files.push(relativePath);
}
}
}
return files.sort();
}
async function createZip(files) {
const localParts = [];
const centralParts = [];
let offset = 0;
for (const relativePath of files) {
if (relativePath.startsWith("/") || relativePath.includes("..")) {
throw new Error(`invalid zip entry path: ${relativePath}`);
}
const data = await fs.readFile(path.join(repoRoot, relativePath));
const name = Buffer.from(relativePath, "utf8");
const crc = crc32(data);
const localHeader = Buffer.alloc(30);
localHeader.writeUInt32LE(0x04034b50, 0);
localHeader.writeUInt16LE(20, 4);
localHeader.writeUInt16LE(0x0800, 6);
localHeader.writeUInt16LE(0, 8);
localHeader.writeUInt16LE(0, 10);
localHeader.writeUInt16LE(0, 12);
localHeader.writeUInt32LE(crc, 14);
localHeader.writeUInt32LE(data.length, 18);
localHeader.writeUInt32LE(data.length, 22);
localHeader.writeUInt16LE(name.length, 26);
localHeader.writeUInt16LE(0, 28);
localParts.push(localHeader, name, data);
const centralHeader = Buffer.alloc(46);
centralHeader.writeUInt32LE(0x02014b50, 0);
centralHeader.writeUInt16LE(20, 4);
centralHeader.writeUInt16LE(20, 6);
centralHeader.writeUInt16LE(0x0800, 8);
centralHeader.writeUInt16LE(0, 10);
centralHeader.writeUInt16LE(0, 12);
centralHeader.writeUInt16LE(0, 14);
centralHeader.writeUInt32LE(crc, 16);
centralHeader.writeUInt32LE(data.length, 20);
centralHeader.writeUInt32LE(data.length, 24);
centralHeader.writeUInt16LE(name.length, 28);
centralHeader.writeUInt16LE(0, 30);
centralHeader.writeUInt16LE(0, 32);
centralHeader.writeUInt16LE(0, 34);
centralHeader.writeUInt16LE(0, 36);
centralHeader.writeUInt32LE(0, 38);
centralHeader.writeUInt32LE(offset, 42);
centralParts.push(centralHeader, name);
offset += localHeader.length + name.length + data.length;
}
const centralDirectory = Buffer.concat(centralParts);
const end = Buffer.alloc(22);
end.writeUInt32LE(0x06054b50, 0);
end.writeUInt16LE(0, 4);
end.writeUInt16LE(0, 6);
end.writeUInt16LE(files.length, 8);
end.writeUInt16LE(files.length, 10);
end.writeUInt32LE(centralDirectory.length, 12);
end.writeUInt32LE(offset, 16);
end.writeUInt16LE(0, 20);
return Buffer.concat([...localParts, centralDirectory, end]);
}
function crc32(buffer) {
let crc = 0xffffffff;
for (const byte of buffer) {
crc = (crc >>> 8) ^ crcTable[(crc ^ byte) & 0xff];
}
return (crc ^ 0xffffffff) >>> 0;
}

View File

@ -127,7 +127,8 @@ ${pluginSdkExports.map((_, index) => ` | typeof sdk${index}`).join("\n")};
function renderRuntimeIndex() { function renderRuntimeIndex() {
const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8")); const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8"));
return `import { registerAllHooks } from "./generated-hooks.js"; return `import { PLUGIN_ID } from "./constants.js";
import { registerAllHooks } from "./generated-hooks.js";
import { registerAllRegistrars } from "./generated-registrars.js"; import { registerAllRegistrars } from "./generated-registrars.js";
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js"; import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
import { import {
@ -136,7 +137,7 @@ import {
} from "./personality.js"; } from "./personality.js";
export const plugin = { export const plugin = {
id: "openclaw-kitchen-sink-fixture", id: PLUGIN_ID,
name: "OpenClaw Kitchen Sink", name: "OpenClaw Kitchen Sink",
version: "${packageJson.version}", version: "${packageJson.version}",
description: "Credential-free fixture covering OpenClaw plugin API seams.", description: "Credential-free fixture covering OpenClaw plugin API seams.",
@ -214,6 +215,29 @@ function renderManifest({ manifestContracts, packageVersion }) {
onCommands: ["kitchen", "kitchen-sink"], onCommands: ["kitchen", "kitchen-sink"],
onCapabilities: ["provider", "channel", "tool", "hook"], onCapabilities: ["provider", "channel", "tool", "hook"],
}, },
channelConfigs: {
"kitchen-sink-channel": {
schema: {
type: "object",
additionalProperties: false,
properties: {
configured: { type: "boolean", default: true },
disabled: { type: "boolean", default: false },
enabled: { type: "boolean", default: true },
token: { type: "string" },
},
},
uiHints: {
token: { sensitive: true },
},
label: "Kitchen Sink",
description: "Credential-free channel fixture for deterministic Kitchen Sink conversations.",
commands: {
nativeCommandsAutoEnabled: true,
nativeSkillsAutoEnabled: true,
},
},
},
setup: { setup: {
providers: [ providers: [
{ id: "kitchen-sink-provider", authMethods: ["none"], envVars: [] }, { id: "kitchen-sink-provider", authMethods: ["none"], envVars: [] },
@ -250,6 +274,18 @@ function renderPackageJson({ packageVersion }) {
openclawVersion: packageVersion, openclawVersion: packageVersion,
pluginSdkVersion: packageVersion, pluginSdkVersion: packageVersion,
}; };
packageJson.openclaw.install = {
...(packageJson.openclaw.install ?? {}),
clawhubSpec: "clawhub:@openclaw/kitchen-sink",
npmSpec: "@openclaw/kitchen-sink",
defaultChoice: "clawhub",
minHostVersion: `>=${packageVersion}`,
};
packageJson.openclaw.release = {
...(packageJson.openclaw.release ?? {}),
publishToClawHub: true,
publishToNpm: true,
};
if (packageJson.dependencies?.openclaw) { if (packageJson.dependencies?.openclaw) {
packageJson.dependencies.openclaw = packageVersion; packageJson.dependencies.openclaw = packageVersion;
} }

23
src/constants.js Normal file
View File

@ -0,0 +1,23 @@
export const PLUGIN_ID = "openclaw-kitchen-sink-fixture";
export const IMAGE_PROVIDER_ID = "kitchen-sink-image";
export const MEDIA_PROVIDER_ID = "kitchen-sink-media";
export const TEXT_PROVIDER_ID = "kitchen-sink-llm";
export const WEB_SEARCH_PROVIDER_ID = "kitchen-sink-search";
export const WEB_FETCH_PROVIDER_ID = "kitchen-sink-fetch";
export const SPEECH_PROVIDER_ID = "kitchen-sink-speech";
export const REALTIME_TRANSCRIPTION_PROVIDER_ID = "kitchen-sink-realtime-transcription";
export const REALTIME_VOICE_PROVIDER_ID = "kitchen-sink-realtime-voice";
export const VIDEO_PROVIDER_ID = "kitchen-sink-video";
export const MUSIC_PROVIDER_ID = "kitchen-sink-music";
export const MEMORY_EMBEDDING_PROVIDER_ID = "kitchen-sink-memory-embedding";
export const COMPACTION_PROVIDER_ID = "kitchen-sink-compaction";
export const CHANNEL_ID = "kitchen-sink-channel";
export const CHANNEL_ACCOUNT_ID = "local";
export const DEFAULT_IMAGE_MODEL = "kitchen-sink-image-v1";
export const DEFAULT_MEDIA_MODEL = "kitchen-sink-vision-v1";
export const DEFAULT_TEXT_MODEL = "kitchen-sink-text-v1";
export const DEFAULT_SPEECH_MODEL = "kitchen-sink-tts-v1";
export const DEFAULT_VIDEO_MODEL = "kitchen-sink-video-v1";
export const DEFAULT_MUSIC_MODEL = "kitchen-sink-music-v1";
export const DEFAULT_EMBEDDING_MODEL = "kitchen-sink-embed-v1";
export const DEFAULT_IMAGE_DELAY_MS = 10_000;

81
src/fixtures/images.js Normal file
View File

@ -0,0 +1,81 @@
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { DEFAULT_IMAGE_MODEL, PLUGIN_ID } from "../constants.js";
// Use a real bundled PNG so image-provider consumers exercise binary payloads,
// data URLs, hashes, and dimensions instead of a text-only mock.
const KITCHEN_SINK_OFFICE_IMAGE_FILE = "kitchen_sink_office.png";
const KITCHEN_SINK_OFFICE_IMAGE = readFileSync(
new URL(`../assets/${KITCHEN_SINK_OFFICE_IMAGE_FILE}`, import.meta.url),
);
const KITCHEN_SINK_OFFICE_SHA256 = sha256Hex(KITCHEN_SINK_OFFICE_IMAGE);
const KITCHEN_IMAGE_FIXTURES = [
{
id: "office-lobby-sink",
label: "Kitchen Sink Office",
assetName: KITCHEN_SINK_OFFICE_IMAGE_FILE,
buffer: KITCHEN_SINK_OFFICE_IMAGE,
sha256: KITCHEN_SINK_OFFICE_SHA256,
mimeType: "image/png",
width: 1024,
height: 1024,
description: "office lobby scene with a lobster-costumed figure holding a real sink",
source: "bundled-real-image",
},
];
export function createKitchenSinkImageAsset({
prompt,
jobId,
scenario = "image.generate",
model = DEFAULT_IMAGE_MODEL,
}) {
const fixture = selectKitchenImageFixture(prompt);
const buffer = Buffer.from(fixture.buffer);
const seed = stableHash(`${jobId}:${prompt}:${fixture.id}`);
return {
buffer,
mimeType: fixture.mimeType,
fileName: `${jobId}.png`,
dataUrl: `data:${fixture.mimeType};base64,${buffer.toString("base64")}`,
revisedPrompt: `Kitchen Sink office image fixture: ${prompt}`,
metadata: {
kitchenSink: true,
assetId: fixture.id,
assetName: fixture.assetName,
source: fixture.source,
model,
width: fixture.width,
height: fixture.height,
sizeBytes: buffer.byteLength,
sha256: fixture.sha256,
contentHash: fixture.sha256.slice(0, 16),
seed,
finishReason: "success",
pluginId: PLUGIN_ID,
scenarioId: scenario,
jobId,
prompt,
},
};
}
function selectKitchenImageFixture(_prompt) {
// Single fixture today, prompt-aware selection later if we add more real
// assets for edit/upscale/multi-image scenarios.
return KITCHEN_IMAGE_FIXTURES[0];
}
function sha256Hex(buffer) {
return createHash("sha256").update(buffer).digest("hex");
}
function stableHash(input) {
let hash = 2166136261;
for (let index = 0; index < input.length; index += 1) {
hash ^= input.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return (hash >>> 0).toString(16).padStart(8, "0");
}

209
src/fixtures/text.js Normal file
View File

@ -0,0 +1,209 @@
import {
DEFAULT_IMAGE_MODEL,
DEFAULT_MEDIA_MODEL,
DEFAULT_TEXT_MODEL,
IMAGE_PROVIDER_ID,
TEXT_PROVIDER_ID,
WEB_FETCH_PROVIDER_ID,
WEB_SEARCH_PROVIDER_ID,
} from "../constants.js";
export function kitchenTextProviderConfig() {
// Looks like a real provider config, but stays credential-free and local so
// installs can use it in CI, Crabpot, and plugin-inspector.
return {
baseUrl: "kitchen-sink://local",
apiKey: "kitchen-sink-local-fixture",
auth: "token",
api: "kitchen-sink",
models: [kitchenTextModelDefinition()],
};
}
export function kitchenTextModelDefinition() {
return {
id: DEFAULT_TEXT_MODEL,
name: "Kitchen Sink Text Fixture",
api: "kitchen-sink",
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 8192,
maxTokens: 2048,
description: "Deterministic OpenClaw plugin text-provider fixture.",
};
}
export function createKitchenTextStream(model, context) {
// Emit the same coarse lifecycle events a streaming text provider would emit;
// consumers can test stream handling without a live model.
const stream = createAssistantMessageEventStream();
queueMicrotask(() => {
const prompt = extractLastUserPrompt(context);
const text = kitchenTextResponse(prompt);
const message = {
role: "assistant",
content: [{ type: "text", text }],
api: model?.api || "kitchen-sink",
provider: TEXT_PROVIDER_ID,
model: model?.id || DEFAULT_TEXT_MODEL,
usage: estimateUsage(prompt, text),
stopReason: "stop",
timestamp: Date.now(),
};
stream.push({ type: "start", partial: { ...message, content: [] } });
stream.push({ type: "text_start", contentIndex: 0, partial: { ...message, content: [] } });
stream.push({ type: "text_delta", contentIndex: 0, delta: text, partial: message });
stream.push({ type: "text_end", contentIndex: 0, content: text, partial: message });
stream.push({ type: "done", reason: "stop", message });
stream.end(message);
});
return stream;
}
export function kitchenTextResponse(prompt) {
const normalized = normalizePrompt(prompt, "kitchen sink text inference");
if (/\b(image|picture|draw|generate)\b/i.test(normalized)) {
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
`I would route this to ${IMAGE_PROVIDER_ID}/${DEFAULT_IMAGE_MODEL}, create a queued image job, wait for completion, then return the bundled kitchen_sink_office.png asset with PNG metadata.`,
].join(" ");
}
if (/\b(search|find|lookup|web)\b/i.test(normalized)) {
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
`I would call ${WEB_SEARCH_PROVIDER_ID} for ranked fixture results and ${WEB_FETCH_PROVIDER_ID} for deterministic document fetches.`,
].join(" ");
}
if (/\b(rate limit|timeout|fail|error)\b/i.test(normalized)) {
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
"Failure fixtures are available: rate limit returns 429 with retry metadata, timeout returns 504, and fail returns a deterministic provider error.",
].join(" ");
}
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
"Available realistic surfaces: direct prefix, registered tools, image provider lifecycle, media understanding, web search, web fetch, channel health, hooks, detached tasks, and text provider catalog.",
].join(" ");
}
export function kitchenImageDescription(prompt, count) {
return [
`Kitchen Sink media fixture described ${count || 1} image${count === 1 ? "" : "s"}.`,
`Prompt: ${normalizePrompt(prompt, "describe kitchen sink image")}.`,
"Visible content: the bundled kitchen_sink_office PNG: an office lobby scene with a lobster-costumed figure holding a real sink.",
].join(" ");
}
export function estimateUsage(prompt = "", text = "") {
const input = estimateTokens(prompt);
const output = estimateTokens(text);
return {
input,
output,
cacheRead: 0,
cacheWrite: 0,
totalTokens: input + output,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
};
}
function createAssistantMessageEventStream() {
const queue = [];
const waiters = [];
let done = false;
let finalResult;
let resolveResult;
const resultPromise = new Promise((resolve) => {
resolveResult = resolve;
});
return {
push(event) {
if (done) {
return;
}
if (event.type === "done" || event.type === "error") {
finalResult = event.type === "done" ? event.message : event.error;
done = true;
resolveResult(finalResult);
}
const waiter = waiters.shift();
if (waiter) {
waiter({ value: event, done: false });
} else {
queue.push(event);
}
},
end(result) {
if (result !== undefined && finalResult === undefined) {
finalResult = result;
resolveResult(result);
}
done = true;
while (waiters.length > 0) {
waiters.shift()({ value: undefined, done: true });
}
},
async *[Symbol.asyncIterator]() {
while (true) {
if (queue.length > 0) {
yield queue.shift();
} else if (done) {
return;
} else {
const next = await new Promise((resolve) => waiters.push(resolve));
if (next.done) {
return;
}
yield next.value;
}
}
},
result() {
return resultPromise;
},
};
}
function extractLastUserPrompt(context) {
const messages = Array.isArray(context?.messages) ? context.messages : [];
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (message?.role !== "user") {
continue;
}
if (typeof message.content === "string") {
return message.content;
}
if (Array.isArray(message.content)) {
const text = message.content
.filter((item) => item?.type === "text" && typeof item.text === "string")
.map((item) => item.text)
.join(" ")
.trim();
if (text) {
return text;
}
}
}
return "kitchen sink text inference";
}
function estimateTokens(text) {
return Math.max(1, Math.ceil(String(text).trim().split(/\s+/).filter(Boolean).length * 1.35));
}
function normalizePrompt(value, fallback) {
const text = String(value ?? "").replace(/\s+/g, " ").trim();
return text || fallback;
}

View File

@ -1,10 +1,11 @@
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand. // Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
import { observeKitchenHook } from "./scenarios.js"; import { observeKitchenHook } from "./scenarios.js";
export function registerAllHooks(api) { export function registerAllHooks(api) {
api.on("after_compaction", kitchenSinkHook("after_compaction")); api.on("after_compaction", kitchenSinkHook("after_compaction"));
api.on("after_tool_call", kitchenSinkHook("after_tool_call")); api.on("after_tool_call", kitchenSinkHook("after_tool_call"));
api.on("agent_end", kitchenSinkHook("agent_end")); api.on("agent_end", kitchenSinkHook("agent_end"));
api.on("agent_turn_prepare", kitchenSinkHook("agent_turn_prepare"));
api.on("before_agent_finalize", kitchenSinkHook("before_agent_finalize")); api.on("before_agent_finalize", kitchenSinkHook("before_agent_finalize"));
api.on("before_agent_reply", kitchenSinkHook("before_agent_reply")); api.on("before_agent_reply", kitchenSinkHook("before_agent_reply"));
api.on("before_agent_start", kitchenSinkHook("before_agent_start")); api.on("before_agent_start", kitchenSinkHook("before_agent_start"));
@ -16,8 +17,10 @@ export function registerAllHooks(api) {
api.on("before_prompt_build", kitchenSinkHook("before_prompt_build")); api.on("before_prompt_build", kitchenSinkHook("before_prompt_build"));
api.on("before_reset", kitchenSinkHook("before_reset")); api.on("before_reset", kitchenSinkHook("before_reset"));
api.on("before_tool_call", kitchenSinkHook("before_tool_call")); api.on("before_tool_call", kitchenSinkHook("before_tool_call"));
api.on("cron_changed", kitchenSinkHook("cron_changed"));
api.on("gateway_start", kitchenSinkHook("gateway_start")); api.on("gateway_start", kitchenSinkHook("gateway_start"));
api.on("gateway_stop", kitchenSinkHook("gateway_stop")); api.on("gateway_stop", kitchenSinkHook("gateway_stop"));
api.on("heartbeat_prompt_contribution", kitchenSinkHook("heartbeat_prompt_contribution"));
api.on("inbound_claim", kitchenSinkHook("inbound_claim")); api.on("inbound_claim", kitchenSinkHook("inbound_claim"));
api.on("llm_input", kitchenSinkHook("llm_input")); api.on("llm_input", kitchenSinkHook("llm_input"));
api.on("llm_output", kitchenSinkHook("llm_output")); api.on("llm_output", kitchenSinkHook("llm_output"));

View File

@ -1,6 +1,7 @@
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand. // Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
export function registerAllRegistrars(api) { export function registerAllRegistrars(api) {
safeRegister("registerAgentEventSubscription", () => api.registerAgentEventSubscription(payloadFor("registerAgentEventSubscription")));
safeRegister("registerAgentHarness", () => api.registerAgentHarness(payloadFor("registerAgentHarness"))); safeRegister("registerAgentHarness", () => api.registerAgentHarness(payloadFor("registerAgentHarness")));
safeRegister("registerAgentToolResultMiddleware", () => api.registerAgentToolResultMiddleware(payloadFor("registerAgentToolResultMiddleware"))); safeRegister("registerAgentToolResultMiddleware", () => api.registerAgentToolResultMiddleware(payloadFor("registerAgentToolResultMiddleware")));
safeRegister("registerAutoEnableProbe", () => api.registerAutoEnableProbe(payloadFor("registerAutoEnableProbe"))); safeRegister("registerAutoEnableProbe", () => api.registerAutoEnableProbe(payloadFor("registerAutoEnableProbe")));
@ -12,6 +13,7 @@ export function registerAllRegistrars(api) {
safeRegister("registerCompactionProvider", () => api.registerCompactionProvider(payloadFor("registerCompactionProvider"))); safeRegister("registerCompactionProvider", () => api.registerCompactionProvider(payloadFor("registerCompactionProvider")));
safeRegister("registerConfigMigration", () => api.registerConfigMigration(payloadFor("registerConfigMigration"))); safeRegister("registerConfigMigration", () => api.registerConfigMigration(payloadFor("registerConfigMigration")));
safeRegister("registerContextEngine", () => api.registerContextEngine(payloadFor("registerContextEngine"))); safeRegister("registerContextEngine", () => api.registerContextEngine(payloadFor("registerContextEngine")));
safeRegister("registerControlUiDescriptor", () => api.registerControlUiDescriptor(payloadFor("registerControlUiDescriptor")));
void "api.registerDetachedTaskRuntime("; // Covered by the hand-owned Kitchen Sink task runtime. void "api.registerDetachedTaskRuntime("; // Covered by the hand-owned Kitchen Sink task runtime.
safeRegister("registerGatewayDiscoveryService", () => api.registerGatewayDiscoveryService(payloadFor("registerGatewayDiscoveryService"))); safeRegister("registerGatewayDiscoveryService", () => api.registerGatewayDiscoveryService(payloadFor("registerGatewayDiscoveryService")));
safeRegister("registerGatewayMethod", () => api.registerGatewayMethod(payloadFor("registerGatewayMethod"))); safeRegister("registerGatewayMethod", () => api.registerGatewayMethod(payloadFor("registerGatewayMethod")));
@ -30,15 +32,21 @@ export function registerAllRegistrars(api) {
safeRegister("registerMigrationProvider", () => api.registerMigrationProvider(payloadFor("registerMigrationProvider"))); safeRegister("registerMigrationProvider", () => api.registerMigrationProvider(payloadFor("registerMigrationProvider")));
safeRegister("registerMusicGenerationProvider", () => api.registerMusicGenerationProvider(payloadFor("registerMusicGenerationProvider"))); safeRegister("registerMusicGenerationProvider", () => api.registerMusicGenerationProvider(payloadFor("registerMusicGenerationProvider")));
safeRegister("registerNodeHostCommand", () => api.registerNodeHostCommand(payloadFor("registerNodeHostCommand"))); safeRegister("registerNodeHostCommand", () => api.registerNodeHostCommand(payloadFor("registerNodeHostCommand")));
safeRegister("registerNodeInvokePolicy", () => api.registerNodeInvokePolicy(payloadFor("registerNodeInvokePolicy")));
safeRegister("registerProvider", () => api.registerProvider(payloadFor("registerProvider"))); safeRegister("registerProvider", () => api.registerProvider(payloadFor("registerProvider")));
safeRegister("registerRealtimeTranscriptionProvider", () => api.registerRealtimeTranscriptionProvider(payloadFor("registerRealtimeTranscriptionProvider"))); safeRegister("registerRealtimeTranscriptionProvider", () => api.registerRealtimeTranscriptionProvider(payloadFor("registerRealtimeTranscriptionProvider")));
safeRegister("registerRealtimeVoiceProvider", () => api.registerRealtimeVoiceProvider(payloadFor("registerRealtimeVoiceProvider"))); safeRegister("registerRealtimeVoiceProvider", () => api.registerRealtimeVoiceProvider(payloadFor("registerRealtimeVoiceProvider")));
safeRegister("registerReload", () => api.registerReload(payloadFor("registerReload"))); safeRegister("registerReload", () => api.registerReload(payloadFor("registerReload")));
safeRegister("registerRuntimeLifecycle", () => api.registerRuntimeLifecycle(payloadFor("registerRuntimeLifecycle")));
safeRegister("registerSecurityAuditCollector", () => api.registerSecurityAuditCollector(payloadFor("registerSecurityAuditCollector"))); safeRegister("registerSecurityAuditCollector", () => api.registerSecurityAuditCollector(payloadFor("registerSecurityAuditCollector")));
safeRegister("registerService", () => api.registerService(payloadFor("registerService"))); safeRegister("registerService", () => api.registerService(payloadFor("registerService")));
safeRegister("registerSessionExtension", () => api.registerSessionExtension(payloadFor("registerSessionExtension")));
safeRegister("registerSessionSchedulerJob", () => api.registerSessionSchedulerJob(payloadFor("registerSessionSchedulerJob")));
safeRegister("registerSpeechProvider", () => api.registerSpeechProvider(payloadFor("registerSpeechProvider"))); safeRegister("registerSpeechProvider", () => api.registerSpeechProvider(payloadFor("registerSpeechProvider")));
safeRegister("registerTextTransforms", () => api.registerTextTransforms(payloadFor("registerTextTransforms"))); safeRegister("registerTextTransforms", () => api.registerTextTransforms(payloadFor("registerTextTransforms")));
safeRegister("registerTool", () => api.registerTool(payloadFor("registerTool"))); safeRegister("registerTool", () => api.registerTool(payloadFor("registerTool")));
safeRegister("registerToolMetadata", () => api.registerToolMetadata(payloadFor("registerToolMetadata")));
safeRegister("registerTrustedToolPolicy", () => api.registerTrustedToolPolicy(payloadFor("registerTrustedToolPolicy")));
safeRegister("registerVideoGenerationProvider", () => api.registerVideoGenerationProvider(payloadFor("registerVideoGenerationProvider"))); safeRegister("registerVideoGenerationProvider", () => api.registerVideoGenerationProvider(payloadFor("registerVideoGenerationProvider")));
safeRegister("registerWebFetchProvider", () => api.registerWebFetchProvider(payloadFor("registerWebFetchProvider"))); safeRegister("registerWebFetchProvider", () => api.registerWebFetchProvider(payloadFor("registerWebFetchProvider")));
safeRegister("registerWebSearchProvider", () => api.registerWebSearchProvider(payloadFor("registerWebSearchProvider"))); safeRegister("registerWebSearchProvider", () => api.registerWebSearchProvider(payloadFor("registerWebSearchProvider")));

View File

@ -1,4 +1,4 @@
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand. // Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
import type * as sdk0 from "openclaw/plugin-sdk"; import type * as sdk0 from "openclaw/plugin-sdk";
import type * as sdk1 from "openclaw/plugin-sdk/account-core"; import type * as sdk1 from "openclaw/plugin-sdk/account-core";
import type * as sdk2 from "openclaw/plugin-sdk/account-helpers"; import type * as sdk2 from "openclaw/plugin-sdk/account-helpers";
@ -8,261 +8,290 @@ import type * as sdk5 from "openclaw/plugin-sdk/account-resolution-runtime";
import type * as sdk6 from "openclaw/plugin-sdk/acp-binding-resolve-runtime"; import type * as sdk6 from "openclaw/plugin-sdk/acp-binding-resolve-runtime";
import type * as sdk7 from "openclaw/plugin-sdk/acp-binding-runtime"; import type * as sdk7 from "openclaw/plugin-sdk/acp-binding-runtime";
import type * as sdk8 from "openclaw/plugin-sdk/acp-runtime"; import type * as sdk8 from "openclaw/plugin-sdk/acp-runtime";
import type * as sdk9 from "openclaw/plugin-sdk/agent-config-primitives"; import type * as sdk9 from "openclaw/plugin-sdk/acp-runtime-backend";
import type * as sdk10 from "openclaw/plugin-sdk/agent-harness"; import type * as sdk10 from "openclaw/plugin-sdk/agent-config-primitives";
import type * as sdk11 from "openclaw/plugin-sdk/agent-harness-runtime"; import type * as sdk11 from "openclaw/plugin-sdk/agent-harness";
import type * as sdk12 from "openclaw/plugin-sdk/agent-media-payload"; import type * as sdk12 from "openclaw/plugin-sdk/agent-harness-runtime";
import type * as sdk13 from "openclaw/plugin-sdk/agent-runtime"; import type * as sdk13 from "openclaw/plugin-sdk/agent-media-payload";
import type * as sdk14 from "openclaw/plugin-sdk/allow-from"; import type * as sdk14 from "openclaw/plugin-sdk/agent-runtime";
import type * as sdk15 from "openclaw/plugin-sdk/allowlist-config-edit"; import type * as sdk15 from "openclaw/plugin-sdk/agent-runtime-test-contracts";
import type * as sdk16 from "openclaw/plugin-sdk/approval-auth-runtime"; import type * as sdk16 from "openclaw/plugin-sdk/allow-from";
import type * as sdk17 from "openclaw/plugin-sdk/approval-client-runtime"; import type * as sdk17 from "openclaw/plugin-sdk/allowlist-config-edit";
import type * as sdk18 from "openclaw/plugin-sdk/approval-delivery-runtime"; import type * as sdk18 from "openclaw/plugin-sdk/approval-auth-runtime";
import type * as sdk19 from "openclaw/plugin-sdk/approval-gateway-runtime"; import type * as sdk19 from "openclaw/plugin-sdk/approval-client-runtime";
import type * as sdk20 from "openclaw/plugin-sdk/approval-handler-adapter-runtime"; import type * as sdk20 from "openclaw/plugin-sdk/approval-delivery-runtime";
import type * as sdk21 from "openclaw/plugin-sdk/approval-handler-runtime"; import type * as sdk21 from "openclaw/plugin-sdk/approval-gateway-runtime";
import type * as sdk22 from "openclaw/plugin-sdk/approval-native-runtime"; import type * as sdk22 from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type * as sdk23 from "openclaw/plugin-sdk/approval-reply-runtime"; import type * as sdk23 from "openclaw/plugin-sdk/approval-handler-runtime";
import type * as sdk24 from "openclaw/plugin-sdk/approval-runtime"; import type * as sdk24 from "openclaw/plugin-sdk/approval-native-runtime";
import type * as sdk25 from "openclaw/plugin-sdk/boolean-param"; import type * as sdk25 from "openclaw/plugin-sdk/approval-reply-runtime";
import type * as sdk26 from "openclaw/plugin-sdk/browser-config"; import type * as sdk26 from "openclaw/plugin-sdk/approval-runtime";
import type * as sdk27 from "openclaw/plugin-sdk/channel-actions"; import type * as sdk27 from "openclaw/plugin-sdk/async-lock-runtime";
import type * as sdk28 from "openclaw/plugin-sdk/channel-config-helpers"; import type * as sdk28 from "openclaw/plugin-sdk/boolean-param";
import type * as sdk29 from "openclaw/plugin-sdk/channel-config-primitives"; import type * as sdk29 from "openclaw/plugin-sdk/browser-config";
import type * as sdk30 from "openclaw/plugin-sdk/channel-config-schema"; import type * as sdk30 from "openclaw/plugin-sdk/bundled-channel-config-schema";
import type * as sdk31 from "openclaw/plugin-sdk/channel-config-schema-legacy"; import type * as sdk31 from "openclaw/plugin-sdk/channel-actions";
import type * as sdk32 from "openclaw/plugin-sdk/channel-config-writes"; import type * as sdk32 from "openclaw/plugin-sdk/channel-activity-runtime";
import type * as sdk33 from "openclaw/plugin-sdk/channel-contract"; import type * as sdk33 from "openclaw/plugin-sdk/channel-config-helpers";
import type * as sdk34 from "openclaw/plugin-sdk/channel-contract-testing"; import type * as sdk34 from "openclaw/plugin-sdk/channel-config-primitives";
import type * as sdk35 from "openclaw/plugin-sdk/channel-core"; import type * as sdk35 from "openclaw/plugin-sdk/channel-config-schema";
import type * as sdk36 from "openclaw/plugin-sdk/channel-entry-contract"; import type * as sdk36 from "openclaw/plugin-sdk/channel-config-schema-legacy";
import type * as sdk37 from "openclaw/plugin-sdk/channel-envelope"; import type * as sdk37 from "openclaw/plugin-sdk/channel-config-writes";
import type * as sdk38 from "openclaw/plugin-sdk/channel-feedback"; import type * as sdk38 from "openclaw/plugin-sdk/channel-contract";
import type * as sdk39 from "openclaw/plugin-sdk/channel-inbound"; import type * as sdk39 from "openclaw/plugin-sdk/channel-contract-testing";
import type * as sdk40 from "openclaw/plugin-sdk/channel-inbound-debounce"; import type * as sdk40 from "openclaw/plugin-sdk/channel-core";
import type * as sdk41 from "openclaw/plugin-sdk/channel-inbound-roots"; import type * as sdk41 from "openclaw/plugin-sdk/channel-entry-contract";
import type * as sdk42 from "openclaw/plugin-sdk/channel-lifecycle"; import type * as sdk42 from "openclaw/plugin-sdk/channel-envelope";
import type * as sdk43 from "openclaw/plugin-sdk/channel-location"; import type * as sdk43 from "openclaw/plugin-sdk/channel-feedback";
import type * as sdk44 from "openclaw/plugin-sdk/channel-logging"; import type * as sdk44 from "openclaw/plugin-sdk/channel-inbound";
import type * as sdk45 from "openclaw/plugin-sdk/channel-mention-gating"; import type * as sdk45 from "openclaw/plugin-sdk/channel-inbound-debounce";
import type * as sdk46 from "openclaw/plugin-sdk/channel-pairing"; import type * as sdk46 from "openclaw/plugin-sdk/channel-inbound-roots";
import type * as sdk47 from "openclaw/plugin-sdk/channel-pairing-paths"; import type * as sdk47 from "openclaw/plugin-sdk/channel-lifecycle";
import type * as sdk48 from "openclaw/plugin-sdk/channel-plugin-common"; import type * as sdk48 from "openclaw/plugin-sdk/channel-location";
import type * as sdk49 from "openclaw/plugin-sdk/channel-policy"; import type * as sdk49 from "openclaw/plugin-sdk/channel-logging";
import type * as sdk50 from "openclaw/plugin-sdk/channel-reply-options-runtime"; import type * as sdk50 from "openclaw/plugin-sdk/channel-mention-gating";
import type * as sdk51 from "openclaw/plugin-sdk/channel-reply-pipeline"; import type * as sdk51 from "openclaw/plugin-sdk/channel-pairing";
import type * as sdk52 from "openclaw/plugin-sdk/channel-runtime"; import type * as sdk52 from "openclaw/plugin-sdk/channel-pairing-paths";
import type * as sdk53 from "openclaw/plugin-sdk/channel-runtime-context"; import type * as sdk53 from "openclaw/plugin-sdk/channel-plugin-common";
import type * as sdk54 from "openclaw/plugin-sdk/channel-secret-basic-runtime"; import type * as sdk54 from "openclaw/plugin-sdk/channel-policy";
import type * as sdk55 from "openclaw/plugin-sdk/channel-secret-runtime"; import type * as sdk55 from "openclaw/plugin-sdk/channel-reply-options-runtime";
import type * as sdk56 from "openclaw/plugin-sdk/channel-secret-tts-runtime"; import type * as sdk56 from "openclaw/plugin-sdk/channel-reply-pipeline";
import type * as sdk57 from "openclaw/plugin-sdk/channel-send-result"; import type * as sdk57 from "openclaw/plugin-sdk/channel-route";
import type * as sdk58 from "openclaw/plugin-sdk/channel-setup"; import type * as sdk58 from "openclaw/plugin-sdk/channel-runtime";
import type * as sdk59 from "openclaw/plugin-sdk/channel-status"; import type * as sdk59 from "openclaw/plugin-sdk/channel-runtime-context";
import type * as sdk60 from "openclaw/plugin-sdk/channel-streaming"; import type * as sdk60 from "openclaw/plugin-sdk/channel-secret-basic-runtime";
import type * as sdk61 from "openclaw/plugin-sdk/channel-targets"; import type * as sdk61 from "openclaw/plugin-sdk/channel-secret-runtime";
import type * as sdk62 from "openclaw/plugin-sdk/cli-backend"; import type * as sdk62 from "openclaw/plugin-sdk/channel-secret-tts-runtime";
import type * as sdk63 from "openclaw/plugin-sdk/cli-runtime"; import type * as sdk63 from "openclaw/plugin-sdk/channel-send-result";
import type * as sdk64 from "openclaw/plugin-sdk/collection-runtime"; import type * as sdk64 from "openclaw/plugin-sdk/channel-setup";
import type * as sdk65 from "openclaw/plugin-sdk/command-auth"; import type * as sdk65 from "openclaw/plugin-sdk/channel-status";
import type * as sdk66 from "openclaw/plugin-sdk/command-auth-native"; import type * as sdk66 from "openclaw/plugin-sdk/channel-streaming";
import type * as sdk67 from "openclaw/plugin-sdk/command-detection"; import type * as sdk67 from "openclaw/plugin-sdk/channel-target-testing";
import type * as sdk68 from "openclaw/plugin-sdk/command-gating"; import type * as sdk68 from "openclaw/plugin-sdk/channel-targets";
import type * as sdk69 from "openclaw/plugin-sdk/command-primitives-runtime"; import type * as sdk69 from "openclaw/plugin-sdk/channel-test-helpers";
import type * as sdk70 from "openclaw/plugin-sdk/command-status"; import type * as sdk70 from "openclaw/plugin-sdk/cli-backend";
import type * as sdk71 from "openclaw/plugin-sdk/command-status-runtime"; import type * as sdk71 from "openclaw/plugin-sdk/cli-runtime";
import type * as sdk72 from "openclaw/plugin-sdk/command-surface"; import type * as sdk72 from "openclaw/plugin-sdk/collection-runtime";
import type * as sdk73 from "openclaw/plugin-sdk/compat"; import type * as sdk73 from "openclaw/plugin-sdk/command-auth";
import type * as sdk74 from "openclaw/plugin-sdk/config-mutation"; import type * as sdk74 from "openclaw/plugin-sdk/command-auth-native";
import type * as sdk75 from "openclaw/plugin-sdk/config-runtime"; import type * as sdk75 from "openclaw/plugin-sdk/command-detection";
import type * as sdk76 from "openclaw/plugin-sdk/config-schema"; import type * as sdk76 from "openclaw/plugin-sdk/command-gating";
import type * as sdk77 from "openclaw/plugin-sdk/config-types"; import type * as sdk77 from "openclaw/plugin-sdk/command-primitives-runtime";
import type * as sdk78 from "openclaw/plugin-sdk/context-visibility-runtime"; import type * as sdk78 from "openclaw/plugin-sdk/command-status";
import type * as sdk79 from "openclaw/plugin-sdk/conversation-binding-runtime"; import type * as sdk79 from "openclaw/plugin-sdk/command-status-runtime";
import type * as sdk80 from "openclaw/plugin-sdk/conversation-runtime"; import type * as sdk80 from "openclaw/plugin-sdk/command-surface";
import type * as sdk81 from "openclaw/plugin-sdk/core"; import type * as sdk81 from "openclaw/plugin-sdk/compat";
import type * as sdk82 from "openclaw/plugin-sdk/cron-store-runtime"; import type * as sdk82 from "openclaw/plugin-sdk/concurrency-runtime";
import type * as sdk83 from "openclaw/plugin-sdk/dangerous-name-runtime"; import type * as sdk83 from "openclaw/plugin-sdk/config-mutation";
import type * as sdk84 from "openclaw/plugin-sdk/device-bootstrap"; import type * as sdk84 from "openclaw/plugin-sdk/config-runtime";
import type * as sdk85 from "openclaw/plugin-sdk/diagnostic-runtime"; import type * as sdk85 from "openclaw/plugin-sdk/config-schema";
import type * as sdk86 from "openclaw/plugin-sdk/direct-dm"; import type * as sdk86 from "openclaw/plugin-sdk/config-types";
import type * as sdk87 from "openclaw/plugin-sdk/direct-dm-access"; import type * as sdk87 from "openclaw/plugin-sdk/context-visibility-runtime";
import type * as sdk88 from "openclaw/plugin-sdk/direct-dm-guard-policy"; import type * as sdk88 from "openclaw/plugin-sdk/conversation-binding-runtime";
import type * as sdk89 from "openclaw/plugin-sdk/directory-config-runtime"; import type * as sdk89 from "openclaw/plugin-sdk/conversation-runtime";
import type * as sdk90 from "openclaw/plugin-sdk/directory-runtime"; import type * as sdk90 from "openclaw/plugin-sdk/core";
import type * as sdk91 from "openclaw/plugin-sdk/document-extractor"; import type * as sdk91 from "openclaw/plugin-sdk/cron-store-runtime";
import type * as sdk92 from "openclaw/plugin-sdk/error-runtime"; import type * as sdk92 from "openclaw/plugin-sdk/dangerous-name-runtime";
import type * as sdk93 from "openclaw/plugin-sdk/extension-shared"; import type * as sdk93 from "openclaw/plugin-sdk/dedupe-runtime";
import type * as sdk94 from "openclaw/plugin-sdk/fetch-runtime"; import type * as sdk94 from "openclaw/plugin-sdk/delivery-queue-runtime";
import type * as sdk95 from "openclaw/plugin-sdk/file-lock"; import type * as sdk95 from "openclaw/plugin-sdk/device-bootstrap";
import type * as sdk96 from "openclaw/plugin-sdk/gateway-runtime"; import type * as sdk96 from "openclaw/plugin-sdk/diagnostic-runtime";
import type * as sdk97 from "openclaw/plugin-sdk/global-singleton"; import type * as sdk97 from "openclaw/plugin-sdk/direct-dm";
import type * as sdk98 from "openclaw/plugin-sdk/group-access"; import type * as sdk98 from "openclaw/plugin-sdk/direct-dm-access";
import type * as sdk99 from "openclaw/plugin-sdk/group-activation"; import type * as sdk99 from "openclaw/plugin-sdk/direct-dm-guard-policy";
import type * as sdk100 from "openclaw/plugin-sdk/hook-runtime"; import type * as sdk100 from "openclaw/plugin-sdk/directory-config-runtime";
import type * as sdk101 from "openclaw/plugin-sdk/host-runtime"; import type * as sdk101 from "openclaw/plugin-sdk/directory-runtime";
import type * as sdk102 from "openclaw/plugin-sdk/image-generation"; import type * as sdk102 from "openclaw/plugin-sdk/discord";
import type * as sdk103 from "openclaw/plugin-sdk/image-generation-core"; import type * as sdk103 from "openclaw/plugin-sdk/document-extractor";
import type * as sdk104 from "openclaw/plugin-sdk/image-generation-runtime"; import type * as sdk104 from "openclaw/plugin-sdk/error-runtime";
import type * as sdk105 from "openclaw/plugin-sdk/inbound-envelope"; import type * as sdk105 from "openclaw/plugin-sdk/extension-shared";
import type * as sdk106 from "openclaw/plugin-sdk/inbound-reply-dispatch"; import type * as sdk106 from "openclaw/plugin-sdk/fetch-runtime";
import type * as sdk107 from "openclaw/plugin-sdk/infra-runtime"; import type * as sdk107 from "openclaw/plugin-sdk/file-access-runtime";
import type * as sdk108 from "openclaw/plugin-sdk/interactive-runtime"; import type * as sdk108 from "openclaw/plugin-sdk/file-lock";
import type * as sdk109 from "openclaw/plugin-sdk/json-store"; import type * as sdk109 from "openclaw/plugin-sdk/gateway-runtime";
import type * as sdk110 from "openclaw/plugin-sdk/keyed-async-queue"; import type * as sdk110 from "openclaw/plugin-sdk/global-singleton";
import type * as sdk111 from "openclaw/plugin-sdk/lazy-runtime"; import type * as sdk111 from "openclaw/plugin-sdk/group-access";
import type * as sdk112 from "openclaw/plugin-sdk/lmstudio"; import type * as sdk112 from "openclaw/plugin-sdk/group-activation";
import type * as sdk113 from "openclaw/plugin-sdk/lmstudio-runtime"; import type * as sdk113 from "openclaw/plugin-sdk/heartbeat-runtime";
import type * as sdk114 from "openclaw/plugin-sdk/logging-core"; import type * as sdk114 from "openclaw/plugin-sdk/hook-runtime";
import type * as sdk115 from "openclaw/plugin-sdk/markdown-table-runtime"; import type * as sdk115 from "openclaw/plugin-sdk/host-runtime";
import type * as sdk116 from "openclaw/plugin-sdk/media-generation-runtime"; import type * as sdk116 from "openclaw/plugin-sdk/image-generation";
import type * as sdk117 from "openclaw/plugin-sdk/media-generation-runtime-shared"; import type * as sdk117 from "openclaw/plugin-sdk/image-generation-core";
import type * as sdk118 from "openclaw/plugin-sdk/media-mime"; import type * as sdk118 from "openclaw/plugin-sdk/image-generation-runtime";
import type * as sdk119 from "openclaw/plugin-sdk/media-runtime"; import type * as sdk119 from "openclaw/plugin-sdk/inbound-envelope";
import type * as sdk120 from "openclaw/plugin-sdk/media-store"; import type * as sdk120 from "openclaw/plugin-sdk/inbound-reply-dispatch";
import type * as sdk121 from "openclaw/plugin-sdk/media-understanding"; import type * as sdk121 from "openclaw/plugin-sdk/infra-runtime";
import type * as sdk122 from "openclaw/plugin-sdk/media-understanding-runtime"; import type * as sdk122 from "openclaw/plugin-sdk/interactive-runtime";
import type * as sdk123 from "openclaw/plugin-sdk/memory-core-engine-runtime"; import type * as sdk123 from "openclaw/plugin-sdk/json-store";
import type * as sdk124 from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; import type * as sdk124 from "openclaw/plugin-sdk/keyed-async-queue";
import type * as sdk125 from "openclaw/plugin-sdk/memory-core-host-engine-foundation"; import type * as sdk125 from "openclaw/plugin-sdk/lazy-runtime";
import type * as sdk126 from "openclaw/plugin-sdk/memory-core-host-engine-qmd"; import type * as sdk126 from "openclaw/plugin-sdk/lmstudio";
import type * as sdk127 from "openclaw/plugin-sdk/memory-core-host-engine-storage"; import type * as sdk127 from "openclaw/plugin-sdk/lmstudio-runtime";
import type * as sdk128 from "openclaw/plugin-sdk/memory-core-host-events"; import type * as sdk128 from "openclaw/plugin-sdk/logging-core";
import type * as sdk129 from "openclaw/plugin-sdk/memory-core-host-multimodal"; import type * as sdk129 from "openclaw/plugin-sdk/markdown-table-runtime";
import type * as sdk130 from "openclaw/plugin-sdk/memory-core-host-query"; import type * as sdk130 from "openclaw/plugin-sdk/media-generation-runtime";
import type * as sdk131 from "openclaw/plugin-sdk/memory-core-host-runtime-cli"; import type * as sdk131 from "openclaw/plugin-sdk/media-generation-runtime-shared";
import type * as sdk132 from "openclaw/plugin-sdk/memory-core-host-runtime-core"; import type * as sdk132 from "openclaw/plugin-sdk/media-mime";
import type * as sdk133 from "openclaw/plugin-sdk/memory-core-host-runtime-files"; import type * as sdk133 from "openclaw/plugin-sdk/media-runtime";
import type * as sdk134 from "openclaw/plugin-sdk/memory-core-host-secret"; import type * as sdk134 from "openclaw/plugin-sdk/media-store";
import type * as sdk135 from "openclaw/plugin-sdk/memory-core-host-status"; import type * as sdk135 from "openclaw/plugin-sdk/media-understanding";
import type * as sdk136 from "openclaw/plugin-sdk/memory-host-core"; import type * as sdk136 from "openclaw/plugin-sdk/media-understanding-runtime";
import type * as sdk137 from "openclaw/plugin-sdk/memory-host-events"; import type * as sdk137 from "openclaw/plugin-sdk/memory-core-engine-runtime";
import type * as sdk138 from "openclaw/plugin-sdk/memory-host-files"; import type * as sdk138 from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
import type * as sdk139 from "openclaw/plugin-sdk/memory-host-markdown"; import type * as sdk139 from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import type * as sdk140 from "openclaw/plugin-sdk/memory-host-search"; import type * as sdk140 from "openclaw/plugin-sdk/memory-core-host-engine-qmd";
import type * as sdk141 from "openclaw/plugin-sdk/memory-host-status"; import type * as sdk141 from "openclaw/plugin-sdk/memory-core-host-engine-storage";
import type * as sdk142 from "openclaw/plugin-sdk/messaging-targets"; import type * as sdk142 from "openclaw/plugin-sdk/memory-core-host-events";
import type * as sdk143 from "openclaw/plugin-sdk/migration"; import type * as sdk143 from "openclaw/plugin-sdk/memory-core-host-multimodal";
import type * as sdk144 from "openclaw/plugin-sdk/migration-runtime"; import type * as sdk144 from "openclaw/plugin-sdk/memory-core-host-query";
import type * as sdk145 from "openclaw/plugin-sdk/model-session-runtime"; import type * as sdk145 from "openclaw/plugin-sdk/memory-core-host-runtime-cli";
import type * as sdk146 from "openclaw/plugin-sdk/models-provider-runtime"; import type * as sdk146 from "openclaw/plugin-sdk/memory-core-host-runtime-core";
import type * as sdk147 from "openclaw/plugin-sdk/music-generation"; import type * as sdk147 from "openclaw/plugin-sdk/memory-core-host-runtime-files";
import type * as sdk148 from "openclaw/plugin-sdk/music-generation-core"; import type * as sdk148 from "openclaw/plugin-sdk/memory-core-host-secret";
import type * as sdk149 from "openclaw/plugin-sdk/native-command-config-runtime"; import type * as sdk149 from "openclaw/plugin-sdk/memory-core-host-status";
import type * as sdk150 from "openclaw/plugin-sdk/native-command-registry"; import type * as sdk150 from "openclaw/plugin-sdk/memory-host-core";
import type * as sdk151 from "openclaw/plugin-sdk/outbound-media"; import type * as sdk151 from "openclaw/plugin-sdk/memory-host-events";
import type * as sdk152 from "openclaw/plugin-sdk/outbound-runtime"; import type * as sdk152 from "openclaw/plugin-sdk/memory-host-files";
import type * as sdk153 from "openclaw/plugin-sdk/outbound-send-deps"; import type * as sdk153 from "openclaw/plugin-sdk/memory-host-markdown";
import type * as sdk154 from "openclaw/plugin-sdk/param-readers"; import type * as sdk154 from "openclaw/plugin-sdk/memory-host-search";
import type * as sdk155 from "openclaw/plugin-sdk/persistent-dedupe"; import type * as sdk155 from "openclaw/plugin-sdk/memory-host-status";
import type * as sdk156 from "openclaw/plugin-sdk/plugin-config-runtime"; import type * as sdk156 from "openclaw/plugin-sdk/messaging-targets";
import type * as sdk157 from "openclaw/plugin-sdk/plugin-entry"; import type * as sdk157 from "openclaw/plugin-sdk/migration";
import type * as sdk158 from "openclaw/plugin-sdk/plugin-runtime"; import type * as sdk158 from "openclaw/plugin-sdk/migration-runtime";
import type * as sdk159 from "openclaw/plugin-sdk/poll-runtime"; import type * as sdk159 from "openclaw/plugin-sdk/model-session-runtime";
import type * as sdk160 from "openclaw/plugin-sdk/process-runtime"; import type * as sdk160 from "openclaw/plugin-sdk/models-provider-runtime";
import type * as sdk161 from "openclaw/plugin-sdk/provider-auth"; import type * as sdk161 from "openclaw/plugin-sdk/music-generation";
import type * as sdk162 from "openclaw/plugin-sdk/provider-auth-api-key"; import type * as sdk162 from "openclaw/plugin-sdk/music-generation-core";
import type * as sdk163 from "openclaw/plugin-sdk/provider-auth-login"; import type * as sdk163 from "openclaw/plugin-sdk/native-command-config-runtime";
import type * as sdk164 from "openclaw/plugin-sdk/provider-auth-result"; import type * as sdk164 from "openclaw/plugin-sdk/native-command-registry";
import type * as sdk165 from "openclaw/plugin-sdk/provider-auth-runtime"; import type * as sdk165 from "openclaw/plugin-sdk/number-runtime";
import type * as sdk166 from "openclaw/plugin-sdk/provider-catalog-shared"; import type * as sdk166 from "openclaw/plugin-sdk/outbound-media";
import type * as sdk167 from "openclaw/plugin-sdk/provider-entry"; import type * as sdk167 from "openclaw/plugin-sdk/outbound-runtime";
import type * as sdk168 from "openclaw/plugin-sdk/provider-env-vars"; import type * as sdk168 from "openclaw/plugin-sdk/outbound-send-deps";
import type * as sdk169 from "openclaw/plugin-sdk/provider-http"; import type * as sdk169 from "openclaw/plugin-sdk/param-readers";
import type * as sdk170 from "openclaw/plugin-sdk/provider-model-shared"; import type * as sdk170 from "openclaw/plugin-sdk/persistent-dedupe";
import type * as sdk171 from "openclaw/plugin-sdk/provider-model-types"; import type * as sdk171 from "openclaw/plugin-sdk/plugin-config-runtime";
import type * as sdk172 from "openclaw/plugin-sdk/provider-onboard"; import type * as sdk172 from "openclaw/plugin-sdk/plugin-entry";
import type * as sdk173 from "openclaw/plugin-sdk/provider-selection-runtime"; import type * as sdk173 from "openclaw/plugin-sdk/plugin-runtime";
import type * as sdk174 from "openclaw/plugin-sdk/provider-setup"; import type * as sdk174 from "openclaw/plugin-sdk/plugin-test-api";
import type * as sdk175 from "openclaw/plugin-sdk/provider-stream"; import type * as sdk175 from "openclaw/plugin-sdk/plugin-test-contracts";
import type * as sdk176 from "openclaw/plugin-sdk/provider-stream-family"; import type * as sdk176 from "openclaw/plugin-sdk/plugin-test-runtime";
import type * as sdk177 from "openclaw/plugin-sdk/provider-stream-shared"; import type * as sdk177 from "openclaw/plugin-sdk/poll-runtime";
import type * as sdk178 from "openclaw/plugin-sdk/provider-tools"; import type * as sdk178 from "openclaw/plugin-sdk/process-runtime";
import type * as sdk179 from "openclaw/plugin-sdk/provider-transport-runtime"; import type * as sdk179 from "openclaw/plugin-sdk/provider-auth";
import type * as sdk180 from "openclaw/plugin-sdk/provider-usage"; import type * as sdk180 from "openclaw/plugin-sdk/provider-auth-api-key";
import type * as sdk181 from "openclaw/plugin-sdk/provider-web-fetch"; import type * as sdk181 from "openclaw/plugin-sdk/provider-auth-login";
import type * as sdk182 from "openclaw/plugin-sdk/provider-web-fetch-contract"; import type * as sdk182 from "openclaw/plugin-sdk/provider-auth-result";
import type * as sdk183 from "openclaw/plugin-sdk/provider-web-search"; import type * as sdk183 from "openclaw/plugin-sdk/provider-auth-runtime";
import type * as sdk184 from "openclaw/plugin-sdk/provider-web-search-config-contract"; import type * as sdk184 from "openclaw/plugin-sdk/provider-catalog-runtime";
import type * as sdk185 from "openclaw/plugin-sdk/provider-web-search-contract"; import type * as sdk185 from "openclaw/plugin-sdk/provider-catalog-shared";
import type * as sdk186 from "openclaw/plugin-sdk/provider-zai-endpoint"; import type * as sdk186 from "openclaw/plugin-sdk/provider-entry";
import type * as sdk187 from "openclaw/plugin-sdk/proxy-capture"; import type * as sdk187 from "openclaw/plugin-sdk/provider-env-vars";
import type * as sdk188 from "openclaw/plugin-sdk/qa-runner-runtime"; import type * as sdk188 from "openclaw/plugin-sdk/provider-http";
import type * as sdk189 from "openclaw/plugin-sdk/realtime-transcription"; import type * as sdk189 from "openclaw/plugin-sdk/provider-http-test-mocks";
import type * as sdk190 from "openclaw/plugin-sdk/realtime-voice"; import type * as sdk190 from "openclaw/plugin-sdk/provider-model-shared";
import type * as sdk191 from "openclaw/plugin-sdk/reply-chunking"; import type * as sdk191 from "openclaw/plugin-sdk/provider-model-types";
import type * as sdk192 from "openclaw/plugin-sdk/reply-dedupe"; import type * as sdk192 from "openclaw/plugin-sdk/provider-onboard";
import type * as sdk193 from "openclaw/plugin-sdk/reply-dispatch-runtime"; import type * as sdk193 from "openclaw/plugin-sdk/provider-selection-runtime";
import type * as sdk194 from "openclaw/plugin-sdk/reply-history"; import type * as sdk194 from "openclaw/plugin-sdk/provider-setup";
import type * as sdk195 from "openclaw/plugin-sdk/reply-payload"; import type * as sdk195 from "openclaw/plugin-sdk/provider-stream";
import type * as sdk196 from "openclaw/plugin-sdk/reply-reference"; import type * as sdk196 from "openclaw/plugin-sdk/provider-stream-family";
import type * as sdk197 from "openclaw/plugin-sdk/reply-runtime"; import type * as sdk197 from "openclaw/plugin-sdk/provider-stream-shared";
import type * as sdk198 from "openclaw/plugin-sdk/request-url"; import type * as sdk198 from "openclaw/plugin-sdk/provider-test-contracts";
import type * as sdk199 from "openclaw/plugin-sdk/response-limit-runtime"; import type * as sdk199 from "openclaw/plugin-sdk/provider-tools";
import type * as sdk200 from "openclaw/plugin-sdk/retry-runtime"; import type * as sdk200 from "openclaw/plugin-sdk/provider-transport-runtime";
import type * as sdk201 from "openclaw/plugin-sdk/routing"; import type * as sdk201 from "openclaw/plugin-sdk/provider-usage";
import type * as sdk202 from "openclaw/plugin-sdk/run-command"; import type * as sdk202 from "openclaw/plugin-sdk/provider-web-fetch";
import type * as sdk203 from "openclaw/plugin-sdk/runtime"; import type * as sdk203 from "openclaw/plugin-sdk/provider-web-fetch-contract";
import type * as sdk204 from "openclaw/plugin-sdk/runtime-config-snapshot"; import type * as sdk204 from "openclaw/plugin-sdk/provider-web-search";
import type * as sdk205 from "openclaw/plugin-sdk/runtime-doctor"; import type * as sdk205 from "openclaw/plugin-sdk/provider-web-search-config-contract";
import type * as sdk206 from "openclaw/plugin-sdk/runtime-env"; import type * as sdk206 from "openclaw/plugin-sdk/provider-web-search-contract";
import type * as sdk207 from "openclaw/plugin-sdk/runtime-fetch"; import type * as sdk207 from "openclaw/plugin-sdk/provider-zai-endpoint";
import type * as sdk208 from "openclaw/plugin-sdk/runtime-group-policy"; import type * as sdk208 from "openclaw/plugin-sdk/proxy-capture";
import type * as sdk209 from "openclaw/plugin-sdk/runtime-logger"; import type * as sdk209 from "openclaw/plugin-sdk/qa-runner-runtime";
import type * as sdk210 from "openclaw/plugin-sdk/runtime-secret-resolution"; import type * as sdk210 from "openclaw/plugin-sdk/realtime-transcription";
import type * as sdk211 from "openclaw/plugin-sdk/runtime-store"; import type * as sdk211 from "openclaw/plugin-sdk/realtime-voice";
import type * as sdk212 from "openclaw/plugin-sdk/sandbox"; import type * as sdk212 from "openclaw/plugin-sdk/reply-chunking";
import type * as sdk213 from "openclaw/plugin-sdk/secret-file-runtime"; import type * as sdk213 from "openclaw/plugin-sdk/reply-dedupe";
import type * as sdk214 from "openclaw/plugin-sdk/secret-input"; import type * as sdk214 from "openclaw/plugin-sdk/reply-dispatch-runtime";
import type * as sdk215 from "openclaw/plugin-sdk/secret-input-runtime"; import type * as sdk215 from "openclaw/plugin-sdk/reply-history";
import type * as sdk216 from "openclaw/plugin-sdk/secret-ref-runtime"; import type * as sdk216 from "openclaw/plugin-sdk/reply-payload";
import type * as sdk217 from "openclaw/plugin-sdk/security-runtime"; import type * as sdk217 from "openclaw/plugin-sdk/reply-reference";
import type * as sdk218 from "openclaw/plugin-sdk/self-hosted-provider-setup"; import type * as sdk218 from "openclaw/plugin-sdk/reply-runtime";
import type * as sdk219 from "openclaw/plugin-sdk/session-binding-runtime"; import type * as sdk219 from "openclaw/plugin-sdk/request-url";
import type * as sdk220 from "openclaw/plugin-sdk/session-key-runtime"; import type * as sdk220 from "openclaw/plugin-sdk/response-limit-runtime";
import type * as sdk221 from "openclaw/plugin-sdk/session-store-runtime"; import type * as sdk221 from "openclaw/plugin-sdk/retry-runtime";
import type * as sdk222 from "openclaw/plugin-sdk/session-transcript-hit"; import type * as sdk222 from "openclaw/plugin-sdk/routing";
import type * as sdk223 from "openclaw/plugin-sdk/session-visibility"; import type * as sdk223 from "openclaw/plugin-sdk/run-command";
import type * as sdk224 from "openclaw/plugin-sdk/setup"; import type * as sdk224 from "openclaw/plugin-sdk/runtime";
import type * as sdk225 from "openclaw/plugin-sdk/setup-adapter-runtime"; import type * as sdk225 from "openclaw/plugin-sdk/runtime-config-snapshot";
import type * as sdk226 from "openclaw/plugin-sdk/setup-runtime"; import type * as sdk226 from "openclaw/plugin-sdk/runtime-doctor";
import type * as sdk227 from "openclaw/plugin-sdk/setup-tools"; import type * as sdk227 from "openclaw/plugin-sdk/runtime-env";
import type * as sdk228 from "openclaw/plugin-sdk/simple-completion-runtime"; import type * as sdk228 from "openclaw/plugin-sdk/runtime-fetch";
import type * as sdk229 from "openclaw/plugin-sdk/skill-commands-runtime"; import type * as sdk229 from "openclaw/plugin-sdk/runtime-group-policy";
import type * as sdk230 from "openclaw/plugin-sdk/skills-runtime"; import type * as sdk230 from "openclaw/plugin-sdk/runtime-logger";
import type * as sdk231 from "openclaw/plugin-sdk/speech"; import type * as sdk231 from "openclaw/plugin-sdk/runtime-secret-resolution";
import type * as sdk232 from "openclaw/plugin-sdk/speech-core"; import type * as sdk232 from "openclaw/plugin-sdk/runtime-store";
import type * as sdk233 from "openclaw/plugin-sdk/ssrf-dispatcher"; import type * as sdk233 from "openclaw/plugin-sdk/sandbox";
import type * as sdk234 from "openclaw/plugin-sdk/ssrf-policy"; import type * as sdk234 from "openclaw/plugin-sdk/secret-file-runtime";
import type * as sdk235 from "openclaw/plugin-sdk/ssrf-runtime"; import type * as sdk235 from "openclaw/plugin-sdk/secret-input";
import type * as sdk236 from "openclaw/plugin-sdk/state-paths"; import type * as sdk236 from "openclaw/plugin-sdk/secret-input-runtime";
import type * as sdk237 from "openclaw/plugin-sdk/status-helpers"; import type * as sdk237 from "openclaw/plugin-sdk/secret-ref-runtime";
import type * as sdk238 from "openclaw/plugin-sdk/string-coerce-runtime"; import type * as sdk238 from "openclaw/plugin-sdk/secure-random-runtime";
import type * as sdk239 from "openclaw/plugin-sdk/string-normalization-runtime"; import type * as sdk239 from "openclaw/plugin-sdk/security-runtime";
import type * as sdk240 from "openclaw/plugin-sdk/talk-config-runtime"; import type * as sdk240 from "openclaw/plugin-sdk/self-hosted-provider-setup";
import type * as sdk241 from "openclaw/plugin-sdk/target-resolver-runtime"; import type * as sdk241 from "openclaw/plugin-sdk/session-binding-runtime";
import type * as sdk242 from "openclaw/plugin-sdk/telegram-command-config"; import type * as sdk242 from "openclaw/plugin-sdk/session-key-runtime";
import type * as sdk243 from "openclaw/plugin-sdk/temp-path"; import type * as sdk243 from "openclaw/plugin-sdk/session-store-runtime";
import type * as sdk244 from "openclaw/plugin-sdk/testing"; import type * as sdk244 from "openclaw/plugin-sdk/session-transcript-hit";
import type * as sdk245 from "openclaw/plugin-sdk/text-autolink-runtime"; import type * as sdk245 from "openclaw/plugin-sdk/session-visibility";
import type * as sdk246 from "openclaw/plugin-sdk/text-chunking"; import type * as sdk246 from "openclaw/plugin-sdk/setup";
import type * as sdk247 from "openclaw/plugin-sdk/text-runtime"; import type * as sdk247 from "openclaw/plugin-sdk/setup-adapter-runtime";
import type * as sdk248 from "openclaw/plugin-sdk/thread-bindings-runtime"; import type * as sdk248 from "openclaw/plugin-sdk/setup-runtime";
import type * as sdk249 from "openclaw/plugin-sdk/thread-bindings-session-runtime"; import type * as sdk249 from "openclaw/plugin-sdk/setup-tools";
import type * as sdk250 from "openclaw/plugin-sdk/tool-payload"; import type * as sdk250 from "openclaw/plugin-sdk/simple-completion-runtime";
import type * as sdk251 from "openclaw/plugin-sdk/tool-send"; import type * as sdk251 from "openclaw/plugin-sdk/skill-commands-runtime";
import type * as sdk252 from "openclaw/plugin-sdk/tts-runtime"; import type * as sdk252 from "openclaw/plugin-sdk/skills-runtime";
import type * as sdk253 from "openclaw/plugin-sdk/video-generation"; import type * as sdk253 from "openclaw/plugin-sdk/speech";
import type * as sdk254 from "openclaw/plugin-sdk/video-generation-core"; import type * as sdk254 from "openclaw/plugin-sdk/speech-core";
import type * as sdk255 from "openclaw/plugin-sdk/video-generation-runtime"; import type * as sdk255 from "openclaw/plugin-sdk/ssrf-dispatcher";
import type * as sdk256 from "openclaw/plugin-sdk/web-content-extractor"; import type * as sdk256 from "openclaw/plugin-sdk/ssrf-policy";
import type * as sdk257 from "openclaw/plugin-sdk/web-media"; import type * as sdk257 from "openclaw/plugin-sdk/ssrf-runtime";
import type * as sdk258 from "openclaw/plugin-sdk/webhook-ingress"; import type * as sdk258 from "openclaw/plugin-sdk/state-paths";
import type * as sdk259 from "openclaw/plugin-sdk/webhook-path"; import type * as sdk259 from "openclaw/plugin-sdk/status-helpers";
import type * as sdk260 from "openclaw/plugin-sdk/webhook-request-guards"; import type * as sdk260 from "openclaw/plugin-sdk/string-coerce-runtime";
import type * as sdk261 from "openclaw/plugin-sdk/webhook-targets"; import type * as sdk261 from "openclaw/plugin-sdk/string-normalization-runtime";
import type * as sdk262 from "openclaw/plugin-sdk/windows-spawn"; import type * as sdk262 from "openclaw/plugin-sdk/system-event-runtime";
import type * as sdk263 from "openclaw/plugin-sdk/zod"; import type * as sdk263 from "openclaw/plugin-sdk/talk-config-runtime";
import type * as sdk264 from "openclaw/plugin-sdk/target-resolver-runtime";
import type * as sdk265 from "openclaw/plugin-sdk/telegram-account";
import type * as sdk266 from "openclaw/plugin-sdk/telegram-command-config";
import type * as sdk267 from "openclaw/plugin-sdk/temp-path";
import type * as sdk268 from "openclaw/plugin-sdk/test-env";
import type * as sdk269 from "openclaw/plugin-sdk/test-fixtures";
import type * as sdk270 from "openclaw/plugin-sdk/test-node-mocks";
import type * as sdk271 from "openclaw/plugin-sdk/testing";
import type * as sdk272 from "openclaw/plugin-sdk/text-autolink-runtime";
import type * as sdk273 from "openclaw/plugin-sdk/text-chunking";
import type * as sdk274 from "openclaw/plugin-sdk/text-runtime";
import type * as sdk275 from "openclaw/plugin-sdk/thread-bindings-runtime";
import type * as sdk276 from "openclaw/plugin-sdk/thread-bindings-session-runtime";
import type * as sdk277 from "openclaw/plugin-sdk/time-runtime";
import type * as sdk278 from "openclaw/plugin-sdk/tool-payload";
import type * as sdk279 from "openclaw/plugin-sdk/tool-send";
import type * as sdk280 from "openclaw/plugin-sdk/transport-ready-runtime";
import type * as sdk281 from "openclaw/plugin-sdk/tts-runtime";
import type * as sdk282 from "openclaw/plugin-sdk/video-generation";
import type * as sdk283 from "openclaw/plugin-sdk/video-generation-core";
import type * as sdk284 from "openclaw/plugin-sdk/video-generation-runtime";
import type * as sdk285 from "openclaw/plugin-sdk/web-content-extractor";
import type * as sdk286 from "openclaw/plugin-sdk/web-media";
import type * as sdk287 from "openclaw/plugin-sdk/webhook-ingress";
import type * as sdk288 from "openclaw/plugin-sdk/webhook-path";
import type * as sdk289 from "openclaw/plugin-sdk/webhook-request-guards";
import type * as sdk290 from "openclaw/plugin-sdk/webhook-targets";
import type * as sdk291 from "openclaw/plugin-sdk/windows-spawn";
import type * as sdk292 from "openclaw/plugin-sdk/zod";
export type KitchenSinkSdkImportSurface = export type KitchenSinkSdkImportSurface =
| typeof sdk0 | typeof sdk0
@ -528,4 +557,33 @@ export type KitchenSinkSdkImportSurface =
| typeof sdk260 | typeof sdk260
| typeof sdk261 | typeof sdk261
| typeof sdk262 | typeof sdk262
| typeof sdk263; | typeof sdk263
| typeof sdk264
| typeof sdk265
| typeof sdk266
| typeof sdk267
| typeof sdk268
| typeof sdk269
| typeof sdk270
| typeof sdk271
| typeof sdk272
| typeof sdk273
| typeof sdk274
| typeof sdk275
| typeof sdk276
| typeof sdk277
| typeof sdk278
| typeof sdk279
| typeof sdk280
| typeof sdk281
| typeof sdk282
| typeof sdk283
| typeof sdk284
| typeof sdk285
| typeof sdk286
| typeof sdk287
| typeof sdk288
| typeof sdk289
| typeof sdk290
| typeof sdk291
| typeof sdk292;

View File

@ -1,3 +1,4 @@
import { PLUGIN_ID } from "./constants.js";
import { registerAllHooks } from "./generated-hooks.js"; import { registerAllHooks } from "./generated-hooks.js";
import { registerAllRegistrars } from "./generated-registrars.js"; import { registerAllRegistrars } from "./generated-registrars.js";
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js"; import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
@ -7,9 +8,9 @@ import {
} from "./personality.js"; } from "./personality.js";
export const plugin = { export const plugin = {
id: "openclaw-kitchen-sink-fixture", id: PLUGIN_ID,
name: "OpenClaw Kitchen Sink", name: "OpenClaw Kitchen Sink",
version: "0.2.2", version: "0.2.5",
description: "Credential-free fixture covering OpenClaw plugin API seams.", description: "Credential-free fixture covering OpenClaw plugin API seams.",
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS, expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
register(api) { register(api) {

View File

@ -1,56 +1,48 @@
import { buildKitchenChannel } from "./runtime/channel.js";
import {
buildKitchenCommand,
buildKitchenImageTool,
buildKitchenInteractiveHandler,
buildKitchenSearchTool,
buildKitchenSinkCommand,
buildKitchenTextTool,
} from "./runtime/commands.js";
import {
buildKitchenCliMetadata,
buildKitchenCliRegistrar,
buildKitchenGatewayMethod,
buildKitchenHttpRoute,
buildKitchenService,
buildKitchenToolResultMiddleware,
} from "./runtime/platform.js";
import {
buildKitchenCompactionProvider,
buildKitchenImageProvider,
buildKitchenMediaProvider,
buildKitchenMemoryCorpusSupplement,
buildKitchenMemoryEmbeddingProvider,
buildKitchenMusicProvider,
buildKitchenRealtimeTranscriptionProvider,
buildKitchenRealtimeVoiceProvider,
buildKitchenSpeechProvider,
buildKitchenTextProvider,
buildKitchenVideoProvider,
buildKitchenWebFetchProvider,
buildKitchenWebSearchProvider,
} from "./runtime/providers.js";
import { buildKitchenDetachedTaskRuntime } from "./runtime/tasks.js";
import { import {
DEFAULT_IMAGE_MODEL,
DEFAULT_MEDIA_MODEL,
DEFAULT_TEXT_MODEL,
CHANNEL_ACCOUNT_ID,
CHANNEL_ID,
COMPACTION_PROVIDER_ID,
DEFAULT_EMBEDDING_MODEL,
IMAGE_PROVIDER_ID,
MEDIA_PROVIDER_ID,
MEMORY_EMBEDDING_PROVIDER_ID,
MUSIC_PROVIDER_ID,
PLUGIN_ID,
REALTIME_TRANSCRIPTION_PROVIDER_ID,
REALTIME_VOICE_PROVIDER_ID,
SPEECH_PROVIDER_ID,
TEXT_PROVIDER_ID,
VIDEO_PROVIDER_ID,
WEB_FETCH_PROVIDER_ID,
WEB_SEARCH_PROVIDER_ID,
createKitchenCompaction,
createKitchenEmbedding,
createKitchenMemorySearch,
createKitchenChannelDelivery,
createKitchenMusicResult,
createKitchenScenarioRuntime, createKitchenScenarioRuntime,
createKitchenSinkImageAsset, createKitchenSinkImageAsset,
createKitchenSpeechAsset,
createKitchenTextStream,
createKitchenTranscription,
createKitchenVideoResult,
extractInteractiveText,
kitchenChannelAccount,
kitchenImageDescription,
kitchenPromptGuidance, kitchenPromptGuidance,
kitchenSearchSchema,
kitchenTextModelDefinition,
kitchenTextProviderConfig,
kitchenToolSchema,
normalizeKitchenTarget,
readPrompt,
readQuery,
readUrl,
runKitchenCommand,
runKitchenFetch,
runKitchenImageTool,
runKitchenSearch,
shouldHandleKitchenText, shouldHandleKitchenText,
stripDataUrl,
} from "./scenarios.js"; } from "./scenarios.js";
export { createKitchenSinkImageAsset, kitchenPromptGuidance, shouldHandleKitchenText }; export { createKitchenSinkImageAsset, kitchenPromptGuidance, shouldHandleKitchenText };
// Keep this file as the readable runtime table of contents. The builders live
// under src/runtime/* so plugin authors can inspect one OpenClaw surface at a
// time without losing the full registration order.
export function registerKitchenSinkRuntime(api, options = {}) { export function registerKitchenSinkRuntime(api, options = {}) {
const runtime = createKitchenSinkRuntime(options); const runtime = createKitchenSinkRuntime(options);
const includeAgentToolResultMiddleware = options.includeAgentToolResultMiddleware !== false; const includeAgentToolResultMiddleware = options.includeAgentToolResultMiddleware !== false;
@ -126,767 +118,9 @@ export function createKitchenSinkRuntime(options = {}) {
return createKitchenScenarioRuntime(options); return createKitchenScenarioRuntime(options);
} }
function buildKitchenCommand(runtime) {
return {
name: "kitchen",
nativeNames: { default: "kitchen" },
description: "Run deterministic Kitchen Sink fixture scenarios.",
acceptsArgs: true,
requireAuth: false,
agentPromptGuidance: kitchenPromptGuidance(),
handler: async (ctx) => runKitchenCommand(runtime, ctx?.args ?? ctx?.commandBody ?? ""),
};
}
function buildKitchenSinkCommand(runtime) {
return {
...buildKitchenCommand(runtime),
name: "kitchen-sink",
nativeNames: { default: "kitchen-sink" },
};
}
function buildKitchenInteractiveHandler(runtime) {
return {
channel: "*",
namespace: "kitchen-sink",
handler: async (ctx) => {
const text = extractInteractiveText(ctx);
if (!shouldHandleKitchenText(text)) {
return { handled: false };
}
return {
handled: true,
reply: await runKitchenCommand(runtime, text.replace(/^kitchen\b/i, "").trim()),
};
},
};
}
function buildKitchenImageTool(runtime) {
return {
id: "kitchen_sink_image_job",
name: "kitchen_sink_image_job",
description:
"Generate a deterministic Kitchen Sink image fixture. Use when the user asks for a kitchen sink image, fixture image, or image-provider smoke test.",
inputSchema: kitchenToolSchema("Prompt for the deterministic image fixture."),
schema: kitchenToolSchema("Prompt for the deterministic image fixture."),
parameters: kitchenToolSchema("Prompt for the deterministic image fixture."),
handler: async (input) => runKitchenImageTool(runtime, input),
run: async (input) => runKitchenImageTool(runtime, input),
execute: async (input) => runKitchenImageTool(runtime, input),
};
}
function buildKitchenTextTool(runtime) {
return {
id: "kitchen_sink_text",
name: "kitchen_sink_text",
description:
"Return a deterministic text inference fixture response for Kitchen Sink plugin smoke tests.",
inputSchema: kitchenToolSchema("Prompt for the deterministic text fixture."),
schema: kitchenToolSchema("Prompt for the deterministic text fixture."),
parameters: kitchenToolSchema("Prompt for the deterministic text fixture."),
handler: async (input) => runtime.runTextJob({ prompt: readPrompt(input), route: "tool:kitchen_sink_text" }),
run: async (input) => runtime.runTextJob({ prompt: readPrompt(input), route: "tool:kitchen_sink_text" }),
execute: async (input) => runtime.runTextJob({ prompt: readPrompt(input), route: "tool:kitchen_sink_text" }),
};
}
function buildKitchenSearchTool() {
return {
id: "kitchen_sink_search",
name: "kitchen_sink_search",
description: "Return deterministic Kitchen Sink search results for tool-routing smoke tests.",
inputSchema: kitchenSearchSchema(),
schema: kitchenSearchSchema(),
parameters: kitchenSearchSchema(),
handler: async (input) => runKitchenSearch(readQuery(input)),
run: async (input) => runKitchenSearch(readQuery(input)),
execute: async (input) => runKitchenSearch(readQuery(input)),
};
}
function buildKitchenChannel() {
return {
id: CHANNEL_ID,
meta: {
id: CHANNEL_ID,
label: "Kitchen Sink",
selectionLabel: "Kitchen Sink",
docsPath: "/plugins/kitchen-sink",
docsLabel: "Kitchen Sink",
blurb: "Credential-free channel fixture for deterministic Kitchen Sink conversations.",
aliases: ["kitchen", "kitchen-sink"],
exposure: { configured: true, setup: true, docs: true },
showConfigured: true,
showInSetup: true,
},
capabilities: {
chatTypes: ["direct", "group", "channel"],
media: true,
nativeCommands: true,
reply: true,
threads: true,
},
config: {
listAccountIds: () => [CHANNEL_ACCOUNT_ID],
defaultAccountId: () => CHANNEL_ACCOUNT_ID,
resolveAccount: (cfg, accountId) => kitchenChannelAccount(accountId || CHANNEL_ACCOUNT_ID, cfg),
isEnabled: (cfg) => cfg?.disabled !== true,
isConfigured: (cfg) => cfg?.configured !== false,
describeAccount: (account) => kitchenChannelAccount(account.accountId, account),
resolveDefaultTo: () => "kitchen",
},
status: {
defaultRuntime: kitchenChannelAccount(),
probeAccount: async ({ account }) => ({
ok: true,
accountId: account.accountId,
scenarioId: "channel.probe",
}),
buildAccountSnapshot: ({ account }) => kitchenChannelAccount(account.accountId),
},
outbound: {
deliveryMode: "direct",
textChunkLimit: 2000,
sendText: async (ctx) =>
createKitchenChannelDelivery({ kind: "text", text: ctx?.text, to: ctx?.to }),
sendMedia: async (ctx) =>
createKitchenChannelDelivery({ kind: "media", text: ctx?.mediaUrl || ctx?.text, to: ctx?.to }),
},
messaging: {
normalizeTarget: (raw) => normalizeKitchenTarget(raw),
parseExplicitTarget: ({ raw }) => ({
to: normalizeKitchenTarget(raw),
chatType: "direct",
}),
inferTargetChatType: () => "direct",
resolveOutboundSessionRoute: ({ agentId, target, threadId }) => {
const to = normalizeKitchenTarget(target);
return {
sessionKey: `kitchen:${agentId || "agent"}:${to}`,
baseSessionKey: `kitchen:${agentId || "agent"}:${to}`,
peer: { kind: "direct", id: to },
chatType: "direct",
from: CHANNEL_ACCOUNT_ID,
to,
threadId: threadId || undefined,
};
},
},
agentPrompt: {
messageToolHints: () => kitchenPromptGuidance(),
messageToolCapabilities: () => [
"Kitchen Sink channel accepts deterministic dry messages prefixed with kitchen.",
"Kitchen Sink channel can deliver text and media without external credentials.",
],
},
};
}
function buildKitchenImageProvider(runtime) {
return {
id: IMAGE_PROVIDER_ID,
aliases: ["kitchen", "kitchen-sink", "openclaw-kitchen-sink"],
label: "Kitchen Sink Image",
defaultModel: DEFAULT_IMAGE_MODEL,
models: [DEFAULT_IMAGE_MODEL],
capabilities: {
generate: {
maxCount: 1,
supportsSize: true,
supportsAspectRatio: true,
supportsResolution: true,
},
edit: {
enabled: true,
maxInputImages: 1,
maxCount: 1,
},
geometry: {
sizes: ["1024x1024"],
aspectRatios: ["1:1"],
resolutions: ["1K"],
},
},
isConfigured: () => true,
generateImage: async (req) => {
const result = await runtime.runScenario({
scenario: "image.generate",
prompt: req?.prompt,
route: "provider:image",
model: req?.model,
});
if (result.error) {
throw kitchenProviderError(result);
}
return {
images: [stripDataUrl(result.image)],
model: req?.model || DEFAULT_IMAGE_MODEL,
metadata: {
kitchenSink: true,
job: result.job,
asset: result.image.metadata,
provider: IMAGE_PROVIDER_ID,
pluginId: PLUGIN_ID,
scenarioId: result.scenarioId,
route: result.route,
request: {
prompt: req?.prompt,
size: req?.size,
aspectRatio: req?.aspectRatio,
count: req?.count || 1,
},
},
};
},
};
}
function buildKitchenMediaProvider() {
return {
id: MEDIA_PROVIDER_ID,
capabilities: ["image", "audio", "video"],
defaultModels: { image: DEFAULT_MEDIA_MODEL },
autoPriority: { image: 5 },
describeImage: async (req) => ({
text: kitchenImageDescription(req?.prompt, 1),
model: req?.model || DEFAULT_MEDIA_MODEL,
}),
describeImages: async (req) => ({
text: kitchenImageDescription(req?.prompt, Array.isArray(req?.images) ? req.images.length : 0),
model: req?.model || DEFAULT_MEDIA_MODEL,
}),
transcribeAudio: async (req) => createKitchenTranscription({ audio: req?.audio, prompt: req?.prompt }),
describeVideo: async (req) => ({
text: "Kitchen Sink video fixture: three deterministic frames show the office sink asset, a close-up, and a fixture badge.",
model: req?.model || DEFAULT_MEDIA_MODEL,
metadata: { kitchenSink: true, provider: MEDIA_PROVIDER_ID, scenarioId: "media.video-describe" },
}),
};
}
function buildKitchenSpeechProvider() {
return {
id: SPEECH_PROVIDER_ID,
label: "Kitchen Sink Speech",
voices: ["kitchen-neutral", "kitchen-robot"],
defaultVoice: "kitchen-neutral",
isConfigured: () => true,
synthesize: async (req) => createKitchenSpeechAsset({
text: req?.text,
voice: req?.voice,
model: req?.model,
}),
speak: async (req) => createKitchenSpeechAsset({
text: req?.text,
voice: req?.voice,
model: req?.model,
}),
};
}
function buildKitchenRealtimeTranscriptionProvider() {
return {
id: REALTIME_TRANSCRIPTION_PROVIDER_ID,
label: "Kitchen Sink Realtime Transcription",
isConfigured: () => true,
createSession: (req = {}) => {
const chunks = [];
return {
provider: REALTIME_TRANSCRIPTION_PROVIDER_ID,
async connect() {
req.onReady?.({ provider: REALTIME_TRANSCRIPTION_PROVIDER_ID });
return { ok: true, provider: REALTIME_TRANSCRIPTION_PROVIDER_ID };
},
sendAudio(audio) {
chunks.push(audio);
req.onTranscript?.(`Kitchen Sink partial transcript ${chunks.length}.`);
},
async close() {
const result = createKitchenTranscription({ audio: Buffer.concat(chunks.map(toBuffer)) });
req.onTranscript?.(result.text);
req.onClose?.({ code: 1000, reason: "kitchen sink complete" });
return result;
},
};
},
};
}
function buildKitchenRealtimeVoiceProvider() {
return {
id: REALTIME_VOICE_PROVIDER_ID,
label: "Kitchen Sink Realtime Voice",
isConfigured: () => true,
createBridge: (req = {}) => {
let connected = false;
const audio = [];
return {
supportsToolResultContinuation: true,
async connect() {
connected = true;
req.onEvent?.({ type: "connected", provider: REALTIME_VOICE_PROVIDER_ID });
},
sendAudio(chunk) {
audio.push(chunk);
req.onTranscript?.("Kitchen Sink realtime voice heard audio.");
},
setMediaTimestamp(timestampMs) {
req.onEvent?.({ type: "media_timestamp", timestampMs });
},
submitToolResult(result) {
req.onEvent?.({ type: "tool_result", result });
},
acknowledgeMark(mark) {
req.onEvent?.({ type: "mark", mark });
},
close() {
connected = false;
req.onEvent?.({ type: "closed", audioChunks: audio.length });
},
isConnected: () => connected,
};
},
};
}
function buildKitchenVideoProvider() {
return {
id: VIDEO_PROVIDER_ID,
label: "Kitchen Sink Video",
defaultModel: "kitchen-sink-video-v1",
capabilities: {
generate: { maxVideos: 1, maxDurationSeconds: 3, supportsResolution: true },
imageToVideo: { enabled: true, maxVideos: 1, maxInputImages: 1, maxDurationSeconds: 3 },
videoToVideo: { enabled: false },
},
isConfigured: () => true,
generateVideo: async (req) => createKitchenVideoResult({ prompt: req?.prompt, model: req?.model }),
};
}
function buildKitchenMusicProvider() {
return {
id: MUSIC_PROVIDER_ID,
label: "Kitchen Sink Music",
defaultModel: "kitchen-sink-music-v1",
capabilities: {
generate: { maxTracks: 1, maxDurationSeconds: 1 },
edit: { enabled: true, maxInputAudio: 1, maxTracks: 1 },
},
isConfigured: () => true,
generateMusic: async (req) => createKitchenMusicResult({ prompt: req?.prompt, model: req?.model }),
generate: async (req) => createKitchenMusicResult({ prompt: req?.prompt, model: req?.model }),
};
}
function buildKitchenTextProvider() {
return {
id: TEXT_PROVIDER_ID,
label: "Kitchen Sink LLM",
docsPath: "/providers/models",
aliases: ["kitchen-sink-text", "kitchen"],
envVars: [],
auth: [
{
id: "none",
label: "No credentials",
hint: "Deterministic local fixture provider.",
kind: "custom",
run: async () => ({
profiles: [
{
id: "kitchen-sink-local",
label: "Kitchen Sink Local",
configured: true,
source: "fixture",
},
],
defaultModel: `${TEXT_PROVIDER_ID}/${DEFAULT_TEXT_MODEL}`,
notes: ["Kitchen Sink LLM is deterministic and does not call a network service."],
}),
},
],
staticCatalog: {
order: "simple",
run: async () => ({
provider: kitchenTextProviderConfig(),
}),
},
catalog: {
order: "simple",
run: async () => ({
provider: kitchenTextProviderConfig(),
}),
},
resolveDynamicModel: ({ modelId }) =>
modelId === DEFAULT_TEXT_MODEL ? kitchenTextModelDefinition() : undefined,
resolveSyntheticAuth: () => ({
apiKey: "kitchen-sink-local-fixture",
source: "kitchen-sink fixture",
mode: "token",
}),
createStreamFn: () => createKitchenTextStream,
resolveSystemPromptContribution: () => ({
stablePrefix: kitchenPromptGuidance().join("\n"),
}),
};
}
function buildKitchenWebSearchProvider() {
return {
id: WEB_SEARCH_PROVIDER_ID,
label: "Kitchen Sink Search",
hint: "Credential-free deterministic search fixture.",
requiresCredential: false,
envVars: [],
placeholder: "no key required",
signupUrl: "https://github.com/openclaw/kitchen-sink",
docsUrl: "https://github.com/openclaw/kitchen-sink#readme",
credentialPath: `${pluginConfigPath()}.search`,
getCredentialValue: () => "fixture",
setCredentialValue: (target, value) => {
target.fixture = value;
},
applySelectionConfig: (config) => config,
resolveRuntimeMetadata: async () => ({ provider: WEB_SEARCH_PROVIDER_ID, pluginId: PLUGIN_ID }),
createTool: () => ({
description: "Search the deterministic Kitchen Sink fixture corpus.",
parameters: kitchenSearchSchema(),
execute: async (args) => runKitchenSearch(readQuery(args)),
}),
};
}
function buildKitchenWebFetchProvider() {
return {
id: WEB_FETCH_PROVIDER_ID,
label: "Kitchen Sink Fetch",
hint: "Credential-free deterministic fetch fixture.",
requiresCredential: false,
envVars: [],
placeholder: "no key required",
signupUrl: "https://github.com/openclaw/kitchen-sink",
docsUrl: "https://github.com/openclaw/kitchen-sink#readme",
credentialPath: `${pluginConfigPath()}.fetch`,
getCredentialValue: () => "fixture",
setCredentialValue: (target, value) => {
target.fixture = value;
},
applySelectionConfig: (config) => config,
resolveRuntimeMetadata: async () => ({ provider: WEB_FETCH_PROVIDER_ID, pluginId: PLUGIN_ID }),
createTool: () => ({
description: "Fetch deterministic Kitchen Sink fixture documents.",
parameters: {
type: "object",
additionalProperties: false,
properties: {
url: { type: "string", description: "Fixture URL or topic." },
},
},
execute: async (args) => runKitchenFetch(readUrl(args)),
}),
};
}
function buildKitchenMemoryEmbeddingProvider() {
return {
id: MEMORY_EMBEDDING_PROVIDER_ID,
label: "Kitchen Sink Memory Embeddings",
model: DEFAULT_EMBEDDING_MODEL,
dimensions: 8,
isConfigured: () => true,
embed: async (input) => ({
provider: MEMORY_EMBEDDING_PROVIDER_ID,
model: DEFAULT_EMBEDDING_MODEL,
embedding: createKitchenEmbedding(typeof input === "string" ? input : input?.text),
}),
embedMany: async (input) => {
const texts = Array.isArray(input) ? input : Array.isArray(input?.texts) ? input.texts : [input?.text ?? ""];
return {
provider: MEMORY_EMBEDDING_PROVIDER_ID,
model: DEFAULT_EMBEDDING_MODEL,
embeddings: texts.map((text) => createKitchenEmbedding(text)),
};
},
};
}
function buildKitchenMemoryCorpusSupplement() {
return {
id: "kitchen-sink-memory-corpus",
label: "Kitchen Sink Memory Corpus",
search: async (query) => createKitchenMemorySearch(typeof query === "string" ? query : query?.query),
read: async (id = "ks-memory-runtime-surfaces") => ({
id,
title: "Kitchen Sink runtime surfaces",
text: "Kitchen Sink memory corpus fixture covering providers, channels, hooks, compaction, and tasks.",
metadata: { kitchenSink: true, pluginId: PLUGIN_ID, scenarioId: "memory.read" },
}),
list: async () => ({
items: [{ id: "ks-memory-runtime-surfaces", title: "Kitchen Sink runtime surfaces" }],
}),
};
}
function buildKitchenCompactionProvider() {
return {
id: COMPACTION_PROVIDER_ID,
label: "Kitchen Sink Compaction",
compact: async (input) => createKitchenCompaction(input),
summarize: async (input) => createKitchenCompaction(input),
};
}
function buildKitchenToolResultMiddleware() {
return async (event = {}) => ({
...event,
kitchenSink: true,
pluginId: PLUGIN_ID,
scenarioId: "tool-result.middleware",
result: event.result,
metadata: {
...(event.metadata || {}),
kitchenSinkToolResultMiddleware: true,
},
});
}
function buildKitchenService() {
return {
id: "kitchen-sink-service",
name: "Kitchen Sink Service",
description: "Credential-free background service fixture.",
start: async () => ({ ok: true, service: "kitchen-sink-service", state: "started" }),
stop: async () => ({ ok: true, service: "kitchen-sink-service", state: "stopped" }),
probe: async () => ({ ok: true, service: "kitchen-sink-service", state: "ready" }),
};
}
function buildKitchenHttpRoute() {
return {
id: "kitchen-sink-http-status",
path: "/kitchen-sink/status",
auth: "gateway",
match: "exact",
handler: async (_req, res) => {
const body = JSON.stringify({ ok: true, pluginId: PLUGIN_ID, scenarioId: "http.status" });
if (res && typeof res === "object") {
res.statusCode = 200;
res.setHeader?.("content-type", "application/json");
res.end?.(body);
}
return { ok: true, body };
},
};
}
function buildKitchenGatewayMethod() {
return async () => ({
ok: true,
pluginId: PLUGIN_ID,
providerIds: [
SPEECH_PROVIDER_ID,
REALTIME_TRANSCRIPTION_PROVIDER_ID,
REALTIME_VOICE_PROVIDER_ID,
VIDEO_PROVIDER_ID,
MUSIC_PROVIDER_ID,
MEMORY_EMBEDDING_PROVIDER_ID,
COMPACTION_PROVIDER_ID,
],
});
}
function buildKitchenCliRegistrar() {
return async ({ program } = {}) => {
program?.command?.("kitchen-sink")?.description?.("Run Kitchen Sink fixture commands.");
return { ok: true, command: "kitchen-sink" };
};
}
function buildKitchenCliMetadata() {
return {
descriptors: [
{
name: "kitchen-sink",
description: "Run Kitchen Sink fixture commands.",
hasSubcommands: true,
},
],
};
}
function buildKitchenDetachedTaskRuntime() {
const tasks = new Map();
function create(params, status) {
const now = Date.now();
const runId = params.runId || `ks_task_${Math.abs(hashTask(params.task || status))}`;
const task = {
taskId: runId,
runId,
runtime: params.runtime || "cli",
taskKind: params.taskKind || "kitchen-sink",
sourceId: params.sourceId || PLUGIN_ID,
requesterSessionKey: params.requesterSessionKey || "kitchen-sink",
ownerKey: params.ownerKey || PLUGIN_ID,
scopeKind: params.scopeKind || "session",
childSessionKey: params.childSessionKey,
parentFlowId: params.parentFlowId,
parentTaskId: params.parentTaskId,
agentId: params.agentId,
label: params.label || "Kitchen Sink task",
task: params.task,
status,
deliveryStatus: params.deliveryStatus || "not_applicable",
notifyPolicy: params.notifyPolicy || "done_only",
createdAt: now,
startedAt: status === "running" ? params.startedAt || now : params.startedAt,
lastEventAt: params.lastEventAt || now,
progressSummary: params.progressSummary || undefined,
};
tasks.set(runId, task);
return task;
}
function update(runId, patch) {
const current = tasks.get(runId);
if (!current) {
return [];
}
const cleanPatch = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined));
const next = { ...current, ...cleanPatch };
tasks.set(runId, next);
return [next];
}
return {
createQueuedTaskRun: (params) => create(params, "queued"),
createRunningTaskRun: (params) => create(params, "running"),
startTaskRunByRunId: (params) =>
update(params.runId, {
status: "running",
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
startedAt: params.startedAt || Date.now(),
lastEventAt: params.lastEventAt || Date.now(),
progressSummary: params.progressSummary || params.eventSummary || "Kitchen Sink task started.",
}),
recordTaskRunProgressByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
lastEventAt: params.lastEventAt || Date.now(),
progressSummary: params.progressSummary || params.eventSummary || "Kitchen Sink task progressed.",
}),
finalizeTaskRunByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
status: params.status,
endedAt: params.endedAt,
lastEventAt: params.lastEventAt || params.endedAt,
error: params.error,
progressSummary: params.progressSummary || undefined,
terminalSummary: params.terminalSummary || undefined,
terminalOutcome: params.terminalOutcome || undefined,
}),
completeTaskRunByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
status: "succeeded",
endedAt: params.endedAt,
lastEventAt: params.lastEventAt || params.endedAt,
progressSummary: params.progressSummary || undefined,
terminalSummary: params.terminalSummary || "Kitchen Sink task completed.",
terminalOutcome: params.terminalOutcome || "succeeded",
}),
failTaskRunByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
status: params.status || "failed",
endedAt: params.endedAt,
lastEventAt: params.lastEventAt || params.endedAt,
error: params.error,
progressSummary: params.progressSummary || undefined,
terminalSummary: params.terminalSummary || "Kitchen Sink task failed.",
}),
setDetachedTaskDeliveryStatusByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
deliveryStatus: params.deliveryStatus,
error: params.error,
}),
cancelDetachedTaskRunById: async ({ taskId }) => {
const current = tasks.get(taskId);
if (!current) {
return { found: false, cancelled: false, reason: "not owned by Kitchen Sink" };
}
const task = {
...current,
status: "cancelled",
endedAt: Date.now(),
lastEventAt: Date.now(),
terminalSummary: "Kitchen Sink task cancelled.",
};
tasks.set(taskId, task);
return { found: true, cancelled: true, task };
},
tryRecoverTaskBeforeMarkLost: ({ task }) => ({
recovered: Boolean(task?.taskId && tasks.has(task.taskId)),
}),
};
}
function hashTask(input) {
let hash = 0;
for (const char of String(input)) {
hash = Math.imul(31, hash) + char.charCodeAt(0);
}
return hash;
}
function pluginConfigPath() {
return `plugins.${PLUGIN_ID}`;
}
function kitchenProviderError(result) {
const error = new Error(result.error.message);
error.name = "KitchenSinkProviderError";
error.code = result.error.code;
error.statusCode = result.error.statusCode;
error.retryable = result.error.retryable;
error.retryAfterMs = result.error.retryAfterMs;
error.metadata = {
kitchenSink: true,
job: result.job,
pluginId: PLUGIN_ID,
provider: IMAGE_PROVIDER_ID,
scenarioId: result.scenarioId,
route: result.route,
};
return error;
}
function toBuffer(value) {
if (Buffer.isBuffer(value)) {
return value;
}
if (value instanceof Uint8Array) {
return Buffer.from(value);
}
if (typeof value === "string") {
return Buffer.from(value);
}
return Buffer.alloc(0);
}
function optionalRegister(api, method, register) { function optionalRegister(api, method, register) {
// Kitchen Sink runs against multiple SDK/inspector versions. Missing optional
// registrar methods should quietly no-op instead of making older hosts fail.
if (typeof api?.[method] !== "function") { if (typeof api?.[method] !== "function") {
return; return;
} }

View File

@ -5,28 +5,44 @@ export const DEFAULT_KITCHEN_SINK_PERSONALITY = "full";
export const KITCHEN_SINK_EXPECTED_DIAGNOSTICS = { export const KITCHEN_SINK_EXPECTED_DIAGNOSTICS = {
full: [ full: [
"only bundled plugins can register agent tool result middleware", "only bundled plugins can register agent tool result middleware",
"agent event subscription registration requires id and handle",
'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods', 'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods',
'channel "kitchen-sink-channel-probe" registration missing required config helpers', 'channel "kitchen-sink-channel-probe" registration missing required config helpers',
"cli registration missing explicit commands metadata", "cli registration missing explicit commands metadata",
"only bundled plugins can register Codex app-server extension factories", "only bundled plugins can register Codex app-server extension factories",
'compaction provider "kitchen-sink-compaction-provider" registration missing summarize', 'compaction provider "kitchen-sink-compaction-provider" registration missing summarize',
"context engine registration missing id", "context engine registration missing id",
"control UI descriptor registration requires id, surface, label, and valid optional fields",
"http route registration missing or invalid auth: /kitchen-sink/http-route", "http route registration missing or invalid auth: /kitchen-sink/http-route",
"node invoke policy registration missing commands",
"only bundled plugins can register trusted tool policies",
"plugin must declare contracts.tools for: kitchen-sink-tool",
"plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider", "plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider",
"memory prompt supplement registration missing builder", "memory prompt supplement registration missing builder",
"session extension registration requires namespace and description",
"session scheduler job registration requires unique id, sessionKey, and kind",
"tool metadata registration missing toolName",
], ],
conformance: [], conformance: [],
adversarial: [ adversarial: [
"only bundled plugins can register agent tool result middleware", "only bundled plugins can register agent tool result middleware",
"agent event subscription registration requires id and handle",
'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods', 'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods',
'channel "kitchen-sink-channel-probe" registration missing required config helpers', 'channel "kitchen-sink-channel-probe" registration missing required config helpers',
"cli registration missing explicit commands metadata", "cli registration missing explicit commands metadata",
"only bundled plugins can register Codex app-server extension factories", "only bundled plugins can register Codex app-server extension factories",
'compaction provider "kitchen-sink-compaction-provider" registration missing summarize', 'compaction provider "kitchen-sink-compaction-provider" registration missing summarize',
"context engine registration missing id", "context engine registration missing id",
"control UI descriptor registration requires id, surface, label, and valid optional fields",
"http route registration missing or invalid auth: /kitchen-sink/http-route", "http route registration missing or invalid auth: /kitchen-sink/http-route",
"node invoke policy registration missing commands",
"only bundled plugins can register trusted tool policies",
"plugin must declare contracts.tools for: kitchen-sink-tool",
"plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider", "plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider",
"memory prompt supplement registration missing builder", "memory prompt supplement registration missing builder",
"session extension registration requires namespace and description",
"session scheduler job registration requires unique id, sessionKey, and kind",
"tool metadata registration missing toolName",
], ],
}; };

88
src/runtime/channel.js Normal file
View File

@ -0,0 +1,88 @@
import {
CHANNEL_ACCOUNT_ID,
CHANNEL_ID,
} from "../constants.js";
import {
createKitchenChannelDelivery,
kitchenChannelAccount,
kitchenPromptGuidance,
normalizeKitchenTarget,
} from "../scenarios.js";
export function buildKitchenChannel() {
return {
id: CHANNEL_ID,
meta: {
id: CHANNEL_ID,
label: "Kitchen Sink",
selectionLabel: "Kitchen Sink",
docsPath: "/plugins/kitchen-sink",
docsLabel: "Kitchen Sink",
blurb: "Credential-free channel fixture for deterministic Kitchen Sink conversations.",
aliases: ["kitchen", "kitchen-sink"],
exposure: { configured: true, setup: true, docs: true },
showConfigured: true,
showInSetup: true,
},
capabilities: {
chatTypes: ["direct", "group", "channel"],
media: true,
nativeCommands: true,
reply: true,
threads: true,
},
config: {
listAccountIds: () => [CHANNEL_ACCOUNT_ID],
defaultAccountId: () => CHANNEL_ACCOUNT_ID,
resolveAccount: (cfg, accountId) => kitchenChannelAccount(accountId || CHANNEL_ACCOUNT_ID, cfg),
isEnabled: (cfg) => cfg?.disabled !== true,
isConfigured: (cfg) => cfg?.configured !== false,
describeAccount: (account) => kitchenChannelAccount(account.accountId, account),
resolveDefaultTo: () => "kitchen",
},
status: {
defaultRuntime: kitchenChannelAccount(),
probeAccount: async ({ account }) => ({
ok: true,
accountId: account.accountId,
scenarioId: "channel.probe",
}),
buildAccountSnapshot: ({ account }) => kitchenChannelAccount(account.accountId),
},
outbound: {
deliveryMode: "direct",
textChunkLimit: 2000,
sendText: async (ctx) =>
createKitchenChannelDelivery({ kind: "text", text: ctx?.text, to: ctx?.to }),
sendMedia: async (ctx) =>
createKitchenChannelDelivery({ kind: "media", text: ctx?.mediaUrl || ctx?.text, to: ctx?.to }),
},
messaging: {
normalizeTarget: (raw) => normalizeKitchenTarget(raw),
parseExplicitTarget: ({ raw }) => ({
to: normalizeKitchenTarget(raw),
chatType: "direct",
}),
inferTargetChatType: () => "direct",
resolveOutboundSessionRoute: ({ agentId, target, threadId }) => {
const to = normalizeKitchenTarget(target);
return {
sessionKey: `kitchen:${agentId || "agent"}:${to}`,
baseSessionKey: `kitchen:${agentId || "agent"}:${to}`,
peer: { kind: "direct", id: to },
chatType: "direct",
from: CHANNEL_ACCOUNT_ID,
to,
threadId: threadId || undefined,
};
},
},
agentPrompt: {
messageToolHints: () => kitchenPromptGuidance(),
messageToolCapabilities: () => [
"Kitchen Sink channel accepts deterministic dry messages prefixed with kitchen.",
"Kitchen Sink channel can deliver text and media without external credentials.",
],
},
};
}

93
src/runtime/commands.js Normal file
View File

@ -0,0 +1,93 @@
import {
extractInteractiveText,
kitchenPromptGuidance,
kitchenSearchSchema,
kitchenToolSchema,
readPrompt,
readQuery,
runKitchenCommand,
runKitchenImageTool,
runKitchenSearch,
shouldHandleKitchenText,
} from "../scenarios.js";
export function buildKitchenCommand(runtime) {
return {
name: "kitchen",
nativeNames: { default: "kitchen" },
description: "Run deterministic Kitchen Sink fixture scenarios.",
acceptsArgs: true,
requireAuth: false,
agentPromptGuidance: kitchenPromptGuidance(),
handler: async (ctx) => runKitchenCommand(runtime, ctx?.args ?? ctx?.commandBody ?? ""),
};
}
export function buildKitchenSinkCommand(runtime) {
return {
...buildKitchenCommand(runtime),
name: "kitchen-sink",
nativeNames: { default: "kitchen-sink" },
};
}
export function buildKitchenInteractiveHandler(runtime) {
return {
channel: "*",
namespace: "kitchen-sink",
handler: async (ctx) => {
const text = extractInteractiveText(ctx);
if (!shouldHandleKitchenText(text)) {
return { handled: false };
}
return {
handled: true,
reply: await runKitchenCommand(runtime, text.replace(/^kitchen\b/i, "").trim()),
};
},
};
}
export function buildKitchenImageTool(runtime) {
return {
id: "kitchen_sink_image_job",
name: "kitchen_sink_image_job",
description:
"Generate a deterministic Kitchen Sink image fixture. Use when the user asks for a kitchen sink image, fixture image, or image-provider smoke test.",
inputSchema: kitchenToolSchema("Prompt for the deterministic image fixture."),
schema: kitchenToolSchema("Prompt for the deterministic image fixture."),
parameters: kitchenToolSchema("Prompt for the deterministic image fixture."),
handler: async (input) => runKitchenImageTool(runtime, input),
run: async (input) => runKitchenImageTool(runtime, input),
execute: async (input) => runKitchenImageTool(runtime, input),
};
}
export function buildKitchenTextTool(runtime) {
return {
id: "kitchen_sink_text",
name: "kitchen_sink_text",
description:
"Return a deterministic text inference fixture response for Kitchen Sink plugin smoke tests.",
inputSchema: kitchenToolSchema("Prompt for the deterministic text fixture."),
schema: kitchenToolSchema("Prompt for the deterministic text fixture."),
parameters: kitchenToolSchema("Prompt for the deterministic text fixture."),
handler: async (input) => runtime.runTextJob({ prompt: readPrompt(input), route: "tool:kitchen_sink_text" }),
run: async (input) => runtime.runTextJob({ prompt: readPrompt(input), route: "tool:kitchen_sink_text" }),
execute: async (input) => runtime.runTextJob({ prompt: readPrompt(input), route: "tool:kitchen_sink_text" }),
};
}
export function buildKitchenSearchTool() {
return {
id: "kitchen_sink_search",
name: "kitchen_sink_search",
description: "Return deterministic Kitchen Sink search results for tool-routing smoke tests.",
inputSchema: kitchenSearchSchema(),
schema: kitchenSearchSchema(),
parameters: kitchenSearchSchema(),
handler: async (input) => runKitchenSearch(readQuery(input)),
run: async (input) => runKitchenSearch(readQuery(input)),
execute: async (input) => runKitchenSearch(readQuery(input)),
};
}

88
src/runtime/platform.js Normal file
View File

@ -0,0 +1,88 @@
import {
COMPACTION_PROVIDER_ID,
MEMORY_EMBEDDING_PROVIDER_ID,
MUSIC_PROVIDER_ID,
PLUGIN_ID,
REALTIME_TRANSCRIPTION_PROVIDER_ID,
REALTIME_VOICE_PROVIDER_ID,
SPEECH_PROVIDER_ID,
VIDEO_PROVIDER_ID,
} from "../constants.js";
export function buildKitchenToolResultMiddleware() {
return async (event = {}) => ({
...event,
kitchenSink: true,
pluginId: PLUGIN_ID,
scenarioId: "tool-result.middleware",
result: event.result,
metadata: {
...(event.metadata || {}),
kitchenSinkToolResultMiddleware: true,
},
});
}
export function buildKitchenService() {
return {
id: "kitchen-sink-service",
name: "Kitchen Sink Service",
description: "Credential-free background service fixture.",
start: async () => ({ ok: true, service: "kitchen-sink-service", state: "started" }),
stop: async () => ({ ok: true, service: "kitchen-sink-service", state: "stopped" }),
probe: async () => ({ ok: true, service: "kitchen-sink-service", state: "ready" }),
};
}
export function buildKitchenHttpRoute() {
return {
id: "kitchen-sink-http-status",
path: "/kitchen-sink/status",
auth: "gateway",
match: "exact",
handler: async (_req, res) => {
const body = JSON.stringify({ ok: true, pluginId: PLUGIN_ID, scenarioId: "http.status" });
if (res && typeof res === "object") {
res.statusCode = 200;
res.setHeader?.("content-type", "application/json");
res.end?.(body);
}
return { ok: true, body };
},
};
}
export function buildKitchenGatewayMethod() {
return async () => ({
ok: true,
pluginId: PLUGIN_ID,
providerIds: [
SPEECH_PROVIDER_ID,
REALTIME_TRANSCRIPTION_PROVIDER_ID,
REALTIME_VOICE_PROVIDER_ID,
VIDEO_PROVIDER_ID,
MUSIC_PROVIDER_ID,
MEMORY_EMBEDDING_PROVIDER_ID,
COMPACTION_PROVIDER_ID,
],
});
}
export function buildKitchenCliRegistrar() {
return async ({ program } = {}) => {
program?.command?.("kitchen-sink")?.description?.("Run Kitchen Sink fixture commands.");
return { ok: true, command: "kitchen-sink" };
};
}
export function buildKitchenCliMetadata() {
return {
descriptors: [
{
name: "kitchen-sink",
description: "Run Kitchen Sink fixture commands.",
hasSubcommands: true,
},
],
};
}

431
src/runtime/providers.js Normal file
View File

@ -0,0 +1,431 @@
import {
COMPACTION_PROVIDER_ID,
DEFAULT_EMBEDDING_MODEL,
DEFAULT_IMAGE_MODEL,
DEFAULT_MEDIA_MODEL,
DEFAULT_TEXT_MODEL,
IMAGE_PROVIDER_ID,
MEDIA_PROVIDER_ID,
MEMORY_EMBEDDING_PROVIDER_ID,
MUSIC_PROVIDER_ID,
PLUGIN_ID,
REALTIME_TRANSCRIPTION_PROVIDER_ID,
REALTIME_VOICE_PROVIDER_ID,
SPEECH_PROVIDER_ID,
TEXT_PROVIDER_ID,
VIDEO_PROVIDER_ID,
WEB_FETCH_PROVIDER_ID,
WEB_SEARCH_PROVIDER_ID,
} from "../constants.js";
import {
createKitchenCompaction,
createKitchenEmbedding,
createKitchenMemorySearch,
createKitchenMusicResult,
createKitchenSpeechAsset,
createKitchenTextStream,
createKitchenTranscription,
createKitchenVideoResult,
kitchenImageDescription,
kitchenPromptGuidance,
kitchenSearchSchema,
kitchenTextModelDefinition,
kitchenTextProviderConfig,
readQuery,
readUrl,
runKitchenFetch,
runKitchenSearch,
stripDataUrl,
} from "../scenarios.js";
// Provider builders intentionally stay thin: map OpenClaw provider contracts to
// deterministic scenarios/fixtures, and keep the mock behavior outside runtime wiring.
export function buildKitchenImageProvider(runtime) {
return {
id: IMAGE_PROVIDER_ID,
aliases: ["kitchen", "kitchen-sink", "openclaw-kitchen-sink"],
label: "Kitchen Sink Image",
defaultModel: DEFAULT_IMAGE_MODEL,
models: [DEFAULT_IMAGE_MODEL],
capabilities: {
generate: {
maxCount: 1,
supportsSize: true,
supportsAspectRatio: true,
supportsResolution: true,
},
edit: {
enabled: true,
maxInputImages: 1,
maxCount: 1,
},
geometry: {
sizes: ["1024x1024"],
aspectRatios: ["1:1"],
resolutions: ["1K"],
},
},
isConfigured: () => true,
generateImage: async (req) => {
const result = await runtime.runScenario({
scenario: "image.generate",
prompt: req?.prompt,
route: "provider:image",
model: req?.model,
});
if (result.error) {
throw kitchenProviderError(result);
}
return {
images: [stripDataUrl(result.image)],
model: req?.model || DEFAULT_IMAGE_MODEL,
metadata: {
kitchenSink: true,
job: result.job,
asset: result.image.metadata,
provider: IMAGE_PROVIDER_ID,
pluginId: PLUGIN_ID,
scenarioId: result.scenarioId,
route: result.route,
request: {
prompt: req?.prompt,
size: req?.size,
aspectRatio: req?.aspectRatio,
count: req?.count || 1,
},
},
};
},
};
}
export function buildKitchenMediaProvider() {
return {
id: MEDIA_PROVIDER_ID,
capabilities: ["image", "audio", "video"],
defaultModels: { image: DEFAULT_MEDIA_MODEL },
autoPriority: { image: 5 },
describeImage: async (req) => ({
text: kitchenImageDescription(req?.prompt, 1),
model: req?.model || DEFAULT_MEDIA_MODEL,
}),
describeImages: async (req) => ({
text: kitchenImageDescription(req?.prompt, Array.isArray(req?.images) ? req.images.length : 0),
model: req?.model || DEFAULT_MEDIA_MODEL,
}),
transcribeAudio: async (req) => createKitchenTranscription({ audio: req?.audio, prompt: req?.prompt }),
describeVideo: async (req) => ({
text: "Kitchen Sink video fixture: three deterministic frames show the office sink asset, a close-up, and a fixture badge.",
model: req?.model || DEFAULT_MEDIA_MODEL,
metadata: { kitchenSink: true, provider: MEDIA_PROVIDER_ID, scenarioId: "media.video-describe" },
}),
};
}
export function buildKitchenSpeechProvider() {
return {
id: SPEECH_PROVIDER_ID,
label: "Kitchen Sink Speech",
voices: ["kitchen-neutral", "kitchen-robot"],
defaultVoice: "kitchen-neutral",
isConfigured: () => true,
synthesize: async (req) => createKitchenSpeechAsset({
text: req?.text,
voice: req?.voice,
model: req?.model,
}),
speak: async (req) => createKitchenSpeechAsset({
text: req?.text,
voice: req?.voice,
model: req?.model,
}),
};
}
export function buildKitchenRealtimeTranscriptionProvider() {
return {
id: REALTIME_TRANSCRIPTION_PROVIDER_ID,
label: "Kitchen Sink Realtime Transcription",
isConfigured: () => true,
createSession: (req = {}) => {
const chunks = [];
return {
provider: REALTIME_TRANSCRIPTION_PROVIDER_ID,
async connect() {
req.onReady?.({ provider: REALTIME_TRANSCRIPTION_PROVIDER_ID });
return { ok: true, provider: REALTIME_TRANSCRIPTION_PROVIDER_ID };
},
sendAudio(audio) {
chunks.push(audio);
req.onTranscript?.(`Kitchen Sink partial transcript ${chunks.length}.`);
},
async close() {
const result = createKitchenTranscription({ audio: Buffer.concat(chunks.map(toBuffer)) });
req.onTranscript?.(result.text);
req.onClose?.({ code: 1000, reason: "kitchen sink complete" });
return result;
},
};
},
};
}
export function buildKitchenRealtimeVoiceProvider() {
return {
id: REALTIME_VOICE_PROVIDER_ID,
label: "Kitchen Sink Realtime Voice",
isConfigured: () => true,
createBridge: (req = {}) => {
let connected = false;
const audio = [];
return {
supportsToolResultContinuation: true,
async connect() {
connected = true;
req.onEvent?.({ type: "connected", provider: REALTIME_VOICE_PROVIDER_ID });
},
sendAudio(chunk) {
audio.push(chunk);
req.onTranscript?.("Kitchen Sink realtime voice heard audio.");
},
setMediaTimestamp(timestampMs) {
req.onEvent?.({ type: "media_timestamp", timestampMs });
},
submitToolResult(result) {
req.onEvent?.({ type: "tool_result", result });
},
acknowledgeMark(mark) {
req.onEvent?.({ type: "mark", mark });
},
close() {
connected = false;
req.onEvent?.({ type: "closed", audioChunks: audio.length });
},
isConnected: () => connected,
};
},
};
}
export function buildKitchenVideoProvider() {
return {
id: VIDEO_PROVIDER_ID,
label: "Kitchen Sink Video",
defaultModel: "kitchen-sink-video-v1",
capabilities: {
generate: { maxVideos: 1, maxDurationSeconds: 3, supportsResolution: true },
imageToVideo: { enabled: true, maxVideos: 1, maxInputImages: 1, maxDurationSeconds: 3 },
videoToVideo: { enabled: false },
},
isConfigured: () => true,
generateVideo: async (req) => createKitchenVideoResult({ prompt: req?.prompt, model: req?.model }),
};
}
export function buildKitchenMusicProvider() {
return {
id: MUSIC_PROVIDER_ID,
label: "Kitchen Sink Music",
defaultModel: "kitchen-sink-music-v1",
capabilities: {
generate: { maxTracks: 1, maxDurationSeconds: 1 },
edit: { enabled: true, maxInputAudio: 1, maxTracks: 1 },
},
isConfigured: () => true,
generateMusic: async (req) => createKitchenMusicResult({ prompt: req?.prompt, model: req?.model }),
generate: async (req) => createKitchenMusicResult({ prompt: req?.prompt, model: req?.model }),
};
}
export function buildKitchenTextProvider() {
return {
id: TEXT_PROVIDER_ID,
label: "Kitchen Sink LLM",
docsPath: "/providers/models",
aliases: ["kitchen-sink-text", "kitchen"],
envVars: [],
auth: [
{
id: "none",
label: "No credentials",
hint: "Deterministic local fixture provider.",
kind: "custom",
run: async () => ({
profiles: [
{
id: "kitchen-sink-local",
label: "Kitchen Sink Local",
configured: true,
source: "fixture",
},
],
defaultModel: `${TEXT_PROVIDER_ID}/${DEFAULT_TEXT_MODEL}`,
notes: ["Kitchen Sink LLM is deterministic and does not call a network service."],
}),
},
],
staticCatalog: {
order: "simple",
run: async () => ({
provider: kitchenTextProviderConfig(),
}),
},
catalog: {
order: "simple",
run: async () => ({
provider: kitchenTextProviderConfig(),
}),
},
resolveDynamicModel: ({ modelId }) =>
modelId === DEFAULT_TEXT_MODEL ? kitchenTextModelDefinition() : undefined,
resolveSyntheticAuth: () => ({
apiKey: "kitchen-sink-local-fixture",
source: "kitchen-sink fixture",
mode: "token",
}),
createStreamFn: () => createKitchenTextStream,
resolveSystemPromptContribution: () => ({
stablePrefix: kitchenPromptGuidance().join("\n"),
}),
};
}
export function buildKitchenWebSearchProvider() {
return {
id: WEB_SEARCH_PROVIDER_ID,
label: "Kitchen Sink Search",
hint: "Credential-free deterministic search fixture.",
requiresCredential: false,
envVars: [],
placeholder: "no key required",
signupUrl: "https://github.com/openclaw/kitchen-sink",
docsUrl: "https://github.com/openclaw/kitchen-sink#readme",
credentialPath: `${pluginConfigPath()}.search`,
getCredentialValue: () => "fixture",
setCredentialValue: (target, value) => {
target.fixture = value;
},
applySelectionConfig: (config) => config,
resolveRuntimeMetadata: async () => ({ provider: WEB_SEARCH_PROVIDER_ID, pluginId: PLUGIN_ID }),
createTool: () => ({
description: "Search the deterministic Kitchen Sink fixture corpus.",
parameters: kitchenSearchSchema(),
execute: async (args) => runKitchenSearch(readQuery(args)),
}),
};
}
export function buildKitchenWebFetchProvider() {
return {
id: WEB_FETCH_PROVIDER_ID,
label: "Kitchen Sink Fetch",
hint: "Credential-free deterministic fetch fixture.",
requiresCredential: false,
envVars: [],
placeholder: "no key required",
signupUrl: "https://github.com/openclaw/kitchen-sink",
docsUrl: "https://github.com/openclaw/kitchen-sink#readme",
credentialPath: `${pluginConfigPath()}.fetch`,
getCredentialValue: () => "fixture",
setCredentialValue: (target, value) => {
target.fixture = value;
},
applySelectionConfig: (config) => config,
resolveRuntimeMetadata: async () => ({ provider: WEB_FETCH_PROVIDER_ID, pluginId: PLUGIN_ID }),
createTool: () => ({
description: "Fetch deterministic Kitchen Sink fixture documents.",
parameters: {
type: "object",
additionalProperties: false,
properties: {
url: { type: "string", description: "Fixture URL or topic." },
},
},
execute: async (args) => runKitchenFetch(readUrl(args)),
}),
};
}
export function buildKitchenMemoryEmbeddingProvider() {
return {
id: MEMORY_EMBEDDING_PROVIDER_ID,
label: "Kitchen Sink Memory Embeddings",
model: DEFAULT_EMBEDDING_MODEL,
dimensions: 8,
isConfigured: () => true,
embed: async (input) => ({
provider: MEMORY_EMBEDDING_PROVIDER_ID,
model: DEFAULT_EMBEDDING_MODEL,
embedding: createKitchenEmbedding(typeof input === "string" ? input : input?.text),
}),
embedMany: async (input) => {
const texts = Array.isArray(input) ? input : Array.isArray(input?.texts) ? input.texts : [input?.text ?? ""];
return {
provider: MEMORY_EMBEDDING_PROVIDER_ID,
model: DEFAULT_EMBEDDING_MODEL,
embeddings: texts.map((text) => createKitchenEmbedding(text)),
};
},
};
}
export function buildKitchenMemoryCorpusSupplement() {
return {
id: "kitchen-sink-memory-corpus",
label: "Kitchen Sink Memory Corpus",
search: async (query) => createKitchenMemorySearch(typeof query === "string" ? query : query?.query),
read: async (id = "ks-memory-runtime-surfaces") => ({
id,
title: "Kitchen Sink runtime surfaces",
text: "Kitchen Sink memory corpus fixture covering providers, channels, hooks, compaction, and tasks.",
metadata: { kitchenSink: true, pluginId: PLUGIN_ID, scenarioId: "memory.read" },
}),
list: async () => ({
items: [{ id: "ks-memory-runtime-surfaces", title: "Kitchen Sink runtime surfaces" }],
}),
};
}
export function buildKitchenCompactionProvider() {
return {
id: COMPACTION_PROVIDER_ID,
label: "Kitchen Sink Compaction",
compact: async (input) => createKitchenCompaction(input),
summarize: async (input) => createKitchenCompaction(input),
};
}
function pluginConfigPath() {
return `plugins.${PLUGIN_ID}`;
}
function kitchenProviderError(result) {
const error = new Error(result.error.message);
error.name = "KitchenSinkProviderError";
error.code = result.error.code;
error.statusCode = result.error.statusCode;
error.retryable = result.error.retryable;
error.retryAfterMs = result.error.retryAfterMs;
error.metadata = {
kitchenSink: true,
job: result.job,
pluginId: PLUGIN_ID,
provider: IMAGE_PROVIDER_ID,
scenarioId: result.scenarioId,
route: result.route,
};
return error;
}
function toBuffer(value) {
if (Buffer.isBuffer(value)) {
return value;
}
if (value instanceof Uint8Array) {
return Buffer.from(value);
}
if (typeof value === "string") {
return Buffer.from(value);
}
return Buffer.alloc(0);
}

134
src/runtime/tasks.js Normal file
View File

@ -0,0 +1,134 @@
import { PLUGIN_ID } from "../constants.js";
export function buildKitchenDetachedTaskRuntime() {
const tasks = new Map();
function create(params, status) {
const now = Date.now();
const runId = params.runId || `ks_task_${Math.abs(hashTask(params.task || status))}`;
const task = {
taskId: runId,
runId,
runtime: params.runtime || "cli",
taskKind: params.taskKind || "kitchen-sink",
sourceId: params.sourceId || PLUGIN_ID,
requesterSessionKey: params.requesterSessionKey || "kitchen-sink",
ownerKey: params.ownerKey || PLUGIN_ID,
scopeKind: params.scopeKind || "session",
childSessionKey: params.childSessionKey,
parentFlowId: params.parentFlowId,
parentTaskId: params.parentTaskId,
agentId: params.agentId,
label: params.label || "Kitchen Sink task",
task: params.task,
status,
deliveryStatus: params.deliveryStatus || "not_applicable",
notifyPolicy: params.notifyPolicy || "done_only",
createdAt: now,
startedAt: status === "running" ? params.startedAt || now : params.startedAt,
lastEventAt: params.lastEventAt || now,
progressSummary: params.progressSummary || undefined,
};
tasks.set(runId, task);
return task;
}
function update(runId, patch) {
const current = tasks.get(runId);
if (!current) {
return [];
}
const cleanPatch = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined));
const next = { ...current, ...cleanPatch };
tasks.set(runId, next);
return [next];
}
return {
createQueuedTaskRun: (params) => create(params, "queued"),
createRunningTaskRun: (params) => create(params, "running"),
startTaskRunByRunId: (params) =>
update(params.runId, {
status: "running",
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
startedAt: params.startedAt || Date.now(),
lastEventAt: params.lastEventAt || Date.now(),
progressSummary: params.progressSummary || params.eventSummary || "Kitchen Sink task started.",
}),
recordTaskRunProgressByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
lastEventAt: params.lastEventAt || Date.now(),
progressSummary: params.progressSummary || params.eventSummary || "Kitchen Sink task progressed.",
}),
finalizeTaskRunByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
status: params.status,
endedAt: params.endedAt,
lastEventAt: params.lastEventAt || params.endedAt,
error: params.error,
progressSummary: params.progressSummary || undefined,
terminalSummary: params.terminalSummary || undefined,
terminalOutcome: params.terminalOutcome || undefined,
}),
completeTaskRunByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
status: "succeeded",
endedAt: params.endedAt,
lastEventAt: params.lastEventAt || params.endedAt,
progressSummary: params.progressSummary || undefined,
terminalSummary: params.terminalSummary || "Kitchen Sink task completed.",
terminalOutcome: params.terminalOutcome || "succeeded",
}),
failTaskRunByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
status: params.status || "failed",
endedAt: params.endedAt,
lastEventAt: params.lastEventAt || params.endedAt,
error: params.error,
progressSummary: params.progressSummary || undefined,
terminalSummary: params.terminalSummary || "Kitchen Sink task failed.",
}),
setDetachedTaskDeliveryStatusByRunId: (params) =>
update(params.runId, {
runtime: params.runtime,
requesterSessionKey: params.sessionKey,
deliveryStatus: params.deliveryStatus,
error: params.error,
}),
cancelDetachedTaskRunById: async ({ taskId }) => {
const current = tasks.get(taskId);
if (!current) {
return { found: false, cancelled: false, reason: "not owned by Kitchen Sink" };
}
const task = {
...current,
status: "cancelled",
endedAt: Date.now(),
lastEventAt: Date.now(),
terminalSummary: "Kitchen Sink task cancelled.",
};
tasks.set(taskId, task);
return { found: true, cancelled: true, task };
},
tryRecoverTaskBeforeMarkLost: ({ task }) => ({
recovered: Boolean(task?.taskId && tasks.has(task.taskId)),
}),
};
}
function hashTask(input) {
let hash = 0;
for (const char of String(input)) {
hash = Math.imul(31, hash) + char.charCodeAt(0);
}
return hash;
}

View File

@ -1,49 +1,72 @@
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import { readFileSync } from "node:fs"; import {
CHANNEL_ACCOUNT_ID,
CHANNEL_ID,
COMPACTION_PROVIDER_ID,
DEFAULT_IMAGE_DELAY_MS,
DEFAULT_IMAGE_MODEL,
DEFAULT_MEDIA_MODEL,
DEFAULT_MUSIC_MODEL,
DEFAULT_SPEECH_MODEL,
DEFAULT_VIDEO_MODEL,
IMAGE_PROVIDER_ID,
MEDIA_PROVIDER_ID,
MEMORY_EMBEDDING_PROVIDER_ID,
MUSIC_PROVIDER_ID,
PLUGIN_ID,
REALTIME_TRANSCRIPTION_PROVIDER_ID,
SPEECH_PROVIDER_ID,
TEXT_PROVIDER_ID,
VIDEO_PROVIDER_ID,
WEB_FETCH_PROVIDER_ID,
WEB_SEARCH_PROVIDER_ID,
} from "./constants.js";
import { createKitchenSinkImageAsset } from "./fixtures/images.js";
import {
createKitchenTextStream,
estimateUsage,
kitchenImageDescription,
kitchenTextModelDefinition,
kitchenTextProviderConfig,
kitchenTextResponse,
} from "./fixtures/text.js";
export const PLUGIN_ID = "openclaw-kitchen-sink-fixture"; export {
export const IMAGE_PROVIDER_ID = "kitchen-sink-image"; CHANNEL_ACCOUNT_ID,
export const MEDIA_PROVIDER_ID = "kitchen-sink-media"; CHANNEL_ID,
export const TEXT_PROVIDER_ID = "kitchen-sink-llm"; COMPACTION_PROVIDER_ID,
export const WEB_SEARCH_PROVIDER_ID = "kitchen-sink-search"; DEFAULT_EMBEDDING_MODEL,
export const WEB_FETCH_PROVIDER_ID = "kitchen-sink-fetch"; DEFAULT_IMAGE_DELAY_MS,
export const SPEECH_PROVIDER_ID = "kitchen-sink-speech"; DEFAULT_IMAGE_MODEL,
export const REALTIME_TRANSCRIPTION_PROVIDER_ID = "kitchen-sink-realtime-transcription"; DEFAULT_MEDIA_MODEL,
export const REALTIME_VOICE_PROVIDER_ID = "kitchen-sink-realtime-voice"; DEFAULT_MUSIC_MODEL,
export const VIDEO_PROVIDER_ID = "kitchen-sink-video"; DEFAULT_SPEECH_MODEL,
export const MUSIC_PROVIDER_ID = "kitchen-sink-music"; DEFAULT_TEXT_MODEL,
export const MEMORY_EMBEDDING_PROVIDER_ID = "kitchen-sink-memory-embedding"; DEFAULT_VIDEO_MODEL,
export const COMPACTION_PROVIDER_ID = "kitchen-sink-compaction"; IMAGE_PROVIDER_ID,
export const CHANNEL_ID = "kitchen-sink-channel"; MEDIA_PROVIDER_ID,
export const CHANNEL_ACCOUNT_ID = "local"; MEMORY_EMBEDDING_PROVIDER_ID,
export const DEFAULT_IMAGE_MODEL = "kitchen-sink-image-v1"; MUSIC_PROVIDER_ID,
export const DEFAULT_MEDIA_MODEL = "kitchen-sink-vision-v1"; PLUGIN_ID,
export const DEFAULT_TEXT_MODEL = "kitchen-sink-text-v1"; REALTIME_TRANSCRIPTION_PROVIDER_ID,
export const DEFAULT_SPEECH_MODEL = "kitchen-sink-tts-v1"; REALTIME_VOICE_PROVIDER_ID,
export const DEFAULT_VIDEO_MODEL = "kitchen-sink-video-v1"; SPEECH_PROVIDER_ID,
export const DEFAULT_MUSIC_MODEL = "kitchen-sink-music-v1"; TEXT_PROVIDER_ID,
export const DEFAULT_EMBEDDING_MODEL = "kitchen-sink-embed-v1"; VIDEO_PROVIDER_ID,
export const DEFAULT_IMAGE_DELAY_MS = 10_000; WEB_FETCH_PROVIDER_ID,
const KITCHEN_SINK_OFFICE_IMAGE_FILE = "kitchen_sink_office.png"; WEB_SEARCH_PROVIDER_ID,
const KITCHEN_SINK_OFFICE_IMAGE = readFileSync( } from "./constants.js";
new URL(`./assets/${KITCHEN_SINK_OFFICE_IMAGE_FILE}`, import.meta.url), export { createKitchenSinkImageAsset } from "./fixtures/images.js";
); export {
const KITCHEN_SINK_OFFICE_SHA256 = sha256Hex(KITCHEN_SINK_OFFICE_IMAGE); createKitchenTextStream,
const KITCHEN_IMAGE_FIXTURES = [ kitchenImageDescription,
{ kitchenTextModelDefinition,
id: "office-lobby-sink", kitchenTextProviderConfig,
label: "Kitchen Sink Office", kitchenTextResponse,
assetName: KITCHEN_SINK_OFFICE_IMAGE_FILE, } from "./fixtures/text.js";
buffer: KITCHEN_SINK_OFFICE_IMAGE,
sha256: KITCHEN_SINK_OFFICE_SHA256,
mimeType: "image/png",
width: 1024,
height: 1024,
description: "office lobby scene with a lobster-costumed figure holding a real sink",
source: "bundled-real-image",
},
];
// Human scenarios are the end-to-end smoke matrix: dry prefix routing, live LLM
// plus Kitchen Sink provider routing, hooks, channels, search/fetch, and memory.
export const KITCHEN_HUMAN_SCENARIOS = Object.freeze([ export const KITCHEN_HUMAN_SCENARIOS = Object.freeze([
{ {
id: "dry.prefix-image", id: "dry.prefix-image",
@ -90,6 +113,8 @@ export const KITCHEN_HUMAN_SCENARIOS = Object.freeze([
]); ]);
export function createKitchenScenarioRuntime(options = {}) { export function createKitchenScenarioRuntime(options = {}) {
// Clock and sleep are injectable so tests can prove the 10s job lifecycle
// without actually waiting, while real runtime execution still behaves async.
const runtime = { const runtime = {
delayMs: normalizeDelayMs(options.delayMs), delayMs: normalizeDelayMs(options.delayMs),
sleep: typeof options.sleep === "function" ? options.sleep : defaultSleep, sleep: typeof options.sleep === "function" ? options.sleep : defaultSleep,
@ -211,6 +236,8 @@ export async function runKitchenHumanScenario(runtime, idOrPrompt) {
} }
export async function runKitchenScenario(runtime, request = {}) { export async function runKitchenScenario(runtime, request = {}) {
// Central dispatcher for deterministic provider behavior. Runtime builders
// adapt OpenClaw APIs into this small scenario vocabulary.
const scenario = normalizeScenario(request.scenario); const scenario = normalizeScenario(request.scenario);
if (scenario === "image.generate") { if (scenario === "image.generate") {
const prompt = normalizePrompt(request.prompt, "a kitchen sink fixture image"); const prompt = normalizePrompt(request.prompt, "a kitchen sink fixture image");
@ -302,37 +329,6 @@ export async function runKitchenScenario(runtime, request = {}) {
}; };
} }
export function createKitchenSinkImageAsset({ prompt, jobId, scenario = "image.generate", model = DEFAULT_IMAGE_MODEL }) {
const fixture = selectKitchenImageFixture(prompt);
const buffer = Buffer.from(fixture.buffer);
const seed = stableHash(`${jobId}:${prompt}:${fixture.id}`);
return {
buffer,
mimeType: fixture.mimeType,
fileName: `${jobId}.png`,
dataUrl: `data:${fixture.mimeType};base64,${buffer.toString("base64")}`,
revisedPrompt: `Kitchen Sink office image fixture: ${prompt}`,
metadata: {
kitchenSink: true,
assetId: fixture.id,
assetName: fixture.assetName,
source: fixture.source,
model,
width: fixture.width,
height: fixture.height,
sizeBytes: buffer.byteLength,
sha256: fixture.sha256,
contentHash: fixture.sha256.slice(0, 16),
seed,
finishReason: "success",
pluginId: PLUGIN_ID,
scenarioId: scenario,
jobId,
prompt,
},
};
}
export function shouldHandleKitchenText(text) { export function shouldHandleKitchenText(text) {
return /^kitchen(?:\s|$)/i.test(String(text ?? "").trim()); return /^kitchen(?:\s|$)/i.test(String(text ?? "").trim());
} }
@ -734,92 +730,6 @@ export function kitchenImageReply(result) {
}; };
} }
export function kitchenTextProviderConfig() {
return {
baseUrl: "kitchen-sink://local",
apiKey: "kitchen-sink-local-fixture",
auth: "token",
api: "kitchen-sink",
models: [kitchenTextModelDefinition()],
};
}
export function kitchenTextModelDefinition() {
return {
id: DEFAULT_TEXT_MODEL,
name: "Kitchen Sink Text Fixture",
api: "kitchen-sink",
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 8192,
maxTokens: 2048,
description: "Deterministic OpenClaw plugin text-provider fixture.",
};
}
export function createKitchenTextStream(model, context) {
const stream = createAssistantMessageEventStream();
queueMicrotask(() => {
const prompt = extractLastUserPrompt(context);
const text = kitchenTextResponse(prompt);
const message = {
role: "assistant",
content: [{ type: "text", text }],
api: model?.api || "kitchen-sink",
provider: TEXT_PROVIDER_ID,
model: model?.id || DEFAULT_TEXT_MODEL,
usage: estimateUsage(prompt, text),
stopReason: "stop",
timestamp: Date.now(),
};
stream.push({ type: "start", partial: { ...message, content: [] } });
stream.push({ type: "text_start", contentIndex: 0, partial: { ...message, content: [] } });
stream.push({ type: "text_delta", contentIndex: 0, delta: text, partial: message });
stream.push({ type: "text_end", contentIndex: 0, content: text, partial: message });
stream.push({ type: "done", reason: "stop", message });
stream.end(message);
});
return stream;
}
export function kitchenTextResponse(prompt) {
const normalized = normalizePrompt(prompt, "kitchen sink text inference");
if (/\b(image|picture|draw|generate)\b/i.test(normalized)) {
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
`I would route this to ${IMAGE_PROVIDER_ID}/${DEFAULT_IMAGE_MODEL}, create a queued image job, wait for completion, then return the bundled kitchen_sink_office.png asset with PNG metadata.`,
].join(" ");
}
if (/\b(search|find|lookup|web)\b/i.test(normalized)) {
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
`I would call ${WEB_SEARCH_PROVIDER_ID} for ranked fixture results and ${WEB_FETCH_PROVIDER_ID} for deterministic document fetches.`,
].join(" ");
}
if (/\b(rate limit|timeout|fail|error)\b/i.test(normalized)) {
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
"Failure fixtures are available: rate limit returns 429 with retry metadata, timeout returns 504, and fail returns a deterministic provider error.",
].join(" ");
}
return [
"Kitchen Sink text fixture:",
`prompt="${normalized}"`,
"Available realistic surfaces: direct prefix, registered tools, image provider lifecycle, media understanding, web search, web fetch, channel health, hooks, detached tasks, and text provider catalog.",
].join(" ");
}
export function kitchenImageDescription(prompt, count) {
return [
`Kitchen Sink media fixture described ${count || 1} image${count === 1 ? "" : "s"}.`,
`Prompt: ${normalizePrompt(prompt, "describe kitchen sink image")}.`,
"Visible content: the bundled kitchen_sink_office PNG: an office lobby scene with a lobster-costumed figure holding a real sink.",
].join(" ");
}
export function kitchenToolSchema(promptDescription) { export function kitchenToolSchema(promptDescription) {
return { return {
type: "object", type: "object",
@ -886,6 +796,8 @@ export function extractInteractiveText(ctx) {
} }
export function observeKitchenHook(name, event, context) { export function observeKitchenHook(name, event, context) {
// Hooks receive different shapes across tool, provider, and agent surfaces.
// Normalize them into scenario ids so reports stay comparable.
const toolId = firstHookString(event, ["toolId", "toolName", "name", "id"]) || const toolId = firstHookString(event, ["toolId", "toolName", "name", "id"]) ||
firstHookString(event?.tool, ["id", "name"]); firstHookString(event?.tool, ["id", "name"]);
const providerId = firstHookString(event, ["providerId", "provider", "selectedProvider"]) || const providerId = firstHookString(event, ["providerId", "provider", "selectedProvider"]) ||
@ -958,111 +870,6 @@ function transitionKitchenJob(job, status, date, patch = {}) {
}; };
} }
function createAssistantMessageEventStream() {
const queue = [];
const waiters = [];
let done = false;
let finalResult;
let resolveResult;
const resultPromise = new Promise((resolve) => {
resolveResult = resolve;
});
return {
push(event) {
if (done) {
return;
}
if (event.type === "done" || event.type === "error") {
finalResult = event.type === "done" ? event.message : event.error;
done = true;
resolveResult(finalResult);
}
const waiter = waiters.shift();
if (waiter) {
waiter({ value: event, done: false });
} else {
queue.push(event);
}
},
end(result) {
if (result !== undefined && finalResult === undefined) {
finalResult = result;
resolveResult(result);
}
done = true;
while (waiters.length > 0) {
waiters.shift()({ value: undefined, done: true });
}
},
async *[Symbol.asyncIterator]() {
while (true) {
if (queue.length > 0) {
yield queue.shift();
} else if (done) {
return;
} else {
const next = await new Promise((resolve) => waiters.push(resolve));
if (next.done) {
return;
}
yield next.value;
}
}
},
result() {
return resultPromise;
},
};
}
function extractLastUserPrompt(context) {
const messages = Array.isArray(context?.messages) ? context.messages : [];
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (message?.role !== "user") {
continue;
}
if (typeof message.content === "string") {
return message.content;
}
if (Array.isArray(message.content)) {
const text = message.content
.filter((item) => item?.type === "text" && typeof item.text === "string")
.map((item) => item.text)
.join(" ")
.trim();
if (text) {
return text;
}
}
}
return "kitchen sink text inference";
}
function estimateUsage(prompt = "", text = "") {
const input = estimateTokens(prompt);
const output = estimateTokens(text);
return {
input,
output,
cacheRead: 0,
cacheWrite: 0,
totalTokens: input + output,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
};
}
function estimateTokens(text) {
return Math.max(1, Math.ceil(String(text).trim().split(/\s+/).filter(Boolean).length * 1.35));
}
function classifyKitchenFailure(prompt) { function classifyKitchenFailure(prompt) {
const text = String(prompt ?? "").toLowerCase(); const text = String(prompt ?? "").toLowerCase();
if (/\brate[ -]?limit|429|too many requests\b/.test(text)) { if (/\brate[ -]?limit|429|too many requests\b/.test(text)) {
@ -1094,10 +901,6 @@ function classifyKitchenFailure(prompt) {
return undefined; return undefined;
} }
function selectKitchenImageFixture(_prompt) {
return KITCHEN_IMAGE_FIXTURES[0];
}
function mediaJob(kind, id, prompt, scenarioId) { function mediaJob(kind, id, prompt, scenarioId) {
const createdAt = "2026-04-28T00:00:00.000Z"; const createdAt = "2026-04-28T00:00:00.000Z";
return { return {