Compare commits

...

83 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
Patrick Erichsen
145e683b5b Use unscoped package name for release 2026-04-28 15:55:36 -07:00
Patrick Erichsen
dc7e904033
Merge pull request #5 from openclaw/pe/release-0.1.1
Release kitchen sink 0.1.1
2026-04-28 15:45:37 -07:00
Patrick Erichsen
f411726bfa Release kitchen sink 0.1.1 2026-04-28 15:45:16 -07:00
Patrick Erichsen
fd26db6769
Merge pull request #4 from openclaw/pe/enable-clawhub-package-publish
Use unclaimed ClawHub runtime id
2026-04-28 15:41:37 -07:00
Patrick Erichsen
1256b0b0ed Merge main and update ClawHub runtime id 2026-04-28 15:41:01 -07:00
Patrick Erichsen
3bbdf4dade Rename package for ClawHub publishing 2026-04-28 15:38:15 -07:00
Patrick Erichsen
d7deb7c147
Merge pull request #3 from openclaw/pe/enable-clawhub-package-publish
[codex] Enable ClawHub package publishing
2026-04-28 15:22:15 -07:00
Patrick Erichsen
0876dbc3e4 Enable ClawHub package publishing 2026-04-28 15:20:50 -07:00
Vincent Koc
a4d7ecb2c0
chore(release): bump kitchen sink to 0.0.2 2026-04-28 15:11:43 -07:00
Vincent Koc
b8672b2a0d
fix(ci): grant npm publish oidc 2026-04-28 15:06:49 -07:00
Vincent Koc
b6c446a76c
ci: add draft release flow 2026-04-28 15:03:29 -07:00
Vincent Koc
0b8f65683f
chore(release): bump kitchen sink to 0.1.0 2026-04-28 14:10:48 -07:00
Vincent Koc
55d1d99512
Update README.md 2026-04-28 13:35:56 -07:00
Vincent Koc
b2b50bfdac
Update README.md 2026-04-28 13:34:38 -07:00
Vincent Koc
d08b8728bf
chore(release): rename npm package 2026-04-28 13:32:33 -07:00
Vincent Koc
b5c023ff71
ci: add package publish workflows 2026-04-28 13:25:48 -07:00
Vincent Koc
d43292fd11
Update README.md 2026-04-28 13:17:56 -07:00
Patrick Erichsen
77f84d6aab Clarify kitchen sink surface generation 2026-04-28 13:04:12 -07:00
Patrick Erichsen
2d27ca8cf5 Auto-update OpenClaw fixture dependencies 2026-04-28 13:01:28 -07:00
Patrick Erichsen
457d1e4d66 Automate OpenClaw SDK surface updates 2026-04-28 12:58:01 -07:00
Patrick Erichsen
a00e021d93 Link compatibility fixture references 2026-04-27 10:24:54 -07:00
Patrick Erichsen
ddc7bc00ba Add plugin inspector checks 2026-04-27 08:58:12 -07:00
42 changed files with 7743 additions and 1345 deletions

View File

@ -9,3 +9,6 @@ updates:
openclaw-sdk:
patterns:
- "openclaw"
ignore:
- dependency-name: "openclaw"
- dependency-name: "@openclaw/plugin-inspector"

View File

@ -17,10 +17,26 @@ 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
- run: npm ci
- run: npm test
- run: npm run plugin:inspect:runtime
- run: npm run pack:check
- uses: actions/upload-artifact@v7
if: always()
with:
name: plugin-inspector-reports
path: reports/plugin-inspector-*
clawhub-dry-run:
if: github.event_name == 'pull_request'
permissions:
contents: read
id-token: write
uses: openclaw/clawhub/.github/workflows/package-publish.yml@main
with:
dry_run: true

71
.github/workflows/draft-release.yml vendored Normal file
View File

@ -0,0 +1,71 @@
name: Draft Release
on:
workflow_dispatch:
inputs:
tag:
description: Release tag. Leave empty to derive v<package.json version>.
required: false
type: string
target:
description: Branch or SHA to tag.
required: false
type: string
default: main
prerelease:
description: Mark the draft as a prerelease.
required: false
type: boolean
default: false
permissions:
contents: write
jobs:
draft:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.target || 'main' }}
fetch-depth: 0
- name: Draft GitHub release
env:
GH_TOKEN: ${{ github.token }}
INPUT_TAG: ${{ inputs.tag }}
INPUT_PRERELEASE: ${{ inputs.prerelease }}
run: |
set -euo pipefail
package_version="$(node -p "require('./package.json').version")"
tag="${INPUT_TAG:-v${package_version}}"
expected_tag="v${package_version}"
if [[ "$tag" != "$expected_tag" ]]; then
echo "::error::Tag/version mismatch. Expected ${expected_tag}, got ${tag}."
exit 1
fi
if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
if [[ "$(git rev-parse "refs/tags/${tag}^{commit}")" != "$(git rev-parse HEAD)" ]]; then
echo "::error::Existing tag ${tag} does not point at HEAD."
exit 1
fi
else
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$tag" -m "$tag"
git push origin "$tag"
fi
release_flags=(--draft --verify-tag --generate-notes --title "$tag")
if [[ "$INPUT_PRERELEASE" == "true" ]]; then
release_flags+=(--prerelease)
fi
if gh release view "$tag" >/dev/null 2>&1; then
gh release edit "$tag" "${release_flags[@]}"
else
gh release create "$tag" "${release_flags[@]}"
fi

