mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: retire due compat-ledger surfaces (context-engine host params, deactivate alias, logging internals) (#121845)
* refactor(plugins): retire deactivate hook alias
* refactor(plugin-sdk): prune retired facade exports
* test(logging): isolate logger test controls
* refactor(logging): internalize file transport controls
* test(plugin-sdk): preserve retired facade coverage
* test(auto-reply): remove stale diagnostic imports
* refactor(logging): delete dead config-read guard
shouldSkipMutatingLoggingConfigRead had no production caller even on main;
it survived the dead-export scan only via logger's testApi re-export. The
test-isolation commit removed that mask, exposing the fossil. Delete the
guard, its test-only re-export, its mock entry, and its dedicated test file.
* refactor(plugin-sdk): retire due compatibility subpaths
* test(plugin-sdk): type group policy predicates
* refactor(plugin-sdk): split removed subpath records
* refactor(secrets): remove retired collector barrel
* test(plugin-sdk): tighten wildcard surface pin
* refactor(plugin-sdk): retire matrix facade metadata
* style(plugin-sdk): format facade metadata
* fix(ci): load channel setup contracts from source
Repair the main-owned regression from 99d662473c (Peter Steinberger): the new env-contract test could consume stale ignored dist metadata instead of the checked-in plugin declaration.
* test(plugin-sdk): refresh API baseline after rebase
This commit is contained in:
committed by
GitHub
parent
49092ebab3
commit
dceb2c343c
@@ -1 +0,0 @@
|
||||
{"contentHash":"0349da0a93dadbdcff03ae8064b5066c5f2e1c28ee298bfa9ab3f1f8563300eb","entrypoint":"agent-config-primitives","importSpecifier":"openclaw/plugin-sdk/agent-config-primitives"}
|
||||
@@ -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"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"108af57a4b4bc57c70912fc40319876bb6ec994cce59d2b9c991980e18c4f80f","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
{"contentHash":"4580a38b9e871c7d448c90cd247673929caaa7a298877d96d8e3f7e7c970b27a","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"adc174fdd6ceeaa664ecf483d76aec22ecb111a7a8f6534cb78463b4347abb5e","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
|
||||
{"contentHash":"96ad6087a29b2cb11f793f954afd31446854d173396b0d7fded033fddddf4dd3","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"dd255204bf11dccfe9d866e7a6725935bfead7378a52ddfa6b60b9a58c0cdf76","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
{"contentHash":"b84b53ca4792a410eb488ae4fb303e362c3c84d8eb72e37cf9c922183792a7cd","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"f0be441226a760f75dc1634f93d7f28586a0b18f690e2dd0f87c5360f2cb7f03","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
|
||||
{"contentHash":"017a51cefc0713190d68e7ba9a7901d45342451ca3b05c4830072340fa86515e","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"contentHash":"be803c0341214a8e731f98cb7bf98654410ab1e0254d90f9f1ad9f442f70669f","entrypoint":"channel-logging","importSpecifier":"openclaw/plugin-sdk/channel-logging"}
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"8bf7d1fd1e21861fd472365e0ab10ca811800e1b0cb9e6aac0e4d6a41613a9a2","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
{"contentHash":"abb22832153a23cd5187f92f2cdee6d81889d61697fae04c8647eb3370a1aa93","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"d430c3def27acb48a5356dafac17b89b0cbfcb1d3f959136b8c7ae82586d87df","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
{"contentHash":"a82dcfdcc661b2567ba2ba631d589887d5ca29cfe1d8600696fe0db859f4969f","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"6200788e555ccd22764a7aff25a94b4ce7176dd1443a5fe6f3bb81f23c561811","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"}
|
||||
{"contentHash":"d6252e057b614160557b42923f45f42491b5872c835e5f1d372a031e338ecdd4","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"contentHash":"c705cf7c1ba06b0d473d6cd83e4701e8ec464b226446d5465ebffb0f656dc4d0","entrypoint":"channel-secret-runtime","importSpecifier":"openclaw/plugin-sdk/channel-secret-runtime"}
|
||||
@@ -1 +0,0 @@
|
||||
{"contentHash":"2e6f9a8e8a07de789f6673813b5acc17dd08a11420c9de1b3ac509d37786adc6","entrypoint":"channel-streaming","importSpecifier":"openclaw/plugin-sdk/channel-streaming"}
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"f2849884f368eadff174176a2808c1c74d041968049b460d6f8b408682c5862b","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"}
|
||||
{"contentHash":"56a3c566d98d262c3ac07c830f2b33bd864e35930335d7b4e3e2ccc76607779e","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"2c21d121fd9d14d7f45368b5f02dff257b6750215d63d4be9ace28b5c0d3c0f8","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
|
||||
{"contentHash":"180a151549ad301c08f9265c3f9fb7a17821c93dd5b0e4f6128f7512312dc909","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"4c667b7e88cd752c294bcc731bc157c1ccb2a7aa4afc58f4013409191f3a9b28","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
{"contentHash":"2efff507ffe87196b554e29043eadc0ba17a32864f3ccd1864d4200add9d8f87","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"027b2280171324b628865fb4cdf9f72c13c059385a2fbec5b650c69d66beecdc","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
{"contentHash":"31bfbc294becf37f8ee037a0f8f50c69e4d04e097ff9bea16089115ee93b80df","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"adf5aa7e44a7bc6f6e0607fa35eae1fcb197749bb5a129c1848f85d61dc319a2","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"}
|
||||
{"contentHash":"bb371c8a354a150acf264357e3cbcb755efc6f42d9a2143e5a9254c3bc3c2aa8","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"contentHash":"c6ff5b325384c258ae96e677568d58652fe66ebc6745669a2137970154ef4b92","entrypoint":"group-access","importSpecifier":"openclaw/plugin-sdk/group-access"}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"ca7135503da9f048e237fd1242f463f74feccb22da0bc63770e16d982487a7a9","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"}
|
||||
{"contentHash":"c27a0ddd9f0a12bbb8377e8fcf8422ed2e066b8d248bc250c34e06518f1891a3","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"0b044375b584739e90aa9591e4be679a90cae0fd5e29b3ede60b942d236a7f3a","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"}
|
||||
{"contentHash":"2efeb44c37461f4b366018d4e5e11b0c2e107336202388d8f17688b71954fa39","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"contentHash":"192aa90e9d1d6e1bae65abd591cd0ab6307dc2bcc7626092e48ba96a171f30f8","entrypoint":"matrix","importSpecifier":"openclaw/plugin-sdk/matrix"}
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"69fe3479e42b74771fb86d228264e8e19a3a16e1a07df4f89499111c2df2bc13","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
{"contentHash":"5ccd913160d86827e36459bf6c6e88a169306dd24e913b85e288cbbf2ea12157","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"f058be9cbffbcb6e1dafe69cbc701c7cc06d1ffc90323256685e31b111f91193","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
{"contentHash":"d9ae989750b9f69072b5767cdf8995bde3f431da315e031cb80f35d2052a3917","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"e7ac61e29d41e43a4c780831a8aa0e5bed10e47630a36879f2555216eb1cce54","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
{"contentHash":"dce833138906cea4ce741068911c2e4818ebc37693edb2d2883e54eb7b25778e","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"c63113a4720b3d4795a3deb00ce3652cc9481522af311b9a269e4fe0d84703bb","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"}
|
||||
{"contentHash":"ae9c54c47a1937328f56b23f03590d458248f58c597645ec5e0f65b19464e75a","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"538f9d1c94c854e2c23a20e2b178be281775abf1ede99d21a543c689ccb28a78","entrypoint":"security-runtime","importSpecifier":"openclaw/plugin-sdk/security-runtime"}
|
||||
{"contentHash":"e793cfb3c3eb076bb73ed290ddc08c49d2c8ef35fcf46bdbcac66f24c6413bf2","entrypoint":"security-runtime","importSpecifier":"openclaw/plugin-sdk/security-runtime"}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"contentHash":"9d63e2bd18378154e40c4055f34010a6db6c6e7913ebbbfcf2e8565ad7302d93","entrypoint":"text-runtime","importSpecifier":"openclaw/plugin-sdk/text-runtime"}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"f76d5908b66bb49851cccc530dc63fc5f3352b327963f1091a0f34fe9ccc693b","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
{"contentHash":"a392d8bbb2ababa55a16ac115beff0ed0b790723c8e6c69c0a46d44c6984ed4e","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"e9dd8d432098b26f2e102fe41edee7c556558ea9b26a0077da72b0967d6649d2","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
{"contentHash":"a9f8b45ec3684fdb607c50fc47c41f62255c9ac0eae282b42441df58400fce31","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"contentHash":"eafdc7277066acf893bfef9b635689c1a69d6d5af22ad8cee2b47351d9b53ca6","entrypoint":"zod","importSpecifier":"openclaw/plugin-sdk/zod"}
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="deactivate hook -> gateway_stop">
|
||||
**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.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="subagent_spawning hook -> core thread binding">
|
||||
**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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Public package facade for channel secret runtime helpers.
|
||||
|
||||
export * from "../../../src/plugin-sdk/channel-secret-runtime.js";
|
||||
@@ -1,3 +0,0 @@
|
||||
// Public package facade for channel streaming helpers.
|
||||
|
||||
export * from "../../../src/plugin-sdk/channel-streaming.js";
|
||||
@@ -1,3 +0,0 @@
|
||||
// Public package facade for text runtime helpers.
|
||||
|
||||
export * from "../../../src/plugin-sdk/text-runtime.js";
|
||||
@@ -1,3 +0,0 @@
|
||||
// Public package facade for the shared zod re-export.
|
||||
|
||||
export * from "../../../src/plugin-sdk/zod.js";
|
||||
@@ -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<PluginRuntime>({
|
||||
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,
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[
|
||||
"channel-lifecycle",
|
||||
"infra-runtime",
|
||||
"text-runtime"
|
||||
"infra-runtime"
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<string>();
|
||||
|
||||
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[] = [];
|
||||
|
||||
@@ -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<string, number>);
|
||||
|
||||
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,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -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", () => ({
|
||||
|
||||
@@ -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<StuckSessionRecoveryOutcome>>(
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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> | boolean | void) {
|
||||
return createChannelProgressDraftCompositor({
|
||||
|
||||
+12
-30
@@ -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", () => {
|
||||
@@ -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;
|
||||
|
||||
@@ -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<void> {
|
||||
// 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<typeof import("../channels/plugins/bundled.js")>(
|
||||
"../channels/plugins/bundled.js",
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
|
||||
+2
-3
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -173,7 +173,7 @@ function applyRecoveryOutcomeToDiagnosticState(params: {
|
||||
markActivity();
|
||||
}
|
||||
|
||||
export function requestStuckSessionRecoveryOutcome(
|
||||
function requestStuckSessionRecoveryOutcome(
|
||||
params: RequestStuckSessionRecoveryParams,
|
||||
): Promise<StuckSessionRecoveryOutcome | undefined> {
|
||||
const inFlightKey = recoveryRequestKey(params.request);
|
||||
|
||||
@@ -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<unknown>): Promise<void> {
|
||||
let settled = false;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import "./diagnostic.js";
|
||||
|
||||
type DiagnosticTestApi = {
|
||||
resetDiagnosticStateForTest(): void;
|
||||
resolveStuckSessionAbortMs(stuckSessionWarnMs: number): number;
|
||||
resolveStuckSessionWarnMs(): number;
|
||||
};
|
||||
|
||||
function getTestApi(): DiagnosticTestApi {
|
||||
return (globalThis as Record<PropertyKey, unknown>)[
|
||||
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();
|
||||
}
|
||||
@@ -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<typeof startDiagnosticHeartbeatImpl>[0],
|
||||
|
||||
+15
-80
@@ -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<StuckSessionRecoveryOutcome | undefined> {
|
||||
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<PropertyKey, unknown>)[Symbol.for("openclaw.diagnosticTestApi")] = testing;
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -7,7 +7,6 @@ const { readLoggingConfigMock } = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("./config.js", () => ({
|
||||
readLoggingConfig: readLoggingConfigMock,
|
||||
shouldSkipMutatingLoggingConfigRead: () => false,
|
||||
}));
|
||||
|
||||
let logging: typeof import("../logging.js");
|
||||
|
||||
@@ -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-");
|
||||
|
||||
@@ -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-");
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
async function flushFileLogQueue(): Promise<void> {
|
||||
for (;;) {
|
||||
if (scheduledFlush) {
|
||||
clearImmediate(scheduledFlush);
|
||||
@@ -323,7 +323,7 @@ export async function flushFileLogQueue(): Promise<void> {
|
||||
}
|
||||
|
||||
/** 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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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-");
|
||||
|
||||
|
||||
@@ -44,9 +44,7 @@ describe("logger transport registry", () => {
|
||||
expect(
|
||||
(loggerModule as unknown as Record<string, unknown>).registerLogTransport,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(loggerModule.testApi as unknown as Record<string, unknown>).registerLogTransportForTest,
|
||||
).toBeUndefined();
|
||||
expect((loggerModule as unknown as Record<string, unknown>).testApi).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not publish mutable log transport state on a well-known global symbol", async () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
+11
-41
@@ -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<string, unknown>;
|
||||
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<string, string | number | boolean>;
|
||||
|
||||
@@ -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<LogObj> {
|
||||
...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<void> {
|
||||
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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -1,7 +0,0 @@
|
||||
/** @deprecated Compatibility subpath. Use `channel-inbound` or `channel-outbound`. */
|
||||
export {
|
||||
type LogFn,
|
||||
logAckFailure,
|
||||
logInboundDrop,
|
||||
logTypingFailure,
|
||||
} from "../channels/logging.js";
|
||||
@@ -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(
|
||||
|
||||
@@ -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<string | number> | undefined | null): string[] {
|
||||
if (!Array.isArray(list)) {
|
||||
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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<typeof resolveSenderScopedGroupPolicy>[0];
|
||||
expected: ReturnType<typeof resolveSenderScopedGroupPolicy>;
|
||||
}> = [
|
||||
{
|
||||
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<typeof evaluateSenderGroupAccessForPolicy>[0];
|
||||
expected: ReturnType<typeof evaluateSenderGroupAccessForPolicy>;
|
||||
}> = [
|
||||
{
|
||||
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<typeof evaluateGroupRouteAccessForPolicy>[0];
|
||||
expected: ReturnType<typeof evaluateGroupRouteAccessForPolicy>;
|
||||
}> = [
|
||||
{
|
||||
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<typeof evaluateMatchedGroupAccessForPolicy>[0];
|
||||
expected: ReturnType<typeof evaluateMatchedGroupAccessForPolicy>;
|
||||
}> = [
|
||||
{
|
||||
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<typeof evaluateSenderGroupAccess>[0];
|
||||
expected: ReturnType<typeof evaluateSenderGroupAccess>;
|
||||
}> = [
|
||||
{
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user