* 'main' of https://github.com/openclaw/openclaw:
  fix(plugins): satisfy doctor compat lint
  chore(plugins): inventory doctor deprecation compat
  fix(plugins): record crabpot compat deprecations
  docs(dreaming): rewrite with AccordionGroup for phases and backfill, Tabs for quick start and CLI workflow, ParamField for dreaming defaults
This commit is contained in:
Vincent Koc
2026-04-26 04:05:11 -07:00
10 changed files with 628 additions and 127 deletions

View File

@@ -25,12 +25,22 @@ Use this skill for release and publish-time workflow. Keep ordinary development
- Before release branching, commit any dirty files in coherent groups, push,
pull/rebase, then run `/changelog` on `main` and commit/push/pull that
changelog rewrite immediately before creating the release branch.
- During release planning, inspect `src/plugins/compat/registry.ts` before
branching and again before final publish. For every deprecated or
removal-pending compatibility record whose `removeAfter` date is on or before
the release date, either remove the compatibility path where safe and
validate the affected tests, or write down why removal is blocked and get
explicit maintainer approval before shipping the expired compatibility path.
- During release planning, inspect both `src/plugins/compat/registry.ts` and
`src/commands/doctor/shared/deprecation-compat.ts` before branching and again
before final publish. For every deprecated or removal-pending compatibility
record whose `removeAfter` date is on or before the release date, either
remove the compatibility path where safe and validate the affected tests, or
write down why removal is blocked and get explicit maintainer approval before
shipping the expired compatibility path.
- When removing deprecated runtime/config compatibility, preserve any doctor
migration, repair, or hint that is still needed by supported upgrade paths.
Doctor-side compatibility should stay tracked in
`src/commands/doctor/shared/deprecation-compat.ts` until maintainers confirm
the repair is no longer needed.
- Revalidate compatibility replacement text during release planning. The
recommended replacement can shift as plugin ownership, externalization, and
config footprint move, so do not blindly copy stale replacement annotations
into release notes.
- Do not delete or rewrite beta tags after they leave the machine. If a
published or pushed beta needs a fix, commit the fix on the release branch and
increment to the next `-beta.N`.
@@ -123,12 +133,13 @@ Use this skill for release and publish-time workflow. Keep ordinary development
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
or editing a release, extract from `## YYYY.M.D` through the line before the
next level-2 heading and use that complete block as the release notes.
- When preparing release notes, scan `src/plugins/compat/registry.ts` for
plugin compatibility records with `warningStarts` or `removeAfter` within 7
days after the release date. Add an `Upcoming deprecations` note to the
release notes when any exist, including the compatibility code, target date,
replacement, and a link to the record's `docsPath` or `/plugins/compatibility`
when no more specific deprecation page exists.
- When preparing release notes, scan `src/plugins/compat/registry.ts` and
`src/commands/doctor/shared/deprecation-compat.ts` for compatibility records
with `warningStarts` or `removeAfter` within 7 days after the release date.
Add an `Upcoming deprecations` note to the release notes when any exist,
including the compatibility code, target date, replacement, and a link to the
record's `docsPath` or `/plugins/compatibility` when no more specific
deprecation page exists.
- When cutting a mac release with a beta GitHub prerelease:
- tag `vYYYY.M.D-beta.N` from the release commit
- create a prerelease titled `openclaw YYYY.M.D-beta.N`

View File

@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
- Plugins/startup: load the default `memory-core` slot during Gateway startup when permitted so active-memory recall can call `memory_search` and `memory_get` without requiring an explicit `plugins.slots.memory` entry, while preserving `plugins.slots.memory: "none"`. Thanks @codex.
- Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet.
- Plugins/compat: inventory doctor-side deprecation migrations separately from runtime plugin compatibility so release sweeps preserve needed repairs while enforcing dated removal windows. Thanks @vincentkoc.
- Plugins/compat: add missing dated compatibility records for legacy extension-api, memory registration, provider hook/type aliases, runtime aliases, channel SDK helpers, and approval/test utility shims. Thanks @vincentkoc.
- Plugins/CLI: refresh the persisted registry after managed plugin files are removed so ClawHub uninstall cannot leave stale `plugins list` entries. Thanks @codex.
- Plugins/CLI: make plugin install and uninstall config writes conflict-aware, clear stale denylist entries on explicit reinstall/removal, and delete managed plugin files only after config/index commit succeeds. Thanks @codex.

View File