163
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,163 @@
name: Release
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: Release tag to publish, for example v0.1.0
required: true
type: string
permissions:
contents: read
id-token: write
jobs:
validate:
runs-on: ubuntu-latest
outputs:
package_name: ${{ steps.verify.outputs.package_name }}
package_version: ${{ steps.verify.outputs.package_version }}
tag_name: ${{ steps.verify.outputs.tag_name }}
npm_tag: ${{ steps.verify.outputs.npm_tag }}
npm_already_published: ${{ steps.npm_exists.outputs.already_published }}
tarball_name: ${{ steps.pack.outputs.tarball_name }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ inputs.tag || github.event.release.tag_name }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "22.14.0"
registry-url: "https://registry.npmjs.org"
- name: Setup npm
run: npm install -g npm@11.12.1
- name: Validate release tag matches package version
id: verify
env:
TAG_NAME: ${{ inputs.tag || github.event.release.tag_name }}
run: |
PACKAGE_NAME="$(node -p "require('./package.json').name")"
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
NPM_TAG="latest"
if [[ "$PACKAGE_VERSION" == 0.0.* ]]; then
NPM_TAG="verification"
fi
if [ -z "$TAG_NAME" ]; then
echo "tag input is required for workflow_dispatch, and release.tag_name is required for release runs."
exit 1
fi
EXPECTED_TAG="v${PACKAGE_VERSION}"
if [ "$TAG_NAME" != "$EXPECTED_TAG" ]; then
echo "Tag/version mismatch."
echo "Expected tag: $EXPECTED_TAG"
echo "Actual tag: $TAG_NAME"
echo "package.json version: $PACKAGE_VERSION"
exit 1
fi
echo "package_name=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
echo "package_version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT"
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
echo "npm_tag=$NPM_TAG" >> "$GITHUB_OUTPUT"
- name: Check if this version is already published to npm
id: npm_exists
run: |
if npm view "${{ steps.verify.outputs.package_name }}@${{ steps.verify.outputs.package_version }}" version >/dev/null 2>&1; then
echo "already_published=true" >> "$GITHUB_OUTPUT"
else
echo "already_published=false" >> "$GITHUB_OUTPUT"
fi
- name: Install dependencies
if: steps.npm_exists.outputs.already_published != 'true'
run: npm ci
- name: Run checks
if: steps.npm_exists.outputs.already_published != 'true'
run: npm test
- name: Run runtime inspector
if: steps.npm_exists.outputs.already_published != 'true'
run: npm run plugin:inspect:runtime
- name: Verify package payload
if: steps.npm_exists.outputs.already_published != 'true'
run: npm run pack:check
- name: Pack npm release artifact
id: pack
if: steps.npm_exists.outputs.already_published != 'true'
run: |
TARBALL_NAME="$(npm pack --json | jq -r '.[0].filename')"
echo "tarball_name=$TARBALL_NAME" >> "$GITHUB_OUTPUT"
- name: Upload npm release artifact
if: steps.npm_exists.outputs.already_published != 'true'
uses: actions/upload-artifact@v7
with:
name: npm-release-tarball
path: ${{ steps.pack.outputs.tarball_name }}
if-no-files-found: error
- name: Report already-published release state
if: steps.npm_exists.outputs.already_published == 'true'
run: |
echo "Skipping publish: ${{ steps.verify.outputs.package_name }}@${{ steps.verify.outputs.package_version }} is already published to npm."
publish-npm:
needs: validate
if: needs.validate.outputs.npm_already_published != 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "22.14.0"
registry-url: "https://registry.npmjs.org"
- name: Setup npm
run: npm install -g npm@11.12.1
- name: Download npm release artifact
uses: actions/download-artifact@v8
with:
name: npm-release-tarball
path: ./release-artifacts
- name: Publish to npm
run: |
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: 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 }}
secrets:
clawhub_token: ${{ secrets.CLAWHUB_TOKEN }}

View File

