From dceb2c343c44b284087df2eeea67ebb394a0d6c7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 12 Aug 2026 12:41:27 -0700 Subject: [PATCH] refactor: retire due compat-ledger surfaces (context-engine host params, deactivate alias, logging internals) (#121845) * refactor(plugins): retire deactivate hook alias * refactor(plugin-sdk): prune retired facade exports * test(logging): isolate logger test controls * refactor(logging): internalize file transport controls * test(plugin-sdk): preserve retired facade coverage * test(auto-reply): remove stale diagnostic imports * refactor(logging): delete dead config-read guard shouldSkipMutatingLoggingConfigRead had no production caller even on main; it survived the dead-export scan only via logger's testApi re-export. The test-isolation commit removed that mask, exposing the fossil. Delete the guard, its test-only re-export, its mock entry, and its dedicated test file. * refactor(plugin-sdk): retire due compatibility subpaths * test(plugin-sdk): type group policy predicates * refactor(plugin-sdk): split removed subpath records * refactor(secrets): remove retired collector barrel * test(plugin-sdk): tighten wildcard surface pin * refactor(plugin-sdk): retire matrix facade metadata * style(plugin-sdk): format facade metadata * fix(ci): load channel setup contracts from source Repair the main-owned regression from 99d662473cb4 (Peter Steinberger): the new env-contract test could consume stale ignored dist metadata instead of the checked-in plugin declaration. * test(plugin-sdk): refresh API baseline after rebase --- .../agent-config-primitives.json | 1 - .../agent-harness-runtime.json | 2 +- .../agent-harness.json | 2 +- .../agent-runtime.json | 2 +- .../plugin-sdk-api-baseline/channel-core.json | 2 +- .../channel-entry-contract.json | 2 +- .../channel-inbound.json | 2 +- .../channel-logging.json | 1 - .../channel-message.json | 2 +- .../channel-outbound.json | 2 +- .../channel-plugin-common.json | 2 +- .../channel-policy.json | 2 +- .../channel-secret-runtime.json | 1 - .../channel-streaming.json | 1 - .../config-mutation.json | 2 +- .../config-runtime.json | 2 +- .../plugin-sdk-api-baseline/core.json | 2 +- .../plugin-sdk-api-baseline/discord.json | 2 +- .../gateway-runtime.json | 2 +- .../plugin-sdk-api-baseline/group-access.json | 1 - .../plugin-sdk-api-baseline/hook-runtime.json | 2 +- .../inbound-reply-dispatch.json | 2 +- .../plugin-sdk-api-baseline/logging-core.json | 2 +- .../plugin-sdk-api-baseline/matrix.json | 1 - .../meeting-runtime.json | 2 +- .../model-session-runtime.json | 2 +- .../plugin-sdk-api-baseline/plugin-entry.json | 2 +- .../plugin-runtime.json | 2 +- .../provider-auth.json | 2 +- .../provider-catalog-runtime.json | 2 +- .../security-runtime.json | 2 +- .../plugin-sdk-api-baseline/text-runtime.json | 1 - .../plugin-sdk-api-baseline/tool-plugin.json | 2 +- .../webhook-ingress.json | 2 +- .../plugin-sdk-api-baseline/zod.json | 1 - docs/plugins/architecture-internals.md | 2 +- docs/plugins/compatibility.md | 4 +- docs/plugins/hooks.md | 7 +- docs/plugins/sdk-migration.md | 49 +-- docs/plugins/sdk-subpaths.md | 11 +- .../tsconfig.package-boundary.paths.json | 6 - extensions/xai/tsconfig.json | 6 - package.json | 32 -- packages/plugin-sdk/package.json | 16 - .../plugin-sdk/src/channel-secret-runtime.ts | 3 - packages/plugin-sdk/src/channel-streaming.ts | 3 - packages/plugin-sdk/src/text-runtime.ts | 3 - packages/plugin-sdk/src/zod.ts | 3 - scripts/check-plugin-sdk-exports.mts | 7 +- scripts/lib/deprecated-plugin-sdk-usage.mts | 4 - scripts/lib/extension-package-boundary.ts | 2 - ...plugin-sdk-deprecated-barrel-subpaths.json | 3 +- ...plugin-sdk-deprecated-public-subpaths.json | 10 +- scripts/lib/plugin-sdk-entries.mts | 6 +- scripts/lib/plugin-sdk-entrypoints.json | 8 - scripts/openclaw-npm-postpublish-verify.ts | 101 +----- scripts/plugin-sdk-surface-report.mts | 22 +- src/auto-reply/reply/dispatch-acp.test.ts | 5 - ...ispatch-from-config.shared.test-harness.ts | 14 - ...ispatch-from-config.stale-recovery.test.ts | 4 - ...atch-from-config.terminal-recovery.test.ts | 3 - .../dispatch-from-config.test-harness.ts | 6 - .../progress-draft-compositor.test.ts | 3 +- ...ogress-draft-compositor.visibility.test.ts | 3 +- .../streaming.lifecycle.test.ts} | 42 +-- src/channels/streaming.ts | 19 +- src/commands/channels.add.test.ts | 3 + .../run.diagnostic-events.test.ts | 2 +- src/logger.test.ts | 5 +- src/logger.ts | 12 - src/logging/config.ts | 7 - src/logging/console-capture.test.ts | 2 +- ...diagnostic-session-recovery-coordinator.ts | 2 +- ...stuck-session-recovery.integration.test.ts | 7 +- src/logging/diagnostic.test-support.ts | 25 ++ src/logging/diagnostic.test.ts | 6 +- src/logging/diagnostic.ts | 95 +----- src/logging/level-filter.test.ts | 1 - src/logging/log-file-size-cap.test.ts | 2 +- src/logging/logger-file-transport.test.ts | 3 +- src/logging/logger-file-transport.ts | 21 +- src/logging/logger-hostname-state.ts | 13 + src/logging/logger-redaction-behavior.test.ts | 2 +- src/logging/logger-timestamp.test.ts | 2 +- src/logging/logger-transport.test.ts | 4 +- src/logging/logger.settings.test.ts | 36 -- src/logging/logger.test-support.ts | 22 ++ src/logging/logger.ts | 52 +-- src/logging/redact.ts | 8 +- src/logging/subsystem.test.ts | 3 +- src/plugin-sdk/agent-config-primitives.ts | 6 - src/plugin-sdk/channel-logging.ts | 7 - src/plugin-sdk/channel-policy.test.ts | 103 ++++++ src/plugin-sdk/channel-policy.ts | 96 +++++- src/plugin-sdk/channel-secret-runtime.ts | 34 -- src/plugin-sdk/channel-streaming.ts | 4 - src/plugin-sdk/group-access.test.ts | 319 ------------------ src/plugin-sdk/group-access.ts | 177 ---------- src/plugin-sdk/matrix.ts | 6 - src/plugin-sdk/text-runtime.ts | 99 ------ src/plugin-sdk/zod.ts | 4 - .../compat/plugin-sdk-subpath-records.ts | 83 +++-- src/plugins/compat/registry-records.ts | 17 +- src/plugins/compat/registry.test.ts | 34 ++ ...tension-package-project-boundaries.test.ts | 10 - .../contracts/plugin-sdk-subpaths.test.ts | 22 -- src/plugins/hook-types.ts | 23 +- .../loader.hooks-and-runtime.test-utils.ts | 39 --- .../registry-registrars-tools-hooks.ts | 57 +--- .../channel-secret-collector-runtime.ts | 21 -- src/security/dm-policy-shared.ts | 68 ++-- src/shared/scoped-expiring-id-cache.ts | 2 +- .../runtime/logging-file-boundary-runtime.ts | 8 +- test/openclaw-npm-postpublish-verify.test.ts | 72 ---- test/release-check.test.ts | 1 - .../check-deprecated-api-usage.test.ts | 3 - 116 files changed, 562 insertions(+), 1557 deletions(-) delete mode 100644 docs/.generated/plugin-sdk-api-baseline/agent-config-primitives.json delete mode 100644 docs/.generated/plugin-sdk-api-baseline/channel-logging.json delete mode 100644 docs/.generated/plugin-sdk-api-baseline/channel-secret-runtime.json delete mode 100644 docs/.generated/plugin-sdk-api-baseline/channel-streaming.json delete mode 100644 docs/.generated/plugin-sdk-api-baseline/group-access.json delete mode 100644 docs/.generated/plugin-sdk-api-baseline/matrix.json delete mode 100644 docs/.generated/plugin-sdk-api-baseline/text-runtime.json delete mode 100644 docs/.generated/plugin-sdk-api-baseline/zod.json delete mode 100644 packages/plugin-sdk/src/channel-secret-runtime.ts delete mode 100644 packages/plugin-sdk/src/channel-streaming.ts delete mode 100644 packages/plugin-sdk/src/text-runtime.ts delete mode 100644 packages/plugin-sdk/src/zod.ts rename src/{plugin-sdk/channel-streaming.test.ts => channels/streaming.lifecycle.test.ts} (95%) create mode 100644 src/logging/diagnostic.test-support.ts create mode 100644 src/logging/logger-hostname-state.ts delete mode 100644 src/logging/logger.settings.test.ts create mode 100644 src/logging/logger.test-support.ts delete mode 100644 src/plugin-sdk/agent-config-primitives.ts delete mode 100644 src/plugin-sdk/channel-logging.ts delete mode 100644 src/plugin-sdk/channel-secret-runtime.ts delete mode 100644 src/plugin-sdk/channel-streaming.ts delete mode 100644 src/plugin-sdk/group-access.test.ts delete mode 100644 src/plugin-sdk/group-access.ts delete mode 100644 src/plugin-sdk/matrix.ts delete mode 100644 src/plugin-sdk/text-runtime.ts delete mode 100644 src/plugin-sdk/zod.ts delete mode 100644 src/secrets/channel-secret-collector-runtime.ts diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-config-primitives.json b/docs/.generated/plugin-sdk-api-baseline/agent-config-primitives.json deleted file mode 100644 index 92084313d4fb..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/agent-config-primitives.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"0349da0a93dadbdcff03ae8064b5066c5f2e1c28ee298bfa9ab3f1f8563300eb","entrypoint":"agent-config-primitives","importSpecifier":"openclaw/plugin-sdk/agent-config-primitives"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json index 65bc523e47e7..dcd2ac08e887 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json @@ -1 +1 @@ -{"contentHash":"31bcb6e7b685c67391cc16a005f4ff55ffd97d382a10f4b1796ddf2fd303a308","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} +{"contentHash":"0afe8b5e500dce4237ca4b5023ab225cfd31a9897e0d4bfdb2341886df035952","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json index d8f24331b9b5..1ca9cba851fc 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json @@ -1 +1 @@ -{"contentHash":"108af57a4b4bc57c70912fc40319876bb6ec994cce59d2b9c991980e18c4f80f","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} +{"contentHash":"4580a38b9e871c7d448c90cd247673929caaa7a298877d96d8e3f7e7c970b27a","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json index 86df5f87e0e0..f28c79c245c1 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json @@ -1 +1 @@ -{"contentHash":"adc174fdd6ceeaa664ecf483d76aec22ecb111a7a8f6534cb78463b4347abb5e","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} +{"contentHash":"96ad6087a29b2cb11f793f954afd31446854d173396b0d7fded033fddddf4dd3","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-core.json b/docs/.generated/plugin-sdk-api-baseline/channel-core.json index 84a99d349f4f..f0ede275a3c8 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-core.json @@ -1 +1 @@ -{"contentHash":"dd255204bf11dccfe9d866e7a6725935bfead7378a52ddfa6b60b9a58c0cdf76","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} +{"contentHash":"b84b53ca4792a410eb488ae4fb303e362c3c84d8eb72e37cf9c922183792a7cd","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json index 2c34141dd292..b7fb7fb60212 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json @@ -1 +1 @@ -{"contentHash":"4bc2ad950266148cfbb2acc7996de31401d88dd5f80ea28754e76f3bd66ef0d2","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} +{"contentHash":"cd582aff7d7278ddc71a3c99bd4f8b46e2083411f02096e8649e74b39ab001ea","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json index bb54e32fc363..660be69bf4c9 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json @@ -1 +1 @@ -{"contentHash":"f0be441226a760f75dc1634f93d7f28586a0b18f690e2dd0f87c5360f2cb7f03","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} +{"contentHash":"017a51cefc0713190d68e7ba9a7901d45342451ca3b05c4830072340fa86515e","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-logging.json b/docs/.generated/plugin-sdk-api-baseline/channel-logging.json deleted file mode 100644 index 5521c25263a8..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/channel-logging.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"be803c0341214a8e731f98cb7bf98654410ab1e0254d90f9f1ad9f442f70669f","entrypoint":"channel-logging","importSpecifier":"openclaw/plugin-sdk/channel-logging"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-message.json b/docs/.generated/plugin-sdk-api-baseline/channel-message.json index c6937ba3e11d..31ba5e415d4b 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-message.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-message.json @@ -1 +1 @@ -{"contentHash":"8bf7d1fd1e21861fd472365e0ab10ca811800e1b0cb9e6aac0e4d6a41613a9a2","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} +{"contentHash":"abb22832153a23cd5187f92f2cdee6d81889d61697fae04c8647eb3370a1aa93","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json index 2bbe61f4c659..aa67a89ff3e7 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json @@ -1 +1 @@ -{"contentHash":"d430c3def27acb48a5356dafac17b89b0cbfcb1d3f959136b8c7ae82586d87df","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} +{"contentHash":"a82dcfdcc661b2567ba2ba631d589887d5ca29cfe1d8600696fe0db859f4969f","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json index 588ab76e1ecb..a6e29608d23f 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json @@ -1 +1 @@ -{"contentHash":"aa9a880fa3abde607a3664e55cf6bd35966528d436c0438aa3c1b7607c8e46e7","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} +{"contentHash":"034d00199482a5490cb8202aa2ff6c5acd438486e8ab616b5f76674af7fb78c4","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-policy.json b/docs/.generated/plugin-sdk-api-baseline/channel-policy.json index 9058617212c0..6f71782e6ca3 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-policy.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-policy.json @@ -1 +1 @@ -{"contentHash":"6200788e555ccd22764a7aff25a94b4ce7176dd1443a5fe6f3bb81f23c561811","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"} +{"contentHash":"d6252e057b614160557b42923f45f42491b5872c835e5f1d372a031e338ecdd4","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-secret-runtime.json b/docs/.generated/plugin-sdk-api-baseline/channel-secret-runtime.json deleted file mode 100644 index 0e2edbb6305f..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/channel-secret-runtime.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"c705cf7c1ba06b0d473d6cd83e4701e8ec464b226446d5465ebffb0f656dc4d0","entrypoint":"channel-secret-runtime","importSpecifier":"openclaw/plugin-sdk/channel-secret-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-streaming.json b/docs/.generated/plugin-sdk-api-baseline/channel-streaming.json deleted file mode 100644 index f8d490b91aaa..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/channel-streaming.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"2e6f9a8e8a07de789f6673813b5acc17dd08a11420c9de1b3ac509d37786adc6","entrypoint":"channel-streaming","importSpecifier":"openclaw/plugin-sdk/channel-streaming"} diff --git a/docs/.generated/plugin-sdk-api-baseline/config-mutation.json b/docs/.generated/plugin-sdk-api-baseline/config-mutation.json index 9c4f6fc5b510..58dbd1435035 100644 --- a/docs/.generated/plugin-sdk-api-baseline/config-mutation.json +++ b/docs/.generated/plugin-sdk-api-baseline/config-mutation.json @@ -1 +1 @@ -{"contentHash":"f2849884f368eadff174176a2808c1c74d041968049b460d6f8b408682c5862b","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"} +{"contentHash":"56a3c566d98d262c3ac07c830f2b33bd864e35930335d7b4e3e2ccc76607779e","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"} diff --git a/docs/.generated/plugin-sdk-api-baseline/config-runtime.json b/docs/.generated/plugin-sdk-api-baseline/config-runtime.json index 3874d0f43170..cc46bb7e5ed9 100644 --- a/docs/.generated/plugin-sdk-api-baseline/config-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/config-runtime.json @@ -1 +1 @@ -{"contentHash":"2c21d121fd9d14d7f45368b5f02dff257b6750215d63d4be9ace28b5c0d3c0f8","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"} +{"contentHash":"180a151549ad301c08f9265c3f9fb7a17821c93dd5b0e4f6128f7512312dc909","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/core.json b/docs/.generated/plugin-sdk-api-baseline/core.json index a97ddd3aecfe..310ebd0e5de0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/core.json +++ b/docs/.generated/plugin-sdk-api-baseline/core.json @@ -1 +1 @@ -{"contentHash":"4c667b7e88cd752c294bcc731bc157c1ccb2a7aa4afc58f4013409191f3a9b28","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} +{"contentHash":"2efff507ffe87196b554e29043eadc0ba17a32864f3ccd1864d4200add9d8f87","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/discord.json b/docs/.generated/plugin-sdk-api-baseline/discord.json index 2c7c6e047633..e0a1e4af68ef 100644 --- a/docs/.generated/plugin-sdk-api-baseline/discord.json +++ b/docs/.generated/plugin-sdk-api-baseline/discord.json @@ -1 +1 @@ -{"contentHash":"027b2280171324b628865fb4cdf9f72c13c059385a2fbec5b650c69d66beecdc","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} +{"contentHash":"31bfbc294becf37f8ee037a0f8f50c69e4d04e097ff9bea16089115ee93b80df","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} diff --git a/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json b/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json index 50ead4f63efc..d231bce30e8e 100644 --- a/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json @@ -1 +1 @@ -{"contentHash":"adf5aa7e44a7bc6f6e0607fa35eae1fcb197749bb5a129c1848f85d61dc319a2","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"} +{"contentHash":"bb371c8a354a150acf264357e3cbcb755efc6f42d9a2143e5a9254c3bc3c2aa8","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/group-access.json b/docs/.generated/plugin-sdk-api-baseline/group-access.json deleted file mode 100644 index 2f5a11384c9d..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/group-access.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"c6ff5b325384c258ae96e677568d58652fe66ebc6745669a2137970154ef4b92","entrypoint":"group-access","importSpecifier":"openclaw/plugin-sdk/group-access"} diff --git a/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json b/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json index 6664287d31a2..a393c5f5c399 100644 --- a/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json @@ -1 +1 @@ -{"contentHash":"ca7135503da9f048e237fd1242f463f74feccb22da0bc63770e16d982487a7a9","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"} +{"contentHash":"c27a0ddd9f0a12bbb8377e8fcf8422ed2e066b8d248bc250c34e06518f1891a3","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json index c746dcca3132..5788f56f29af 100644 --- a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json +++ b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json @@ -1 +1 @@ -{"contentHash":"94ee499e1ea0653d3e7daed7c6aaa2db8d7c3026433eba62b19a5f6cda93028b","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} +{"contentHash":"07dbe53008bb2487dd63f28db4d81424cfb67498e4508aae6d86667e78fe1855","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} diff --git a/docs/.generated/plugin-sdk-api-baseline/logging-core.json b/docs/.generated/plugin-sdk-api-baseline/logging-core.json index ddadcb6d5273..0c8517a847d5 100644 --- a/docs/.generated/plugin-sdk-api-baseline/logging-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/logging-core.json @@ -1 +1 @@ -{"contentHash":"0b044375b584739e90aa9591e4be679a90cae0fd5e29b3ede60b942d236a7f3a","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"} +{"contentHash":"2efeb44c37461f4b366018d4e5e11b0c2e107336202388d8f17688b71954fa39","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/matrix.json b/docs/.generated/plugin-sdk-api-baseline/matrix.json deleted file mode 100644 index c70c04240d1c..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/matrix.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"192aa90e9d1d6e1bae65abd591cd0ab6307dc2bcc7626092e48ba96a171f30f8","entrypoint":"matrix","importSpecifier":"openclaw/plugin-sdk/matrix"} diff --git a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json index cfe816da9801..c9e854aa8c44 100644 --- a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json @@ -1 +1 @@ -{"contentHash":"69fe3479e42b74771fb86d228264e8e19a3a16e1a07df4f89499111c2df2bc13","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} +{"contentHash":"5ccd913160d86827e36459bf6c6e88a169306dd24e913b85e288cbbf2ea12157","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json b/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json index 608f8931e9d9..f6b00167233c 100644 --- a/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json @@ -1 +1 @@ -{"contentHash":"9c8d6c57eae34ab72ec465dbf610f75d5d1bf737d7f04eadab63c806ea8a51fa","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"} +{"contentHash":"cd909c52a27a558de6f2647c44544b81f80038bb02577fe9b60ea612a609948e","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json index 0bb25fd1bf8f..8006d43c8b2c 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json @@ -1 +1 @@ -{"contentHash":"f058be9cbffbcb6e1dafe69cbc701c7cc06d1ffc90323256685e31b111f91193","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} +{"contentHash":"d9ae989750b9f69072b5767cdf8995bde3f431da315e031cb80f35d2052a3917","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json index 3eb80e8bec0e..a2c1e7fd652d 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json @@ -1 +1 @@ -{"contentHash":"e7ac61e29d41e43a4c780831a8aa0e5bed10e47630a36879f2555216eb1cce54","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} +{"contentHash":"dce833138906cea4ce741068911c2e4818ebc37693edb2d2883e54eb7b25778e","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/provider-auth.json b/docs/.generated/plugin-sdk-api-baseline/provider-auth.json index 5630ca54d16e..3834a8520cb8 100644 --- a/docs/.generated/plugin-sdk-api-baseline/provider-auth.json +++ b/docs/.generated/plugin-sdk-api-baseline/provider-auth.json @@ -1 +1 @@ -{"contentHash":"c63113a4720b3d4795a3deb00ce3652cc9481522af311b9a269e4fe0d84703bb","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"} +{"contentHash":"ae9c54c47a1937328f56b23f03590d458248f58c597645ec5e0f65b19464e75a","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"} diff --git a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json index 1e1d21020f8a..d3a4f4a7148f 100644 --- a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json @@ -1 +1 @@ -{"contentHash":"12f6874f2b2c49f6fa22b91acdae820a725564d381a5b26dc4b2b4e65d0ef803","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} +{"contentHash":"0010661448f741237afcb47b75bdc2178d962e4e5751dfc4d26fdb1c4c487997","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/security-runtime.json b/docs/.generated/plugin-sdk-api-baseline/security-runtime.json index 6bb5c25f928f..ea07ae323dbd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/security-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/security-runtime.json @@ -1 +1 @@ -{"contentHash":"538f9d1c94c854e2c23a20e2b178be281775abf1ede99d21a543c689ccb28a78","entrypoint":"security-runtime","importSpecifier":"openclaw/plugin-sdk/security-runtime"} +{"contentHash":"e793cfb3c3eb076bb73ed290ddc08c49d2c8ef35fcf46bdbcac66f24c6413bf2","entrypoint":"security-runtime","importSpecifier":"openclaw/plugin-sdk/security-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/text-runtime.json b/docs/.generated/plugin-sdk-api-baseline/text-runtime.json deleted file mode 100644 index 637b59337ab9..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/text-runtime.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"9d63e2bd18378154e40c4055f34010a6db6c6e7913ebbbfcf2e8565ad7302d93","entrypoint":"text-runtime","importSpecifier":"openclaw/plugin-sdk/text-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json index b7d31acd4088..a189078481cd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json +++ b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json @@ -1 +1 @@ -{"contentHash":"f76d5908b66bb49851cccc530dc63fc5f3352b327963f1091a0f34fe9ccc693b","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} +{"contentHash":"a392d8bbb2ababa55a16ac115beff0ed0b790723c8e6c69c0a46d44c6984ed4e","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} diff --git a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json index f8e224cc6ced..1fc735ceeae6 100644 --- a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json +++ b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json @@ -1 +1 @@ -{"contentHash":"e9dd8d432098b26f2e102fe41edee7c556558ea9b26a0077da72b0967d6649d2","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} +{"contentHash":"a9f8b45ec3684fdb607c50fc47c41f62255c9ac0eae282b42441df58400fce31","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} diff --git a/docs/.generated/plugin-sdk-api-baseline/zod.json b/docs/.generated/plugin-sdk-api-baseline/zod.json deleted file mode 100644 index 7db0d093823e..000000000000 --- a/docs/.generated/plugin-sdk-api-baseline/zod.json +++ /dev/null @@ -1 +0,0 @@ -{"contentHash":"eafdc7277066acf893bfef9b635689c1a69d6d5af22ad8cee2b47351d9b53ca6","entrypoint":"zod","importSpecifier":"openclaw/plugin-sdk/zod"} diff --git a/docs/plugins/architecture-internals.md b/docs/plugins/architecture-internals.md index c49686cc0d78..60cc15da854f 100644 --- a/docs/plugins/architecture-internals.md +++ b/docs/plugins/architecture-internals.md @@ -697,7 +697,7 @@ plugin fields. See [Channel plugins](/plugins/sdk-channel-plugins). Runtime and config helpers live under matching focused `*-runtime` subpaths (`approval-runtime`, `agent-runtime`, `lazy-runtime`, `directory-runtime`, -`text-runtime`, `runtime-store`, `system-event-runtime`, `heartbeat-runtime`, +`text-utility-runtime`, `runtime-store`, `system-event-runtime`, `heartbeat-runtime`, `channel-activity-runtime`, etc.). Prefer `config-contracts`, `plugin-config-runtime`, `runtime-config-snapshot`, and `config-mutation` instead of the broad `config-runtime` compatibility barrel. diff --git a/docs/plugins/compatibility.md b/docs/plugins/compatibility.md index b9e64c279ca2..8b8ce7953e93 100644 --- a/docs/plugins/compatibility.md +++ b/docs/plugins/compatibility.md @@ -78,8 +78,8 @@ 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 +- the September SDK subpath window listed in the migration guide +- the `api.on("subagent_spawning", ...)` hook alias - 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` diff --git a/docs/plugins/hooks.md b/docs/plugins/hooks.md index 7fa3a355cf9c..c980cd0e07a6 100644 --- a/docs/plugins/hooks.md +++ b/docs/plugins/hooks.md @@ -212,7 +212,6 @@ For `sessions.create` calls with `parentSessionKey` and `emitCommandHooks: true` | Hook | Purpose | | -------------------------------- | ---------------------------------------------------------------------------------------------------- | | `gateway_start` / `gateway_stop` | Start or stop plugin-owned services with the Gateway | -| `deactivate` | Deprecated compatibility alias for `gateway_stop`; use `gateway_stop` in new plugins | | `cron_reconciled` | Reconcile against the complete Gateway cron state after startup or reload | | `cron_changed` | Observe Gateway-owned cron lifecycle changes (added, updated, removed, started, finished, scheduled) | | **`before_install`** | Inspect staged skill or plugin install material from a loaded plugin runtime | @@ -846,6 +845,10 @@ clean up long-running resources. The cron scheduler can still be loading when `gateway_start` runs, so do not use it as the baseline signal for an external cron projection. +The legacy `api.on("deactivate", ...)` alias was removed in August 2026. Use +`gateway_stop` for cleanup; see the +[migration note](/plugins/sdk-migration#deactivate-hook-alias). + Do not rely on the internal `gateway:startup` hook for plugin-owned runtime services. @@ -1049,8 +1052,6 @@ before the next major release: new plugins should not return thread routing from it. Core prepares `thread: true` subagent bindings through channel session-binding adapters before `subagent_spawned` fires. -- **`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`. diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 6b0383e54d0c..50d82e4a208f 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -576,6 +576,23 @@ Provider plugins should register text-inference providers through `ApiRegistry` should register directly on that registry so provider ownership and teardown stay scoped to the prepared runtime. +### Deactivate hook alias + +The `api.on("deactivate", handler)` compatibility alias was removed. Register +the same shutdown cleanup with `gateway_stop`: + +```typescript +// Before +api.on("deactivate", async (event, ctx) => { + await stopPluginService(ctx); +}); + +// After +api.on("gateway_stop", async (event, ctx) => { + await stopPluginService(ctx); +}); +``` + ### Private testing barrel `openclaw/plugin-sdk/testing` was repo-local and excluded from shipped package @@ -663,29 +680,6 @@ timeline for current status. - - **Old**: `api.on("deactivate", handler)`. - - **New**: `api.on("gateway_stop", handler)`. 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 it is - removed after 2026-08-16. - - - **Old**: `api.on("subagent_spawning", handler)` returning `threadBindingReady` or `deliveryOrigin`. @@ -1073,9 +1067,16 @@ The July 30 rows were removed after their early maintainer-authorized sweep: unused subpaths were deleted, earlier compatibility aliases were deleted, and bundled-only modules were demoted to private-local build mappings. +The August 15 compatibility subpaths `agent-config-primitives`, +`channel-logging`, `channel-secret-runtime`, `channel-streaming`, +`group-access`, `matrix`, `text-runtime`, and `zod` were retired early by +explicit SDK-owner approval in August 2026. Use the focused replacements in +the [Plugin SDK subpath catalog](/plugins/sdk-subpaths), and import `zod` +directly from the `zod` package. `inbound-reply-dispatch` remains available +until the next Plugin SDK major. + | Removal gate | Tier | SDK subpaths | | ----------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `2026-08-15` | Earlier compatibility deprecations | `agent-config-primitives`, `channel-logging`, `channel-secret-runtime`, `channel-streaming`, `group-access`, `matrix`, `text-runtime`, `zod` | | `2026-09-01` | Earlier compatibility deprecations | `channel-lifecycle`, `channel-message`, `channel-reply-pipeline`, `config-runtime`, `infra-runtime` | | `next-plugin-sdk-major` | Major-version compatibility gate | `inbound-reply-dispatch` | | `2026-10-01` | Media legacy projection | `agent-media-payload`, plus the non-subpath `MsgContext Media*` fields, channel inbound media payload builders, `buildMediaPayload`, hook media aliases, and `{{Media*}}` templates | diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index f70479a060a7..d1a1f2a408ae 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -41,9 +41,8 @@ Only the later-window deprecated subpaths remain exported. July 2026 aliases and unused subpaths were deleted, while bundled-only helpers were removed from the public package and are labeled private-local below. The maintained list is `scripts/lib/plugin-sdk-deprecated-public-subpaths.json`; CI rejects bundled -`plugin-sdk/text-runtime` are compatibility only, and `plugin-sdk/zod` is a -compatibility re-export: import `zod` directly from `zod`. The broad domain -barrels `plugin-sdk/agent-runtime`, `plugin-sdk/channel-lifecycle`, +imports of these compatibility-only subpaths. The broad domain barrels +`plugin-sdk/agent-runtime`, `plugin-sdk/channel-lifecycle`, `plugin-sdk/conversation-runtime`, `plugin-sdk/hook-runtime`, `plugin-sdk/media-runtime`, `plugin-sdk/plugin-runtime`, and `plugin-sdk/security-runtime` are likewise deprecated in favor of focused @@ -111,7 +110,6 @@ new code; see the per-row notes below. | `plugin-sdk/channel-config-writes` | Private-local after July 2026; Channel config-write authorization helpers | | `plugin-sdk/channel-plugin-common` | Shared channel plugin prelude exports | | `plugin-sdk/allowlist-config-edit` | Allowlist config edit/read helpers | - | `plugin-sdk/group-access` | Deprecated group-access decision helpers; use `resolveChannelMessageIngress` from `plugin-sdk/channel-ingress-runtime` | | `plugin-sdk/direct-dm-guard-policy` | Private-local after July 2026; Narrow direct-DM pre-crypto guard policy helpers | | `plugin-sdk/discord` | Deprecated Discord compatibility facade for published `@openclaw/discord@2026.3.13` and tracked owner compatibility; new plugins should use generic channel SDK subpaths | | `plugin-sdk/telegram-account` | Deprecated Telegram account-resolution compatibility facade for tracked owner compatibility; new plugins should use injected runtime helpers or generic channel SDK subpaths | @@ -120,7 +118,6 @@ new code; see the per-row notes below. | `plugin-sdk/channel-inbound` | Shared inbound helpers for event classification, context building, formatting, roots, debounce, mention matching, mention-policy, and inbound logging | | `plugin-sdk/channel-inbound-debounce` | Narrow inbound debounce helpers | | `plugin-sdk/channel-mention-gating` | Private-local after July 2026; Narrow mention-policy, mention marker, and mention text helpers without the broader inbound runtime surface | - | `plugin-sdk/channel-streaming` | Deprecated compatibility facade. Use `plugin-sdk/channel-outbound`. | | `plugin-sdk/channel-streaming-config` | Dependency-light channel streaming config readers (`getChannelStreamingConfigObject`, `resolveChannelStreamingNativeTransport`) for doctor contract closures and other control-plane paths that must not load the reply pipeline | | `plugin-sdk/channel-send-result` | Reply result types | | `plugin-sdk/channel-actions` | Channel message-action helpers, plus deprecated native schema helpers kept for plugin compatibility | @@ -197,7 +194,6 @@ usage endpoint failed or returned no usable usage data. | `plugin-sdk/command-surface` | Private-local after July 2026; Command-body normalization and command-surface helpers | | `plugin-sdk/allow-from` | Allow-from parsing, normalization, resolution, and matching helpers | | `plugin-sdk/provider-auth-login-flow-runtime` | Private-local after July 2026; Lazy provider auth login flow helpers for private channel and Web UI device-code pairing | - | `plugin-sdk/channel-secret-runtime` | Deprecated broad secret-contract surface (`collectSimpleChannelFieldAssignments`, `getChannelSurface`, `pushAssignment`, secret target types); prefer the focused subpaths below | | `plugin-sdk/channel-secret-basic-runtime` | Narrow secret-contract exports and target-registry builders for non-TTS channel/plugin secret surfaces | | `plugin-sdk/channel-secret-tts-runtime` | Private-local after July 2026; Narrow nested channel TTS secret assignment helpers | | `plugin-sdk/secret-ref-runtime` | Narrow SecretRef typing, resolution, setup-plan construction, and setup CLI scaffolding for plugin-owned secret providers | @@ -223,7 +219,6 @@ Use `isLoopbackHost(host)` when a plugin must accept only the local machine. It | `plugin-sdk/codex-mcp-projection` | Private-local after July 2026; Bundled Codex helper for projecting user MCP server config into Codex thread config; not for third-party plugins | | `plugin-sdk/codex-session-transcript-runtime` | Private-local bundled Codex helper for serializing transcript-mirror writes; not for third-party plugins | | `plugin-sdk/channel-runtime-context` | Generic channel runtime-context registration and lookup helpers | - | `plugin-sdk/matrix` | Deprecated Matrix compatibility facade for older third-party channel packages; new plugins should import `plugin-sdk/run-command` directly | | `plugin-sdk/runtime-store` | `createPluginRuntimeStore` | | `plugin-sdk/plugin-command-runtime` | Registry-generation-bound native plugin command candidates, terminal catalog decisions, and exact selected dispatch execution | | `plugin-sdk/plugin-runtime` | Deprecated broad barrel for plugin command/hook/http/interactive helpers; prefer focused plugin runtime subpaths | @@ -282,7 +277,6 @@ Use `isLoopbackHost(host)` when a plugin must accept only the local machine. It | `plugin-sdk/acp-runtime` | Private-local after July 2026; ACP runtime/session and reply-dispatch helpers | | `plugin-sdk/acp-runtime-backend` | Private-local after July 2026; Lightweight ACP backend registration and reply-dispatch helpers for startup-loaded plugins | | `plugin-sdk/acp-binding-resolve-runtime` | Private-local after July 2026; Read-only ACP binding resolution without lifecycle startup imports | - | `plugin-sdk/agent-config-primitives` | Deprecated agent runtime config-schema primitives; import schema primitives from a maintained plugin-owned surface | | `plugin-sdk/boolean-param` | Loose boolean param reader | | `plugin-sdk/dangerous-name-runtime` | Private-local after July 2026; Dangerous-name matching resolution helpers | | `plugin-sdk/device-bootstrap` | Device bootstrap and pairing token helpers, including `BOOTSTRAP_HANDOFF_OPERATOR_SCOPES` | @@ -352,7 +346,6 @@ Use `isLoopbackHost(host)` when a plugin must accept only the local machine. It | `plugin-sdk/transcripts` | Private-local after July 2026; Shared transcript source provider types, registry helpers, meeting-provider bridge factory, session descriptors, and utterance metadata | | `plugin-sdk/webhook-targets` | Private-local after July 2026; Webhook target registry and route-install helpers | | `plugin-sdk/web-media` | Shared remote/local media loading helpers | - | `plugin-sdk/zod` | Deprecated compatibility re-export; import `zod` from `zod` directly | | `plugin-sdk/plugin-test-api` | Repo-local minimal `createTestPluginApi` helper for direct plugin registration unit tests without importing repo test helper bridges | | `plugin-sdk/agent-runtime-test-contracts` | Repo-local native agent-runtime adapter contract fixtures for auth, delivery, fallback, tool-hook, prompt-overlay, schema, and transcript projection tests | | `plugin-sdk/channel-test-helpers` | Repo-local channel-oriented test helpers for generic actions/setup/status contracts, directory assertions, account startup lifecycle, send-config threading, runtime mocks, status issues, outbound delivery, and hook registration | diff --git a/extensions/tsconfig.package-boundary.paths.json b/extensions/tsconfig.package-boundary.paths.json index b32af237b6f5..543bb4380d47 100644 --- a/extensions/tsconfig.package-boundary.paths.json +++ b/extensions/tsconfig.package-boundary.paths.json @@ -554,12 +554,6 @@ "openclaw/plugin-sdk/channel-secret-basic-runtime": [ "../dist/plugin-sdk/channel-secret-basic-runtime.d.ts" ], - "openclaw/plugin-sdk/channel-secret-runtime": [ - "../dist/plugin-sdk/channel-secret-runtime.d.ts" - ], - "openclaw/plugin-sdk/channel-streaming": [ - "../dist/plugin-sdk/channel-streaming.d.ts" - ], "openclaw/plugin-sdk/error-runtime": [ "../dist/plugin-sdk/error-runtime.d.ts" ], diff --git a/extensions/xai/tsconfig.json b/extensions/xai/tsconfig.json index a0c22dd206d4..37b860b2ad7f 100644 --- a/extensions/xai/tsconfig.json +++ b/extensions/xai/tsconfig.json @@ -548,12 +548,6 @@ "openclaw/plugin-sdk/browser-maintenance": [ "../../dist/plugin-sdk/src/plugin-sdk/browser-maintenance.d.ts" ], - "openclaw/plugin-sdk/channel-secret-runtime": [ - "../../dist/plugin-sdk/channel-secret-runtime.d.ts" - ], - "openclaw/plugin-sdk/channel-streaming": [ - "../../dist/plugin-sdk/channel-streaming.d.ts" - ], "openclaw/plugin-sdk/error-runtime": [ "../../dist/plugin-sdk/error-runtime.d.ts" ], diff --git a/package.json b/package.json index cf7f61094445..56ae87743893 100644 --- a/package.json +++ b/package.json @@ -451,10 +451,6 @@ "types": "./dist/plugin-sdk/channel-setup.d.ts", "default": "./dist/plugin-sdk/channel-setup.js" }, - "./plugin-sdk/channel-streaming": { - "types": "./dist/plugin-sdk/channel-streaming.d.ts", - "default": "./dist/plugin-sdk/channel-streaming.js" - }, "./plugin-sdk/channel-streaming-config": { "types": "./dist/plugin-sdk/channel-streaming-config.d.ts", "default": "./dist/plugin-sdk/channel-streaming-config.js" @@ -704,10 +700,6 @@ "./plugin-sdk/thread-bindings-session-runtime": { "default": "./dist/plugin-sdk/thread-bindings-session-runtime.js" }, - "./plugin-sdk/text-runtime": { - "types": "./dist/plugin-sdk/text-runtime.d.ts", - "default": "./dist/plugin-sdk/text-runtime.js" - }, "./plugin-sdk/text-chunking": { "types": "./dist/plugin-sdk/text-chunking.d.ts", "default": "./dist/plugin-sdk/text-chunking.js" @@ -745,10 +737,6 @@ "types": "./dist/plugin-sdk/channel-secret-basic-runtime.d.ts", "default": "./dist/plugin-sdk/channel-secret-basic-runtime.js" }, - "./plugin-sdk/channel-secret-runtime": { - "types": "./dist/plugin-sdk/channel-secret-runtime.d.ts", - "default": "./dist/plugin-sdk/channel-secret-runtime.js" - }, "./plugin-sdk/channel-secret-tts-runtime": { "default": "./dist/plugin-sdk/channel-secret-tts-runtime.js" }, @@ -881,10 +869,6 @@ "./plugin-sdk/account-resolution-runtime": { "default": "./dist/plugin-sdk/account-resolution-runtime.js" }, - "./plugin-sdk/agent-config-primitives": { - "types": "./dist/plugin-sdk/agent-config-primitives.d.ts", - "default": "./dist/plugin-sdk/agent-config-primitives.js" - }, "./plugin-sdk/access-groups": { "default": "./dist/plugin-sdk/access-groups.js" }, @@ -947,10 +931,6 @@ "types": "./dist/plugin-sdk/discord.d.ts", "default": "./dist/plugin-sdk/discord.js" }, - "./plugin-sdk/matrix": { - "types": "./dist/plugin-sdk/matrix.d.ts", - "default": "./dist/plugin-sdk/matrix.js" - }, "./plugin-sdk/device-bootstrap": { "types": "./dist/plugin-sdk/device-bootstrap.d.ts", "default": "./dist/plugin-sdk/device-bootstrap.js" @@ -1020,10 +1000,6 @@ "types": "./dist/plugin-sdk/channel-inbound-debounce.d.ts", "default": "./dist/plugin-sdk/channel-inbound-debounce.js" }, - "./plugin-sdk/channel-logging": { - "types": "./dist/plugin-sdk/channel-logging.d.ts", - "default": "./dist/plugin-sdk/channel-logging.js" - }, "./plugin-sdk/channel-mention-gating": { "default": "./dist/plugin-sdk/channel-mention-gating.js" }, @@ -1125,10 +1101,6 @@ "./plugin-sdk/group-activation": { "default": "./dist/plugin-sdk/group-activation.js" }, - "./plugin-sdk/group-access": { - "types": "./dist/plugin-sdk/group-access.d.ts", - "default": "./dist/plugin-sdk/group-access.js" - }, "./plugin-sdk/global-singleton": { "default": "./dist/plugin-sdk/global-singleton.js" }, @@ -1472,10 +1444,6 @@ "types": "./dist/plugin-sdk/web-media.d.ts", "default": "./dist/plugin-sdk/web-media.js" }, - "./plugin-sdk/zod": { - "types": "./dist/plugin-sdk/zod.d.ts", - "default": "./dist/plugin-sdk/zod.js" - }, "./plugin-sdk/agent-core": { "default": "./dist/plugin-sdk/agent-core.js" }, diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index a438a57cc7c7..73067a3855e3 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -28,14 +28,6 @@ "types": "./dist/src/plugin-sdk/channel-activity-runtime.d.ts", "default": "./src/channel-activity-runtime.ts" }, - "./channel-secret-runtime": { - "types": "./dist/src/plugin-sdk/channel-secret-runtime.d.ts", - "default": "./src/channel-secret-runtime.ts" - }, - "./channel-streaming": { - "types": "./dist/src/plugin-sdk/channel-streaming.d.ts", - "default": "./src/channel-streaming.ts" - }, "./cli-runtime": { "types": "./dist/src/plugin-sdk/cli-runtime.d.ts", "default": "./src/cli-runtime.ts" @@ -212,10 +204,6 @@ "types": "./dist/src/plugin-sdk/talk-config-runtime.d.ts", "default": "./src/talk-config-runtime.ts" }, - "./text-runtime": { - "types": "./dist/src/plugin-sdk/text-runtime.d.ts", - "default": "./src/text-runtime.ts" - }, "./text-utility-runtime": { "types": "./dist/src/plugin-sdk/text-utility-runtime.d.ts", "default": "./src/text-utility-runtime.ts" @@ -235,10 +223,6 @@ "./video-generation": { "types": "./dist/src/plugin-sdk/video-generation.d.ts", "default": "./src/video-generation.ts" - }, - "./zod": { - "types": "./dist/src/plugin-sdk/zod.d.ts", - "default": "./src/zod.ts" } } } diff --git a/packages/plugin-sdk/src/channel-secret-runtime.ts b/packages/plugin-sdk/src/channel-secret-runtime.ts deleted file mode 100644 index f09cc6023bed..000000000000 --- a/packages/plugin-sdk/src/channel-secret-runtime.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Public package facade for channel secret runtime helpers. - -export * from "../../../src/plugin-sdk/channel-secret-runtime.js"; diff --git a/packages/plugin-sdk/src/channel-streaming.ts b/packages/plugin-sdk/src/channel-streaming.ts deleted file mode 100644 index 1c45e018e551..000000000000 --- a/packages/plugin-sdk/src/channel-streaming.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Public package facade for channel streaming helpers. - -export * from "../../../src/plugin-sdk/channel-streaming.js"; diff --git a/packages/plugin-sdk/src/text-runtime.ts b/packages/plugin-sdk/src/text-runtime.ts deleted file mode 100644 index 92808d8d5f35..000000000000 --- a/packages/plugin-sdk/src/text-runtime.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Public package facade for text runtime helpers. - -export * from "../../../src/plugin-sdk/text-runtime.js"; diff --git a/packages/plugin-sdk/src/zod.ts b/packages/plugin-sdk/src/zod.ts deleted file mode 100644 index 877675452e24..000000000000 --- a/packages/plugin-sdk/src/zod.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Public package facade for the shared zod re-export. - -export * from "../../../src/plugin-sdk/zod.js"; diff --git a/scripts/check-plugin-sdk-exports.mts b/scripts/check-plugin-sdk-exports.mts index 5d8304e56b79..f0a3129e7cc2 100755 --- a/scripts/check-plugin-sdk-exports.mts +++ b/scripts/check-plugin-sdk-exports.mts @@ -70,7 +70,7 @@ let missing = 0; `import { buildChannelConfigSchema, DmPolicySchema } from "openclaw/plugin-sdk/channel-config-schema"; import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core"; import { createPluginRuntimeStore, type PluginRuntime } from "openclaw/plugin-sdk/runtime-store"; -import { z } from "openclaw/plugin-sdk/zod"; +import { z } from "zod"; const runtimeStore = createPluginRuntimeStore({ pluginId: "package-consumer", @@ -110,6 +110,11 @@ export default defineChannelPluginEntry({ const openclawPackagePath = join(consumerRoot, "node_modules", "openclaw"); mkdirSync(dirname(openclawPackagePath), { recursive: true }); symlinkSync(repoRoot, openclawPackagePath, process.platform === "win32" ? "junction" : "dir"); + symlinkSync( + join(repoRoot, "node_modules", "zod"), + join(consumerRoot, "node_modules", "zod"), + process.platform === "win32" ? "junction" : "dir", + ); const result = spawnSync( process.execPath, diff --git a/scripts/lib/deprecated-plugin-sdk-usage.mts b/scripts/lib/deprecated-plugin-sdk-usage.mts index 706f09f51949..cb4b0f2b3c0a 100644 --- a/scripts/lib/deprecated-plugin-sdk-usage.mts +++ b/scripts/lib/deprecated-plugin-sdk-usage.mts @@ -51,8 +51,4 @@ export const BANNED_INTERNAL_PLUGIN_SDK_FACADE_MODULES: BannedInternalPluginSdkF modulePath: "src/plugin-sdk/inbound-envelope", canonical: "openclaw/plugin-sdk/channel-inbound", }, - { - modulePath: "src/plugin-sdk/text-runtime", - canonical: "the focused typed public Plugin SDK subpath for the imported helper", - }, ]; diff --git a/scripts/lib/extension-package-boundary.ts b/scripts/lib/extension-package-boundary.ts index 6a5a4cd91883..90c78cb1835d 100644 --- a/scripts/lib/extension-package-boundary.ts +++ b/scripts/lib/extension-package-boundary.ts @@ -64,8 +64,6 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = { "openclaw/plugin-sdk/channel-secret-basic-runtime": [ "../dist/plugin-sdk/channel-secret-basic-runtime.d.ts", ], - "openclaw/plugin-sdk/channel-secret-runtime": ["../dist/plugin-sdk/channel-secret-runtime.d.ts"], - "openclaw/plugin-sdk/channel-streaming": ["../dist/plugin-sdk/channel-streaming.d.ts"], "openclaw/plugin-sdk/error-runtime": ["../dist/plugin-sdk/error-runtime.d.ts"], "openclaw/plugin-sdk/secret-ref-runtime": ["../dist/plugin-sdk/secret-ref-runtime.d.ts"], "openclaw/plugin-sdk/ssrf-runtime": ["../dist/plugin-sdk/ssrf-runtime.d.ts"], diff --git a/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json b/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json index 1f8f37412524..10ff17b029ba 100644 --- a/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json +++ b/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json @@ -1,5 +1,4 @@ [ "channel-lifecycle", - "infra-runtime", - "text-runtime" + "infra-runtime" ] diff --git a/scripts/lib/plugin-sdk-deprecated-public-subpaths.json b/scripts/lib/plugin-sdk-deprecated-public-subpaths.json index 1723fa83b3e6..6f7699c8df34 100644 --- a/scripts/lib/plugin-sdk-deprecated-public-subpaths.json +++ b/scripts/lib/plugin-sdk-deprecated-public-subpaths.json @@ -1,21 +1,13 @@ [ "agent-media-payload", - "agent-config-primitives", "channel-lifecycle", - "channel-logging", "channel-message", "channel-reply-pipeline", - "channel-secret-runtime", - "channel-streaming", "command-auth", "config-runtime", "discord", - "group-access", "inbound-reply-dispatch", "infra-runtime", - "matrix", "messaging-targets", - "telegram-account", - "text-runtime", - "zod" + "telegram-account" ] diff --git a/scripts/lib/plugin-sdk-entries.mts b/scripts/lib/plugin-sdk-entries.mts index 173f3137c7c8..d4995533386d 100644 --- a/scripts/lib/plugin-sdk-entries.mts +++ b/scripts/lib/plugin-sdk-entries.mts @@ -111,11 +111,7 @@ export const deprecatedBarrelPluginSdkEntrypoints = pluginSdkSubpaths.filter((en ); /** Supported SDK facades backed by bundled plugins until generic contracts replace them. */ -export const supportedBundledFacadeSdkEntrypoints = [ - "discord", - "matrix", - "telegram-account", -] as const; +export const supportedBundledFacadeSdkEntrypoints = ["discord", "telegram-account"] as const; /** Plugin-owned surfaces intentionally public and documented for third-party plugins. */ export const publicPluginOwnedSdkEntrypoints = ["memory-core-host-engine-foundation"] as const; diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 811729352b0f..9a3da0c736e1 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -18,7 +18,6 @@ "setup", "setup-runtime", "channel-setup", - "channel-streaming", "channel-streaming-config", "setup-tools", "archive", @@ -90,7 +89,6 @@ "conversation-runtime", "thread-bindings-runtime", "thread-bindings-session-runtime", - "text-runtime", "text-chunking", "agent-scope-runtime", "agent-runtime", @@ -101,7 +99,6 @@ "plugin-command-runtime", "plugin-runtime", "channel-secret-basic-runtime", - "channel-secret-runtime", "channel-secret-tts-runtime", "secret-ref-runtime", "secret-file-runtime", @@ -158,7 +155,6 @@ "account-id", "account-resolution", "account-resolution-runtime", - "agent-config-primitives", "access-groups", "allow-from", "allowlist-config-edit", @@ -177,7 +173,6 @@ "collection-runtime", "direct-dm-guard-policy", "discord", - "matrix", "device-bootstrap", "diagnostic-runtime", "error-runtime", @@ -196,7 +191,6 @@ "channel-feedback", "channel-inbound", "channel-inbound-debounce", - "channel-logging", "channel-mention-gating", "channel-lifecycle", "channel-ingress-runtime", @@ -228,7 +222,6 @@ "ssrf-dispatcher", "string-coerce-runtime", "group-activation", - "group-access", "global-singleton", "directory-config-runtime", "directory-runtime", @@ -332,7 +325,6 @@ "webhook-targets", "webhook-request-guards", "web-media", - "zod", "agent-core", "agent-sessions", "llm" diff --git a/scripts/openclaw-npm-postpublish-verify.ts b/scripts/openclaw-npm-postpublish-verify.ts index 220fb598c880..b732d7613efd 100644 --- a/scripts/openclaw-npm-postpublish-verify.ts +++ b/scripts/openclaw-npm-postpublish-verify.ts @@ -15,14 +15,7 @@ import { import { builtinModules } from "node:module"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; -import { - dirname, - isAbsolute, - join, - posix as pathPosix, - relative, - win32 as pathWin32, -} from "node:path"; +import { isAbsolute, join, posix as pathPosix, relative, win32 as pathWin32 } from "node:path"; import { pathToFileURL } from "node:url"; import { expectDefined } from "../packages/normalization-core/src/expect.js"; import { ALWAYS_ALLOWED_RUNTIME_DIR_NAMES } from "../src/plugin-sdk/facade-activation-contract.ts"; @@ -469,7 +462,6 @@ export function collectInstalledPackageErrors(params: { errors.push(...collectInstalledBundledExtensionManifestErrors(params.packageRoot)); errors.push(...collectInstalledAlwaysAllowedRuntimeFacadeErrors(params.packageRoot)); errors.push(...collectInstalledContextEngineRuntimeErrors(params.packageRoot)); - errors.push(...collectInstalledPluginSdkZodArtifactErrors(params.packageRoot)); errors.push(...collectInstalledPluginSdkDeclarationErrors(params.packageRoot)); errors.push(...collectInstalledRootDependencyManifestErrors(params.packageRoot)); @@ -613,97 +605,6 @@ export function collectInstalledContextEngineRuntimeErrors(packageRoot: string): return errors; } -function resolveInstalledDistRelativeImport(params: { - distRoot: string; - importerPath: string; - specifier: string; -}): string | null { - if (!params.specifier.startsWith(".")) { - return null; - } - - const candidatePath = join(dirname(params.importerPath), params.specifier); - const candidatePaths = [ - candidatePath, - `${candidatePath}.js`, - `${candidatePath}.mjs`, - `${candidatePath}.cjs`, - join(candidatePath, "index.js"), - join(candidatePath, "index.mjs"), - join(candidatePath, "index.cjs"), - ]; - - for (const resolvedPath of candidatePaths) { - const relativePath = relative(params.distRoot, resolvedPath); - if ( - relativePath.length === 0 || - relativePath.startsWith("..") || - isAbsolute(relativePath) || - !existsSync(resolvedPath) - ) { - continue; - } - return resolvedPath; - } - - return null; -} - -export function collectInstalledPluginSdkZodArtifactErrors(packageRoot: string): string[] { - const distRoot = join(packageRoot, "dist"); - const entryRelativePath = "dist/plugin-sdk/zod.js"; - const entryPath = join(packageRoot, entryRelativePath); - const pending = [entryPath]; - const visited = new Set(); - - while (pending.length > 0) { - const filePath = pending.pop(); - if (!filePath || visited.has(filePath)) { - continue; - } - visited.add(filePath); - - if (!existsSync(filePath)) { - return [`installed package is missing required plugin SDK artifact: ${entryRelativePath}`]; - } - - const relativePath = relative(packageRoot, filePath).replaceAll("\\", "/"); - const fileStat = lstatSync(filePath); - if (!fileStat.isFile() || fileStat.size > MAX_INSTALLED_ROOT_DIST_JS_BYTES) { - return [ - `installed package plugin SDK artifact '${relativePath}' is invalid or exceeds ${MAX_INSTALLED_ROOT_DIST_JS_BYTES} bytes.`, - ]; - } - - const source = readFileSync(filePath, "utf8"); - const parsedSpecifiers = extractJavaScriptImportSpecifiers(source); - if (!parsedSpecifiers.ok) { - return [ - `installed package plugin SDK artifact '${relativePath}' could not be parsed for runtime dependency verification: ${parsedSpecifiers.error}.`, - ]; - } - - for (const specifier of parsedSpecifiers.specifiers) { - if (specifier === "zod" || specifier.startsWith("zod/")) { - return [ - `installed package plugin SDK zod artifact must be self-contained but ${relativePath} imports ${specifier}.`, - ]; - } - - const resolvedPath = resolveInstalledDistRelativeImport({ - distRoot, - importerPath: filePath, - specifier, - }); - if (resolvedPath) { - pending.push(resolvedPath); - } - } - } - - return []; -} - function collectInstalledPluginSdkDeclarationErrors(packageRoot: string): string[] { const pluginSdkDistRoot = join(packageRoot, "dist", "plugin-sdk"); const errors: string[] = []; diff --git a/scripts/plugin-sdk-surface-report.mts b/scripts/plugin-sdk-surface-report.mts index 4a9dd26ad799..464b59ed05bd 100644 --- a/scripts/plugin-sdk-surface-report.mts +++ b/scripts/plugin-sdk-surface-report.mts @@ -122,7 +122,6 @@ const defaultPublicDeprecatedExportsByEntrypointBudget = Object.freeze({ health: 0, // +1: shipped channel setup state-migration declaration during its migration window. "channel-entry-contract": 1, - "channel-streaming": 54, "approval-gateway-runtime": 1, "approval-handler-runtime": 1, "approval-reply-runtime": 0, @@ -141,22 +140,16 @@ const defaultPublicDeprecatedExportsByEntrypointBudget = Object.freeze({ "agent-media-payload": 3, // +2: deprecated media projection type and builder. "reply-payload": 2, - // +1: flushLogger projected through the deprecated text-runtime barrel. - "text-runtime": 192, "agent-runtime": 2, - "channel-secret-runtime": 23, // +4: session-write lease no-op compatibility stubs through the 2026.10 train. // +4: legacy AgentHarness, attempt, embedded-run, and side-question contracts remain // deprecated while external harnesses migrate to required-capability V2 contracts. "agent-harness": 2, "agent-harness-runtime": 12, - "agent-config-primitives": 2, "command-auth": 78, discord: 47, - matrix: 1, // +4: deprecated media projection type, builder, and turn aliases. "channel-inbound": 18, - "channel-logging": 4, "channel-lifecycle": 23, // +1: shared ingress error factory projected through the deprecated message barrel. // +1: shared ingress retention defaults projected through the deprecated message barrel. @@ -169,12 +162,10 @@ const defaultPublicDeprecatedExportsByEntrypointBudget = Object.freeze({ "session-store-runtime": 4, // +2: shipped Slack and Discord setup helpers retained through their package migration window. "setup-runtime": 2, - "group-access": 13, "reply-history": 6, "messaging-targets": 12, "provider-auth": 19, "telegram-account": 3, - zod: 282, } satisfies Record); export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env) { @@ -194,7 +185,7 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env // +1: dependency-light channel streaming config readers for doctor closures // (realtime-voice-activation is private-local and not counted here). // +1: registry-bound plugin command planning and exact selected execution. - 152, + 144, env, ), publicExports: readPluginSdkSurfaceBudgetEnv( @@ -275,7 +266,7 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env // +2: narrow channel agent-run terminal reader and outcome contract. // +5: narrow string, record, and error coercion helpers. // +1: normalized Gateway public origin resolver for plugin-generated links. - 4879, + 4308, env, ), publicFunctionExports: readPluginSdkSurfaceBudgetEnv( @@ -342,7 +333,7 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env // +1: narrow channel agent-run terminal reader. // +5: narrow string, record, and error coercion helpers. // +1: normalized Gateway public origin resolver for plugin-generated links. - 2932, + 2572, env, ), publicDeprecatedExports: readPluginSdkSurfaceBudgetEnv( @@ -351,24 +342,21 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env // +2: shipped Slack and Discord setup compatibility helpers. // +10: named media legacy projection deprecations across public compatibility barrels. // +2: channel prompt-context type and metadata builder compatibility aliases. - // +1: flushLogger projected through the deprecated text-runtime barrel. // +1: shared ingress error factory projected through channel-message. // +1: shared ingress retention defaults projected through channel-message. // +1: shipped channel setup state-migration declaration during its migration window. // +4: session-write lease no-op compatibility stubs through the 2026.10 train. // +7: restore still-existing deprecated inbound-dispatch compatibility re-exports. // +6: source-compatible harness contracts retained during the V2 migration window. - 1716, + 1142, env, ), publicWildcardReexports: readPluginSdkSurfaceBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_WILDCARD_REEXPORTS", - // -1: text-runtime now names its global-singleton exports explicitly. // -1: infra-runtime now names its error exports explicitly. // -1: infra-runtime excludes the internal system-event receipt API. - // -2: text-runtime names record and string coercion compatibility exports explicitly. // -1: infra-runtime re-exports number coercion directly from its canonical owner. - 75, + 50, env, ), }; diff --git a/src/auto-reply/reply/dispatch-acp.test.ts b/src/auto-reply/reply/dispatch-acp.test.ts index 727fb84ff65d..b19fd1937812 100644 --- a/src/auto-reply/reply/dispatch-acp.test.ts +++ b/src/auto-reply/reply/dispatch-acp.test.ts @@ -249,11 +249,6 @@ vi.mock("./dispatch-acp-media.runtime.js", async () => { vi.mock("../../logging/diagnostic.js", () => ({ markDiagnosticSessionProgress: diagnosticMocks.markDiagnosticSessionProgress, - isStuckSessionRecoveryEnabled: (config?: { diagnostics?: { enabled?: boolean } }) => - config?.diagnostics?.enabled !== false, - requestStuckDiagnosticSessionRecovery: vi.fn(), - resolveStuckSessionWarnMs: () => 120_000, - resolveStuckSessionAbortMs: () => 360_000, })); vi.mock("./dispatch-acp-transcript.runtime.js", () => ({ diff --git a/src/auto-reply/reply/dispatch-from-config.shared.test-harness.ts b/src/auto-reply/reply/dispatch-from-config.shared.test-harness.ts index 1e3d7e5c3bb5..d5a95b8960f4 100644 --- a/src/auto-reply/reply/dispatch-from-config.shared.test-harness.ts +++ b/src/auto-reply/reply/dispatch-from-config.shared.test-harness.ts @@ -3,7 +3,6 @@ import { vi } from "vitest"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { TtsAutoMode } from "../../config/types.tts.js"; import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js"; -import type { StuckSessionRecoveryOutcome } from "../../logging/diagnostic-session-recovery.js"; import type { PluginHookBeforeDispatchResult, PluginHookReplyDispatchResult, @@ -69,13 +68,6 @@ const diagnosticMocks = vi.hoisted(() => ({ logMessageProcessed: vi.fn(), logSessionStateChange: vi.fn(), markDiagnosticSessionProgress: vi.fn(), - requestStuckDiagnosticSessionRecovery: vi.fn<() => Promise>( - async () => ({ - status: "skipped" as const, - action: "keep_lane" as const, - reason: "active_reply_work" as const, - }), - ), })); const messageAuditMocks = vi.hoisted(() => ({ enabled: true, @@ -500,12 +492,6 @@ vi.mock("../../logging/diagnostic.js", () => ({ logSessionStateChange: diagnosticMocks.logSessionStateChange, logSessionTurnCreated: vi.fn(), markDiagnosticSessionProgress: diagnosticMocks.markDiagnosticSessionProgress, - isStuckSessionRecoveryEnabled: (config?: { diagnostics?: { enabled?: boolean } }) => - config?.diagnostics?.enabled !== false, - requestStuckDiagnosticSessionRecovery: diagnosticMocks.requestStuckDiagnosticSessionRecovery, - resolveStuckSessionWarnMs: () => 120_000, - resolveStuckSessionAbortMs: (stuckSessionWarnMs: number) => - Math.max(300_000, stuckSessionWarnMs * 3), })); vi.mock("../../audit/message-audit-events.js", () => ({ emitTrustedMessageAuditEvent: messageAuditMocks.emitTrustedMessageAuditEvent, diff --git a/src/auto-reply/reply/dispatch-from-config.stale-recovery.test.ts b/src/auto-reply/reply/dispatch-from-config.stale-recovery.test.ts index 32b6dc01707f..d60a53e89de6 100644 --- a/src/auto-reply/reply/dispatch-from-config.stale-recovery.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.stale-recovery.test.ts @@ -4,7 +4,6 @@ import { RUN_STALE_TAKEOVER_MS } from "../../logging/diagnostic-run-activity.js" import type { ReplyPayload } from "../types.js"; import { createDispatcher, - diagnosticMocks, mocks, noAbortResult, resetPluginTtsAndThreadMocks, @@ -67,7 +66,6 @@ describe("dispatchReplyFromConfig stale visible admission recovery", () => { mocks.routeReply.mockResolvedValue({ ok: true, delivered: true, messageId: "mock" }); mocks.tryFastAbortFromMessage.mockReset(); setNoAbort(); - diagnosticMocks.requestStuckDiagnosticSessionRecovery.mockReset(); }); afterEach(() => { @@ -107,7 +105,6 @@ describe("dispatchReplyFromConfig stale visible admission recovery", () => { expect(settled).toBe(false); expect(waitChanges).toEqual([true]); expect(replyResolver).not.toHaveBeenCalled(); - expect(diagnosticMocks.requestStuckDiagnosticSessionRecovery).not.toHaveBeenCalled(); activeOperation.complete(); const result = await resultPromise; @@ -143,7 +140,6 @@ describe("dispatchReplyFromConfig stale visible admission recovery", () => { await vi.advanceTimersByTimeAsync(REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS); const result = await resultPromise; - expect(diagnosticMocks.requestStuckDiagnosticSessionRecovery).not.toHaveBeenCalled(); expect(activeOperation.result).toEqual({ kind: "failed", code: "run_stalled" }); expect(result).toMatchObject({ queuedFinal: true, diff --git a/src/auto-reply/reply/dispatch-from-config.terminal-recovery.test.ts b/src/auto-reply/reply/dispatch-from-config.terminal-recovery.test.ts index d3eac4fc8521..72a6523748ed 100644 --- a/src/auto-reply/reply/dispatch-from-config.terminal-recovery.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.terminal-recovery.test.ts @@ -4,7 +4,6 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { ReplyPayload } from "../types.js"; import { createDispatcher, - diagnosticMocks, mocks, noAbortResult, resetPluginTtsAndThreadMocks, @@ -56,7 +55,6 @@ describe("dispatchReplyFromConfig terminal visible admission recovery", () => { mocks.routeReply.mockResolvedValue({ ok: true, delivered: true, messageId: "mock" }); mocks.tryFastAbortFromMessage.mockReset(); mocks.tryFastAbortFromMessage.mockResolvedValue(noAbortResult); - diagnosticMocks.requestStuckDiagnosticSessionRecovery.mockReset(); sessionStoreMocks.currentEntry = undefined; sessionStoreMocks.entriesBySessionKey.clear(); }); @@ -88,7 +86,6 @@ describe("dispatchReplyFromConfig terminal visible admission recovery", () => { const result = await dispatchReplyFromConfig(dispatchParams); - expect(diagnosticMocks.requestStuckDiagnosticSessionRecovery).not.toHaveBeenCalled(); expect(activeOperation.result).toMatchObject({ kind: "failed", code: "run_failed", diff --git a/src/auto-reply/reply/dispatch-from-config.test-harness.ts b/src/auto-reply/reply/dispatch-from-config.test-harness.ts index 6d9bbba97a9f..ad15d8947f64 100644 --- a/src/auto-reply/reply/dispatch-from-config.test-harness.ts +++ b/src/auto-reply/reply/dispatch-from-config.test-harness.ts @@ -501,12 +501,6 @@ export const describe0BeforeEach0 = () => { diagnosticMocks.logMessageProcessed.mockClear(); diagnosticMocks.logSessionStateChange.mockClear(); diagnosticMocks.markDiagnosticSessionProgress.mockClear(); - diagnosticMocks.requestStuckDiagnosticSessionRecovery.mockReset(); - diagnosticMocks.requestStuckDiagnosticSessionRecovery.mockResolvedValue({ - status: "skipped", - action: "keep_lane", - reason: "active_reply_work", - }); diagnosticMocks.logMessageDispatchStarted.mockClear(); diagnosticMocks.logMessageDispatchCompleted.mockClear(); hookMocks.runner.hasHooks.mockClear(); diff --git a/src/channels/progress-draft-compositor.test.ts b/src/channels/progress-draft-compositor.test.ts index 7c538ab3da86..890ebd5ca13c 100644 --- a/src/channels/progress-draft-compositor.test.ts +++ b/src/channels/progress-draft-compositor.test.ts @@ -19,7 +19,8 @@ function createTestProgressDraftCompositor( ...overrides, }); } -import { DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS } from "./streaming.js"; + +const DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS = 1_500; describe("createChannelProgressDraftCompositor", () => { it("tracks compact per-turn progress receipts", () => { diff --git a/src/channels/progress-draft-compositor.visibility.test.ts b/src/channels/progress-draft-compositor.visibility.test.ts index 65e22d2cec91..7bc10e50be52 100644 --- a/src/channels/progress-draft-compositor.visibility.test.ts +++ b/src/channels/progress-draft-compositor.visibility.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { createChannelProgressDraftCompositor } from "./progress-draft-compositor.js"; -import { DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS } from "./streaming.js"; + +const DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS = 1_500; function createProgress(update: () => Promise | boolean | void) { return createChannelProgressDraftCompositor({ diff --git a/src/plugin-sdk/channel-streaming.test.ts b/src/channels/streaming.lifecycle.test.ts similarity index 95% rename from src/plugin-sdk/channel-streaming.test.ts rename to src/channels/streaming.lifecycle.test.ts index 01f2d74624c4..106c45f070df 100644 --- a/src/plugin-sdk/channel-streaming.test.ts +++ b/src/channels/streaming.lifecycle.test.ts @@ -5,8 +5,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { buildChannelProgressDraftLine, createChannelProgressDraftGate, - DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS, - DEFAULT_PROGRESS_DRAFT_LABELS, formatChannelProgressDraftLine, formatChannelProgressDraftLineForEntry, formatChannelProgressDraftText, @@ -16,7 +14,6 @@ import { mergeChannelProgressDraftLine, resolveChannelPreviewStreamMode, resolveChannelProgressDraftMaxLineChars, - resolveChannelProgressDraftLabel, resolveChannelProgressDraftMaxLines, resolveChannelProgressDraftRender, resolveChannelStreamingBlockCoalesce, @@ -30,7 +27,9 @@ import { resolveChannelStreamingPreviewToolProgress, resolveTranscriptBackedChannelFinalText, selectLongerFinalText, -} from "./channel-streaming.js"; +} from "./streaming.js"; + +const DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS = 1_500; describe("channel-streaming", () => { afterEach(() => { @@ -216,22 +215,6 @@ describe("channel-streaming", () => { ).toBe(false); }); - it("uses auto progress labels when no explicit label is configured", () => { - expect(DEFAULT_PROGRESS_DRAFT_LABELS).toEqual(["Working"]); - expect(resolveChannelProgressDraftLabel({ random: () => 0 })).toBe( - DEFAULT_PROGRESS_DRAFT_LABELS[0], - ); - expect(resolveChannelProgressDraftLabel({ random: () => 0.99 })).toBe( - DEFAULT_PROGRESS_DRAFT_LABELS.at(-1), - ); - expect( - resolveChannelProgressDraftLabel({ - entry: { streaming: { progress: { label: " AUTO " } } }, - random: () => 0, - }), - ).toBe(DEFAULT_PROGRESS_DRAFT_LABELS[0]); - }); - it("separates progress labels from detail lines with a blank line", () => { const entry = { streaming: { progress: { label: "Working" } } }; @@ -243,23 +226,22 @@ describe("channel-streaming", () => { ).toBe("Working\n\n🛠️ pgrep -fl Discord || true (agent)\n• Discord is installed."); }); - it("supports explicit progress labels and custom label sets", () => { + it("renders automatic and configured progress labels through the public formatter", () => { + expect(formatChannelProgressDraftText({ lines: [], random: () => 0 })).toBe("Working"); expect( - resolveChannelProgressDraftLabel({ - entry: { streaming: { progress: { label: "Crunching" } } }, + formatChannelProgressDraftText({ + entry: { streaming: { progress: { label: " AUTO " } } }, + lines: [], + random: () => 0, }), - ).toBe("Crunching"); + ).toBe("Working"); expect( - resolveChannelProgressDraftLabel({ + formatChannelProgressDraftText({ entry: { streaming: { progress: { labels: ["Pearling"] } } }, + lines: [], random: () => 0.5, }), ).toBe("Pearling"); - expect( - resolveChannelProgressDraftLabel({ - entry: { streaming: { progress: { label: false } } }, - }), - ).toBeUndefined(); }); it("formats bounded progress draft text", () => { diff --git a/src/channels/streaming.ts b/src/channels/streaming.ts index 43d4dfa25a27..6dd636c16f97 100644 --- a/src/channels/streaming.ts +++ b/src/channels/streaming.ts @@ -35,10 +35,7 @@ export type { ChannelDeliveryStreamingConfig, ChannelPreviewStreamingConfig, ChannelStreamingBlockConfig, - ChannelStreamingCommandTextMode, - ChannelStreamingConfig, ChannelStreamingProgressConfig, - ChannelStreamingPreviewConfig, StreamingMode, TextChunkMode, } from "../config/types.base.js"; @@ -78,12 +75,10 @@ function asCommandTextMode(value: unknown): ChannelStreamingCommandTextMode | un return value === "raw" || value === "status" ? value : undefined; } -export { DEFAULT_PROGRESS_DRAFT_LABELS } from "../shared/progress-labels.js"; - // Short enough that a multi-tool turn is never silent, long enough that a // quick answer posts no draft at all: the gate only creates the draft when the // timer fires, and finalize cancels it. -export const DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS = 1_500; +const DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS = 1_500; const DEFAULT_PROGRESS_DRAFT_MAX_LINE_CHARS = 120; // Narration is a short paragraph, not a compact tool line; it gets its own // budget so the utility-model text is not mid-word truncated at line width. @@ -173,7 +168,7 @@ export type ChannelProgressLineOptions = { commandText?: ChannelStreamingCommandTextMode; }; -export type ChannelProgressDraftRenderMode = "text" | "rich"; +type ChannelProgressDraftRenderMode = "text" | "rich"; export type AgentPlanStepStatus = "pending" | "in_progress" | "completed"; @@ -182,7 +177,7 @@ export type AgentPlanStep = { status: AgentPlanStepStatus; }; -export type AgentPlanStepInput = AgentPlanStep | string; +type AgentPlanStepInput = AgentPlanStep | string; function isAgentPlanStepStatus(value: unknown): value is AgentPlanStepStatus { return value === "pending" || value === "in_progress" || value === "completed"; @@ -276,7 +271,7 @@ export type ChannelProgressDraftLineInput = summary?: string; }; -export type ChannelProgressDraftLineKind = ChannelProgressDraftLineInput["event"]; +type ChannelProgressDraftLineKind = ChannelProgressDraftLineInput["event"]; export type ChannelProgressDraftLine = { /** Stable line id used to update an existing progress line in place. */ @@ -379,7 +374,7 @@ function itemKindToToolName(kind: string | undefined): string | undefined { } /** Tools whose detail is raw command text; commandText policy applies to these. */ -export function isCommandToolName(name: string | undefined): boolean { +function isCommandToolName(name: string | undefined): boolean { return isCommandBearingToolCall(name); } @@ -484,7 +479,7 @@ export function formatChannelProgressDraftLine( return buildChannelProgressDraftLine(input, options)?.text; } -export function resolveChannelProgressDraftLineOptions( +function resolveChannelProgressDraftLineOptions( /** Channel streaming config source for command-text defaults. */ entry: StreamingCompatEntry | null | undefined, /** Caller-supplied line formatting overrides. */ @@ -927,7 +922,7 @@ function normalizeProgressLabels(labels: unknown): string[] { return normalized; } -export function resolveChannelProgressDraftLabel(params: { +function resolveChannelProgressDraftLabel(params: { entry?: StreamingCompatEntry | null; seed?: string; random?: () => number; diff --git a/src/commands/channels.add.test.ts b/src/commands/channels.add.test.ts index 65601ccfd79d..17456883ee43 100644 --- a/src/commands/channels.add.test.ts +++ b/src/commands/channels.add.test.ts @@ -1,4 +1,5 @@ // Channels add tests cover guided setup, plugin install paths, and channel account config writes. +import path from "node:path"; import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getBundledChannelSetupPlugin } from "../channels/plugins/bundled.js"; @@ -372,6 +373,8 @@ function registerExternalChatSetupPlugin(pluginId = "@vendor/external-chat-plugi } async function registerBundledSetupPlugin(channelId: string): Promise { + // Exercise the checked-in declarations, not a stale local dist tree left by an earlier build. + vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", path.resolve("extensions")); const actual = await vi.importActual( "../channels/plugins/bundled.js", ); diff --git a/src/cron/isolated-agent/run.diagnostic-events.test.ts b/src/cron/isolated-agent/run.diagnostic-events.test.ts index eb3923248ad4..833d5b9c0b42 100644 --- a/src/cron/isolated-agent/run.diagnostic-events.test.ts +++ b/src/cron/isolated-agent/run.diagnostic-events.test.ts @@ -5,7 +5,7 @@ import { onInternalDiagnosticEvent, resetDiagnosticEventsForTest, } from "../../infra/diagnostic-events.js"; -import { resetDiagnosticStateForTest } from "../../logging/diagnostic.js"; +import { resetDiagnosticStateForTest } from "../../logging/diagnostic.test-support.js"; vi.mock("../../agents/auth-profiles/source-check.js", () => ({ hasAnyAuthProfileStoreSource: vi.fn(() => false), diff --git a/src/logger.test.ts b/src/logger.test.ts index 68f3b2b9c3af..556cc7d35a92 100644 --- a/src/logger.test.ts +++ b/src/logger.test.ts @@ -4,7 +4,7 @@ import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import { theme } from "../packages/terminal-core/src/theme.js"; import { isVerbose, isYes, logVerbose, setVerbose, setYes } from "./globals.js"; -import { logDebug, logError, logInfo, logSuccess, logWarn } from "./logger.js"; +import { logDebug, logError, logInfo, logWarn } from "./logger.js"; import { resetLogger, setLoggerOverride, @@ -29,10 +29,9 @@ describe("logger helpers", () => { logInfo("info", runtime); logWarn("warn", runtime); - logSuccess("ok", runtime); logError("bad", runtime); - expect(log).toHaveBeenCalledTimes(3); + expect(log).toHaveBeenCalledTimes(2); expect(error).toHaveBeenCalledTimes(1); }); diff --git a/src/logger.ts b/src/logger.ts index fa2f5808a72d..5c188741dc6e 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -47,7 +47,6 @@ function logWithSubsystem(params: { const info = theme.info; const warn = theme.warn; -const success = theme.success; const danger = theme.error; export function logInfo(message: string, runtime: RuntimeEnv = defaultRuntime) { @@ -72,17 +71,6 @@ export function logWarn(message: string, runtime: RuntimeEnv = defaultRuntime) { }); } -export function logSuccess(message: string, runtime: RuntimeEnv = defaultRuntime) { - logWithSubsystem({ - message, - runtime, - runtimeMethod: "log", - runtimeFormatter: success, - loggerMethod: "info", - subsystemMethod: "info", - }); -} - export function logError(message: string, runtime: RuntimeEnv = defaultRuntime) { logWithSubsystem({ message, diff --git a/src/logging/config.ts b/src/logging/config.ts index 9b55d2dc48bc..09080ec97021 100644 --- a/src/logging/config.ts +++ b/src/logging/config.ts @@ -1,7 +1,6 @@ // Logging config helpers read and normalize logger configuration. import fs from "node:fs"; import { isRecord as isObjectRecord } from "@openclaw/normalization-core/record-coerce"; -import { getCommandPathWithRootOptions } from "../cli/argv.js"; import { resolveConfigEnvVars } from "../config/env-substitution.js"; import { resolveConfigIncludes, resolveConfigIncludesForTopLevelKey } from "../config/includes.js"; import { resolveConfigPath, resolveIncludeRoots } from "../config/paths.js"; @@ -55,12 +54,6 @@ function resolvePartialDiagnosticLoggingConfig(logging: unknown): LoggingConfig return Object.keys(partial).length > 0 ? (partial as LoggingConfig) : undefined; } -/** Avoids config reads that can mutate or validate config while schema/config commands run. */ -export function shouldSkipMutatingLoggingConfigRead(argv: string[] = process.argv): boolean { - const [primary, secondary] = getCommandPathWithRootOptions(argv, 2); - return primary === "config" && (secondary === "schema" || secondary === "validate"); -} - /** Reads the logging block from config, caching by resolved config path. */ export function readLoggingConfig(): LoggingConfig | undefined { try { diff --git a/src/logging/console-capture.test.ts b/src/logging/console-capture.test.ts index bbbf6357be78..402dec173865 100644 --- a/src/logging/console-capture.test.ts +++ b/src/logging/console-capture.test.ts @@ -14,7 +14,7 @@ import { import { defaultRuntime } from "../runtime.js"; import { withEnv } from "../test-utils/env.js"; import { createSuiteLogPathTracker } from "./log-test-helpers.js"; -import { testApi } from "./logger.js"; +import { testApi } from "./logger.test-support.js"; import { loggingState } from "./state.js"; import { captureConsoleSnapshot, diff --git a/src/logging/diagnostic-session-recovery-coordinator.ts b/src/logging/diagnostic-session-recovery-coordinator.ts index 106364e28c7e..a0f3aee2c209 100644 --- a/src/logging/diagnostic-session-recovery-coordinator.ts +++ b/src/logging/diagnostic-session-recovery-coordinator.ts @@ -173,7 +173,7 @@ function applyRecoveryOutcomeToDiagnosticState(params: { markActivity(); } -export function requestStuckSessionRecoveryOutcome( +function requestStuckSessionRecoveryOutcome( params: RequestStuckSessionRecoveryParams, ): Promise { const inFlightKey = recoveryRequestKey(params.request); diff --git a/src/logging/diagnostic-stuck-session-recovery.integration.test.ts b/src/logging/diagnostic-stuck-session-recovery.integration.test.ts index 8a9ecc208313..e74c58b5bec0 100644 --- a/src/logging/diagnostic-stuck-session-recovery.integration.test.ts +++ b/src/logging/diagnostic-stuck-session-recovery.integration.test.ts @@ -26,11 +26,8 @@ import { } from "./diagnostic-run-activity.js"; import { markDiagnosticModelStartedForTest } from "./diagnostic-run-activity.test-support.js"; import { recoverStuckDiagnosticSession } from "./diagnostic-stuck-session-recovery.runtime.js"; -import { - logSessionStateChange, - resetDiagnosticStateForTest, - startDiagnosticHeartbeat, -} from "./diagnostic.js"; +import { logSessionStateChange, startDiagnosticHeartbeat } from "./diagnostic.js"; +import { resetDiagnosticStateForTest } from "./diagnostic.test-support.js"; async function expectPendingAfterEventLoopTurn(promise: Promise): Promise { let settled = false; diff --git a/src/logging/diagnostic.test-support.ts b/src/logging/diagnostic.test-support.ts new file mode 100644 index 000000000000..9d14f7008542 --- /dev/null +++ b/src/logging/diagnostic.test-support.ts @@ -0,0 +1,25 @@ +import "./diagnostic.js"; + +type DiagnosticTestApi = { + resetDiagnosticStateForTest(): void; + resolveStuckSessionAbortMs(stuckSessionWarnMs: number): number; + resolveStuckSessionWarnMs(): number; +}; + +function getTestApi(): DiagnosticTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.diagnosticTestApi") + ] as DiagnosticTestApi; +} + +export function resetDiagnosticStateForTest(): void { + getTestApi().resetDiagnosticStateForTest(); +} + +export function resolveStuckSessionAbortMs(stuckSessionWarnMs: number): number { + return getTestApi().resolveStuckSessionAbortMs(stuckSessionWarnMs); +} + +export function resolveStuckSessionWarnMs(): number { + return getTestApi().resolveStuckSessionWarnMs(); +} diff --git a/src/logging/diagnostic.test.ts b/src/logging/diagnostic.test.ts index 0364e8bcb98b..0622a86120ed 100644 --- a/src/logging/diagnostic.test.ts +++ b/src/logging/diagnostic.test.ts @@ -51,11 +51,13 @@ import { logMessageQueued, logSessionStateChange, markDiagnosticSessionProgress, + startDiagnosticHeartbeat as startDiagnosticHeartbeatImpl, +} from "./diagnostic.js"; +import { resetDiagnosticStateForTest, resolveStuckSessionAbortMs, resolveStuckSessionWarnMs, - startDiagnosticHeartbeat as startDiagnosticHeartbeatImpl, -} from "./diagnostic.js"; +} from "./diagnostic.test-support.js"; function startDiagnosticHeartbeat( config?: Parameters[0], diff --git a/src/logging/diagnostic.ts b/src/logging/diagnostic.ts index 46387c522b45..e10dd2ebf2f0 100644 --- a/src/logging/diagnostic.ts +++ b/src/logging/diagnostic.ts @@ -44,7 +44,6 @@ import { } from "./diagnostic-session-context.js"; import { requestStuckSessionRecovery, - requestStuckSessionRecoveryOutcome, resetDiagnosticSessionRecoveryCoordinatorForTest, type RecoverStuckSession, } from "./diagnostic-session-recovery-coordinator.js"; @@ -71,7 +70,7 @@ import { stopDiagnosticStabilityRecorder, } from "./diagnostic-stability.js"; -export { diagnosticLogger, logLaneDequeue, logLaneEnqueue } from "./diagnostic-runtime.js"; +export { diagnosticLogger } from "./diagnostic-runtime.js"; const webhookStats = { received: 0, @@ -179,36 +178,6 @@ async function recoverStuckSession( }); } -/** - * @deprecated Unused by core since the dispatch-side recovery loop was removed - * (#101910); reply admission owns stale-run reclaim now. Kept only because the - * plugin SDK re-exports this module; scheduled for removal in the next SDK major. - */ -export function isStuckSessionRecoveryEnabled(config?: OpenClawConfig): boolean { - return areDiagnosticsEnabledForProcess() && isDiagnosticsEnabled(config); -} - -/** - * @deprecated Unused by core since the dispatch-side recovery loop was removed - * (#101910); reply admission owns stale-run reclaim now. Kept only because the - * plugin SDK re-exports this module; scheduled for removal in the next SDK major. - */ -export async function requestStuckDiagnosticSessionRecovery( - params: StuckSessionRecoveryRequest, -): Promise { - return requestStuckSessionRecoveryOutcome({ - recover: recoverStuckSession, - classification: { - eventType: "session.stalled", - reason: "visible_reply_wait_timeout", - classification: "stalled_agent_run", - activeWorkKind: "embedded_run", - recoveryEligible: false, - }, - request: params, - }); -} - function formatDiagnosticWorkLabel( state: { sessionId?: string; @@ -499,11 +468,11 @@ function formatDiagnosticWorkLabels(work: DiagnosticWorkSnapshot): string { return parts.join(" "); } -export function resolveStuckSessionWarnMs(): number { +function resolveStuckSessionWarnMs(): number { return DEFAULT_STUCK_SESSION_WARN_MS; } -export function resolveStuckSessionAbortMs(stuckSessionWarnMs: number): number { +function resolveStuckSessionAbortMs(stuckSessionWarnMs: number): number { return resolveStalledEmbeddedRunAbortMs(stuckSessionWarnMs); } @@ -916,15 +885,6 @@ export function logSessionStateChange( markActivity(); } -export function updateDiagnosticSessionFile(params: SessionRef) { - if (!areDiagnosticsEnabledForProcess()) { - return; - } - const state = getDiagnosticSessionState(params); - state.sessionFile = params.sessionFile?.trim() || undefined; - markActivity(); -} - export function markDiagnosticSessionProgress(params: SessionRef) { if (!areDiagnosticsEnabledForProcess()) { return; @@ -996,7 +956,7 @@ function formatSessionActivityLogFields(activity: DiagnosticSessionActivitySnaps return fields.join(" "); } -export function logSessionAttention( +function logSessionAttention( params: SessionRef & { state: SessionStateValue; ageMs: number; @@ -1127,25 +1087,6 @@ export function logSessionAttention( return classification; } -export function logRunAttempt(params: SessionRef & { runId: string; attempt: number }) { - if (!areDiagnosticsEnabledForProcess()) { - return; - } - diag.debug( - `run attempt: sessionId=${params.sessionId ?? "unknown"} sessionKey=${ - params.sessionKey ?? "unknown" - } runId=${params.runId} attempt=${params.attempt}`, - ); - emitDiagnosticEvent({ - type: "run.attempt", - sessionId: params.sessionId, - sessionKey: params.sessionKey, - runId: params.runId, - attempt: params.attempt, - }); - markActivity(); -} - export function logToolLoopAction( params: SessionRef & { toolName: string; @@ -1191,18 +1132,6 @@ export function logToolLoopAction( markActivity(); } -export function logActiveRuns() { - if (!areDiagnosticsEnabledForProcess()) { - return; - } - const now = Date.now(); - const activeSessions = Array.from(diagnosticSessionStates.entries()) - .filter(([, s]) => s.state === "processing") - .map(([id, s]) => `${id}(q=${s.queueDepth},age=${Math.round((now - s.lastActivity) / 1000)}s)`); - diag.debug(`active runs: count=${activeSessions.length} sessions=[${activeSessions.join(", ")}]`); - markActivity(); -} - let heartbeatInterval: NodeJS.Timeout | null = null; let lastDiagnosticHeartbeatTickAt: number | undefined; @@ -1391,11 +1320,7 @@ export function stopDiagnosticHeartbeat() { uninstallDiagnosticStabilityFatalHook(); } -export function getDiagnosticSessionStateCountForTest(): number { - return diagnosticSessionStates.size; -} - -export function resetDiagnosticStateForTest(): void { +function resetDiagnosticStateForTest(): void { stopDiagnosticHeartbeat(); resetDiagnosticSessionRecoveryCoordinatorForTest(); resetDiagnosticSessionStateForTest(); @@ -1410,4 +1335,14 @@ export function resetDiagnosticStateForTest(): void { resetDiagnosticStabilityRecorderForTest(); resetDiagnosticStabilityBundleForTest(); } + +const testing = { + resetDiagnosticStateForTest, + resolveStuckSessionAbortMs, + resolveStuckSessionWarnMs, +}; + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.diagnosticTestApi")] = testing; +} /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/logging/level-filter.test.ts b/src/logging/level-filter.test.ts index 682d8b733a20..ddcbd854e495 100644 --- a/src/logging/level-filter.test.ts +++ b/src/logging/level-filter.test.ts @@ -7,7 +7,6 @@ const { readLoggingConfigMock } = vi.hoisted(() => ({ vi.mock("./config.js", () => ({ readLoggingConfig: readLoggingConfigMock, - shouldSkipMutatingLoggingConfigRead: () => false, })); let logging: typeof import("../logging.js"); diff --git a/src/logging/log-file-size-cap.test.ts b/src/logging/log-file-size-cap.test.ts index 0f1380ad9ed7..30a0d61ecd84 100644 --- a/src/logging/log-file-size-cap.test.ts +++ b/src/logging/log-file-size-cap.test.ts @@ -9,7 +9,7 @@ import { setLoggerOverride, } from "../logging.js"; import { createSuiteLogPathTracker } from "./log-test-helpers.js"; -import { testApi } from "./logger.js"; +import { testApi } from "./logger.test-support.js"; const DEFAULT_MAX_FILE_BYTES = 100 * 1024 * 1024; const logPathTracker = createSuiteLogPathTracker("openclaw-log-cap-"); diff --git a/src/logging/logger-file-transport.test.ts b/src/logging/logger-file-transport.test.ts index 5f9dff1e4791..7e38ff59148e 100644 --- a/src/logging/logger-file-transport.test.ts +++ b/src/logging/logger-file-transport.test.ts @@ -3,7 +3,8 @@ import fs from "node:fs"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { appendRegularFile } from "../infra/regular-file.js"; import { createSuiteLogPathTracker } from "./log-test-helpers.js"; -import { getLogger, resetLogger, setLoggerOverride, testApi } from "./logger.js"; +import { getLogger, resetLogger, setLoggerOverride } from "./logger.js"; +import { testApi } from "./logger.test-support.js"; const logPathTracker = createSuiteLogPathTracker("openclaw-file-transport-"); diff --git a/src/logging/logger-file-transport.ts b/src/logging/logger-file-transport.ts index 981d13abde03..cbc3fc9a2b20 100644 --- a/src/logging/logger-file-transport.ts +++ b/src/logging/logger-file-transport.ts @@ -283,7 +283,7 @@ if (process.env.VITEST !== "true") { } /** Enqueues one serialized record without waiting for filesystem I/O. */ -export function enqueueFileLog(entry: FileLogQueueEntry): void { +function enqueueFileLog(entry: FileLogQueueEntry): void { if (processExiting) { writeEntriesSync([entry]); return; @@ -305,7 +305,7 @@ export function enqueueFileLog(entry: FileLogQueueEntry): void { } /** Waits until every record currently queued for the async transport has settled. */ -export async function flushFileLogQueue(): Promise { +async function flushFileLogQueue(): Promise { for (;;) { if (scheduledFlush) { clearImmediate(scheduledFlush); @@ -323,7 +323,7 @@ export async function flushFileLogQueue(): Promise { } /** Synchronously rescues pending records for process.exit() and crash-adjacent paths. */ -export function drainFileLogQueueSync(): void { +function drainFileLogQueueSync(): void { if (scheduledFlush) { clearImmediate(scheduledFlush); scheduledFlush = null; @@ -339,15 +339,15 @@ export function drainFileLogQueueSync(): void { writeEntriesSync(entries); } -export function setFileLogQueueMaxRecordsForTests(value?: number): void { +function setFileLogQueueMaxRecordsForTests(value?: number): void { maxQueuedRecords = Math.max(1, value ?? DEFAULT_MAX_QUEUED_RECORDS); } -export function setFileLogAppenderForTests(value?: FileLogAppender): void { +function setFileLogAppenderForTests(value?: FileLogAppender): void { appendFile = value ?? appendRegularFile; } -export function resetFileLogTransportForTests(): void { +function resetFileLogTransportForTests(): void { drainFileLogQueueSync(); removeProcessHooks(); processExiting = false; @@ -355,3 +355,12 @@ export function resetFileLogTransportForTests(): void { maxQueuedRecords = DEFAULT_MAX_QUEUED_RECORDS; warnedRotationFiles.clear(); } + +export const fileLogTransport = { + drainSync: drainFileLogQueueSync, + enqueue: enqueueFileLog, + flush: flushFileLogQueue, + resetForTests: resetFileLogTransportForTests, + setAppenderForTests: setFileLogAppenderForTests, + setMaxQueuedRecordsForTests: setFileLogQueueMaxRecordsForTests, +}; diff --git a/src/logging/logger-hostname-state.ts b/src/logging/logger-hostname-state.ts new file mode 100644 index 000000000000..7d72cad734e4 --- /dev/null +++ b/src/logging/logger-hostname-state.ts @@ -0,0 +1,13 @@ +import os from "node:os"; + +type LoggerHostnameResolver = () => string; + +export const defaultLoggerHostnameResolver: LoggerHostnameResolver = () => os.hostname(); + +export const loggerHostnameState: { + cached: string | null; + resolver: LoggerHostnameResolver; +} = { + cached: null, + resolver: defaultLoggerHostnameResolver, +}; diff --git a/src/logging/logger-redaction-behavior.test.ts b/src/logging/logger-redaction-behavior.test.ts index 001485c5280a..6c509edd564b 100644 --- a/src/logging/logger-redaction-behavior.test.ts +++ b/src/logging/logger-redaction-behavior.test.ts @@ -10,7 +10,7 @@ import { import { getChildLogger, getLogger, resetLogger, setLoggerOverride } from "../logging.js"; import { withEnv } from "../test-utils/env.js"; import { createSuiteLogPathTracker } from "./log-test-helpers.js"; -import { testApi as loggerTest } from "./logger.js"; +import { testApi as loggerTest } from "./logger.test-support.js"; import { createDiagnosticLogRecordCapture } from "./test-helpers/diagnostic-log-capture.js"; const secret = "sk-testsecret1234567890abcd"; diff --git a/src/logging/logger-timestamp.test.ts b/src/logging/logger-timestamp.test.ts index a2176e8b51e1..16ab100eab26 100644 --- a/src/logging/logger-timestamp.test.ts +++ b/src/logging/logger-timestamp.test.ts @@ -4,7 +4,7 @@ import { expectDefined } from "@openclaw/normalization-core"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { getLogger, resetLogger, setLoggerOverride } from "../logging.js"; import { createSuiteLogPathTracker } from "./log-test-helpers.js"; -import { testApi } from "./logger.js"; +import { testApi } from "./logger.test-support.js"; const logPathTracker = createSuiteLogPathTracker("openclaw-log-ts-"); diff --git a/src/logging/logger-transport.test.ts b/src/logging/logger-transport.test.ts index 9ed538c72593..f6ba889e904e 100644 --- a/src/logging/logger-transport.test.ts +++ b/src/logging/logger-transport.test.ts @@ -44,9 +44,7 @@ describe("logger transport registry", () => { expect( (loggerModule as unknown as Record).registerLogTransport, ).toBeUndefined(); - expect( - (loggerModule.testApi as unknown as Record).registerLogTransportForTest, - ).toBeUndefined(); + expect((loggerModule as unknown as Record).testApi).toBeUndefined(); }); it("does not publish mutable log transport state on a well-known global symbol", async () => { diff --git a/src/logging/logger.settings.test.ts b/src/logging/logger.settings.test.ts deleted file mode 100644 index ec7f6f415b84..000000000000 --- a/src/logging/logger.settings.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Logger settings tests cover normalization of logger configuration values. -import { describe, expect, it } from "vitest"; -import { testApi } from "./logger.js"; - -describe("shouldSkipMutatingLoggingConfigRead", () => { - it("matches config schema and validate invocations", () => { - expect( - testApi.shouldSkipMutatingLoggingConfigRead(["node", "openclaw", "config", "schema"]), - ).toBe(true); - expect( - testApi.shouldSkipMutatingLoggingConfigRead(["node", "openclaw", "config", "validate"]), - ).toBe(true); - }); - - it("handles root flags before config validate", () => { - expect( - testApi.shouldSkipMutatingLoggingConfigRead([ - "node", - "openclaw", - "--profile", - "work", - "--no-color", - "config", - "validate", - "--json", - ]), - ).toBe(true); - }); - - it("does not match other commands", () => { - expect( - testApi.shouldSkipMutatingLoggingConfigRead(["node", "openclaw", "config", "get", "foo"]), - ).toBe(false); - expect(testApi.shouldSkipMutatingLoggingConfigRead(["node", "openclaw", "status"])).toBe(false); - }); -}); diff --git a/src/logging/logger.test-support.ts b/src/logging/logger.test-support.ts new file mode 100644 index 000000000000..354b3e8b06f1 --- /dev/null +++ b/src/logging/logger.test-support.ts @@ -0,0 +1,22 @@ +import { expandHomePrefix } from "../infra/home-dir.js"; +import { isLegacyRollingLogFilePath, resolveRollingLogFilePathForDate } from "./log-file-path.js"; +import { fileLogTransport } from "./logger-file-transport.js"; +import { defaultLoggerHostnameResolver, loggerHostnameState } from "./logger-hostname-state.js"; + +export const testApi = { + drainFileLogQueueSyncForTests: fileLogTransport.drainSync, + flushFileLogQueueForTests: fileLogTransport.flush, + resetFileLogTransportForTests: fileLogTransport.resetForTests, + resolveActiveLogFile(file: string): string { + const expandedFile = expandHomePrefix(file); + return isLegacyRollingLogFilePath(expandedFile) + ? resolveRollingLogFilePathForDate(expandedFile, new Date()) + : expandedFile; + }, + setFileLogAppenderForTests: fileLogTransport.setAppenderForTests, + setFileLogQueueMaxRecordsForTests: fileLogTransport.setMaxQueuedRecordsForTests, + setHostnameResolverForTests(resolver?: () => string): void { + loggerHostnameState.resolver = resolver ?? defaultLoggerHostnameResolver; + loggerHostnameState.cached = null; + }, +}; diff --git a/src/logging/logger.ts b/src/logging/logger.ts index b17ed3aa5146..d28e182b7cc6 100644 --- a/src/logging/logger.ts +++ b/src/logging/logger.ts @@ -1,6 +1,5 @@ // Logger implementation writes structured log output with redaction and transports. import fs from "node:fs"; -import os from "node:os"; import path from "node:path"; import { expectDefined } from "@openclaw/normalization-core"; import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; @@ -23,20 +22,14 @@ import { DEFAULT_POSIX_TMP_ROOT, resolvePreferredOpenClawTmpDir, } from "../infra/tmp-openclaw-dir.js"; -import { readLoggingConfig, shouldSkipMutatingLoggingConfigRead } from "./config.js"; +import { readLoggingConfig } from "./config.js"; import { resolveEnvLogLevelOverride } from "./env-log-level.js"; import { type LogLevel, levelToMinLevel, normalizeLogLevel } from "./levels.js"; import { isLegacyRollingLogFilePath, resolveRollingLogFilePathForDate } from "./log-file-path.js"; import { resolveDefaultRollingLogFile } from "./log-file-path.js"; import { canUseNodeFs, formatLocalDate, LOG_PREFIX, LOG_SUFFIX } from "./log-file-shared.js"; -import { - drainFileLogQueueSync, - enqueueFileLog, - flushFileLogQueue, - resetFileLogTransportForTests, - setFileLogAppenderForTests, - setFileLogQueueMaxRecordsForTests, -} from "./logger-file-transport.js"; +import { fileLogTransport } from "./logger-file-transport.js"; +import { defaultLoggerHostnameResolver, loggerHostnameState } from "./logger-hostname-state.js"; import { setLoggerFileTargetResolver } from "./logger-settings-internal.js"; import { redactSecrets, redactSensitiveText } from "./redact.js"; import { loggingState } from "./state.js"; @@ -71,7 +64,6 @@ type ResolvedRuntimeSettings = ResolvedSettings & { rolling: boolean }; export type LoggerResolvedSettings = ResolvedSettings; type TsLogRecord = Record; type LoggerConfigLoader = () => OpenClawConfig["logging"] | undefined; -type HostnameResolver = () => string; type DiagnosticLogCode = { line?: number; @@ -95,9 +87,6 @@ const MAX_DIAGNOSTIC_LOG_NAME_CHARS = 120; const MAX_FILE_LOG_MESSAGE_CHARS = 4 * 1024; const MAX_FILE_LOG_CONTEXT_VALUE_CHARS = 512; const DIAGNOSTIC_LOG_ATTRIBUTE_KEY_RE = /^[A-Za-z0-9_.:-]{1,64}$/u; -const defaultHostnameResolver: HostnameResolver = () => os.hostname(); -let hostnameResolver: HostnameResolver = defaultHostnameResolver; -let cachedHostname: string | null = null; type DiagnosticLogAttributes = Record; @@ -298,14 +287,14 @@ function buildFileLogMessage(numericArgs: readonly unknown[]): string | undefine } function resolveLogHostname(): string { - if (cachedHostname) { - return cachedHostname; + if (loggerHostnameState.cached) { + return loggerHostnameState.cached; } - const hostname = hostnameResolver().trim(); + const hostname = loggerHostnameState.resolver().trim(); if (!hostname) { return "unknown"; } - cachedHostname = hostname; + loggerHostnameState.cached = hostname; return hostname; } @@ -636,7 +625,7 @@ function buildLogger(settings: ResolvedRuntimeSettings): TsLogger { ...traceFields, }; const line = redactSensitiveText(JSON.stringify(redactLogRecordForTransport(record))); - enqueueFileLog({ + fileLogTransport.enqueue({ file: activeFile, hostname: expectDefined(structuredFields.hostname, "structured log hostname"), maxFileBytes: settings.maxFileBytes, @@ -746,7 +735,7 @@ export function getResolvedLoggerSettings(): LoggerResolvedSettings { /** Flushes queued file logs before a graceful owner exits the process. */ export async function flushLogger(): Promise { - await flushFileLogQueue(); + await fileLogTransport.flush(); } // Test helpers @@ -763,27 +752,8 @@ export function resetLogger() { loggingState.cachedConsoleSettings = null; loggingState.overrideSettings = null; loadLoggerConfig = loadLoggerConfigDefault; - hostnameResolver = defaultHostnameResolver; - cachedHostname = null; -} - -export const testApi = { - drainFileLogQueueSyncForTests: drainFileLogQueueSync, - flushFileLogQueueForTests: flushFileLogQueue, - resetFileLogTransportForTests, - resolveActiveLogFile, - setFileLogAppenderForTests, - setFileLogQueueMaxRecordsForTests, - setHostnameResolverForTests: (resolver?: HostnameResolver) => { - hostnameResolver = resolver ?? defaultHostnameResolver; - cachedHostname = null; - }, - shouldSkipMutatingLoggingConfigRead, -}; -export { testApi as __test__ }; - -function resolveActiveLogFile(file: string): string { - return resolveActiveLogFileWithMode(file, isLegacyRollingLogFilePath(file)); + loggerHostnameState.resolver = defaultLoggerHostnameResolver; + loggerHostnameState.cached = null; } function resolveActiveLogFileWithMode(file: string, rolling: boolean): string { diff --git a/src/logging/redact.ts b/src/logging/redact.ts index a080a057bdf2..e87dd68b942d 100644 --- a/src/logging/redact.ts +++ b/src/logging/redact.ts @@ -25,8 +25,8 @@ import { } from "./redact-patterns.js"; import { redactRegisteredSecretValues } from "./secret-redaction-registry.js"; -export type RedactSensitiveMode = "off" | "tools"; -export type RedactPattern = string | RegExp; +type RedactSensitiveMode = "off" | "tools"; +type RedactPattern = string | RegExp; type LoggingConfig = OpenClawConfig["logging"]; const DEFAULT_REDACT_MODE: RedactSensitiveMode = "tools"; @@ -127,12 +127,12 @@ const DEFAULT_REDACT_PREFILTER_RE = new RegExp( "iu", ); -export type RedactOptions = { +type RedactOptions = { mode?: RedactSensitiveMode; patterns?: RedactPattern[]; }; -export type ResolvedRedactOptions = { +type ResolvedRedactOptions = { mode: RedactSensitiveMode; patterns: RegExp[]; redactFormBodies: boolean; diff --git a/src/logging/subsystem.test.ts b/src/logging/subsystem.test.ts index 6b6bf723021f..30823ce21acb 100644 --- a/src/logging/subsystem.test.ts +++ b/src/logging/subsystem.test.ts @@ -4,7 +4,8 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { setConsoleSubsystemFilter, shouldLogSubsystemToConsole } from "./console.js"; import { createSuiteLogPathTracker } from "./log-test-helpers.js"; -import { resetLogger, setLoggerOverride, testApi } from "./logger.js"; +import { resetLogger, setLoggerOverride } from "./logger.js"; +import { testApi } from "./logger.test-support.js"; import { loggingState } from "./state.js"; import { createSubsystemLogger } from "./subsystem.js"; diff --git a/src/plugin-sdk/agent-config-primitives.ts b/src/plugin-sdk/agent-config-primitives.ts deleted file mode 100644 index 931e8b4b5929..000000000000 --- a/src/plugin-sdk/agent-config-primitives.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Import the needed schema primitives from a maintained plugin-owned surface. - */ -export { ReplyRuntimeConfigSchemaShape } from "../config/zod-schema.core.js"; -export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js"; diff --git a/src/plugin-sdk/channel-logging.ts b/src/plugin-sdk/channel-logging.ts deleted file mode 100644 index 243721b7850d..000000000000 --- a/src/plugin-sdk/channel-logging.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** @deprecated Compatibility subpath. Use `channel-inbound` or `channel-outbound`. */ -export { - type LogFn, - logAckFailure, - logInboundDrop, - logTypingFailure, -} from "../channels/logging.js"; diff --git a/src/plugin-sdk/channel-policy.test.ts b/src/plugin-sdk/channel-policy.test.ts index 3ef32c468e24..d8e6b1d4b69c 100644 --- a/src/plugin-sdk/channel-policy.test.ts +++ b/src/plugin-sdk/channel-policy.test.ts @@ -10,9 +10,112 @@ import { coerceNativeSetting, createDangerousNameMatchingMutableAllowlistWarningCollector, createRestrictSendersChannelSecurity, + evaluateGroupRouteAccessForPolicy, + evaluateSenderGroupAccessForPolicy, normalizeAllowFromList, + resolveSenderScopedGroupPolicy, } from "./channel-policy.js"; +describe("retained group policy helpers", () => { + it.each([ + { + name: "preserves disabled policy", + input: { groupPolicy: "disabled" as const, groupAllowFrom: ["a"] }, + expected: "disabled", + }, + { + name: "keeps allowlist policy when sender allowlist is present", + input: { groupPolicy: "allowlist" as const, groupAllowFrom: ["a"] }, + expected: "allowlist", + }, + { + name: "maps allowlist to open when sender allowlist is empty", + input: { groupPolicy: "allowlist" as const, groupAllowFrom: [] }, + expected: "open", + }, + ])("$name", ({ input, expected }) => { + expect(resolveSenderScopedGroupPolicy(input)).toBe(expected); + }); + + it.each([ + { + name: "blocks disabled sender policy", + input: { + groupPolicy: "disabled" as const, + groupAllowFrom: ["123"], + senderId: "123", + isSenderAllowed: (): boolean => true, + }, + expected: { + allowed: false, + reason: "disabled", + groupPolicy: "disabled", + providerMissingFallbackApplied: false, + }, + }, + { + name: "blocks sender allowlist with an empty list", + input: { + groupPolicy: "allowlist" as const, + groupAllowFrom: [], + senderId: "123", + isSenderAllowed: (): boolean => true, + }, + expected: { + allowed: false, + reason: "empty_allowlist", + groupPolicy: "allowlist", + providerMissingFallbackApplied: false, + }, + }, + ])("$name", ({ input, expected }) => { + expect(evaluateSenderGroupAccessForPolicy(input)).toEqual(expected); + }); + + it.each([ + { + name: "blocks disabled route policy", + input: { + groupPolicy: "disabled" as const, + routeAllowlistConfigured: true, + routeMatched: true, + routeEnabled: true, + }, + reason: "disabled", + }, + { + name: "blocks an empty route allowlist", + input: { + groupPolicy: "allowlist" as const, + routeAllowlistConfigured: false, + routeMatched: false, + }, + reason: "empty_allowlist", + }, + { + name: "blocks an unmatched allowlisted route", + input: { + groupPolicy: "allowlist" as const, + routeAllowlistConfigured: true, + routeMatched: false, + }, + reason: "route_not_allowlisted", + }, + { + name: "blocks a disabled matched route", + input: { + groupPolicy: "open" as const, + routeAllowlistConfigured: true, + routeMatched: true, + routeEnabled: false, + }, + reason: "route_disabled", + }, + ])("$name", ({ input, reason }) => { + expect(evaluateGroupRouteAccessForPolicy(input)).toMatchObject({ allowed: false, reason }); + }); +}); + describe("mutable allowlist table helpers", () => { it("collects standard account, DM, and nested group lists in stable order", () => { expect( diff --git a/src/plugin-sdk/channel-policy.ts b/src/plugin-sdk/channel-policy.ts index 0b0fc3733f55..9bfb31beaab3 100644 --- a/src/plugin-sdk/channel-policy.ts +++ b/src/plugin-sdk/channel-policy.ts @@ -68,13 +68,99 @@ export { resolveEffectiveAllowFromLists, resolveOpenDmAllowlistAccess, } from "./channel-access-compat.js"; -export { - evaluateGroupRouteAccessForPolicy, - evaluateSenderGroupAccessForPolicy, - resolveSenderScopedGroupPolicy, -} from "./group-access.js"; export { createAllowlistProviderRestrictSendersWarningCollector }; +type GroupRouteAccessDecision = { + allowed: boolean; + groupPolicy: GroupPolicy; + reason: "allowed" | "disabled" | "empty_allowlist" | "route_not_allowlisted" | "route_disabled"; +}; + +type SenderGroupAccessDecision = { + allowed: boolean; + groupPolicy: GroupPolicy; + providerMissingFallbackApplied: boolean; + reason: "allowed" | "disabled" | "empty_allowlist" | "sender_not_allowlisted"; +}; + +/** @deprecated Use `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ +export function resolveSenderScopedGroupPolicy(params: { + groupPolicy: GroupPolicy; + groupAllowFrom: string[]; +}): GroupPolicy { + if (params.groupPolicy === "disabled") { + return "disabled"; + } + return params.groupAllowFrom.length > 0 ? "allowlist" : "open"; +} + +/** @deprecated Use route descriptors with `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ +export function evaluateGroupRouteAccessForPolicy(params: { + groupPolicy: GroupPolicy; + routeAllowlistConfigured: boolean; + routeMatched: boolean; + routeEnabled?: boolean; +}): GroupRouteAccessDecision { + if (params.groupPolicy === "disabled") { + return { allowed: false, groupPolicy: params.groupPolicy, reason: "disabled" }; + } + if (params.routeMatched && params.routeEnabled === false) { + return { allowed: false, groupPolicy: params.groupPolicy, reason: "route_disabled" }; + } + if (params.groupPolicy === "allowlist") { + if (!params.routeAllowlistConfigured) { + return { allowed: false, groupPolicy: params.groupPolicy, reason: "empty_allowlist" }; + } + if (!params.routeMatched) { + return { allowed: false, groupPolicy: params.groupPolicy, reason: "route_not_allowlisted" }; + } + } + return { allowed: true, groupPolicy: params.groupPolicy, reason: "allowed" }; +} + +/** @deprecated Use `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ +export function evaluateSenderGroupAccessForPolicy(params: { + groupPolicy: GroupPolicy; + providerMissingFallbackApplied?: boolean; + groupAllowFrom: string[]; + senderId: string; + isSenderAllowed: (senderId: string, allowFrom: string[]) => boolean; +}): SenderGroupAccessDecision { + const providerMissingFallbackApplied = Boolean(params.providerMissingFallbackApplied); + if (params.groupPolicy === "disabled") { + return { + allowed: false, + groupPolicy: params.groupPolicy, + providerMissingFallbackApplied, + reason: "disabled", + }; + } + if (params.groupPolicy === "allowlist") { + if (params.groupAllowFrom.length === 0) { + return { + allowed: false, + groupPolicy: params.groupPolicy, + providerMissingFallbackApplied, + reason: "empty_allowlist", + }; + } + if (!params.isSenderAllowed(params.senderId, params.groupAllowFrom)) { + return { + allowed: false, + groupPolicy: params.groupPolicy, + providerMissingFallbackApplied, + reason: "sender_not_allowlisted", + }; + } + } + return { + allowed: true, + groupPolicy: params.groupPolicy, + providerMissingFallbackApplied, + reason: "allowed", + }; +} + /** Normalizes allowFrom entries into trimmed unique string identifiers. */ export function normalizeAllowFromList(list: Array | undefined | null): string[] { if (!Array.isArray(list)) { diff --git a/src/plugin-sdk/channel-secret-runtime.ts b/src/plugin-sdk/channel-secret-runtime.ts deleted file mode 100644 index 850a62ef7379..000000000000 --- a/src/plugin-sdk/channel-secret-runtime.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Prefer focused channel secret subpaths such as channel-secret-basic-runtime - * and channel-secret-tts-runtime. - */ - -export { - collectConditionalChannelFieldAssignments, - collectNestedChannelFieldAssignments, - collectNestedChannelTtsAssignments, - collectSimpleChannelFieldAssignments, - getChannelRecord, - getChannelSurface, - hasConfiguredSecretInputValue, - isBaseFieldActiveForChannelSurface, - normalizeSecretStringValue, - resolveChannelAccountSurface, -} from "../secrets/channel-secret-collector-runtime.js"; -export type { - ChannelAccountEntry, - ChannelAccountPredicate, - ChannelAccountSurface, -} from "../secrets/channel-secret-collector-runtime.js"; -export { - collectSecretInputAssignment, - hasOwnProperty, - isEnabledFlag, - pushAssignment, - pushInactiveSurfaceWarning, - pushWarning, -} from "../secrets/runtime-shared.js"; -export type { ResolverContext, SecretDefaults } from "../secrets/runtime-shared.js"; -export { isRecord } from "../secrets/shared.js"; -export type { SecretTargetRegistryEntry } from "../secrets/target-registry-types.js"; diff --git a/src/plugin-sdk/channel-streaming.ts b/src/plugin-sdk/channel-streaming.ts deleted file mode 100644 index cbcc88050777..000000000000 --- a/src/plugin-sdk/channel-streaming.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-outbound`. */ -export * from "../channels/streaming.js"; -/** @deprecated Shipped through this facade; remove with the subpath after 2026-08-15. */ -export type { SlackChannelStreamingConfig } from "../config/types.slack.js"; diff --git a/src/plugin-sdk/group-access.test.ts b/src/plugin-sdk/group-access.test.ts deleted file mode 100644 index d4024c4ec615..000000000000 --- a/src/plugin-sdk/group-access.test.ts +++ /dev/null @@ -1,319 +0,0 @@ -/** - * Tests group access policy helpers and SDK-visible access decisions. - */ -import { describe, expect, it } from "vitest"; -import { - evaluateGroupRouteAccessForPolicy, - evaluateMatchedGroupAccessForPolicy, - evaluateSenderGroupAccess, - evaluateSenderGroupAccessForPolicy, - resolveSenderScopedGroupPolicy, -} from "./group-access.js"; - -describe("resolveSenderScopedGroupPolicy", () => { - const cases: Array<{ - name: string; - input: Parameters[0]; - expected: ReturnType; - }> = [ - { - name: "preserves disabled policy", - input: { - groupPolicy: "disabled", - groupAllowFrom: ["a"], - }, - expected: "disabled", - }, - { - name: "keeps allowlist policy when sender allowlist is present", - input: { - groupPolicy: "allowlist", - groupAllowFrom: ["a"], - }, - expected: "allowlist", - }, - { - name: "maps allowlist to open when sender allowlist is empty", - input: { - groupPolicy: "allowlist", - groupAllowFrom: [], - }, - expected: "open", - }, - ]; - - it.each(cases)("$name", ({ input, expected }) => { - expect(resolveSenderScopedGroupPolicy(input)).toBe(expected); - }); -}); - -describe("evaluateSenderGroupAccessForPolicy", () => { - const cases: Array<{ - name: string; - input: Parameters[0]; - expected: ReturnType; - }> = [ - { - name: "blocks disabled policy", - input: { - groupPolicy: "disabled", - groupAllowFrom: ["123"], - senderId: "123", - isSenderAllowed: () => true, - }, - expected: { - allowed: false, - reason: "disabled", - groupPolicy: "disabled", - providerMissingFallbackApplied: false, - }, - }, - { - name: "blocks allowlist with empty list", - input: { - groupPolicy: "allowlist", - groupAllowFrom: [], - senderId: "123", - isSenderAllowed: () => true, - }, - expected: { - allowed: false, - reason: "empty_allowlist", - groupPolicy: "allowlist", - providerMissingFallbackApplied: false, - }, - }, - ]; - - it.each(cases)("$name", ({ input, expected }) => { - expect(evaluateSenderGroupAccessForPolicy(input)).toEqual(expected); - }); -}); - -describe("evaluateGroupRouteAccessForPolicy", () => { - const cases: Array<{ - name: string; - input: Parameters[0]; - expected: ReturnType; - }> = [ - { - name: "blocks disabled policy", - input: { - groupPolicy: "disabled", - routeAllowlistConfigured: true, - routeMatched: true, - routeEnabled: true, - }, - expected: { - allowed: false, - groupPolicy: "disabled", - reason: "disabled", - }, - }, - { - name: "blocks allowlist without configured routes", - input: { - groupPolicy: "allowlist", - routeAllowlistConfigured: false, - routeMatched: false, - }, - expected: { - allowed: false, - groupPolicy: "allowlist", - reason: "empty_allowlist", - }, - }, - { - name: "blocks unmatched allowlist route", - input: { - groupPolicy: "allowlist", - routeAllowlistConfigured: true, - routeMatched: false, - }, - expected: { - allowed: false, - groupPolicy: "allowlist", - reason: "route_not_allowlisted", - }, - }, - { - name: "blocks disabled matched route even when group policy is open", - input: { - groupPolicy: "open", - routeAllowlistConfigured: true, - routeMatched: true, - routeEnabled: false, - }, - expected: { - allowed: false, - groupPolicy: "open", - reason: "route_disabled", - }, - }, - ]; - - it.each(cases)("$name", ({ input, expected }) => { - expect(evaluateGroupRouteAccessForPolicy(input)).toEqual(expected); - }); -}); - -describe("evaluateMatchedGroupAccessForPolicy", () => { - const cases: Array<{ - name: string; - input: Parameters[0]; - expected: ReturnType; - }> = [ - { - name: "blocks disabled policy", - input: { - groupPolicy: "disabled", - allowlistConfigured: true, - allowlistMatched: true, - }, - expected: { - allowed: false, - groupPolicy: "disabled", - reason: "disabled", - }, - }, - { - name: "blocks allowlist without configured entries", - input: { - groupPolicy: "allowlist", - allowlistConfigured: false, - allowlistMatched: false, - }, - expected: { - allowed: false, - groupPolicy: "allowlist", - reason: "empty_allowlist", - }, - }, - { - name: "blocks allowlist when required match input is missing", - input: { - groupPolicy: "allowlist", - requireMatchInput: true, - hasMatchInput: false, - allowlistConfigured: true, - allowlistMatched: false, - }, - expected: { - allowed: false, - groupPolicy: "allowlist", - reason: "missing_match_input", - }, - }, - { - name: "blocks unmatched allowlist sender", - input: { - groupPolicy: "allowlist", - allowlistConfigured: true, - allowlistMatched: false, - }, - expected: { - allowed: false, - groupPolicy: "allowlist", - reason: "not_allowlisted", - }, - }, - { - name: "allows open policy", - input: { - groupPolicy: "open", - allowlistConfigured: false, - allowlistMatched: false, - }, - expected: { - allowed: true, - groupPolicy: "open", - reason: "allowed", - }, - }, - ]; - - it.each(cases)("$name", ({ input, expected }) => { - expect(evaluateMatchedGroupAccessForPolicy(input)).toEqual(expected); - }); -}); - -describe("evaluateSenderGroupAccess", () => { - const cases: Array<{ - name: string; - input: Parameters[0]; - expected: ReturnType; - }> = [ - { - name: "defaults missing provider config to allowlist", - input: { - providerConfigPresent: false, - configuredGroupPolicy: undefined, - defaultGroupPolicy: "open", - groupAllowFrom: ["123"], - senderId: "123", - isSenderAllowed: () => true, - }, - expected: { - allowed: true, - groupPolicy: "allowlist", - providerMissingFallbackApplied: true, - reason: "allowed", - }, - }, - { - name: "blocks disabled policy", - input: { - providerConfigPresent: true, - configuredGroupPolicy: "disabled", - defaultGroupPolicy: "open", - groupAllowFrom: ["123"], - senderId: "123", - isSenderAllowed: () => true, - }, - expected: { - allowed: false, - reason: "disabled", - groupPolicy: "disabled", - providerMissingFallbackApplied: false, - }, - }, - { - name: "blocks allowlist with empty list", - input: { - providerConfigPresent: true, - configuredGroupPolicy: "allowlist", - defaultGroupPolicy: "open", - groupAllowFrom: [], - senderId: "123", - isSenderAllowed: () => true, - }, - expected: { - allowed: false, - reason: "empty_allowlist", - groupPolicy: "allowlist", - providerMissingFallbackApplied: false, - }, - }, - { - name: "blocks sender not allowlisted", - input: { - providerConfigPresent: true, - configuredGroupPolicy: "allowlist", - defaultGroupPolicy: "open", - groupAllowFrom: ["123"], - senderId: "999", - isSenderAllowed: () => false, - }, - expected: { - allowed: false, - reason: "sender_not_allowlisted", - groupPolicy: "allowlist", - providerMissingFallbackApplied: false, - }, - }, - ]; - - it.each(cases)("$name", ({ input, expected }) => { - expect(evaluateSenderGroupAccess(input)).toEqual(expected); - }); -}); diff --git a/src/plugin-sdk/group-access.ts b/src/plugin-sdk/group-access.ts deleted file mode 100644 index 91d440e79b2d..000000000000 --- a/src/plugin-sdk/group-access.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Use resolveChannelMessageIngress from channel-ingress-runtime instead. - */ - -import { resolveOpenProviderRuntimeGroupPolicy } from "../config/runtime-group-policy.js"; -import type { GroupPolicy } from "../config/types.base.js"; - -export { resolveOpenProviderRuntimeGroupPolicy }; -export type { GroupPolicy }; - -/** Reason code returned when evaluating a sender against group policy. */ -export type SenderGroupAccessReason = - | "allowed" - | "disabled" - | "empty_allowlist" - | "sender_not_allowlisted"; -/** Sender-level group access decision plus the effective group policy. */ -export type SenderGroupAccessDecision = { - allowed: boolean; - groupPolicy: GroupPolicy; - providerMissingFallbackApplied: boolean; - reason: SenderGroupAccessReason; -}; -/** Reason code returned when evaluating a configured group route. */ -export type GroupRouteAccessReason = - | "allowed" - | "disabled" - | "empty_allowlist" - | "route_not_allowlisted" - | "route_disabled"; -/** Route-level group access decision plus the effective group policy. */ -export type GroupRouteAccessDecision = { - allowed: boolean; - groupPolicy: GroupPolicy; - reason: GroupRouteAccessReason; -}; -/** Reason code returned when evaluating a precomputed allowlist match. */ -export type MatchedGroupAccessReason = - | "allowed" - | "disabled" - | "missing_match_input" - | "empty_allowlist" - | "not_allowlisted"; -/** Matched-input group access decision plus the effective group policy. */ -export type MatchedGroupAccessDecision = { - allowed: boolean; - groupPolicy: GroupPolicy; - reason: MatchedGroupAccessReason; -}; - -/** @deprecated Use `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ -export function resolveSenderScopedGroupPolicy(params: { - groupPolicy: GroupPolicy; - groupAllowFrom: string[]; -}): GroupPolicy { - if (params.groupPolicy === "disabled") { - return "disabled"; - } - return params.groupAllowFrom.length > 0 ? "allowlist" : "open"; -} - -/** @deprecated Use route descriptors with `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ -export function evaluateGroupRouteAccessForPolicy(params: { - groupPolicy: GroupPolicy; - routeAllowlistConfigured: boolean; - routeMatched: boolean; - routeEnabled?: boolean; -}): GroupRouteAccessDecision { - if (params.groupPolicy === "disabled") { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "disabled" }; - } - if (params.routeMatched && params.routeEnabled === false) { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "route_disabled" }; - } - if (params.groupPolicy === "allowlist") { - if (!params.routeAllowlistConfigured) { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "empty_allowlist" }; - } - if (!params.routeMatched) { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "route_not_allowlisted" }; - } - } - return { allowed: true, groupPolicy: params.groupPolicy, reason: "allowed" }; -} - -/** @deprecated Use `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ -export function evaluateMatchedGroupAccessForPolicy(params: { - groupPolicy: GroupPolicy; - allowlistConfigured: boolean; - allowlistMatched: boolean; - requireMatchInput?: boolean; - hasMatchInput?: boolean; -}): MatchedGroupAccessDecision { - if (params.groupPolicy === "disabled") { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "disabled" }; - } - if (params.groupPolicy === "allowlist") { - if (params.requireMatchInput && !params.hasMatchInput) { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "missing_match_input" }; - } - if (!params.allowlistConfigured) { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "empty_allowlist" }; - } - if (!params.allowlistMatched) { - return { allowed: false, groupPolicy: params.groupPolicy, reason: "not_allowlisted" }; - } - } - return { allowed: true, groupPolicy: params.groupPolicy, reason: "allowed" }; -} - -/** @deprecated Use `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ -export function evaluateSenderGroupAccessForPolicy(params: { - groupPolicy: GroupPolicy; - providerMissingFallbackApplied?: boolean; - groupAllowFrom: string[]; - senderId: string; - isSenderAllowed: (senderId: string, allowFrom: string[]) => boolean; -}): SenderGroupAccessDecision { - const providerMissingFallbackApplied = Boolean(params.providerMissingFallbackApplied); - if (params.groupPolicy === "disabled") { - return { - allowed: false, - groupPolicy: params.groupPolicy, - providerMissingFallbackApplied, - reason: "disabled", - }; - } - if (params.groupPolicy === "allowlist") { - if (params.groupAllowFrom.length === 0) { - return { - allowed: false, - groupPolicy: params.groupPolicy, - providerMissingFallbackApplied, - reason: "empty_allowlist", - }; - } - if (!params.isSenderAllowed(params.senderId, params.groupAllowFrom)) { - return { - allowed: false, - groupPolicy: params.groupPolicy, - providerMissingFallbackApplied, - reason: "sender_not_allowlisted", - }; - } - } - return { - allowed: true, - groupPolicy: params.groupPolicy, - providerMissingFallbackApplied, - reason: "allowed", - }; -} - -/** @deprecated Use `resolveOpenProviderRuntimeGroupPolicy` plus `resolveChannelMessageIngress` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ -export function evaluateSenderGroupAccess(params: { - providerConfigPresent: boolean; - configuredGroupPolicy?: GroupPolicy; - defaultGroupPolicy?: GroupPolicy; - groupAllowFrom: string[]; - senderId: string; - isSenderAllowed: (senderId: string, allowFrom: string[]) => boolean; -}): SenderGroupAccessDecision { - const { groupPolicy, providerMissingFallbackApplied } = resolveOpenProviderRuntimeGroupPolicy({ - providerConfigPresent: params.providerConfigPresent, - groupPolicy: params.configuredGroupPolicy, - defaultGroupPolicy: params.defaultGroupPolicy, - }); - - return evaluateSenderGroupAccessForPolicy({ - groupPolicy, - providerMissingFallbackApplied, - groupAllowFrom: params.groupAllowFrom, - senderId: params.senderId, - isSenderAllowed: params.isSenderAllowed, - }); -} diff --git a/src/plugin-sdk/matrix.ts b/src/plugin-sdk/matrix.ts deleted file mode 100644 index fdbfac52aaa6..000000000000 --- a/src/plugin-sdk/matrix.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @deprecated Compatibility facade for older third-party channel packages that - * imported the previous Matrix-shaped helper bundle. New plugins should import - * `openclaw/plugin-sdk/run-command` directly. - */ -export { runPluginCommandWithTimeout } from "./run-command.js"; diff --git a/src/plugin-sdk/text-runtime.ts b/src/plugin-sdk/text-runtime.ts deleted file mode 100644 index e4f4145c83d2..000000000000 --- a/src/plugin-sdk/text-runtime.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @deprecated Broad public SDK barrel. Prefer focused text/chunking/logging - * subpaths and avoid adding new imports here. - */ - -export * from "../logger.js"; -export * from "../logging/diagnostic.js"; -export * from "../logging/logger.js"; -export * from "../logging/redact.js"; -export * from "../logging/redact-identifier.js"; -export * from "../../packages/markdown-core/src/ir.js"; -export * from "../../packages/markdown-core/src/render-aware-chunking.js"; -export * from "../../packages/markdown-core/src/render.js"; -export * from "../../packages/markdown-core/src/tables.js"; -export { resolveGlobalMap, resolveGlobalSingleton } from "../shared/global-singleton.js"; -// Public compatibility: this explicit list intentionally keeps isRecord on text-runtime. -export { - asNullableObjectRecord, - asNullableRecord, - asOptionalObjectRecord, - asOptionalRecord, - asRecord, - isRecord, - readStringField, -} from "../../packages/normalization-core/src/record-coerce.js"; -export * from "../shared/scoped-expiring-id-cache.js"; -export { - hasNonEmptyString, - localeLowercasePreservingWhitespace, - lowercasePreservingWhitespace, - normalizeFastMode, - normalizeLowercaseStringOrEmpty, - normalizeNullableString, - normalizeOptionalLowercaseString, - normalizeOptionalString, - normalizeOptionalStringifiedId, - normalizeOptionalThreadValue, - normalizeStringifiedEntries, - normalizeStringifiedOptionalString, - readStringValue, - resolvePrimaryStringValue, - type FastMode, -} from "../../packages/normalization-core/src/string-coerce.js"; -// Public compatibility: keep this legacy barrel's existing normalization surface -// without promoting new normalization-core helpers into the Plugin SDK. -export { - normalizeArrayBackedTrimmedStringList, - normalizeAtHashSlug, - normalizeCsvOrLooseStringList, - normalizeHyphenSlug, - normalizeOptionalTrimmedStringList, - normalizeSingleOrTrimmedStringList, - normalizeSortedUniqueStringEntries, - normalizeSortedUniqueTrimmedStringList, - normalizeStringEntries, - normalizeStringEntriesLower, - normalizeTrimmedStringList, - normalizeUniqueSingleOrTrimmedStringList, - normalizeUniqueStringEntries, - normalizeUniqueStringEntriesLower, - normalizeUniqueTrimmedStringList, - sortUniqueStrings, - uniqueStrings, - uniqueValues, -} from "../../packages/normalization-core/src/string-normalization.js"; -export * from "../shared/string-sample.js"; -export * from "../shared/text/assistant-visible-text.js"; -export * from "../shared/text/auto-linked-file-ref.js"; -export * from "../shared/text/code-regions.js"; -export * from "../shared/text/reasoning-tags.js"; -export * from "../shared/text/strip-markdown.js"; -export * from "../../packages/terminal-core/src/safe-text.js"; -export * from "../infra/system-message.ts"; -export * from "../utils/directive-tags.js"; -export * from "../utils/chunk-items.js"; -export * from "../utils/fetch-timeout.js"; -export * from "../utils/reaction-level.js"; -export * from "../utils/with-timeout.js"; -export { - CONFIG_DIR, - clamp, - clampInt, - clampNumber, - displayPath, - displayString, - ensureDir, - escapeRegExp, - normalizeE164, - pathExists, - resolveConfigDir, - resolveHomeDir, - resolveUserPath, - tryParseJson as safeParseJson, - shortenHomeInString, - shortenHomePath, - sleep, - sliceUtf16Safe, - truncateUtf16Safe, -} from "../utils.js"; diff --git a/src/plugin-sdk/zod.ts b/src/plugin-sdk/zod.ts deleted file mode 100644 index f44a598ccf43..000000000000 --- a/src/plugin-sdk/zod.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Public SDK subpath that exposes the supported Zod dependency for plugin schemas. - */ -export * from "zod"; diff --git a/src/plugins/compat/plugin-sdk-subpath-records.ts b/src/plugins/compat/plugin-sdk-subpath-records.ts index bb27e27212f3..f13b49650da6 100644 --- a/src/plugins/compat/plugin-sdk-subpath-records.ts +++ b/src/plugins/compat/plugin-sdk-subpath-records.ts @@ -1,16 +1,19 @@ import type { PluginCompatRecord } from "./types.js"; type SeedFields = "code" | "owner" | "removeAfter" | "removalGate" | "replacement"; -type DeprecatedPluginSdkSubpathSeed = Pick & - Record<"subpath", string>; +type PluginSdkSubpathSeed = Pick & + Record<"subpath", string> & + Partial>; -const DEPRECATED_PLUGIN_SDK_SUBPATH_SEEDS = [ +const PLUGIN_SDK_SUBPATH_SEEDS = [ { code: "plugin-sdk-channel-streaming-subpath", subpath: "channel-streaming", + status: "removed", owner: "channel", - removeAfter: "2026-08-15", replacement: "`openclaw/plugin-sdk/channel-outbound`", + releaseNote: + "The deprecated `channel-streaming` Plugin SDK subpath was removed; plugins now import channel streaming helpers from `channel-outbound`.", }, { code: "plugin-sdk-config-runtime-subpath", @@ -45,39 +48,49 @@ const DEPRECATED_PLUGIN_SDK_SUBPATH_SEEDS = [ { code: "plugin-sdk-text-runtime-subpath", subpath: "text-runtime", + status: "removed", owner: "sdk", - removeAfter: "2026-08-15", replacement: "`openclaw/plugin-sdk/logging-core`, `openclaw/plugin-sdk/text-chunking`, `openclaw/plugin-sdk/text-utility-runtime`, and `openclaw/plugin-sdk/string-coerce-runtime`", + releaseNote: + "The deprecated `text-runtime` Plugin SDK facade was removed; plugins now import logging, chunking, text utility, and string coercion helpers from their focused subpaths.", }, { code: "plugin-sdk-channel-secret-runtime-subpath", subpath: "channel-secret-runtime", + status: "removed", owner: "channel", - removeAfter: "2026-08-15", replacement: "`openclaw/plugin-sdk/channel-secret-basic-runtime` and `openclaw/plugin-sdk/channel-secret-tts-runtime`", + releaseNote: + "The deprecated `channel-secret-runtime` Plugin SDK subpath was removed; plugins now use the focused basic and TTS secret-runtime subpaths.", }, { code: "plugin-sdk-agent-config-primitives-subpath", subpath: "agent-config-primitives", + status: "removed", owner: "config", - removeAfter: "2026-08-15", replacement: "`openclaw/plugin-sdk/channel-config-schema`", + releaseNote: + "The deprecated `agent-config-primitives` Plugin SDK subpath was removed; plugins now use maintained config-schema primitives.", }, { code: "plugin-sdk-matrix-subpath", subpath: "matrix", + status: "removed", owner: "channel", - removeAfter: "2026-08-15", replacement: "`openclaw/plugin-sdk/run-command`", + releaseNote: + "The deprecated `matrix` Plugin SDK facade was removed; command execution now uses the generic `run-command` subpath.", }, { code: "plugin-sdk-channel-logging-subpath", subpath: "channel-logging", + status: "removed", owner: "channel", - removeAfter: "2026-08-15", replacement: "`openclaw/plugin-sdk/channel-inbound` and `openclaw/plugin-sdk/channel-outbound`", + releaseNote: + "The deprecated `channel-logging` Plugin SDK subpath was removed; channel logging helpers now come from the inbound and outbound channel surfaces.", }, { code: "plugin-sdk-channel-lifecycle-subpath", @@ -96,38 +109,60 @@ const DEPRECATED_PLUGIN_SDK_SUBPATH_SEEDS = [ { code: "plugin-sdk-group-access-subpath", subpath: "group-access", + status: "removed", owner: "channel", - removeAfter: "2026-08-15", replacement: "`openclaw/plugin-sdk/channel-ingress-runtime`", + releaseNote: + "The deprecated `group-access` Plugin SDK subpath was removed; plugins now resolve message admission through `channel-ingress-runtime`.", }, { code: "plugin-sdk-zod-subpath", subpath: "zod", + status: "removed", owner: "sdk", - removeAfter: "2026-08-15", replacement: "the direct `zod` package import", + releaseNote: + "The deprecated `zod` Plugin SDK re-export was removed; plugins now import `zod` directly.", }, -] as const satisfies readonly DeprecatedPluginSdkSubpathSeed[]; +] as const satisfies readonly PluginSdkSubpathSeed[]; -export const DEPRECATED_PLUGIN_SDK_SUBPATH_RECORDS = DEPRECATED_PLUGIN_SDK_SUBPATH_SEEDS.map( - (seed) => - ({ +function buildPluginSdkSubpathRecord(seed: (typeof PLUGIN_SDK_SUBPATH_SEEDS)[number]) { + if ("status" in seed) { + return { code: seed.code, - status: "deprecated" as const, + status: seed.status, owner: seed.owner, introduced: "2026-07-06", - deprecated: "2026-07-06", - warningStarts: "2026-07-06", - removeAfter: "removeAfter" in seed ? seed.removeAfter : undefined, - removalGate: "removalGate" in seed ? seed.removalGate : undefined, replacement: seed.replacement, docsPath: "/plugins/sdk-migration", surfaces: [`openclaw/plugin-sdk/${seed.subpath}`], - diagnostics: [ - "repository deprecated API usage guard for core and bundled plugins; no external runtime import warning", - ], + diagnostics: ["plugin SDK compatibility registry and migration guide"], tests: ["src/plugins/compat/registry.test.ts"], - }) satisfies PluginCompatRecord, + releaseNote: seed.releaseNote, + } satisfies PluginCompatRecord; + } + + return { + code: seed.code, + status: "deprecated", + owner: seed.owner, + introduced: "2026-07-06", + deprecated: "2026-07-06", + warningStarts: "2026-07-06", + removeAfter: "removeAfter" in seed ? seed.removeAfter : undefined, + removalGate: "removalGate" in seed ? seed.removalGate : undefined, + replacement: seed.replacement, + docsPath: "/plugins/sdk-migration", + surfaces: [`openclaw/plugin-sdk/${seed.subpath}`], + diagnostics: [ + "repository deprecated API usage guard for core and bundled plugins; no external runtime import warning", + ], + tests: ["src/plugins/compat/registry.test.ts"], + } satisfies PluginCompatRecord; +} + +export const PLUGIN_SDK_SUBPATH_RECORDS = PLUGIN_SDK_SUBPATH_SEEDS.map( + buildPluginSdkSubpathRecord, ) satisfies readonly PluginCompatRecord[]; const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS = [ diff --git a/src/plugins/compat/registry-records.ts b/src/plugins/compat/registry-records.ts index 333555f81311..ee6fc4b1770a 100644 --- a/src/plugins/compat/registry-records.ts +++ b/src/plugins/compat/registry-records.ts @@ -2,12 +2,12 @@ import { DEPRECATION_MARKING_COMPAT_RECORDS } from "./deprecation-marking.js"; import { MEDIA_LEGACY_PROJECTION_COMPAT_RECORD } from "./media-legacy-projection.js"; import { BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS, - DEPRECATED_PLUGIN_SDK_SUBPATH_RECORDS, + PLUGIN_SDK_SUBPATH_RECORDS, } from "./plugin-sdk-subpath-records.js"; import type { PluginCompatRecord } from "./types.js"; export const PLUGIN_COMPAT_RECORDS = [ - ...DEPRECATED_PLUGIN_SDK_SUBPATH_RECORDS, + ...PLUGIN_SDK_SUBPATH_RECORDS, ...BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS, ...DEPRECATION_MARKING_COMPAT_RECORDS, MEDIA_LEGACY_PROJECTION_COMPAT_RECORD, @@ -44,19 +44,16 @@ export const PLUGIN_COMPAT_RECORDS = [ }, { code: "legacy-deactivate-hook-alias", - status: "deprecated", + status: "removed", 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", + docsPath: "/plugins/sdk-migration#deactivate-hook-alias", surfaces: ['api.on("deactivate", ...)', "plugin typed hook registration"], - diagnostics: ["plugin runtime compatibility warning"], - tests: ["src/plugins/loader.test.ts"], + diagnostics: ["plugin compatibility registry and migration guide"], + tests: ["src/plugins/compat/registry.test.ts"], releaseNote: - '`api.on("deactivate", ...)` remains wired as a deprecated compatibility alias while plugins migrate to `gateway_stop`.', + 'The deprecated `api.on("deactivate", ...)` hook alias was removed; plugins must register cleanup with `gateway_stop`.', }, { code: "legacy-subagent-spawning-hook", diff --git a/src/plugins/compat/registry.test.ts b/src/plugins/compat/registry.test.ts index cc8ac652cc36..1bc4fc7789b6 100644 --- a/src/plugins/compat/registry.test.ts +++ b/src/plugins/compat/registry.test.ts @@ -25,6 +25,16 @@ const removalDatePendingCompatCodes = new Set([ "plugin-sdk-tool-plugin-public-demotion", "agent-harness-sdk-alias", ]); +const retiredPluginSdkSubpathCodes = [ + "plugin-sdk-channel-streaming-subpath", + "plugin-sdk-text-runtime-subpath", + "plugin-sdk-channel-secret-runtime-subpath", + "plugin-sdk-agent-config-primitives-subpath", + "plugin-sdk-matrix-subpath", + "plugin-sdk-channel-logging-subpath", + "plugin-sdk-group-access-subpath", + "plugin-sdk-zod-subpath", +] as const satisfies readonly PluginCompatCode[]; const deprecationMarkingCodes = [ "plugin-sdk-channel-setup-input-fields", "plugin-sdk-broad-runtime-barrels", @@ -137,6 +147,18 @@ describe("plugin compatibility registry", () => { ]); }); + it("keeps retired Plugin SDK subpaths as migration tombstones", () => { + const records = new Map(listPluginCompatRecords().map((record) => [record.code, record])); + + for (const code of retiredPluginSdkSubpathCodes) { + expect(records.get(code)).toMatchObject({ + status: "removed", + releaseNote: expect.stringMatching(/\S/u), + }); + expect(records.get(code)?.removeAfter, code).toBeUndefined(); + } + }); + it("tracks the deprecation-marking families through the approved window", () => { const records = new Map(listPluginCompatRecords().map((record) => [record.code, record])); @@ -189,6 +211,18 @@ describe("plugin compatibility registry", () => { expect(record?.removeAfter).toBeUndefined(); }); + it("keeps the removed deactivate hook alias as a migration tombstone", () => { + const record = listPluginCompatRecords().find( + (candidate) => candidate.code === "legacy-deactivate-hook-alias", + ); + + expect(record).toMatchObject({ + status: "removed", + replacement: "`gateway_stop` hook", + }); + expect(record?.removeAfter).toBeUndefined(); + }); + it("keeps deprecated explicit target parser calls inside compatibility shims", () => { expect(deprecatedTargetParserOffenders).toEqual([]); }); diff --git a/src/plugins/contracts/extension-package-project-boundaries.test.ts b/src/plugins/contracts/extension-package-project-boundaries.test.ts index 9da6e78700a2..5f30eae52db4 100644 --- a/src/plugins/contracts/extension-package-project-boundaries.test.ts +++ b/src/plugins/contracts/extension-package-project-boundaries.test.ts @@ -227,12 +227,6 @@ describe("opt-in extension package boundaries", () => { expect(packageJson.exports?.["./acp-runtime"]?.types).toBe( "./dist/src/plugin-sdk/acp-runtime.d.ts", ); - expect(packageJson.exports?.["./channel-secret-runtime"]?.types).toBe( - "./dist/src/plugin-sdk/channel-secret-runtime.d.ts", - ); - expect(packageJson.exports?.["./channel-streaming"]?.types).toBe( - "./dist/src/plugin-sdk/channel-streaming.d.ts", - ); expect(packageJson.exports?.["./cli-runtime"]?.types).toBe( "./dist/src/plugin-sdk/cli-runtime.d.ts", ); @@ -291,10 +285,6 @@ describe("opt-in extension package boundaries", () => { expect(packageJson.exports?.["./infra-runtime"]?.types).toBe( "./dist/src/plugin-sdk/infra-runtime.d.ts", ); - expect(packageJson.exports?.["./text-runtime"]?.types).toBe( - "./dist/src/plugin-sdk/text-runtime.d.ts", - ); - expect(packageJson.exports?.["./zod"]?.types).toBe("./dist/src/plugin-sdk/zod.d.ts"); expect(fs.existsSync(resolve(REPO_ROOT, "packages/plugin-sdk/types/plugin-entry.d.ts"))).toBe( false, ); diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index 5ac5ecf9fe4b..d98ff40645fa 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -667,7 +667,6 @@ describe("plugin-sdk subpath exports", () => { "createChannelHistoryWindow", "recordPendingHistoryEntryIfEnabled", ]); - expectSourceMentions("matrix", ["runPluginCommandWithTimeout"]); expectSourceContract("reply-runtime", { omits: [ "buildPendingHistoryContextFromMap", @@ -694,25 +693,6 @@ describe("plugin-sdk subpath exports", () => { ], omits: ["collectNestedChannelTtsAssignments"], }); - expectSourceContract("channel-secret-runtime", { - mentions: [ - "collectSimpleChannelFieldAssignments", - "collectConditionalChannelFieldAssignments", - "collectSecretInputAssignment", - "getChannelSurface", - "pushAssignment", - "pushInactiveSurfaceWarning", - "ResolverContext", - "SecretTargetRegistryEntry", - ], - omits: [ - "buildChannelMetadata", - "buildUntrustedChannelMetadata", - "evaluateSupplementalContextVisibility", - "resolvePinnedMainDmOwnerFromAllowlist", - "safeMatchRegex", - ], - }); expectSourceContract("channel-secret-tts-runtime", { mentions: ["collectNestedChannelTtsAssignments"], omits: ["collectSimpleChannelFieldAssignments", "collectConditionalChannelFieldAssignments"], @@ -1160,8 +1140,6 @@ describe("plugin-sdk subpath exports", () => { ]); expectRepoSourceOmitsSnippet("src/channels/ack-reactions.ts", "shouldAckReactionForWhatsApp"); expectRepoSourceOmitsSnippet("src/channels/ack-reactions.ts", "WhatsAppAckReactionMode"); - expectSourceMentions("channel-streaming", ["SlackChannelStreamingConfig"]); - expectRepoSourceOmitsSnippet("src/channels/streaming.ts", "SlackChannelStreamingConfig"); expectSourceMentions("status-helpers", [ "appendMatchMetadata", "asString", diff --git a/src/plugins/hook-types.ts b/src/plugins/hook-types.ts index cda36ad9c8b0..ea7d8b8ff24e 100644 --- a/src/plugins/hook-types.ts +++ b/src/plugins/hook-types.ts @@ -129,8 +129,6 @@ export type PluginHookName = | "subagent_spawned" | "subagent_progress" | "subagent_ended" - /** @deprecated Use gateway_stop. */ - | "deactivate" | "gateway_start" | "gateway_stop" | "heartbeat_prompt_contribution" @@ -176,7 +174,6 @@ const PLUGIN_HOOK_NAMES = [ "subagent_spawned", "subagent_progress", "subagent_ended", - "deactivate", "gateway_start", "gateway_stop", "heartbeat_prompt_contribution", @@ -197,7 +194,7 @@ type AssertAllPluginHookNamesListed = MissingPluginHookNames extends never ? tru const assertAllPluginHookNamesListed: AssertAllPluginHookNamesListed = true; void assertAllPluginHookNamesListed; -type DeprecatedPluginHookName = "subagent_spawning" | "deactivate"; +type DeprecatedPluginHookName = "subagent_spawning"; type PluginHookDeprecation = { replacement: string; @@ -231,11 +228,6 @@ export const DEPRECATED_PLUGIN_HOOKS = { "Core prepares thread-bound subagent bindings through channel session-binding adapters before `subagent_spawned` fires.", removeAfter: "2026-08-30", }, - deactivate: { - replacement: "`gateway_stop`", - reason: "`deactivate` is a legacy cleanup hook alias for `gateway_stop`.", - removeAfter: "2026-08-16", - }, } as const satisfies Record; const DEPRECATED_PLUGIN_HOOK_NAMES = Object.keys( @@ -1375,19 +1367,6 @@ export type PluginHookHandlerMap = { event: PluginHookSubagentEndedEvent, ctx: PluginHookSubagentContext, ) => Promise | void; - /** - * 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, - ctx: PluginHookGatewayContext, - ) => Promise | void; gateway_start: ( event: PluginHookGatewayStartEvent, ctx: PluginHookGatewayContext, diff --git a/src/plugins/loader.hooks-and-runtime.test-utils.ts b/src/plugins/loader.hooks-and-runtime.test-utils.ts index 5c2803b51e62..899d4ce660f4 100644 --- a/src/plugins/loader.hooks-and-runtime.test-utils.ts +++ b/src/plugins/loader.hooks-and-runtime.test-utils.ts @@ -1464,45 +1464,6 @@ ${channelPluginSource({ ]); }); - it("normalizes legacy deactivate typed hooks onto gateway_stop", () => { - useNoBundledPlugins(); - const plugin = writePlugin({ - id: "legacy-deactivate-hook", - filename: "legacy-deactivate-hook.cjs", - body: `module.exports = { id: "legacy-deactivate-hook", register(api) { - api.on("deactivate", () => undefined); - } };`, - }); - - const registry = loadRegistryFromSinglePlugin({ - plugin, - pluginConfig: { - allow: ["legacy-deactivate-hook"], - entries: { - "legacy-deactivate-hook": { - hooks: { - timeoutMs: 250, - }, - }, - }, - }, - }); - - expect(registry.plugins.find((entry) => entry.id === "legacy-deactivate-hook")?.status).toBe( - "loaded", - ); - expect(registry.typedHooks.map((entry) => entry.hookName)).toEqual(["gateway_stop"]); - expect(registry.typedHooks[0]?.timeoutMs).toBe(250); - expect( - registry.diagnostics.some( - (diag) => - diag.pluginId === "legacy-deactivate-hook" && - 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); - }); - it("warns when plugins register deprecated subagent_spawning typed hooks", () => { useNoBundledPlugins(); const plugin = writePlugin({ diff --git a/src/plugins/registry-registrars-tools-hooks.ts b/src/plugins/registry-registrars-tools-hooks.ts index 8d31b0b8bf13..cbf27555ace7 100644 --- a/src/plugins/registry-registrars-tools-hooks.ts +++ b/src/plugins/registry-registrars-tools-hooks.ts @@ -52,7 +52,6 @@ import type { PluginHookRegistration as TypedPluginHookRegistration, } from "./types.js"; -const LEGACY_DEACTIVATE_HOOK_ALIAS_COMPAT = getPluginCompatRecord("legacy-deactivate-hook-alias"); const LEGACY_SUBAGENT_SPAWNING_HOOK_COMPAT = getPluginCompatRecord("legacy-subagent-spawning-hook"); function normalizeEligibleTriggers(value: unknown) { @@ -66,17 +65,8 @@ function normalizeEligibleTriggers(value: unknown) { return uniqueValues(triggers); } -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}.` - ); -} - function formatDeprecatedTypedHookDiagnostic(hookName: PluginHookName): string | undefined { - if (!isDeprecatedPluginHookName(hookName) || hookName === "deactivate") { + if (!isDeprecatedPluginHookName(hookName)) { return undefined; } const deprecation = DEPRECATED_PLUGIN_HOOKS[hookName]; @@ -425,36 +415,25 @@ export function createToolHookRegistrars(state: PluginRegistryState) { }); return; } - const effectiveHookName = hookName === "deactivate" ? "gateway_stop" : hookName; - if (hookName === "deactivate") { + const diagnostic = formatDeprecatedTypedHookDiagnostic(hookName); + if (diagnostic) { pushDiagnostic({ level: "warn", pluginId: record.id, source: record.source, - message: formatLegacyDeactivateHookAliasDiagnostic(), + message: diagnostic, }); - } else { - const diagnostic = formatDeprecatedTypedHookDiagnostic(hookName); - if (diagnostic) { - pushDiagnostic({ - level: "warn", - pluginId: record.id, - source: record.source, - message: diagnostic, - }); - } } - const effectiveHandler = handler; - if (policy?.allowPromptInjection === false && isPromptInjectionHookName(effectiveHookName)) { + if (policy?.allowPromptInjection === false && isPromptInjectionHookName(hookName)) { pushDiagnostic({ level: "warn", pluginId: record.id, source: record.source, - message: `typed hook "${effectiveHookName}" blocked by plugins.entries.${record.id}.hooks.allowPromptInjection=false`, + message: `typed hook "${hookName}" blocked by plugins.entries.${record.id}.hooks.allowPromptInjection=false`, }); return; } - if (isConversationHookName(effectiveHookName)) { + if (isConversationHookName(hookName)) { const explicitConversationAccess = policy?.allowConversationAccess; if (record.origin !== "bundled" && explicitConversationAccess !== true) { pushDiagnostic({ @@ -462,7 +441,7 @@ export function createToolHookRegistrars(state: PluginRegistryState) { pluginId: record.id, source: record.source, message: - `typed hook "${effectiveHookName}" blocked because non-bundled plugins must set ` + + `typed hook "${hookName}" blocked because non-bundled plugins must set ` + `plugins.entries.${record.id}.hooks.allowConversationAccess=true`, }); return; @@ -472,38 +451,34 @@ export function createToolHookRegistrars(state: PluginRegistryState) { level: "warn", pluginId: record.id, source: record.source, - message: `typed hook "${effectiveHookName}" blocked by plugins.entries.${record.id}.hooks.allowConversationAccess=false`, + message: `typed hook "${hookName}" blocked by plugins.entries.${record.id}.hooks.allowConversationAccess=false`, }); return; } } - const timeoutMs = resolveTypedHookTimeoutMs({ hookName: effectiveHookName, opts, policy }); + const timeoutMs = resolveTypedHookTimeoutMs({ hookName, opts, policy }); const eligibleTriggers = - effectiveHookName === "before_agent_reply" + hookName === "before_agent_reply" ? normalizeEligibleTriggers(opts?.eligibleTriggers) : undefined; const matcher = - effectiveHookName === "before_tool_call" || effectiveHookName === "after_tool_call" + hookName === "before_tool_call" || hookName === "after_tool_call" ? normalizePluginToolMatcher(opts?.matcher) : undefined; - if ( - opts?.matcher && - effectiveHookName !== "before_tool_call" && - effectiveHookName !== "after_tool_call" - ) { + if (opts?.matcher && hookName !== "before_tool_call" && hookName !== "after_tool_call") { pushDiagnostic({ level: "warn", pluginId: record.id, source: record.source, - message: `typed hook "${effectiveHookName}" ignores tool matcher`, + message: `typed hook "${hookName}" ignores tool matcher`, }); } record.hookCount += 1; registry.typedHooks.push({ pluginId: record.id, ...(opts?.registrationId ? { registrationId: opts.registrationId } : {}), - hookName: effectiveHookName, - handler: effectiveHandler, + hookName, + handler, ...(matcher ? { matcher } : {}), priority: opts?.priority, ...(timeoutMs !== undefined ? { timeoutMs } : {}), diff --git a/src/secrets/channel-secret-collector-runtime.ts b/src/secrets/channel-secret-collector-runtime.ts deleted file mode 100644 index 7a166d7e3b4f..000000000000 --- a/src/secrets/channel-secret-collector-runtime.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Runtime barrel for channel secret collectors used by bundled channel contracts. - * Keep channel packages on this narrow surface instead of deep runtime modules. - */ -export { - collectConditionalChannelFieldAssignments, - collectNestedChannelFieldAssignments, - collectSimpleChannelFieldAssignments, - getChannelRecord, - getChannelSurface, - hasConfiguredSecretInputValue, - isBaseFieldActiveForChannelSurface, - normalizeSecretStringValue, - resolveChannelAccountSurface, -} from "./channel-secret-basic-runtime.js"; -export type { - ChannelAccountEntry, - ChannelAccountPredicate, - ChannelAccountSurface, -} from "./channel-secret-basic-runtime.js"; -export { collectNestedChannelTtsAssignments } from "./channel-secret-tts-runtime.js"; diff --git a/src/security/dm-policy-shared.ts b/src/security/dm-policy-shared.ts index dce584acae62..947e8e1691df 100644 --- a/src/security/dm-policy-shared.ts +++ b/src/security/dm-policy-shared.ts @@ -5,7 +5,6 @@ import { resolveChannelIngressEffectiveAllowFromLists } from "../channels/messag import { readChannelIngressStoreAllowFromForDmPolicy } from "../channels/message-access/store-allow-from.js"; import type { ChannelId } from "../channels/plugins/channel-id.types.js"; import type { GroupPolicy } from "../config/types.base.js"; -import { evaluateMatchedGroupAccessForPolicy } from "../plugin-sdk/group-access.js"; /** * Derive a stable main-DM owner from a single-entry allowlist. @@ -116,32 +115,6 @@ type DmGroupAccessInputParams = { isSenderAllowed: (allowFrom: string[]) => boolean; }; -const GROUP_ACCESS_RESULT: Record< - Exclude["reason"], "allowed">, - DmGroupAccessResult -> = { - disabled: dmGroupAccess( - "block", - DM_GROUP_ACCESS_REASON.GROUP_POLICY_DISABLED, - "groupPolicy=disabled", - ), - empty_allowlist: dmGroupAccess( - "block", - DM_GROUP_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST, - "groupPolicy=allowlist (empty allowlist)", - ), - missing_match_input: dmGroupAccess( - "block", - DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED, - "groupPolicy=allowlist (not allowlisted)", - ), - not_allowlisted: dmGroupAccess( - "block", - DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED, - "groupPolicy=allowlist (not allowlisted)", - ), -}; - /** @deprecated Use `resolveChannelMessageIngress` or `readChannelIngressStoreAllowFromForDmPolicy` from `openclaw/plugin-sdk/channel-ingress-runtime`. */ export async function readStoreAllowFromForDmPolicy(params: { provider: ChannelId; @@ -170,31 +143,34 @@ function resolveLegacyDmGroupAccessDecision(params: { const effectiveGroupAllowFrom = normalizeStringEntries(params.effectiveGroupAllowFrom); if (params.isGroup) { - const groupAccess = evaluateMatchedGroupAccessForPolicy({ - groupPolicy, - allowlistConfigured: effectiveGroupAllowFrom.length > 0, - allowlistMatched: params.isSenderAllowed(effectiveGroupAllowFrom), - }); - if (groupAccess.allowed) { + if (groupPolicy === "disabled") { return dmGroupAccess( - "allow", - DM_GROUP_ACCESS_REASON.GROUP_POLICY_ALLOWED, - `groupPolicy=${groupPolicy}`, + "block", + DM_GROUP_ACCESS_REASON.GROUP_POLICY_DISABLED, + "groupPolicy=disabled", ); } - switch (groupAccess.reason) { - case "disabled": - case "empty_allowlist": - case "missing_match_input": - case "not_allowlisted": - return GROUP_ACCESS_RESULT[groupAccess.reason]; - case "allowed": + if (groupPolicy === "allowlist") { + if (effectiveGroupAllowFrom.length === 0) { return dmGroupAccess( - "allow", - DM_GROUP_ACCESS_REASON.GROUP_POLICY_ALLOWED, - `groupPolicy=${groupPolicy}`, + "block", + DM_GROUP_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST, + "groupPolicy=allowlist (empty allowlist)", ); + } + if (!params.isSenderAllowed(effectiveGroupAllowFrom)) { + return dmGroupAccess( + "block", + DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED, + "groupPolicy=allowlist (not allowlisted)", + ); + } } + return dmGroupAccess( + "allow", + DM_GROUP_ACCESS_REASON.GROUP_POLICY_ALLOWED, + `groupPolicy=${groupPolicy}`, + ); } if (dmPolicy === "disabled") { diff --git a/src/shared/scoped-expiring-id-cache.ts b/src/shared/scoped-expiring-id-cache.ts index 078087e61311..87cb3e074fa7 100644 --- a/src/shared/scoped-expiring-id-cache.ts +++ b/src/shared/scoped-expiring-id-cache.ts @@ -4,7 +4,7 @@ import { resolveNonNegativeIntegerOption, } from "@openclaw/normalization-core/number-coercion"; -export type ScopedExpiringIdCache = { +type ScopedExpiringIdCache = { /** Records an id for a scope at the provided timestamp or current time. */ record: (scope: TScope, id: TId, now?: number) => void; /** Returns true while the id is present and within the inclusive TTL window. */ diff --git a/test/e2e/qa-lab/runtime/logging-file-boundary-runtime.ts b/test/e2e/qa-lab/runtime/logging-file-boundary-runtime.ts index a0793d22c6f0..4bfca3b51319 100644 --- a/test/e2e/qa-lab/runtime/logging-file-boundary-runtime.ts +++ b/test/e2e/qa-lab/runtime/logging-file-boundary-runtime.ts @@ -9,12 +9,8 @@ import { createDiagnosticTraceContext, runWithDiagnosticTraceContext, } from "../../../../src/infra/diagnostic-trace-context.js"; -import { - getChildLogger, - resetLogger, - setLoggerOverride, - testApi, -} from "../../../../src/logging/logger.js"; +import { getChildLogger, resetLogger, setLoggerOverride } from "../../../../src/logging/logger.js"; +import { testApi } from "../../../../src/logging/logger.test-support.js"; import { createQaScriptEvidenceWriter } from "./script-evidence.js"; function artifactBase(argv: readonly string[]): string { diff --git a/test/openclaw-npm-postpublish-verify.test.ts b/test/openclaw-npm-postpublish-verify.test.ts index 899f29f17db5..4dc80559bbdc 100644 --- a/test/openclaw-npm-postpublish-verify.test.ts +++ b/test/openclaw-npm-postpublish-verify.test.ts @@ -13,7 +13,6 @@ import { collectInstalledBundledExtensionManifestErrors, collectInstalledBundledRuntimeSidecarPaths, collectInstalledContextEngineRuntimeErrors, - collectInstalledPluginSdkZodArtifactErrors, collectInstalledRootDependencyManifestErrors, collectInstalledPackageErrors, fetchRegistryJson, @@ -907,77 +906,6 @@ describe("collectInstalledContextEngineRuntimeErrors", () => { }); }); -describe("collectInstalledPluginSdkZodArtifactErrors", () => { - function withInstalledPackageRoot(run: (packageRoot: string) => void): void { - const packageRoot = mkdtempSync(join(tmpdir(), "openclaw-postpublish-zod-sdk-")); - try { - run(packageRoot); - } finally { - rmSync(packageRoot, { recursive: true, force: true }); - } - } - - function writeInstalledFile(packageRoot: string, relativePath: string, contents: string): void { - const filePath = join(packageRoot, ...relativePath.split("/")); - mkdirSync(dirname(filePath), { recursive: true }); - writeFileSync(filePath, contents, "utf8"); - } - - it("requires the plugin-sdk zod artifact", () => { - withInstalledPackageRoot((packageRoot) => { - expect(collectInstalledPluginSdkZodArtifactErrors(packageRoot)).toEqual([ - "installed package is missing required plugin SDK artifact: dist/plugin-sdk/zod.js", - ]); - }); - }); - - it("rejects plugin-sdk zod artifacts with a bare zod export", () => { - withInstalledPackageRoot((packageRoot) => { - writeInstalledFile( - packageRoot, - "dist/plugin-sdk/zod.js", - 'import "../zod-D2c0iocA.js";\nexport * from "zod";\n', - ); - - expect(collectInstalledPluginSdkZodArtifactErrors(packageRoot)).toEqual([ - "installed package plugin SDK zod artifact must be self-contained but dist/plugin-sdk/zod.js imports zod.", - ]); - }); - }); - - it("rejects plugin-sdk zod artifacts when a reachable local chunk imports zod", () => { - withInstalledPackageRoot((packageRoot) => { - writeInstalledFile( - packageRoot, - "dist/plugin-sdk/zod.js", - 'export { z } from "../zod-D2c0iocA.js";\n', - ); - writeInstalledFile( - packageRoot, - "dist/zod-D2c0iocA.js", - 'import * as zodCore from "zod/v4/core";\nexport const z = zodCore;\n', - ); - - expect(collectInstalledPluginSdkZodArtifactErrors(packageRoot)).toEqual([ - "installed package plugin SDK zod artifact must be self-contained but dist/zod-D2c0iocA.js imports zod/v4/core.", - ]); - }); - }); - - it("accepts plugin-sdk zod artifacts that only import package-local chunks", () => { - withInstalledPackageRoot((packageRoot) => { - writeInstalledFile( - packageRoot, - "dist/plugin-sdk/zod.js", - 'export { z } from "../zod-D2c0iocA.js";\n', - ); - writeInstalledFile(packageRoot, "dist/zod-D2c0iocA.js", "export const z = {};\n"); - - expect(collectInstalledPluginSdkZodArtifactErrors(packageRoot)).toEqual([]); - }); - }); -}); - describe("normalizeInstalledBinaryVersion", () => { it("accepts decorated CLI version output", () => { expect(normalizeInstalledBinaryVersion("OpenClaw 2026.4.8 (9ece252)")).toBe("2026.4.8"); diff --git a/test/release-check.test.ts b/test/release-check.test.ts index 6e4f54d60c2e..78b49163f524 100644 --- a/test/release-check.test.ts +++ b/test/release-check.test.ts @@ -820,7 +820,6 @@ describe("collectMissingPackPaths", () => { packageRoot, }), ).toEqual([ - "installed package is missing required plugin SDK artifact: dist/plugin-sdk/zod.js", "installed package root dist file 'typescript-compiler.js' is invalid or exceeds 6291456 bytes.", ]); } finally { diff --git a/test/scripts/check-deprecated-api-usage.test.ts b/test/scripts/check-deprecated-api-usage.test.ts index b5682700141f..c78ce6665f14 100644 --- a/test/scripts/check-deprecated-api-usage.test.ts +++ b/test/scripts/check-deprecated-api-usage.test.ts @@ -74,7 +74,6 @@ describe("scripts/check-deprecated-api-usage", () => { "src/plugin-sdk/channel-message", "src/plugin-sdk/channel-reply-pipeline", "src/plugin-sdk/inbound-reply-dispatch", - "src/plugin-sdk/text-runtime", ]) { expect(modulePaths.has(facade), facade).toBe(true); } @@ -96,7 +95,6 @@ describe("scripts/check-deprecated-api-usage", () => { 'import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";', 'export { runChannelInboundEvent } from "../plugin-sdk/inbound-reply-dispatch.js";', 'const facade = await import ("../plugin-sdk/channel-message.js", { with: {} });', - 'const text = require("@openclaw/plugin-sdk/text-runtime");', ].join("\n"), }); @@ -108,7 +106,6 @@ describe("scripts/check-deprecated-api-usage", () => { "src/channels/probe.ts:2: ../plugin-sdk/inbound-reply-dispatch.js", ); expect(result.stderr).toContain("src/channels/probe.ts:3: ../plugin-sdk/channel-message.js"); - expect(result.stderr).toContain("src/channels/probe.ts:4: @openclaw/plugin-sdk/text-runtime"); }); it("allows canonical compat re-exports and test files", () => {