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", () => {