Files
openclaw/docs/plugins/compatibility.md
Jesse Merhi d7627d6f4c refactor(prompt): use plain inbound context labels and drop system-tag sanitizer (#112000)
* refactor(prompt): plain inbound context labels with a provenance marker

Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.

Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.

Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.

Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.

* fix(ci): resolve gate failures for plain inbound context labels

- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
  so the Kysely connection-boundary guardrail holds; unexport the now-internal
  transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
  plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
  the 700-line oxlint cap; suppressions are disallowed, so follow the existing
  sibling record-module pattern. Public exports and PluginCompatCode literals
  unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
  only). The bracket de-fang and System: rewrite it expected were removed with
  sanitizeInboundSystemTags; forged system lines are neutralized at the
  system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.

* fix(prompt): harden inbound context label migration and drop in-band sanitizer

Review follow-ups on the plain-label + provenance-marker change:

- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
  look-alike `System:`/`[System]` markers corrupted legitimate user text and is
  not a real injection boundary; role separation plus external-content wrapping
  is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
  now requires a complete known legacy sentinel line, a legacy label followed by
  a fenced JSON body, or the complete legacy external-content header. The prior
  predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
  deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
  alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
  header's line break, migrated assistant rows skip newline normalization, and
  without it the marked-header replace wins and the body strips to empty. Added
  a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
  prose-block strip path.

Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
2026-07-27 11:27:53 +10:00

213 lines
12 KiB
Markdown

