Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
123
qa/scenarios/plugins/bundled-plugin-skill-runtime.yaml
Normal file
123
qa/scenarios/plugins/bundled-plugin-skill-runtime.yaml
Normal file
@@ -0,0 +1,123 @@
|
||||
title: Bundled plugin skill runtime
|
||||
|
||||
scenario:
|
||||
id: bundled-plugin-skill-runtime
|
||||
surface: skills
|
||||
coverage:
|
||||
primary:
|
||||
- plugins.skills
|
||||
secondary:
|
||||
- plugins.runtime
|
||||
objective: Verify packaged bundled plugin skills load from dist-runtime instead of being skipped by path-containment checks.
|
||||
successCriteria:
|
||||
- The runtime-packaged bundled plugin tree is used as OPENCLAW_BUNDLED_PLUGINS_DIR.
|
||||
- The enabled bundled plugin skill is reported as eligible by the skills CLI.
|
||||
- The check fails on SKILL.md symlink escapes and passes when runtime staging copies SKILL.md as a real file.
|
||||
docsRefs:
|
||||
- docs/tools/skills.md
|
||||
- docs/plugins/manifest.md
|
||||
codeRefs:
|
||||
- scripts/stage-bundled-plugin-runtime.mjs
|
||||
- src/agents/skills/workspace.ts
|
||||
- src/agents/skills/plugin-skills.ts
|
||||
execution:
|
||||
kind: flow
|
||||
summary: Force the packaged dist-runtime plugin tree and verify an enabled bundled plugin skill survives discovery.
|
||||
config:
|
||||
pluginId: open-prose
|
||||
expectedSkillName: prose
|
||||
|
||||
flow:
|
||||
steps:
|
||||
- name: loads a bundled plugin skill from dist-runtime
|
||||
actions:
|
||||
- set: skillCheck
|
||||
value:
|
||||
expr: |-
|
||||
(async () => {
|
||||
const { spawnSync } = await qaImport("node:child_process");
|
||||
const fsSync = await qaImport("node:fs");
|
||||
const distRuntimeExtensions = path.join(env.repoRoot, "dist-runtime", "extensions");
|
||||
const skillPath = path.join(
|
||||
distRuntimeExtensions,
|
||||
config.pluginId,
|
||||
"skills",
|
||||
config.expectedSkillName,
|
||||
"SKILL.md",
|
||||
);
|
||||
const tempRoot = await fs.mkdtemp(path.join(env.gateway.tempRoot, "bundled-skill-runtime-"));
|
||||
const homeDir = path.join(tempRoot, "home");
|
||||
const stateDir = path.join(tempRoot, "state");
|
||||
const workspaceDir = path.join(tempRoot, "workspace");
|
||||
const xdgConfigHome = path.join(tempRoot, "xdg-config");
|
||||
const xdgDataHome = path.join(tempRoot, "xdg-data");
|
||||
const xdgCacheHome = path.join(tempRoot, "xdg-cache");
|
||||
await Promise.all(
|
||||
[homeDir, stateDir, workspaceDir, xdgConfigHome, xdgDataHome, xdgCacheHome].map((dir) =>
|
||||
fs.mkdir(dir, { recursive: true }),
|
||||
),
|
||||
);
|
||||
const configPath = path.join(tempRoot, "openclaw.json");
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
agents: { defaults: { workspace: workspaceDir } },
|
||||
plugins: {
|
||||
allow: [config.pluginId],
|
||||
entries: { [config.pluginId]: { enabled: true } },
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
const cliEnv = {
|
||||
...env.gateway.runtimeEnv,
|
||||
HOME: homeDir,
|
||||
OPENCLAW_HOME: homeDir,
|
||||
OPENCLAW_CONFIG_PATH: configPath,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_OAUTH_DIR: path.join(stateDir, "credentials"),
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: distRuntimeExtensions,
|
||||
XDG_CONFIG_HOME: xdgConfigHome,
|
||||
XDG_DATA_HOME: xdgDataHome,
|
||||
XDG_CACHE_HOME: xdgCacheHome,
|
||||
};
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[path.join(env.repoRoot, "dist", "index.js"), "skills", "list", "--json", "--eligible"],
|
||||
{
|
||||
cwd: tempRoot,
|
||||
env: cliEnv,
|
||||
encoding: "utf8",
|
||||
timeout: 60000,
|
||||
},
|
||||
);
|
||||
let parsed = null;
|
||||
let parseError = null;
|
||||
try {
|
||||
parsed = result.stdout ? JSON.parse(result.stdout) : null;
|
||||
} catch (error) {
|
||||
parseError = formatErrorMessage(error);
|
||||
}
|
||||
const skills = Array.isArray(parsed?.skills) ? parsed.skills : [];
|
||||
const skill = skills.find((entry) => entry?.name === config.expectedSkillName);
|
||||
return {
|
||||
exitCode: result.status,
|
||||
signal: result.signal,
|
||||
parseError,
|
||||
skill,
|
||||
skillNames: skills.map((entry) => entry?.name).filter(Boolean).sort(),
|
||||
skillPath: path.relative(env.repoRoot, skillPath),
|
||||
skillMdSymlink: fsSync.existsSync(skillPath) ? fsSync.lstatSync(skillPath).isSymbolicLink() : null,
|
||||
stderr: String(result.stderr ?? "").replaceAll(env.repoRoot, "<repo>").trim().slice(0, 1200),
|
||||
};
|
||||
})()
|
||||
- assert:
|
||||
expr: "skillCheck.exitCode === 0 && skillCheck.skill?.eligible === true && !skillCheck.skill?.disabled && !skillCheck.skill?.blockedByAllowlist"
|
||||
message:
|
||||
expr: |-
|
||||
`expected bundled plugin skill "${config.expectedSkillName}" from "${config.pluginId}" to load from dist-runtime; got ${JSON.stringify(skillCheck.skill)}; SKILL.md symlink=${skillCheck.skillMdSymlink}; stderr=${skillCheck.stderr || "(empty)"}`
|
||||
detailsExpr: skillCheck
|
||||
32
qa/scenarios/plugins/clawhub-marketplace-list.yaml
Normal file
32
qa/scenarios/plugins/clawhub-marketplace-list.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
title: ClawHub marketplace list package lane
|
||||
|
||||
scenario:
|
||||
id: clawhub-marketplace-list
|
||||
surface: clawhub-and-external-plugin-distribution
|
||||
category: clawhub-and-external-plugin-distribution.plugin-lifecycle-and-health
|
||||
coverage:
|
||||
primary:
|
||||
- clawhub.marketplace-list
|
||||
secondary:
|
||||
- clawhub.update-by-plugin-id
|
||||
- clawhub.uninstall-config-index-policy-file-cleanup
|
||||
objective: Verify package-installed marketplace listing, install, update, and uninstall behavior through the release plugin marketplace lane.
|
||||
successCriteria:
|
||||
- A fixture marketplace is written into the package-installed home.
|
||||
- The package-installed CLI lists the fixture marketplace as JSON and includes the expected plugin.
|
||||
- The marketplace plugin installs by marketplace shortcut, exposes its CLI, updates by plugin id, and exposes the updated CLI output.
|
||||
- Uninstall removes the plugin CLI and install metadata.
|
||||
docsRefs:
|
||||
- docs/help/testing.md
|
||||
- docs/concepts/qa-e2e-automation.md
|
||||
codeRefs:
|
||||
- scripts/e2e/release-plugin-marketplace-docker.sh
|
||||
- scripts/e2e/lib/release-plugin-marketplace/scenario.sh
|
||||
- scripts/e2e/lib/release-scenarios/write-marketplace.mjs
|
||||
execution:
|
||||
kind: script
|
||||
path: test/e2e/qa-lab/runtime/docker-e2e-lane.ts
|
||||
summary: Runs the release-plugin-marketplace Docker E2E lane as QA Lab evidence for ClawHub marketplace list coverage.
|
||||
args:
|
||||
- --lane
|
||||
- release-plugin-marketplace
|
||||
@@ -0,0 +1,35 @@
|
||||
title: ClawHub release candidate package install proof
|
||||
|
||||
scenario:
|
||||
id: clawhub-release-candidate-checklist
|
||||
surface: clawhub-and-external-plugin-distribution
|
||||
category: clawhub-and-external-plugin-distribution.plugin-lifecycle-and-health
|
||||
coverage:
|
||||
primary:
|
||||
- clawhub.npm-pack-local-release-candidate-installs
|
||||
objective: Verify a release-candidate npm pack installs and updates through real package semantics before publishing.
|
||||
successCriteria:
|
||||
- The producer resolves an explicit candidate .tgz or an explicitly requested checkout-built tarball.
|
||||
- The candidate tarball has package metadata and build metadata before the install lane runs.
|
||||
- The Parallels npm-update lane hosts the candidate tarball, runs fresh install proof, and runs same-guest update proof.
|
||||
- Primary evidence is pass only when at least one selected platform passes fresh target install and at least one selected platform passes update.
|
||||
- Missing candidate tarball or Parallels prerequisites are reported as blocked evidence, not false passing evidence.
|
||||
docsRefs:
|
||||
- docs/help/testing.md
|
||||
- docs/concepts/qa-e2e-automation.md
|
||||
codeRefs:
|
||||
- test/e2e/qa-lab/plugins/clawhub-release-candidate-install.ts
|
||||
- scripts/e2e/parallels-npm-update-smoke.sh
|
||||
- scripts/e2e/parallels/npm-update-smoke.ts
|
||||
- test/scripts/release-candidate-checklist.test.ts
|
||||
execution:
|
||||
kind: script
|
||||
path: test/e2e/qa-lab/plugins/clawhub-release-candidate-install.ts
|
||||
summary: Runs the Parallels npm-update install proof against a candidate release tarball and emits QA Lab evidence.
|
||||
allowBlockedEvidence: true
|
||||
timeoutMs: 14400000
|
||||
args:
|
||||
- --artifact-base
|
||||
- ${outputDir}
|
||||
- --tarball-env
|
||||
- OPENCLAW_QA_RELEASE_CANDIDATE_TARBALL
|
||||
22
qa/scenarios/plugins/clawhub-skill-installs.yaml
Normal file
22
qa/scenarios/plugins/clawhub-skill-installs.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
title: ClawHub skill install evidence
|
||||
|
||||
scenario:
|
||||
id: clawhub-skill-installs
|
||||
surface: clawhub-and-external-plugin-distribution
|
||||
category: clawhub-and-external-plugin-distribution.plugin-lifecycle-and-health
|
||||
coverage:
|
||||
primary:
|
||||
- clawhub.skill-installs
|
||||
objective: Link ClawHub-backed skill install e2e coverage to ClawHub maturity accounting.
|
||||
successCriteria:
|
||||
- The CLI resolves a ClawHub skill install descriptor.
|
||||
- The GitHub-backed skill archive is downloaded and installed into the state directory.
|
||||
- Install telemetry reports the installed skill slug and version.
|
||||
docsRefs:
|
||||
- docs/help/testing.md
|
||||
codeRefs:
|
||||
- src/cli/skills-cli.clawhub-install.e2e.test.ts
|
||||
execution:
|
||||
kind: vitest
|
||||
path: src/cli/skills-cli.clawhub-install.e2e.test.ts
|
||||
summary: Vitest e2e coverage for ClawHub-backed skill installs.
|
||||
514
qa/scenarios/plugins/kitchen-sink-live-openai.yaml
Normal file
514
qa/scenarios/plugins/kitchen-sink-live-openai.yaml
Normal file
@@ -0,0 +1,514 @@
|
||||
title: "Kitchen Sink live OpenAI plugin gauntlet"
|
||||
|
||||
scenario:
|
||||
id: kitchen-sink-live-openai
|
||||
surface: plugins
|
||||
category: pre-release
|
||||
coverage:
|
||||
primary:
|
||||
- plugins.kitchen-sink
|
||||
secondary:
|
||||
- plugins.lifecycle
|
||||
- plugins.plugin-tools
|
||||
- models.live-openai
|
||||
- gateway.performance
|
||||
risk: high
|
||||
objective: Verify the external Kitchen Sink plugin can be installed into a qa-lab gateway, expose its major runtime surfaces, and coexist with a live OpenAI provider turn.
|
||||
successCriteria:
|
||||
- The npm Kitchen Sink package installs, enables, and inspects as loaded.
|
||||
- Kitchen Sink command inventory, MCP tool, and channel status work after gateway restart.
|
||||
- A live OpenAI turn either completes or surfaces the Kitchen Sink plugin's before-agent-run block without hanging.
|
||||
- Gateway logs and process metrics are captured and stay under broad anomaly thresholds.
|
||||
docsRefs:
|
||||
- docs/concepts/qa-e2e-automation.md
|
||||
- docs/channels/qa-channel.md
|
||||
- docs/plugins/manifest.md
|
||||
codeRefs:
|
||||
- extensions/qa-lab/src/suite.ts
|
||||
- extensions/qa-lab/src/gateway-child.ts
|
||||
- scripts/e2e/kitchen-sink-plugin-docker.sh
|
||||
execution:
|
||||
kind: flow
|
||||
suiteIsolation: isolated
|
||||
isolationReason: Mutates gateway plugin/channel/tool config across gateway restarts.
|
||||
summary: Install @openclaw/kitchen-sink, restart the gateway, exercise command inventory/tool/channel/OpenAI-or-block paths, and record CPU/RSS/log evidence.
|
||||
config:
|
||||
requiredProviderMode: live-frontier
|
||||
requiredProvider: openai
|
||||
pluginSpec: npm:@openclaw/kitchen-sink@latest
|
||||
pluginId: openclaw-kitchen-sink-fixture
|
||||
pluginPersonality: conformance
|
||||
adversarialPersonality: adversarial
|
||||
channelId: kitchen-sink-channel
|
||||
channelAccountId: local
|
||||
textProviderId: kitchen-sink-llm
|
||||
textModel: kitchen-sink-text-v1
|
||||
expectedProviderAny:
|
||||
- kitchen-sink-provider
|
||||
- kitchen-sink-llm
|
||||
expectedToolAny:
|
||||
- kitchen_sink_text
|
||||
- kitchen_sink_search
|
||||
- kitchen_sink_image_job
|
||||
expectedSurfaceIds:
|
||||
speechProviderIds:
|
||||
- kitchen-sink-speech
|
||||
- kitchen-sink-speech-provider
|
||||
realtimeTranscriptionProviderIds:
|
||||
- kitchen-sink-realtime-transcription
|
||||
- kitchen-sink-realtime-transcription-provider
|
||||
realtimeVoiceProviderIds:
|
||||
- kitchen-sink-realtime-voice
|
||||
- kitchen-sink-realtime-voice-provider
|
||||
mediaUnderstandingProviderIds:
|
||||
- kitchen-sink-media
|
||||
- kitchen-sink-media-understanding-provider
|
||||
imageGenerationProviderIds:
|
||||
- kitchen-sink-image
|
||||
- kitchen-sink-image-generation-provider
|
||||
videoGenerationProviderIds:
|
||||
- kitchen-sink-video
|
||||
- kitchen-sink-video-generation-provider
|
||||
musicGenerationProviderIds:
|
||||
- kitchen-sink-music
|
||||
- kitchen-sink-music-generation-provider
|
||||
webFetchProviderIds:
|
||||
- kitchen-sink-fetch
|
||||
- kitchen-sink-web-fetch-provider
|
||||
webSearchProviderIds:
|
||||
- kitchen-sink-search
|
||||
- kitchen-sink-web-search-provider
|
||||
migrationProviderIds:
|
||||
- kitchen-sink-migration-providers
|
||||
- kitchen-sink-migration-provider
|
||||
maxGatewayCpuCoreRatio: 1.5
|
||||
maxGatewayRssMiB: 2048
|
||||
agentTurnTimeoutMs: 120000
|
||||
outboundTimeoutMs: 60000
|
||||
livePrompt: "Kitchen Sink OpenAI marker. Reply exactly: KITCHEN-SINK-OPENAI-OK"
|
||||
blockedReplyNeedle: "blocked by openclaw-kitchen-sink-fixture"
|
||||
expectedAdversarialDiagnostics:
|
||||
- agent event subscription registration requires id and handle
|
||||
- agent tool result middleware must be a function
|
||||
- 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
|
||||
- hosted media resolver registration missing resolver
|
||||
- "http route registration missing or invalid auth: /kitchen-sink/http-route"
|
||||
- "plugin must declare contracts.embeddingProviders for adapter: kitchen-sink-embedding-provider"
|
||||
- "plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider"
|
||||
- "trusted tool policy registration requires id, description, and evaluate()"
|
||||
- memory prompt supplement registration missing builder
|
||||
- model catalog provider registration missing provider
|
||||
- node invoke policy registration missing commands
|
||||
- session extension registration requires namespace and description
|
||||
- session scheduler job registration requires unique id, sessionKey, and kind
|
||||
- "plugin must declare contracts.tools for: kitchen-sink-tool"
|
||||
- tool metadata registration missing toolName
|
||||
|
||||
flow:
|
||||
steps:
|
||||
- name: installs and inspects the Kitchen Sink plugin
|
||||
actions:
|
||||
- call: runQaCli
|
||||
args:
|
||||
- ref: env
|
||||
- - plugins
|
||||
- install
|
||||
- expr: config.pluginSpec
|
||||
- timeoutMs: 180000
|
||||
- call: runQaCli
|
||||
args:
|
||||
- ref: env
|
||||
- - plugins
|
||||
- enable
|
||||
- expr: config.pluginId
|
||||
- timeoutMs: 60000
|
||||
- set: configuredPluginPath
|
||||
value:
|
||||
expr: |-
|
||||
(async () => {
|
||||
const raw = await fs.readFile(env.gateway.configPath, "utf8").catch(() => "{}");
|
||||
const cfg = JSON.parse(raw || "{}");
|
||||
cfg.plugins = cfg.plugins || {};
|
||||
cfg.plugins.allow = [...new Set([...(cfg.plugins.allow || []), config.pluginId])];
|
||||
cfg.plugins.entries = cfg.plugins.entries || {};
|
||||
cfg.plugins.entries[config.pluginId] = {
|
||||
...(cfg.plugins.entries[config.pluginId] || {}),
|
||||
enabled: true,
|
||||
config: {
|
||||
...(cfg.plugins.entries[config.pluginId]?.config || {}),
|
||||
personality: config.pluginPersonality,
|
||||
},
|
||||
hooks: {
|
||||
...(cfg.plugins.entries[config.pluginId]?.hooks || {}),
|
||||
allowConversationAccess: true,
|
||||
},
|
||||
};
|
||||
cfg.channels = {
|
||||
...(cfg.channels || {}),
|
||||
[config.channelId]: { enabled: true, token: "kitchen-sink-qa" },
|
||||
};
|
||||
cfg.tools = {
|
||||
...(cfg.tools || {}),
|
||||
alsoAllow: [...new Set([...(cfg.tools?.alsoAllow || []), ...config.expectedToolAny])],
|
||||
};
|
||||
await fs.writeFile(env.gateway.configPath, `${JSON.stringify(cfg, null, 2)}\n`, "utf8");
|
||||
return env.gateway.configPath;
|
||||
})()
|
||||
- call: runQaCli
|
||||
saveAs: pluginList
|
||||
args:
|
||||
- ref: env
|
||||
- - plugins
|
||||
- list
|
||||
- --json
|
||||
- json: true
|
||||
timeoutMs: 60000
|
||||
- call: runQaCli
|
||||
saveAs: inspect
|
||||
args:
|
||||
- ref: env
|
||||
- - plugins
|
||||
- inspect
|
||||
- expr: config.pluginId
|
||||
- --runtime
|
||||
- --json
|
||||
- json: true
|
||||
timeoutMs: 60000
|
||||
- set: inspectFacts
|
||||
value:
|
||||
expr: |-
|
||||
(() => {
|
||||
const plugin = inspect.plugin ?? {};
|
||||
const namesFromTools = Array.isArray(inspect.tools)
|
||||
? inspect.tools.flatMap((entry) => Array.isArray(entry?.names) ? entry.names : [entry?.name]).filter(Boolean)
|
||||
: [];
|
||||
const contracts = plugin.contracts && typeof plugin.contracts === "object" ? plugin.contracts : {};
|
||||
return {
|
||||
id: plugin.id,
|
||||
enabled: plugin.enabled,
|
||||
status: plugin.status,
|
||||
channels: [...new Set([...(plugin.channelIds ?? []), ...(plugin.channels ?? [])])],
|
||||
providers: [...new Set([...(plugin.providerIds ?? []), ...(plugin.providers ?? [])])],
|
||||
tools: [...new Set([...namesFromTools, ...(contracts.tools ?? [])])],
|
||||
commands: inspect.commands ?? [],
|
||||
services: inspect.services ?? [],
|
||||
typedHookCount: Array.isArray(inspect.typedHooks) ? inspect.typedHooks.length : 0,
|
||||
hookCount: plugin.hookCount ?? 0,
|
||||
surfaceIds: Object.fromEntries(
|
||||
Object.keys(config.expectedSurfaceIds ?? {})
|
||||
.map((field) => [field, Array.isArray(plugin[field]) ? plugin[field] : []])
|
||||
),
|
||||
agentHarnessIds: plugin.agentHarnessIds ?? [],
|
||||
diagnostics: [...(pluginList.diagnostics ?? []), ...(inspect.diagnostics ?? [])]
|
||||
.filter((entry) => entry?.level === "error")
|
||||
.map((entry) => String(entry.message ?? "")),
|
||||
unexpectedDiagnostics: [...new Set([...(pluginList.diagnostics ?? []), ...(inspect.diagnostics ?? [])]
|
||||
.filter((entry) => entry?.level === "error")
|
||||
.map((entry) => String(entry.message ?? ""))
|
||||
.filter((message) => !config.expectedAdversarialDiagnostics.includes(message)))],
|
||||
};
|
||||
})()
|
||||
- assert:
|
||||
expr: "inspectFacts.id === config.pluginId && inspectFacts.enabled === true && inspectFacts.status === 'loaded'"
|
||||
message:
|
||||
expr: "`Kitchen Sink plugin did not inspect as enabled+loaded: ${JSON.stringify(inspectFacts)}`"
|
||||
- assert:
|
||||
expr: "inspectFacts.channels.includes(config.channelId)"
|
||||
message:
|
||||
expr: "`Kitchen Sink channel missing from inspect output: ${JSON.stringify(inspectFacts.channels)}`"
|
||||
- assert:
|
||||
expr: "config.expectedProviderAny.some((provider) => inspectFacts.providers.includes(provider))"
|
||||
message:
|
||||
expr: "`Kitchen Sink providers missing from inspect output: ${JSON.stringify(inspectFacts.providers)}`"
|
||||
- assert:
|
||||
expr: "config.expectedToolAny.some((tool) => inspectFacts.tools.includes(tool))"
|
||||
message:
|
||||
expr: "`Kitchen Sink tools missing from inspect output: ${JSON.stringify(inspectFacts.tools)}`"
|
||||
- assert:
|
||||
expr: "Object.entries(config.expectedSurfaceIds).every(([field, expected]) => expected.some((id) => (inspectFacts.surfaceIds[field] ?? []).includes(id)))"
|
||||
message:
|
||||
expr: "`Kitchen Sink SDK provider surface missing from inspect output: ${JSON.stringify(inspectFacts.surfaceIds)}`"
|
||||
- assert:
|
||||
expr: "inspectFacts.commands.includes('kitchen') && inspectFacts.services.includes('kitchen-sink-service')"
|
||||
message:
|
||||
expr: "`Kitchen Sink command/service surfaces missing: ${JSON.stringify({ commands: inspectFacts.commands, services: inspectFacts.services })}`"
|
||||
- assert:
|
||||
expr: "inspectFacts.hookCount >= 30 && inspectFacts.typedHookCount >= 30"
|
||||
message:
|
||||
expr: "`Kitchen Sink hook surfaces missing: ${JSON.stringify({ hookCount: inspectFacts.hookCount, typedHookCount: inspectFacts.typedHookCount })}`"
|
||||
- assert:
|
||||
expr: "!inspectFacts.agentHarnessIds.includes('kitchen-sink-agent-harness')"
|
||||
message:
|
||||
expr: "`External Kitchen Sink plugin unexpectedly registered bundled-only agent harness: ${JSON.stringify(inspectFacts.agentHarnessIds)}`"
|
||||
- assert:
|
||||
expr: "inspectFacts.unexpectedDiagnostics.length === 0"
|
||||
message:
|
||||
expr: "`Kitchen Sink conformance personality emitted unexpected diagnostics: ${JSON.stringify(inspectFacts.unexpectedDiagnostics)}`"
|
||||
detailsExpr: inspectFacts
|
||||
|
||||
- name: restarts gateway with Kitchen Sink configured
|
||||
actions:
|
||||
- assert:
|
||||
expr: "typeof env.gateway.restartAfterStateMutation === 'function'"
|
||||
message: "qa gateway child does not expose restartAfterStateMutation"
|
||||
- call: env.gateway.restartAfterStateMutation
|
||||
args:
|
||||
- lambda:
|
||||
async: true
|
||||
params: [ctx]
|
||||
expr: |-
|
||||
(async () => {
|
||||
const raw = await fs.readFile(ctx.configPath, "utf8").catch(() => "{}");
|
||||
const cfg = JSON.parse(raw || "{}");
|
||||
cfg.plugins = cfg.plugins || {};
|
||||
cfg.plugins.allow = [...new Set([...(cfg.plugins.allow || []), config.pluginId])];
|
||||
cfg.plugins.entries = cfg.plugins.entries || {};
|
||||
cfg.plugins.entries[config.pluginId] = {
|
||||
...(cfg.plugins.entries[config.pluginId] || {}),
|
||||
enabled: true,
|
||||
config: {
|
||||
...(cfg.plugins.entries[config.pluginId]?.config || {}),
|
||||
personality: config.pluginPersonality,
|
||||
},
|
||||
hooks: {
|
||||
...(cfg.plugins.entries[config.pluginId]?.hooks || {}),
|
||||
allowConversationAccess: true,
|
||||
},
|
||||
};
|
||||
cfg.channels = {
|
||||
...(cfg.channels || {}),
|
||||
[config.channelId]: { enabled: true, token: "kitchen-sink-qa" },
|
||||
};
|
||||
cfg.tools = {
|
||||
...(cfg.tools || {}),
|
||||
alsoAllow: [...new Set([...(cfg.tools?.alsoAllow || []), ...config.expectedToolAny])],
|
||||
};
|
||||
await fs.writeFile(ctx.configPath, `${JSON.stringify(cfg, null, 2)}\n`, "utf8");
|
||||
})()
|
||||
- call: waitForGatewayHealthy
|
||||
args:
|
||||
- ref: env
|
||||
- 120000
|
||||
- call: fetchJson
|
||||
saveAs: healthz
|
||||
args:
|
||||
- expr: "`${env.gateway.baseUrl}/healthz`"
|
||||
- call: fetchJson
|
||||
saveAs: readyz
|
||||
args:
|
||||
- expr: "`${env.gateway.baseUrl}/readyz`"
|
||||
- assert:
|
||||
expr: "healthz?.ok === true && healthz?.status === 'live'"
|
||||
message:
|
||||
expr: "`/healthz did not report live: ${JSON.stringify(healthz)}`"
|
||||
- assert:
|
||||
expr: "readyz?.ready === true"
|
||||
message:
|
||||
expr: "`/readyz did not report ready: ${JSON.stringify(readyz)}`"
|
||||
- call: waitForQaChannelReady
|
||||
args:
|
||||
- ref: env
|
||||
- 120000
|
||||
- set: perfStartedAtMs
|
||||
value:
|
||||
expr: "Date.now()"
|
||||
- set: cpuStartMs
|
||||
value:
|
||||
expr: "env.gateway.getProcessCpuMs?.() ?? null"
|
||||
- set: rssStartBytes
|
||||
value:
|
||||
expr: "env.gateway.getProcessRssBytes?.() ?? null"
|
||||
- call: env.gateway.call
|
||||
saveAs: channelStatus
|
||||
args:
|
||||
- channels.status
|
||||
- probe: true
|
||||
timeoutMs: 10000
|
||||
- timeoutMs: 15000
|
||||
- set: kitchenChannelAccount
|
||||
value:
|
||||
expr: "(channelStatus.channelAccounts?.[config.channelId] ?? []).find((entry) => entry.accountId === config.channelAccountId) ?? null"
|
||||
- assert:
|
||||
expr: "kitchenChannelAccount?.running === true && kitchenChannelAccount?.configured === true"
|
||||
message:
|
||||
expr: "`Kitchen Sink channel did not report running+configured: ${JSON.stringify(kitchenChannelAccount)}`"
|
||||
detailsExpr: "{ healthz, readyz, kitchenChannelAccount }"
|
||||
|
||||
- name: exercises command inventory and MCP tool surfaces
|
||||
actions:
|
||||
- call: env.gateway.call
|
||||
saveAs: commandList
|
||||
args:
|
||||
- commands.list
|
||||
- agentId: qa
|
||||
scope: text
|
||||
- timeoutMs: 15000
|
||||
- set: pluginCommandNames
|
||||
value:
|
||||
expr: "(commandList.commands ?? []).filter((entry) => entry.source === 'plugin').map((entry) => entry.name).sort()"
|
||||
- assert:
|
||||
expr: "pluginCommandNames.includes('kitchen') && pluginCommandNames.includes('kitchen-sink')"
|
||||
message:
|
||||
expr: "`Kitchen Sink plugin commands missing from commands.list: ${JSON.stringify(pluginCommandNames)}`"
|
||||
- call: callPluginToolsMcp
|
||||
saveAs: mcpTool
|
||||
args:
|
||||
- env:
|
||||
ref: env
|
||||
toolName: kitchen_sink_search
|
||||
args:
|
||||
query: "kitchen sink qa live openai"
|
||||
- set: mcpToolText
|
||||
value:
|
||||
expr: "JSON.stringify(mcpTool.content ?? mcpTool)"
|
||||
- assert:
|
||||
expr: "mcpToolText.includes('Kitchen Sink image fixture')"
|
||||
message:
|
||||
expr: "`Kitchen Sink MCP tool output missed expected fixture: ${mcpToolText.slice(0, 500)}`"
|
||||
detailsExpr: "{ pluginCommandNames, mcpToolText: mcpToolText.slice(0, 500) }"
|
||||
|
||||
- name: runs live OpenAI turn with Kitchen Sink loaded
|
||||
actions:
|
||||
- call: reset
|
||||
- call: runAgentPrompt
|
||||
args:
|
||||
- ref: env
|
||||
- sessionKey:
|
||||
expr: "`agent:qa:kitchen-sink-openai:${randomUUID().slice(0, 8)}`"
|
||||
message:
|
||||
expr: config.livePrompt
|
||||
timeoutMs:
|
||||
expr: liveTurnTimeoutMs(env, config.agentTurnTimeoutMs)
|
||||
- call: waitForOutboundMessage
|
||||
saveAs: openaiReply
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "candidate.conversation.id === 'qa-operator' && (candidate.text.includes('KITCHEN-SINK-OPENAI-OK') || normalizeLowercaseStringOrEmpty(candidate.text).includes(config.blockedReplyNeedle))"
|
||||
- expr: liveTurnTimeoutMs(env, config.outboundTimeoutMs)
|
||||
- set: normalizedOpenAiReply
|
||||
value:
|
||||
expr: "normalizeLowercaseStringOrEmpty(openaiReply.text)"
|
||||
- assert:
|
||||
expr: "openaiReply.text.includes('KITCHEN-SINK-OPENAI-OK') || normalizedOpenAiReply.includes(config.blockedReplyNeedle)"
|
||||
message:
|
||||
expr: "`Kitchen Sink live turn neither completed nor surfaced the plugin block: ${openaiReply.text}`"
|
||||
detailsExpr: "{ openaiReply: openaiReply.text }"
|
||||
|
||||
- name: records gateway CPU RSS and log anomaly evidence
|
||||
actions:
|
||||
- set: perfEvidence
|
||||
value:
|
||||
expr: |-
|
||||
(() => {
|
||||
const cpuStart = typeof vars.cpuStartMs === "number" ? vars.cpuStartMs : null;
|
||||
const cpuEnd = env.gateway.getProcessCpuMs?.() ?? null;
|
||||
const rssStart = typeof vars.rssStartBytes === "number" ? vars.rssStartBytes : null;
|
||||
const rssEnd = env.gateway.getProcessRssBytes?.() ?? null;
|
||||
const logs = env.gateway.logs?.() ?? "";
|
||||
const deny = [
|
||||
/\buncaught exception\b/iu,
|
||||
/\bunhandled rejection\b/iu,
|
||||
/\bfatal\b/iu,
|
||||
/\bpanic\b/iu,
|
||||
];
|
||||
const findings = logs
|
||||
.split(/\r?\n/u)
|
||||
.filter((line) => deny.some((pattern) => pattern.test(line)))
|
||||
.slice(0, 10)
|
||||
.map((line) => line.replaceAll(env.repoRoot, "<repo>").slice(0, 500));
|
||||
const wallMs = Date.now() - Number(vars.perfStartedAtMs ?? Date.now());
|
||||
const cpuDeltaMs = cpuStart === null || cpuEnd === null ? null : Math.max(0, cpuEnd - cpuStart);
|
||||
const cpuCoreRatio = cpuDeltaMs === null || wallMs <= 0 ? null : Math.round((cpuDeltaMs / wallMs) * 1000) / 1000;
|
||||
const rssMiB = rssEnd === null ? null : Math.round((rssEnd / 1024 / 1024) * 10) / 10;
|
||||
return {
|
||||
wallMs,
|
||||
cpuStart,
|
||||
cpuEnd,
|
||||
cpuDeltaMs,
|
||||
cpuCoreRatio,
|
||||
rssStartBytes: rssStart,
|
||||
rssEndBytes: rssEnd,
|
||||
rssMiB,
|
||||
logBytes: logs.length,
|
||||
findings,
|
||||
};
|
||||
})()
|
||||
- assert:
|
||||
expr: "perfEvidence.findings.length === 0"
|
||||
message:
|
||||
expr: "`Gateway logs contain fatal runtime lines: ${JSON.stringify(perfEvidence.findings)}`"
|
||||
- assert:
|
||||
expr: "perfEvidence.cpuCoreRatio === null || perfEvidence.cpuCoreRatio <= config.maxGatewayCpuCoreRatio"
|
||||
message:
|
||||
expr: "`Gateway CPU ratio exceeded Kitchen Sink anomaly threshold: ${JSON.stringify(perfEvidence)}`"
|
||||
- assert:
|
||||
expr: "perfEvidence.rssMiB === null || perfEvidence.rssMiB <= config.maxGatewayRssMiB"
|
||||
message:
|
||||
expr: "`Gateway RSS exceeded Kitchen Sink anomaly threshold: ${JSON.stringify(perfEvidence)}`"
|
||||
detailsExpr: perfEvidence
|
||||
|
||||
- name: verifies adversarial diagnostics personality
|
||||
actions:
|
||||
- call: env.gateway.restartAfterStateMutation
|
||||
args:
|
||||
- lambda:
|
||||
async: true
|
||||
params: [ctx]
|
||||
expr: |-
|
||||
(async () => {
|
||||
const raw = await fs.readFile(ctx.configPath, "utf8").catch(() => "{}");
|
||||
const cfg = JSON.parse(raw || "{}");
|
||||
cfg.plugins = cfg.plugins || {};
|
||||
cfg.plugins.allow = [...new Set([...(cfg.plugins.allow || []), config.pluginId])];
|
||||
cfg.plugins.entries = cfg.plugins.entries || {};
|
||||
cfg.plugins.entries[config.pluginId] = {
|
||||
...(cfg.plugins.entries[config.pluginId] || {}),
|
||||
enabled: true,
|
||||
config: {
|
||||
...(cfg.plugins.entries[config.pluginId]?.config || {}),
|
||||
personality: config.adversarialPersonality,
|
||||
},
|
||||
hooks: {
|
||||
...(cfg.plugins.entries[config.pluginId]?.hooks || {}),
|
||||
allowConversationAccess: true,
|
||||
},
|
||||
};
|
||||
await fs.writeFile(ctx.configPath, `${JSON.stringify(cfg, null, 2)}\n`, "utf8");
|
||||
})()
|
||||
- call: waitForGatewayHealthy
|
||||
args:
|
||||
- ref: env
|
||||
- 120000
|
||||
- call: runQaCli
|
||||
saveAs: adversarialInspect
|
||||
args:
|
||||
- ref: env
|
||||
- - plugins
|
||||
- inspect
|
||||
- expr: config.pluginId
|
||||
- --runtime
|
||||
- --json
|
||||
- json: true
|
||||
timeoutMs: 60000
|
||||
- set: adversarialDiagnostics
|
||||
value:
|
||||
expr: |-
|
||||
(adversarialInspect.diagnostics ?? [])
|
||||
.filter((entry) => entry?.level === "error")
|
||||
.map((entry) => String(entry.message ?? ""))
|
||||
- assert:
|
||||
expr: "config.expectedAdversarialDiagnostics.every((message) => adversarialDiagnostics.includes(message))"
|
||||
message:
|
||||
expr: "`Kitchen Sink adversarial diagnostics missing expected messages: ${JSON.stringify({ expected: config.expectedAdversarialDiagnostics, actual: adversarialDiagnostics })}`"
|
||||
- assert:
|
||||
expr: "adversarialDiagnostics.every((message) => config.expectedAdversarialDiagnostics.includes(message))"
|
||||
message:
|
||||
expr: "`Kitchen Sink adversarial diagnostics contained unexpected messages: ${JSON.stringify(adversarialDiagnostics)}`"
|
||||
detailsExpr: "{ diagnostics: adversarialDiagnostics }"
|
||||
30
qa/scenarios/plugins/mcp-plugin-tools-call.yaml
Normal file
30
qa/scenarios/plugins/mcp-plugin-tools-call.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
title: MCP plugin-tools call
|
||||
|
||||
scenario:
|
||||
id: mcp-plugin-tools-call
|
||||
surface: mcp
|
||||
coverage:
|
||||
primary:
|
||||
- plugins.mcp-tools
|
||||
- tools.invocation
|
||||
objective: Verify OpenClaw can expose plugin tools over MCP and a real MCP client can call one successfully.
|
||||
successCriteria:
|
||||
- Plugin tools MCP server lists memory_search.
|
||||
- A real MCP client calls memory_search successfully.
|
||||
- The returned MCP payload includes the expected memory-only fact.
|
||||
docsRefs:
|
||||
- docs/cli/mcp.md
|
||||
- docs/gateway/protocol.md
|
||||
codeRefs:
|
||||
- test/e2e/qa-lab/runtime/gateway-mcp-real-transports.ts
|
||||
- src/mcp/plugin-tools-serve.ts
|
||||
- src/mcp/plugin-tools-handlers.ts
|
||||
execution:
|
||||
kind: script
|
||||
path: test/e2e/qa-lab/runtime/gateway-mcp-real-transports.ts
|
||||
summary: Registers a fixture plugin, starts the real plugin-tools stdio server, and calls its tool with a real MCP client.
|
||||
args:
|
||||
- --scenario
|
||||
- mcp-plugin-tools-call
|
||||
- --artifact-base
|
||||
- ${outputDir}
|
||||
@@ -0,0 +1,36 @@
|
||||
title: Packaged bundled plugin install uninstall
|
||||
|
||||
scenario:
|
||||
id: packaged-bundled-plugin-install-uninstall
|
||||
surface: plugin-sdk-and-bundled-plugin-architecture
|
||||
category: plugin-sdk-and-bundled-plugin-architecture.bundled-plugins
|
||||
coverage:
|
||||
secondary:
|
||||
- plugins.packaged-bundled-plugins
|
||||
- plugins.bundled-plugin-listing
|
||||
- clawhub.managed-install-records-that-preserve-source
|
||||
objective: Verify packaged distributions discover bundled plugins from packaged roots and can install, smoke, and uninstall them.
|
||||
successCriteria:
|
||||
- The probe selects installable bundled plugins from package dist or dist-runtime roots instead of raw source extension directories.
|
||||
- The probe rejects explicit bundled plugin ids that are not installable in the packaged runtime.
|
||||
- Install record assertions require bundled source paths to match the selected packaged root.
|
||||
- Uninstall assertions detect packaged bundled load paths across POSIX and Windows path shapes.
|
||||
docsRefs:
|
||||
- docs/plugins/plugin-inventory.md
|
||||
- docs/cli/plugins.md
|
||||
- docs/help/testing.md
|
||||
codeRefs:
|
||||
- scripts/e2e/bundled-plugin-install-uninstall-docker.sh
|
||||
- scripts/e2e/lib/bundled-plugin-install-uninstall/sweep.sh
|
||||
- scripts/e2e/lib/bundled-plugin-install-uninstall/probe.mjs
|
||||
- scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs
|
||||
- test/e2e/qa-lab/runtime/docker-e2e-lane.ts
|
||||
- test/scripts/bundled-plugin-install-uninstall-probe.test.ts
|
||||
execution:
|
||||
kind: script
|
||||
path: test/e2e/qa-lab/runtime/docker-e2e-lane.ts
|
||||
summary: Runs the packaged bundled-plugin Docker sweep and asserts install, runtime load, and uninstall evidence for each selected plugin.
|
||||
timeoutMs: 7200000
|
||||
args:
|
||||
- --lane
|
||||
- bundled-plugin-install-uninstall
|
||||
69
qa/scenarios/plugins/plugin-hook-health-sentinel.yaml
Normal file
69
qa/scenarios/plugins/plugin-hook-health-sentinel.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
title: Plugin hook health sentinel
|
||||
|
||||
scenario:
|
||||
id: plugin-hook-health-sentinel
|
||||
surface: runtime
|
||||
runtimeParityTier: live-only
|
||||
coverage:
|
||||
primary:
|
||||
- runtime.gateway-log-sentinel.plugin-hooks
|
||||
secondary:
|
||||
- plugins.before-prompt-build
|
||||
- plugins.before-tool-call
|
||||
objective: Fail the live parity lane when plugin hook crashes appear in gateway logs during ordinary prompt and tool activity.
|
||||
successCriteria:
|
||||
- An ordinary live agent turn completes with the expected marker.
|
||||
- No `before_prompt_build` or `before_tool_call` plugin hook failure is logged after the scenario cursor.
|
||||
docsRefs:
|
||||
- docs/plugins/hooks.md
|
||||
- qa/scenarios/index.yaml
|
||||
codeRefs:
|
||||
- extensions/qa-lab/src/gateway-log-sentinel.ts
|
||||
- src/plugins/runtime.ts
|
||||
execution:
|
||||
kind: flow
|
||||
summary: Mark the gateway log cursor, run a simple agent turn that may invoke session_status, and fail on plugin hook crash sentinels.
|
||||
config:
|
||||
expectedMarker: PLUGIN-HOOK-OK
|
||||
|
||||
flow:
|
||||
steps:
|
||||
- name: detects plugin hook failures around ordinary agent activity
|
||||
actions:
|
||||
- call: waitForGatewayHealthy
|
||||
args:
|
||||
- ref: env
|
||||
- 60000
|
||||
- call: reset
|
||||
- set: logCursor
|
||||
value:
|
||||
expr: markGatewayLogCursor()
|
||||
- set: startIndex
|
||||
value:
|
||||
expr: state.getSnapshot().messages.length
|
||||
- call: runAgentPrompt
|
||||
args:
|
||||
- ref: env
|
||||
- sessionKey:
|
||||
expr: "`agent:qa:plugin-hook-health:${randomUUID().slice(0, 8)}`"
|
||||
message:
|
||||
expr: "`If session_status is available, call it once, then reply exactly ${config.expectedMarker}.`"
|
||||
timeoutMs:
|
||||
expr: liveTurnTimeoutMs(env, 60000)
|
||||
- call: waitForOutboundMessage
|
||||
saveAs: outbound
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "candidate.conversation.id === 'qa-operator' && normalizeLowercaseStringOrEmpty(candidate.text).includes(normalizeLowercaseStringOrEmpty(config.expectedMarker))"
|
||||
- expr: liveTurnTimeoutMs(env, 30000)
|
||||
- sinceIndex:
|
||||
ref: startIndex
|
||||
- call: assertNoGatewayLogSentinels
|
||||
args:
|
||||
- since:
|
||||
ref: logCursor
|
||||
kinds:
|
||||
- plugin-hook-failure
|
||||
detailsExpr: outbound.text
|
||||
114
qa/scenarios/plugins/plugin-lifecycle-hot-reload.yaml
Normal file
114
qa/scenarios/plugins/plugin-lifecycle-hot-reload.yaml
Normal file
@@ -0,0 +1,114 @@
|
||||
title: Plugin lifecycle hot reload
|
||||
|
||||
scenario:
|
||||
id: plugin-lifecycle-hot-reload
|
||||
surface: plugins
|
||||
coverage:
|
||||
primary:
|
||||
- plugins.lifecycle
|
||||
secondary:
|
||||
- plugins.hot-reload
|
||||
- config.hot-apply
|
||||
objective: Verify a runtime-owned capability can be disabled and re-enabled through hot config reload without stale state.
|
||||
successCriteria:
|
||||
- Workspace skill capability is eligible before reload.
|
||||
- Hot config disables the capability and status reflects the disabled state.
|
||||
- A second hot reload re-enables the capability and the next agent turn can use it.
|
||||
docsRefs:
|
||||
- docs/tools/skills.md
|
||||
- docs/gateway/configuration.md
|
||||
- docs/plugins/manifest.md
|
||||
codeRefs:
|
||||
- src/agents/skills-status.ts
|
||||
- src/gateway/server-methods/config.ts
|
||||
- extensions/qa-lab/src/suite-runtime-agent-tools.ts
|
||||
execution:
|
||||
kind: flow
|
||||
summary: Disable and re-enable a workspace skill through config.patch and verify the capability is not stale.
|
||||
config:
|
||||
skillName: qa-lifecycle-hot-reload-skill
|
||||
prompt: "Lifecycle hot reload marker. Reply exactly: LIFECYCLE-HOT-RELOAD-OK"
|
||||
expectedReply: LIFECYCLE-HOT-RELOAD-OK
|
||||
skillBody: |-
|
||||
---
|
||||
name: qa-lifecycle-hot-reload-skill
|
||||
description: Lifecycle hot reload QA marker
|
||||
---
|
||||
When the user asks for the lifecycle marker exactly, reply with exactly: LIFECYCLE-HOT-RELOAD-OK
|
||||
|
||||
flow:
|
||||
steps:
|
||||
- name: disables and re-enables a runtime capability without stale state
|
||||
actions:
|
||||
- call: writeWorkspaceSkill
|
||||
args:
|
||||
- env:
|
||||
ref: env
|
||||
name:
|
||||
expr: config.skillName
|
||||
body:
|
||||
expr: config.skillBody
|
||||
- call: waitForCondition
|
||||
args:
|
||||
- lambda:
|
||||
async: true
|
||||
expr: "findSkill(await readSkillStatus(env), config.skillName)?.eligible ? true : undefined"
|
||||
- 15000
|
||||
- 200
|
||||
- call: patchConfig
|
||||
args:
|
||||
- env:
|
||||
ref: env
|
||||
patch:
|
||||
skills:
|
||||
entries:
|
||||
expr: "({ [config.skillName]: { enabled: false } })"
|
||||
- call: waitForQaChannelReady
|
||||
args:
|
||||
- ref: env
|
||||
- 60000
|
||||
- call: waitForCondition
|
||||
args:
|
||||
- lambda:
|
||||
async: true
|
||||
expr: "findSkill(await readSkillStatus(env), config.skillName)?.disabled ? true : undefined"
|
||||
- 15000
|
||||
- 200
|
||||
- call: patchConfig
|
||||
args:
|
||||
- env:
|
||||
ref: env
|
||||
patch:
|
||||
skills:
|
||||
entries:
|
||||
expr: "({ [config.skillName]: { enabled: true } })"
|
||||
- call: waitForQaChannelReady
|
||||
args:
|
||||
- ref: env
|
||||
- 60000
|
||||
- call: waitForCondition
|
||||
args:
|
||||
- lambda:
|
||||
async: true
|
||||
expr: "((skill) => skill?.eligible && !skill?.disabled ? true : undefined)(findSkill(await readSkillStatus(env), config.skillName))"
|
||||
- 15000
|
||||
- 200
|
||||
- call: reset
|
||||
- call: runAgentPrompt
|
||||
args:
|
||||
- ref: env
|
||||
- sessionKey:
|
||||
expr: "`agent:qa:plugin-lifecycle:${randomUUID().slice(0, 8)}`"
|
||||
message:
|
||||
expr: config.prompt
|
||||
timeoutMs:
|
||||
expr: liveTurnTimeoutMs(env, 30000)
|
||||
- call: waitForOutboundMessage
|
||||
saveAs: outbound
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "candidate.conversation.id === 'qa-operator' && candidate.text.includes(config.expectedReply)"
|
||||
- expr: liveTurnTimeoutMs(env, 20000)
|
||||
detailsExpr: outbound.text
|
||||
33
qa/scenarios/plugins/plugin-lifecycle-probe.yaml
Normal file
33
qa/scenarios/plugins/plugin-lifecycle-probe.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
title: Plugin lifecycle probe evidence
|
||||
|
||||
scenario:
|
||||
id: plugin-lifecycle-probe
|
||||
surface: plugins
|
||||
coverage:
|
||||
secondary:
|
||||
- plugins.lifecycle
|
||||
- cli.plugin-validation-repair
|
||||
- plugins.setup-flows
|
||||
objective: Exercise packaged plugin install, inspect, disable, enable, update, downgrade, and uninstall behavior in a clean Docker runtime.
|
||||
successCriteria:
|
||||
- A clean container installs the candidate OpenClaw package and fixture plugin package.
|
||||
- Runtime inspect reports the installed plugin as enabled and loaded.
|
||||
- Disable and enable commands persist the expected plugin state.
|
||||
- Update and downgrade commands replace the installed plugin version while preserving the npm project root.
|
||||
- Forced uninstall succeeds after installed plugin code is removed and emits a bounded resource summary.
|
||||
docsRefs:
|
||||
- docs/plugins/manifest.md
|
||||
- docs/cli/plugins.md
|
||||
- docs/concepts/qa-e2e-automation.md
|
||||
codeRefs:
|
||||
- scripts/e2e/plugin-lifecycle-matrix-docker.sh
|
||||
- test/e2e/qa-lab/plugins/plugin-lifecycle-probe-runtime.ts
|
||||
- test/e2e/qa-lab/runtime/docker-e2e-lane.ts
|
||||
- test/e2e/qa-lab/plugins/plugin-lifecycle-probe.e2e.test.ts
|
||||
execution:
|
||||
kind: script
|
||||
path: test/e2e/qa-lab/runtime/docker-e2e-lane.ts
|
||||
summary: Runs the packaged plugin lifecycle matrix in a clean Docker runtime and asserts each emitted lifecycle phase.
|
||||
args:
|
||||
- --lane
|
||||
- plugin-lifecycle-matrix
|
||||
42
qa/scenarios/plugins/plugin-manifest-contract-health.yaml
Normal file
42
qa/scenarios/plugins/plugin-manifest-contract-health.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
title: Plugin manifest contract health
|
||||
|
||||
scenario:
|
||||
id: plugin-manifest-contract-health
|
||||
surface: runtime
|
||||
runtimeParityTier: live-only
|
||||
coverage:
|
||||
primary:
|
||||
- runtime.gateway-log-sentinel.plugin-contracts
|
||||
secondary:
|
||||
- plugins.contracts.tools
|
||||
objective: Fail live proof when gateway startup logs show plugin manifest contract registration errors such as missing `contracts.tools`.
|
||||
successCriteria:
|
||||
- Gateway reaches healthy state.
|
||||
- Startup logs contain no plugin contract registration sentinel.
|
||||
docsRefs:
|
||||
- docs/plugins/manifest.md
|
||||
- qa/scenarios/index.yaml
|
||||
codeRefs:
|
||||
- extensions/qa-lab/src/gateway-log-sentinel.ts
|
||||
- src/plugins/manifest.ts
|
||||
execution:
|
||||
kind: flow
|
||||
summary: Scan startup logs from cursor 0 for plugin manifest contract registration failures.
|
||||
config:
|
||||
startupCursor: 0
|
||||
|
||||
flow:
|
||||
steps:
|
||||
- name: fails on startup plugin contract registration errors
|
||||
actions:
|
||||
- call: waitForGatewayHealthy
|
||||
args:
|
||||
- ref: env
|
||||
- 60000
|
||||
- call: assertNoGatewayLogSentinels
|
||||
args:
|
||||
- since:
|
||||
expr: config.startupCursor
|
||||
kinds:
|
||||
- plugin-contract-error
|
||||
detailsExpr: "'plugin manifest contract logs clean'"
|
||||
80
qa/scenarios/plugins/skill-install-hot-availability.yaml
Normal file
80
qa/scenarios/plugins/skill-install-hot-availability.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
title: Skill install hot availability
|
||||
|
||||
scenario:
|
||||
id: skill-install-hot-availability
|
||||
surface: skills
|
||||
coverage:
|
||||
primary:
|
||||
- plugins.skills
|
||||
secondary:
|
||||
- plugins.hot-install
|
||||
objective: Verify a newly added workspace skill shows up without a broken intermediate state and can influence the next turn immediately.
|
||||
successCriteria:
|
||||
- Skill is absent before install.
|
||||
- skills.status reports it after install without a restart.
|
||||
- The next agent turn reflects the new skill marker.
|
||||
docsRefs:
|
||||
- docs/tools/skills.md
|
||||
- docs/gateway/configuration.md
|
||||
codeRefs:
|
||||
- src/agents/skills-status.ts
|
||||
- extensions/qa-lab/src/suite.ts
|
||||
execution:
|
||||
kind: flow
|
||||
summary: Verify a newly added workspace skill shows up without a broken intermediate state and can influence the next turn immediately.
|
||||
config:
|
||||
skillName: qa-hot-install-skill
|
||||
skillBody: |-
|
||||
---
|
||||
name: qa-hot-install-skill
|
||||
description: Hot install QA marker
|
||||
---
|
||||
When the user asks for the hot install marker exactly, reply with exactly: HOT-INSTALL-OK
|
||||
prompt: "Hot install marker: give me the hot install marker exactly."
|
||||
expectedContains: "HOT-INSTALL-OK"
|
||||
|
||||
flow:
|
||||
steps:
|
||||
- name: picks up a newly added workspace skill without restart
|
||||
actions:
|
||||
- call: readSkillStatus
|
||||
saveAs: before
|
||||
args:
|
||||
- ref: env
|
||||
- assert:
|
||||
expr: "!findSkill(before, config.skillName)"
|
||||
message:
|
||||
expr: "`${config.skillName} unexpectedly already present`"
|
||||
- call: writeWorkspaceSkill
|
||||
args:
|
||||
- env:
|
||||
ref: env
|
||||
name:
|
||||
expr: config.skillName
|
||||
body:
|
||||
expr: config.skillBody
|
||||
- call: waitForCondition
|
||||
args:
|
||||
- lambda:
|
||||
async: true
|
||||
expr: "((await readSkillStatus(env)).find((skill) => skill.name === config.skillName)?.eligible ? true : undefined)"
|
||||
- 15000
|
||||
- 200
|
||||
- call: reset
|
||||
- call: runAgentPrompt
|
||||
args:
|
||||
- ref: env
|
||||
- sessionKey: agent:qa:hot-skill
|
||||
message:
|
||||
expr: config.prompt
|
||||
timeoutMs:
|
||||
expr: liveTurnTimeoutMs(env, 30000)
|
||||
- call: waitForOutboundMessage
|
||||
saveAs: outbound
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "candidate.conversation.id === 'qa-operator' && candidate.text.includes(config.expectedContains)"
|
||||
- expr: liveTurnTimeoutMs(env, 20000)
|
||||
detailsExpr: outbound.text
|
||||
76
qa/scenarios/plugins/skill-visibility-invocation.yaml
Normal file
76
qa/scenarios/plugins/skill-visibility-invocation.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
title: Skill visibility and invocation
|
||||
|
||||
scenario:
|
||||
id: skill-visibility-invocation
|
||||
surface: skills
|
||||
coverage:
|
||||
primary:
|
||||
- plugins.skills
|
||||
secondary:
|
||||
- tools.invocation
|
||||
objective: Verify a workspace skill becomes visible in skills.status and influences the next agent turn.
|
||||
successCriteria:
|
||||
- skills.status reports the seeded skill as visible and eligible.
|
||||
- The next agent turn reflects the skill instruction marker.
|
||||
- The result stays scoped to the active QA workspace skill.
|
||||
docsRefs:
|
||||
- docs/tools/skills.md
|
||||
- docs/gateway/protocol.md
|
||||
codeRefs:
|
||||
- src/agents/skills-status.ts
|
||||
- extensions/qa-lab/src/suite.ts
|
||||
execution:
|
||||
kind: flow
|
||||
summary: Verify a workspace skill becomes visible in skills.status and influences the next agent turn.
|
||||
config:
|
||||
skillName: qa-visible-skill
|
||||
skillBody: |-
|
||||
---
|
||||
name: qa-visible-skill
|
||||
description: Visible QA skill marker
|
||||
---
|
||||
When the user asks for the visible skill marker exactly, or explicitly asks you to use qa-visible-skill, reply with exactly: VISIBLE-SKILL-OK
|
||||
prompt: "Use qa-visible-skill now. Reply exactly with the visible skill marker and nothing else."
|
||||
expectedContains: "VISIBLE-SKILL-OK"
|
||||
|
||||
flow:
|
||||
steps:
|
||||
- name: reports visible skill and applies its marker on the next turn
|
||||
actions:
|
||||
- call: writeWorkspaceSkill
|
||||
args:
|
||||
- env:
|
||||
ref: env
|
||||
name:
|
||||
expr: config.skillName
|
||||
body:
|
||||
expr: config.skillBody
|
||||
- call: readSkillStatus
|
||||
saveAs: skills
|
||||
args:
|
||||
- ref: env
|
||||
- set: visible
|
||||
value:
|
||||
expr: findSkill(skills, config.skillName)
|
||||
- assert:
|
||||
expr: "visible?.eligible === true && !visible?.disabled && !visible?.blockedByAllowlist"
|
||||
message:
|
||||
expr: "`skill not visible/eligible: ${JSON.stringify(visible)}`"
|
||||
- call: reset
|
||||
- call: runAgentPrompt
|
||||
args:
|
||||
- ref: env
|
||||
- sessionKey: agent:qa:visible-skill
|
||||
message:
|
||||
expr: config.prompt
|
||||
timeoutMs:
|
||||
expr: liveTurnTimeoutMs(env, 30000)
|
||||
- call: waitForOutboundMessage
|
||||
saveAs: outbound
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "candidate.conversation.id === 'qa-operator' && candidate.text.includes(config.expectedContains)"
|
||||
- expr: liveTurnTimeoutMs(env, 20000)
|
||||
detailsExpr: outbound.text
|
||||
28
qa/scenarios/plugins/voice-call-cli-rpc-agent-tool.yaml
Normal file
28
qa/scenarios/plugins/voice-call-cli-rpc-agent-tool.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
title: Voice Call CLI, RPC, and agent tool mock-provider flow
|
||||
|
||||
scenario:
|
||||
id: voice-call-cli-rpc-agent-tool
|
||||
surface: voice-call-channel
|
||||
category: voice-call-channel.channel-setup-and-operations
|
||||
coverage:
|
||||
primary:
|
||||
- voice-call.cli-rpc-agent-tool
|
||||
objective: Verify the Voice Call CLI, Gateway RPC, and agent tool share one executable mock-provider runtime.
|
||||
successCriteria:
|
||||
- The CLI starts an outbound call through its Gateway RPC path.
|
||||
- The registered Gateway RPC starts an outbound call through the mock provider.
|
||||
- The registered agent tool starts an outbound call through the same runtime.
|
||||
- Runtime status reports all calls and cleanup stops the local webhook fixture.
|
||||
docsRefs:
|
||||
- docs/cli/voicecall.md
|
||||
- docs/plugins/voice-call.md
|
||||
- docs/gateway/protocol.md
|
||||
codeRefs:
|
||||
- extensions/voice-call/index.ts
|
||||
- extensions/voice-call/src/cli.ts
|
||||
- extensions/voice-call/src/manager.test-harness.ts
|
||||
- extensions/voice-call/src/voice-call-cli-rpc-agent-tool.e2e.test.ts
|
||||
execution:
|
||||
kind: vitest
|
||||
path: extensions/voice-call/src/voice-call-cli-rpc-agent-tool.e2e.test.ts
|
||||
summary: Vitest QA Lab coverage for Voice Call CLI, RPC, and agent tool entry points using the mock provider.
|
||||
Reference in New Issue
Block a user