fix(plugins): deprecate deactivate hook alias

This commit is contained in:
Vincent Koc
2026-05-16 18:35:00 +08:00
parent 7e4929e004
commit a85cd65775
9 changed files with 68 additions and 7 deletions

View File

@@ -20,7 +20,7 @@ Docs: https://docs.openclaw.ai
- Telegram: persist polling updates through restart replay so queued same-topic messages resume in order instead of losing context after a gateway restart. (#82256) Thanks @VACInc.
- Gateway/Gmail: abort in-flight Gmail watcher startup and hot-reload restarts before shutdown so reloads cannot spawn `gog serve` after the Gateway is closing. Thanks @frankekn.
- MCP plugin tools: forward host MCP `tools/call` `AbortSignal` through `createPluginToolsMcpHandlers().callTool` into plugin `tool.execute`, so host cancellation actually cancels in-flight plugin tool calls instead of letting them run to completion. Fixes #82424. (#82443) Thanks @joshavant.
- Plugins: accept `api.on("deactivate")` as a compatibility alias for `gateway_stop`, so external plugin cleanup handlers run on Gateway shutdown instead of being ignored as unknown hooks.
- Plugins: accept deprecated `api.on("deactivate")` registrations as a dated compatibility alias for `gateway_stop`, so external plugin cleanup handlers run on Gateway shutdown while authors get migration guidance.
- Media: ignore image MIME and filename hints when bytes sniff as generic containers, so zip/octet-stream payloads mislabeled as images do not become local image media or keep image file extensions when staged.
- Update/doctor: avoid materializing `groupAllowFrom` for channel schemas that reject it, so package-swap doctor repairs do not fail on externalized Slack configs.
- Gateway/media: prevent image filenames from overriding generic non-image byte sniffing, so zip/octet-stream payloads mislabeled as images are offloaded or rejected before they become inline image attachments.

View File

@@ -112,6 +112,8 @@ Current compatibility records include:
- legacy broad SDK imports such as `openclaw/plugin-sdk/compat`
- legacy hook-only plugin shapes and `before_agent_start`
- legacy `api.on("deactivate", ...)` cleanup hook names while plugins migrate to
`gateway_stop`
- legacy `activate(api)` plugin entrypoints while plugins migrate to
`register(api)`
- legacy SDK aliases such as `openclaw/extension-api`,

View File

@@ -145,7 +145,7 @@ observation-only.
**Lifecycle**
- `gateway_start` / `gateway_stop` - start or stop plugin-owned services with the Gateway
- `deactivate` - compatibility alias for `gateway_stop`; prefer `gateway_stop` in new plugins
- `deactivate` - deprecated compatibility alias for `gateway_stop`; use `gateway_stop` in new plugins
- `cron_changed` - observe gateway-owned cron lifecycle changes (added, updated, removed, started, finished, scheduled)
- **`before_install`** - inspect skill or plugin install scans and optionally block
@@ -438,8 +438,8 @@ before the next major release:
- **`before_agent_start`** remains for compatibility. New plugins should use
`before_model_resolve` and `before_prompt_build` instead of the combined
phase.
- **`deactivate`** remains as a cleanup compatibility alias. New plugins should
use `gateway_stop`.
- **`deactivate`** remains as a deprecated cleanup compatibility alias until
after 2026-08-16. New plugins should use `gateway_stop`.
- **`onResolution` in `before_tool_call`** now uses the typed
`PluginApprovalResolution` union (`allow-once` / `allow-always` / `deny` /
`timeout` / `cancelled`) instead of a free-form `string`.

View File

@@ -757,6 +757,29 @@ canonical replacement.
</Accordion>
<Accordion title="deactivate hook → gateway_stop">
**Old**: `api.on("deactivate", handler)`.
**New**: `api.on("gateway_stop", handler)`. The event and context are the
same shutdown cleanup contract; only the hook name changes.
```typescript
// Before
api.on("deactivate", async (event, ctx) => {
await stopPluginService(ctx);
});
// After
api.on("gateway_stop", async (event, ctx) => {
await stopPluginService(ctx);
});
```
`deactivate` remains wired as a deprecated compatibility alias until after
2026-08-16.
</Accordion>
<Accordion title="Provider discovery types → provider catalog types">
Four discovery type aliases are now thin wrappers over the
catalog-era types:

View File

@@ -135,6 +135,11 @@ const knownDeprecatedSurfaceMarkers = [
file: "src/plugin-sdk/compat.ts",
marker: "@deprecated Use `openclaw/plugin-sdk/channel-message`.",
},
{
code: "legacy-deactivate-hook-alias",
file: "src/plugins/hook-types.ts",
marker: "@deprecated Use gateway_stop",
},
{
code: "channel-route-key-aliases",
file: "src/plugin-sdk/channel-route.ts",

View File

@@ -23,6 +23,22 @@ export const PLUGIN_COMPAT_RECORDS = [
releaseNote:
"Legacy `before_agent_start` hook compatibility remains wired while plugins migrate to modern hook stages.",
},
{
code: "legacy-deactivate-hook-alias",
status: "deprecated",
owner: "sdk",
introduced: "2026-05-16",
deprecated: "2026-05-16",
warningStarts: "2026-05-16",
removeAfter: "2026-08-16",
replacement: "`gateway_stop` hook",
docsPath: "/plugins/hooks#upcoming-deprecations",
surfaces: ["api.on(\"deactivate\", ...)", "plugin typed hook registration"],
diagnostics: ["plugin runtime compatibility warning"],
tests: ["src/plugins/loader.test.ts"],
releaseNote:
"`api.on(\"deactivate\", ...)` remains wired as a deprecated compatibility alias while plugins migrate to `gateway_stop`.",
},
{
code: "hook-only-plugin-shape",
status: "active",

View File

@@ -94,6 +94,7 @@ export type PluginHookName =
| "subagent_delivery_target"
| "subagent_spawned"
| "subagent_ended"
/** @deprecated Use gateway_stop. */
| "deactivate"
| "gateway_start"
| "gateway_stop"
@@ -1003,11 +1004,13 @@ export type PluginHookHandlerMap = {
ctx: PluginHookSubagentContext,
) => Promise<void> | void;
/**
* Compatibility alias for gateway_stop.
* Deprecated compatibility alias for gateway_stop.
*
* New plugins should register gateway_stop directly; the loader normalizes
* deactivate registrations onto gateway_stop so cleanup handlers still run
* during Gateway shutdown.
*
* @deprecated Use gateway_stop.
*/
deactivate: (
event: PluginHookGatewayStopEvent,

View File

@@ -5939,7 +5939,8 @@ module.exports = {
registry.diagnostics.some(
(diag) =>
diag.pluginId === "legacy-deactivate-hook" &&
diag.message === 'typed hook "deactivate" is a compatibility alias for "gateway_stop"',
diag.message ===
'typed hook "deactivate" is deprecated (legacy-deactivate-hook-alias); use "gateway_stop". This compatibility alias will be removed after 2026-08-16.',
),
).toBe(true);
});

View File

@@ -51,6 +51,7 @@ import {
import { buildPluginApi } from "./api-builder.js";
import { normalizeRegisteredChannelPlugin } from "./channel-validation.js";
import { CODEX_APP_SERVER_EXTENSION_RUNTIME_ID } from "./codex-app-server-extension-factory.js";
import { getPluginCompatRecord } from "./compat/registry.js";
import type { CodexAppServerExtensionFactory } from "./codex-app-server-extension-types.js";
import {
isReservedCommandName,
@@ -187,6 +188,16 @@ export type PluginHttpRouteRegistration = RegistryTypesPluginHttpRouteRegistrati
};
const GATEWAY_METHOD_DISPATCH_CONTRACT = "authenticated-request";
const LEGACY_DEACTIVATE_HOOK_ALIAS_COMPAT = getPluginCompatRecord("legacy-deactivate-hook-alias");
function formatLegacyDeactivateHookAliasDiagnostic(): string {
const removeAfter =
LEGACY_DEACTIVATE_HOOK_ALIAS_COMPAT.removeAfter ?? "a future breaking release";
return (
`typed hook "deactivate" is deprecated (${LEGACY_DEACTIVATE_HOOK_ALIAS_COMPAT.code}); ` +
`use "gateway_stop". This compatibility alias will be removed after ${removeAfter}.`
);
}
type PluginOwnedProviderRegistration<T extends { id: string }> = {
pluginId: string;
@@ -2304,7 +2315,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
level: "warn",
pluginId: record.id,
source: record.source,
message: 'typed hook "deactivate" is a compatibility alias for "gateway_stop"',
message: formatLegacyDeactivateHookAliasDiagnostic(),
});
}
let effectiveHandler = handler;