Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
088bb4bee4 | ||
|
|
95ec036d85 | ||
|
|
c84b7aa983 | ||
|
|
849fe726ea | ||
|
|
f57a0fc7c4 | ||
|
|
fbae1bed22 | ||
|
|
7657ba8249 | ||
|
|
67ca4a3370 | ||
|
|
c023ff6f85 | ||
|
|
6995e72caa | ||
|
|
bc26de1a38 | ||
|
|
7f92e87bfc | ||
|
|
02370e2338 | ||
|
|
2861f04237 | ||
|
|
768be7da53 | ||
|
|
89449910b4 | ||
|
|
87d135b61f | ||
|
|
02421e56a6 | ||
|
|
4bd96b6f2d | ||
|
|
c5ffa2a3c0 | ||
|
|
d1daa6e3de | ||
|
|
6a2cf98404 | ||
|
|
ba65248dc2 | ||
|
|
da085688fd | ||
|
|
dcf61bc18c | ||
|
|
f254b77d71 |
35
README.md
35
README.md
@ -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
|
||||||
|
|||||||
@ -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
2513
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -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"
|
||||||
|
|||||||
@ -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": [
|
||||||
|
|||||||
@ -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 |
|
||||||
|
|||||||
@ -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);
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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");
|
||||||
|
|||||||
@ -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++));
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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/");
|
||||||
}
|
}
|
||||||
|
|||||||
59
scripts/fixtures/installed-consumer-probe.mjs
Normal file
59
scripts/fixtures/installed-consumer-probe.mjs
Normal 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);
|
||||||
90
scripts/lib/plugin-registration-harness.mjs
Normal file
90
scripts/lib/plugin-registration-harness.mjs
Normal 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
134
scripts/pack-zip.mjs
Normal 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;
|
||||||
|
}
|
||||||
@ -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
23
src/constants.js
Normal 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
81
src/fixtures/images.js
Normal 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
209
src/fixtures/text.js
Normal 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;
|
||||||
|
}
|
||||||
@ -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"));
|
||||||
|
|||||||
@ -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")));
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
88
src/runtime/channel.js
Normal 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
93
src/runtime/commands.js
Normal 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
88
src/runtime/platform.js
Normal 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
431
src/runtime/providers.js
Normal 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
134
src/runtime/tasks.js
Normal 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;
|
||||||
|
}
|
||||||
341
src/scenarios.js
341
src/scenarios.js
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user