@ -2,13 +2,17 @@ name: Update OpenClaw SDK Surface
on:
schedule:
- cron: "17 9 * * *"
- cron: "*/10 * * * *"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: update-openclaw-sdk-surface
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@ -16,15 +20,18 @@ 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
- run: npm install openclaw@latest --save-exact --package-lock
- run: npm install @openclaw/plugin-inspector@latest --save-dev --save-exact --package-lock
- run: npm run sync:surface
- run: npm test
- run: npm run plugin:inspect:runtime
- name: Open pull request
id: pull_request
env:
GH_TOKEN: ${{ github.token }}
run: |
@ -32,16 +39,36 @@ jobs:
echo "OpenClaw SDK surface is already current."
exit 0
fi
version="$(node -p "require('./node_modules/openclaw/package.json').version")"
branch="automation/openclaw-sdk-${version}"
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}-${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 SDK surface to ${version}"
git push --force-with-lease origin "$branch"
gh pr create \
--base main \
--head "$branch" \
--title "Update OpenClaw SDK surface to ${version}" \
--body "Automated update of the pinned OpenClaw SDK package and generated kitchen-sink API surface."
git commit -m "Update OpenClaw fixture dependencies"
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 \
--base main \
--head "$branch" \
--title "Update OpenClaw fixture dependencies" \
--body "Automated update of openclaw@${openclaw_version}, @openclaw/plugin-inspector@${inspector_version}, and the generated kitchen-sink API surface.")"
fi
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
echo "openclaw_version=$openclaw_version" >> "$GITHUB_OUTPUT"
echo "inspector_version=$inspector_version" >> "$GITHUB_OUTPUT"
- name: Auto-merge pull request
if: ${{ steps.pull_request.outputs.url != '' }}
env:
GH_TOKEN: ${{ github.token }}
PR_URL: ${{ steps.pull_request.outputs.url }}
OPENCLAW_VERSION: ${{ steps.pull_request.outputs.openclaw_version }}
INSPECTOR_VERSION: ${{ steps.pull_request.outputs.inspector_version }}
run: |
gh pr merge "$PR_URL" \
--squash \
--delete-branch \
--subject "Update OpenClaw fixture dependencies" \
--body "Automated update of openclaw@${OPENCLAW_VERSION}, @openclaw/plugin-inspector@${INSPECTOR_VERSION}, and the generated kitchen-sink API surface."

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
node_modules/
reports/plugin-inspector-*
npm-debug.log*
*.tgz
.DS_Store

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`.

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 OpenClaw contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

168
README.md
View File

@ -1,33 +1,171 @@
# OpenClaw Kitchen Sink Plugin
# 🧽 OpenClaw Kitchen Sink Plugin
Credential-free OpenClaw plugin fixture that intentionally touches the public
plugin API surface.
plugin API surface and works as a kitchen sink boilerplate for plugin authors.
This repo is both:
- a readable example for plugin authors
- a dummy compatibility fixture for Crabpot and plugin-inspector
- 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
The generated files under `src/generated-*` are derived from the installed
`openclaw` package:
The generated fixture is derived from the installed `openclaw` package. It
extracts the public plugin surface from:
- registrar methods
- hook names
- manifest contract fields
- exported plugin SDK subpaths
It then writes explicit static evidence for those surfaces: hook registrations,
registrar calls with no-op callback payloads, SDK import coverage, and manifest
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
```
When Dependabot bumps `openclaw`, `npm test` verifies that this fixture still
covers every discovered hook, registrar, manifest contract key, and plugin SDK
export path for that package version.
The `Update OpenClaw SDK Surface` workflow automatically checks
`openclaw@latest` and `@openclaw/plugin-inspector@latest` every 10 minutes. When
either package changes, it regenerates the pinned dependency, lockfile,
manifest, hooks, registrars, and SDK import fixture files, runs the static and
runtime plugin-inspector checks, then creates and squash-merges its own
automation PR after those checks pass.
## Why Generated Files Exist
Dependabot still watches npm dependencies, but ignores `openclaw` and
`@openclaw/plugin-inspector` because those updates should flow through the
generated updater instead of package-only bump PRs.
Crabpot and plugin-inspector rely on static source evidence. The generated files
therefore contain explicit calls such as `api.registerTool(...)` and
`api.on("before_prompt_build", ...)` instead of dynamic loops.
## Publishing
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.
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",
"id": "openclaw-kitchen-sink-fixture",
"name": "OpenClaw Kitchen Sink",
"version": "0.1.0",
"description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.4.24.",
"version": "0.2.5",
"description": "Generated kitchen-sink fixture for OpenClaw plugin API surface 2026.5.7.",
"enabledByDefault": false,
"kind": [
"tool",
@ -14,25 +14,40 @@
"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"
"pluginId": "openclaw-kitchen-sink-fixture"
}
],
"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,40 +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": {
@ -117,6 +233,15 @@
"enabled": {
"type": "boolean",
"default": false
},
"personality": {
"type": "string",
"enum": [
"full",
"conformance",
"adversarial"
],
"default": "full"
}
}
}

3202
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,38 @@
{
"name": "openclaw-kitchen-sink-plugin",
"version": "0.1.0",
"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",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/Patrick-Erichsen/openclaw-kitchen-sink-plugin.git"
"url": "git+https://github.com/openclaw/kitchen-sink.git"
},
"bugs": {
"url": "https://github.com/openclaw/kitchen-sink/issues"
},
"homepage": "https://github.com/openclaw/kitchen-sink#readme",
"keywords": [
"openclaw",
"openclaw-plugin",
"crabpot",
"plugin-inspector"
],
"files": [
"src/",
"openclaw.plugin.json",
"plugin-inspector.config.json",
"README.md"
],
"publishConfig": {
"access": "public"
},
"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": {
@ -29,15 +45,45 @@
"setupEntry": "./src/setup.js",
"compat": {
"pluginApi": "2026.4"
},
"install": {
"clawhubSpec": "clawhub:@openclaw/kitchen-sink",
"npmSpec": "@openclaw/kitchen-sink",
"defaultChoice": "clawhub",
"minHostVersion": ">=2026.5.7"
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
},
"build": {
"openclawVersion": "2026.5.7",
"pluginSdkVersion": "2026.5.7"
}
},
"scripts": {
"check": "npm run sync:surface -- --check && node scripts/check-sdk-surface.mjs",
"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.24"
"openclaw": "2026.5.7"
},
"devDependencies": {
"@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,41 @@
{
"version": 1,
"plugin": {
"id": "openclaw-kitchen-sink-fixture",
"priority": "high",
"seams": [
"dynamic-tool",
"llm-observer",
"gateway-service",
"provider-capability",
"manifest-contract",
"sdk-surface"
],
"sourceRoot": "src",
"expect": {
"hooks": [
"before_prompt_build",
"before_tool_call",
"llm_input",
"llm_output",
"agent_end",
"gateway_start",
"gateway_stop"
],
"registrations": [
"registerTool",
"registerChannel",
"registerService",
"registerHttpRoute",
"registerSpeechProvider"
],
"manifestContracts": [
"tools",
"speechProviders"
]
}
},
"capture": {
"mockSdk": true
}
}

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

@ -0,0 +1,141 @@
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs";
const pack = spawnSync("npm", ["pack", "--dry-run", "--json"], {
cwd: process.cwd(),
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
if (pack.status !== 0) {
process.stderr.write(pack.stdout);
process.stderr.write(pack.stderr);
process.exit(pack.status ?? 1);
}
let payload;
try {
payload = JSON.parse(pack.stdout);
} catch (error) {
console.error(`npm pack did not return JSON: ${String(error)}`);
process.stderr.write(pack.stdout);
process.exit(1);
}
const files = new Set(payload[0]?.files?.map((file) => file.path) ?? []);
const requiredFiles = [
"package.json",
"README.md",
"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",
"src/generated-sdk-imports.ts",
];
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) {
return (
Array.isArray(actual) &&
actual.length === expected.length &&
actual.every((value, index) => value === expected[index])
);
}
function requireStringArray(name, actual, expected) {
if (!sameStringArray(actual, expected)) {
issues.push(`${name} must be ${JSON.stringify(expected)}`);
}
}
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"');
}
requireStringArray("openclaw.extensions", packageJson.openclaw?.extensions, ["./src/index.js"]);
requireStringArray("openclaw.runtimeExtensions", packageJson.openclaw?.runtimeExtensions, [
"./src/index.js",
]);
if (packageJson.openclaw?.setupEntry !== "./src/setup.js") {
issues.push('openclaw.setupEntry must be "./src/setup.js"');
}
const compatPluginApi = packageJson.openclaw?.compat?.pluginApi;
const buildOpenClawVersion = packageJson.openclaw?.build?.openclawVersion;
const buildPluginSdkVersion = packageJson.openclaw?.build?.pluginSdkVersion;
if (typeof compatPluginApi !== "string" || compatPluginApi.trim().length === 0) {
issues.push("openclaw.compat.pluginApi must be a non-empty string");
}
if (typeof buildOpenClawVersion !== "string" || buildOpenClawVersion.trim().length === 0) {
issues.push("openclaw.build.openclawVersion must be a non-empty string");
}
if (buildPluginSdkVersion !== buildOpenClawVersion) {
issues.push("openclaw.build.pluginSdkVersion must match openclaw.build.openclawVersion");
}
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- ")}`);
process.exit(1);
}
console.log(`Package payload OK: ${payload[0]?.entryCount ?? files.size} files, runtime ./src/index.js`);

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