@@ -1,17 +1,18 @@
---
summary: "Background memory consolidation with light, deep, and REM phases plus a Dream Diary"
title: "Dreaming"
sidebarTitle: "Dreaming"
read_when:
- You want memory promotion to run automatically
- You want to understand what each dreaming phase does
- You want to tune consolidation without polluting MEMORY.md
---
Dreaming is the background memory consolidation system in `memory-core`.
It helps OpenClaw move strong short-term signals into durable memory while
keeping the process explainable and reviewable.
Dreaming is the background memory consolidation system in `memory-core`. It helps OpenClaw move strong short-term signals into durable memory while keeping the process explainable and reviewable.
<Note>
Dreaming is **opt-in** and disabled by default.
</Note>
## What dreaming writes
@@ -32,69 +33,63 @@ Dreaming uses three cooperative phases:
| Deep | Score and promote durable candidates | Yes (`MEMORY.md`) |
| REM | Reflect on themes and recurring ideas | No |
These phases are internal implementation details, not separate user-configured
"modes."
These phases are internal implementation details, not separate user-configured "modes."
### Light phase
<AccordionGroup>
<Accordion title="Light phase">
Light phase ingests recent daily memory signals and recall traces, dedupes them, and stages candidate lines.
Light phase ingests recent daily memory signals and recall traces, dedupes them,
and stages candidate lines.
- Reads from short-term recall state, recent daily memory files, and redacted session transcripts when available.
- Writes a managed `## Light Sleep` block when storage includes inline output.
- Records reinforcement signals for later deep ranking.
- Never writes to `MEMORY.md`.
- Reads from short-term recall state, recent daily memory files, and redacted session transcripts when available.
- Writes a managed `## Light Sleep` block when storage includes inline output.
- Records reinforcement signals for later deep ranking.
- Never writes to `MEMORY.md`.
</Accordion>
<Accordion title="Deep phase">
Deep phase decides what becomes long-term memory.
### Deep phase
- Ranks candidates using weighted scoring and threshold gates.
- Requires `minScore`, `minRecallCount`, and `minUniqueQueries` to pass.
- Rehydrates snippets from live daily files before writing, so stale/deleted snippets are skipped.
- Appends promoted entries to `MEMORY.md`.
- Writes a `## Deep Sleep` summary into `DREAMS.md` and optionally writes `memory/dreaming/deep/YYYY-MM-DD.md`.
Deep phase decides what becomes long-term memory.
</Accordion>
<Accordion title="REM phase">
REM phase extracts patterns and reflective signals.
- Ranks candidates using weighted scoring and threshold gates.
- Requires `minScore`, `minRecallCount`, and `minUniqueQueries` to pass.
- Rehydrates snippets from live daily files before writing, so stale/deleted snippets are skipped.
- Appends promoted entries to `MEMORY.md`.
- Writes a `## Deep Sleep` summary into `DREAMS.md` and optionally writes `memory/dreaming/deep/YYYY-MM-DD.md`.
- Builds theme and reflection summaries from recent short-term traces.
- Writes a managed `## REM Sleep` block when storage includes inline output.
- Records REM reinforcement signals used by deep ranking.
- Never writes to `MEMORY.md`.
### REM phase
REM phase extracts patterns and reflective signals.
- Builds theme and reflection summaries from recent short-term traces.
- Writes a managed `## REM Sleep` block when storage includes inline output.
- Records REM reinforcement signals used by deep ranking.
- Never writes to `MEMORY.md`.
</Accordion>
</AccordionGroup>
## Session transcript ingestion
Dreaming can ingest redacted session transcripts into the dreaming corpus. When
transcripts are available, they are fed into the light phase alongside daily
memory signals and recall traces. Personal and sensitive content is redacted
before ingestion.
Dreaming can ingest redacted session transcripts into the dreaming corpus. When transcripts are available, they are fed into the light phase alongside daily memory signals and recall traces. Personal and sensitive content is redacted before ingestion.
## Dream Diary
Dreaming also keeps a narrative **Dream Diary** in `DREAMS.md`.
After each phase has enough material, `memory-core` runs a best-effort background
subagent turn (using the default runtime model) and appends a short diary entry.
Dreaming also keeps a narrative **Dream Diary** in `DREAMS.md`. After each phase has enough material, `memory-core` runs a best-effort background subagent turn (using the default runtime model) and appends a short diary entry.
This diary is for human reading in the Dreams UI, not a promotion source.
Dreaming-generated diary/report artifacts are excluded from short-term
promotion. Only grounded memory snippets are eligible to promote into
`MEMORY.md`.
<Note>
This diary is for human reading in the Dreams UI, not a promotion source. Dreaming-generated diary/report artifacts are excluded from short-term promotion. Only grounded memory snippets are eligible to promote into `MEMORY.md`.
</Note>
There is also a grounded historical backfill lane for review and recovery work:
- `memory rem-harness --path ... --grounded` previews grounded diary output from historical `YYYY-MM-DD.md` notes.
- `memory rem-backfill --path ...` writes reversible grounded diary entries into `DREAMS.md`.
- `memory rem-backfill --path ... --stage-short-term` stages grounded durable candidates into the same short-term evidence store the normal deep phase already uses.
- `memory rem-backfill --rollback` and `--rollback-short-term` remove those staged backfill artifacts without touching ordinary diary entries or live short-term recall.
<AccordionGroup>
<Accordion title="Backfill commands">
- `memory rem-harness --path ... --grounded` previews grounded diary output from historical `YYYY-MM-DD.md` notes.
- `memory rem-backfill --path ...` writes reversible grounded diary entries into `DREAMS.md`.
- `memory rem-backfill --path ... --stage-short-term` stages grounded durable candidates into the same short-term evidence store the normal deep phase already uses.
- `memory rem-backfill --rollback` and `--rollback-short-term` remove those staged backfill artifacts without touching ordinary diary entries or live short-term recall.
</Accordion>
</AccordionGroup>
The Control UI exposes the same diary backfill/reset flow so you can inspect
results in the Dreams scene before deciding whether the grounded candidates
deserve promotion. The Scene also shows a distinct grounded lane so you can see
which staged short-term entries came from historical replay, which promoted
items were grounded-led, and clear only grounded-only staged entries without
touching ordinary live short-term state.
The Control UI exposes the same diary backfill/reset flow so you can inspect results in the Dreams scene before deciding whether the grounded candidates deserve promotion. The Scene also shows a distinct grounded lane so you can see which staged short-term entries came from historical replay, which promoted items were grounded-led, and clear only grounded-only staged entries without touching ordinary live short-term state.
## Deep ranking signals
@@ -109,13 +104,11 @@ Deep ranking uses six weighted base signals plus phase reinforcement:
| Consolidation | 0.10 | Multi-day recurrence strength |
| Conceptual richness | 0.06 | Concept-tag density from snippet/path |
Light and REM phase hits add a small recency-decayed boost from
`memory/.dreams/phase-signals.json`.
Light and REM phase hits add a small recency-decayed boost from `memory/.dreams/phase-signals.json`.
## Scheduling
When enabled, `memory-core` auto-manages one cron job for a full dreaming
sweep. Each sweep runs phases in order: light -> REM -> deep.
When enabled, `memory-core` auto-manages one cron job for a full dreaming sweep. Each sweep runs phases in order: light → REM → deep.
Default cadence behavior:
@@ -125,43 +118,44 @@ Default cadence behavior:
## Quick start
Enable dreaming:
```json
{
"plugins": {
"entries": {
"memory-core": {
"config": {
"dreaming": {
"enabled": true
<Tabs>
<Tab title="Enable dreaming">
```json
{
"plugins": {
"entries": {
"memory-core": {
"config": {
"dreaming": {
"enabled": true
}
}
}
}
}
}
}
}
```
Enable dreaming with a custom sweep cadence:
```json
{
"plugins": {
"entries": {
"memory-core": {
"config": {
"dreaming": {
"enabled": true,
"timezone": "America/Los_Angeles",
"frequency": "0 */6 * * *"
```
</Tab>
<Tab title="Custom sweep cadence">
```json
{
"plugins": {
"entries": {
"memory-core": {
"config": {
"dreaming": {
"enabled": true,
"timezone": "America/Los_Angeles",
"frequency": "0 */6 * * *"
}
}
}
}
}
}
}
}
```
```
</Tab>
</Tabs>
## Slash command
@@ -174,47 +168,52 @@ Enable dreaming with a custom sweep cadence:
## CLI workflow
Use CLI promotion for preview or manual apply:
<Tabs>
<Tab title="Promotion preview / apply">
```bash
openclaw memory promote
openclaw memory promote --apply
openclaw memory promote --limit 5
openclaw memory status --deep
```
```bash
openclaw memory promote
openclaw memory promote --apply
openclaw memory promote --limit 5
openclaw memory status --deep
```
Manual `memory promote` uses deep-phase thresholds by default unless overridden with CLI flags.
Manual `memory promote` uses deep-phase thresholds by default unless overridden
with CLI flags.
</Tab>
<Tab title="Explain promotion">
Explain why a specific candidate would or would not promote:
Explain why a specific candidate would or would not promote:
```bash
openclaw memory promote-explain "router vlan"
openclaw memory promote-explain "router vlan" --json
```
```bash
openclaw memory promote-explain "router vlan"
openclaw memory promote-explain "router vlan" --json
```
</Tab>
<Tab title="REM harness preview">
Preview REM reflections, candidate truths, and deep promotion output without writing anything:
Preview REM reflections, candidate truths, and deep promotion output without
writing anything:
```bash
openclaw memory rem-harness
openclaw memory rem-harness --json
```
```bash
openclaw memory rem-harness
openclaw memory rem-harness --json
```
</Tab>
</Tabs>
## Key defaults
All settings live under `plugins.entries.memory-core.config.dreaming`.
| Key | Default |
| ----------- | ----------- |
| `enabled` | `false` |
| `frequency` | `0 3 * * *` |
<ParamField path="enabled" type="boolean" default="false">
Enable or disable the dreaming sweep.
</ParamField>
<ParamField path="frequency" type="string" default="0 3 * * *">
Cron cadence for the full dreaming sweep.
</ParamField>
Phase policy, thresholds, and storage behavior are internal implementation
details (not user-facing config).
See [Memory configuration reference](/reference/memory-config#dreaming)
for the full key list.
<Note>
Phase policy, thresholds, and storage behavior are internal implementation details (not user-facing config). See [Memory configuration reference](/reference/memory-config#dreaming) for the full key list.
</Note>
## Dreams UI
@@ -230,6 +229,6 @@ When enabled, the Gateway **Dreams** tab shows:
## Related
- [Memory](/concepts/memory)
- [Memory Search](/concepts/memory-search)
- [memory CLI](/cli/memory)
- [Memory CLI](/cli/memory)
- [Memory configuration reference](/reference/memory-config)
- [Memory search](/concepts/memory-search)

View File

@@ -31,6 +31,18 @@ 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. Also
revalidate each replacement annotation during release planning because plugin
ownership and config footprint can change as providers and channels move out of
core.
## Plugin inspector package
The plugin inspector should live outside the core OpenClaw repo as a separate
@@ -86,8 +98,8 @@ Current compatibility records include:
`register(api)`
- legacy SDK aliases such as `openclaw/extension-api`,
`openclaw/plugin-sdk/channel-runtime`, `openclaw/plugin-sdk/command-auth`
status builders, `openclaw/plugin-sdk/test-utils`, and the `ClawdbotConfig`
type alias
status builders, `openclaw/plugin-sdk/test-utils`, and the `ClawdbotConfig` /
`OpenClawSchemaType` type aliases
- bundled plugin allowlist and enablement behavior
- legacy provider/channel env-var manifest metadata
- legacy provider plugin hooks and type aliases while providers move to
@@ -112,6 +124,10 @@ Current compatibility records include:
- persisted plugin registry disable and install-migration env flags while
repair flows migrate operators to `openclaw plugins registry --refresh` and
`openclaw doctor --fix`
- legacy plugin-owned web search, web fetch, and x_search config paths while
doctor migrates them to `plugins.entries.<plugin>.config`
- legacy `plugins.installs` authored config and bundled plugin load-path
aliases while install metadata moves into the state-managed plugin ledger
New plugin code should prefer the replacement listed in the registry and in the
specific migration guide. Existing plugins can keep using a compatibility path

View File

@@ -0,0 +1,78 @@
import fs from "node:fs";
import { describe, expect, it } from "vitest";
import {
getDoctorDeprecationCompatRecord,
isDoctorDeprecationCompatCode,
listDeprecatedDoctorDeprecationCompatRecords,
listDoctorDeprecationCompatRecords,
} from "./deprecation-compat.js";
const datePattern = /^\d{4}-\d{2}-\d{2}$/u;
const requiredDoctorCompatCodes = [
"doctor-agent-runtime-embedded-harness",
"doctor-plugin-install-config-ledger",
"doctor-bundled-plugin-load-paths",
"doctor-web-search-plugin-config",
"doctor-web-fetch-plugin-config",
"doctor-x-search-plugin-config",
] as const;
function parseDate(date: string): Date {
return new Date(`${date}T00:00:00Z`);
}
function addUtcMonths(date: Date, months: number): Date {
const next = new Date(date);
next.setUTCMonth(next.getUTCMonth() + months);
return next;
}
describe("doctor deprecation compatibility inventory", () => {
it("keeps compatibility codes unique and lookup-safe", () => {
const records = listDoctorDeprecationCompatRecords();
const codes = records.map((record) => record.code);
expect(new Set(codes).size).toBe(codes.length);
expect(isDoctorDeprecationCompatCode("doctor-web-search-plugin-config")).toBe(true);
expect(isDoctorDeprecationCompatCode("missing-code")).toBe(false);
expect(getDoctorDeprecationCompatRecord("doctor-web-search-plugin-config").owner).toBe(
"provider",
);
});
it("tracks the known doctor migrations that protect plugin/config rollout", () => {
for (const code of requiredDoctorCompatCodes) {
expect(isDoctorDeprecationCompatCode(code), code).toBe(true);
}
});
it("requires dated deprecation metadata with a three-month maximum window", () => {
for (const record of listDeprecatedDoctorDeprecationCompatRecords()) {
expect(record.deprecated, record.code).toMatch(datePattern);
expect(record.warningStarts, record.code).toMatch(datePattern);
expect(record.removeAfter, record.code).toMatch(datePattern);
if (!record.warningStarts || !record.removeAfter) {
throw new Error(`${record.code} is missing deprecation window dates`);
}
const maxRemoveAfter = addUtcMonths(parseDate(record.warningStarts), 3);
const removeAfter = parseDate(record.removeAfter);
expect(removeAfter <= maxRemoveAfter, record.code).toBe(true);
}
});
it("keeps every record actionable", () => {
for (const record of listDoctorDeprecationCompatRecords()) {
expect(record.introduced, record.code).toMatch(datePattern);
expect(record.source, record.code).toBeTruthy();
expect(record.migration, record.code).toBeTruthy();
expect(record.replacement, record.code).toBeTruthy();
expect(record.docsPath, record.code).toMatch(/^\//u);
expect(fs.existsSync(record.migration), `${record.code}: ${record.migration}`).toBe(true);
expect(record.tests.length, record.code).toBeGreaterThan(0);
for (const testPath of record.tests) {
expect(fs.existsSync(testPath), `${record.code}: ${testPath}`).toBe(true);
}
}
});
});

View File

@@ -0,0 +1,273 @@
export type DoctorDeprecationCompatStatus = "active" | "deprecated" | "removal-pending" | "removed";
export type DoctorDeprecationCompatOwner =
| "agent-runtime"
| "audio"
| "browser"
| "channel"
| "config"
| "gateway"
| "plugin"
| "provider"
| "tools"
| "tts";
export type DoctorDeprecationCompatRecord<Code extends string = string> = {
code: Code;
status: DoctorDeprecationCompatStatus;
owner: DoctorDeprecationCompatOwner;
introduced: string;
deprecated?: string;
warningStarts?: string;
removeAfter?: string;
source: string;
migration: string;
replacement: string;
docsPath: string;
tests: readonly string[];
notes?: string;
};
const TODAY = "2026-04-26";
const MAX_REMOVE_AFTER = "2026-07-26";
function deprecatedCompatRecord<Code extends string>(
record: Omit<
DoctorDeprecationCompatRecord<Code>,
"deprecated" | "warningStarts" | "removeAfter" | "status"
> &
Partial<
Pick<
DoctorDeprecationCompatRecord<Code>,
"deprecated" | "removeAfter" | "status" | "warningStarts"
>
>,
): DoctorDeprecationCompatRecord<Code> {
return {
status: "deprecated",
deprecated: TODAY,
warningStarts: TODAY,
removeAfter: MAX_REMOVE_AFTER,
...record,
};
}
// Doctor migrations and repair shims can outlive the runtime/config compatibility
// path they repair. Release removals must check this inventory before deleting
// doctor fixes, and replacement notes should be revalidated against the current
// architecture because ownership and config footprint can shift during rollout.
export const DOCTOR_DEPRECATION_COMPAT_RECORDS = [
deprecatedCompatRecord({
code: "doctor-agent-runtime-embedded-harness",
owner: "agent-runtime",
introduced: "2026-04-25",
source: "agents.defaults.embeddedHarness; agents.list[].embeddedHarness",
migration: "src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts",
replacement: "agents.defaults.agentRuntime and agents.list[].agentRuntime",
docsPath: "/plugins/sdk-agent-harness",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
notes:
"Runtime-policy naming changed during the plugin architecture work; verify replacement wording against current agentRuntime docs before removal.",
}),
deprecatedCompatRecord({
code: "doctor-agent-sandbox-persession",
owner: "agent-runtime",
introduced: "2026-04-26",
source: "agents.defaults.sandbox.perSession; agents.list[].sandbox.perSession",
migration: "src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts",
replacement: "agents.*.sandbox.scope",
docsPath: "/cli/doctor",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-top-level-memory-search",
owner: "config",
introduced: "2026-04-26",
source: "memorySearch",
migration: "src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts",
replacement: "agents.defaults.memorySearch",
docsPath: "/cli/doctor",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-top-level-heartbeat",
owner: "config",
introduced: "2026-04-26",
source: "heartbeat",
migration: "src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts",
replacement: "agents.defaults.heartbeat and channels.defaults.heartbeat",
docsPath: "/automation",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-gateway-bind-host-aliases",
owner: "gateway",
introduced: "2026-04-26",
source: "gateway.bind host aliases such as 0.0.0.0 and localhost",
migration: "src/commands/doctor/shared/legacy-config-migrations.runtime.gateway.ts",
replacement: "gateway.bind.mode values such as lan, loopback, custom, tailnet, and auto",
docsPath: "/gateway/configuration",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-audio-transcription-command",
owner: "audio",
introduced: "2026-04-26",
source: "audio.transcription",
migration: "src/commands/doctor/shared/legacy-config-migrations.audio.ts",
replacement: "tools.media.audio.models",
docsPath: "/tools/media-overview",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-channel-thread-binding-ttl",
owner: "channel",
introduced: "2026-04-26",
source: "threadBindings.ttlHours",
migration: "src/commands/doctor/shared/legacy-config-migrations.channels.ts",
replacement: "threadBindings.idleHours",
docsPath: "/channels/channel-routing",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-channel-dm-aliases",
owner: "channel",
introduced: "2026-04-26",
source: "channels.<id>.dm.policy and channels.<id>.dm.allowFrom",
migration: "src/config/channel-compat-normalization.ts",
replacement: "channels.<id>.dmPolicy and channels.<id>.allowFrom",
docsPath: "/channels/channel-routing",
tests: ["src/commands/doctor/shared/channel-legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-channel-streaming-aliases",
owner: "channel",
introduced: "2026-04-26",
source: "streamMode, scalar streaming, chunkMode, blockStreaming, draftChunk, nativeStreaming",
migration: "src/config/channel-compat-normalization.ts",
replacement: "channels.<id>.streaming.*",
docsPath: "/channels/channel-routing",
tests: ["src/commands/doctor/shared/channel-legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-tts-provider-aliases",
owner: "tts",
introduced: "2026-04-26",
source: "messages.tts.openai/elevenlabs/edge and plugins.entries.voice-call.config.tts aliases",
migration: "src/commands/doctor/shared/legacy-config-migrations.runtime.tts.ts",
replacement: "messages.tts.providers.<provider> and microsoft instead of edge",
docsPath: "/tools/tts",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-plugin-install-config-ledger",
owner: "plugin",
introduced: "2026-04-25",
source: "plugins.installs in authored config",
migration: "src/config/plugin-install-config-migration.ts",
replacement: "state-managed plugins/installs.json install ledger",
docsPath: "/cli/plugins#registry",
tests: [
"src/config/io.write-config.test.ts",
"src/commands/doctor/shared/plugin-registry-migration.test.ts",
],
}),
deprecatedCompatRecord({
code: "doctor-bundled-plugin-load-paths",
owner: "plugin",
introduced: "2026-04-25",
source: "plugins.load.paths entries that point at bundled plugin source/dist locations",
migration: "src/commands/doctor/shared/bundled-plugin-load-paths.ts",
replacement: "packaged bundled plugins and the persisted plugin registry",
docsPath: "/cli/plugins#registry",
tests: ["src/commands/doctor/shared/bundled-plugin-load-paths.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-web-search-plugin-config",
owner: "provider",
introduced: "2026-04-26",
source: "tools.web.search.apiKey and tools.web.search.<provider>",
migration: "src/commands/doctor/shared/legacy-web-search-migrate.ts",
replacement: "plugins.entries.<plugin>.config.webSearch",
docsPath: "/tools/web",
tests: ["src/commands/doctor/shared/legacy-web-search-migrate.test.ts"],
notes:
"Provider/plugin ownership can move as bundled providers externalize; verify the current manifest owner before deleting migration support.",
}),
deprecatedCompatRecord({
code: "doctor-web-fetch-plugin-config",
owner: "provider",
introduced: "2026-04-26",
source: "tools.web.fetch.firecrawl",
migration: "src/commands/doctor/shared/legacy-web-fetch-migrate.ts",
replacement: "plugins.entries.firecrawl.config.webFetch",
docsPath: "/tools/web-fetch",
tests: ["src/commands/doctor/shared/legacy-web-fetch-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-x-search-plugin-config",
owner: "provider",
introduced: "2026-04-26",
source: "tools.web.x_search.apiKey",
migration: "src/commands/doctor/shared/legacy-x-search-migrate.ts",
replacement: "plugins.entries.xai.config.webSearch.apiKey",
docsPath: "/tools/grok-search",
tests: [
"src/commands/doctor/shared/legacy-x-search-migrate.test.ts",
"src/commands/doctor/shared/legacy-config-migrate.test.ts",
],
}),
deprecatedCompatRecord({
code: "doctor-talk-provider-shape",
owner: "tts",
introduced: "2026-04-26",
source: "legacy talk provider scalar fields and provider/provider ids",
migration: "src/commands/doctor/shared/legacy-talk-config-normalizer.ts",
replacement: "talk.providers.<provider>",
docsPath: "/tools/tts",
tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"],
}),
deprecatedCompatRecord({
code: "doctor-legacy-tools-by-sender",
owner: "tools",
introduced: "2026-04-26",
source: "untyped toolsBySender keys",
migration: "src/commands/doctor/shared/legacy-tools-by-sender.ts",
replacement: "typed id:, e164:, username:, or name: sender keys",
docsPath: "/tools/exec-approvals",
tests: ["src/commands/doctor/shared/legacy-tools-by-sender.test.ts"],
}),
] as const satisfies readonly DoctorDeprecationCompatRecord[];
export type DoctorDeprecationCompatCode =
(typeof DOCTOR_DEPRECATION_COMPAT_RECORDS)[number]["code"];
export type KnownDoctorDeprecationCompatRecord = DoctorDeprecationCompatRecord;
const doctorDeprecationCompatRecordByCode = new Map<
DoctorDeprecationCompatCode,
KnownDoctorDeprecationCompatRecord
>(DOCTOR_DEPRECATION_COMPAT_RECORDS.map((record) => [record.code, record]));
export function listDoctorDeprecationCompatRecords(): readonly KnownDoctorDeprecationCompatRecord[] {
return DOCTOR_DEPRECATION_COMPAT_RECORDS;
}
export function listDeprecatedDoctorDeprecationCompatRecords(): readonly KnownDoctorDeprecationCompatRecord[] {
return DOCTOR_DEPRECATION_COMPAT_RECORDS.filter((record) =>
(["deprecated", "removal-pending"] as readonly string[]).includes(record.status),
);
}
export function isDoctorDeprecationCompatCode(code: string): code is DoctorDeprecationCompatCode {
return doctorDeprecationCompatRecordByCode.has(code);
}
export function getDoctorDeprecationCompatRecord(
code: DoctorDeprecationCompatCode,
): KnownDoctorDeprecationCompatRecord {
const record = doctorDeprecationCompatRecordByCode.get(code);
if (!record) {
throw new Error(`Unknown doctor deprecation compatibility code: ${code}`);
}
return record;
}

View File

@@ -80,6 +80,8 @@ export type {
export type { OpenClawConfig } from "../config/config.js";
/** @deprecated Use OpenClawConfig instead */
export type { OpenClawConfig as ClawdbotConfig } from "../config/config.js";
/** @deprecated Use OpenClawConfig instead */
export type { OpenClawConfig as OpenClawSchemaType } from "../config/config.js";
export type {
MemoryPluginCapability,
MemoryPluginPublicArtifact,

View File

@@ -90,6 +90,31 @@ const knownDeprecatedSurfaceMarkers = [
file: "src/plugin-sdk/test-utils.ts",
marker: "Deprecated compatibility alias",
},
{
code: "plugin-install-config-ledger",
file: "src/config/plugin-install-config-migration.ts",
marker: "stripShippedPluginInstallConfigRecords",
},
{
code: "bundled-plugin-load-path-aliases",
file: "src/commands/doctor/shared/bundled-plugin-load-paths.ts",
marker: "plugins.load.paths",
},
{
code: "plugin-owned-web-search-config",
file: "src/commands/doctor/shared/legacy-web-search-migrate.ts",
marker: "tools.web.search",
},
{
code: "plugin-owned-web-fetch-config",
file: "src/commands/doctor/shared/legacy-web-fetch-migrate.ts",
marker: "tools.web.fetch.firecrawl",
},
{
code: "plugin-owned-x-search-config",
file: "src/commands/doctor/shared/legacy-x-search-migrate.ts",
marker: "tools.web.x_search",
},
] as const;
function parseDate(date: string): Date {

View File

@@ -267,6 +267,82 @@ export const PLUGIN_COMPAT_RECORDS = [
diagnostics: ["postinstall migration skip", "postinstall migration force deprecation warning"],
tests: ["src/commands/doctor/shared/plugin-registry-migration.test.ts"],
},
{
code: "plugin-install-config-ledger",
status: "deprecated",
owner: "config",
introduced: "2026-04-25",
deprecated: "2026-04-26",
warningStarts: "2026-04-26",
removeAfter: "2026-07-26",
replacement: "state-managed `plugins/installs.json` install ledger",
docsPath: "/cli/plugins#registry",
surfaces: ["plugins.installs authored config", "plugin install/update migration"],
diagnostics: ["config write migration warning", "doctor registry migration"],
tests: [
"src/config/io.write-config.test.ts",
"src/commands/doctor/shared/plugin-registry-migration.test.ts",
],
},
{
code: "bundled-plugin-load-path-aliases",
status: "deprecated",
owner: "config",
introduced: "2026-04-25",
deprecated: "2026-04-26",
warningStarts: "2026-04-26",
removeAfter: "2026-07-26",
replacement: "packaged bundled plugins resolved through the persisted plugin registry",
docsPath: "/cli/plugins#registry",
surfaces: ["plugins.load.paths entries pointing at bundled plugin source/dist paths"],
diagnostics: ["doctor bundled plugin load-path warning"],
tests: ["src/commands/doctor/shared/bundled-plugin-load-paths.test.ts"],
},
{
code: "plugin-owned-web-search-config",
status: "deprecated",
owner: "provider",
introduced: "2026-04-26",
deprecated: "2026-04-26",
warningStarts: "2026-04-26",
removeAfter: "2026-07-26",
replacement: "`plugins.entries.<plugin>.config.webSearch`",
docsPath: "/tools/web",
surfaces: ["tools.web.search.apiKey", "tools.web.search.<provider>"],
diagnostics: ["doctor legacy web-search config migration"],
tests: ["src/commands/doctor/shared/legacy-web-search-migrate.test.ts"],
},
{
code: "plugin-owned-web-fetch-config",
status: "deprecated",
owner: "provider",
introduced: "2026-04-26",
deprecated: "2026-04-26",
warningStarts: "2026-04-26",
removeAfter: "2026-07-26",
replacement: "`plugins.entries.firecrawl.config.webFetch`",
docsPath: "/tools/web-fetch",
surfaces: ["tools.web.fetch.firecrawl"],
diagnostics: ["doctor legacy web-fetch config migration"],
tests: ["src/commands/doctor/shared/legacy-web-fetch-migrate.test.ts"],
},
{
code: "plugin-owned-x-search-config",
status: "deprecated",
owner: "provider",
introduced: "2026-04-26",
deprecated: "2026-04-26",
warningStarts: "2026-04-26",
removeAfter: "2026-07-26",
replacement: "`plugins.entries.xai.config.webSearch.apiKey`",
docsPath: "/tools/grok-search",
surfaces: ["tools.web.x_search.apiKey"],
diagnostics: ["doctor legacy x_search config migration"],
tests: [
"src/commands/doctor/shared/legacy-x-search-migrate.test.ts",
LEGACY_CONFIG_MIGRATE_TEST_PATH,
],
},
{
code: "plugin-activate-entrypoint-alias",
status: "deprecated",
@@ -367,6 +443,20 @@ export const PLUGIN_COMPAT_RECORDS = [
diagnostics: ["plugin SDK compatibility warning"],
tests: ["src/plugins/contracts/plugin-sdk-index.test.ts"],
},
{
code: "openclaw-schema-type-alias",
status: "deprecated",
owner: "sdk",
introduced: "2026-04-26",
deprecated: "2026-04-26",
warningStarts: "2026-04-26",
removeAfter: "2026-07-26",
replacement: "`OpenClawConfig` from `openclaw/plugin-sdk/config-schema`",
docsPath: "/plugins/sdk-migration",
surfaces: ["openclaw/plugin-sdk `OpenClawSchemaType` type export"],
diagnostics: ["plugin SDK compatibility warning"],
tests: ["src/plugins/contracts/plugin-sdk-index.test.ts"],
},
{
code: "legacy-extension-api-import",
status: "deprecated",

View File

@@ -1,8 +1,9 @@
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { describe, expect, expectTypeOf, it } from "vitest";
import { buildPluginSdkPackageExports } from "../../plugin-sdk/entrypoints.js";
import type { ClawdbotConfig, OpenClawConfig, OpenClawSchemaType } from "../../plugin-sdk/index.js";
const pluginSdkIndexPath = fileURLToPath(new URL("../../plugin-sdk/index.ts", import.meta.url));
@@ -104,6 +105,11 @@ describe("plugin-sdk exports", () => {
]);
});
it("keeps deprecated root config type aliases aligned", () => {
expectTypeOf<ClawdbotConfig>().toEqualTypeOf<OpenClawConfig>();
expectTypeOf<OpenClawSchemaType>().toEqualTypeOf<OpenClawConfig>();
});
it("keeps package.json plugin-sdk exports synced with the manifest", async () => {
const packageJsonPath = path.join(process.cwd(), "package.json");
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8")) as {