Compare commits

...

61 Commits

Author SHA1 Message Date
github-actions[bot]
088bb4bee4
Update OpenClaw fixture dependencies
Some checks failed
Check / check (push) Has been cancelled
Check / clawhub-dry-run (push) Has been cancelled
Automated update of openclaw@2026.5.7, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-07 22:14:49 +00:00
github-actions[bot]
95ec036d85
Update OpenClaw fixture dependencies
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
Automated update of openclaw@2026.5.6, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-06 17:40:47 +00:00
github-actions[bot]
c84b7aa983
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.5, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-06 09:38:55 +00:00
github-actions[bot]
849fe726ea
Update OpenClaw fixture dependencies
Some checks failed
Check / check (push) Has been cancelled
Check / clawhub-dry-run (push) Has been cancelled
Automated update of openclaw@2026.5.4, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-05 08:19:22 +00:00
github-actions[bot]
f57a0fc7c4
Update OpenClaw fixture dependencies
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
Automated update of openclaw@2026.5.3-1, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-04 10:00:26 +00:00
github-actions[bot]
fbae1bed22
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.3, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-04 08:10:01 +00:00
github-actions[bot]
7657ba8249
Update OpenClaw fixture dependencies
Some checks are pending
Check / clawhub-dry-run (push) Waiting to run
Check / check (push) Waiting to run
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.10, and the generated kitchen-sink API surface.
2026-05-03 08:20:18 +00:00
github-actions[bot]
67ca4a3370
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.8, and the generated kitchen-sink API surface.
2026-05-03 07:32:12 +00:00
github-actions[bot]
c023ff6f85
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.7, and the generated kitchen-sink API surface.
2026-05-03 05:46:15 +00:00
Vincent Koc
6995e72caa
chore(deps): update plugin inspector 2026-05-02 19:10:17 -07:00
Vincent Koc
bc26de1a38
fix: pin safe uuid resolution 2026-05-02 18:50:31 -07:00
Vincent Koc
7f92e87bfc
fix: publish valid install metadata (#15) 2026-05-02 18:22:01 -07:00
github-actions[bot]
02370e2338
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.5.2, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-05-03 01:03:37 +00:00
Vincent Koc
2861f04237
chore(release): bump kitchen sink to 0.2.4 2026-05-02 14:40:34 -07:00
Vincent Koc
768be7da53
chore(release): bump kitchen sink to 0.2.3 2026-05-02 14:21:16 -07:00
Vincent Koc
89449910b4
fix(package): restore kitchen sink install artifacts
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
2026-05-02 08:40:15 -07:00
github-actions[bot]
87d135b61f
Update OpenClaw fixture dependencies
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
Automated update of openclaw@2026.4.29, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-04-30 21:04:52 +00:00
Vincent Koc
02421e56a6
fix(security): override anthropic sdk patched version
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
2026-04-29 16:19:27 -07:00
github-actions[bot]
4bd96b6f2d
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.4.27, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-04-29 22:41:07 +00:00
Vincent Koc
c5ffa2a3c0
docs(code): add kitchen sink wayfinding comments 2026-04-29 14:45:09 -07:00
Vincent Koc
d1daa6e3de
docs(dx): document kitchen sink source layout 2026-04-29 14:29:08 -07:00
Vincent Koc
6a2cf98404
refactor(fixtures): move image and text mocks 2026-04-29 14:29:08 -07:00
Vincent Koc
ba65248dc2
refactor(core): centralize kitchen sink constants 2026-04-29 14:29:08 -07:00
Vincent Koc
da085688fd
refactor(tests): share kitchen sink harness 2026-04-29 14:29:07 -07:00
Vincent Koc
dcf61bc18c
refactor(runtime): split kitchen sink registration builders 2026-04-29 14:29:07 -07:00
github-actions[bot]
f254b77d71
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.4.26, @openclaw/plugin-inspector@0.3.5, and the generated kitchen-sink API surface.
2026-04-29 21:11:35 +00:00
Vincent Koc
f24c6aea64
chore: release kitchen sink 0.2.2 2026-04-29 13:45:26 -07:00
Vincent Koc
dec072719e
feat: split kitchen sink test personalities
Split Kitchen Sink into full, conformance, and adversarial test personalities with expected diagnostics metadata.
2026-04-29 13:40:32 -07:00
Vincent Koc
e9bccb6ad1
fix: make channel fixture timestamps deterministic 2026-04-29 13:37:23 -07:00
Vincent Koc
e98ab47d52
feat: add realistic kitchen scenarios 2026-04-29 13:33:29 -07:00
Vincent Koc
1e6474223f
test: smoke installed npm package
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
2026-04-29 13:29:10 -07:00
Vincent Koc
7ca1d2c53e
test: add kitchen sink contract probes 2026-04-29 13:24:02 -07:00
Vincent Koc
dcfec9ecef
chore: release kitchen sink 0.2.1
Some checks are pending
Check / check (push) Waiting to run
Check / clawhub-dry-run (push) Waiting to run
2026-04-29 01:01:46 -07:00
Vincent Koc
f42815277f
chore(ci): update GitHub Actions majors 2026-04-29 00:59:41 -07:00
github-actions[bot]
574e921186
Update OpenClaw fixture dependencies
Automated update of openclaw@2026.4.26, @openclaw/plugin-inspector@0.3.4, and the generated kitchen-sink API surface.
2026-04-29 07:58:55 +00:00
Vincent Koc
d460c269e1
chore(ci): update artifact upload action 2026-04-28 22:22:39 -07:00
Vincent Koc
5df0c552f5
chore: release kitchen sink 0.2.0 2026-04-28 21:02:37 -07:00
Vincent Koc
a2fc069d0f
feat: expand kitchen sink provider fixtures 2026-04-28 20:49:43 -07:00
Vincent Koc
d3a89c90ea
feat: add realistic kitchen sink fixture flows 2026-04-28 20:08:06 -07:00
Vincent Koc
87d20d901e
feat: use bundled kitchen sink image fixture 2026-04-28 19:58:56 -07:00
Patrick Erichsen
a74467af8b chore: release kitchen sink 0.1.5 2026-04-28 19:14:58 -07:00
Patrick Erichsen
803414878a
Merge pull request #9 from openclaw/pe/clawhub-release-owner-openclaw
ci: publish ClawHub releases as openclaw
2026-04-28 19:11:48 -07:00
Patrick Erichsen
bd655ed854 ci: publish ClawHub releases as openclaw 2026-04-28 19:10:32 -07:00
Patrick Erichsen
4c59478567
Merge pull request #8 from openclaw/pe/enable-clawhub-release-publish
ci: enable ClawHub release publishing
2026-04-28 18:52:44 -07:00
Patrick Erichsen
5cac655a1b ci: enable ClawHub release publishing 2026-04-28 18:51:55 -07:00
Patrick Erichsen
5f5e94fc01
Merge pull request #7 from openclaw/pe/use-clawhub-reusable-workflow
ci: use canonical ClawHub publish workflow
2026-04-28 18:47:16 -07:00
Patrick Erichsen
07e25e02de ci: use canonical ClawHub publish workflow 2026-04-28 18:43:14 -07:00
Vincent Koc
f99033a127
chore(release): restore scoped package publishing 2026-04-28 17:32:23 -07:00
Vincent Koc
92d2cb5fff
chore(release): prepare 0.1.5 2026-04-28 17:20:09 -07:00
Vincent Koc
7dc10968da
chore(lockfile): match node 22 npm output 2026-04-28 17:12:55 -07:00
Vincent Koc
9b260c7fcc
ci(update): avoid force-pushing sdk update branches 2026-04-28 17:10:23 -07:00
Vincent Koc
ab202a3834
ci(update): handle existing sdk update branch 2026-04-28 17:07:38 -07:00
Vincent Koc
675ac0e125
docs(readme): describe runtime fixture surfaces 2026-04-28 16:37:04 -07:00
Vincent Koc
748acb9f3a
feat(tasks): add kitchen detached task runtime 2026-04-28 16:36:20 -07:00
Vincent Koc
b659e8c02e
feat(channel): add kitchen sink channel fixture 2026-04-28 16:33:52 -07:00
Vincent Koc
932a938795
feat(hooks): classify kitchen scenarios 2026-04-28 16:30:38 -07:00
Vincent Koc
8238961e7a
refactor(runtime): add kitchen scenario engine 2026-04-28 16:28:22 -07:00
Vincent Koc
6dc166f898
fix(inspector): avoid placeholder hook scans 2026-04-28 16:23:11 -07:00
Vincent Koc
860f46b1c1
fix(sdk): prune retired fixture imports 2026-04-28 16:15:07 -07:00
Vincent Koc
8baa3347a9
feat(runtime): add kitchen fixture routes 2026-04-28 16:13:46 -07:00
Patrick Erichsen
4032f366db
Merge pull request #6 from openclaw/pe/unscoped-package-name
Use unscoped package name for release
2026-04-28 15:56:02 -07:00
39 changed files with 7101 additions and 1083 deletions

View File

@ -17,8 +17,8 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
@ -26,7 +26,7 @@ jobs:
- run: npm test
- run: npm run plugin:inspect:runtime
- run: npm run pack:check
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: always()
with:
name: plugin-inspector-reports
@ -37,6 +37,6 @@ jobs:
permissions:
contents: read
id-token: write
uses: ./.github/workflows/clawhub-package-publish.yml
uses: openclaw/clawhub/.github/workflows/package-publish.yml@main
with:
dry_run: true

View File

@ -1,287 +0,0 @@
name: ClawHub Package Publish
on:
workflow_call:
inputs:
source:
description: Package source to publish. Usually owner/repo, owner/repo@ref, or a GitHub URL.
required: false
type: string
default: ""
ref:
description: Optional ref to append to the source when source is not already pinned.
required: false
type: string
dry_run:
description: Preview only. When true, no publish mutation is performed.
required: false
type: boolean
default: true
json:
description: Emit structured JSON output.
required: false
type: boolean
default: true
registry:
description: ClawHub registry URL.
required: false
type: string
default: https://clawhub.ai
site:
description: ClawHub site URL.
required: false
type: string
default: https://clawhub.ai
owner:
description: Optional owner handle override for org/shared publishing.
required: false
type: string
version:
description: Optional package version override.
required: false
type: string
tags:
description: Optional comma-separated tags override.
required: false
type: string
default: latest
source_repo:
description: Optional source repo override for local-folder publishes.
required: false
type: string
source_commit:
description: Optional source commit override for local-folder publishes.
required: false
type: string
source_ref:
description: Optional source ref override for local-folder publishes.
required: false
type: string
clawhub_version:
description: Legacy npm CLI version input. Kept for compatibility; the workflow now runs the checked-out source.
required: false
type: string
default: latest
secrets:
clawhub_token:
required: false
outputs:
publish_json:
description: Structured JSON output from clawhub package publish.
value: ${{ jobs.publish.outputs.publish_json }}
release_id:
description: Published release id when dry_run is false.
value: ${{ jobs.publish.outputs.release_id }}
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
outputs:
publish_json: ${{ steps.capture.outputs.publish_json }}
release_id: ${{ steps.capture.outputs.release_id }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
- uses: oven-sh/setup-bun@e3914758a49697077f7bcd190d36582a61667aad
with:
bun-version: 1.3.10
- uses: actions/checkout@v6
with:
repository: openclaw/clawhub
ref: 4787be4eb1e06b4ffb947456a6559835a4307f7b
path: clawhub-source
- name: Install ClawHub CLI dependencies
working-directory: clawhub-source
run: bun install --frozen-lockfile
- name: Validate publish mode inputs
env:
DRY_RUN: ${{ inputs.dry_run }}
JSON_MODE: ${{ inputs.json }}
CLAWHUB_TOKEN: ${{ secrets.clawhub_token }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
run: |
if [[ "$JSON_MODE" != "true" ]]; then
echo "::warning::This reusable workflow always emits JSON output; forcing --json for downstream parsing."
fi
if [[ "$DRY_RUN" == "true" ]]; then
exit 0
fi
if [[ -n "$CLAWHUB_TOKEN" ]]; then
exit 0
fi
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" && -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" && -n "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ]]; then
echo "No ClawHub token provided; publish will rely on GitHub OIDC trusted publishing."
exit 0
fi
echo "::error::Real publishes need secrets.clawhub_token, or GitHub OIDC on workflow_dispatch runs (permissions.id-token=write)."
exit 1
- name: Write ClawHub config
env:
CLAWHUB_TOKEN: ${{ secrets.clawhub_token }}
CLAWHUB_REGISTRY: ${{ inputs.registry }}
run: |
if [[ -z "$CLAWHUB_TOKEN" ]]; then
echo "No ClawHub token provided, skipping config file creation."
exit 0
fi
python3 - <<'PY'
import json
import os
from pathlib import Path
path = Path(os.environ["RUNNER_TEMP"]) / "clawhub-config.json"
path.write_text(
json.dumps(
{
"registry": os.environ["CLAWHUB_REGISTRY"],
"token": os.environ["CLAWHUB_TOKEN"],
},
indent=2,
)
+ "\n",
encoding="utf-8",
)
print(path)
PY
echo "CLAWHUB_CONFIG_PATH=$RUNNER_TEMP/clawhub-config.json" >> "$GITHUB_ENV"
- name: Resolve publish command
env:
INPUT_SOURCE: ${{ inputs.source }}
INPUT_REF: ${{ inputs.ref }}
INPUT_DRY_RUN: ${{ inputs.dry_run }}
INPUT_OWNER: ${{ inputs.owner }}
INPUT_VERSION: ${{ inputs.version }}
INPUT_TAGS: ${{ inputs.tags }}
INPUT_SOURCE_REPO: ${{ inputs.source_repo }}
INPUT_SOURCE_COMMIT: ${{ inputs.source_commit }}
INPUT_SOURCE_REF: ${{ inputs.source_ref }}
INPUT_SITE: ${{ inputs.site }}
INPUT_REGISTRY: ${{ inputs.registry }}
CLAWHUB_TOKEN: ${{ secrets.clawhub_token }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REF: ${{ github.ref }}
GITHUB_SHA: ${{ github.sha }}
run: |
python3 - <<'PY'
import os
import shlex
from pathlib import Path
source = os.environ["INPUT_SOURCE"].strip()
if not source:
source = os.environ["GITHUB_REPOSITORY"]
source_is_current_repo = source == os.environ["GITHUB_REPOSITORY"]
ref = os.environ["INPUT_REF"].strip()
if not ref and source_is_current_repo:
ref = os.environ["GITHUB_SHA"].strip()
is_local_source = source.startswith(".") or source.startswith("/") or Path(source).exists()
if ref and "@" not in source and not source.startswith("http") and not is_local_source:
source = f"{source}@{ref}"
cli_entry = (
Path(os.environ["GITHUB_WORKSPACE"])
/ "clawhub-source"
/ "packages"
/ "clawhub"
/ "src"
/ "cli.ts"
)
if not cli_entry.exists():
raise SystemExit(f"Missing ClawHub CLI entrypoint at {cli_entry}")
cmd = [
"bun",
str(cli_entry),
"package",
"publish",
source,
"--site",
os.environ["INPUT_SITE"],
"--registry",
os.environ["INPUT_REGISTRY"],
]
if os.environ["INPUT_DRY_RUN"] == "true":
cmd.append("--dry-run")
cmd.append("--json")
owner = os.environ["INPUT_OWNER"].strip()
version = os.environ["INPUT_VERSION"].strip()
tags = os.environ["INPUT_TAGS"].strip()
if owner:
cmd += ["--owner", owner]
if version:
cmd += ["--version", version]
if tags:
cmd += ["--tags", tags]
source_repo = os.environ["INPUT_SOURCE_REPO"].strip()
source_commit = os.environ["INPUT_SOURCE_COMMIT"].strip()
source_ref = os.environ["INPUT_SOURCE_REF"].strip()
if source_repo:
cmd += ["--source-repo", source_repo]
if source_commit:
cmd += ["--source-commit", source_commit]
if source_ref:
cmd += ["--source-ref", source_ref]
elif source_is_current_repo:
github_ref = os.environ["GITHUB_REF"].strip()
if github_ref:
cmd += ["--source-ref", github_ref]
if os.environ["INPUT_DRY_RUN"] != "true" and os.environ["CLAWHUB_TOKEN"].strip():
cmd += [
"--manual-override-reason",
f"GitHub Actions {os.environ['GITHUB_EVENT_NAME'].strip()} publish via CLAWHUB_TOKEN",
]
path = Path(os.environ["RUNNER_TEMP"]) / "clawhub-package-publish-command.sh"
shell_line = " ".join(shlex.quote(part) for part in cmd)
path.write_text("#!/usr/bin/env bash\nset -euo pipefail\n" + shell_line + "\n", encoding="utf-8")
path.chmod(0o755)
print(shell_line)
PY
- name: Run package publish
run: |
set -euo pipefail
"$RUNNER_TEMP/clawhub-package-publish-command.sh" | tee "$RUNNER_TEMP/package-publish.json"
- name: Capture workflow outputs
id: capture
run: |
python3 - <<'PY'
import json
import os
from pathlib import Path
output_path = Path(os.environ["RUNNER_TEMP"]) / "package-publish.json"
raw = output_path.read_text(encoding="utf-8").strip()
parsed = json.loads(raw)
github_output = Path(os.environ["GITHUB_OUTPUT"])
with github_output.open("a", encoding="utf-8") as fh:
fh.write("publish_json<<__CLAWHUB_JSON__\n")
fh.write(json.dumps(parsed, indent=2))
fh.write("\n__CLAWHUB_JSON__\n")
release_id = str(parsed.get("releaseId", "") or "")
fh.write(f"release_id={release_id}\n")
PY
- name: Upload publish JSON artifact
uses: actions/upload-artifact@v7
with:
name: clawhub-package-publish-json
path: ${{ runner.temp }}/package-publish.json
if-no-files-found: error

View File

@ -25,7 +25,7 @@ jobs:
draft:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ inputs.target || 'main' }}
fetch-depth: 0

View File

@ -39,7 +39,7 @@ jobs:
registry-url: "https://registry.npmjs.org"
- name: Setup npm
run: npm install -g npm@11.6.2
run: npm install -g npm@11.12.1
- name: Validate release tag matches package version
id: verify
@ -135,7 +135,7 @@ jobs:
registry-url: "https://registry.npmjs.org"
- name: Setup npm
run: npm install -g npm@11.6.2
run: npm install -g npm@11.12.1
- name: Download npm release artifact
uses: actions/download-artifact@v8
@ -143,28 +143,19 @@ jobs:
name: npm-release-tarball
path: ./release-artifacts
- name: Prepare trusted publishing auth
run: |
npm config delete //registry.npmjs.org/:_authToken || true
- name: Publish to npm
run: |
if [ "${{ github.event.repository.private }}" = "true" ]; then
npm publish "./release-artifacts/${{ needs.validate.outputs.tarball_name }}" --access public --tag "${{ needs.validate.outputs.npm_tag }}"
else
npm publish "./release-artifacts/${{ needs.validate.outputs.tarball_name }}" --provenance --access public --tag "${{ needs.validate.outputs.npm_tag }}"
fi
env:
NODE_AUTH_TOKEN: ""
npm publish "./release-artifacts/${{ needs.validate.outputs.tarball_name }}" --access public --tag "${{ needs.validate.outputs.npm_tag }}"
publish-clawhub:
needs: validate
permissions:
contents: read
id-token: write
uses: ./.github/workflows/clawhub-package-publish.yml
uses: openclaw/clawhub/.github/workflows/package-publish.yml@main
with:
dry_run: false
owner: openclaw
ref: ${{ needs.validate.outputs.tag_name }}
version: ${{ needs.validate.outputs.package_version }}
source_ref: refs/tags/${{ needs.validate.outputs.tag_name }}

View File

@ -20,8 +20,8 @@ jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
@ -41,13 +41,13 @@ jobs:
fi
openclaw_version="$(node -p "require('./node_modules/openclaw/package.json').version")"
inspector_version="$(node -p "require('./node_modules/@openclaw/plugin-inspector/package.json').version")"
branch="automation/openclaw-fixture-deps-openclaw-${openclaw_version}-inspector-${inspector_version}"
branch="automation/openclaw-fixture-deps-openclaw-${openclaw_version}-inspector-${inspector_version}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "$branch"
git add package.json package-lock.json openclaw.plugin.json src/generated-hooks.js src/generated-registrars.js src/generated-sdk-imports.ts
git commit -m "Update OpenClaw fixture dependencies"
git push --force-with-lease origin "$branch"
git push origin "$branch"
pr_url="$(gh pr list --base main --head "$branch" --state open --json url --jq '.[0].url // ""')"
if [[ -z "$pr_url" ]]; then
pr_url="$(gh pr create \

17
AGENTS.md Normal file
View File

@ -0,0 +1,17 @@
# AGENTS.md
Work from repo root. Keep changes small and commit/push them to `main` when asked.
## Release
- npm package: `@openclaw/kitchen-sink`.
- Trusted publisher: GitHub Actions, repository `openclaw/kitchen-sink`, workflow `release.yml`.
- Do not publish npm releases locally. Cut releases by bumping `package.json`/`package-lock.json`, syncing generated surface files, pushing `main`, creating an annotated `vX.Y.Z` tag, pushing the tag, then publishing the GitHub release with `gh release create vX.Y.Z --verify-tag --generate-notes --title vX.Y.Z`.
- The `release.yml` workflow owns npm publishing through OIDC trusted publishing. Keep `permissions.id-token: write`; do not add `NODE_AUTH_TOKEN` or long-lived npm token secrets for publish.
- ClawHub release publishing is enabled through the canonical reusable ClawHub workflow. Keep `permissions.id-token: write` and continue passing the `CLAWHUB_TOKEN` secret for release publishes.
## Validation
- Use Node 22.
- Before release commits, run `npm run check`, `npm run plugin:inspect:runtime`, `npm run pack:check`, and `git diff --check`.
- Generated surface files are expected to change when the package version changes; run `npm run sync:surface`.

123
README.md
View File

@ -9,8 +9,110 @@ This repo is both:
- a dummy compatibility fixture for [Crabpot](https://github.com/openclaw/crabpot) and [plugin-inspector](https://github.com/openclaw/plugin-inspector)
- a live plugin `@openclaw/kitchen-sink` that can be installed via clawhub and npm for testing features
The runtime handlers are no-op probes. They should not call external services,
read secrets, spawn processes, or require live credentials.
The generated runtime probes are credential-free. The hand-owned Kitchen Sink
runtime also registers deterministic direct commands, tools, image generation,
speech, realtime transcription/voice, video, music, media understanding, web
search, web fetch, memory, compaction, gateway/service/CLI, channel, hook,
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:
```text
kitchen image generate a kitchen sink
kitchen image rate limit
kitchen image timeout
kitchen search kitchen sink provider routing
kitchen fetch kitchen://fixture/redirect
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/`.
- `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
finish metadata.
- `kitchen-sink-image` is a registered image generation provider with aliases
`kitchen`, `kitchen-sink`, and `openclaw-kitchen-sink`; prompts containing
`rate limit`, `timeout`, or `fail` exercise deterministic provider error
paths.
- `kitchen-sink-media` describes images with deterministic fixture text.
- `kitchen-sink-speech`, `kitchen-sink-realtime-transcription`,
`kitchen-sink-realtime-voice`, `kitchen-sink-video`, and
`kitchen-sink-music` expose credential-free media provider fixtures with
deterministic WAV, transcript, bridge, storyboard, and track payloads.
- `kitchen-sink-search` and `kitchen-sink-fetch` provide credential-free web
tool fixtures with realistic status codes, request ids, result metadata,
redirects, headers, cache metadata, links, and markdown content.
- `kitchen-sink-memory-embedding`, `kitchen-sink-memory-corpus`, and
`kitchen-sink-compaction` provide deterministic memory vectors, corpus
results, reads, and transcript summaries.
- `kitchen-sink-channel` is a credential-free channel fixture that can resolve
local ready/disabled/misconfigured accounts, route outbound sessions, and
deliver deterministic text/media records.
- `kitchen.status`, `/kitchen-sink/status`, `kitchen-sink-service`, and the
lazy CLI descriptor exercise gateway method, HTTP route, service, and CLI
registration surfaces.
- `kitchen-sink-llm` exposes a deterministic text-provider catalog row,
provider-owned stream function, and prompt guidance so live LLM providers can
discover the Kitchen Sink routes; responses describe which real plugin
surface would handle image, search, fetch, and failure prompts.
- generated hooks classify Kitchen Sink prompts, tool calls, and provider
selections into shared scenario ids such as `image.generate`, `web.search`,
and `text.reply`.
- the detached-task runtime records queued/running/completed/cancelled task
transitions in memory so async OpenClaw task surfaces can be smoke-tested.
## API Surface Sync
@ -29,8 +131,11 @@ contract coverage.
```sh
npm install
npm run sync:surface
npm test
npm run check:runtime
npm run check:inspector
npm run check:install
npm run pack:check
npm run pack:zip
```
The `Update OpenClaw SDK Surface` workflow automatically checks
@ -50,11 +155,17 @@ 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
not replace the stable `latest` tag.
ClawHub publish wiring is present but intentionally not called from CI yet. The
ClawHub org/package ownership for this fixture still needs to be set up before
the dry-run or release publish jobs are enabled.
Pull requests run a ClawHub package-publish dry run through the canonical
`openclaw/clawhub` reusable workflow on `main`, so the fixture tests the current
ClawHub publishing path instead of a vendored copy. Releases publish to ClawHub
through the same canonical workflow after validation.

View File

@ -1,8 +1,8 @@
{
"id": "openclaw-kitchen-sink-fixture",
"name": "OpenClaw Kitchen Sink",
"version": "0.1.2",
"description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.4.26.",
"version": "0.2.5",
"description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.5.7.",
"enabledByDefault": false,
"kind": [
"tool",
@ -14,12 +14,21 @@
"kitchen-sink-channel"
],
"providers": [
"kitchen-sink-provider"
"kitchen-sink-provider",
"kitchen-sink-llm",
"kitchen-sink-image",
"kitchen-sink-speech",
"kitchen-sink-video",
"kitchen-sink-music"
],
"cliBackends": [
"kitchen-sink-cli-backend"
],
"commandAliases": [
{
"command": "kitchen",
"pluginId": "openclaw-kitchen-sink-fixture"
},
{
"command": "kitchen-sink",
"pluginId": "openclaw-kitchen-sink-fixture"
@ -27,12 +36,18 @@
],
"activation": {
"onProviders": [
"kitchen-sink-provider"
"kitchen-sink-provider",
"kitchen-sink-llm",
"kitchen-sink-image",
"kitchen-sink-speech",
"kitchen-sink-video",
"kitchen-sink-music"
],
"onChannels": [
"kitchen-sink-channel"
],
"onCommands": [
"kitchen",
"kitchen-sink"
],
"onCapabilities": [
@ -42,6 +57,42 @@
"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": [
{
@ -50,6 +101,55 @@
"none"
],
"envVars": []
},
{
"id": "kitchen-sink-llm",
"authMethods": [
"none"
],
"envVars": []
},
{
"id": "kitchen-sink-image",
"authMethods": [
"none"
],
"envVars": []
},
{
"id": "kitchen-sink-speech",
"authMethods": [
"none"
],
"envVars": []
},
{
"id": "kitchen-sink-realtime-transcription",
"authMethods": [
"none"
],
"envVars": []
},
{
"id": "kitchen-sink-realtime-voice",
"authMethods": [
"none"
],
"envVars": []
},
{
"id": "kitchen-sink-video",
"authMethods": [
"none"
],
"envVars": []
},
{
"id": "kitchen-sink-music",
"authMethods": [
"none"
],
"envVars": []
}
],
"cliBackends": [
@ -74,43 +174,56 @@
"kitchen-sink-external-auth-providers"
],
"imageGenerationProviders": [
"kitchen-sink-image-generation-providers"
"kitchen-sink-image-generation-providers",
"kitchen-sink-image"
],
"mediaUnderstandingProviders": [
"kitchen-sink-media-understanding-providers"
"kitchen-sink-media-understanding-providers",
"kitchen-sink-media"
],
"memoryEmbeddingProviders": [
"kitchen-sink-memory-embedding-providers"
"kitchen-sink-memory-embedding-providers",
"kitchen-sink-memory-embedding"
],
"migrationProviders": [
"kitchen-sink-migration-providers"
],
"musicGenerationProviders": [
"kitchen-sink-music-generation-providers"
"kitchen-sink-music-generation-providers",
"kitchen-sink-music"
],
"realtimeTranscriptionProviders": [
"kitchen-sink-realtime-transcription-providers"
"kitchen-sink-realtime-transcription-providers",
"kitchen-sink-realtime-transcription"
],
"realtimeVoiceProviders": [
"kitchen-sink-realtime-voice-providers"
"kitchen-sink-realtime-voice-providers",
"kitchen-sink-realtime-voice"
],
"speechProviders": [
"kitchen-sink-speech-providers"
"kitchen-sink-speech-providers",
"kitchen-sink-speech"
],
"tools": [
"kitchen-sink-tools"
"kitchen-sink-tools",
"kitchen_sink_image_job",
"kitchen_sink_text",
"kitchen_sink_search"
],
"videoGenerationProviders": [
"kitchen-sink-video-generation-providers"
"kitchen-sink-video-generation-providers",
"kitchen-sink-video"
],
"webContentExtractors": [
"kitchen-sink-web-content-extractors"
],
"webFetchProviders": [
"kitchen-sink-web-fetch-providers"
"kitchen-sink-web-fetch-providers",
"kitchen-sink-fetch"
],
"webSearchProviders": [
"kitchen-sink-web-search-providers"
"kitchen-sink-web-search-providers",
"kitchen-sink-search"
]
},
"configSchema": {
@ -120,6 +233,15 @@
"enabled": {
"type": "boolean",
"default": false
},
"personality": {
"type": "string",
"enum": [
"full",
"conformance",
"adversarial"
],
"default": "full"
}
}
}

2532
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "openclaw-kitchen-sink",
"version": "0.1.2",
"name": "@openclaw/kitchen-sink",
"version": "0.2.5",
"private": false,
"description": "Credential-free kitchen-sink OpenClaw plugin fixture covering the public plugin API surface.",
"type": "module",
@ -30,7 +30,9 @@
},
"exports": {
".": "./src/index.js",
"./runtime": "./src/index.js",
"./personality": "./src/personality.js",
"./runtime": "./src/kitchen-runtime.js",
"./scenarios": "./src/scenarios.js",
"./setup": "./src/setup.js"
},
"openclaw": {
@ -44,24 +46,44 @@
"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.4.26",
"pluginSdkVersion": "2026.4.26"
"openclawVersion": "2026.5.7",
"pluginSdkVersion": "2026.5.7"
}
},
"scripts": {
"check": "npm run sync:surface -- --check && node scripts/check-sdk-surface.mjs && npm run plugin:inspect",
"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",
"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",
"sync:surface": "node scripts/sync-surface.mjs",
"test": "npm run check"
},
"dependencies": {
"openclaw": "2026.4.26"
"openclaw": "2026.5.7"
},
"devDependencies": {
"@openclaw/plugin-inspector": "0.3.2"
"@openclaw/plugin-inspector": "0.3.10"
},
"overrides": {
"@anthropic-ai/sdk": "0.91.1",
"uuid": "14.0.0"
},
"engines": {
"node": ">=22"

View File

@ -0,0 +1,549 @@
{
"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"
]
}
}
}

View File

@ -0,0 +1,65 @@
# 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 |

View File

@ -0,0 +1,68 @@
#!/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);
}
}

View File

@ -0,0 +1,135 @@
#!/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");
}

View File

@ -0,0 +1,414 @@
#!/usr/bin/env node
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 commands = registrations.registerCommand?.map(([command]) => command) ?? [];
assert.ok(commands.some((command) => command.name === "kitchen"), "registers kitchen command");
assert.ok(commands.some((command) => command.name === "kitchen-sink"), "registers kitchen-sink command");
const beforeToolHook = findHook("before_tool_call");
const hookResult = await beforeToolHook(
{ toolId: "kitchen_sink_image_job", args: { prompt: "generate an image with kitchen sink" } },
{ providerId: "kitchen-sink-image" },
);
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");
assert.equal(channelAccount.configured, true);
assert.equal(channelAccount.enabled, true);
assert.equal(channelAccount.statusState, "ready");
assert.equal(channelAccount.health.ok, true);
assert.equal(channel.config.resolveAccount({ disabled: true }, "disabled").statusState, "disabled");
const channelDelivery = await channel.outbound.sendText({
cfg: {},
to: "kitchen demo",
text: "kitchen generate an image",
});
assert.equal(channelDelivery.channel, "kitchen-sink-channel");
assert.equal(channelDelivery.conversationId, "kitchen-demo");
assert.equal(channelDelivery.deliveryStatus, "sent");
assert.equal(channelDelivery.transport, "kitchen-sink-local");
assert.equal(channelDelivery.meta.scenarioId, "image.generate");
const channelRoute = await channel.messaging.resolveOutboundSessionRoute({
cfg: {},
agentId: "fixture-agent",
target: "kitchen demo",
});
assert.equal(channelRoute.sessionKey, "kitchen:fixture-agent:kitchen-demo");
assert.equal(channelRoute.peer.kind, "direct");
const taskRuntime = registrations.registerDetachedTaskRuntime?.at(-1)?.[0];
assert.equal(typeof taskRuntime?.createRunningTaskRun, "function");
const task = taskRuntime.createRunningTaskRun({
runtime: "cli",
runId: "ks_image_runtime_test",
taskKind: "image.generate",
task: "generate an image with kitchen sink",
});
assert.equal(task.status, "running");
assert.equal(task.sourceId, "openclaw-kitchen-sink-fixture");
const completedTasks = taskRuntime.completeTaskRunByRunId({
runId: "ks_image_runtime_test",
runtime: "cli",
endedAt: 1_776_600_000_000,
terminalSummary: "Kitchen Sink image completed.",
});
assert.equal(completedTasks[0].status, "succeeded");
assert.equal(completedTasks[0].terminalSummary, "Kitchen Sink image completed.");
const imageProvider = findRegistration("registerImageGenerationProvider", "kitchen-sink-image");
assert.equal(imageProvider.defaultModel, "kitchen-sink-image-v1");
const sleeps = [];
const {
PLUGIN_ID,
listKitchenHumanScenarios,
runKitchenHumanScenario,
runKitchenImageTool,
runKitchenScenario,
} = await import("../src/scenarios.js");
const { createKitchenSinkRuntime } = await import("../src/kitchen-runtime.js");
const fastRuntime = createKitchenSinkRuntime({
delayMs: 10_000,
sleep: async (ms) => {
sleeps.push(ms);
},
now: fixedNow(),
});
const imageResult = await fastRuntime.runImageJob({ prompt: "generate an image with kitchen sink" });
assert.deepEqual(sleeps, [10_000]);
assert.equal(imageResult.scenarioId, "image.generate");
assert.equal(imageResult.route, "provider:image");
assert.equal(imageResult.job.status, "completed");
assert.equal(imageResult.job.pluginId, "openclaw-kitchen-sink-fixture");
assert.equal(imageResult.job.progressPercent, 100);
assert.equal(imageResult.job.statusUrl, `kitchen://jobs/${imageResult.job.id}`);
assert.deepEqual(
imageResult.job.timeline.map((entry) => entry.status),
["queued", "running", "completed"],
);
assert.equal(imageResult.job.output.contentHash, "e126064123bb13d8");
assert.equal(imageResult.image.metadata.pluginId, "openclaw-kitchen-sink-fixture");
assert.equal(imageResult.image.metadata.assetId, "office-lobby-sink");
assert.equal(imageResult.image.metadata.assetName, "kitchen_sink_office.png");
assert.equal(imageResult.image.metadata.source, "bundled-real-image");
assert.equal(imageResult.image.metadata.model, "kitchen-sink-image-v1");
assert.equal(imageResult.image.metadata.width, 1024);
assert.equal(imageResult.image.metadata.height, 1024);
assert.equal(imageResult.image.metadata.sizeBytes, 948291);
assert.equal(imageResult.image.metadata.sha256, "e126064123bb13d8ee01a22c204e079bc22397c103ed1c3a191c60d5ae3319aa");
assert.equal(imageResult.image.metadata.contentHash, "e126064123bb13d8");
assert.equal(imageResult.image.metadata.finishReason, "success");
assert.equal(imageResult.image.mimeType, "image/png");
assert.equal(imageResult.image.fileName, `${imageResult.job.id}.png`);
assert.deepEqual(
[...imageResult.image.buffer.subarray(0, 8)],
[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
);
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.equal(failedImageResult.job.status, "failed");
assert.deepEqual(
failedImageResult.job.timeline.map((entry) => entry.status),
["queued", "running", "failed"],
);
assert.equal(failedImageResult.error.code, "rate_limited");
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);
const scenarioResult = await runKitchenScenario(fastRuntime, {
scenario: "web.fetch",
url: "kitchen://fixture/redirect",
route: "test:scenario-engine",
});
assert.equal(PLUGIN_ID, "openclaw-kitchen-sink-fixture");
assert.equal(scenarioResult.scenarioId, "web.fetch");
assert.equal(scenarioResult.route, "test:scenario-engine");
assert.equal(scenarioResult.ok, true);
assert.equal(scenarioResult.statusCode, 200);
assert.equal(scenarioResult.finalUrl, "kitchen://fixture/readme");
assert.equal(scenarioResult.redirects.length, 1);
assert.equal(scenarioResult.headers["x-kitchen-sink-fixture"], "true");
assert.match(scenarioResult.content, /deterministic document/);
const mediaProvider = findRegistration("registerMediaUnderstandingProvider", "kitchen-sink-media");
const mediaResult = await mediaProvider.describeImage({
prompt: "what is in this image",
model: "kitchen-sink-vision-v1",
});
assert.match(mediaResult.text, /Kitchen Sink media fixture/);
const audioDescription = await mediaProvider.transcribeAudio({
audio: Buffer.from("audio fixture"),
prompt: "transcribe this kitchen audio",
});
assert.match(audioDescription.text, /Kitchen Sink transcript/);
assert.equal(audioDescription.segments.length, 2);
const videoDescription = await mediaProvider.describeVideo({ prompt: "describe kitchen video" });
assert.match(videoDescription.text, /three deterministic frames/);
const speechProvider = findRegistration("registerSpeechProvider", "kitchen-sink-speech");
const speechResult = await speechProvider.synthesize({ text: "say kitchen sink" });
assert.equal(speechResult.mimeType, "audio/wav");
assert.equal(speechResult.audioBuffer.subarray(0, 4).toString("ascii"), "RIFF");
assert.equal(speechResult.metadata.providerId, "kitchen-sink-speech");
const realtimeTranscriptionProvider = findRegistration(
"registerRealtimeTranscriptionProvider",
"kitchen-sink-realtime-transcription",
);
const realtimeTranscripts = [];
const realtimeSession = realtimeTranscriptionProvider.createSession({
onTranscript: (text) => realtimeTranscripts.push(text),
});
await realtimeSession.connect();
realtimeSession.sendAudio(Buffer.from("abc"));
const realtimeFinal = await realtimeSession.close();
assert.match(realtimeFinal.text, /Kitchen Sink transcript/);
assert.ok(realtimeTranscripts.some((text) => /partial transcript/.test(text)));
const realtimeVoiceProvider = findRegistration("registerRealtimeVoiceProvider", "kitchen-sink-realtime-voice");
const realtimeVoiceEvents = [];
const realtimeBridge = realtimeVoiceProvider.createBridge({
onEvent: (event) => realtimeVoiceEvents.push(event.type),
});
await realtimeBridge.connect();
assert.equal(realtimeBridge.isConnected(), true);
realtimeBridge.setMediaTimestamp(123);
realtimeBridge.submitToolResult({ ok: true });
realtimeBridge.close();
assert.equal(realtimeBridge.isConnected(), false);
assert.deepEqual(realtimeVoiceEvents, ["connected", "media_timestamp", "tool_result", "closed"]);
const videoProvider = findRegistration("registerVideoGenerationProvider", "kitchen-sink-video");
const videoResult = await videoProvider.generateVideo({ prompt: "kitchen video" });
assert.equal(videoResult.videos[0].mimeType, "application/vnd.openclaw.kitchen-video+json");
assert.equal(videoResult.job.status, "completed");
const musicProvider = findRegistration("registerMusicGenerationProvider", "kitchen-sink-music");
const musicResult = await musicProvider.generateMusic({ prompt: "kitchen song" });
assert.equal(musicResult.tracks[0].mimeType, "audio/wav");
assert.equal(musicResult.tracks[0].audioBuffer.subarray(0, 4).toString("ascii"), "RIFF");
const searchProvider = findRegistration("registerWebSearchProvider", "kitchen-sink-search");
const searchTool = searchProvider.createTool({});
const searchResult = await searchTool.execute({ query: "kitchen sink image provider" });
assert.equal(searchResult.results.length, 3);
assert.equal(searchResult.provider, "kitchen-sink-search");
assert.equal(searchResult.ok, true);
assert.equal(searchResult.statusCode, 200);
assert.equal(searchResult.results[0].id, "ks-result-image-provider");
assert.equal(searchResult.results[0].metadata.provider, "kitchen-sink-image");
const emptySearchResult = await searchTool.execute({ query: "kitchen empty results" });
assert.equal(emptySearchResult.ok, true);
assert.equal(emptySearchResult.results.length, 0);
const textProvider = findRegistration("registerProvider", "kitchen-sink-llm");
const catalog = await textProvider.staticCatalog.run({ config: {}, env: {} });
assert.equal(catalog.provider.models[0].id, "kitchen-sink-text-v1");
assert.equal(catalog.provider.models[0].api, "kitchen-sink");
const authResult = await textProvider.auth[0].run();
assert.equal(authResult.profiles[0].id, "kitchen-sink-local");
const streamFn = textProvider.createStreamFn({});
const stream = streamFn(catalog.provider.models[0], {
messages: [{ role: "user", content: "kitchen explain text inference", timestamp: 0 }],
});
const streamEvents = [];
for await (const event of stream) {
streamEvents.push(event.type);
}
const streamMessage = await stream.result();
assert.deepEqual(streamEvents, ["start", "text_start", "text_delta", "text_end", "done"]);
assert.match(streamMessage.content[0].text, /kitchen explain text inference/);
assert.ok(streamMessage.usage.totalTokens > 0);
const embeddingProvider = findRegistration("registerMemoryEmbeddingProvider", "kitchen-sink-memory-embedding");
const embeddingResult = await embeddingProvider.embed({ text: "kitchen memory" });
assert.equal(embeddingResult.embedding.length, 8);
assert.equal(embeddingResult.model, "kitchen-sink-embed-v1");
const embeddingBatch = await embeddingProvider.embedMany({ texts: ["one", "two"] });
assert.equal(embeddingBatch.embeddings.length, 2);
const memoryCorpus = findRegistration("registerMemoryCorpusSupplement", "kitchen-sink-memory-corpus");
const memorySearch = await memoryCorpus.search({ query: "runtime surfaces" });
assert.equal(memorySearch.results[0].id, "ks-memory-runtime-surfaces");
const memoryRead = await memoryCorpus.read("ks-memory-runtime-surfaces");
assert.match(memoryRead.text, /providers, channels, hooks/);
const compactionProvider = findRegistration("registerCompactionProvider", "kitchen-sink-compaction");
const compacted = await compactionProvider.compact({
messages: [{ role: "user", content: "remember job ks_image_1f8a5a98 and the image fixture" }],
});
assert.match(compacted.summary, /Kitchen Sink compacted/);
assert.deepEqual(compacted.preservedIdentifiers, ["ks_image_1f8a5a98"]);
const middleware = registrations.registerAgentToolResultMiddleware.at(-1);
assert.equal(typeof middleware?.[0], "function");
assert.deepEqual(middleware[1].runtimes, ["pi", "codex", "cli"]);
const middlewareResult = await middleware[0]({ result: { content: "tool output" } });
assert.equal(middlewareResult.metadata.kitchenSinkToolResultMiddleware, true);
const service = registrations.registerService.map(([value]) => value).at(-1);
assert.equal(service.id, "kitchen-sink-service");
assert.equal((await service.probe()).state, "ready");
assert.equal((await service.start()).state, "started");
const httpRoute = findRegistration("registerHttpRoute", "kitchen-sink-http-status");
let httpBody = "";
const httpResult = await httpRoute.handler({}, {
setHeader: () => {},
end: (body) => {
httpBody = body;
},
});
assert.equal(httpRoute.path, "/kitchen-sink/status");
assert.equal(httpResult.ok, true);
assert.match(httpBody, /openclaw-kitchen-sink-fixture/);
const gatewayMethod = registrations.registerGatewayMethod.find(([name]) => name === "kitchen.status");
assert.ok(gatewayMethod, "registers kitchen.status gateway method");
const gatewayResult = await gatewayMethod[1]({});
assert.ok(gatewayResult.providerIds.includes("kitchen-sink-video"));
const cliRegistration = registrations.registerCli.at(-1);
assert.equal(typeof cliRegistration?.[0], "function");
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");

View File

@ -31,6 +31,18 @@ const requiredFiles = [
"openclaw.plugin.json",
"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",
"src/generated-registrars.js",
@ -39,6 +51,7 @@ 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) {
@ -59,8 +72,8 @@ if (missingFiles.length > 0) {
issues.push(`missing packed files: ${missingFiles.join(", ")}`);
}
if (packageJson.name !== "openclaw-kitchen-sink") {
issues.push('package name must be "openclaw-kitchen-sink"');
if (packageJson.name !== "@openclaw/kitchen-sink") {
issues.push('package name must be "@openclaw/kitchen-sink"');
}
requireStringArray("openclaw.extensions", packageJson.openclaw?.extensions, ["./src/index.js"]);
@ -88,9 +101,37 @@ 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- ")}`);

View File

@ -12,7 +12,7 @@ const manifest = JSON.parse(read("openclaw.plugin.json"));
const errors = [];
for (const hook of surface.hooks) {
if (!hooksSource.includes(`api.on("${hook}"`)) {
if (!hooksSource.includes(`api.on(${JSON.stringify(hook)}`)) {
errors.push(`missing hook coverage: ${hook}`);
}
}

View File

@ -0,0 +1,24 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import path from "node:path";
import { readOpenClawSurface } from "./openclaw-surface.mjs";
const rootDir = path.resolve(import.meta.dirname, "..");
const generatedSdkImports = readFileSync(path.join(rootDir, "src/generated-sdk-imports.ts"), "utf8");
const importedSdkSpecifiers = [
...generatedSdkImports.matchAll(/from\s+["'](openclaw\/plugin-sdk(?:\/[^"']+)?)["']/g),
].map((match) => match[1]);
const targetSurface = readOpenClawSurface();
const targetSdkExports = new Set(targetSurface.pluginSdkExports);
const importsMissingFromTarget = importedSdkSpecifiers.filter((specifier) => !targetSdkExports.has(specifier));
if (importsMissingFromTarget.length > 0) {
throw new Error(
`generated SDK imports missing from target OpenClaw ${targetSurface.packageVersion} exports:\n${importsMissingFromTarget.join("\n")}`,
);
}
console.log(
`Generated SDK imports are valid for OpenClaw ${targetSurface.packageVersion}: ${importedSdkSpecifiers.length} imports, ${targetSurface.pluginSdkExports.length} target exports`,
);

View File

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

View File

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

View File

@ -4,14 +4,72 @@ import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
// These bundled-plugin convenience barrels existed in published OpenClaw builds
// but were retired from the public package export contract on current main.
const retiredPluginSdkExports = new Set([
"openclaw/plugin-sdk/bluebubbles",
"openclaw/plugin-sdk/bluebubbles-policy",
"openclaw/plugin-sdk/browser-cdp",
"openclaw/plugin-sdk/browser-config-runtime",
"openclaw/plugin-sdk/browser-config-support",
"openclaw/plugin-sdk/browser-control-auth",
"openclaw/plugin-sdk/browser-node-runtime",
"openclaw/plugin-sdk/browser-profiles",
"openclaw/plugin-sdk/browser-security-runtime",
"openclaw/plugin-sdk/browser-setup-tools",
"openclaw/plugin-sdk/browser-support",
"openclaw/plugin-sdk/diagnostics-otel",
"openclaw/plugin-sdk/diagnostics-prometheus",
"openclaw/plugin-sdk/diffs",
"openclaw/plugin-sdk/feishu",
"openclaw/plugin-sdk/feishu-conversation",
"openclaw/plugin-sdk/feishu-setup",
"openclaw/plugin-sdk/github-copilot-login",
"openclaw/plugin-sdk/github-copilot-token",
"openclaw/plugin-sdk/googlechat",
"openclaw/plugin-sdk/googlechat-runtime-shared",
"openclaw/plugin-sdk/irc",
"openclaw/plugin-sdk/irc-surface",
"openclaw/plugin-sdk/line",
"openclaw/plugin-sdk/line-core",
"openclaw/plugin-sdk/line-runtime",
"openclaw/plugin-sdk/line-surface",
"openclaw/plugin-sdk/llm-task",
"openclaw/plugin-sdk/matrix",
"openclaw/plugin-sdk/matrix-helper",
"openclaw/plugin-sdk/matrix-runtime-heavy",
"openclaw/plugin-sdk/matrix-runtime-shared",
"openclaw/plugin-sdk/matrix-runtime-surface",
"openclaw/plugin-sdk/matrix-surface",
"openclaw/plugin-sdk/matrix-thread-bindings",
"openclaw/plugin-sdk/mattermost",
"openclaw/plugin-sdk/mattermost-policy",
"openclaw/plugin-sdk/memory-core",
"openclaw/plugin-sdk/memory-lancedb",
"openclaw/plugin-sdk/msteams",
"openclaw/plugin-sdk/nextcloud-talk",
"openclaw/plugin-sdk/nostr",
"openclaw/plugin-sdk/opencode",
"openclaw/plugin-sdk/telegram-command-ui",
"openclaw/plugin-sdk/thread-ownership",
"openclaw/plugin-sdk/tlon",
"openclaw/plugin-sdk/twitch",
"openclaw/plugin-sdk/voice-call",
"openclaw/plugin-sdk/volc-model-catalog-shared",
"openclaw/plugin-sdk/zalo",
"openclaw/plugin-sdk/zalo-setup",
"openclaw/plugin-sdk/zalouser",
]);
export function readOpenClawSurface() {
const packageEntryPath = require.resolve("openclaw");
const packageEntryPath = resolveOpenClawPackageEntry();
const packageRoot = findPackageRoot(packageEntryPath);
const packageJsonPath = path.join(packageRoot, "package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
const pluginSdkExports = Object.keys(packageJson.exports ?? {})
.filter((specifier) => specifier === "./plugin-sdk" || specifier.startsWith("./plugin-sdk/"))
.map((specifier) => `openclaw/${specifier.slice(2)}`)
.filter((specifier) => !retiredPluginSdkExports.has(specifier))
.sort();
const apiBuilderPath = firstExistingPath([
@ -41,6 +99,14 @@ export function readOpenClawSurface() {
};
}
function resolveOpenClawPackageEntry() {
const packageRoot = process.env.OPENCLAW_PACKAGE_ROOT;
if (packageRoot) {
return path.join(path.resolve(packageRoot), "package.json");
}
return require.resolve("openclaw");
}
function findPackageRoot(entryPath) {
let current = path.dirname(entryPath);
while (current !== path.dirname(current)) {

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

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

View File

@ -37,18 +37,14 @@ console.log(
);
function renderHooks({ hooks, packageVersion }) {
return `${header(packageVersion)}
return `${header(packageVersion)}import { observeKitchenHook } from "./scenarios.js";
export function registerAllHooks(api) {
${hooks.map((hook) => ` api.on("${hook}", kitchenSinkHook("${hook}"));`).join("\n")}
${hooks.map((hook) => ` api.on(${JSON.stringify(hook)}, kitchenSinkHook(${JSON.stringify(hook)}));`).join("\n")}
}
function kitchenSinkHook(name) {
return async (event, context) => ({
kitchenSink: true,
hook: name,
observedEventKeys: Object.keys(event ?? {}),
observedContextKeys: Object.keys(context ?? {}),
});
return async (event, context) => observeKitchenHook(name, event, context);
}
`;
}
@ -56,7 +52,7 @@ function kitchenSinkHook(name) {
function renderRegistrars({ registrars, packageVersion }) {
return `${header(packageVersion)}
export function registerAllRegistrars(api) {
${registrars.map((registrar) => ` safeRegister("${registrar}", () => api.${registrar}(payloadFor("${registrar}")));`).join("\n")}
${registrars.map(renderRegistrarCoverage).join("\n")}
}
function safeRegister(name, register) {
@ -71,11 +67,12 @@ export const apiSurfaceProbeFailures = [];
function payloadFor(name) {
const id = name.replace(/^register/, "").replace(/[A-Z]/g, (letter, index) => (index === 0 ? "" : "-") + letter.toLowerCase()) || "probe";
const probeId = name === "registerChannel" ? "kitchen-sink-channel-probe" : "kitchen-sink-" + id;
return {
id: "kitchen-sink-" + id,
name: "kitchen-sink-" + id,
id: probeId,
name: probeId,
description: "Kitchen-sink no-op probe for " + name + ".",
command: "kitchen-sink-" + id,
command: probeId,
path: "/kitchen-sink/" + id,
method: "POST",
inputSchema: objectSchema(),
@ -111,6 +108,13 @@ function objectSchema() {
`;
}
function renderRegistrarCoverage(registrar) {
if (registrar === "registerDetachedTaskRuntime") {
return ` void "api.registerDetachedTaskRuntime("; // Covered by the hand-owned Kitchen Sink task runtime.`;
}
return ` safeRegister("${registrar}", () => api.${registrar}(payloadFor("${registrar}")));`;
}
function renderSdkImports({ pluginSdkExports, packageVersion }) {
return `${header(packageVersion)}${pluginSdkExports
.map((specifier, index) => `import type * as sdk${index} from "${specifier}";`)
@ -123,17 +127,32 @@ ${pluginSdkExports.map((_, index) => ` | typeof sdk${index}`).join("\n")};
function renderRuntimeIndex() {
const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8"));
return `import { registerAllHooks } from "./generated-hooks.js";
return `import { PLUGIN_ID } from "./constants.js";
import { registerAllHooks } from "./generated-hooks.js";
import { registerAllRegistrars } from "./generated-registrars.js";
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
import {
KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
resolveKitchenSinkPersonality,
} from "./personality.js";
export const plugin = {
id: "openclaw-kitchen-sink-fixture",
id: PLUGIN_ID,
name: "OpenClaw Kitchen Sink",
version: "${packageJson.version}",
description: "No-op plugin fixture covering OpenClaw plugin API seams.",
description: "Credential-free fixture covering OpenClaw plugin API seams.",
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
register(api) {
const personality = resolveKitchenSinkPersonality(api);
registerAllHooks(api);
registerAllRegistrars(api);
if (personality !== "conformance") {
registerAllRegistrars(api);
}
if (personality !== "adversarial") {
registerKitchenSinkRuntime(api, {
includeAgentToolResultMiddleware: personality !== "conformance",
});
}
},
};
@ -148,6 +167,20 @@ export default plugin;
function renderManifest({ manifestContracts, packageVersion }) {
const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8"));
const contracts = Object.fromEntries(manifestContracts.map((field) => [field, [`kitchen-sink-${kebab(field)}`]]));
appendContract(contracts, "imageGenerationProviders", "kitchen-sink-image");
appendContract(contracts, "mediaUnderstandingProviders", "kitchen-sink-media");
appendContract(contracts, "speechProviders", "kitchen-sink-speech");
appendContract(contracts, "realtimeTranscriptionProviders", "kitchen-sink-realtime-transcription");
appendContract(contracts, "realtimeVoiceProviders", "kitchen-sink-realtime-voice");
appendContract(contracts, "videoGenerationProviders", "kitchen-sink-video");
appendContract(contracts, "musicGenerationProviders", "kitchen-sink-music");
appendContract(contracts, "memoryEmbeddingProviders", "kitchen-sink-memory-embedding");
appendContract(contracts, "agentToolResultMiddleware", "kitchen-sink-agent-tool-result-middleware");
appendContract(contracts, "webSearchProviders", "kitchen-sink-search");
appendContract(contracts, "webFetchProviders", "kitchen-sink-fetch");
appendContract(contracts, "tools", "kitchen_sink_image_job");
appendContract(contracts, "tools", "kitchen_sink_text");
appendContract(contracts, "tools", "kitchen_sink_search");
const manifest = {
id: "openclaw-kitchen-sink-fixture",
name: "OpenClaw Kitchen Sink",
@ -156,17 +189,66 @@ function renderManifest({ manifestContracts, packageVersion }) {
enabledByDefault: false,
kind: ["tool", "hook", "channel", "provider"],
channels: ["kitchen-sink-channel"],
providers: ["kitchen-sink-provider"],
providers: [
"kitchen-sink-provider",
"kitchen-sink-llm",
"kitchen-sink-image",
"kitchen-sink-speech",
"kitchen-sink-video",
"kitchen-sink-music",
],
cliBackends: ["kitchen-sink-cli-backend"],
commandAliases: [{ command: "kitchen-sink", pluginId: "openclaw-kitchen-sink-fixture" }],
commandAliases: [
{ command: "kitchen", pluginId: "openclaw-kitchen-sink-fixture" },
{ command: "kitchen-sink", pluginId: "openclaw-kitchen-sink-fixture" },
],
activation: {
onProviders: ["kitchen-sink-provider"],
onProviders: [
"kitchen-sink-provider",
"kitchen-sink-llm",
"kitchen-sink-image",
"kitchen-sink-speech",
"kitchen-sink-video",
"kitchen-sink-music",
],
onChannels: ["kitchen-sink-channel"],
onCommands: ["kitchen-sink"],
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: [] }],
providers: [
{ id: "kitchen-sink-provider", authMethods: ["none"], envVars: [] },
{ id: "kitchen-sink-llm", authMethods: ["none"], envVars: [] },
{ id: "kitchen-sink-image", authMethods: ["none"], envVars: [] },
{ id: "kitchen-sink-speech", authMethods: ["none"], envVars: [] },
{ id: "kitchen-sink-realtime-transcription", authMethods: ["none"], envVars: [] },
{ id: "kitchen-sink-realtime-voice", authMethods: ["none"], envVars: [] },
{ id: "kitchen-sink-video", authMethods: ["none"], envVars: [] },
{ id: "kitchen-sink-music", authMethods: ["none"], envVars: [] },
],
cliBackends: ["kitchen-sink-cli-backend"],
configMigrations: ["kitchen-sink-config-migration"],
requiresRuntime: false,
@ -177,6 +259,7 @@ function renderManifest({ manifestContracts, packageVersion }) {
additionalProperties: false,
properties: {
enabled: { type: "boolean", default: false },
personality: { type: "string", enum: ["full", "conformance", "adversarial"], default: "full" },
},
},
};
@ -191,6 +274,18 @@ 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;
}
@ -205,6 +300,15 @@ function kebab(value) {
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`).replace(/^-/, "");
}
function appendContract(contracts, field, id) {
if (!Array.isArray(contracts[field])) {
contracts[field] = [];
}
if (!contracts[field].includes(id)) {
contracts[field].push(id);
}
}
function readIfExists(filePath) {
try {
return readFileSync(filePath, "utf8");

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 KiB

23
src/constants.js Normal file
View File

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

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

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

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

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

View File

@ -1,9 +1,11 @@
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand.
// Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
import { observeKitchenHook } from "./scenarios.js";
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"));
@ -15,8 +17,10 @@ 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"));
@ -36,10 +40,5 @@ export function registerAllHooks(api) {
}
function kitchenSinkHook(name) {
return async (event, context) => ({
kitchenSink: true,
hook: name,
observedEventKeys: Object.keys(event ?? {}),
observedContextKeys: Object.keys(context ?? {}),
});
return async (event, context) => observeKitchenHook(name, event, context);
}

View File

@ -1,6 +1,7 @@
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.26. Do not edit by hand.
// Generated by scripts/sync-surface.mjs from openclaw 2026.5.7. Do not edit by hand.
export function registerAllRegistrars(api) {
safeRegister("registerAgentEventSubscription", () => api.registerAgentEventSubscription(payloadFor("registerAgentEventSubscription")));
safeRegister("registerAgentHarness", () => api.registerAgentHarness(payloadFor("registerAgentHarness")));
safeRegister("registerAgentToolResultMiddleware", () => api.registerAgentToolResultMiddleware(payloadFor("registerAgentToolResultMiddleware")));
safeRegister("registerAutoEnableProbe", () => api.registerAutoEnableProbe(payloadFor("registerAutoEnableProbe")));
@ -12,7 +13,8 @@ export function registerAllRegistrars(api) {
safeRegister("registerCompactionProvider", () => api.registerCompactionProvider(payloadFor("registerCompactionProvider")));
safeRegister("registerConfigMigration", () => api.registerConfigMigration(payloadFor("registerConfigMigration")));
safeRegister("registerContextEngine", () => api.registerContextEngine(payloadFor("registerContextEngine")));
safeRegister("registerDetachedTaskRuntime", () => api.registerDetachedTaskRuntime(payloadFor("registerDetachedTaskRuntime")));
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")));
safeRegister("registerHook", () => api.registerHook(payloadFor("registerHook")));
@ -30,15 +32,21 @@ 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")));
@ -56,11 +64,12 @@ export const apiSurfaceProbeFailures = [];
function payloadFor(name) {
const id = name.replace(/^register/, "").replace(/[A-Z]/g, (letter, index) => (index === 0 ? "" : "-") + letter.toLowerCase()) || "probe";
const probeId = name === "registerChannel" ? "kitchen-sink-channel-probe" : "kitchen-sink-" + id;
return {
id: "kitchen-sink-" + id,
name: "kitchen-sink-" + id,
id: probeId,
name: probeId,
description: "Kitchen-sink no-op probe for " + name + ".",
command: "kitchen-sink-" + id,
command: probeId,
path: "/kitchen-sink/" + id,
method: "POST",
inputSchema: objectSchema(),

View File

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

View File

@ -1,14 +1,29 @@
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: "openclaw-kitchen-sink-fixture",
id: PLUGIN_ID,
name: "OpenClaw Kitchen Sink",
version: "0.1.2",
description: "No-op plugin fixture covering OpenClaw plugin API seams.",
version: "0.2.5",
description: "Credential-free fixture covering OpenClaw plugin API seams.",
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
register(api) {
const personality = resolveKitchenSinkPersonality(api);
registerAllHooks(api);
registerAllRegistrars(api);
if (personality !== "conformance") {
registerAllRegistrars(api);
}
if (personality !== "adversarial") {
registerKitchenSinkRuntime(api, {
includeAgentToolResultMiddleware: personality !== "conformance",
});
}
},
};

128
src/kitchen-runtime.js Normal file
View File

@ -0,0 +1,128 @@
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 {
createKitchenScenarioRuntime,
createKitchenSinkImageAsset,
kitchenPromptGuidance,
shouldHandleKitchenText,
} 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)));
optionalRegister(api, "registerInteractiveHandler", () =>
api.registerInteractiveHandler(buildKitchenInteractiveHandler(runtime)),
);
optionalRegister(api, "registerChannel", () => api.registerChannel(buildKitchenChannel()));
optionalRegister(api, "registerTool", () => api.registerTool(buildKitchenImageTool(runtime)));
optionalRegister(api, "registerTool", () => api.registerTool(buildKitchenTextTool(runtime)));
optionalRegister(api, "registerTool", () => api.registerTool(buildKitchenSearchTool()));
optionalRegister(api, "registerProvider", () => api.registerProvider(buildKitchenTextProvider()));
optionalRegister(api, "registerImageGenerationProvider", () =>
api.registerImageGenerationProvider(buildKitchenImageProvider(runtime)),
);
optionalRegister(api, "registerMediaUnderstandingProvider", () =>
api.registerMediaUnderstandingProvider(buildKitchenMediaProvider()),
);
optionalRegister(api, "registerSpeechProvider", () => api.registerSpeechProvider(buildKitchenSpeechProvider()));
optionalRegister(api, "registerRealtimeTranscriptionProvider", () =>
api.registerRealtimeTranscriptionProvider(buildKitchenRealtimeTranscriptionProvider()),
);
optionalRegister(api, "registerRealtimeVoiceProvider", () =>
api.registerRealtimeVoiceProvider(buildKitchenRealtimeVoiceProvider()),
);
optionalRegister(api, "registerVideoGenerationProvider", () =>
api.registerVideoGenerationProvider(buildKitchenVideoProvider()),
);
optionalRegister(api, "registerMusicGenerationProvider", () =>
api.registerMusicGenerationProvider(buildKitchenMusicProvider()),
);
optionalRegister(api, "registerWebSearchProvider", () =>
api.registerWebSearchProvider(buildKitchenWebSearchProvider()),
);
optionalRegister(api, "registerWebFetchProvider", () =>
api.registerWebFetchProvider(buildKitchenWebFetchProvider()),
);
optionalRegister(api, "registerDetachedTaskRuntime", () =>
api.registerDetachedTaskRuntime(buildKitchenDetachedTaskRuntime()),
);
optionalRegister(api, "registerMemoryEmbeddingProvider", () =>
api.registerMemoryEmbeddingProvider(buildKitchenMemoryEmbeddingProvider()),
);
optionalRegister(api, "registerMemoryCorpusSupplement", () =>
api.registerMemoryCorpusSupplement(buildKitchenMemoryCorpusSupplement()),
);
optionalRegister(api, "registerCompactionProvider", () =>
api.registerCompactionProvider(buildKitchenCompactionProvider()),
);
if (includeAgentToolResultMiddleware) {
optionalRegister(api, "registerAgentToolResultMiddleware", () =>
api.registerAgentToolResultMiddleware(buildKitchenToolResultMiddleware(), {
runtimes: ["pi", "codex", "cli"],
}),
);
}
optionalRegister(api, "registerService", () => api.registerService(buildKitchenService()));
optionalRegister(api, "registerHttpRoute", () => api.registerHttpRoute(buildKitchenHttpRoute()));
optionalRegister(api, "registerGatewayMethod", () =>
api.registerGatewayMethod("kitchen.status", buildKitchenGatewayMethod()),
);
optionalRegister(api, "registerCli", () => api.registerCli(buildKitchenCliRegistrar(), buildKitchenCliMetadata()));
optionalRegister(api, "registerMemoryPromptSupplement", () =>
api.registerMemoryPromptSupplement(async () => kitchenPromptGuidance().join("\n")),
);
return runtime;
}
export function createKitchenSinkRuntime(options = {}) {
return createKitchenScenarioRuntime(options);
}
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;
}
register();
}

52
src/personality.js Normal file
View File

@ -0,0 +1,52 @@
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;
}

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

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

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

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

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

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

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

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

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

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

1205
src/scenarios.js Normal file

File diff suppressed because it is too large Load Diff