@ -7,9 +7,11 @@ const check = process.argv.includes("--check");
const surface = readOpenClawSurface();
const generated = new Map([
["package.json", renderPackageJson(surface)],
["src/generated-hooks.js", renderHooks(surface)],
["src/generated-registrars.js", renderRegistrars(surface)],
["src/generated-sdk-imports.ts", renderSdkImports(surface)],
["src/index.js", renderRuntimeIndex(surface)],
["openclaw.plugin.json", renderManifest(surface)],
]);
@ -35,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);
}
`;
}
@ -54,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) {
@ -69,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(),
@ -109,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}";`)
@ -119,27 +125,130 @@ ${pluginSdkExports.map((_, index) => ` | typeof sdk${index}`).join("\n")};
`;
}
function renderRuntimeIndex() {
const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8"));
return `import { PLUGIN_ID } from "./constants.js";
import { registerAllHooks } from "./generated-hooks.js";
import { registerAllRegistrars } from "./generated-registrars.js";
import { registerKitchenSinkRuntime } from "./kitchen-runtime.js";
import {
KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
resolveKitchenSinkPersonality,
} from "./personality.js";
export const plugin = {
id: PLUGIN_ID,
name: "OpenClaw Kitchen Sink",
version: "${packageJson.version}",
description: "Credential-free fixture covering OpenClaw plugin API seams.",
expectedDiagnostics: KITCHEN_SINK_EXPECTED_DIAGNOSTICS,
register(api) {
const personality = resolveKitchenSinkPersonality(api);
registerAllHooks(api);
if (personality !== "conformance") {
registerAllRegistrars(api);
}
if (personality !== "adversarial") {
registerKitchenSinkRuntime(api, {
includeAgentToolResultMiddleware: personality !== "conformance",
});
}
},
};
export function register(api) {
plugin.register(api);
}
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",
id: "openclaw-kitchen-sink-fixture",
name: "OpenClaw Kitchen Sink",
version: "0.1.0",
version: packageJson.version,
description: `Generated kitchen-sink fixture for OpenClaw plugin API surface ${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" }],
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,
@ -150,12 +259,39 @@ function renderManifest({ manifestContracts, packageVersion }) {
additionalProperties: false,
properties: {
enabled: { type: "boolean", default: false },
personality: { type: "string", enum: ["full", "conformance", "adversarial"], default: "full" },
},
},
};
return `${JSON.stringify(manifest, null, 2)}\n`;
}
function renderPackageJson({ packageVersion }) {
const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8"));
packageJson.openclaw ??= {};
packageJson.openclaw.build = {
...(packageJson.openclaw.build ?? {}),
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;
}
return `${JSON.stringify(packageJson, null, 2)}\n`;
}
function header(packageVersion) {
return `// Generated by scripts/sync-surface.mjs from openclaw ${packageVersion}. Do not edit by hand.\n`;
}
@ -164,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,12 @@
// Generated by scripts/sync-surface.mjs from openclaw 2026.4.24. 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"));
api.on("before_compaction", kitchenSinkHook("before_compaction"));
@ -14,14 +17,18 @@ 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"));
api.on("message_received", kitchenSinkHook("message_received"));
api.on("message_sending", kitchenSinkHook("message_sending"));
api.on("message_sent", kitchenSinkHook("message_sent"));
api.on("model_call_ended", kitchenSinkHook("model_call_ended"));
api.on("model_call_started", kitchenSinkHook("model_call_started"));
api.on("reply_dispatch", kitchenSinkHook("reply_dispatch"));
api.on("session_end", kitchenSinkHook("session_end"));
api.on("session_start", kitchenSinkHook("session_start"));
@ -33,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.24. 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")));
@ -27,17 +29,24 @@ export function registerAllRegistrars(api) {
safeRegister("registerMemoryPromptSection", () => api.registerMemoryPromptSection(payloadFor("registerMemoryPromptSection")));
safeRegister("registerMemoryPromptSupplement", () => api.registerMemoryPromptSupplement(payloadFor("registerMemoryPromptSupplement")));
safeRegister("registerMemoryRuntime", () => api.registerMemoryRuntime(payloadFor("registerMemoryRuntime")));
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")));
@ -55,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.24. 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,303 +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-writes";
import type * as sdk43 from "openclaw/plugin-sdk/channel-contract";
import type * as sdk44 from "openclaw/plugin-sdk/channel-contract-testing";
import type * as sdk45 from "openclaw/plugin-sdk/channel-core";
import type * as sdk46 from "openclaw/plugin-sdk/channel-entry-contract";
import type * as sdk47 from "openclaw/plugin-sdk/channel-envelope";
import type * as sdk48 from "openclaw/plugin-sdk/channel-feedback";
import type * as sdk49 from "openclaw/plugin-sdk/channel-inbound";
import type * as sdk50 from "openclaw/plugin-sdk/channel-inbound-debounce";
import type * as sdk51 from "openclaw/plugin-sdk/channel-inbound-roots";
import type * as sdk52 from "openclaw/plugin-sdk/channel-lifecycle";
import type * as sdk53 from "openclaw/plugin-sdk/channel-location";
import type * as sdk54 from "openclaw/plugin-sdk/channel-logging";
import type * as sdk55 from "openclaw/plugin-sdk/channel-mention-gating";
import type * as sdk56 from "openclaw/plugin-sdk/channel-pairing";
import type * as sdk57 from "openclaw/plugin-sdk/channel-pairing-paths";
import type * as sdk58 from "openclaw/plugin-sdk/channel-plugin-common";
import type * as sdk59 from "openclaw/plugin-sdk/channel-policy";
import type * as sdk60 from "openclaw/plugin-sdk/channel-reply-options-runtime";
import type * as sdk61 from "openclaw/plugin-sdk/channel-reply-pipeline";
import type * as sdk62 from "openclaw/plugin-sdk/channel-runtime";
import type * as sdk63 from "openclaw/plugin-sdk/channel-runtime-context";
import type * as sdk64 from "openclaw/plugin-sdk/channel-secret-basic-runtime";
import type * as sdk65 from "openclaw/plugin-sdk/channel-secret-runtime";
import type * as sdk66 from "openclaw/plugin-sdk/channel-secret-tts-runtime";
import type * as sdk67 from "openclaw/plugin-sdk/channel-send-result";
import type * as sdk68 from "openclaw/plugin-sdk/channel-setup";
import type * as sdk69 from "openclaw/plugin-sdk/channel-status";
import type * as sdk70 from "openclaw/plugin-sdk/channel-streaming";
import type * as sdk71 from "openclaw/plugin-sdk/channel-targets";
import type * as sdk72 from "openclaw/plugin-sdk/cli-backend";
import type * as sdk73 from "openclaw/plugin-sdk/cli-runtime";
import type * as sdk74 from "openclaw/plugin-sdk/collection-runtime";
import type * as sdk75 from "openclaw/plugin-sdk/command-auth";
import type * as sdk76 from "openclaw/plugin-sdk/command-auth-native";
import type * as sdk77 from "openclaw/plugin-sdk/command-detection";
import type * as sdk78 from "openclaw/plugin-sdk/command-gating";
import type * as sdk79 from "openclaw/plugin-sdk/command-primitives-runtime";
import type * as sdk80 from "openclaw/plugin-sdk/command-status";
import type * as sdk81 from "openclaw/plugin-sdk/command-status-runtime";
import type * as sdk82 from "openclaw/plugin-sdk/command-surface";
import type * as sdk83 from "openclaw/plugin-sdk/compat";
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/context-visibility-runtime";
import type * as sdk87 from "openclaw/plugin-sdk/conversation-binding-runtime";
import type * as sdk88 from "openclaw/plugin-sdk/conversation-runtime";
import type * as sdk89 from "openclaw/plugin-sdk/core";
import type * as sdk90 from "openclaw/plugin-sdk/dangerous-name-runtime";
import type * as sdk91 from "openclaw/plugin-sdk/device-bootstrap";
import type * as sdk92 from "openclaw/plugin-sdk/diagnostic-runtime";
import type * as sdk93 from "openclaw/plugin-sdk/diagnostics-otel";
import type * as sdk94 from "openclaw/plugin-sdk/diffs";
import type * as sdk95 from "openclaw/plugin-sdk/direct-dm";
import type * as sdk96 from "openclaw/plugin-sdk/direct-dm-access";
import type * as sdk97 from "openclaw/plugin-sdk/direct-dm-guard-policy";
import type * as sdk98 from "openclaw/plugin-sdk/directory-config-runtime";
import type * as sdk99 from "openclaw/plugin-sdk/directory-runtime";
import type * as sdk100 from "openclaw/plugin-sdk/document-extractor";
import type * as sdk101 from "openclaw/plugin-sdk/error-runtime";
import type * as sdk102 from "openclaw/plugin-sdk/extension-shared";
import type * as sdk103 from "openclaw/plugin-sdk/feishu";
import type * as sdk104 from "openclaw/plugin-sdk/feishu-conversation";
import type * as sdk105 from "openclaw/plugin-sdk/feishu-setup";
import type * as sdk86 from "openclaw/plugin-sdk/config-types";
import type * as sdk87 from "openclaw/plugin-sdk/context-visibility-runtime";
import type * as sdk88 from "openclaw/plugin-sdk/conversation-binding-runtime";
import type * as sdk89 from "openclaw/plugin-sdk/conversation-runtime";
import type * as sdk90 from "openclaw/plugin-sdk/core";
import type * as sdk91 from "openclaw/plugin-sdk/cron-store-runtime";
import type * as sdk92 from "openclaw/plugin-sdk/dangerous-name-runtime";
import type * as sdk93 from "openclaw/plugin-sdk/dedupe-runtime";
import type * as sdk94 from "openclaw/plugin-sdk/delivery-queue-runtime";
import type * as sdk95 from "openclaw/plugin-sdk/device-bootstrap";
import type * as sdk96 from "openclaw/plugin-sdk/diagnostic-runtime";
import type * as sdk97 from "openclaw/plugin-sdk/direct-dm";
import type * as sdk98 from "openclaw/plugin-sdk/direct-dm-access";
import type * as sdk99 from "openclaw/plugin-sdk/direct-dm-guard-policy";
import type * as sdk100 from "openclaw/plugin-sdk/directory-config-runtime";
import type * as sdk101 from "openclaw/plugin-sdk/directory-runtime";
import type * as sdk102 from "openclaw/plugin-sdk/discord";
import type * as sdk103 from "openclaw/plugin-sdk/document-extractor";
import type * as sdk104 from "openclaw/plugin-sdk/error-runtime";
import type * as sdk105 from "openclaw/plugin-sdk/extension-shared";
import type * as sdk106 from "openclaw/plugin-sdk/fetch-runtime";
import type * as sdk107 from "openclaw/plugin-sdk/file-lock";
import type * as sdk108 from "openclaw/plugin-sdk/gateway-runtime";
import type * as sdk109 from "openclaw/plugin-sdk/github-copilot-login";
import type * as sdk110 from "openclaw/plugin-sdk/github-copilot-token";
import type * as sdk111 from "openclaw/plugin-sdk/global-singleton";
import type * as sdk112 from "openclaw/plugin-sdk/googlechat";
import type * as sdk113 from "openclaw/plugin-sdk/googlechat-runtime-shared";
import type * as sdk114 from "openclaw/plugin-sdk/group-access";
import type * as sdk115 from "openclaw/plugin-sdk/group-activation";
import type * as sdk116 from "openclaw/plugin-sdk/hook-runtime";
import type * as sdk117 from "openclaw/plugin-sdk/host-runtime";
import type * as sdk118 from "openclaw/plugin-sdk/image-generation";
import type * as sdk119 from "openclaw/plugin-sdk/image-generation-core";
import type * as sdk120 from "openclaw/plugin-sdk/image-generation-runtime";
import type * as sdk121 from "openclaw/plugin-sdk/inbound-envelope";
import type * as sdk122 from "openclaw/plugin-sdk/inbound-reply-dispatch";
import type * as sdk123 from "openclaw/plugin-sdk/infra-runtime";
import type * as sdk124 from "openclaw/plugin-sdk/interactive-runtime";
import type * as sdk125 from "openclaw/plugin-sdk/irc";
import type * as sdk126 from "openclaw/plugin-sdk/irc-surface";
import type * as sdk127 from "openclaw/plugin-sdk/json-store";
import type * as sdk128 from "openclaw/plugin-sdk/keyed-async-queue";
import type * as sdk129 from "openclaw/plugin-sdk/lazy-runtime";
import type * as sdk130 from "openclaw/plugin-sdk/line";
import type * as sdk131 from "openclaw/plugin-sdk/line-core";
import type * as sdk132 from "openclaw/plugin-sdk/line-runtime";
import type * as sdk133 from "openclaw/plugin-sdk/line-surface";
import type * as sdk134 from "openclaw/plugin-sdk/llm-task";
import type * as sdk135 from "openclaw/plugin-sdk/lmstudio";
import type * as sdk136 from "openclaw/plugin-sdk/lmstudio-runtime";
import type * as sdk137 from "openclaw/plugin-sdk/logging-core";
import type * as sdk138 from "openclaw/plugin-sdk/markdown-table-runtime";
import type * as sdk139 from "openclaw/plugin-sdk/matrix";
import type * as sdk140 from "openclaw/plugin-sdk/matrix-helper";
import type * as sdk141 from "openclaw/plugin-sdk/matrix-runtime-heavy";
import type * as sdk142 from "openclaw/plugin-sdk/matrix-runtime-shared";
import type * as sdk143 from "openclaw/plugin-sdk/matrix-runtime-surface";
import type * as sdk144 from "openclaw/plugin-sdk/matrix-surface";
import type * as sdk145 from "openclaw/plugin-sdk/matrix-thread-bindings";
import type * as sdk146 from "openclaw/plugin-sdk/mattermost";
import type * as sdk147 from "openclaw/plugin-sdk/mattermost-policy";
import type * as sdk148 from "openclaw/plugin-sdk/media-generation-runtime";
import type * as sdk149 from "openclaw/plugin-sdk/media-generation-runtime-shared";
import type * as sdk150 from "openclaw/plugin-sdk/media-mime";
import type * as sdk151 from "openclaw/plugin-sdk/media-runtime";
import type * as sdk152 from "openclaw/plugin-sdk/media-store";
import type * as sdk153 from "openclaw/plugin-sdk/media-understanding";
import type * as sdk154 from "openclaw/plugin-sdk/media-understanding-runtime";
import type * as sdk155 from "openclaw/plugin-sdk/memory-core";
import type * as sdk156 from "openclaw/plugin-sdk/memory-core-engine-runtime";
import type * as sdk157 from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
import type * as sdk158 from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import type * as sdk159 from "openclaw/plugin-sdk/memory-core-host-engine-qmd";
import type * as sdk160 from "openclaw/plugin-sdk/memory-core-host-engine-storage";
import type * as sdk161 from "openclaw/plugin-sdk/memory-core-host-events";
import type * as sdk162 from "openclaw/plugin-sdk/memory-core-host-multimodal";
import type * as sdk163 from "openclaw/plugin-sdk/memory-core-host-query";
import type * as sdk164 from "openclaw/plugin-sdk/memory-core-host-runtime-cli";
import type * as sdk165 from "openclaw/plugin-sdk/memory-core-host-runtime-core";
import type * as sdk166 from "openclaw/plugin-sdk/memory-core-host-runtime-files";
import type * as sdk167 from "openclaw/plugin-sdk/memory-core-host-secret";
import type * as sdk168 from "openclaw/plugin-sdk/memory-core-host-status";
import type * as sdk169 from "openclaw/plugin-sdk/memory-host-core";
import type * as sdk170 from "openclaw/plugin-sdk/memory-host-events";
import type * as sdk171 from "openclaw/plugin-sdk/memory-host-files";
import type * as sdk172 from "openclaw/plugin-sdk/memory-host-markdown";
import type * as sdk173 from "openclaw/plugin-sdk/memory-host-search";
import type * as sdk174 from "openclaw/plugin-sdk/memory-host-status";
import type * as sdk175 from "openclaw/plugin-sdk/memory-lancedb";
import type * as sdk176 from "openclaw/plugin-sdk/messaging-targets";
import type * as sdk177 from "openclaw/plugin-sdk/models-provider-runtime";
import type * as sdk178 from "openclaw/plugin-sdk/msteams";
import type * as sdk179 from "openclaw/plugin-sdk/music-generation";
import type * as sdk180 from "openclaw/plugin-sdk/music-generation-core";
import type * as sdk181 from "openclaw/plugin-sdk/native-command-config-runtime";
import type * as sdk182 from "openclaw/plugin-sdk/native-command-registry";
import type * as sdk183 from "openclaw/plugin-sdk/nextcloud-talk";
import type * as sdk184 from "openclaw/plugin-sdk/nostr";
import type * as sdk185 from "openclaw/plugin-sdk/opencode";
import type * as sdk186 from "openclaw/plugin-sdk/outbound-media";
import type * as sdk187 from "openclaw/plugin-sdk/outbound-runtime";
import type * as sdk188 from "openclaw/plugin-sdk/param-readers";
import type * as sdk189 from "openclaw/plugin-sdk/persistent-dedupe";
import type * as sdk190 from "openclaw/plugin-sdk/plugin-entry";
import type * as sdk191 from "openclaw/plugin-sdk/plugin-runtime";
import type * as sdk192 from "openclaw/plugin-sdk/poll-runtime";
import type * as sdk193 from "openclaw/plugin-sdk/process-runtime";
import type * as sdk194 from "openclaw/plugin-sdk/provider-auth";
import type * as sdk195 from "openclaw/plugin-sdk/provider-auth-api-key";
import type * as sdk196 from "openclaw/plugin-sdk/provider-auth-login";
import type * as sdk197 from "openclaw/plugin-sdk/provider-auth-result";
import type * as sdk198 from "openclaw/plugin-sdk/provider-auth-runtime";
import type * as sdk199 from "openclaw/plugin-sdk/provider-catalog-shared";
import type * as sdk200 from "openclaw/plugin-sdk/provider-entry";
import type * as sdk201 from "openclaw/plugin-sdk/provider-env-vars";
import type * as sdk202 from "openclaw/plugin-sdk/provider-http";
import type * as sdk203 from "openclaw/plugin-sdk/provider-model-shared";
import type * as sdk204 from "openclaw/plugin-sdk/provider-model-types";
import type * as sdk205 from "openclaw/plugin-sdk/provider-onboard";
import type * as sdk206 from "openclaw/plugin-sdk/provider-selection-runtime";
import type * as sdk207 from "openclaw/plugin-sdk/provider-setup";
import type * as sdk208 from "openclaw/plugin-sdk/provider-stream";
import type * as sdk209 from "openclaw/plugin-sdk/provider-stream-family";
import type * as sdk210 from "openclaw/plugin-sdk/provider-stream-shared";
import type * as sdk211 from "openclaw/plugin-sdk/provider-tools";
import type * as sdk212 from "openclaw/plugin-sdk/provider-transport-runtime";
import type * as sdk213 from "openclaw/plugin-sdk/provider-usage";
import type * as sdk214 from "openclaw/plugin-sdk/provider-web-fetch";
import type * as sdk215 from "openclaw/plugin-sdk/provider-web-fetch-contract";
import type * as sdk216 from "openclaw/plugin-sdk/provider-web-search";
import type * as sdk217 from "openclaw/plugin-sdk/provider-web-search-config-contract";
import type * as sdk218 from "openclaw/plugin-sdk/provider-web-search-contract";
import type * as sdk219 from "openclaw/plugin-sdk/provider-zai-endpoint";
import type * as sdk220 from "openclaw/plugin-sdk/proxy-capture";
import type * as sdk221 from "openclaw/plugin-sdk/qa-channel";
import type * as sdk222 from "openclaw/plugin-sdk/qa-channel-protocol";
import type * as sdk223 from "openclaw/plugin-sdk/qa-runner-runtime";
import type * as sdk224 from "openclaw/plugin-sdk/realtime-transcription";
import type * as sdk225 from "openclaw/plugin-sdk/realtime-voice";
import type * as sdk226 from "openclaw/plugin-sdk/reply-chunking";
import type * as sdk227 from "openclaw/plugin-sdk/reply-dedupe";
import type * as sdk228 from "openclaw/plugin-sdk/reply-dispatch-runtime";
import type * as sdk229 from "openclaw/plugin-sdk/reply-history";
import type * as sdk230 from "openclaw/plugin-sdk/reply-payload";
import type * as sdk231 from "openclaw/plugin-sdk/reply-reference";
import type * as sdk232 from "openclaw/plugin-sdk/reply-runtime";
import type * as sdk233 from "openclaw/plugin-sdk/request-url";
import type * as sdk234 from "openclaw/plugin-sdk/response-limit-runtime";
import type * as sdk235 from "openclaw/plugin-sdk/retry-runtime";
import type * as sdk236 from "openclaw/plugin-sdk/routing";
import type * as sdk237 from "openclaw/plugin-sdk/run-command";
import type * as sdk238 from "openclaw/plugin-sdk/runtime";
import type * as sdk239 from "openclaw/plugin-sdk/runtime-config-snapshot";
import type * as sdk240 from "openclaw/plugin-sdk/runtime-doctor";
import type * as sdk241 from "openclaw/plugin-sdk/runtime-env";
import type * as sdk242 from "openclaw/plugin-sdk/runtime-fetch";
import type * as sdk243 from "openclaw/plugin-sdk/runtime-group-policy";
import type * as sdk244 from "openclaw/plugin-sdk/runtime-logger";
import type * as sdk245 from "openclaw/plugin-sdk/runtime-secret-resolution";
import type * as sdk246 from "openclaw/plugin-sdk/runtime-store";
import type * as sdk247 from "openclaw/plugin-sdk/sandbox";
import type * as sdk248 from "openclaw/plugin-sdk/secret-file-runtime";
import type * as sdk249 from "openclaw/plugin-sdk/secret-input";
import type * as sdk250 from "openclaw/plugin-sdk/secret-input-runtime";
import type * as sdk251 from "openclaw/plugin-sdk/secret-ref-runtime";
import type * as sdk252 from "openclaw/plugin-sdk/security-runtime";
import type * as sdk253 from "openclaw/plugin-sdk/self-hosted-provider-setup";
import type * as sdk254 from "openclaw/plugin-sdk/session-binding-runtime";
import type * as sdk255 from "openclaw/plugin-sdk/session-key-runtime";
import type * as sdk256 from "openclaw/plugin-sdk/session-store-runtime";
import type * as sdk257 from "openclaw/plugin-sdk/session-transcript-hit";
import type * as sdk258 from "openclaw/plugin-sdk/session-visibility";
import type * as sdk259 from "openclaw/plugin-sdk/setup";
import type * as sdk260 from "openclaw/plugin-sdk/setup-adapter-runtime";
import type * as sdk261 from "openclaw/plugin-sdk/setup-runtime";
import type * as sdk262 from "openclaw/plugin-sdk/setup-tools";
import type * as sdk263 from "openclaw/plugin-sdk/simple-completion-runtime";
import type * as sdk264 from "openclaw/plugin-sdk/skill-commands-runtime";
import type * as sdk265 from "openclaw/plugin-sdk/skills-runtime";
import type * as sdk266 from "openclaw/plugin-sdk/speech";
import type * as sdk267 from "openclaw/plugin-sdk/speech-core";
import type * as sdk268 from "openclaw/plugin-sdk/ssrf-dispatcher";
import type * as sdk269 from "openclaw/plugin-sdk/ssrf-policy";
import type * as sdk270 from "openclaw/plugin-sdk/ssrf-runtime";
import type * as sdk271 from "openclaw/plugin-sdk/state-paths";
import type * as sdk272 from "openclaw/plugin-sdk/status-helpers";
import type * as sdk273 from "openclaw/plugin-sdk/string-coerce-runtime";
import type * as sdk274 from "openclaw/plugin-sdk/string-normalization-runtime";
import type * as sdk275 from "openclaw/plugin-sdk/target-resolver-runtime";
import type * as sdk276 from "openclaw/plugin-sdk/telegram-command-config";
import type * as sdk277 from "openclaw/plugin-sdk/telegram-command-ui";
import type * as sdk278 from "openclaw/plugin-sdk/temp-path";
import type * as sdk279 from "openclaw/plugin-sdk/testing";
import type * as sdk280 from "openclaw/plugin-sdk/text-autolink-runtime";
import type * as sdk281 from "openclaw/plugin-sdk/text-chunking";
import type * as sdk282 from "openclaw/plugin-sdk/text-runtime";
import type * as sdk283 from "openclaw/plugin-sdk/thread-bindings-runtime";
import type * as sdk284 from "openclaw/plugin-sdk/thread-bindings-session-runtime";
import type * as sdk285 from "openclaw/plugin-sdk/thread-ownership";
import type * as sdk286 from "openclaw/plugin-sdk/tlon";
import type * as sdk287 from "openclaw/plugin-sdk/tool-payload";
import type * as sdk288 from "openclaw/plugin-sdk/tool-send";
import type * as sdk289 from "openclaw/plugin-sdk/twitch";
import type * as sdk290 from "openclaw/plugin-sdk/video-generation";
import type * as sdk291 from "openclaw/plugin-sdk/video-generation-core";
import type * as sdk292 from "openclaw/plugin-sdk/video-generation-runtime";
import type * as sdk293 from "openclaw/plugin-sdk/voice-call";
import type * as sdk294 from "openclaw/plugin-sdk/volc-model-catalog-shared";
import type * as sdk295 from "openclaw/plugin-sdk/web-content-extractor";
import type * as sdk296 from "openclaw/plugin-sdk/web-media";
import type * as sdk297 from "openclaw/plugin-sdk/webhook-ingress";
import type * as sdk298 from "openclaw/plugin-sdk/webhook-path";
import type * as sdk299 from "openclaw/plugin-sdk/webhook-request-guards";
import type * as sdk300 from "openclaw/plugin-sdk/webhook-targets";
import type * as sdk301 from "openclaw/plugin-sdk/windows-spawn";
import type * as sdk302 from "openclaw/plugin-sdk/zalo";
import type * as sdk303 from "openclaw/plugin-sdk/zalo-setup";
import type * as sdk304 from "openclaw/plugin-sdk/zalouser";
import type * as sdk305 from "openclaw/plugin-sdk/zod";
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
@ -599,17 +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 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",
id: PLUGIN_ID,
name: "OpenClaw Kitchen Sink",
version: "0.1.0",
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