---
summary: "Plugin compatibility contracts, deprecation metadata, and migration expectations"
title: "Plugin compatibility"
read_when:
- You maintain an OpenClaw plugin
- You see a plugin compatibility warning
- You are planning a plugin SDK or manifest migration
---
OpenClaw keeps older plugin contracts wired through named compatibility
adapters before removing them. This protects existing bundled and external
plugins while the SDK, manifest, setup, config, and agent runtime contracts
evolve.
## Compatibility registry
Plugin compatibility contracts are tracked in the core registry at
`src/plugins/compat/registry.ts`. Each record has:
- a stable compatibility code
- status: `active`, `deprecated`, `removal-pending`, or `removed`
- owner: `sdk`, `config`, `setup`, `channel`, `provider`, `plugin-execution`,
`agent-runtime`, or `core`
- introduction and deprecation dates when applicable
- an exact removal date once the owning maintainer approves it; an omitted
`removeAfter` keeps a deprecated surface ineligible for removal
- replacement guidance
- docs, diagnostics, and tests that cover the old and new behavior
The registry is the source for maintainer planning and future plugin
inspector checks. If a plugin-facing behavior changes, add or update the
compatibility record in the same change that adds the adapter.
Doctor repair and migration compatibility is tracked separately at
`src/commands/doctor/shared/deprecation-compat.ts`. Those records cover old
config shapes, install-ledger layouts, and repair shims that may need to
stay available after the runtime compatibility path is removed.
Release sweeps should check both registries. Do not delete a doctor
migration just because the matching runtime or config compatibility record
expired; first verify there is no supported upgrade path that still needs
the repair. Revalidate each replacement annotation during release planning
too, since plugin ownership and config footprint can change as providers
and channels move out of core.
## Deprecation policy
OpenClaw should not remove a documented plugin contract in the same release
that introduces its replacement. Migration sequence:
1. Add the new contract.
2. Keep the old behavior wired through a named compatibility adapter.
3. Emit diagnostics or warnings when plugin authors can act.
4. Document the replacement and timeline.
5. Test both old and new paths.
6. Wait through the announced migration window.
7. Remove only with explicit breaking-release approval.
Deprecated records must include a warning start date, replacement, docs
link, and a final removal date no more than three months after the warning
starts. Do not add a deprecated compatibility path with an open-ended
removal window unless maintainers explicitly decide it is permanent
compatibility and mark it `active` instead.
## Current compatibility areas
The July 2026 sweep removed the expired root SDK, manifest, provider, runtime,
registry-flag, and plugin-owned web-config aliases. Doctor migrations remain
separately tracked so supported upgrade paths can still repair old config.
The remaining dated compatibility areas are:
- the August and September SDK subpath windows listed in the migration guide
- `api.on("deactivate", ...)` and `api.on("subagent_spawning", ...)` hook aliases
- memory-specific embedding registration and the beta.5 session-store bridge
- WhatsApp inbound callback aliases described below
- explicit channel target parsing and `openclaw/plugin-sdk/messaging-targets`
- embedded Pi agent aliases
- the shipped agent-harness SDK aliases, whose removal is pending a new
externally documented migration decision
- the October 2026 SDK annotation families listed below
Active, undated registry records cover supported behavior rather than removal
debt, including activation hints, plugin capture, bundled plugin enablement,
and the generated channel-config fallback.
The annotation-only compatibility audit added these dated records. Their
`removeAfter` date is an earliest review date, not permission to remove a
surface while its stated reader or migration condition remains unmet.
| Compatibility code | Removal condition | `removeAfter` |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------- |
| `plugin-sdk-channel-setup-input-fields` | Repeat the published-plugin artifact sweep and remove only fields with no reader. | 2026-10-01 |
| `plugin-sdk-broad-runtime-barrels` | Move bundled and indexed external consumers to focused SDK subpaths. | 2026-10-01 |
| `plugin-sdk-provider-owned-helper-shims` | Move each deprecated provider helper to its provider-local API and prove no published reader remains. | 2026-10-01 |
| `message-presentation-legacy-bridges` | Move reply producers and official channel packages to `MessagePresentation`. | 2026-10-01 |
| `plugin-sdk-focused-compat-aliases` | Prove every enumerated alias has no bundled or published reader. | 2026-10-01 |
| `agent-harness-terminal-result-aliases` | Move harnesses to `terminal` and `visibleReplies`, then prove the legacy result fields are unread. | 2026-10-01 |
| `official-plugin-export-aliases` | Move users of Google Meet testing, channel presentation, and Discord timeout exports to canonical APIs. | 2026-10-01 |
| `memory-host-compatibility-aliases` | Use canonical memory tables and prepared runtime config everywhere. | 2026-10-01 |
| `plugin-runtime-api-compat-aliases` | Move flat plugin registration/runtime calls to their namespaced or focused replacements. | 2026-10-01 |
| `plugin-provider-manifest-compat-aliases` | Move kind/setup/catalog ownership to manifests and model-catalog registration. | 2026-10-01 |
| `deprecated-session-store-beta5-api` | End the v2026.7.x whole-store upgrade window, including package-root aliases. | 2026-10-12 |
`pnpm plugins:boundary-report` reports `removal-pending` records separately
from deprecated records. A due `removal-pending` record remains blocked until
its reported migration condition is satisfied and its reader references are
cleared; the existing `--fail-on-eligible-compat` gate continues to apply only
to dated `deprecated` records. Reader references are surface-token matches for
triage; use the published-artifact sweep before authorizing removal.
### Channel prompt-context identifier aliases
New channel plugins should use `MsgContext.ChannelPromptContext`,
`MsgContext.ChannelStructuredContext`, `ChannelStructuredContextEntry`, and
`SupplementalContextFacts.channelStructuredContext`. The older
`UntrustedContext`, `UntrustedStructuredContext`,
`UntrustedStructuredContextEntry`, and supplemental `untrustedContext` names
remain as deprecated SDK aliases until 2026-09-08 (registry record
`sdk-untrusted-context-identifier-aliases`). Inbound finalization folds those
deprecated fields into the channel-named fields and removes the old keys from
runtime context.
The security runtime similarly exports `buildChannelMetadata`; the deprecated
`buildUntrustedChannelMetadata` alias remains available on the same schedule.
### WhatsApp inbound callback flat aliases
WhatsApp runtime callbacks deliver `WebInboundMessage`: the canonical
nested `event`, `payload`, `quote`, `group`, and `platform` contexts plus
deprecated flat aliases for the shipped callback fields. New callback code
should read the nested contexts. Code that constructs clean nested callback
messages can use `WebInboundCallbackMessage`; compatibility listeners that
still inject old flat test or plugin messages should use
`LegacyFlatWebInboundMessage` or `WebInboundMessageInput`.
The flat aliases remain available until **2026-08-30**; that window applies
only to flat alias access, not to the nested shape, which is the canonical
runtime contract. Each flat alias's TypeScript `@deprecated` annotation
names its exact nested replacement. Common examples:
- `id`, `timestamp`, and `isBatched` move under `event`.
- `body`, `mediaPath`, `mediaType`, `mediaFileName`, `mediaUrl`, `location`,
and `channelStructuredContext` move under `payload`.
- `to`, `chatId`, sender/self fields, `sendComposing`, `reply(...)`, and
`sendMedia(...)` move under `platform`.
- `replyTo*` fields move under `quote`; group subject/participant/mention
fields move under `group`.
`payload.channelStructuredContext` is extracted from inbound provider
payloads. Plugins should inspect `label`, `source`, and `type` before
treating its `payload` as authoritative.
### WhatsApp inbound admission fields
Accepted WhatsApp callback messages carry `admission`, a public-safe
envelope for the access-control decision that admitted the message. New
callback code should read admission facts from `msg.admission` instead of
the older top-level admission fields.
The top-level fields remain available until **2026-08-30**. Each field's
TypeScript `@deprecated` annotation names its replacement:
- `from` and `conversationId` move to `admission.conversation.id`.
- `accountId` moves to `admission.accountId`.
- `accessControlPassed` is a derived compatibility view of
`admission.ingress.decision === "allow"`; on messages that already carry
`admission`, writing the legacy boolean does not rewrite the ingress
graph.
- `chatType` moves to `admission.conversation.kind`.
## Plugin inspector package
The plugin inspector should live outside the core OpenClaw repo as a
separate package/repository backed by the versioned compatibility and
manifest contracts. The day-one CLI should be:
```sh
openclaw-plugin-inspector ./my-plugin
```
It should emit manifest/schema validation, the contract compatibility
version being checked, install/source metadata checks, cold-path import
checks, and deprecation/compatibility warnings. Use `--json` for stable
machine-readable output in CI annotations. OpenClaw core should expose
contracts and fixtures the inspector can consume, but should not publish the
inspector binary from the main `openclaw` package.
### Maintainer acceptance lane
Use Crabbox-backed Blacksmith Testbox for the installable-package acceptance
lane when validating the external inspector against OpenClaw plugin
packages. Run it from a clean OpenClaw checkout after the package is built:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json --shell -- "pnpm install && pnpm build && npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/telegram --json"
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json --shell -- "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/discord --json"
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json --shell -- "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- <clawhub-plugin-dir> --json"
```
Keep this lane opt-in for maintainers, since it installs an external npm
package and may inspect plugin packages cloned outside the repo. The local
repo guards cover the SDK export map, compatibility registry metadata,
deprecated SDK-import burn-down, and bundled extension import boundaries;
Testbox inspector proof covers the package as external plugin authors
consume it.
## Release notes
Release notes should include upcoming plugin deprecations with target dates
and links to migration docs, before a compatibility path moves to
`removal-pending` or `removed`.