Compare commits
No commits in common. "main" and "v0.2.1" have entirely different histories.
59
README.md
59
README.md
@ -17,37 +17,6 @@ detached-task, and text-provider catalog surfaces.
|
||||
It should not call external services, read secrets, spawn processes, or require
|
||||
live credentials.
|
||||
|
||||
The plugin exposes three test personalities through
|
||||
`plugins.entries.openclaw-kitchen-sink-fixture.config.personality`:
|
||||
|
||||
- `full` is the default compatibility mode and keeps both generated probe
|
||||
registrations and the hand-owned runtime.
|
||||
- `conformance` loads only the valid runtime surfaces and skips intentionally
|
||||
invalid probes so OpenClaw can assert a clean external-plugin install.
|
||||
- `adversarial` loads only generated invalid probes so OpenClaw can assert
|
||||
expected diagnostics without mixing them with a live runtime smoke.
|
||||
|
||||
## 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
|
||||
|
||||
The fixture can be used dry, without an LLM:
|
||||
@ -63,22 +32,8 @@ kitchen explain the fixture
|
||||
|
||||
It also exposes provider and tool surfaces for live model routing:
|
||||
|
||||
- `listKitchenHumanScenarios()` and `runKitchenHumanScenario(runtime, id)`
|
||||
provide deterministic end-to-end user scenarios for fixture consumers:
|
||||
`dry.prefix-image`, `live.openai-text-kitchen-image`,
|
||||
`search.fetch.summarize`, `channel.prefix-image`, `hook.block-tool`, and
|
||||
`memory.compact-fixture`.
|
||||
- When a live text provider such as OpenAI is active and Kitchen Sink is
|
||||
selected as the image provider, the `live.openai-text-kitchen-image` scenario
|
||||
proves the human prompt can route to the Kitchen Sink image provider and
|
||||
return the bundled `kitchen_sink_office.png` asset without external image
|
||||
credentials.
|
||||
- The `hook.block-tool` scenario proves terminal `before_tool_call` blocking,
|
||||
and the contract probe script also checks the approval path and conversation
|
||||
privacy observations for `llm_input`, `llm_output`, and `agent_end`.
|
||||
|
||||
- `src/scenarios.js` routes deterministic user scenarios; reusable mock payloads
|
||||
live in `src/fixtures/`.
|
||||
- `src/scenarios.js` is the shared deterministic fixture engine used by dry
|
||||
commands, tools, providers, hooks, channel delivery, and tests.
|
||||
- `kitchen_sink_image_job` returns a deterministic image job, waits 10 seconds
|
||||
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
|
||||
@ -131,11 +86,8 @@ contract coverage.
|
||||
```sh
|
||||
npm install
|
||||
npm run sync:surface
|
||||
npm run check:runtime
|
||||
npm run check:inspector
|
||||
npm run check:install
|
||||
npm test
|
||||
npm run pack:check
|
||||
npm run pack:zip
|
||||
```
|
||||
|
||||
The `Update OpenClaw SDK Surface` workflow automatically checks
|
||||
@ -155,11 +107,6 @@ 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
|
||||
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
|
||||
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
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "openclaw-kitchen-sink-fixture",
|
||||
"name": "OpenClaw Kitchen Sink",
|
||||
"version": "0.2.5",
|
||||
"description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.5.7.",
|
||||
"version": "0.2.1",
|
||||
"description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.4.26.",
|
||||
"enabledByDefault": false,
|
||||
"kind": [
|
||||
"tool",
|
||||
@ -57,42 +57,6 @@
|
||||
"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": {
|
||||
"providers": [
|
||||
{
|
||||
@ -233,15 +197,6 @@
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"personality": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"full",
|
||||
"conformance",
|
||||
"adversarial"
|
||||
],
|
||||
"default": "full"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2497
package-lock.json
generated
2497
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/kitchen-sink",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.1",
|
||||
"private": false,
|
||||
"description": "Credential-free kitchen-sink OpenClaw plugin fixture covering the public plugin API surface.",
|
||||
"type": "module",
|
||||
@ -30,8 +30,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
"./personality": "./src/personality.js",
|
||||
"./runtime": "./src/kitchen-runtime.js",
|
||||
"./runtime": "./src/index.js",
|
||||
"./scenarios": "./src/scenarios.js",
|
||||
"./setup": "./src/setup.js"
|
||||
},
|
||||
@ -46,29 +45,14 @@
|
||||
"compat": {
|
||||
"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": {
|
||||
"openclawVersion": "2026.5.7",
|
||||
"pluginSdkVersion": "2026.5.7"
|
||||
"openclawVersion": "2026.4.26",
|
||||
"pluginSdkVersion": "2026.4.26"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"acceptance:install": "node scripts/check-installed-package.mjs",
|
||||
"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",
|
||||
"check": "npm run sync:surface -- --check && node scripts/check-sdk-surface.mjs && node scripts/check-kitchen-runtime.mjs && npm run plugin:inspect",
|
||||
"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: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",
|
||||
@ -76,14 +60,10 @@
|
||||
"test": "npm run check"
|
||||
},
|
||||
"dependencies": {
|
||||
"openclaw": "2026.5.7"
|
||||
"openclaw": "2026.4.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-inspector": "0.3.10"
|
||||
},
|
||||
"overrides": {
|
||||
"@anthropic-ai/sdk": "0.91.1",
|
||||
"uuid": "14.0.0"
|
||||
"@openclaw/plugin-inspector": "0.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
|
||||
@ -1,549 +0,0 @@
|
||||
{
|
||||
"beforeToolCall": {
|
||||
"allow": {
|
||||
"kitchenSink": true,
|
||||
"pluginId": "openclaw-kitchen-sink-fixture",
|
||||
"hook": "before_tool_call",
|
||||
"route": "hook:before_tool_call",
|
||||
"matchedKitchen": true,
|
||||
"scenarioId": "image.generate",
|
||||
"observedEventKeys": [
|
||||
"toolId",
|
||||
"args"
|
||||
],
|
||||
"observedContextKeys": [
|
||||
"providerId"
|
||||
],
|
||||
"params": {
|
||||
"args": {
|
||||
"prompt": "generate a kitchen image",
|
||||
"kitchenSinkScenario": "image.generate",
|
||||
"kitchenSinkPluginId": "openclaw-kitchen-sink-fixture"
|
||||
}
|
||||
},
|
||||
"decision": "allow"
|
||||
},
|
||||
"block": {
|
||||
"kitchenSink": true,
|
||||
"pluginId": "openclaw-kitchen-sink-fixture",
|
||||
"hook": "before_tool_call",
|
||||
"route": "hook:before_tool_call",
|
||||
"matchedKitchen": true,
|
||||
"scenarioId": "image.generate",
|
||||
"observedEventKeys": [
|
||||
"toolId",
|
||||
"args"
|
||||
],
|
||||
"observedContextKeys": [
|
||||
"providerId"
|
||||
],
|
||||
"params": {
|
||||
"args": {
|
||||
"prompt": "kitchen block image generation",
|
||||
"kitchenSinkScenario": "image.generate",
|
||||
"kitchenSinkPluginId": "openclaw-kitchen-sink-fixture"
|
||||
}
|
||||
},
|
||||
"block": true,
|
||||
"blockReason": "Kitchen Sink fixture blocked kitchen_sink_image_job for image.generate.",
|
||||
"terminal": true,
|
||||
"decision": "block"
|
||||
},
|
||||
"approval": {
|
||||
"kitchenSink": true,
|
||||
"pluginId": "openclaw-kitchen-sink-fixture",
|
||||
"hook": "before_tool_call",
|
||||
"route": "hook:before_tool_call",
|
||||
"matchedKitchen": true,
|
||||
"scenarioId": "image.generate",
|
||||
"observedEventKeys": [
|
||||
"toolId",
|
||||
"args"
|
||||
],
|
||||
"observedContextKeys": [
|
||||
"providerId"
|
||||
],
|
||||
"params": {
|
||||
"args": {
|
||||
"prompt": "kitchen image generation needs approval",
|
||||
"kitchenSinkScenario": "image.generate",
|
||||
"kitchenSinkPluginId": "openclaw-kitchen-sink-fixture"
|
||||
}
|
||||
},
|
||||
"requireApproval": {
|
||||
"id": "ks_approval_9863b78c",
|
||||
"title": "Kitchen Sink tool approval",
|
||||
"reason": "Kitchen Sink fixture requires approval before kitchen_sink_image_job runs.",
|
||||
"summary": "Approve deterministic image.generate fixture execution.",
|
||||
"scenarioId": "image.generate",
|
||||
"pluginId": "openclaw-kitchen-sink-fixture"
|
||||
},
|
||||
"decision": "approval"
|
||||
}
|
||||
},
|
||||
"conversationPrivacy": {
|
||||
"input": {
|
||||
"kitchenSink": true,
|
||||
"pluginId": "openclaw-kitchen-sink-fixture",
|
||||
"hook": "llm_input",
|
||||
"route": "hook:llm_input",
|
||||
"matchedKitchen": true,
|
||||
"scenarioId": "text.reply",
|
||||
"observedEventKeys": [
|
||||
"prompt",
|
||||
"apiKey",
|
||||
"token"
|
||||
],
|
||||
"observedContextKeys": [
|
||||
"providerId",
|
||||
"authorization"
|
||||
],
|
||||
"privacy": {
|
||||
"boundary": "conversation-observer",
|
||||
"promptHash": "21ed2705",
|
||||
"promptLength": 55,
|
||||
"redactedFields": [
|
||||
"event.apiKey",
|
||||
"event.token",
|
||||
"context.authorization"
|
||||
],
|
||||
"secretPatternCount": 3,
|
||||
"storesRawPayload": false,
|
||||
"exposesRawPayload": false
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"kitchenSink": true,
|
||||
"pluginId": "openclaw-kitchen-sink-fixture",
|
||||
"hook": "llm_output",
|
||||
"route": "hook:llm_output",
|
||||
"matchedKitchen": true,
|
||||
"scenarioId": "text.reply",
|
||||
"observedEventKeys": [
|
||||
"prompt",
|
||||
"apiKey",
|
||||
"token"
|
||||
],
|
||||
"observedContextKeys": [
|
||||
"providerId",
|
||||
"authorization"
|
||||
],
|
||||
"privacy": {
|
||||
"boundary": "conversation-observer",
|
||||
"promptHash": "a3e6f809",
|
||||
"promptLength": 41,
|
||||
"redactedFields": [
|
||||
"event.apiKey",
|
||||
"event.token",
|
||||
"context.authorization"
|
||||
],
|
||||
"secretPatternCount": 3,
|
||||
"storesRawPayload": false,
|
||||
"exposesRawPayload": false
|
||||
}
|
||||
},
|
||||
"end": {
|
||||
"kitchenSink": true,
|
||||
"pluginId": "openclaw-kitchen-sink-fixture",
|
||||
"hook": "agent_end",
|
||||
"route": "hook:agent_end",
|
||||
"matchedKitchen": true,
|
||||
"scenarioId": "text.reply",
|
||||
"observedEventKeys": [
|
||||
"prompt",
|
||||
"apiKey",
|
||||
"token"
|
||||
],
|
||||
"observedContextKeys": [
|
||||
"providerId",
|
||||
"authorization"
|
||||
],
|
||||
"privacy": {
|
||||
"boundary": "conversation-observer",
|
||||
"promptHash": "8bf533dd",
|
||||
"promptLength": 41,
|
||||
"redactedFields": [
|
||||
"event.apiKey",
|
||||
"event.token",
|
||||
"context.authorization"
|
||||
],
|
||||
"secretPatternCount": 3,
|
||||
"storesRawPayload": false,
|
||||
"exposesRawPayload": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"channel": {
|
||||
"account": {
|
||||
"accountId": "local",
|
||||
"name": "Kitchen Sink Local",
|
||||
"enabled": true,
|
||||
"configured": true,
|
||||
"statusState": "ready",
|
||||
"linked": true,
|
||||
"running": true,
|
||||
"connected": true,
|
||||
"mode": "local",
|
||||
"health": {
|
||||
"ok": true,
|
||||
"checkedAt": "2026-04-28T00:00:00.000Z",
|
||||
"message": "Kitchen Sink local fixture account is ready."
|
||||
},
|
||||
"capabilities": [
|
||||
"text",
|
||||
"media",
|
||||
"threads",
|
||||
"dry-run"
|
||||
]
|
||||
},
|
||||
"delivery": {
|
||||
"channel": "kitchen-sink-channel",
|
||||
"messageId": "ks_channel_d813aa04",
|
||||
"conversationId": "kitchen-demo",
|
||||
"channelId": "kitchen-demo",
|
||||
"timestamp": 1777334400000,
|
||||
"deliveryStatus": "sent",
|
||||
"transport": "kitchen-sink-local",
|
||||
"meta": {
|
||||
"kitchenSink": true,
|
||||
"pluginId": "openclaw-kitchen-sink-fixture",
|
||||
"scenarioId": "image.generate",
|
||||
"kind": "text"
|
||||
}
|
||||
},
|
||||
"route": {
|
||||
"sessionKey": "kitchen:fixture-agent:kitchen-demo",
|
||||
"baseSessionKey": "kitchen:fixture-agent:kitchen-demo",
|
||||
"peer": {
|
||||
"kind": "direct",
|
||||
"id": "kitchen-demo"
|
||||
},
|
||||
"chatType": "direct",
|
||||
"from": "local",
|
||||
"to": "kitchen-demo",
|
||||
"threadId": "thread-1"
|
||||
}
|
||||
},
|
||||
"runtimeRegistrations": {
|
||||
"registerAgentEventSubscription": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-agent-event-subscription"
|
||||
]
|
||||
},
|
||||
"registerAgentHarness": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-agent-harness"
|
||||
]
|
||||
},
|
||||
"registerAgentToolResultMiddleware": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-agent-tool-result-middleware",
|
||||
"kitchen-sink-agent-tool-result-middleware"
|
||||
]
|
||||
},
|
||||
"registerAutoEnableProbe": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-auto-enable-probe"
|
||||
]
|
||||
},
|
||||
"registerChannel": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-channel",
|
||||
"kitchen-sink-channel-probe"
|
||||
]
|
||||
},
|
||||
"registerCli": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink",
|
||||
"kitchen-sink-cli"
|
||||
]
|
||||
},
|
||||
"registerCliBackend": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-cli-backend"
|
||||
]
|
||||
},
|
||||
"registerCodexAppServerExtensionFactory": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-codex-app-server-extension-factory"
|
||||
]
|
||||
},
|
||||
"registerCommand": {
|
||||
"count": 3,
|
||||
"ids": [
|
||||
"kitchen",
|
||||
"kitchen-sink",
|
||||
"kitchen-sink-command"
|
||||
]
|
||||
},
|
||||
"registerCompactionProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-compaction",
|
||||
"kitchen-sink-compaction-provider"
|
||||
]
|
||||
},
|
||||
"registerConfigMigration": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-config-migration"
|
||||
]
|
||||
},
|
||||
"registerContextEngine": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-context-engine"
|
||||
]
|
||||
},
|
||||
"registerControlUiDescriptor": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-control-ui-descriptor"
|
||||
]
|
||||
},
|
||||
"registerDetachedTaskRuntime": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-detached-task-runtime"
|
||||
]
|
||||
},
|
||||
"registerGatewayDiscoveryService": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-gateway-discovery-service"
|
||||
]
|
||||
},
|
||||
"registerGatewayMethod": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-gateway-method",
|
||||
"kitchen.status"
|
||||
]
|
||||
},
|
||||
"registerHook": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-hook"
|
||||
]
|
||||
},
|
||||
"registerHttpRoute": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-http-route",
|
||||
"kitchen-sink-http-status"
|
||||
]
|
||||
},
|
||||
"registerImageGenerationProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-image",
|
||||
"kitchen-sink-image-generation-provider"
|
||||
]
|
||||
},
|
||||
"registerInteractiveHandler": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-interactive-handler",
|
||||
"kitchen-sink-interactive-handler"
|
||||
]
|
||||
},
|
||||
"registerMediaUnderstandingProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-media",
|
||||
"kitchen-sink-media-understanding-provider"
|
||||
]
|
||||
},
|
||||
"registerMemoryCapability": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-memory-capability"
|
||||
]
|
||||
},
|
||||
"registerMemoryCorpusSupplement": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-memory-corpus",
|
||||
"kitchen-sink-memory-corpus-supplement"
|
||||
]
|
||||
},
|
||||
"registerMemoryEmbeddingProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-memory-embedding",
|
||||
"kitchen-sink-memory-embedding-provider"
|
||||
]
|
||||
},
|
||||
"registerMemoryFlushPlan": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-memory-flush-plan"
|
||||
]
|
||||
},
|
||||
"registerMemoryPromptSection": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-memory-prompt-section"
|
||||
]
|
||||
},
|
||||
"registerMemoryPromptSupplement": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-memory-prompt-supplement",
|
||||
"kitchen-sink-memory-prompt-supplement"
|
||||
]
|
||||
},
|
||||
"registerMemoryRuntime": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-memory-runtime"
|
||||
]
|
||||
},
|
||||
"registerMigrationProvider": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-migration-provider"
|
||||
]
|
||||
},
|
||||
"registerMusicGenerationProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-music",
|
||||
"kitchen-sink-music-generation-provider"
|
||||
]
|
||||
},
|
||||
"registerNodeHostCommand": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-node-host-command"
|
||||
]
|
||||
},
|
||||
"registerNodeInvokePolicy": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-node-invoke-policy"
|
||||
]
|
||||
},
|
||||
"registerProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-llm",
|
||||
"kitchen-sink-provider"
|
||||
]
|
||||
},
|
||||
"registerRealtimeTranscriptionProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-realtime-transcription",
|
||||
"kitchen-sink-realtime-transcription-provider"
|
||||
]
|
||||
},
|
||||
"registerRealtimeVoiceProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-realtime-voice",
|
||||
"kitchen-sink-realtime-voice-provider"
|
||||
]
|
||||
},
|
||||
"registerReload": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-reload"
|
||||
]
|
||||
},
|
||||
"registerRuntimeLifecycle": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-runtime-lifecycle"
|
||||
]
|
||||
},
|
||||
"registerSecurityAuditCollector": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-security-audit-collector"
|
||||
]
|
||||
},
|
||||
"registerService": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-service",
|
||||
"kitchen-sink-service"
|
||||
]
|
||||
},
|
||||
"registerSessionExtension": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-session-extension"
|
||||
]
|
||||
},
|
||||
"registerSessionSchedulerJob": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-session-scheduler-job"
|
||||
]
|
||||
},
|
||||
"registerSpeechProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-speech",
|
||||
"kitchen-sink-speech-provider"
|
||||
]
|
||||
},
|
||||
"registerTextTransforms": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-text-transforms"
|
||||
]
|
||||
},
|
||||
"registerTool": {
|
||||
"count": 4,
|
||||
"ids": [
|
||||
"kitchen-sink-tool",
|
||||
"kitchen_sink_image_job",
|
||||
"kitchen_sink_search",
|
||||
"kitchen_sink_text"
|
||||
]
|
||||
},
|
||||
"registerToolMetadata": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-tool-metadata"
|
||||
]
|
||||
},
|
||||
"registerTrustedToolPolicy": {
|
||||
"count": 1,
|
||||
"ids": [
|
||||
"kitchen-sink-trusted-tool-policy"
|
||||
]
|
||||
},
|
||||
"registerVideoGenerationProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-video",
|
||||
"kitchen-sink-video-generation-provider"
|
||||
]
|
||||
},
|
||||
"registerWebFetchProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-fetch",
|
||||
"kitchen-sink-web-fetch-provider"
|
||||
]
|
||||
},
|
||||
"registerWebSearchProvider": {
|
||||
"count": 2,
|
||||
"ids": [
|
||||
"kitchen-sink-search",
|
||||
"kitchen-sink-web-search-provider"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
# Kitchen Sink Contract Probes
|
||||
|
||||
Generated: deterministic
|
||||
Status: PASS
|
||||
|
||||
## Covered Inspector Gaps
|
||||
|
||||
- before_tool_call allow/block/approval semantics
|
||||
- llm_input, llm_output, and agent_end privacy-boundary probes
|
||||
- runtime registrar capture for service, route, gateway, command, interactive handler, and channel surfaces
|
||||
- channel account, envelope, and outbound route probes
|
||||
|
||||
## Runtime Registrations
|
||||
|
||||
| Method | Count | IDs |
|
||||
| ------ | ----- | --- |
|
||||
| registerAgentEventSubscription | 1 | kitchen-sink-agent-event-subscription |
|
||||
| registerAgentHarness | 1 | kitchen-sink-agent-harness |
|
||||
| registerAgentToolResultMiddleware | 2 | kitchen-sink-agent-tool-result-middleware, kitchen-sink-agent-tool-result-middleware |
|
||||
| registerAutoEnableProbe | 1 | kitchen-sink-auto-enable-probe |
|
||||
| registerChannel | 2 | kitchen-sink-channel, kitchen-sink-channel-probe |
|
||||
| registerCli | 2 | kitchen-sink, kitchen-sink-cli |
|
||||
| registerCliBackend | 1 | kitchen-sink-cli-backend |
|
||||
| registerCodexAppServerExtensionFactory | 1 | kitchen-sink-codex-app-server-extension-factory |
|
||||
| registerCommand | 3 | kitchen, kitchen-sink, kitchen-sink-command |
|
||||
| registerCompactionProvider | 2 | kitchen-sink-compaction, kitchen-sink-compaction-provider |
|
||||
| registerConfigMigration | 1 | kitchen-sink-config-migration |
|
||||
| registerContextEngine | 1 | kitchen-sink-context-engine |
|
||||
| registerControlUiDescriptor | 1 | kitchen-sink-control-ui-descriptor |
|
||||
| registerDetachedTaskRuntime | 1 | kitchen-sink-detached-task-runtime |
|
||||
| registerGatewayDiscoveryService | 1 | kitchen-sink-gateway-discovery-service |
|
||||
| registerGatewayMethod | 2 | kitchen-sink-gateway-method, kitchen.status |
|
||||
| registerHook | 1 | kitchen-sink-hook |
|
||||
| registerHttpRoute | 2 | kitchen-sink-http-route, kitchen-sink-http-status |
|
||||
| registerImageGenerationProvider | 2 | kitchen-sink-image, kitchen-sink-image-generation-provider |
|
||||
| registerInteractiveHandler | 2 | kitchen-sink-interactive-handler, kitchen-sink-interactive-handler |
|
||||
| registerMediaUnderstandingProvider | 2 | kitchen-sink-media, kitchen-sink-media-understanding-provider |
|
||||
| registerMemoryCapability | 1 | kitchen-sink-memory-capability |
|
||||
| registerMemoryCorpusSupplement | 2 | kitchen-sink-memory-corpus, kitchen-sink-memory-corpus-supplement |
|
||||
| registerMemoryEmbeddingProvider | 2 | kitchen-sink-memory-embedding, kitchen-sink-memory-embedding-provider |
|
||||
| registerMemoryFlushPlan | 1 | kitchen-sink-memory-flush-plan |
|
||||
| registerMemoryPromptSection | 1 | kitchen-sink-memory-prompt-section |
|
||||
| registerMemoryPromptSupplement | 2 | kitchen-sink-memory-prompt-supplement, kitchen-sink-memory-prompt-supplement |
|
||||
| registerMemoryRuntime | 1 | kitchen-sink-memory-runtime |
|
||||
| registerMigrationProvider | 1 | kitchen-sink-migration-provider |
|
||||
| registerMusicGenerationProvider | 2 | kitchen-sink-music, kitchen-sink-music-generation-provider |
|
||||
| registerNodeHostCommand | 1 | kitchen-sink-node-host-command |
|
||||
| registerNodeInvokePolicy | 1 | kitchen-sink-node-invoke-policy |
|
||||
| registerProvider | 2 | kitchen-sink-llm, kitchen-sink-provider |
|
||||
| registerRealtimeTranscriptionProvider | 2 | kitchen-sink-realtime-transcription, kitchen-sink-realtime-transcription-provider |
|
||||
| registerRealtimeVoiceProvider | 2 | kitchen-sink-realtime-voice, kitchen-sink-realtime-voice-provider |
|
||||
| registerReload | 1 | kitchen-sink-reload |
|
||||
| registerRuntimeLifecycle | 1 | kitchen-sink-runtime-lifecycle |
|
||||
| registerSecurityAuditCollector | 1 | kitchen-sink-security-audit-collector |
|
||||
| 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 |
|
||||
| registerTextTransforms | 1 | kitchen-sink-text-transforms |
|
||||
| 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 |
|
||||
| registerWebFetchProvider | 2 | kitchen-sink-fetch, kitchen-sink-web-fetch-provider |
|
||||
| registerWebSearchProvider | 2 | kitchen-sink-search, kitchen-sink-web-search-provider |
|
||||
@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const tempRoot = mkdtempSync(path.join(tmpdir(), "kitchen-sink-install-"));
|
||||
const keepTemp = process.env.KEEP_KITCHEN_INSTALL_SMOKE === "1";
|
||||
let lastStdout = "";
|
||||
|
||||
try {
|
||||
const packDir = path.join(tempRoot, "pack");
|
||||
mkdirSync(packDir, { recursive: true });
|
||||
run("npm", ["pack", "--json", "--pack-destination", packDir], { cwd: repoRoot });
|
||||
const packOutput = JSON.parse(lastStdout);
|
||||
const tarball = path.join(packDir, packOutput[0].filename);
|
||||
|
||||
const projectDir = path.join(tempRoot, "consumer");
|
||||
mkdirSync(projectDir, { recursive: true });
|
||||
run("npm", ["init", "-y"], { cwd: tempRoot });
|
||||
run("npm", ["install", "--prefix", projectDir, "--package-lock=false", "--ignore-scripts", "--no-audit", "--no-fund", tarball], {
|
||||
cwd: tempRoot,
|
||||
});
|
||||
|
||||
const packageDir = path.join(projectDir, "node_modules", "@openclaw", "kitchen-sink");
|
||||
const installedPackageJson = JSON.parse(readFileSync(path.join(packageDir, "package.json"), "utf8"));
|
||||
assert.equal(installedPackageJson.name, "@openclaw/kitchen-sink");
|
||||
assert.equal(installedPackageJson.version, JSON.parse(readFileSync("package.json", "utf8")).version);
|
||||
|
||||
const probeFile = path.join(projectDir, "probe.mjs");
|
||||
writeFileSync(probeFile, readFileSync(new URL("./fixtures/installed-consumer-probe.mjs", import.meta.url), "utf8"));
|
||||
run(process.execPath, [probeFile], { cwd: projectDir });
|
||||
|
||||
const inspectorBin = path.join(repoRoot, "node_modules", ".bin", "plugin-inspector");
|
||||
run(inspectorBin, ["check", "--config", "plugin-inspector.config.json", "--no-openclaw", "--runtime", "--mock-sdk"], {
|
||||
cwd: packageDir,
|
||||
env: {
|
||||
...process.env,
|
||||
PLUGIN_INSPECTOR_EXECUTE_ISOLATED: "1",
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Installed package smoke OK: ${installedPackageJson.name}@${installedPackageJson.version}`);
|
||||
} finally {
|
||||
if (!keepTemp) {
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
} else {
|
||||
console.log(`Kept install smoke temp dir: ${tempRoot}`);
|
||||
}
|
||||
}
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env || process.env,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
lastStdout = result.stdout;
|
||||
if (result.status !== 0) {
|
||||
process.stdout.write(result.stdout);
|
||||
process.stderr.write(result.stderr);
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { plugin } from "../src/index.js";
|
||||
import {
|
||||
capturePluginRegistration,
|
||||
createHookFinder,
|
||||
registrationSummary,
|
||||
} from "./lib/plugin-registration-harness.mjs";
|
||||
|
||||
const registrations = capturePluginRegistration(plugin);
|
||||
const findHook = createHookFinder(registrations);
|
||||
|
||||
const beforeToolCall = findHook("before_tool_call");
|
||||
const llmInput = findHook("llm_input");
|
||||
const llmOutput = findHook("llm_output");
|
||||
const agentEnd = findHook("agent_end");
|
||||
|
||||
const probes = {
|
||||
beforeToolCall: {
|
||||
allow: await beforeToolCall(toolEvent("generate a kitchen image"), { providerId: "kitchen-sink-image" }),
|
||||
block: await beforeToolCall(toolEvent("kitchen block image generation"), { providerId: "kitchen-sink-image" }),
|
||||
approval: await beforeToolCall(toolEvent("kitchen image generation needs approval"), {
|
||||
providerId: "kitchen-sink-image",
|
||||
}),
|
||||
},
|
||||
conversationPrivacy: {
|
||||
input: await llmInput(secretEvent("kitchen explain the fixture"), secretContext()),
|
||||
output: await llmOutput(secretEvent("kitchen image result"), secretContext()),
|
||||
end: await agentEnd(secretEvent("kitchen final answer"), secretContext()),
|
||||
},
|
||||
channel: await captureChannelProbe(),
|
||||
runtimeRegistrations: registrationSummary(registrations),
|
||||
};
|
||||
|
||||
assert.equal(probes.beforeToolCall.allow.decision, "allow");
|
||||
assert.equal(probes.beforeToolCall.allow.params.args.kitchenSinkScenario, "image.generate");
|
||||
assert.equal(probes.beforeToolCall.block.block, true);
|
||||
assert.equal(probes.beforeToolCall.block.terminal, true);
|
||||
assert.equal(probes.beforeToolCall.approval.decision, "approval");
|
||||
assert.equal(probes.beforeToolCall.approval.requireApproval.pluginId, "openclaw-kitchen-sink-fixture");
|
||||
|
||||
for (const result of Object.values(probes.conversationPrivacy)) {
|
||||
assert.equal(result.privacy.boundary, "conversation-observer");
|
||||
assert.equal(result.privacy.storesRawPayload, false);
|
||||
assert.equal(result.privacy.exposesRawPayload, false);
|
||||
assert.ok(result.privacy.redactedFields.length >= 2);
|
||||
assert.ok(result.privacy.secretPatternCount >= 1);
|
||||
}
|
||||
|
||||
for (const method of [
|
||||
"registerChannel",
|
||||
"registerCommand",
|
||||
"registerGatewayMethod",
|
||||
"registerHttpRoute",
|
||||
"registerInteractiveHandler",
|
||||
"registerService",
|
||||
]) {
|
||||
assert.ok(probes.runtimeRegistrations[method]?.count > 0, `${method} was not captured`);
|
||||
}
|
||||
|
||||
assert.equal(probes.channel.account.statusState, "ready");
|
||||
assert.equal(probes.channel.delivery.deliveryStatus, "sent");
|
||||
assert.equal(probes.channel.route.peer.kind, "direct");
|
||||
|
||||
mkdirSync("reports", { recursive: true });
|
||||
writeFileSync("reports/kitchen-contract-probes.json", `${JSON.stringify(probes, null, 2)}\n`);
|
||||
writeFileSync("reports/kitchen-contract-probes.md", renderMarkdown(probes));
|
||||
|
||||
console.log(
|
||||
`Kitchen contract probes OK: ${Object.keys(probes.runtimeRegistrations).length} registration methods, before_tool_call allow/block/approval, conversation privacy, channel envelope`,
|
||||
);
|
||||
|
||||
async function captureChannelProbe() {
|
||||
const channel = registrations.registerChannel?.map(([value]) => value).find((value) => value.id === "kitchen-sink-channel");
|
||||
assert.ok(channel, "kitchen-sink-channel registered");
|
||||
return {
|
||||
account: channel.config.resolveAccount({}, "local"),
|
||||
delivery: await channel.outbound.sendText({ to: "kitchen demo", text: "kitchen generate image" }),
|
||||
route: await channel.messaging.resolveOutboundSessionRoute({
|
||||
agentId: "fixture-agent",
|
||||
target: "kitchen demo",
|
||||
threadId: "thread-1",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function toolEvent(prompt) {
|
||||
return {
|
||||
toolId: "kitchen_sink_image_job",
|
||||
args: { prompt },
|
||||
};
|
||||
}
|
||||
|
||||
function secretEvent(prompt) {
|
||||
return {
|
||||
prompt,
|
||||
apiKey: "sk-local-secret-not-stored",
|
||||
token: "fixture-token-not-stored",
|
||||
};
|
||||
}
|
||||
|
||||
function secretContext() {
|
||||
return {
|
||||
providerId: "kitchen-sink-llm",
|
||||
authorization: "Bearer fixture-secret-not-stored",
|
||||
};
|
||||
}
|
||||
|
||||
function renderMarkdown(report) {
|
||||
const methods = Object.entries(report.runtimeRegistrations)
|
||||
.map(([method, summary]) => `| ${method} | ${summary.count} | ${summary.ids.join(", ")} |`)
|
||||
.join("\n");
|
||||
return [
|
||||
"# Kitchen Sink Contract Probes",
|
||||
"",
|
||||
"Generated: deterministic",
|
||||
"Status: PASS",
|
||||
"",
|
||||
"## Covered Inspector Gaps",
|
||||
"",
|
||||
"- before_tool_call allow/block/approval semantics",
|
||||
"- llm_input, llm_output, and agent_end privacy-boundary probes",
|
||||
"- runtime registrar capture for service, route, gateway, command, interactive handler, and channel surfaces",
|
||||
"- channel account, envelope, and outbound route probes",
|
||||
"",
|
||||
"## Runtime Registrations",
|
||||
"",
|
||||
"| Method | Count | IDs |",
|
||||
"| ------ | ----- | --- |",
|
||||
methods,
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
@ -2,16 +2,38 @@
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { plugin } from "../src/index.js";
|
||||
import {
|
||||
capturePluginRegistration,
|
||||
createHookFinder,
|
||||
createRegistrationFinder,
|
||||
fixedNow,
|
||||
} from "./lib/plugin-registration-harness.mjs";
|
||||
|
||||
const registrations = capturePluginRegistration(plugin);
|
||||
const findRegistration = createRegistrationFinder(registrations);
|
||||
const findHook = createHookFinder(registrations);
|
||||
const registrations = {};
|
||||
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) => {
|
||||
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) ?? [];
|
||||
assert.ok(commands.some((command) => command.name === "kitchen"), "registers kitchen command");
|
||||
@ -26,40 +48,6 @@ assert.equal(hookResult.pluginId, "openclaw-kitchen-sink-fixture");
|
||||
assert.equal(hookResult.route, "hook:before_tool_call");
|
||||
assert.equal(hookResult.scenarioId, "image.generate");
|
||||
assert.equal(hookResult.matchedKitchen, true);
|
||||
assert.equal(hookResult.decision, "allow");
|
||||
assert.equal(hookResult.params.args.kitchenSinkScenario, "image.generate");
|
||||
|
||||
const blockedToolHookResult = await beforeToolHook(
|
||||
{ toolId: "kitchen_sink_image_job", args: { prompt: "kitchen block this image" } },
|
||||
{ providerId: "kitchen-sink-image" },
|
||||
);
|
||||
assert.equal(blockedToolHookResult.block, true);
|
||||
assert.equal(blockedToolHookResult.terminal, true);
|
||||
assert.equal(blockedToolHookResult.decision, "block");
|
||||
assert.match(blockedToolHookResult.blockReason, /blocked kitchen_sink_image_job/);
|
||||
|
||||
const approvalToolHookResult = await beforeToolHook(
|
||||
{ toolId: "kitchen_sink_image_job", args: { prompt: "kitchen image needs approval" } },
|
||||
{ providerId: "kitchen-sink-image" },
|
||||
);
|
||||
assert.equal(approvalToolHookResult.decision, "approval");
|
||||
assert.equal(approvalToolHookResult.requireApproval.pluginId, "openclaw-kitchen-sink-fixture");
|
||||
assert.equal(approvalToolHookResult.requireApproval.scenarioId, "image.generate");
|
||||
|
||||
const llmInputHook = findHook("llm_input");
|
||||
const llmInputResult = await llmInputHook(
|
||||
{
|
||||
prompt: "kitchen explain image routing with api_key sk-test-redacted",
|
||||
apiKey: "sk-real-secret-not-stored",
|
||||
},
|
||||
{ providerId: "kitchen-sink-llm", authorization: "Bearer local-secret" },
|
||||
);
|
||||
assert.equal(llmInputResult.scenarioId, "text.reply");
|
||||
assert.equal(llmInputResult.privacy.boundary, "conversation-observer");
|
||||
assert.equal(llmInputResult.privacy.storesRawPayload, false);
|
||||
assert.equal(llmInputResult.privacy.exposesRawPayload, false);
|
||||
assert.deepEqual(llmInputResult.privacy.redactedFields, ["event.apiKey", "context.authorization"]);
|
||||
assert.ok(llmInputResult.privacy.secretPatternCount >= 2);
|
||||
|
||||
const channel = findRegistration("registerChannel", "kitchen-sink-channel");
|
||||
const channelAccount = channel.config.resolveAccount({}, "local");
|
||||
@ -109,13 +97,7 @@ const imageProvider = findRegistration("registerImageGenerationProvider", "kitch
|
||||
assert.equal(imageProvider.defaultModel, "kitchen-sink-image-v1");
|
||||
|
||||
const sleeps = [];
|
||||
const {
|
||||
PLUGIN_ID,
|
||||
listKitchenHumanScenarios,
|
||||
runKitchenHumanScenario,
|
||||
runKitchenImageTool,
|
||||
runKitchenScenario,
|
||||
} = await import("../src/scenarios.js");
|
||||
const { PLUGIN_ID, runKitchenImageTool, runKitchenScenario } = await import("../src/scenarios.js");
|
||||
const { createKitchenSinkRuntime } = await import("../src/kitchen-runtime.js");
|
||||
const fastRuntime = createKitchenSinkRuntime({
|
||||
delayMs: 10_000,
|
||||
@ -156,40 +138,8 @@ assert.deepEqual(
|
||||
);
|
||||
assert.ok(imageResult.image.dataUrl.startsWith("data:image/png;base64,"));
|
||||
|
||||
const humanScenarios = listKitchenHumanScenarios();
|
||||
assert.deepEqual(
|
||||
humanScenarios.map((scenario) => scenario.id),
|
||||
[
|
||||
"dry.prefix-image",
|
||||
"live.openai-text-kitchen-image",
|
||||
"search.fetch.summarize",
|
||||
"channel.prefix-image",
|
||||
"hook.block-tool",
|
||||
"memory.compact-fixture",
|
||||
],
|
||||
);
|
||||
const liveImageScenario = await runKitchenHumanScenario(fastRuntime, "live.openai-text-kitchen-image");
|
||||
assert.equal(liveImageScenario.mode, "live-llm-compatible");
|
||||
assert.equal(liveImageScenario.result.route, "human:live-llm-image-provider");
|
||||
assert.equal(liveImageScenario.result.image.metadata.assetName, "kitchen_sink_office.png");
|
||||
const searchFetchScenario = await runKitchenHumanScenario(fastRuntime, "search.fetch.summarize");
|
||||
assert.equal(searchFetchScenario.result.search.results[0].id, "ks-result-image-provider");
|
||||
assert.equal(searchFetchScenario.result.fetch.finalUrl, "kitchen://fixture/readme");
|
||||
assert.match(searchFetchScenario.result.summary, /Kitchen Sink text fixture/);
|
||||
const channelScenario = await runKitchenHumanScenario(fastRuntime, "channel.prefix-image");
|
||||
assert.equal(channelScenario.result.delivery.channel, "kitchen-sink-channel");
|
||||
assert.equal(channelScenario.result.delivery.meta.scenarioId, "image.generate");
|
||||
const hookBlockScenario = await runKitchenHumanScenario(fastRuntime, "hook.block-tool");
|
||||
assert.equal(hookBlockScenario.result.block, true);
|
||||
assert.equal(hookBlockScenario.result.decision, "block");
|
||||
const memoryScenario = await runKitchenHumanScenario(fastRuntime, "memory.compact-fixture");
|
||||
assert.equal(memoryScenario.result.embedding.length, 8);
|
||||
assert.equal(memoryScenario.result.memory.results[0].id, "ks-memory-runtime-surfaces");
|
||||
assert.deepEqual(memoryScenario.result.compaction.preservedIdentifiers, ["ks_image_1f8a5a98"]);
|
||||
|
||||
sleeps.length = 0;
|
||||
const failedImageResult = await fastRuntime.runImageJob({ prompt: "kitchen rate limit image" });
|
||||
assert.deepEqual(sleeps, [10_000]);
|
||||
assert.deepEqual(sleeps, [10_000, 10_000]);
|
||||
assert.equal(failedImageResult.job.status, "failed");
|
||||
assert.deepEqual(
|
||||
failedImageResult.job.timeline.map((entry) => entry.status),
|
||||
@ -200,7 +150,6 @@ assert.equal(failedImageResult.error.statusCode, 429);
|
||||
assert.equal(failedImageResult.error.retryAfterMs, 30_000);
|
||||
|
||||
const failedToolResult = await runKitchenImageTool(fastRuntime, { prompt: "kitchen timeout image" });
|
||||
assert.deepEqual(sleeps, [10_000, 10_000]);
|
||||
assert.equal(failedToolResult.ok, false);
|
||||
assert.equal(failedToolResult.error.code, "timeout");
|
||||
assert.equal(failedToolResult.mediaUrl, undefined);
|
||||
@ -365,50 +314,21 @@ assert.equal(cliRegistration[1].descriptors[0].name, "kitchen-sink");
|
||||
const imageTool = findRegistration("registerTool", "kitchen_sink_image_job");
|
||||
assert.equal(typeof imageTool.execute, "function");
|
||||
|
||||
const { KITCHEN_SINK_EXPECTED_DIAGNOSTICS } = await import("../src/personality.js");
|
||||
assert.deepEqual(KITCHEN_SINK_EXPECTED_DIAGNOSTICS.conformance, []);
|
||||
assert.ok(
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS.adversarial.includes(
|
||||
'channel "kitchen-sink-channel-probe" registration missing required config helpers',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS.full.includes(
|
||||
"only bundled plugins can register agent tool result middleware",
|
||||
),
|
||||
);
|
||||
|
||||
const conformance = capturePluginRegistration(plugin, { personality: "conformance" });
|
||||
assert.ok(
|
||||
conformance.registerCommand?.some(([command]) => command.name === "kitchen"),
|
||||
"conformance registers usable commands",
|
||||
);
|
||||
assert.ok(
|
||||
conformance.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"),
|
||||
"conformance registers the usable channel",
|
||||
);
|
||||
assert.equal(conformance.registerAgentToolResultMiddleware, undefined);
|
||||
assert.equal(
|
||||
conformance.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel-probe"),
|
||||
false,
|
||||
);
|
||||
|
||||
const adversarial = capturePluginRegistration(plugin, { personality: "adversarial" });
|
||||
assert.equal(
|
||||
adversarial.registerCommand?.some(([command]) => command.name === "kitchen"),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
adversarial.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"),
|
||||
false,
|
||||
);
|
||||
assert.ok(
|
||||
adversarial.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel-probe"),
|
||||
"adversarial registers generated invalid channel probe",
|
||||
);
|
||||
assert.ok(
|
||||
adversarial.registerCompactionProvider?.some(([provider]) => provider.id === "kitchen-sink-compaction-provider"),
|
||||
"adversarial registers generated invalid compaction probe",
|
||||
);
|
||||
|
||||
console.log("Kitchen runtime OK");
|
||||
|
||||
function 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,16 +32,7 @@ const requiredFiles = [
|
||||
"plugin-inspector.config.json",
|
||||
"src/index.js",
|
||||
"src/assets/kitchen_sink_office.png",
|
||||
"src/constants.js",
|
||||
"src/fixtures/images.js",
|
||||
"src/fixtures/text.js",
|
||||
"src/kitchen-runtime.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/setup.js",
|
||||
"src/generated-hooks.js",
|
||||
@ -51,7 +42,6 @@ const requiredFiles = [
|
||||
const missingFiles = requiredFiles.filter((file) => !files.has(file));
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||
const pluginManifest = JSON.parse(fs.readFileSync("openclaw.plugin.json", "utf8"));
|
||||
const issues = [];
|
||||
|
||||
function sameStringArray(actual, expected) {
|
||||
@ -101,37 +91,9 @@ if (buildPluginSdkVersion !== buildOpenClawVersion) {
|
||||
if (packageJson.dependencies?.openclaw !== buildOpenClawVersion) {
|
||||
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/")) {
|
||||
issues.push("package files must include src/");
|
||||
}
|
||||
if (packageJson.exports?.["./runtime"] !== "./src/kitchen-runtime.js") {
|
||||
issues.push('package exports "./runtime" must point at "./src/kitchen-runtime.js"');
|
||||
}
|
||||
if (packageJson.exports?.["./scenarios"] !== "./src/scenarios.js") {
|
||||
issues.push('package exports "./scenarios" must point at "./src/scenarios.js"');
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
console.error(`Package payload check failed:\n- ${issues.join("\n- ")}`);
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
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);
|
||||
@ -1,90 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
#!/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,32 +127,19 @@ ${pluginSdkExports.map((_, index) => ` | typeof sdk${index}`).join("\n")};
|
||||
|
||||
function renderRuntimeIndex() {
|
||||
const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||
return `import { PLUGIN_ID } from "./constants.js";
|
||||
import { registerAllHooks } from "./generated-hooks.js";
|
||||
return `import { registerAllHooks } from "./generated-hooks.js";
|
||||
import { registerAllRegistrars } from "./generated-registrars.js";
|
||||
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
|
||||
import {
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
resolveKitchenSinkPersonality,
|
||||
} from "./personality.js";
|
||||
|
||||
export const plugin = {
|
||||
id: PLUGIN_ID,
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
name: "OpenClaw Kitchen Sink",
|
||||
version: "${packageJson.version}",
|
||||
description: "Credential-free fixture covering OpenClaw plugin API seams.",
|
||||
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
register(api) {
|
||||
const personality = resolveKitchenSinkPersonality(api);
|
||||
registerAllHooks(api);
|
||||
if (personality !== "conformance") {
|
||||
registerAllRegistrars(api);
|
||||
}
|
||||
if (personality !== "adversarial") {
|
||||
registerKitchenSinkRuntime(api, {
|
||||
includeAgentToolResultMiddleware: personality !== "conformance",
|
||||
});
|
||||
}
|
||||
registerAllRegistrars(api);
|
||||
registerKitchenSinkRuntime(api);
|
||||
},
|
||||
};
|
||||
|
||||
@ -215,29 +202,6 @@ function renderManifest({ manifestContracts, packageVersion }) {
|
||||
onCommands: ["kitchen", "kitchen-sink"],
|
||||
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: {
|
||||
providers: [
|
||||
{ id: "kitchen-sink-provider", authMethods: ["none"], envVars: [] },
|
||||
@ -259,7 +223,6 @@ function renderManifest({ manifestContracts, packageVersion }) {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
enabled: { type: "boolean", default: false },
|
||||
personality: { type: "string", enum: ["full", "conformance", "adversarial"], default: "full" },
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -274,18 +237,6 @@ function renderPackageJson({ packageVersion }) {
|
||||
openclawVersion: 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) {
|
||||
packageJson.dependencies.openclaw = packageVersion;
|
||||
}
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
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;
|
||||
@ -1,81 +0,0 @@
|
||||
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");
|
||||
}
|
||||
@ -1,209 +0,0 @@
|
||||
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,11 +1,10 @@
|
||||
// Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
|
||||
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand.
|
||||
import { observeKitchenHook } from "./scenarios.js";
|
||||
|
||||
export function registerAllHooks(api) {
|
||||
api.on("after_compaction", kitchenSinkHook("after_compaction"));
|
||||
api.on("after_tool_call", kitchenSinkHook("after_tool_call"));
|
||||
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_reply", kitchenSinkHook("before_agent_reply"));
|
||||
api.on("before_agent_start", kitchenSinkHook("before_agent_start"));
|
||||
@ -17,10 +16,8 @@ export function registerAllHooks(api) {
|
||||
api.on("before_prompt_build", kitchenSinkHook("before_prompt_build"));
|
||||
api.on("before_reset", kitchenSinkHook("before_reset"));
|
||||
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_stop", kitchenSinkHook("gateway_stop"));
|
||||
api.on("heartbeat_prompt_contribution", kitchenSinkHook("heartbeat_prompt_contribution"));
|
||||
api.on("inbound_claim", kitchenSinkHook("inbound_claim"));
|
||||
api.on("llm_input", kitchenSinkHook("llm_input"));
|
||||
api.on("llm_output", kitchenSinkHook("llm_output"));
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
|
||||
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand.
|
||||
|
||||
export function registerAllRegistrars(api) {
|
||||
safeRegister("registerAgentEventSubscription", () => api.registerAgentEventSubscription(payloadFor("registerAgentEventSubscription")));
|
||||
safeRegister("registerAgentHarness", () => api.registerAgentHarness(payloadFor("registerAgentHarness")));
|
||||
safeRegister("registerAgentToolResultMiddleware", () => api.registerAgentToolResultMiddleware(payloadFor("registerAgentToolResultMiddleware")));
|
||||
safeRegister("registerAutoEnableProbe", () => api.registerAutoEnableProbe(payloadFor("registerAutoEnableProbe")));
|
||||
@ -13,7 +12,6 @@ export function registerAllRegistrars(api) {
|
||||
safeRegister("registerCompactionProvider", () => api.registerCompactionProvider(payloadFor("registerCompactionProvider")));
|
||||
safeRegister("registerConfigMigration", () => api.registerConfigMigration(payloadFor("registerConfigMigration")));
|
||||
safeRegister("registerContextEngine", () => api.registerContextEngine(payloadFor("registerContextEngine")));
|
||||
safeRegister("registerControlUiDescriptor", () => api.registerControlUiDescriptor(payloadFor("registerControlUiDescriptor")));
|
||||
void "api.registerDetachedTaskRuntime("; // Covered by the hand-owned Kitchen Sink task runtime.
|
||||
safeRegister("registerGatewayDiscoveryService", () => api.registerGatewayDiscoveryService(payloadFor("registerGatewayDiscoveryService")));
|
||||
safeRegister("registerGatewayMethod", () => api.registerGatewayMethod(payloadFor("registerGatewayMethod")));
|
||||
@ -32,21 +30,15 @@ export function registerAllRegistrars(api) {
|
||||
safeRegister("registerMigrationProvider", () => api.registerMigrationProvider(payloadFor("registerMigrationProvider")));
|
||||
safeRegister("registerMusicGenerationProvider", () => api.registerMusicGenerationProvider(payloadFor("registerMusicGenerationProvider")));
|
||||
safeRegister("registerNodeHostCommand", () => api.registerNodeHostCommand(payloadFor("registerNodeHostCommand")));
|
||||
safeRegister("registerNodeInvokePolicy", () => api.registerNodeInvokePolicy(payloadFor("registerNodeInvokePolicy")));
|
||||
safeRegister("registerProvider", () => api.registerProvider(payloadFor("registerProvider")));
|
||||
safeRegister("registerRealtimeTranscriptionProvider", () => api.registerRealtimeTranscriptionProvider(payloadFor("registerRealtimeTranscriptionProvider")));
|
||||
safeRegister("registerRealtimeVoiceProvider", () => api.registerRealtimeVoiceProvider(payloadFor("registerRealtimeVoiceProvider")));
|
||||
safeRegister("registerReload", () => api.registerReload(payloadFor("registerReload")));
|
||||
safeRegister("registerRuntimeLifecycle", () => api.registerRuntimeLifecycle(payloadFor("registerRuntimeLifecycle")));
|
||||
safeRegister("registerSecurityAuditCollector", () => api.registerSecurityAuditCollector(payloadFor("registerSecurityAuditCollector")));
|
||||
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("registerTextTransforms", () => api.registerTextTransforms(payloadFor("registerTextTransforms")));
|
||||
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("registerWebFetchProvider", () => api.registerWebFetchProvider(payloadFor("registerWebFetchProvider")));
|
||||
safeRegister("registerWebSearchProvider", () => api.registerWebSearchProvider(payloadFor("registerWebSearchProvider")));
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
|
||||
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand.
|
||||
import type * as sdk0 from "openclaw/plugin-sdk";
|
||||
import type * as sdk1 from "openclaw/plugin-sdk/account-core";
|
||||
import type * as sdk2 from "openclaw/plugin-sdk/account-helpers";
|
||||
@ -8,290 +8,261 @@ 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 sdk7 from "openclaw/plugin-sdk/acp-binding-runtime";
|
||||
import type * as sdk8 from "openclaw/plugin-sdk/acp-runtime";
|
||||
import type * as sdk9 from "openclaw/plugin-sdk/acp-runtime-backend";
|
||||
import type * as sdk10 from "openclaw/plugin-sdk/agent-config-primitives";
|
||||
import type * as sdk11 from "openclaw/plugin-sdk/agent-harness";
|
||||
import type * as sdk12 from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import type * as sdk13 from "openclaw/plugin-sdk/agent-media-payload";
|
||||
import type * as sdk14 from "openclaw/plugin-sdk/agent-runtime";
|
||||
import type * as sdk15 from "openclaw/plugin-sdk/agent-runtime-test-contracts";
|
||||
import type * as sdk16 from "openclaw/plugin-sdk/allow-from";
|
||||
import type * as sdk17 from "openclaw/plugin-sdk/allowlist-config-edit";
|
||||
import type * as sdk18 from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import type * as sdk19 from "openclaw/plugin-sdk/approval-client-runtime";
|
||||
import type * as sdk20 from "openclaw/plugin-sdk/approval-delivery-runtime";
|
||||
import type * as sdk21 from "openclaw/plugin-sdk/approval-gateway-runtime";
|
||||
import type * as sdk22 from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
|
||||
import type * as sdk23 from "openclaw/plugin-sdk/approval-handler-runtime";
|
||||
import type * as sdk24 from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type * as sdk25 from "openclaw/plugin-sdk/approval-reply-runtime";
|
||||
import type * as sdk26 from "openclaw/plugin-sdk/approval-runtime";
|
||||
import type * as sdk27 from "openclaw/plugin-sdk/async-lock-runtime";
|
||||
import type * as sdk28 from "openclaw/plugin-sdk/boolean-param";
|
||||
import type * as sdk29 from "openclaw/plugin-sdk/browser-config";
|
||||
import type * as sdk30 from "openclaw/plugin-sdk/bundled-channel-config-schema";
|
||||
import type * as sdk31 from "openclaw/plugin-sdk/channel-actions";
|
||||
import type * as sdk32 from "openclaw/plugin-sdk/channel-activity-runtime";
|
||||
import type * as sdk33 from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type * as sdk34 from "openclaw/plugin-sdk/channel-config-primitives";
|
||||
import type * as sdk35 from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import type * as sdk36 from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
import type * as sdk37 from "openclaw/plugin-sdk/channel-config-writes";
|
||||
import type * as sdk38 from "openclaw/plugin-sdk/channel-contract";
|
||||
import type * as sdk39 from "openclaw/plugin-sdk/channel-contract-testing";
|
||||
import type * as sdk40 from "openclaw/plugin-sdk/channel-core";
|
||||
import type * as sdk41 from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
import type * as sdk42 from "openclaw/plugin-sdk/channel-envelope";
|
||||
import type * as sdk43 from "openclaw/plugin-sdk/channel-feedback";
|
||||
import type * as sdk44 from "openclaw/plugin-sdk/channel-inbound";
|
||||
import type * as sdk45 from "openclaw/plugin-sdk/channel-inbound-debounce";
|
||||
import type * as sdk46 from "openclaw/plugin-sdk/channel-inbound-roots";
|
||||
import type * as sdk47 from "openclaw/plugin-sdk/channel-lifecycle";
|
||||
import type * as sdk48 from "openclaw/plugin-sdk/channel-location";
|
||||
import type * as sdk49 from "openclaw/plugin-sdk/channel-logging";
|
||||
import type * as sdk50 from "openclaw/plugin-sdk/channel-mention-gating";
|
||||
import type * as sdk51 from "openclaw/plugin-sdk/channel-pairing";
|
||||
import type * as sdk52 from "openclaw/plugin-sdk/channel-pairing-paths";
|
||||
import type * as sdk53 from "openclaw/plugin-sdk/channel-plugin-common";
|
||||
import type * as sdk54 from "openclaw/plugin-sdk/channel-policy";
|
||||
import type * as sdk55 from "openclaw/plugin-sdk/channel-reply-options-runtime";
|
||||
import type * as sdk56 from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
import type * as sdk57 from "openclaw/plugin-sdk/channel-route";
|
||||
import type * as sdk58 from "openclaw/plugin-sdk/channel-runtime";
|
||||
import type * as sdk59 from "openclaw/plugin-sdk/channel-runtime-context";
|
||||
import type * as sdk60 from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
||||
import type * as sdk61 from "openclaw/plugin-sdk/channel-secret-runtime";
|
||||
import type * as sdk62 from "openclaw/plugin-sdk/channel-secret-tts-runtime";
|
||||
import type * as sdk63 from "openclaw/plugin-sdk/channel-send-result";
|
||||
import type * as sdk64 from "openclaw/plugin-sdk/channel-setup";
|
||||
import type * as sdk65 from "openclaw/plugin-sdk/channel-status";
|
||||
import type * as sdk66 from "openclaw/plugin-sdk/channel-streaming";
|
||||
import type * as sdk67 from "openclaw/plugin-sdk/channel-target-testing";
|
||||
import type * as sdk68 from "openclaw/plugin-sdk/channel-targets";
|
||||
import type * as sdk69 from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type * as sdk70 from "openclaw/plugin-sdk/cli-backend";
|
||||
import type * as sdk71 from "openclaw/plugin-sdk/cli-runtime";
|
||||
import type * as sdk72 from "openclaw/plugin-sdk/collection-runtime";
|
||||
import type * as sdk73 from "openclaw/plugin-sdk/command-auth";
|
||||
import type * as sdk74 from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type * as sdk75 from "openclaw/plugin-sdk/command-detection";
|
||||
import type * as sdk76 from "openclaw/plugin-sdk/command-gating";
|
||||
import type * as sdk77 from "openclaw/plugin-sdk/command-primitives-runtime";
|
||||
import type * as sdk78 from "openclaw/plugin-sdk/command-status";
|
||||
import type * as sdk79 from "openclaw/plugin-sdk/command-status-runtime";
|
||||
import type * as sdk80 from "openclaw/plugin-sdk/command-surface";
|
||||
import type * as sdk81 from "openclaw/plugin-sdk/compat";
|
||||
import type * as sdk82 from "openclaw/plugin-sdk/concurrency-runtime";
|
||||
import type * as sdk83 from "openclaw/plugin-sdk/config-mutation";
|
||||
import type * as sdk84 from "openclaw/plugin-sdk/config-runtime";
|
||||
import type * as sdk85 from "openclaw/plugin-sdk/config-schema";
|
||||
import type * as sdk86 from "openclaw/plugin-sdk/config-types";
|
||||
import type * as sdk87 from "openclaw/plugin-sdk/context-visibility-runtime";
|
||||
import type * as sdk88 from "openclaw/plugin-sdk/conversation-binding-runtime";
|
||||
import type * as sdk89 from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import type * as sdk90 from "openclaw/plugin-sdk/core";
|
||||
import type * as sdk91 from "openclaw/plugin-sdk/cron-store-runtime";
|
||||
import type * as sdk92 from "openclaw/plugin-sdk/dangerous-name-runtime";
|
||||
import type * as sdk93 from "openclaw/plugin-sdk/dedupe-runtime";
|
||||
import type * as sdk94 from "openclaw/plugin-sdk/delivery-queue-runtime";
|
||||
import type * as sdk95 from "openclaw/plugin-sdk/device-bootstrap";
|
||||
import type * as sdk96 from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||
import type * as sdk97 from "openclaw/plugin-sdk/direct-dm";
|
||||
import type * as sdk98 from "openclaw/plugin-sdk/direct-dm-access";
|
||||
import type * as sdk99 from "openclaw/plugin-sdk/direct-dm-guard-policy";
|
||||
import type * as sdk100 from "openclaw/plugin-sdk/directory-config-runtime";
|
||||
import type * as sdk101 from "openclaw/plugin-sdk/directory-runtime";
|
||||
import type * as sdk102 from "openclaw/plugin-sdk/discord";
|
||||
import type * as sdk103 from "openclaw/plugin-sdk/document-extractor";
|
||||
import type * as sdk104 from "openclaw/plugin-sdk/error-runtime";
|
||||
import type * as sdk105 from "openclaw/plugin-sdk/extension-shared";
|
||||
import type * as sdk106 from "openclaw/plugin-sdk/fetch-runtime";
|
||||
import type * as sdk107 from "openclaw/plugin-sdk/file-access-runtime";
|
||||
import type * as sdk108 from "openclaw/plugin-sdk/file-lock";
|
||||
import type * as sdk109 from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import type * as sdk110 from "openclaw/plugin-sdk/global-singleton";
|
||||
import type * as sdk111 from "openclaw/plugin-sdk/group-access";
|
||||
import type * as sdk112 from "openclaw/plugin-sdk/group-activation";
|
||||
import type * as sdk113 from "openclaw/plugin-sdk/heartbeat-runtime";
|
||||
import type * as sdk114 from "openclaw/plugin-sdk/hook-runtime";
|
||||
import type * as sdk115 from "openclaw/plugin-sdk/host-runtime";
|
||||
import type * as sdk116 from "openclaw/plugin-sdk/image-generation";
|
||||
import type * as sdk117 from "openclaw/plugin-sdk/image-generation-core";
|
||||
import type * as sdk118 from "openclaw/plugin-sdk/image-generation-runtime";
|
||||
import type * as sdk119 from "openclaw/plugin-sdk/inbound-envelope";
|
||||
import type * as sdk120 from "openclaw/plugin-sdk/inbound-reply-dispatch";
|
||||
import type * as sdk121 from "openclaw/plugin-sdk/infra-runtime";
|
||||
import type * as sdk122 from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import type * as sdk123 from "openclaw/plugin-sdk/json-store";
|
||||
import type * as sdk124 from "openclaw/plugin-sdk/keyed-async-queue";
|
||||
import type * as sdk125 from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import type * as sdk126 from "openclaw/plugin-sdk/lmstudio";
|
||||
import type * as sdk127 from "openclaw/plugin-sdk/lmstudio-runtime";
|
||||
import type * as sdk128 from "openclaw/plugin-sdk/logging-core";
|
||||
import type * as sdk129 from "openclaw/plugin-sdk/markdown-table-runtime";
|
||||
import type * as sdk130 from "openclaw/plugin-sdk/media-generation-runtime";
|
||||
import type * as sdk131 from "openclaw/plugin-sdk/media-generation-runtime-shared";
|
||||
import type * as sdk132 from "openclaw/plugin-sdk/media-mime";
|
||||
import type * as sdk133 from "openclaw/plugin-sdk/media-runtime";
|
||||
import type * as sdk134 from "openclaw/plugin-sdk/media-store";
|
||||
import type * as sdk135 from "openclaw/plugin-sdk/media-understanding";
|
||||
import type * as sdk136 from "openclaw/plugin-sdk/media-understanding-runtime";
|
||||
import type * as sdk137 from "openclaw/plugin-sdk/memory-core-engine-runtime";
|
||||
import type * as sdk138 from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import type * as sdk139 from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
|
||||
import type * as sdk140 from "openclaw/plugin-sdk/memory-core-host-engine-qmd";
|
||||
import type * as sdk141 from "openclaw/plugin-sdk/memory-core-host-engine-storage";
|
||||
import type * as sdk142 from "openclaw/plugin-sdk/memory-core-host-events";
|
||||
import type * as sdk143 from "openclaw/plugin-sdk/memory-core-host-multimodal";
|
||||
import type * as sdk144 from "openclaw/plugin-sdk/memory-core-host-query";
|
||||
import type * as sdk145 from "openclaw/plugin-sdk/memory-core-host-runtime-cli";
|
||||
import type * as sdk146 from "openclaw/plugin-sdk/memory-core-host-runtime-core";
|
||||
import type * as sdk147 from "openclaw/plugin-sdk/memory-core-host-runtime-files";
|
||||
import type * as sdk148 from "openclaw/plugin-sdk/memory-core-host-secret";
|
||||
import type * as sdk149 from "openclaw/plugin-sdk/memory-core-host-status";
|
||||
import type * as sdk150 from "openclaw/plugin-sdk/memory-host-core";
|
||||
import type * as sdk151 from "openclaw/plugin-sdk/memory-host-events";
|
||||
import type * as sdk152 from "openclaw/plugin-sdk/memory-host-files";
|
||||
import type * as sdk153 from "openclaw/plugin-sdk/memory-host-markdown";
|
||||
import type * as sdk154 from "openclaw/plugin-sdk/memory-host-search";
|
||||
import type * as sdk155 from "openclaw/plugin-sdk/memory-host-status";
|
||||
import type * as sdk156 from "openclaw/plugin-sdk/messaging-targets";
|
||||
import type * as sdk157 from "openclaw/plugin-sdk/migration";
|
||||
import type * as sdk158 from "openclaw/plugin-sdk/migration-runtime";
|
||||
import type * as sdk159 from "openclaw/plugin-sdk/model-session-runtime";
|
||||
import type * as sdk160 from "openclaw/plugin-sdk/models-provider-runtime";
|
||||
import type * as sdk161 from "openclaw/plugin-sdk/music-generation";
|
||||
import type * as sdk162 from "openclaw/plugin-sdk/music-generation-core";
|
||||
import type * as sdk163 from "openclaw/plugin-sdk/native-command-config-runtime";
|
||||
import type * as sdk164 from "openclaw/plugin-sdk/native-command-registry";
|
||||
import type * as sdk165 from "openclaw/plugin-sdk/number-runtime";
|
||||
import type * as sdk166 from "openclaw/plugin-sdk/outbound-media";
|
||||
import type * as sdk167 from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import type * as sdk168 from "openclaw/plugin-sdk/outbound-send-deps";
|
||||
import type * as sdk169 from "openclaw/plugin-sdk/param-readers";
|
||||
import type * as sdk170 from "openclaw/plugin-sdk/persistent-dedupe";
|
||||
import type * as sdk171 from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import type * as sdk172 from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type * as sdk173 from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import type * as sdk174 from "openclaw/plugin-sdk/plugin-test-api";
|
||||
import type * as sdk175 from "openclaw/plugin-sdk/plugin-test-contracts";
|
||||
import type * as sdk176 from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import type * as sdk177 from "openclaw/plugin-sdk/poll-runtime";
|
||||
import type * as sdk178 from "openclaw/plugin-sdk/process-runtime";
|
||||
import type * as sdk179 from "openclaw/plugin-sdk/provider-auth";
|
||||
import type * as sdk180 from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import type * as sdk181 from "openclaw/plugin-sdk/provider-auth-login";
|
||||
import type * as sdk182 from "openclaw/plugin-sdk/provider-auth-result";
|
||||
import type * as sdk183 from "openclaw/plugin-sdk/provider-auth-runtime";
|
||||
import type * as sdk184 from "openclaw/plugin-sdk/provider-catalog-runtime";
|
||||
import type * as sdk185 from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import type * as sdk186 from "openclaw/plugin-sdk/provider-entry";
|
||||
import type * as sdk187 from "openclaw/plugin-sdk/provider-env-vars";
|
||||
import type * as sdk188 from "openclaw/plugin-sdk/provider-http";
|
||||
import type * as sdk189 from "openclaw/plugin-sdk/provider-http-test-mocks";
|
||||
import type * as sdk190 from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import type * as sdk191 from "openclaw/plugin-sdk/provider-model-types";
|
||||
import type * as sdk192 from "openclaw/plugin-sdk/provider-onboard";
|
||||
import type * as sdk193 from "openclaw/plugin-sdk/provider-selection-runtime";
|
||||
import type * as sdk194 from "openclaw/plugin-sdk/provider-setup";
|
||||
import type * as sdk195 from "openclaw/plugin-sdk/provider-stream";
|
||||
import type * as sdk196 from "openclaw/plugin-sdk/provider-stream-family";
|
||||
import type * as sdk197 from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import type * as sdk198 from "openclaw/plugin-sdk/provider-test-contracts";
|
||||
import type * as sdk199 from "openclaw/plugin-sdk/provider-tools";
|
||||
import type * as sdk200 from "openclaw/plugin-sdk/provider-transport-runtime";
|
||||
import type * as sdk201 from "openclaw/plugin-sdk/provider-usage";
|
||||
import type * as sdk202 from "openclaw/plugin-sdk/provider-web-fetch";
|
||||
import type * as sdk203 from "openclaw/plugin-sdk/provider-web-fetch-contract";
|
||||
import type * as sdk204 from "openclaw/plugin-sdk/provider-web-search";
|
||||
import type * as sdk205 from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
import type * as sdk206 from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||
import type * as sdk207 from "openclaw/plugin-sdk/provider-zai-endpoint";
|
||||
import type * as sdk208 from "openclaw/plugin-sdk/proxy-capture";
|
||||
import type * as sdk209 from "openclaw/plugin-sdk/qa-runner-runtime";
|
||||
import type * as sdk210 from "openclaw/plugin-sdk/realtime-transcription";
|
||||
import type * as sdk211 from "openclaw/plugin-sdk/realtime-voice";
|
||||
import type * as sdk212 from "openclaw/plugin-sdk/reply-chunking";
|
||||
import type * as sdk213 from "openclaw/plugin-sdk/reply-dedupe";
|
||||
import type * as sdk214 from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import type * as sdk215 from "openclaw/plugin-sdk/reply-history";
|
||||
import type * as sdk216 from "openclaw/plugin-sdk/reply-payload";
|
||||
import type * as sdk217 from "openclaw/plugin-sdk/reply-reference";
|
||||
import type * as sdk218 from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type * as sdk219 from "openclaw/plugin-sdk/request-url";
|
||||
import type * as sdk220 from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import type * as sdk221 from "openclaw/plugin-sdk/retry-runtime";
|
||||
import type * as sdk222 from "openclaw/plugin-sdk/routing";
|
||||
import type * as sdk223 from "openclaw/plugin-sdk/run-command";
|
||||
import type * as sdk224 from "openclaw/plugin-sdk/runtime";
|
||||
import type * as sdk225 from "openclaw/plugin-sdk/runtime-config-snapshot";
|
||||
import type * as sdk226 from "openclaw/plugin-sdk/runtime-doctor";
|
||||
import type * as sdk227 from "openclaw/plugin-sdk/runtime-env";
|
||||
import type * as sdk228 from "openclaw/plugin-sdk/runtime-fetch";
|
||||
import type * as sdk229 from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import type * as sdk230 from "openclaw/plugin-sdk/runtime-logger";
|
||||
import type * as sdk231 from "openclaw/plugin-sdk/runtime-secret-resolution";
|
||||
import type * as sdk232 from "openclaw/plugin-sdk/runtime-store";
|
||||
import type * as sdk233 from "openclaw/plugin-sdk/sandbox";
|
||||
import type * as sdk234 from "openclaw/plugin-sdk/secret-file-runtime";
|
||||
import type * as sdk235 from "openclaw/plugin-sdk/secret-input";
|
||||
import type * as sdk236 from "openclaw/plugin-sdk/secret-input-runtime";
|
||||
import type * as sdk237 from "openclaw/plugin-sdk/secret-ref-runtime";
|
||||
import type * as sdk238 from "openclaw/plugin-sdk/secure-random-runtime";
|
||||
import type * as sdk239 from "openclaw/plugin-sdk/security-runtime";
|
||||
import type * as sdk240 from "openclaw/plugin-sdk/self-hosted-provider-setup";
|
||||
import type * as sdk241 from "openclaw/plugin-sdk/session-binding-runtime";
|
||||
import type * as sdk242 from "openclaw/plugin-sdk/session-key-runtime";
|
||||
import type * as sdk243 from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import type * as sdk244 from "openclaw/plugin-sdk/session-transcript-hit";
|
||||
import type * as sdk245 from "openclaw/plugin-sdk/session-visibility";
|
||||
import type * as sdk246 from "openclaw/plugin-sdk/setup";
|
||||
import type * as sdk247 from "openclaw/plugin-sdk/setup-adapter-runtime";
|
||||
import type * as sdk248 from "openclaw/plugin-sdk/setup-runtime";
|
||||
import type * as sdk249 from "openclaw/plugin-sdk/setup-tools";
|
||||
import type * as sdk250 from "openclaw/plugin-sdk/simple-completion-runtime";
|
||||
import type * as sdk251 from "openclaw/plugin-sdk/skill-commands-runtime";
|
||||
import type * as sdk252 from "openclaw/plugin-sdk/skills-runtime";
|
||||
import type * as sdk253 from "openclaw/plugin-sdk/speech";
|
||||
import type * as sdk254 from "openclaw/plugin-sdk/speech-core";
|
||||
import type * as sdk255 from "openclaw/plugin-sdk/ssrf-dispatcher";
|
||||
import type * as sdk256 from "openclaw/plugin-sdk/ssrf-policy";
|
||||
import type * as sdk257 from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import type * as sdk258 from "openclaw/plugin-sdk/state-paths";
|
||||
import type * as sdk259 from "openclaw/plugin-sdk/status-helpers";
|
||||
import type * as sdk260 from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type * as sdk261 from "openclaw/plugin-sdk/string-normalization-runtime";
|
||||
import type * as sdk262 from "openclaw/plugin-sdk/system-event-runtime";
|
||||
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";
|
||||
import type * as sdk9 from "openclaw/plugin-sdk/agent-config-primitives";
|
||||
import type * as sdk10 from "openclaw/plugin-sdk/agent-harness";
|
||||
import type * as sdk11 from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import type * as sdk12 from "openclaw/plugin-sdk/agent-media-payload";
|
||||
import type * as sdk13 from "openclaw/plugin-sdk/agent-runtime";
|
||||
import type * as sdk14 from "openclaw/plugin-sdk/allow-from";
|
||||
import type * as sdk15 from "openclaw/plugin-sdk/allowlist-config-edit";
|
||||
import type * as sdk16 from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import type * as sdk17 from "openclaw/plugin-sdk/approval-client-runtime";
|
||||
import type * as sdk18 from "openclaw/plugin-sdk/approval-delivery-runtime";
|
||||
import type * as sdk19 from "openclaw/plugin-sdk/approval-gateway-runtime";
|
||||
import type * as sdk20 from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
|
||||
import type * as sdk21 from "openclaw/plugin-sdk/approval-handler-runtime";
|
||||
import type * as sdk22 from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type * as sdk23 from "openclaw/plugin-sdk/approval-reply-runtime";
|
||||
import type * as sdk24 from "openclaw/plugin-sdk/approval-runtime";
|
||||
import type * as sdk25 from "openclaw/plugin-sdk/boolean-param";
|
||||
import type * as sdk26 from "openclaw/plugin-sdk/browser-config";
|
||||
import type * as sdk27 from "openclaw/plugin-sdk/channel-actions";
|
||||
import type * as sdk28 from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type * as sdk29 from "openclaw/plugin-sdk/channel-config-primitives";
|
||||
import type * as sdk30 from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import type * as sdk31 from "openclaw/plugin-sdk/channel-config-schema-legacy";
|
||||
import type * as sdk32 from "openclaw/plugin-sdk/channel-config-writes";
|
||||
import type * as sdk33 from "openclaw/plugin-sdk/channel-contract";
|
||||
import type * as sdk34 from "openclaw/plugin-sdk/channel-contract-testing";
|
||||
import type * as sdk35 from "openclaw/plugin-sdk/channel-core";
|
||||
import type * as sdk36 from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
import type * as sdk37 from "openclaw/plugin-sdk/channel-envelope";
|
||||
import type * as sdk38 from "openclaw/plugin-sdk/channel-feedback";
|
||||
import type * as sdk39 from "openclaw/plugin-sdk/channel-inbound";
|
||||
import type * as sdk40 from "openclaw/plugin-sdk/channel-inbound-debounce";
|
||||
import type * as sdk41 from "openclaw/plugin-sdk/channel-inbound-roots";
|
||||
import type * as sdk42 from "openclaw/plugin-sdk/channel-lifecycle";
|
||||
import type * as sdk43 from "openclaw/plugin-sdk/channel-location";
|
||||
import type * as sdk44 from "openclaw/plugin-sdk/channel-logging";
|
||||
import type * as sdk45 from "openclaw/plugin-sdk/channel-mention-gating";
|
||||
import type * as sdk46 from "openclaw/plugin-sdk/channel-pairing";
|
||||
import type * as sdk47 from "openclaw/plugin-sdk/channel-pairing-paths";
|
||||
import type * as sdk48 from "openclaw/plugin-sdk/channel-plugin-common";
|
||||
import type * as sdk49 from "openclaw/plugin-sdk/channel-policy";
|
||||
import type * as sdk50 from "openclaw/plugin-sdk/channel-reply-options-runtime";
|
||||
import type * as sdk51 from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
import type * as sdk52 from "openclaw/plugin-sdk/channel-runtime";
|
||||
import type * as sdk53 from "openclaw/plugin-sdk/channel-runtime-context";
|
||||
import type * as sdk54 from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
||||
import type * as sdk55 from "openclaw/plugin-sdk/channel-secret-runtime";
|
||||
import type * as sdk56 from "openclaw/plugin-sdk/channel-secret-tts-runtime";
|
||||
import type * as sdk57 from "openclaw/plugin-sdk/channel-send-result";
|
||||
import type * as sdk58 from "openclaw/plugin-sdk/channel-setup";
|
||||
import type * as sdk59 from "openclaw/plugin-sdk/channel-status";
|
||||
import type * as sdk60 from "openclaw/plugin-sdk/channel-streaming";
|
||||
import type * as sdk61 from "openclaw/plugin-sdk/channel-targets";
|
||||
import type * as sdk62 from "openclaw/plugin-sdk/cli-backend";
|
||||
import type * as sdk63 from "openclaw/plugin-sdk/cli-runtime";
|
||||
import type * as sdk64 from "openclaw/plugin-sdk/collection-runtime";
|
||||
import type * as sdk65 from "openclaw/plugin-sdk/command-auth";
|
||||
import type * as sdk66 from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type * as sdk67 from "openclaw/plugin-sdk/command-detection";
|
||||
import type * as sdk68 from "openclaw/plugin-sdk/command-gating";
|
||||
import type * as sdk69 from "openclaw/plugin-sdk/command-primitives-runtime";
|
||||
import type * as sdk70 from "openclaw/plugin-sdk/command-status";
|
||||
import type * as sdk71 from "openclaw/plugin-sdk/command-status-runtime";
|
||||
import type * as sdk72 from "openclaw/plugin-sdk/command-surface";
|
||||
import type * as sdk73 from "openclaw/plugin-sdk/compat";
|
||||
import type * as sdk74 from "openclaw/plugin-sdk/config-mutation";
|
||||
import type * as sdk75 from "openclaw/plugin-sdk/config-runtime";
|
||||
import type * as sdk76 from "openclaw/plugin-sdk/config-schema";
|
||||
import type * as sdk77 from "openclaw/plugin-sdk/config-types";
|
||||
import type * as sdk78 from "openclaw/plugin-sdk/context-visibility-runtime";
|
||||
import type * as sdk79 from "openclaw/plugin-sdk/conversation-binding-runtime";
|
||||
import type * as sdk80 from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import type * as sdk81 from "openclaw/plugin-sdk/core";
|
||||
import type * as sdk82 from "openclaw/plugin-sdk/cron-store-runtime";
|
||||
import type * as sdk83 from "openclaw/plugin-sdk/dangerous-name-runtime";
|
||||
import type * as sdk84 from "openclaw/plugin-sdk/device-bootstrap";
|
||||
import type * as sdk85 from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||
import type * as sdk86 from "openclaw/plugin-sdk/direct-dm";
|
||||
import type * as sdk87 from "openclaw/plugin-sdk/direct-dm-access";
|
||||
import type * as sdk88 from "openclaw/plugin-sdk/direct-dm-guard-policy";
|
||||
import type * as sdk89 from "openclaw/plugin-sdk/directory-config-runtime";
|
||||
import type * as sdk90 from "openclaw/plugin-sdk/directory-runtime";
|
||||
import type * as sdk91 from "openclaw/plugin-sdk/document-extractor";
|
||||
import type * as sdk92 from "openclaw/plugin-sdk/error-runtime";
|
||||
import type * as sdk93 from "openclaw/plugin-sdk/extension-shared";
|
||||
import type * as sdk94 from "openclaw/plugin-sdk/fetch-runtime";
|
||||
import type * as sdk95 from "openclaw/plugin-sdk/file-lock";
|
||||
import type * as sdk96 from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import type * as sdk97 from "openclaw/plugin-sdk/global-singleton";
|
||||
import type * as sdk98 from "openclaw/plugin-sdk/group-access";
|
||||
import type * as sdk99 from "openclaw/plugin-sdk/group-activation";
|
||||
import type * as sdk100 from "openclaw/plugin-sdk/hook-runtime";
|
||||
import type * as sdk101 from "openclaw/plugin-sdk/host-runtime";
|
||||
import type * as sdk102 from "openclaw/plugin-sdk/image-generation";
|
||||
import type * as sdk103 from "openclaw/plugin-sdk/image-generation-core";
|
||||
import type * as sdk104 from "openclaw/plugin-sdk/image-generation-runtime";
|
||||
import type * as sdk105 from "openclaw/plugin-sdk/inbound-envelope";
|
||||
import type * as sdk106 from "openclaw/plugin-sdk/inbound-reply-dispatch";
|
||||
import type * as sdk107 from "openclaw/plugin-sdk/infra-runtime";
|
||||
import type * as sdk108 from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import type * as sdk109 from "openclaw/plugin-sdk/json-store";
|
||||
import type * as sdk110 from "openclaw/plugin-sdk/keyed-async-queue";
|
||||
import type * as sdk111 from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import type * as sdk112 from "openclaw/plugin-sdk/lmstudio";
|
||||
import type * as sdk113 from "openclaw/plugin-sdk/lmstudio-runtime";
|
||||
import type * as sdk114 from "openclaw/plugin-sdk/logging-core";
|
||||
import type * as sdk115 from "openclaw/plugin-sdk/markdown-table-runtime";
|
||||
import type * as sdk116 from "openclaw/plugin-sdk/media-generation-runtime";
|
||||
import type * as sdk117 from "openclaw/plugin-sdk/media-generation-runtime-shared";
|
||||
import type * as sdk118 from "openclaw/plugin-sdk/media-mime";
|
||||
import type * as sdk119 from "openclaw/plugin-sdk/media-runtime";
|
||||
import type * as sdk120 from "openclaw/plugin-sdk/media-store";
|
||||
import type * as sdk121 from "openclaw/plugin-sdk/media-understanding";
|
||||
import type * as sdk122 from "openclaw/plugin-sdk/media-understanding-runtime";
|
||||
import type * as sdk123 from "openclaw/plugin-sdk/memory-core-engine-runtime";
|
||||
import type * as sdk124 from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import type * as sdk125 from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
|
||||
import type * as sdk126 from "openclaw/plugin-sdk/memory-core-host-engine-qmd";
|
||||
import type * as sdk127 from "openclaw/plugin-sdk/memory-core-host-engine-storage";
|
||||
import type * as sdk128 from "openclaw/plugin-sdk/memory-core-host-events";
|
||||
import type * as sdk129 from "openclaw/plugin-sdk/memory-core-host-multimodal";
|
||||
import type * as sdk130 from "openclaw/plugin-sdk/memory-core-host-query";
|
||||
import type * as sdk131 from "openclaw/plugin-sdk/memory-core-host-runtime-cli";
|
||||
import type * as sdk132 from "openclaw/plugin-sdk/memory-core-host-runtime-core";
|
||||
import type * as sdk133 from "openclaw/plugin-sdk/memory-core-host-runtime-files";
|
||||
import type * as sdk134 from "openclaw/plugin-sdk/memory-core-host-secret";
|
||||
import type * as sdk135 from "openclaw/plugin-sdk/memory-core-host-status";
|
||||
import type * as sdk136 from "openclaw/plugin-sdk/memory-host-core";
|
||||
import type * as sdk137 from "openclaw/plugin-sdk/memory-host-events";
|
||||
import type * as sdk138 from "openclaw/plugin-sdk/memory-host-files";
|
||||
import type * as sdk139 from "openclaw/plugin-sdk/memory-host-markdown";
|
||||
import type * as sdk140 from "openclaw/plugin-sdk/memory-host-search";
|
||||
import type * as sdk141 from "openclaw/plugin-sdk/memory-host-status";
|
||||
import type * as sdk142 from "openclaw/plugin-sdk/messaging-targets";
|
||||
import type * as sdk143 from "openclaw/plugin-sdk/migration";
|
||||
import type * as sdk144 from "openclaw/plugin-sdk/migration-runtime";
|
||||
import type * as sdk145 from "openclaw/plugin-sdk/model-session-runtime";
|
||||
import type * as sdk146 from "openclaw/plugin-sdk/models-provider-runtime";
|
||||
import type * as sdk147 from "openclaw/plugin-sdk/music-generation";
|
||||
import type * as sdk148 from "openclaw/plugin-sdk/music-generation-core";
|
||||
import type * as sdk149 from "openclaw/plugin-sdk/native-command-config-runtime";
|
||||
import type * as sdk150 from "openclaw/plugin-sdk/native-command-registry";
|
||||
import type * as sdk151 from "openclaw/plugin-sdk/outbound-media";
|
||||
import type * as sdk152 from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import type * as sdk153 from "openclaw/plugin-sdk/outbound-send-deps";
|
||||
import type * as sdk154 from "openclaw/plugin-sdk/param-readers";
|
||||
import type * as sdk155 from "openclaw/plugin-sdk/persistent-dedupe";
|
||||
import type * as sdk156 from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import type * as sdk157 from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type * as sdk158 from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import type * as sdk159 from "openclaw/plugin-sdk/poll-runtime";
|
||||
import type * as sdk160 from "openclaw/plugin-sdk/process-runtime";
|
||||
import type * as sdk161 from "openclaw/plugin-sdk/provider-auth";
|
||||
import type * as sdk162 from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import type * as sdk163 from "openclaw/plugin-sdk/provider-auth-login";
|
||||
import type * as sdk164 from "openclaw/plugin-sdk/provider-auth-result";
|
||||
import type * as sdk165 from "openclaw/plugin-sdk/provider-auth-runtime";
|
||||
import type * as sdk166 from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import type * as sdk167 from "openclaw/plugin-sdk/provider-entry";
|
||||
import type * as sdk168 from "openclaw/plugin-sdk/provider-env-vars";
|
||||
import type * as sdk169 from "openclaw/plugin-sdk/provider-http";
|
||||
import type * as sdk170 from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import type * as sdk171 from "openclaw/plugin-sdk/provider-model-types";
|
||||
import type * as sdk172 from "openclaw/plugin-sdk/provider-onboard";
|
||||
import type * as sdk173 from "openclaw/plugin-sdk/provider-selection-runtime";
|
||||
import type * as sdk174 from "openclaw/plugin-sdk/provider-setup";
|
||||
import type * as sdk175 from "openclaw/plugin-sdk/provider-stream";
|
||||
import type * as sdk176 from "openclaw/plugin-sdk/provider-stream-family";
|
||||
import type * as sdk177 from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import type * as sdk178 from "openclaw/plugin-sdk/provider-tools";
|
||||
import type * as sdk179 from "openclaw/plugin-sdk/provider-transport-runtime";
|
||||
import type * as sdk180 from "openclaw/plugin-sdk/provider-usage";
|
||||
import type * as sdk181 from "openclaw/plugin-sdk/provider-web-fetch";
|
||||
import type * as sdk182 from "openclaw/plugin-sdk/provider-web-fetch-contract";
|
||||
import type * as sdk183 from "openclaw/plugin-sdk/provider-web-search";
|
||||
import type * as sdk184 from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
import type * as sdk185 from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||
import type * as sdk186 from "openclaw/plugin-sdk/provider-zai-endpoint";
|
||||
import type * as sdk187 from "openclaw/plugin-sdk/proxy-capture";
|
||||
import type * as sdk188 from "openclaw/plugin-sdk/qa-runner-runtime";
|
||||
import type * as sdk189 from "openclaw/plugin-sdk/realtime-transcription";
|
||||
import type * as sdk190 from "openclaw/plugin-sdk/realtime-voice";
|
||||
import type * as sdk191 from "openclaw/plugin-sdk/reply-chunking";
|
||||
import type * as sdk192 from "openclaw/plugin-sdk/reply-dedupe";
|
||||
import type * as sdk193 from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import type * as sdk194 from "openclaw/plugin-sdk/reply-history";
|
||||
import type * as sdk195 from "openclaw/plugin-sdk/reply-payload";
|
||||
import type * as sdk196 from "openclaw/plugin-sdk/reply-reference";
|
||||
import type * as sdk197 from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type * as sdk198 from "openclaw/plugin-sdk/request-url";
|
||||
import type * as sdk199 from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import type * as sdk200 from "openclaw/plugin-sdk/retry-runtime";
|
||||
import type * as sdk201 from "openclaw/plugin-sdk/routing";
|
||||
import type * as sdk202 from "openclaw/plugin-sdk/run-command";
|
||||
import type * as sdk203 from "openclaw/plugin-sdk/runtime";
|
||||
import type * as sdk204 from "openclaw/plugin-sdk/runtime-config-snapshot";
|
||||
import type * as sdk205 from "openclaw/plugin-sdk/runtime-doctor";
|
||||
import type * as sdk206 from "openclaw/plugin-sdk/runtime-env";
|
||||
import type * as sdk207 from "openclaw/plugin-sdk/runtime-fetch";
|
||||
import type * as sdk208 from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import type * as sdk209 from "openclaw/plugin-sdk/runtime-logger";
|
||||
import type * as sdk210 from "openclaw/plugin-sdk/runtime-secret-resolution";
|
||||
import type * as sdk211 from "openclaw/plugin-sdk/runtime-store";
|
||||
import type * as sdk212 from "openclaw/plugin-sdk/sandbox";
|
||||
import type * as sdk213 from "openclaw/plugin-sdk/secret-file-runtime";
|
||||
import type * as sdk214 from "openclaw/plugin-sdk/secret-input";
|
||||
import type * as sdk215 from "openclaw/plugin-sdk/secret-input-runtime";
|
||||
import type * as sdk216 from "openclaw/plugin-sdk/secret-ref-runtime";
|
||||
import type * as sdk217 from "openclaw/plugin-sdk/security-runtime";
|
||||
import type * as sdk218 from "openclaw/plugin-sdk/self-hosted-provider-setup";
|
||||
import type * as sdk219 from "openclaw/plugin-sdk/session-binding-runtime";
|
||||
import type * as sdk220 from "openclaw/plugin-sdk/session-key-runtime";
|
||||
import type * as sdk221 from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import type * as sdk222 from "openclaw/plugin-sdk/session-transcript-hit";
|
||||
import type * as sdk223 from "openclaw/plugin-sdk/session-visibility";
|
||||
import type * as sdk224 from "openclaw/plugin-sdk/setup";
|
||||
import type * as sdk225 from "openclaw/plugin-sdk/setup-adapter-runtime";
|
||||
import type * as sdk226 from "openclaw/plugin-sdk/setup-runtime";
|
||||
import type * as sdk227 from "openclaw/plugin-sdk/setup-tools";
|
||||
import type * as sdk228 from "openclaw/plugin-sdk/simple-completion-runtime";
|
||||
import type * as sdk229 from "openclaw/plugin-sdk/skill-commands-runtime";
|
||||
import type * as sdk230 from "openclaw/plugin-sdk/skills-runtime";
|
||||
import type * as sdk231 from "openclaw/plugin-sdk/speech";
|
||||
import type * as sdk232 from "openclaw/plugin-sdk/speech-core";
|
||||
import type * as sdk233 from "openclaw/plugin-sdk/ssrf-dispatcher";
|
||||
import type * as sdk234 from "openclaw/plugin-sdk/ssrf-policy";
|
||||
import type * as sdk235 from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import type * as sdk236 from "openclaw/plugin-sdk/state-paths";
|
||||
import type * as sdk237 from "openclaw/plugin-sdk/status-helpers";
|
||||
import type * as sdk238 from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type * as sdk239 from "openclaw/plugin-sdk/string-normalization-runtime";
|
||||
import type * as sdk240 from "openclaw/plugin-sdk/talk-config-runtime";
|
||||
import type * as sdk241 from "openclaw/plugin-sdk/target-resolver-runtime";
|
||||
import type * as sdk242 from "openclaw/plugin-sdk/telegram-command-config";
|
||||
import type * as sdk243 from "openclaw/plugin-sdk/temp-path";
|
||||
import type * as sdk244 from "openclaw/plugin-sdk/testing";
|
||||
import type * as sdk245 from "openclaw/plugin-sdk/text-autolink-runtime";
|
||||
import type * as sdk246 from "openclaw/plugin-sdk/text-chunking";
|
||||
import type * as sdk247 from "openclaw/plugin-sdk/text-runtime";
|
||||
import type * as sdk248 from "openclaw/plugin-sdk/thread-bindings-runtime";
|
||||
import type * as sdk249 from "openclaw/plugin-sdk/thread-bindings-session-runtime";
|
||||
import type * as sdk250 from "openclaw/plugin-sdk/tool-payload";
|
||||
import type * as sdk251 from "openclaw/plugin-sdk/tool-send";
|
||||
import type * as sdk252 from "openclaw/plugin-sdk/tts-runtime";
|
||||
import type * as sdk253 from "openclaw/plugin-sdk/video-generation";
|
||||
import type * as sdk254 from "openclaw/plugin-sdk/video-generation-core";
|
||||
import type * as sdk255 from "openclaw/plugin-sdk/video-generation-runtime";
|
||||
import type * as sdk256 from "openclaw/plugin-sdk/web-content-extractor";
|
||||
import type * as sdk257 from "openclaw/plugin-sdk/web-media";
|
||||
import type * as sdk258 from "openclaw/plugin-sdk/webhook-ingress";
|
||||
import type * as sdk259 from "openclaw/plugin-sdk/webhook-path";
|
||||
import type * as sdk260 from "openclaw/plugin-sdk/webhook-request-guards";
|
||||
import type * as sdk261 from "openclaw/plugin-sdk/webhook-targets";
|
||||
import type * as sdk262 from "openclaw/plugin-sdk/windows-spawn";
|
||||
import type * as sdk263 from "openclaw/plugin-sdk/zod";
|
||||
|
||||
export type KitchenSinkSdkImportSurface =
|
||||
| typeof sdk0
|
||||
@ -557,33 +528,4 @@ export type KitchenSinkSdkImportSurface =
|
||||
| typeof sdk260
|
||||
| typeof sdk261
|
||||
| typeof sdk262
|
||||
| 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;
|
||||
| typeof sdk263;
|
||||
|
||||
21
src/index.js
21
src/index.js
@ -1,29 +1,16 @@
|
||||
import { PLUGIN_ID } from "./constants.js";
|
||||
import { registerAllHooks } from "./generated-hooks.js";
|
||||
import { registerAllRegistrars } from "./generated-registrars.js";
|
||||
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
|
||||
import {
|
||||
KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
resolveKitchenSinkPersonality,
|
||||
} from "./personality.js";
|
||||
|
||||
export const plugin = {
|
||||
id: PLUGIN_ID,
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
name: "OpenClaw Kitchen Sink",
|
||||
version: "0.2.5",
|
||||
version: "0.2.1",
|
||||
description: "Credential-free fixture covering OpenClaw plugin API seams.",
|
||||
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
|
||||
register(api) {
|
||||
const personality = resolveKitchenSinkPersonality(api);
|
||||
registerAllHooks(api);
|
||||
if (personality !== "conformance") {
|
||||
registerAllRegistrars(api);
|
||||
}
|
||||
if (personality !== "adversarial") {
|
||||
registerKitchenSinkRuntime(api, {
|
||||
includeAgentToolResultMiddleware: personality !== "conformance",
|
||||
});
|
||||
}
|
||||
registerAllRegistrars(api);
|
||||
registerKitchenSinkRuntime(api);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -1,51 +1,58 @@
|
||||
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 {
|
||||
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,
|
||||
createKitchenSinkImageAsset,
|
||||
createKitchenSpeechAsset,
|
||||
createKitchenTextStream,
|
||||
createKitchenTranscription,
|
||||
createKitchenVideoResult,
|
||||
extractInteractiveText,
|
||||
kitchenChannelAccount,
|
||||
kitchenImageDescription,
|
||||
kitchenPromptGuidance,
|
||||
kitchenSearchSchema,
|
||||
kitchenTextModelDefinition,
|
||||
kitchenTextProviderConfig,
|
||||
kitchenToolSchema,
|
||||
normalizeKitchenTarget,
|
||||
readPrompt,
|
||||
readQuery,
|
||||
readUrl,
|
||||
runKitchenCommand,
|
||||
runKitchenFetch,
|
||||
runKitchenImageTool,
|
||||
runKitchenSearch,
|
||||
shouldHandleKitchenText,
|
||||
stripDataUrl,
|
||||
} from "./scenarios.js";
|
||||
|
||||
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 = {}) {
|
||||
const runtime = createKitchenSinkRuntime(options);
|
||||
const includeAgentToolResultMiddleware = options.includeAgentToolResultMiddleware !== false;
|
||||
|
||||
optionalRegister(api, "registerCommand", () => api.registerCommand(buildKitchenCommand(runtime)));
|
||||
optionalRegister(api, "registerCommand", () => api.registerCommand(buildKitchenSinkCommand(runtime)));
|
||||
@ -94,13 +101,11 @@ export function registerKitchenSinkRuntime(api, options = {}) {
|
||||
optionalRegister(api, "registerCompactionProvider", () =>
|
||||
api.registerCompactionProvider(buildKitchenCompactionProvider()),
|
||||
);
|
||||
if (includeAgentToolResultMiddleware) {
|
||||
optionalRegister(api, "registerAgentToolResultMiddleware", () =>
|
||||
api.registerAgentToolResultMiddleware(buildKitchenToolResultMiddleware(), {
|
||||
runtimes: ["pi", "codex", "cli"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
optionalRegister(api, "registerAgentToolResultMiddleware", () =>
|
||||
api.registerAgentToolResultMiddleware(buildKitchenToolResultMiddleware(), {
|
||||
runtimes: ["pi", "codex", "cli"],
|
||||
}),
|
||||
);
|
||||
optionalRegister(api, "registerService", () => api.registerService(buildKitchenService()));
|
||||
optionalRegister(api, "registerHttpRoute", () => api.registerHttpRoute(buildKitchenHttpRoute()));
|
||||
optionalRegister(api, "registerGatewayMethod", () =>
|
||||
@ -118,9 +123,767 @@ export function createKitchenSinkRuntime(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) {
|
||||
// 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") {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
export const KITCHEN_SINK_PERSONALITIES = ["full", "conformance", "adversarial"];
|
||||
|
||||
export const DEFAULT_KITCHEN_SINK_PERSONALITY = "full";
|
||||
|
||||
export const KITCHEN_SINK_EXPECTED_DIAGNOSTICS = {
|
||||
full: [
|
||||
"only bundled plugins can register agent tool result middleware",
|
||||
"agent event subscription registration requires id and handle",
|
||||
'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods',
|
||||
'channel "kitchen-sink-channel-probe" registration missing required config helpers',
|
||||
"cli registration missing explicit commands metadata",
|
||||
"only bundled plugins can register Codex app-server extension factories",
|
||||
'compaction provider "kitchen-sink-compaction-provider" registration missing summarize',
|
||||
"context engine registration missing id",
|
||||
"control UI descriptor registration requires id, surface, label, and valid optional fields",
|
||||
"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",
|
||||
"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: [],
|
||||
adversarial: [
|
||||
"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',
|
||||
'channel "kitchen-sink-channel-probe" registration missing required config helpers',
|
||||
"cli registration missing explicit commands metadata",
|
||||
"only bundled plugins can register Codex app-server extension factories",
|
||||
'compaction provider "kitchen-sink-compaction-provider" registration missing summarize',
|
||||
"context engine registration missing id",
|
||||
"control UI descriptor registration requires id, surface, label, and valid optional fields",
|
||||
"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",
|
||||
"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",
|
||||
],
|
||||
};
|
||||
|
||||
export function resolveKitchenSinkPersonality(api) {
|
||||
const configured = api?.config?.personality || process.env.OPENCLAW_KITCHEN_SINK_PERSONALITY;
|
||||
return KITCHEN_SINK_PERSONALITIES.includes(configured) ? configured : DEFAULT_KITCHEN_SINK_PERSONALITY;
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
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.",
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
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)),
|
||||
};
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@ -1,431 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
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;
|
||||
}
|
||||
606
src/scenarios.js
606
src/scenarios.js
@ -1,120 +1,50 @@
|
||||
import { createHash } from "node:crypto";
|
||||
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";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
export {
|
||||
CHANNEL_ACCOUNT_ID,
|
||||
CHANNEL_ID,
|
||||
COMPACTION_PROVIDER_ID,
|
||||
DEFAULT_EMBEDDING_MODEL,
|
||||
DEFAULT_IMAGE_DELAY_MS,
|
||||
DEFAULT_IMAGE_MODEL,
|
||||
DEFAULT_MEDIA_MODEL,
|
||||
DEFAULT_MUSIC_MODEL,
|
||||
DEFAULT_SPEECH_MODEL,
|
||||
DEFAULT_TEXT_MODEL,
|
||||
DEFAULT_VIDEO_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";
|
||||
export { createKitchenSinkImageAsset } from "./fixtures/images.js";
|
||||
export {
|
||||
createKitchenTextStream,
|
||||
kitchenImageDescription,
|
||||
kitchenTextModelDefinition,
|
||||
kitchenTextProviderConfig,
|
||||
kitchenTextResponse,
|
||||
} from "./fixtures/text.js";
|
||||
|
||||
// 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 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;
|
||||
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: "dry.prefix-image",
|
||||
prompt: "kitchen generate an image of the office sink fixture",
|
||||
mode: "dry",
|
||||
route: "prefix:kitchen",
|
||||
surfaces: ["command", "image-provider", "asset"],
|
||||
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",
|
||||
},
|
||||
{
|
||||
id: "live.openai-text-kitchen-image",
|
||||
prompt: "Generate an image with Kitchen Sink while OpenAI handles the text turn.",
|
||||
mode: "live-llm-compatible",
|
||||
route: "human:live-llm-image-provider",
|
||||
surfaces: ["text-provider-guidance", "image-provider", "tool-routing"],
|
||||
},
|
||||
{
|
||||
id: "search.fetch.summarize",
|
||||
prompt: "Search for Kitchen Sink provider routing and fetch the fixture README.",
|
||||
mode: "dry",
|
||||
route: "human:search-fetch-summary",
|
||||
surfaces: ["web-search", "web-fetch", "text-provider"],
|
||||
},
|
||||
{
|
||||
id: "channel.prefix-image",
|
||||
prompt: "kitchen generate an image in this channel",
|
||||
mode: "dry",
|
||||
route: "human:channel-prefix",
|
||||
surfaces: ["channel", "interactive-handler", "image-provider"],
|
||||
},
|
||||
{
|
||||
id: "hook.block-tool",
|
||||
prompt: "kitchen block image generation until the operator reviews it",
|
||||
mode: "dry",
|
||||
route: "human:hook-block",
|
||||
surfaces: ["before_tool_call", "terminal-block"],
|
||||
},
|
||||
{
|
||||
id: "memory.compact-fixture",
|
||||
prompt: "Remember the Kitchen Sink image job and compact this session.",
|
||||
mode: "dry",
|
||||
route: "human:memory-compaction",
|
||||
surfaces: ["memory-embedding", "memory-corpus", "compaction"],
|
||||
},
|
||||
]);
|
||||
];
|
||||
|
||||
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 = {
|
||||
delayMs: normalizeDelayMs(options.delayMs),
|
||||
sleep: typeof options.sleep === "function" ? options.sleep : defaultSleep,
|
||||
@ -141,103 +71,7 @@ export function createKitchenScenarioRuntime(options = {}) {
|
||||
return runtime;
|
||||
}
|
||||
|
||||
export function listKitchenHumanScenarios() {
|
||||
return KITCHEN_HUMAN_SCENARIOS.map((scenario) => ({ ...scenario, surfaces: [...scenario.surfaces] }));
|
||||
}
|
||||
|
||||
export async function runKitchenHumanScenario(runtime, idOrPrompt) {
|
||||
const scenario = resolveKitchenHumanScenario(idOrPrompt);
|
||||
if (scenario.id === "dry.prefix-image") {
|
||||
return {
|
||||
...scenario,
|
||||
result: await runKitchenCommand(runtime, scenario.prompt.replace(/^kitchen\s+/i, "")),
|
||||
};
|
||||
}
|
||||
if (scenario.id === "live.openai-text-kitchen-image") {
|
||||
return {
|
||||
...scenario,
|
||||
guidance: kitchenPromptGuidance(),
|
||||
result: await runtime.runScenario({
|
||||
scenario: "image.generate",
|
||||
prompt: scenario.prompt,
|
||||
route: scenario.route,
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (scenario.id === "search.fetch.summarize") {
|
||||
const search = await runtime.runScenario({
|
||||
scenario: "web.search",
|
||||
prompt: scenario.prompt,
|
||||
route: scenario.route,
|
||||
});
|
||||
const fetch = await runtime.runScenario({
|
||||
scenario: "web.fetch",
|
||||
url: "kitchen://fixture/readme",
|
||||
route: scenario.route,
|
||||
});
|
||||
return {
|
||||
...scenario,
|
||||
result: {
|
||||
search,
|
||||
fetch,
|
||||
summary: kitchenTextResponse(`${search.answer} ${fetch.title}`),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (scenario.id === "channel.prefix-image") {
|
||||
const command = await runKitchenCommand(runtime, scenario.prompt.replace(/^kitchen\s+/i, ""));
|
||||
return {
|
||||
...scenario,
|
||||
result: {
|
||||
command,
|
||||
delivery: createKitchenChannelDelivery({
|
||||
kind: "media",
|
||||
text: scenario.prompt,
|
||||
to: "kitchen demo",
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (scenario.id === "hook.block-tool") {
|
||||
return {
|
||||
...scenario,
|
||||
result: observeKitchenHook(
|
||||
"before_tool_call",
|
||||
{ toolId: "kitchen_sink_image_job", args: { prompt: scenario.prompt } },
|
||||
{ providerId: IMAGE_PROVIDER_ID },
|
||||
),
|
||||
};
|
||||
}
|
||||
if (scenario.id === "memory.compact-fixture") {
|
||||
const memory = createKitchenMemorySearch(scenario.prompt);
|
||||
const compaction = createKitchenCompaction({
|
||||
messages: [
|
||||
{ role: "user", content: scenario.prompt },
|
||||
{ role: "assistant", content: "Kitchen Sink image job ks_image_1f8a5a98 completed." },
|
||||
],
|
||||
});
|
||||
return {
|
||||
...scenario,
|
||||
result: {
|
||||
embedding: createKitchenEmbedding(scenario.prompt),
|
||||
memory,
|
||||
compaction,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...scenario,
|
||||
result: await runtime.runScenario({
|
||||
scenario: "text.reply",
|
||||
prompt: scenario.prompt,
|
||||
route: scenario.route,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
if (scenario === "image.generate") {
|
||||
const prompt = normalizePrompt(request.prompt, "a kitchen sink fixture image");
|
||||
@ -329,6 +163,37 @@ 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) {
|
||||
return /^kitchen(?:\s|$)/i.test(String(text ?? "").trim());
|
||||
}
|
||||
@ -352,7 +217,7 @@ export function createKitchenChannelDelivery({ kind = "text", text = "", to = "k
|
||||
messageId: id,
|
||||
conversationId: normalizedTo,
|
||||
channelId: normalizedTo,
|
||||
timestamp: Date.UTC(2026, 3, 28, 0, 0, 0),
|
||||
timestamp: Date.now(),
|
||||
deliveryStatus: "sent",
|
||||
transport: "kitchen-sink-local",
|
||||
meta: {
|
||||
@ -730,6 +595,92 @@ 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) {
|
||||
return {
|
||||
type: "object",
|
||||
@ -796,8 +747,6 @@ export function extractInteractiveText(ctx) {
|
||||
}
|
||||
|
||||
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"]) ||
|
||||
firstHookString(event?.tool, ["id", "name"]);
|
||||
const providerId = firstHookString(event, ["providerId", "provider", "selectedProvider"]) ||
|
||||
@ -805,7 +754,8 @@ export function observeKitchenHook(name, event, context) {
|
||||
const url = firstHookString(event, ["url"]) || firstHookString(event?.args, ["url"]);
|
||||
const text = extractHookText(event) || extractHookText(context);
|
||||
const scenarioId = inferKitchenScenario({ providerId, text, toolId, url });
|
||||
const observation = {
|
||||
|
||||
return {
|
||||
kitchenSink: true,
|
||||
pluginId: PLUGIN_ID,
|
||||
hook: name,
|
||||
@ -815,22 +765,6 @@ export function observeKitchenHook(name, event, context) {
|
||||
observedEventKeys: Object.keys(event ?? {}),
|
||||
observedContextKeys: Object.keys(context ?? {}),
|
||||
};
|
||||
|
||||
if (name === "before_tool_call") {
|
||||
return {
|
||||
...observation,
|
||||
...createBeforeToolCallDecision({ event, scenarioId, text, toolId }),
|
||||
};
|
||||
}
|
||||
|
||||
if (name === "llm_input" || name === "llm_output" || name === "agent_end") {
|
||||
return {
|
||||
...observation,
|
||||
privacy: createConversationPrivacyProbe({ event, context, text }),
|
||||
};
|
||||
}
|
||||
|
||||
return observation;
|
||||
}
|
||||
|
||||
function createKitchenJob(kind, prompt, date, delayMs, scenarioId, route) {
|
||||
@ -870,6 +804,111 @@ 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) {
|
||||
const text = String(prompt ?? "").toLowerCase();
|
||||
if (/\brate[ -]?limit|429|too many requests\b/.test(text)) {
|
||||
@ -901,6 +940,10 @@ function classifyKitchenFailure(prompt) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function selectKitchenImageFixture(_prompt) {
|
||||
return KITCHEN_IMAGE_FIXTURES[0];
|
||||
}
|
||||
|
||||
function mediaJob(kind, id, prompt, scenarioId) {
|
||||
const createdAt = "2026-04-28T00:00:00.000Z";
|
||||
return {
|
||||
@ -990,31 +1033,6 @@ function readString(input, key) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function resolveKitchenHumanScenario(idOrPrompt) {
|
||||
const text = String(idOrPrompt ?? "").trim();
|
||||
const exact = KITCHEN_HUMAN_SCENARIOS.find((scenario) => scenario.id === text);
|
||||
if (exact) {
|
||||
return exact;
|
||||
}
|
||||
const normalized = text.toLowerCase();
|
||||
if (/\bopenai\b/.test(normalized) && /\bimage\b/.test(normalized)) {
|
||||
return KITCHEN_HUMAN_SCENARIOS.find((scenario) => scenario.id === "live.openai-text-kitchen-image");
|
||||
}
|
||||
if (/\b(search|fetch|lookup|web)\b/.test(normalized)) {
|
||||
return KITCHEN_HUMAN_SCENARIOS.find((scenario) => scenario.id === "search.fetch.summarize");
|
||||
}
|
||||
if (/\bchannel|chat\b/.test(normalized)) {
|
||||
return KITCHEN_HUMAN_SCENARIOS.find((scenario) => scenario.id === "channel.prefix-image");
|
||||
}
|
||||
if (/\bblock|deny|approval\b/.test(normalized)) {
|
||||
return KITCHEN_HUMAN_SCENARIOS.find((scenario) => scenario.id === "hook.block-tool");
|
||||
}
|
||||
if (/\b(memory|compact|remember)\b/.test(normalized)) {
|
||||
return KITCHEN_HUMAN_SCENARIOS.find((scenario) => scenario.id === "memory.compact-fixture");
|
||||
}
|
||||
return KITCHEN_HUMAN_SCENARIOS.find((scenario) => scenario.id === "dry.prefix-image");
|
||||
}
|
||||
|
||||
function inferKitchenScenario({ providerId, text, toolId, url }) {
|
||||
const haystack = [providerId, text, toolId, url].filter(Boolean).join(" ").toLowerCase();
|
||||
if (toolId === "kitchen_sink_image_job" || providerId === IMAGE_PROVIDER_ID) {
|
||||
@ -1055,90 +1073,6 @@ function extractHookText(value) {
|
||||
);
|
||||
}
|
||||
|
||||
function createBeforeToolCallDecision({ event, scenarioId, text, toolId }) {
|
||||
const params = createToolCallParams(event, scenarioId);
|
||||
const lowerText = String(text ?? "").toLowerCase();
|
||||
if (/\b(block|deny|forbid)\b/.test(lowerText)) {
|
||||
return {
|
||||
params,
|
||||
block: true,
|
||||
blockReason: `Kitchen Sink fixture blocked ${toolId || "tool"} for ${scenarioId}.`,
|
||||
terminal: true,
|
||||
decision: "block",
|
||||
};
|
||||
}
|
||||
if (/\b(approval|approve|permission)\b/.test(lowerText)) {
|
||||
const approvalId = `ks_approval_${stableHash(`${toolId}:${text}:${scenarioId}`).slice(0, 10)}`;
|
||||
return {
|
||||
params,
|
||||
requireApproval: {
|
||||
id: approvalId,
|
||||
title: "Kitchen Sink tool approval",
|
||||
reason: `Kitchen Sink fixture requires approval before ${toolId || "tool"} runs.`,
|
||||
summary: `Approve deterministic ${scenarioId} fixture execution.`,
|
||||
scenarioId,
|
||||
pluginId: PLUGIN_ID,
|
||||
},
|
||||
decision: "approval",
|
||||
};
|
||||
}
|
||||
return {
|
||||
params,
|
||||
decision: scenarioId === "observe" ? "observe" : "allow",
|
||||
};
|
||||
}
|
||||
|
||||
function createToolCallParams(event, scenarioId) {
|
||||
const rawParams = event?.params && typeof event.params === "object" ? event.params : {};
|
||||
const rawArgs = event?.args && typeof event.args === "object" ? event.args : {};
|
||||
return {
|
||||
...rawParams,
|
||||
args: {
|
||||
...rawArgs,
|
||||
kitchenSinkScenario: scenarioId,
|
||||
kitchenSinkPluginId: PLUGIN_ID,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createConversationPrivacyProbe({ event, context, text }) {
|
||||
const eventText = extractHookText(event);
|
||||
const contextText = extractHookText(context);
|
||||
const combined = [text, eventText, contextText].filter(Boolean).join("\n");
|
||||
const redactedFields = [];
|
||||
for (const [label, value] of [
|
||||
["event.apiKey", event?.apiKey],
|
||||
["event.authorization", event?.authorization],
|
||||
["event.token", event?.token],
|
||||
["context.apiKey", context?.apiKey],
|
||||
["context.authorization", context?.authorization],
|
||||
["context.token", context?.token],
|
||||
]) {
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
redactedFields.push(label);
|
||||
}
|
||||
}
|
||||
const secretText = [
|
||||
combined,
|
||||
event?.apiKey,
|
||||
event?.authorization,
|
||||
event?.token,
|
||||
context?.apiKey,
|
||||
context?.authorization,
|
||||
context?.token,
|
||||
].filter(Boolean).join("\n");
|
||||
const secretPatternHits = secretText.match(/\b(?:sk-[a-z0-9_-]+|api[_-]?key|authorization|bearer\s+[a-z0-9._-]+|fixture-token-[a-z0-9_-]+)\b/gi) ?? [];
|
||||
return {
|
||||
boundary: "conversation-observer",
|
||||
promptHash: stableHash(combined || "empty"),
|
||||
promptLength: combined.length,
|
||||
redactedFields,
|
||||
secretPatternCount: secretPatternHits.length,
|
||||
storesRawPayload: false,
|
||||
exposesRawPayload: false,
|
||||
};
|
||||
}
|
||||
|
||||
function firstHookString(source, keys) {
|
||||
if (!source || typeof source !== "object") {
|
||||
return "";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user