* [AI] fix(plugins): recognize document-extractors as a capability kind in inspect-shape
PluginCapabilityKind did not include "document-extractors", causing
plugins that declare contracts.documentExtractors (like document-extract)
to show capabilityCount=0 and shape="non-capability" in plugins inspect.
Add "document-extractors" to PluginCapabilityKind and read from
plugin.contracts.documentExtractors in buildPluginCapabilityEntries().
Related to #91539
* [AI] test(plugins): add document-extractors shape contract coverage
Add a test case verifying that plugins declaring
contracts.documentExtractors are classified as
plain-capability shape with capabilityCount=1
and capabilities including the document-extractors kind.
Addresses ClawSweeper P2 review finding on PR #91597.
* [AI] chore: rebase on main to refresh CI
* test(plugins): fold extractor into shape matrix
---------
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
* fix(plugins): stop ClawHub version install from inheriting latest compatibility
When installing a specific older version of a ClawHub plugin, the
compatibility check fell back to the package-level compatibility
metadata when the version-specific response lacked it. The package-level
field reflects the latest version's requirements, so installing e.g.
version 2026.6.8 would incorrectly require OpenClaw >= 2026.6.10.
Remove the fallback to package-level compatibility in
resolveCompatiblePackageVersion(). If a version's artifact response has
no compatibility data, treat it as having no restrictions rather than
inheriting the latest version's constraints.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(plugins): narrow compatibility fallback to latest version only
Preserve package-level compatibility enforcement for unpinned/latest
ClawHub installs when the version response omits compatibility data.
Only suppress the fallback for older pinned versions where the
package-level metadata reflects the latest version's requirements.
Add regression test proving unpinned latest installs still reject
incompatible hosts via the package-level compatibility guard.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(plugins): recover version-specific compatibility from version endpoint
When the artifact endpoint returns sparse metadata (no compatibility)
for a pinned older version, fetch the version endpoint to get the real
version-specific compatibility data. This preserves compatibility
enforcement for pinned versions instead of treating sparse artifact
metadata as unrestricted.
The fallback chain is now: artifact compatibility -> version endpoint
compatibility -> package-level compatibility (latest only).
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(plugins): fail closed when version compatibility recovery fails
When the artifact endpoint returns sparse metadata and the version
endpoint is unavailable (transient 500, network failure, etc.), return
the error instead of proceeding with no compatibility checks. This
prevents incompatible plugins from installing when metadata recovery
fails.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
---------
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(providers): bound self-hosted discovery JSON reads
discoverLlamaCppRuntimeContextTokens and discoverOpenAICompatibleLocalModels
parsed their HTTP responses via an unbounded await response.json(). Self-hosted
provider base URLs are user-supplied and untrusted (an endpoint reachable via
SSRF could stream an unbounded JSON body), so a hostile or buggy endpoint could
drive the setup wizard into OOM.
Route both reads through the shared byte-bounded reader (readResponseWithLimit
from @openclaw/media-core) under a single 4 MiB cap before JSON.parse, mirroring
the bound-stream hardening landed for Anthropic error bodies. Overflow cancels
the stream and is swallowed by the existing discovery error handling, so a
capped endpoint degrades gracefully (returns [] / skips the runtime context
probe) instead of buffering the whole body.
* tune self-hosted discovery cap
Signed-off-by: sallyom <somalley@redhat.com>
---------
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
Update OpenClaw ClawHub docs and user-facing copy for canonical owner-qualified skill routes.\n\nEvidence:\n- pnpm docs:list\n- pnpm test src/plugins/clawhub.test.ts src/cli/plugins-cli.install.test.ts src/gateway/server-methods/skills.clawhub.test.ts ui/src/ui/views/skills.test.ts\n- pnpm exec oxfmt --check --threads=1 docs/clawhub/cli.md docs/clawhub/publishing.md docs/cli/skills.md docs/help/faq.md docs/start/showcase.md docs/tools/creating-skills.md docs/tools/skills.md src/gateway/server-methods/skills.clawhub.test.ts src/plugins/clawhub.test.ts src/plugins/clawhub.ts ui/src/ui/views/skills.test.ts\n- git diff --check\n- exact-head hosted CI passed for 8530374388d8a73235b2ac8444b95a4a4c7d0f1c\n\nNote: repo-native scripts/pr prepare-run was attempted; local broad pnpm test was stopped after unrelated existing failures in agent/media/provider shards, while hosted exact-head CI and targeted ClawHub route/copy validation were green.
* fix(plugins): make empty-allowlist warning actionable for first-time users
* fix(plugins): make empty-allowlist warnings actionable
* fix(plugins): make empty-allowlist warnings actionable
* fix(plugins): make empty-allowlist actionable for new users
---------
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
* plugins: clarify allowlist warning when entries don't match discovered ids
When plugins.allow contains entries that do not match any discovered
plugin id (for example a channel id like feishu instead of the real
plugin id openclaw-lark), stop emitting the misleading "plugins.allow
is empty" warning. Emit a specific mismatch warning that lists the
unknown allow entries alongside the discovered plugin ids and points
users at the plugin id rather than a channel id or npm package name.
Refs #68352
* plugins: treat bundled plugin ids as valid allow entries
Codex P2 on #68389: warnWhenAllowlistIsOpen computed allowHasMatch
against the auto-discoverable (workspace + global) subset only, so
a legitimate bundled-only allowlist like plugins.allow=['telegram']
would trip the new mismatch warning whenever any non-bundled plugin
happened to be discoverable alongside it.
Compare allow entries to every discovered plugin id (bundled +
workspace + global) for both the short-circuit and the unmatched-
entries computation. The warning text stays scoped to non-bundled
auto-discoverable plugins; we just stop flagging bundled ids as
'does not match any discovered plugin ids'. Add a regression test
that covers the bundled-only allowlist + non-bundled workspace
plugin combination.
Refs #68352
* chore: drop release-owned CHANGELOG entry (AGENTS.md: changelog is release-generated)
* plugins: clarify allowlist warning when entries do not match plugin ids
---------
Co-authored-by: Sean Sun <lyfuci11@gmail.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
* perf(plugins): cache existence probes within bundle manifest scan
Bundle plugin discovery re-probes the same marker paths (skills/, commands/,
agents/, .mcp.json, .lsp.json, settings.json, hooks/hooks.json) once in
detectBundleManifestFormat and again in loadBundleManifest's capability
builders. Across the bundled plugin tree this is thousands of redundant
synchronous fs.existsSync calls; #76209 reports 25.4s of self-time on a
Windows cold start.
Add a scan-scoped existence cache (plugin-scan-existence-cache.ts) entered
only around discoverBundleInRoot. pluginScanExistsSync memoizes inside the
active scan and falls back to plain fs.existsSync outside it, so install,
hooks, and doctor flows stay uncached. The cache is push/pop per
discoverBundleInRoot call (try/finally), so a later install/repair pass
re-reads the filesystem — no process-global staleness.
Measured on Windows over a 25-plugin fixture: 550 -> 325 fs.existsSync
calls (41% fewer), 294.75ms -> 208.49ms. Discovery results unchanged.
Closes#76209
* fix(plugins): drop unused test reset helper and satisfy oxlint
Remove __resetPluginScanExistenceCacheForTest: the scan cache is push/pop
balanced by try/finally in withPluginScanExistenceCache, so the stack never
leaks between tests and the helper was dead code. It also tripped oxlint
no-underscore-dangle. Refactor the integration test to count existsSync calls
via a const-returning helper so there is no useless assignment.
Since #85341 the per-model visibility probes behind the chat /models command
(isCliRuntimeProvider({ includeSetupRegistry: true }) in commands-models.ts)
rebuild the plugin setup registry on every call: a synchronous ~65ms manifest
re-scan plus plugin setup module re-execution, issued hundreds of times per
listing. On the stock bundled plugin set this pins a CPU core for ~49s per
workflow step (list -> pick provider -> pick model), in every chat channel.
Cache the manifest scan and the resolved registry in bounded PluginLruCaches
keyed by the control-plane fingerprint, discovery-env fingerprint, metadata
snapshot identity, cwd, and pluginIds scope, with clone-on-store/clone-on-hit
isolation; invalidation rides the existing plugin-metadata lifecycle clear.
Output is identical; the /models data build drops from ~49s to ~150ms and the
per-model probe from ~65ms to ~0.2ms.