diff --git a/docs/.generated/plugin-sdk-api-baseline.jsonl b/docs/.generated/plugin-sdk-api-baseline.jsonl index 96c30f01605a..dce1715ea3b9 100644 --- a/docs/.generated/plugin-sdk-api-baseline.jsonl +++ b/docs/.generated/plugin-sdk-api-baseline.jsonl @@ -3,10 +3,10 @@ {"contentHash":"0d6827cdd7180482c49dc67aacc2b512e587d3453536081c15d3febaea2f1492","entrypoint":"account-id","importSpecifier":"openclaw/plugin-sdk/account-id"} {"contentHash":"0b4930a77ab3e63bed21a9651a23fb7130f623baf6ce153166cb80a965b0a004","entrypoint":"account-resolution","importSpecifier":"openclaw/plugin-sdk/account-resolution"} {"contentHash":"0349da0a93dadbdcff03ae8064b5066c5f2e1c28ee298bfa9ab3f1f8563300eb","entrypoint":"agent-config-primitives","importSpecifier":"openclaw/plugin-sdk/agent-config-primitives"} -{"contentHash":"436e0ea78113e5411324fcf16377a2f269823fcae079d7ff9d1fbd449c5d6c98","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} -{"contentHash":"67cf13823fcfe4b8666b8378dc10b057e75f3dcb2b4a234cf2b2d159c3921b08","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} +{"contentHash":"1f3c0ebd22470430534291f6bd387f153f17f5d0867a3b5419f5442a27f54278","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} +{"contentHash":"7f884c6335908c525f2b15b39cd9d1bcefe04b80469c84f1b2767c917ea736b7","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} {"contentHash":"4387e74b1261f632a0e140276c0f8cbbbe848a11f7ad287325538ebbee3b09da","entrypoint":"agent-media-payload","importSpecifier":"openclaw/plugin-sdk/agent-media-payload"} -{"contentHash":"9d8ba6f6be8f608ea078a10fb915d7122628080637c5dd7ddff91e691446da08","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} +{"contentHash":"2782e8bae7ec62400f52caf45a6e58f61abb403bc0db28cdbfbc7833cd880ef8","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} {"contentHash":"1f0e51c8b1a58fff13ab7d19806d97bf07fca61a5c7edeb3437cd805775a8320","entrypoint":"agent-scope-runtime","importSpecifier":"openclaw/plugin-sdk/agent-scope-runtime"} {"contentHash":"ce460dee97b375e50944f4da2ac95fe4c7a3f7be6b435550f54088b16369ed49","entrypoint":"allow-from","importSpecifier":"openclaw/plugin-sdk/allow-from"} {"contentHash":"c19f8658d0db60b1d3eddfeae9639ca6558592d7d69c6905ab2994b4a572f25b","entrypoint":"allowlist-config-edit","importSpecifier":"openclaw/plugin-sdk/allowlist-config-edit"} @@ -22,23 +22,23 @@ {"contentHash":"7f9135d4ea9e3f53ed16bc51c0e44525fdd8907e5824309492a489a35761c25d","entrypoint":"archive","importSpecifier":"openclaw/plugin-sdk/archive"} {"contentHash":"d87a0eb6bae1b95fa645e77b5b9a81eb6cfdad9c469ae365ccd74b26f750166f","entrypoint":"boolean-param","importSpecifier":"openclaw/plugin-sdk/boolean-param"} {"contentHash":"8dbe8fc49378a684c4d52a991da9f27bdb85bb080530fdce1957bfd5c5687724","entrypoint":"channel-actions","importSpecifier":"openclaw/plugin-sdk/channel-actions"} -{"contentHash":"09e65193adf3ba78b95019d34cf445746ceb7119069b329585d69d16fd90400b","entrypoint":"channel-config-helpers","importSpecifier":"openclaw/plugin-sdk/channel-config-helpers"} +{"contentHash":"5998386ad8e299f9080382e5a982bb96ab9f35d3849ecb6de730df8c63044a77","entrypoint":"channel-config-helpers","importSpecifier":"openclaw/plugin-sdk/channel-config-helpers"} {"contentHash":"222019df4c06aca4e9bbede0fdea4b946f9b024a1524e3837c3c13be6d3d9e6a","entrypoint":"channel-config-primitives","importSpecifier":"openclaw/plugin-sdk/channel-config-primitives"} {"contentHash":"645ec473e40a04e4cc627d05c9156f66d835eb67dab32166f745f66eadc98880","entrypoint":"channel-config-schema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema"} {"contentHash":"75935c1d837265d191436d43bbfe24c6328b35a45a68cad250c157923badf857","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract"} -{"contentHash":"69782257f9972d145234ab79ff78862587ab50b869ee752da958984f1f3da40b","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} +{"contentHash":"7cbe4e1a327ce4623f0c56e7a1debd3ad2d4fc75c148c4366993e90b9951fa5f","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} {"contentHash":"b2d90f161ca88ef2d5ff35f87f160c772bd784a4ebda91b2ab33bf16b6abd843","entrypoint":"channel-dm-policy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy"} -{"contentHash":"0b9b65943323fa62d16e723a469b4ef883c5e5031c264b293ddff22f80ac635e","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} +{"contentHash":"eb5de4fe50596ccc225becd7d40f86900a7467ed409fc1aabd1f7cd4472f0883","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} {"contentHash":"8bc8b784c78a70ee58731aae0be9731c9af01705db00acbafa25de9745a23601","entrypoint":"channel-feedback","importSpecifier":"openclaw/plugin-sdk/channel-feedback"} -{"contentHash":"c702f520272a16b626673930d5ad311acddabef85ef6344cb667a46d8d232081","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} +{"contentHash":"d536677665a306185447d5b83e46ba8a1cd78d11adbc1f4cf4d8171ffdef3116","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} {"contentHash":"87ba6e4e9f2f56621f6d52cc262e8b80fada5e338e841439b4d20983e22dfb82","entrypoint":"channel-inbound-debounce","importSpecifier":"openclaw/plugin-sdk/channel-inbound-debounce"} {"contentHash":"7a69b23fdd7b631ed407d9ae4af55df192d93b61488c81626f041d4199760d95","entrypoint":"channel-ingress-runtime","importSpecifier":"openclaw/plugin-sdk/channel-ingress-runtime"} {"contentHash":"85afc43170761c8d7d6d2d91401b0ad033189636050e8cf85d22132e4aba9f1a","entrypoint":"channel-lifecycle","importSpecifier":"openclaw/plugin-sdk/channel-lifecycle"} {"contentHash":"be803c0341214a8e731f98cb7bf98654410ab1e0254d90f9f1ad9f442f70669f","entrypoint":"channel-logging","importSpecifier":"openclaw/plugin-sdk/channel-logging"} -{"contentHash":"45c44443a11721749cf248f6297fadbb7de7bd3a1aa9ea556588e1b3e8cc6f6d","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} -{"contentHash":"0a59ef1b77768d9feb363f219d76a1f9d56423ff5b5bdf12e7506b08b410761b","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} +{"contentHash":"b465f0fe84c2aff407a3903c0d38d2a4afc2d6fae48e2024ccacf1e2df4767cb","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} +{"contentHash":"ef8e8ab4e213b38b5b9e45467f271ecc3cfcb2622add275da7a78782eadd34fe","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} {"contentHash":"4e059b59e9c0044627d10a7011f13dcf0ea3546f197731ab0a3af3772e35b8cb","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"} -{"contentHash":"9e0e609d46e3b4a64bccf6d228988dffe9e2099d5cea020a92e0126a01ea2f2c","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} +{"contentHash":"252b2da93a59eb13d3cb9f78ca92fdc4bdd577a8fb1912694393c3173781b7ba","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} {"contentHash":"350b4986aec115e3674c9dd3712302e7c185dbefc534b65a98a6a84564e65e05","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"} {"contentHash":"369721c067caf33fc86e50c7dd5aec3e8371d2e97e2e3c4305751b79da9e7387","entrypoint":"channel-reply-pipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline"} {"contentHash":"5181d3f1c6936e1e13a7d3ab4b904f5ae929a1985480c130915710a9bf8e8a25","entrypoint":"channel-runtime-context","importSpecifier":"openclaw/plugin-sdk/channel-runtime-context"} @@ -60,12 +60,12 @@ {"contentHash":"1deb735b023442072fe21294cf4f94af0e44356fd8591ed6a8a9befafc7c3d67","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"} {"contentHash":"40df7e972484ae28718fef1095b2b44cb2cc9eadd35464bf28d4ae0d0f0f66b5","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"} {"contentHash":"7a5a2f4d3e5e18d211480286d125419ecbf8175bb6f8eeee9a6ee105df03cb3f","entrypoint":"conversation-runtime","importSpecifier":"openclaw/plugin-sdk/conversation-runtime"} -{"contentHash":"5986edef7e0fae8b348236d870e6add9e846ef9649347369b20a1fb3e47c7a55","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} +{"contentHash":"db8ccfcf658564acf00cf049861ebbe44a94ceab69f6403e35ca4051c2615e90","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} {"contentHash":"184f6a3849b361adfc9db756699787e934e274f05eeedcda04cd2b7cb4f9fe5e","entrypoint":"dedupe-runtime","importSpecifier":"openclaw/plugin-sdk/dedupe-runtime"} {"contentHash":"8ba476d7f0411071797cd4362f3a71720d9d008d54107b2cb4eccb4a4eb0db0a","entrypoint":"device-bootstrap","importSpecifier":"openclaw/plugin-sdk/device-bootstrap"} {"contentHash":"cb6fbd1612e60755c7528a5243cec69c9123904297d2e16a9c716485785be532","entrypoint":"diagnostic-runtime","importSpecifier":"openclaw/plugin-sdk/diagnostic-runtime"} {"contentHash":"f7b6949b179827646d4e047ce4f181550ca8d67bd4537441405d36fb79438556","entrypoint":"directory-runtime","importSpecifier":"openclaw/plugin-sdk/directory-runtime"} -{"contentHash":"dbf1191e33cdb22bdef411e614c415025a44c40570d18ee43571b338dd12f4a5","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} +{"contentHash":"c4794a31e7efdb8c7740f4da6556206f903d3cc8bcc90507310a615c2823abd9","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} {"contentHash":"e0523b4936d7c7ec043265de9e2a8f6289d32dacba0a28c3999ba3a0975dfc21","entrypoint":"error-runtime","importSpecifier":"openclaw/plugin-sdk/error-runtime"} {"contentHash":"5125d71bf8c4aeca728c4b8c73b2984247ba7bcc088a45ee29692ff64e3ebf90","entrypoint":"extension-shared","importSpecifier":"openclaw/plugin-sdk/extension-shared"} {"contentHash":"cbeb81159a1e558846f3cf9eecf3fd5a84858029cfdd9ccf237af428e35e755c","entrypoint":"gateway-method-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-method-runtime"} @@ -75,8 +75,8 @@ {"contentHash":"273ebd4fcac3041e3a136410fa396b9704c73eedb66414723996127ed80db8f3","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"} {"contentHash":"a6537713e63d101897ef9ddfeaae438a50645e2672be5bdced887c4e8a651b96","entrypoint":"inbound-envelope","importSpecifier":"openclaw/plugin-sdk/inbound-envelope"} {"contentHash":"931a52a774750ab895236a8fa7851781ad0279cb5678b1a78e81af9dd25bb3a4","entrypoint":"inbound-event-delivery","importSpecifier":"openclaw/plugin-sdk/inbound-event-delivery"} -{"contentHash":"0d576051d319599bd81f41e407354e0bddd129f571df4ad6394b601cf879ef58","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} -{"contentHash":"485a44e8099cc31188018a114615be22e4c6f3b4f9ef04df381a95bae480e86b","entrypoint":"infra-runtime","importSpecifier":"openclaw/plugin-sdk/infra-runtime"} +{"contentHash":"d092285fcba3c813078c693c6dd0a2b9774cc775c75e4382d6efecf1bf94c04a","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} +{"contentHash":"b590647f65a2e148ba7a1bc207f70adbd3a57990b41d3bf6b10f61ee8f27ab87","entrypoint":"infra-runtime","importSpecifier":"openclaw/plugin-sdk/infra-runtime"} {"contentHash":"41a1f24a1b745f833d44e622ae1922c9dc66ee5de56f0e967ec1a3f9e03a2d18","entrypoint":"ingress-effect-once","importSpecifier":"openclaw/plugin-sdk/ingress-effect-once"} {"contentHash":"57f79c6e8db72b2806bc8c016066682de71bf690378ccb4403b8da6dd3d38b28","entrypoint":"interactive-runtime","importSpecifier":"openclaw/plugin-sdk/interactive-runtime"} {"contentHash":"e41d2b9acaec4e89bc69880d645c49c068dbbc19d0a3b247b18b891aa0ce8f39","entrypoint":"json-store","importSpecifier":"openclaw/plugin-sdk/json-store"} @@ -89,7 +89,7 @@ {"contentHash":"0df97720dfd7b1a5892bbf3d46bc64297468af9b8cbb8720ce0d80d42ff2e59d","entrypoint":"media-store","importSpecifier":"openclaw/plugin-sdk/media-store"} {"contentHash":"684c061bb0d1112872c9b2d5a00e258d14b4ff8649a26dd0cc67995352d8ca8b","entrypoint":"media-understanding","importSpecifier":"openclaw/plugin-sdk/media-understanding"} {"contentHash":"f48e34067ab9b25a232af71d8ce17f9cbf9165ebce0832deaae3c1d9c9aeaab7","entrypoint":"media-understanding-runtime","importSpecifier":"openclaw/plugin-sdk/media-understanding-runtime"} -{"contentHash":"63416bac276f99e275988fd199742a7774fcc1091b7d4cfdd27be31e591c375c","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} +{"contentHash":"c8bdb6f59fb0e5ecef5c5e1a93061eefa25d0c76be2963404d5ff12351f9cf47","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} {"contentHash":"27509d9bbabc547662a45bf258fa432d1e0c3c3f7c22051858d8a67375486e79","entrypoint":"memory-core-host-engine-foundation","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation"} {"contentHash":"ad875662e95df74808357c234ac36825d1ff729165da941180061591525ff47f","entrypoint":"memory-host-core","importSpecifier":"openclaw/plugin-sdk/memory-host-core"} {"contentHash":"0f140018c2c21965b6a8f7a2ac1882babcaf70e733b477bb693e498641629938","entrypoint":"messaging-targets","importSpecifier":"openclaw/plugin-sdk/messaging-targets"} @@ -101,17 +101,17 @@ {"contentHash":"3b2106094ccada38a6736f39abbb5814e776638cdb7f1f6d065576e6f8a2d3b6","entrypoint":"persistent-dedupe","importSpecifier":"openclaw/plugin-sdk/persistent-dedupe"} {"contentHash":"e43fcfe8f88cf8d338c3e3496f3bdd6ec14afc1fcf341e171f7758f5c0190d5b","entrypoint":"plugin-command-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-command-runtime"} {"contentHash":"756175b7ff16cd8456b3c85a87466f077476a9236e098c5545ea8ef31fcff0d2","entrypoint":"plugin-config-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-config-runtime"} -{"contentHash":"671047f7f29f0ef80021bde1f42f8bc05f900a419b0754ab0a25ec96018d4878","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} -{"contentHash":"c80882b5e1db8433d02a178b04cac3c60e9ae76e0d767d7374bba5e399ee49eb","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} +{"contentHash":"cc7b14cfab794959ca1c8d1ab375e03324053814b3092f1121aeb1f60dda1f8b","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} +{"contentHash":"a569e076eb2c4ea8fc8ac9951a861ae5a4c595789b49e1c1d835af20eb41b457","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} {"contentHash":"d5d21f2a0883eac72d6642d93306c36e2de0f3851286eb3edbe3a2d9d6122dcc","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"} -{"contentHash":"7d8eef8b52b9c091c4fc9916ed5e925fa97ca9a541a91adcdb59239df9dbf4a4","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} +{"contentHash":"88cb3ece55129c7efd194de57fd247b90c40d9d6c5f5d82075ddf309b1056ade","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} {"contentHash":"aee2a235e7218ed4ba36e80e0a6d67ceb3b5de448f25ebc10d6f70552dc35b8a","entrypoint":"proxy-capture","importSpecifier":"openclaw/plugin-sdk/proxy-capture"} {"contentHash":"55bc0be5ce1d700d8b671efccb69ec14a3c942535c2f77d3ed7ca87809dc1ad9","entrypoint":"question-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/question-gateway-runtime"} {"contentHash":"5263836c817c9391fd5a0efdda50552330dd01e4d6bc9cb4ebba58627c48f098","entrypoint":"reply-chunking","importSpecifier":"openclaw/plugin-sdk/reply-chunking"} {"contentHash":"137ac20380a751e39c9e8ea2a7fe69785c1656713af56901d72fe163ab835d2c","entrypoint":"reply-dispatch-runtime","importSpecifier":"openclaw/plugin-sdk/reply-dispatch-runtime"} {"contentHash":"0a7b62b1cfdf15c5d397be2ee951eb991e61712cf331f353faf21b5355863cf1","entrypoint":"reply-history","importSpecifier":"openclaw/plugin-sdk/reply-history"} {"contentHash":"369a0c223cba73be174d9a7ada6dcebff739b15c71c34f767e761b57af667723","entrypoint":"reply-payload","importSpecifier":"openclaw/plugin-sdk/reply-payload"} -{"contentHash":"346a32998d94c82981308dccd54ac8bc3dfd3b833e3fd02f15d6ed79ea5cddbd","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"} +{"contentHash":"e4bb392339efecbb53dc8d2a9af5b83a632f527ebdecccfe7c305ce2a46a13d2","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"} {"contentHash":"91ade8da7db7cd2b9cbafcbf307338fbd0d656ac8a31d10c38cab23a916840b8","entrypoint":"root-walk","importSpecifier":"openclaw/plugin-sdk/root-walk"} {"contentHash":"ad853e4f7e257b2a206f8e3a551b9e50f36a020db1fd36de65c121bcf24a6d5e","entrypoint":"routing","importSpecifier":"openclaw/plugin-sdk/routing"} {"contentHash":"c10788e36c8343600186fe9aadf84eb977e1686cb29e7ee0a581d033b8dfc7e5","entrypoint":"run-command","importSpecifier":"openclaw/plugin-sdk/run-command"} @@ -142,11 +142,11 @@ {"contentHash":"09957526abf9619fd2ef095119a7195f2bebf2d8e8b1a5605aee0868152f8b5c","entrypoint":"temp-path","importSpecifier":"openclaw/plugin-sdk/temp-path"} {"contentHash":"4519df78f420290da1fd755f7e4a7de7d09dbc934b3e17fc139aaf64896ffa06","entrypoint":"text-chunking","importSpecifier":"openclaw/plugin-sdk/text-chunking"} {"contentHash":"c5f29e3b48cb9a92111bf565f739875a32fc3e19a009dd0bab3c27bbb71f7583","entrypoint":"text-runtime","importSpecifier":"openclaw/plugin-sdk/text-runtime"} -{"contentHash":"31d9c24facf34b7a12b38e474421af901a5246702642dda44369b69096019e5f","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} +{"contentHash":"f9645fff7dad30a7848d81c12fe0ec73083332361fcd3fccd88ba8d45359f0ee","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} {"contentHash":"17e58e27b06e2470908acd719f64ec2add1f117c1882adad79eb5a3b5bd74312","entrypoint":"tool-results","importSpecifier":"openclaw/plugin-sdk/tool-results"} {"contentHash":"aabfb4eacce1bd5190b2f92189332abe7bbe3a5cea3655babe66bf1909206984","entrypoint":"tool-send","importSpecifier":"openclaw/plugin-sdk/tool-send"} {"contentHash":"8d432f42395afae27f4a95c897dfca0bf5df6289c0846d326df424ad5f6b491b","entrypoint":"web-media","importSpecifier":"openclaw/plugin-sdk/web-media"} -{"contentHash":"e597e7cd15bb3665cd8e82dc12e1105aaab3911cc0c1e79509da72cd980c96fd","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} +{"contentHash":"dce8b35bf1c8b58681c38dc12ca70537de12703c378cbd8a79a2707e8c4d65f0","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} {"contentHash":"1fe8314d4dfa3d7b536f39ac5622ff688f6a20fa6ae8b8fa4770d4851e85a400","entrypoint":"webhook-request-guards","importSpecifier":"openclaw/plugin-sdk/webhook-request-guards"} {"contentHash":"6091f4048c4464103dfdf659904c6afa6e29c483b9a05c502ab3333df08f942b","entrypoint":"widget-html","importSpecifier":"openclaw/plugin-sdk/widget-html"} {"contentHash":"eafdc7277066acf893bfef9b635689c1a69d6d5af22ad8cee2b47351d9b53ca6","entrypoint":"zod","importSpecifier":"openclaw/plugin-sdk/zod"} diff --git a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts index f6618674661b..ac351be9055c 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts @@ -195,7 +195,7 @@ const dispatchReplyHoisted = vi.hoisted(() => ({ export const dispatchReplyWithBufferedBlockDispatcher = dispatchReplyHoisted.dispatchReplyWithBufferedBlockDispatcher; vi.mock("../../../src/auto-reply/reply/provider-dispatcher.js", () => ({ - dispatchReplyWithBufferedBlockDispatcher: + dispatchReplyWithBufferedBlockDispatcherCore: dispatchReplyHoisted.dispatchReplyWithBufferedBlockDispatcher, })); vi.mock("openclaw/plugin-sdk/channel-inbound", async (importOriginal) => { diff --git a/scripts/lib/export-name-collision-baseline.json b/scripts/lib/export-name-collision-baseline.json index 308601c9fb7d..932adde69960 100644 --- a/scripts/lib/export-name-collision-baseline.json +++ b/scripts/lib/export-name-collision-baseline.json @@ -27,14 +27,6 @@ "src/plugins/uninstall.ts" ] }, - { - "name": "authorizeConfigWrite", - "files": [ - "src/channels/plugins/config-writes.ts", - "src/plugin-sdk/channel-config-helpers.ts" - ], - "sdk": true - }, { "name": "buildBundleMcpToolsFromCatalog", "files": [ @@ -70,22 +62,6 @@ "src/plugins/status.ts" ] }, - { - "name": "buildQaTarget", - "files": [ - "src/plugin-sdk/qa-channel-protocol.ts", - "src/plugin-sdk/qa-channel.ts" - ], - "sdk": true - }, - { - "name": "bundledPluginFile", - "files": [ - "src/plugin-sdk/test-helpers/bundled-plugin-paths.ts", - "src/plugins/contracts/test-helpers/bundled-plugin-roots.ts" - ], - "sdk": true - }, { "name": "callGateway", "files": [ @@ -100,14 +76,6 @@ "src/gateway/call.ts" ] }, - { - "name": "canBypassConfigWritePolicy", - "files": [ - "src/channels/plugins/config-writes.ts", - "src/plugin-sdk/channel-config-helpers.ts" - ], - "sdk": true - }, { "name": "classifySessionKind", "files": [ @@ -137,22 +105,6 @@ "src/secrets/runtime.ts" ] }, - { - "name": "closeActiveMemorySearchManager", - "files": [ - "src/plugin-sdk/memory-host-search.ts", - "src/plugins/memory-runtime.ts" - ], - "sdk": true - }, - { - "name": "closeActiveMemorySearchManagers", - "files": [ - "src/plugin-sdk/memory-host-search.ts", - "src/plugins/memory-runtime.ts" - ], - "sdk": true - }, { "name": "containsConfigIncludeDirective", "files": [ @@ -160,14 +112,6 @@ "src/plugins/install-persistence.ts" ] }, - { - "name": "createFixedWindowRateLimiter", - "files": [ - "src/infra/fixed-window-rate-limit.ts", - "src/plugin-sdk/webhook-memory-guards.ts" - ], - "sdk": true - }, { "name": "createGatewayStartupTrace", "files": [ @@ -189,46 +133,6 @@ "src/test-helpers/temp-dir.ts" ] }, - { - "name": "createTestRegistry", - "files": [ - "src/gateway/server/__tests__/test-utils.ts", - "src/test-utils/channel-plugins.ts" - ], - "sdk": true - }, - { - "name": "defaultRuntime", - "files": [ - "src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts", - "src/runtime.ts" - ], - "sdk": true - }, - { - "name": "deliverInboundReplyWithMessageSendContext", - "files": [ - "src/channels/turn/durable-delivery.ts", - "src/plugin-sdk/channel-outbound.ts" - ], - "sdk": true - }, - { - "name": "dispatchReplyWithBufferedBlockDispatcher", - "files": [ - "src/auto-reply/reply/provider-dispatcher.ts", - "src/plugin-sdk/reply-dispatch-runtime.ts" - ], - "sdk": true - }, - { - "name": "dispatchReplyWithDispatcher", - "files": [ - "src/auto-reply/reply/provider-dispatcher.ts", - "src/plugin-sdk/reply-dispatch-runtime.ts" - ], - "sdk": true - }, { "name": "doctorCommand", "files": [ @@ -236,31 +140,6 @@ "src/flows/doctor-health.ts" ] }, - { - "name": "drainPendingDeliveries", - "files": [ - "src/infra/outbound/delivery-queue-recovery.ts", - "src/plugin-sdk/delivery-queue-runtime.ts" - ], - "sdk": true - }, - { - "name": "enablePluginInConfig", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugin-sdk/provider-enable-config.ts", - "src/plugins/enable.ts" - ], - "sdk": true - }, - { - "name": "ensureConfiguredAcpBindingReady", - "files": [ - "src/acp/persistent-bindings.lifecycle.ts", - "src/plugin-sdk/core.ts" - ], - "sdk": true - }, { "name": "extractAssistantVisibleText", "files": [ @@ -275,15 +154,6 @@ "src/gateway/session-transcript-readers.ts" ] }, - { - "name": "fileExists", - "files": [ - "src/infra/state-migrations.fs.ts", - "src/media-understanding/fs.ts", - "src/plugin-sdk/security-runtime.ts" - ], - "sdk": true - }, { "name": "formatConfigPath", "files": [ @@ -291,46 +161,6 @@ "src/config/logging.ts" ] }, - { - "name": "formatConfigWriteDeniedMessage", - "files": [ - "src/channels/plugins/config-writes.ts", - "src/plugin-sdk/channel-config-helpers.ts" - ], - "sdk": true - }, - { - "name": "formatEnvelopeTimestamp", - "files": [ - "src/auto-reply/envelope.ts", - "src/plugin-sdk/test-helpers/envelope-timestamp.ts" - ], - "sdk": true - }, - { - "name": "formatSkillsForPrompt", - "files": [ - "src/skills/loading/session.ts", - "src/skills/loading/skill-contract.ts" - ], - "sdk": true - }, - { - "name": "generateOAuthState", - "files": [ - "src/plugin-sdk/provider-auth-runtime.ts", - "src/plugin-sdk/provider-oauth-runtime.ts" - ], - "sdk": true - }, - { - "name": "getActiveMemorySearchManager", - "files": [ - "src/plugin-sdk/memory-host-search.ts", - "src/plugins/memory-runtime.ts" - ], - "sdk": true - }, { "name": "getActivePluginChannelRegistryVersion", "files": [ @@ -345,14 +175,6 @@ "src/plugins/runtime.ts" ] }, - { - "name": "getChatChannelMeta", - "files": [ - "src/channels/chat-meta.ts", - "src/plugin-sdk/core.ts" - ], - "sdk": true - }, { "name": "getFreePort", "files": [ @@ -360,38 +182,6 @@ "src/test-utils/ports.ts" ] }, - { - "name": "getReplyFromConfig", - "files": [ - "src/auto-reply/reply/get-reply.ts", - "src/gateway/test-helpers.runtime-state.ts" - ], - "sdk": true - }, - { - "name": "getRuntimeAuthForModel", - "files": [ - "src/plugin-sdk/provider-auth-runtime.ts", - "src/plugins/runtime/runtime-model-auth.runtime.ts" - ], - "sdk": true - }, - { - "name": "hasOwnProperty", - "files": [ - "src/secrets/runtime-shared.ts", - "src/tts/tts-settings.ts" - ], - "sdk": true - }, - { - "name": "inferToolMetaFromArgs", - "files": [ - "src/agents/embedded-agent-utils.ts", - "src/plugin-sdk/agent-harness-runtime.ts" - ], - "sdk": true - }, { "name": "inspectPluginRegistry", "files": [ @@ -462,30 +252,6 @@ "src/plugins/install-package.ts" ] }, - { - "name": "isClaudeCliProvider", - "files": [ - "src/agents/cli-runner/helpers.ts", - "src/plugin-sdk/anthropic-cli.ts" - ], - "sdk": true - }, - { - "name": "isCommandMessage", - "files": [ - "src/auto-reply/commands-registry.ts", - "src/tui/tui-formatters.ts" - ], - "sdk": true - }, - { - "name": "isQaRuntimeAvailable", - "files": [ - "src/plugin-sdk/qa-runner-runtime.ts", - "src/plugin-sdk/qa-runtime.ts" - ], - "sdk": true - }, { "name": "killProcessTree", "files": [ diff --git a/scripts/lib/wrapper-shadowing-baseline.json b/scripts/lib/wrapper-shadowing-baseline.json index 152fbe01230c..61cddbfdcc59 100644 --- a/scripts/lib/wrapper-shadowing-baseline.json +++ b/scripts/lib/wrapper-shadowing-baseline.json @@ -59,17 +59,6 @@ "wrapped": "src/shared/progress-labels.ts", "wrapper": "src/channels/streaming.ts" }, - { - "name": "drainPendingDeliveries", - "wrapped": "src/infra/outbound/delivery-queue-recovery.ts", - "wrapper": "src/plugin-sdk/delivery-queue-runtime.ts", - "via": "src/infra/outbound/delivery-queue.ts" - }, - { - "name": "formatSkillsForPrompt", - "wrapped": "src/skills/loading/skill-contract.ts", - "wrapper": "src/skills/loading/session.ts" - }, { "name": "getActivePluginRegistryWorkspaceDirFromState", "wrapped": "src/plugins/runtime-workspace-state.ts", diff --git a/src/acp/persistent-bindings.lifecycle.ts b/src/acp/persistent-bindings.lifecycle.ts index 26e5540a6fd1..9b82704b51ff 100644 --- a/src/acp/persistent-bindings.lifecycle.ts +++ b/src/acp/persistent-bindings.lifecycle.ts @@ -129,7 +129,7 @@ export async function ensureConfiguredAcpBindingSession(params: { } /** Resolves a configured binding for a conversation and ensures its ACP session exists. */ -export async function ensureConfiguredAcpBindingReady(params: { +export async function ensureConfiguredAcpBindingReadyCore(params: { cfg: OpenClawConfig; configuredBinding: ResolvedConfiguredAcpBinding | null; }): Promise<{ ok: true } | { ok: false; error: string }> { diff --git a/src/acp/translator.ts b/src/acp/translator.ts index bba8394158b4..34929a4f2c2d 100644 --- a/src/acp/translator.ts +++ b/src/acp/translator.ts @@ -29,7 +29,7 @@ import type { AcpServerOptions } from "@openclaw/acp-core/types"; import type { EventFrame } from "../../packages/gateway-protocol/src/index.js"; import type { GatewayClient } from "../gateway/client.js"; import { - createFixedWindowRateLimiter, + createFixedWindowBudget, resolveFixedWindowRateLimitInteger, } from "../infra/fixed-window-rate-limit.js"; import { createLazyRuntimeModule } from "../shared/lazy-runtime.js"; @@ -89,7 +89,7 @@ export class AcpGatewayAgent implements Agent { this.approvalRelays, this.log, ); - const sessionCreateRateLimiter = createFixedWindowRateLimiter({ + const sessionCreateRateLimiter = createFixedWindowBudget({ maxRequests: resolveFixedWindowRateLimitInteger( opts.sessionCreateRateLimit?.maxRequests, SESSION_CREATE_RATE_LIMIT_DEFAULT_MAX_REQUESTS, diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index 3a2b21048cc9..5053bdde8079 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -53,7 +53,7 @@ const CLI_RUN_QUEUE = new KeyedAsyncQueue(); const CLI_IMAGE_SWEEP_TTL_MS = 7 * 24 * 60 * 60 * 1_000; const sweptCliImageRoots = new Set(); -export function isClaudeCliProvider(providerId: string): boolean { +export function isClaudeCliBackendId(providerId: string): boolean { return normalizeOptionalLowercaseString(providerId) === "claude-cli"; } @@ -73,11 +73,11 @@ export function resolveCliRunQueueKey(params: { ownerKey?: string; }): string { const requiresLiveSessionSerialization = - isClaudeCliProvider(params.backendId) && params.liveSession === "claude-stdio"; + isClaudeCliBackendId(params.backendId) && params.liveSession === "claude-stdio"; if (params.serialize === false && !requiresLiveSessionSerialization) { return `${params.backendId}:${params.runId}`; } - if (isClaudeCliProvider(params.backendId)) { + if (isClaudeCliBackendId(params.backendId)) { const ownerKey = params.ownerKey?.trim(); if (requiresLiveSessionSerialization && ownerKey) { return `${params.backendId}:owner:${ownerKey}`; diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index b79fbd0ad03c..f4696e4e0be0 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -128,7 +128,7 @@ import { resolveBundledCliBackendAuthPolicy, type BundledCliBackendAuthPolicy, } from "./cli-backend-auth-policy.js"; -import { buildCliAgentSystemPrompt, isClaudeCliProvider, normalizeCliModel } from "./helpers.js"; +import { buildCliAgentSystemPrompt, isClaudeCliBackendId, normalizeCliModel } from "./helpers.js"; import { cliBackendLog } from "./log.js"; import { buildCliMcpGrantContext, normalizeOptionalMcpContextValue } from "./mcp-grant-context.js"; import { CLAUDE_CLI_CONTEXT_MODEL_ALIASES, detectNodeClaudePlacement } from "./prepare-claude.js"; @@ -796,7 +796,7 @@ export async function prepareCliRunContext( const promptBuildRestrictsTools = promptBuildToolsAllow !== undefined && !promptBuildToolsAllow.some((toolName) => normalizeToolName(toolName) === "*"); - const isClaudeCli = isClaudeCliProvider(params.provider); + const isClaudeCli = isClaudeCliBackendId(params.provider); const requestedContextModelId = isClaudeCli ? resolveClaudeCliContextModelId(modelId) : modelId; const normalizedContextModelId = isClaudeCli ? resolveClaudeCliContextModelId(normalizedModel) @@ -1390,7 +1390,7 @@ export async function prepareCliRunContext( const hasClaudeCliCandidate = !nodeClaudePlacement && candidateClaudeCliSessionId !== undefined && - isClaudeCliProvider(params.provider); + isClaudeCliBackendId(params.provider); const claudeCliTranscriptMissing = hasClaudeCliCandidate && !(await prepareDeps.claudeCliSessionTranscriptHasContent({ diff --git a/src/agents/embedded-agent-runner/compact.hooks.harness.ts b/src/agents/embedded-agent-runner/compact.hooks.harness.ts index b5dae285ec6c..28e5a4e502a7 100644 --- a/src/agents/embedded-agent-runner/compact.hooks.harness.ts +++ b/src/agents/embedded-agent-runner/compact.hooks.harness.ts @@ -1000,7 +1000,7 @@ export async function loadCompactHooksHarness(): Promise<{ })); vi.doMock("../../plugins/memory-runtime.js", () => ({ - getActiveMemorySearchManager: getMemorySearchManagerMock, + getActiveMemorySearchManagerCore: getMemorySearchManagerMock, })); vi.doMock("../date-time.js", () => ({ diff --git a/src/agents/embedded-agent-runner/compaction-hooks.ts b/src/agents/embedded-agent-runner/compaction-hooks.ts index b00c64453acf..dbaadc998cbe 100644 --- a/src/agents/embedded-agent-runner/compaction-hooks.ts +++ b/src/agents/embedded-agent-runner/compaction-hooks.ts @@ -5,7 +5,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; -import { getActiveMemorySearchManager } from "../../plugins/memory-runtime.js"; +import { getActiveMemorySearchManagerCore } from "../../plugins/memory-runtime.js"; import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js"; import { resolveSessionAgentId } from "../agent-scope.js"; import { resolveMemorySearchConfig } from "../memory-search.js"; @@ -47,7 +47,7 @@ async function runPostCompactionSessionMemorySync(params: { if (!resolvedMemory.sync.sessions.postCompactionForce) { return; } - const { manager } = await getActiveMemorySearchManager({ + const { manager } = await getActiveMemorySearchManagerCore({ cfg: params.config, agentId, }); diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.start.ts b/src/agents/embedded-agent-subscribe.handlers.tools.start.ts index fd35d88c1016..5c28785a1d44 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.start.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.start.ts @@ -29,10 +29,9 @@ import { extractMessagingToolSend, sanitizeToolArgs, } from "./embedded-agent-subscribe.tools.js"; -import { inferToolMetaFromArgs } from "./embedded-agent-utils.js"; import { buildAgentHarnessQuestionPromptPayload } from "./harness/user-input-bridge.js"; import type { AgentEvent } from "./runtime/index.js"; -import { isCommandBearingToolCall } from "./tool-display.js"; +import { inferToolMetaFromArgsCore, isCommandBearingToolCall } from "./tool-display.js"; import { resolveFileMutationToolName } from "./tool-mutation-names.js"; import { buildToolMutationState } from "./tool-mutation.js"; import { normalizeToolName } from "./tool-policy.js"; @@ -439,7 +438,7 @@ export function handleToolExecutionStart( const meta = extendExecMeta( toolName, args, - inferToolMetaFromArgs(toolName, args, { + inferToolMetaFromArgsCore(toolName, args, { detailMode: ctx.params.toolProgressDetail ?? "explain", }), ); diff --git a/src/agents/embedded-agent-utils.ts b/src/agents/embedded-agent-utils.ts index 74f4dcef992c..7c2b89337a00 100644 --- a/src/agents/embedded-agent-utils.ts +++ b/src/agents/embedded-agent-utils.ts @@ -17,7 +17,6 @@ import { import { sanitizeUserFacingText } from "./embedded-agent-helpers/sanitize-user-facing-text.js"; import { renderUserFacingText } from "./embedded-agent-helpers/user-facing-text.js"; import type { AgentMessage } from "./runtime/index.js"; -import { formatToolDetail, resolveToolDisplay } from "./tool-display.js"; export { stripDowngradedToolCallText } from "../shared/text/assistant-visible-text.js"; @@ -409,13 +408,3 @@ export function extractThinkingFromTaggedStream( } return text.slice(state.lastTag.end).trim(); } - -/** Infer compact display metadata for a tool call from its args. */ -export function inferToolMetaFromArgs( - toolName: string, - args: unknown, - options?: { detailMode?: "explain" | "raw" }, -): string | undefined { - const display = resolveToolDisplay({ name: toolName, args, detailMode: options?.detailMode }); - return formatToolDetail(display); -} diff --git a/src/agents/tool-display.ts b/src/agents/tool-display.ts index f70622ff5945..0c42eb7d2052 100644 --- a/src/agents/tool-display.ts +++ b/src/agents/tool-display.ts @@ -98,6 +98,17 @@ export function formatToolDetail(display: ToolDisplay): string | undefined { return formatToolDetailText(detailRaw); } +/** Infers compact display metadata for a tool invocation from its arguments. */ +export function inferToolMetaFromArgsCore( + toolName: string, + args: unknown, + options?: { detailMode?: ToolDetailMode }, +): string | undefined { + return formatToolDetail( + resolveToolDisplay({ name: toolName, args, detailMode: options?.detailMode }), + ); +} + /** * Shell-family tools render their command as the whole line instead of * "Label: detail". Backends spell the same tool differently — the Claude CLI diff --git a/src/auto-reply/envelope.test.ts b/src/auto-reply/envelope.test.ts index 52126a12b088..00fe980b0c6b 100644 --- a/src/auto-reply/envelope.test.ts +++ b/src/auto-reply/envelope.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import { withEnv } from "../test-utils/env.js"; import { formatAgentEnvelope, - formatEnvelopeTimestamp, + formatAgentEnvelopeTimestamp, formatInboundEnvelope, resolveEnvelopeFormatOptions, } from "./envelope.js"; @@ -28,7 +28,7 @@ describe("formatAgentEnvelope", () => { it("formats timestamps in local timezone by default", () => { const ts = Date.UTC(2025, 0, 2, 3, 4); - const expectedTimestamp = formatEnvelopeTimestamp(ts, { timezone: "local" }); + const expectedTimestamp = formatAgentEnvelopeTimestamp(ts, { timezone: "local" }); const body = formatAgentEnvelope({ channel: "WebChat", timestamp: ts, @@ -70,15 +70,15 @@ describe("formatAgentEnvelope", () => { agents: { defaults: { userTimezone: "Not/A_Timezone" } }, }); expect(options.timezone).toBe("local"); - expect(formatEnvelopeTimestamp(ts, options)).toBe( - formatEnvelopeTimestamp(ts, { timezone: "local" }), + expect(formatAgentEnvelopeTimestamp(ts, options)).toBe( + formatAgentEnvelopeTimestamp(ts, { timezone: "local" }), ); }); it("keeps the UTC fallback for an invalid explicit timezone option", () => { const ts = Date.UTC(2025, 0, 2, 3, 4, 5); - expect(formatEnvelopeTimestamp(ts, { timezone: "Not/A_Timezone" })).toBe( - formatEnvelopeTimestamp(ts, { timezone: "utc" }), + expect(formatAgentEnvelopeTimestamp(ts, { timezone: "Not/A_Timezone" })).toBe( + formatAgentEnvelopeTimestamp(ts, { timezone: "utc" }), ); }); diff --git a/src/auto-reply/envelope.ts b/src/auto-reply/envelope.ts index 5d8d8a44617d..56f9c0b24f9a 100644 --- a/src/auto-reply/envelope.ts +++ b/src/auto-reply/envelope.ts @@ -111,7 +111,7 @@ function resolveEnvelopeTimezone(options: NormalizedEnvelopeOptions): ResolvedEn } /** Formats an envelope timestamp using local, UTC, user, or explicit IANA timezone rules. */ -export function formatEnvelopeTimestamp( +export function formatAgentEnvelopeTimestamp( ts: number | Date | undefined, options?: EnvelopeFormatOptions, ): string | undefined { @@ -202,7 +202,7 @@ export function formatAgentEnvelope(params: AgentEnvelopeParams): string { if (ip) { parts.push(sanitizeEnvelopeHeaderPart(ip)); } - const ts = formatEnvelopeTimestamp(params.timestamp, resolved); + const ts = formatAgentEnvelopeTimestamp(params.timestamp, resolved); if (ts) { parts.push(ts); } diff --git a/src/auto-reply/reply/agent-runner-cli-dispatch.ts b/src/auto-reply/reply/agent-runner-cli-dispatch.ts index 78df14cda8e7..0ed351546f1b 100644 --- a/src/auto-reply/reply/agent-runner-cli-dispatch.ts +++ b/src/auto-reply/reply/agent-runner-cli-dispatch.ts @@ -5,7 +5,6 @@ import { runCliAgent } from "../../agents/cli-runner.js"; import type { RunCliAgentParams } from "../../agents/cli-runner/types.js"; import { clearCliSession, getCliSessionBinding } from "../../agents/cli-session.js"; import { extractToolResultText } from "../../agents/embedded-agent-subscribe.tools.js"; -import { inferToolMetaFromArgs } from "../../agents/embedded-agent-utils.js"; import type { EmbeddedAgentRunResult } from "../../agents/embedded-agent.js"; import { DEFAULT_FAST_MODE_AUTO_ON_SECONDS, @@ -18,6 +17,7 @@ import { resolveAgentRunAbortLifecycleFields, resolveAgentRunErrorLifecycleFields, } from "../../agents/run-termination.js"; +import { inferToolMetaFromArgsCore } from "../../agents/tool-display.js"; import { isCommandBearingToolCall } from "../../agents/tool-display.js"; import { normalizeAgentPlanSteps } from "../../channels/streaming.js"; import type { SessionEntry } from "../../config/sessions.js"; @@ -313,7 +313,7 @@ export function createCliToolSummaryTracker(params: { if (payload.phase === "start") { if (payload.toolCallId && payload.name) { toolByCallId.set(payload.toolCallId, { - meta: inferToolMetaFromArgs(payload.name, payload.args, { + meta: inferToolMetaFromArgsCore(payload.name, payload.args, { detailMode: params.detailMode ?? "explain", }), commandBearing: isCommandBearingToolCall(payload.name, payload.args), diff --git a/src/auto-reply/reply/agent-runner-command-output.ts b/src/auto-reply/reply/agent-runner-command-output.ts index 26cbfca5de2a..124dbe11cb7c 100644 --- a/src/auto-reply/reply/agent-runner-command-output.ts +++ b/src/auto-reply/reply/agent-runner-command-output.ts @@ -4,7 +4,7 @@ import { normalizeLowercaseStringOrEmpty, readStringValue, } from "@openclaw/normalization-core/string-coerce"; -import { inferToolMetaFromArgs } from "../../agents/embedded-agent-utils.js"; +import { inferToolMetaFromArgsCore } from "../../agents/tool-display.js"; import type { GetReplyOptions } from "../types.js"; /** @@ -93,7 +93,7 @@ export function buildCommandOutputFromToolResultEvent(evt: { const args = readRecordValue(evt.data.args); const title = readStringValue(evt.data.title) ?? - (args ? inferToolMetaFromArgs(name, args, { detailMode: "explain" }) : undefined); + (args ? inferToolMetaFromArgsCore(name, args, { detailMode: "explain" }) : undefined); return { itemId: readStringValue(evt.data.itemId), phase: "end", diff --git a/src/auto-reply/reply/inbound-meta.ts b/src/auto-reply/reply/inbound-meta.ts index a0597073c326..d7602d578494 100644 --- a/src/auto-reply/reply/inbound-meta.ts +++ b/src/auto-reply/reply/inbound-meta.ts @@ -12,7 +12,7 @@ import type { SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { sliceUtf16Safe, truncateUtf16Safe } from "../../utils.js"; import type { EnvelopeFormatOptions } from "../envelope.js"; -import { formatEnvelopeTimestamp } from "../envelope.js"; +import { formatAgentEnvelopeTimestamp } from "../envelope.js"; import type { TemplateContext } from "../templating.js"; import { formatContextJsonBlock, @@ -501,7 +501,7 @@ function formatConversationTimestamp( if (typeof value !== "number" || !Number.isFinite(value)) { return undefined; } - return formatEnvelopeTimestamp(value, envelope); + return formatAgentEnvelopeTimestamp(value, envelope); } function resolveInboundChannel(ctx: TemplateContext): string | undefined { diff --git a/src/auto-reply/reply/provider-dispatcher.test.ts b/src/auto-reply/reply/provider-dispatcher.test.ts index db8b8e429980..55c931b9e695 100644 --- a/src/auto-reply/reply/provider-dispatcher.test.ts +++ b/src/auto-reply/reply/provider-dispatcher.test.ts @@ -21,7 +21,7 @@ vi.mock("../dispatch.js", () => ({ hoisted.plainDispatchMock(...args), })); -const { dispatchReplyWithBufferedBlockDispatcher, dispatchReplyWithDispatcher } = +const { dispatchReplyWithBufferedBlockDispatcherCore, dispatchReplyWithDispatcherCore } = await import("./provider-dispatcher.js"); const dispatchResult = { @@ -41,7 +41,7 @@ describe("provider dispatcher wrappers", () => { deliver: async () => ({ visibleReplySent: false }), } satisfies ReplyDispatcherWithTypingOptions; - await dispatchReplyWithBufferedBlockDispatcher({ + await dispatchReplyWithBufferedBlockDispatcherCore({ ctx: { Body: "hello" }, cfg: {} as OpenClawConfig, dispatcherOptions, @@ -62,7 +62,7 @@ describe("provider dispatcher wrappers", () => { deliver: async () => ({ visibleReplySent: false }), } satisfies ReplyDispatcherOptions; - await dispatchReplyWithDispatcher({ + await dispatchReplyWithDispatcherCore({ ctx: { Body: "hello" }, cfg: {} as OpenClawConfig, dispatcherOptions, diff --git a/src/auto-reply/reply/provider-dispatcher.ts b/src/auto-reply/reply/provider-dispatcher.ts index 65c5f56cfdcb..519d85899417 100644 --- a/src/auto-reply/reply/provider-dispatcher.ts +++ b/src/auto-reply/reply/provider-dispatcher.ts @@ -14,7 +14,7 @@ export type { } from "./provider-dispatcher.types.js"; /** Dispatch a reply using the buffered block dispatcher path. */ -export const dispatchReplyWithBufferedBlockDispatcher: DispatchReplyWithBufferedBlockDispatcher = +export const dispatchReplyWithBufferedBlockDispatcherCore: DispatchReplyWithBufferedBlockDispatcher = async (params) => { return await dispatchInboundMessageWithBufferedDispatcher({ ctx: params.ctx, @@ -27,7 +27,7 @@ export const dispatchReplyWithBufferedBlockDispatcher: DispatchReplyWithBuffered }; /** Dispatch a reply using the standard dispatcher path. */ -export const dispatchReplyWithDispatcher: DispatchReplyWithDispatcher = async (params) => { +export const dispatchReplyWithDispatcherCore: DispatchReplyWithDispatcher = async (params) => { return await dispatchInboundMessageWithDispatcher({ ctx: params.ctx, cfg: params.cfg, diff --git a/src/channels/chat-meta.ts b/src/channels/chat-meta.ts index d4df46cc9878..548683d25684 100644 --- a/src/channels/chat-meta.ts +++ b/src/channels/chat-meta.ts @@ -4,14 +4,20 @@ * Provides ordered channel metadata for setup, status, and selection surfaces. */ import { expectDefined } from "@openclaw/normalization-core"; +import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js"; import { buildChatChannelMetaById, type ChatChannelMeta } from "./chat-meta-shared.js"; import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js"; -let chatChannelMetaCache: Record | null = null; +let chatChannelMetaCache: + | { cacheKey: string; metaById: Record } + | undefined; function getChatChannelMetaById(): Record { - chatChannelMetaCache ??= buildChatChannelMetaById(); - return chatChannelMetaCache; + const cacheKey = resolveBundledPluginsDir(process.env) ?? ""; + if (chatChannelMetaCache?.cacheKey !== cacheKey) { + chatChannelMetaCache = { cacheKey, metaById: buildChatChannelMetaById() }; + } + return chatChannelMetaCache.metaById; } /** diff --git a/src/channels/plugins/acp-stateful-target-driver.test.ts b/src/channels/plugins/acp-stateful-target-driver.test.ts index c50d8354c0f2..24cf32e21eb1 100644 --- a/src/channels/plugins/acp-stateful-target-driver.test.ts +++ b/src/channels/plugins/acp-stateful-target-driver.test.ts @@ -18,7 +18,7 @@ const resolveMocks = vi.hoisted(() => ({ })); vi.mock("../../acp/persistent-bindings.lifecycle.js", () => ({ - ensureConfiguredAcpBindingReady: vi.fn(), + ensureConfiguredAcpBindingReadyCore: vi.fn(), ensureConfiguredAcpBindingSession: vi.fn(), })); vi.mock("../../gateway/session-reset-service.js", () => ({ diff --git a/src/channels/plugins/acp-stateful-target-driver.ts b/src/channels/plugins/acp-stateful-target-driver.ts index e7b074e8e2c9..a6ab01b0f7ee 100644 --- a/src/channels/plugins/acp-stateful-target-driver.ts +++ b/src/channels/plugins/acp-stateful-target-driver.ts @@ -4,7 +4,7 @@ * Ensures ACP-backed bound sessions exist, are ready, and can be reset by Gateway. */ import { - ensureConfiguredAcpBindingReady, + ensureConfiguredAcpBindingReadyCore, ensureConfiguredAcpBindingSession, } from "../../acp/persistent-bindings.lifecycle.js"; import { resolveConfiguredAcpBindingSpecBySessionKey } from "../../acp/persistent-bindings.resolve.js"; @@ -86,7 +86,7 @@ async function ensureAcpTargetReady(params: { error: "Configured ACP binding unavailable", }; } - return await ensureConfiguredAcpBindingReady({ + return await ensureConfiguredAcpBindingReadyCore({ cfg: params.cfg, configuredBinding: { spec: configuredBinding, diff --git a/src/channels/plugins/config-writes.ts b/src/channels/plugins/config-writes.ts index bc451d7714be..b28c5b2cd601 100644 --- a/src/channels/plugins/config-writes.ts +++ b/src/channels/plugins/config-writes.ts @@ -83,7 +83,7 @@ export function canBypassConfigWritePolicy(params: { */ export function formatConfigWriteDeniedMessage(params: { result: Exclude; - fallbackChannelId?: ChannelId | null; + fallbackChannelId?: string | null; }): string { return formatConfigWriteDeniedMessageShared(params); } diff --git a/src/channels/streaming.test.ts b/src/channels/streaming.test.ts index 907a6b48c312..29f29d9dd6e9 100644 --- a/src/channels/streaming.test.ts +++ b/src/channels/streaming.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { inferToolMetaFromArgs } from "../agents/embedded-agent-utils.js"; +import { inferToolMetaFromArgsCore } from "../agents/tool-display.js"; import { formatToolAggregate } from "../auto-reply/tool-meta.js"; import { buildChannelProgressDraftLine, @@ -187,7 +187,7 @@ describe("backend tool-name casing", () => { }, { commandText: "raw" }, ); - const meta = inferToolMetaFromArgs(name, args, { detailMode: "explain" }); + const meta = inferToolMetaFromArgsCore(name, args, { detailMode: "explain" }); const summaryText = formatToolAggregate(name, meta ? [meta] : undefined, { markdown: true }); const merged = mergeChannelProgressDraftLine( diff --git a/src/channels/turn/durable-delivery.test.ts b/src/channels/turn/durable-delivery.test.ts index d264f541b783..10c9f8673f43 100644 --- a/src/channels/turn/durable-delivery.test.ts +++ b/src/channels/turn/durable-delivery.test.ts @@ -23,7 +23,7 @@ vi.mock("../message/send.js", async (importOriginal) => { }); import type { FinalizedMsgContext } from "../../auto-reply/templating.js"; -import { deliverInboundReplyWithMessageSendContext } from "./durable-delivery.js"; +import { deliverInboundReplyWithMessageSendContextCore } from "./durable-delivery.js"; type SendDurableMessageBatchRequest = { cfg?: unknown; @@ -86,7 +86,7 @@ describe("durable inbound reply delivery", () => { }); it("preserves explicit null thread targets instead of falling back to context thread", async () => { - await deliverInboundReplyWithMessageSendContext({ + await deliverInboundReplyWithMessageSendContextCore({ cfg: {}, channel: "telegram", agentId: "main", @@ -110,7 +110,7 @@ describe("durable inbound reply delivery", () => { }); it("does not require unknown-send reconciliation for the default best-effort final path", async () => { - await deliverInboundReplyWithMessageSendContext({ + await deliverInboundReplyWithMessageSendContextCore({ cfg: {}, channel: "telegram", agentId: "main", @@ -132,7 +132,7 @@ describe("durable inbound reply delivery", () => { }); it("uses required durability when a caller explicitly requires unknown-send reconciliation", async () => { - await deliverInboundReplyWithMessageSendContext({ + await deliverInboundReplyWithMessageSendContextCore({ cfg: {}, channel: "telegram", agentId: "main", @@ -172,7 +172,7 @@ describe("durable inbound reply delivery", () => { sentBeforeError: true, }); - const result = await deliverInboundReplyWithMessageSendContext({ + const result = await deliverInboundReplyWithMessageSendContextCore({ cfg: {}, channel: "telegram", agentId: "main", diff --git a/src/channels/turn/durable-delivery.ts b/src/channels/turn/durable-delivery.ts index 04d6e6303811..07ab92570ce9 100644 --- a/src/channels/turn/durable-delivery.ts +++ b/src/channels/turn/durable-delivery.ts @@ -145,7 +145,7 @@ function markDurableInboundReplyDeliveryErrorVisible(error: unknown): unknown { } /** Delivers final inbound replies through the durable message-send context when supported. */ -export async function deliverInboundReplyWithMessageSendContext( +export async function deliverInboundReplyWithMessageSendContextCore( params: DurableInboundReplyDeliveryParams, ): Promise { if (params.info.kind !== "final") { diff --git a/src/channels/turn/lifecycle.ts b/src/channels/turn/lifecycle.ts index ae485dd9034b..1db061b3d596 100644 --- a/src/channels/turn/lifecycle.ts +++ b/src/channels/turn/lifecycle.ts @@ -31,7 +31,7 @@ import { toCoreManagedDeliveryInfo, } from "./direct-delivery-custody.js"; import { - deliverInboundReplyWithMessageSendContext, + deliverInboundReplyWithMessageSendContextCore, isDurableInboundReplyDeliveryHandled, throwIfDurableInboundReplyDeliveryFailed, } from "./durable-delivery.js"; @@ -546,7 +546,7 @@ async function dispatchChannelTurnWithDeliveryOwner( ? await declaredDurable(preparedPayload, info) : declaredDurable; if (durableOptions) { - const durable = await deliverInboundReplyWithMessageSendContext({ + const durable = await deliverInboundReplyWithMessageSendContextCore({ cfg: params.cfg, channel: params.channel, accountId: params.accountId, diff --git a/src/channels/turn/run-channel-turn.delivery.test.ts b/src/channels/turn/run-channel-turn.delivery.test.ts index a75005b453d3..959870f2906c 100644 --- a/src/channels/turn/run-channel-turn.delivery.test.ts +++ b/src/channels/turn/run-channel-turn.delivery.test.ts @@ -37,7 +37,7 @@ vi.mock("../../auto-reply/reply/provider-dispatcher.js", async (importOriginal) await importOriginal(); return { ...actual, - dispatchReplyWithBufferedBlockDispatcher: dispatchReplyWithBufferedBlockDispatcherCore, + dispatchReplyWithBufferedBlockDispatcherCore, }; }); diff --git a/src/channels/turn/run-channel-turn.finalize.test.ts b/src/channels/turn/run-channel-turn.finalize.test.ts index 895eb43870f2..fcd31bd501a6 100644 --- a/src/channels/turn/run-channel-turn.finalize.test.ts +++ b/src/channels/turn/run-channel-turn.finalize.test.ts @@ -37,7 +37,7 @@ vi.mock("../../auto-reply/reply/provider-dispatcher.js", async (importOriginal) await importOriginal(); return { ...actual, - dispatchReplyWithBufferedBlockDispatcher: dispatchReplyWithBufferedBlockDispatcherCore, + dispatchReplyWithBufferedBlockDispatcherCore, }; }); diff --git a/src/channels/turn/run-channel-turn.pipeline.test.ts b/src/channels/turn/run-channel-turn.pipeline.test.ts index 6736ecdcc31f..6408605c57cc 100644 --- a/src/channels/turn/run-channel-turn.pipeline.test.ts +++ b/src/channels/turn/run-channel-turn.pipeline.test.ts @@ -43,7 +43,7 @@ vi.mock("../../auto-reply/reply/provider-dispatcher.js", async (importOriginal) await importOriginal(); return { ...actual, - dispatchReplyWithBufferedBlockDispatcher: dispatchReplyWithBufferedBlockDispatcherCore, + dispatchReplyWithBufferedBlockDispatcherCore, }; }); diff --git a/src/cli/daemon-cli/lifecycle-core.config-guard.test.ts b/src/cli/daemon-cli/lifecycle-core.config-guard.test.ts index 71a2fafc060d..04236edebde7 100644 --- a/src/cli/daemon-cli/lifecycle-core.config-guard.test.ts +++ b/src/cli/daemon-cli/lifecycle-core.config-guard.test.ts @@ -2,7 +2,7 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { VERSION } from "../../version.js"; import { - defaultRuntime, + lifecycleTestRuntime, resetLifecycleRuntimeLogs, resetLifecycleServiceMocks, service, @@ -30,7 +30,7 @@ const pluginPackagingHintItems = pluginPackagingRecoveryHints.map((text) => ({ })); function expectLatestRuntimeJson(payload: unknown) { - const calls = defaultRuntime.writeJson.mock.calls; + const calls = lifecycleTestRuntime.writeJson.mock.calls; expect(calls[calls.length - 1]?.[0]).toEqual(payload); } @@ -49,7 +49,7 @@ vi.mock("../../config/issue-format.js", () => ({ })); vi.mock("../../runtime.js", () => ({ - defaultRuntime, + defaultRuntime: lifecycleTestRuntime, })); function setConfigSnapshot(params: { diff --git a/src/cli/daemon-cli/lifecycle-core.test.ts b/src/cli/daemon-cli/lifecycle-core.test.ts index 817d4b7b7f01..3c24a6261aaa 100644 --- a/src/cli/daemon-cli/lifecycle-core.test.ts +++ b/src/cli/daemon-cli/lifecycle-core.test.ts @@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../../config/config.js"; import type { GatewayServiceControlArgs } from "../../daemon/service-types.js"; import type { GatewayService } from "../../daemon/service.js"; import { - defaultRuntime, + lifecycleTestRuntime, resetLifecycleRuntimeLogs, resetLifecycleServiceMocks, lifecycleRuntimeLogs, @@ -38,7 +38,7 @@ vi.mock("../../config/config.js", () => ({ })); vi.mock("../../runtime.js", () => ({ - defaultRuntime, + defaultRuntime: lifecycleTestRuntime, })); vi.mock("../../infra/restart-intent.js", () => ({ diff --git a/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts b/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts index a5f156c301e8..61b572d794ef 100644 --- a/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts +++ b/src/cli/daemon-cli/test-helpers/lifecycle-core-harness.ts @@ -20,7 +20,7 @@ type LifecycleServiceHarness = GatewayService & { restart: MockFn; }; -export const defaultRuntime: LifecycleRuntimeHarness = lifecycleRuntimeCapture.defaultRuntime; +export const lifecycleTestRuntime: LifecycleRuntimeHarness = lifecycleRuntimeCapture.defaultRuntime; export const service: LifecycleServiceHarness = { label: "TestService", diff --git a/src/cli/plugins-cli-test-helpers.ts b/src/cli/plugins-cli-test-helpers.ts index bdeed230ff4c..134aff34693c 100644 --- a/src/cli/plugins-cli-test-helpers.ts +++ b/src/cli/plugins-cli-test-helpers.ts @@ -102,7 +102,7 @@ export const installPluginFromGitSpec: Mock = vi.fn( const parseGitPluginSpec: Mock = vi.fn(); const listMarketplacePlugins: Mock = vi.fn(); export const resolveMarketplaceInstallShortcut: Mock = vi.fn(); -export const enablePluginInConfig: UnknownMock = vi.fn(); +export const enablePluginInConfigMock: UnknownMock = vi.fn(); export const recordPluginInstall: UnknownMock = vi.fn(); const loadInstalledPluginIndexInstallRecords: AsyncUnknownMock = vi.fn(async () => clonePluginInstallRecords(mockInstalledPluginIndexInstallRecords), @@ -326,7 +326,7 @@ vi.mock("../plugins/enable.js", () => ({ Parameters<(typeof import("../plugins/enable.js"))["enableExplicitlySelectedPluginInConfig"]>, unknown >( - enablePluginInConfig, + enablePluginInConfigMock, ...args, )) as (typeof import("../plugins/enable.js"))["enableExplicitlySelectedPluginInConfig"], enablePluginInConfig: (( @@ -336,7 +336,7 @@ vi.mock("../plugins/enable.js", () => ({ Parameters<(typeof import("../plugins/enable.js"))["enablePluginInConfig"]>, unknown >( - enablePluginInConfig, + enablePluginInConfigMock, ...args, )) as (typeof import("../plugins/enable.js"))["enablePluginInConfig"], })); @@ -827,7 +827,7 @@ export function resetPluginsCliTestState() { installPluginFromMarketplace.mockReset(); listMarketplacePlugins.mockReset(); resolveMarketplaceInstallShortcut.mockReset(); - enablePluginInConfig.mockReset(); + enablePluginInConfigMock.mockReset(); recordPluginInstall.mockReset(); mockInstalledPluginIndexInstallRecords = {}; mockInstalledPluginIndexRevision = 0; @@ -909,7 +909,7 @@ export function resetPluginsCliTestState() { ok: false, error: "marketplace install failed", }); - enablePluginInConfig.mockImplementation(((cfg: OpenClawConfig, pluginId: string) => ({ + enablePluginInConfigMock.mockImplementation(((cfg: OpenClawConfig, pluginId: string) => ({ config: cfg, enabled: true, pluginId, diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index df768d14b735..6daa0063052e 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -15,7 +15,7 @@ import { applyExclusiveSlotSelection, buildPluginSnapshotReport, clearPluginRegistryLoadCache, - enablePluginInConfig, + enablePluginInConfigMock, findBundledPluginSourceMock, installHooksFromNpmSpec, installHooksFromPath, @@ -218,7 +218,7 @@ function primeNpmPluginFallback(pluginId = "demo") { loadConfig.mockReturnValue(cfg); mockClawHubPackageNotFound(pluginId); installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult(pluginId)); - enablePluginInConfig.mockReturnValue({ config: enabledCfg }); + enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); applyExclusiveSlotSelection.mockReturnValue({ config: enabledCfg, @@ -233,7 +233,7 @@ function primeSuccessfulPluginPersistence(pluginId = "demo") { const enabledCfg = createEnabledPluginConfig(pluginId); loadConfig.mockReturnValue(cfg); - enablePluginInConfig.mockReturnValue({ config: enabledCfg }); + enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); applyExclusiveSlotSelection.mockReturnValue({ config: enabledCfg, @@ -1268,7 +1268,7 @@ describe("plugins cli install", () => { marketplaceSource: "local/repo", marketplacePlugin: "alpha", }); - enablePluginInConfig.mockReturnValue({ config: enabledCfg }); + enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); buildPluginSnapshotReport.mockReturnValue({ plugins: [{ id: "alpha", kind: "provider" }], diagnostics: [], @@ -1748,7 +1748,7 @@ describe("plugins cli install", () => { expect(record.source).toBe("path"); expect(String(record.sourcePath)).toContain(pluginId); expect(String(record.installPath)).toContain(pluginId); - expect(enablePluginInConfig).not.toHaveBeenCalled(); + expect(enablePluginInConfigMock).not.toHaveBeenCalled(); expect(applyExclusiveSlotSelection).not.toHaveBeenCalled(); expect(runtimeLogsContain("requires configuration first")).toBe(true); }); @@ -1782,7 +1782,7 @@ describe("plugins cli install", () => { ); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(enablePluginInConfig).not.toHaveBeenCalled(); + expect(enablePluginInConfigMock).not.toHaveBeenCalled(); }); it("enables config-gated bundled installs when provider-backed config is explicit", async () => { @@ -1814,11 +1814,11 @@ describe("plugins cli install", () => { }, requiresConfig: true, }); - enablePluginInConfig.mockReturnValue({ config: enabledCfg }); + enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); await runPluginsCommand(["plugins", "install", pluginId]); - expect(enablePluginInConfig).toHaveBeenCalledTimes(1); + expect(enablePluginInConfigMock).toHaveBeenCalledTimes(1); expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg); expect(runtimeLogsContain("requires configuration first")).toBe(false); }); @@ -2357,7 +2357,7 @@ describe("plugins cli install", () => { version: "1.2.3", extensions: ["./dist/index.js"], }); - enablePluginInConfig.mockReturnValue({ config: enabledCfg }); + enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); applyExclusiveSlotSelection.mockReturnValue({ config: enabledCfg, @@ -2562,7 +2562,7 @@ describe("plugins cli install", () => { extensions: [], }; }); - enablePluginInConfig.mockReturnValue({ config: enabledCfg }); + enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); applyExclusiveSlotSelection.mockReturnValue({ config: enabledCfg, diff --git a/src/cli/plugins-cli.policy.test.ts b/src/cli/plugins-cli.policy.test.ts index e9360ab2cd30..d897a00d78ad 100644 --- a/src/cli/plugins-cli.policy.test.ts +++ b/src/cli/plugins-cli.policy.test.ts @@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { buildPluginRegistrySnapshotReport, - enablePluginInConfig, + enablePluginInConfigMock, loadConfig, replaceConfigFile, refreshPluginRegistry, @@ -74,7 +74,7 @@ describe("plugins cli policy mutations", () => { }, } as OpenClawConfig; loadConfig.mockReturnValue(sourceConfig); - enablePluginInConfig.mockReturnValue({ + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig, enabled: true, pluginId: "alpha", @@ -83,7 +83,7 @@ describe("plugins cli policy mutations", () => { await runPluginsCommand(["plugins", "enable", "alpha"]); - expect(enablePluginInConfig).toHaveBeenCalledWith(sourceConfig, "alpha", { + expect(enablePluginInConfigMock).toHaveBeenCalledWith(sourceConfig, "alpha", { updateChannelConfig: false, }); expect(replaceConfigFile).toHaveBeenCalledWith({ @@ -121,7 +121,7 @@ describe("plugins cli policy mutations", () => { ])("fails without mutations when $policy blocks enablement", async ({ plugins, reason }) => { const sourceConfig = { plugins } as OpenClawConfig; loadConfig.mockReturnValue(sourceConfig); - enablePluginInConfig.mockReturnValue({ + enablePluginInConfigMock.mockReturnValue({ config: sourceConfig, enabled: false, pluginId: "alpha", @@ -153,7 +153,7 @@ describe("plugins cli policy mutations", () => { } } - expect(enablePluginInConfig).not.toHaveBeenCalled(); + expect(enablePluginInConfigMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); }); @@ -199,7 +199,7 @@ describe("plugins cli policy mutations", () => { }, } as OpenClawConfig; loadConfig.mockReturnValue(sourceConfig); - enablePluginInConfig.mockReturnValue({ + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig, enabled: true, pluginId, @@ -208,7 +208,7 @@ describe("plugins cli policy mutations", () => { await runPluginsCommand(["plugins", "enable", alias]); - expect(enablePluginInConfig).toHaveBeenCalledWith(sourceConfig, pluginId, { + expect(enablePluginInConfigMock).toHaveBeenCalledWith(sourceConfig, pluginId, { updateChannelConfig: false, }); expect(replaceConfigFile).toHaveBeenCalledWith({ @@ -262,7 +262,7 @@ describe("plugins cli policy mutations", () => { expect(runtimeErrors).toContain( "Plugin not found: missing-plugin. Run `openclaw plugins list` to see installed plugins, or `openclaw plugins search missing-plugin` to look for installable plugins.", ); - expect(enablePluginInConfig).not.toHaveBeenCalled(); + expect(enablePluginInConfigMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(refreshPluginRegistry).not.toHaveBeenCalled(); }, diff --git a/src/cli/run-main.exit.test.ts b/src/cli/run-main.exit.test.ts index 0f5c356dd204..24aab109ab32 100644 --- a/src/cli/run-main.exit.test.ts +++ b/src/cli/run-main.exit.test.ts @@ -311,7 +311,7 @@ vi.mock("../infra/runtime-guard.js", () => ({ })); vi.mock("../plugins/memory-runtime.js", () => ({ - closeActiveMemorySearchManagers: closeActiveMemorySearchManagersMock, + closeActiveMemorySearchManagersCore: closeActiveMemorySearchManagersMock, })); vi.mock("../plugins/memory-state.js", () => ({ diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 1f2d81959db1..085dbee58547 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -302,8 +302,9 @@ async function closeCliResources(): Promise { async () => { const { hasMemoryRuntime } = await import("../plugins/memory-state.js"); if (hasMemoryRuntime()) { - const { closeActiveMemorySearchManagers } = await import("../plugins/memory-runtime.js"); - await closeActiveMemorySearchManagers(); + const { closeActiveMemorySearchManagersCore } = + await import("../plugins/memory-runtime.js"); + await closeActiveMemorySearchManagersCore(); } }, ]; diff --git a/src/commands/doctor-memory-search.test.ts b/src/commands/doctor-memory-search.test.ts index 871f10272151..b899dcb99232 100644 --- a/src/commands/doctor-memory-search.test.ts +++ b/src/commands/doctor-memory-search.test.ts @@ -21,7 +21,7 @@ const resolveApiKeyForProvider = vi.hoisted(() => vi.fn()); const hasAnyAuthProfileStoreSource = vi.hoisted(() => vi.fn(() => true)); const hasAuthProfileStoreSourceForProvider = vi.hoisted(() => vi.fn(() => true)); const isConfiguredAwsSdkAuthProfileForProvider = vi.hoisted(() => vi.fn(() => false)); -const getActiveMemorySearchManager = vi.hoisted(() => vi.fn()); +const getActiveMemorySearchManagerCore = vi.hoisted(() => vi.fn()); const resolveActiveMemoryBackendConfig = vi.hoisted(() => vi.fn()); const auditDreamingArtifacts = vi.hoisted(() => vi.fn()); const auditShortTermPromotionArtifacts = vi.hoisted(() => vi.fn()); @@ -58,7 +58,7 @@ vi.mock("../agents/auth-profiles.js", () => ({ })); vi.mock("../plugins/memory-runtime.js", () => ({ - getActiveMemorySearchManager, + getActiveMemorySearchManagerCore, resolveActiveMemoryBackendConfig, })); @@ -302,10 +302,10 @@ describe("noteMemorySearchHealth", () => { hasAuthProfileStoreSourceForProvider.mockReturnValue(true); isConfiguredAwsSdkAuthProfileForProvider.mockReset(); isConfiguredAwsSdkAuthProfileForProvider.mockReturnValue(false); - getActiveMemorySearchManager.mockReset(); + getActiveMemorySearchManagerCore.mockReset(); resolveActiveMemoryBackendConfig.mockReset(); resolveActiveMemoryBackendConfig.mockReturnValue({ backend: "builtin" }); - getActiveMemorySearchManager.mockResolvedValue({ + getActiveMemorySearchManagerCore.mockResolvedValue({ manager: { status: () => ({ workspaceDir: "/tmp/agent-default/workspace", backend: "builtin" }), close: vi.fn(async () => {}), @@ -1094,7 +1094,7 @@ describe("memory recall doctor integration", () => { ); resetMemoryRecallMocks(); resolveActiveMemoryBackendConfig.mockReturnValue({ backend: "builtin" }); - getActiveMemorySearchManager.mockResolvedValue({ + getActiveMemorySearchManagerCore.mockResolvedValue({ manager: { status: () => ({ workspaceDir: "/tmp/agent-default/workspace", backend: "builtin" }), close: vi.fn(async () => {}), @@ -1262,12 +1262,12 @@ describe("memory recall doctor integration", () => { }); it("audits and repairs each agent with isolated managers and paths", async () => { - getActiveMemorySearchManager.mockClear(); + getActiveMemorySearchManagerCore.mockClear(); listAgentIds.mockReturnValue(["agent-default", "secondary"]); resolveAgentDir.mockImplementation((_cfg, agentId) => `/tmp/${agentId}`); resolveAgentWorkspaceDir.mockImplementation((_cfg, agentId) => `/tmp/${agentId}/workspace`); const closes = new Map>(); - getActiveMemorySearchManager.mockImplementation(async ({ agentId }) => { + getActiveMemorySearchManagerCore.mockImplementation(async ({ agentId }) => { const close = vi.fn(async () => {}); closes.set(agentId, close); return { @@ -1305,7 +1305,7 @@ describe("memory recall doctor integration", () => { await maybeRepairMemoryRecallHealth({ cfg, prompter }); - expect(getActiveMemorySearchManager).toHaveBeenCalledTimes(2); + expect(getActiveMemorySearchManagerCore).toHaveBeenCalledTimes(2); expect(closes.get("agent-default")).toHaveBeenCalledOnce(); expect(closes.get("secondary")).toHaveBeenCalledOnce(); expect(repairShortTermPromotionArtifacts).toHaveBeenCalledTimes(1); diff --git a/src/commands/doctor-memory-search.ts b/src/commands/doctor-memory-search.ts index d502b2d8424c..61383a0631ad 100644 --- a/src/commands/doctor-memory-search.ts +++ b/src/commands/doctor-memory-search.ts @@ -43,7 +43,7 @@ import { } from "../plugin-sdk/memory-core-bundled-runtime.js"; import { normalizePluginsConfig } from "../plugins/config-state.js"; import { - getActiveMemorySearchManager, + getActiveMemorySearchManagerCore, resolveActiveMemoryBackendConfig, } from "../plugins/memory-runtime.js"; import { defaultSlotIdForKey } from "../plugins/slots.js"; @@ -258,7 +258,7 @@ async function resolveRuntimeMemoryAuditContext( cfg: OpenClawConfig, agentId: string, ): Promise { - const result = await getActiveMemorySearchManager({ + const result = await getActiveMemorySearchManagerCore({ cfg, agentId, purpose: "status", diff --git a/src/commands/status.scan.deps.runtime.ts b/src/commands/status.scan.deps.runtime.ts index b760853824ae..9f5f714566b9 100644 --- a/src/commands/status.scan.deps.runtime.ts +++ b/src/commands/status.scan.deps.runtime.ts @@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { getTailnetHostname } from "../infra/tailscale.js"; import type { MemoryProviderStatus } from "../memory-host-sdk/engine-storage.js"; -import { getActiveMemorySearchManager } from "../plugins/memory-runtime.js"; +import { getActiveMemorySearchManagerCore } from "../plugins/memory-runtime.js"; export { getTailnetHostname }; @@ -21,7 +21,7 @@ export async function getMemorySearchManager(params: { agentId: string; purpose: "status"; }): Promise<{ manager: StatusMemoryManager | null }> { - const { manager } = await getActiveMemorySearchManager(params); + const { manager } = await getActiveMemorySearchManagerCore(params); if (!manager) { return { manager: null }; } diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index 6d0acef549d1..7798c1bc5102 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -533,7 +533,7 @@ vi.mock("../channels/config-presence.js", () => ({ })); vi.mock("../plugins/memory-runtime.js", () => ({ - getActiveMemorySearchManager: vi.fn(async ({ agentId }: { agentId: string }) => ({ + getActiveMemorySearchManagerCore: vi.fn(async ({ agentId }: { agentId: string }) => ({ manager: { probeVectorAvailability: vi.fn(async () => true), status: () => ({ diff --git a/src/gateway/server-methods/doctor.test.ts b/src/gateway/server-methods/doctor.test.ts index 4186d60087c7..620201d85391 100644 --- a/src/gateway/server-methods/doctor.test.ts +++ b/src/gateway/server-methods/doctor.test.ts @@ -54,7 +54,7 @@ vi.mock("../../agents/memory-search.js", () => ({ })); vi.mock("../../plugins/memory-runtime.js", () => ({ - getActiveMemorySearchManager: getMemorySearchManager, + getActiveMemorySearchManagerCore: getMemorySearchManager, })); vi.mock("./doctor.memory-core-runtime.js", () => ({ diff --git a/src/gateway/server-methods/doctor.ts b/src/gateway/server-methods/doctor.ts index 36f7e40e2a33..b5ce13630069 100644 --- a/src/gateway/server-methods/doctor.ts +++ b/src/gateway/server-methods/doctor.ts @@ -18,7 +18,7 @@ import { resolveMemoryDreamingWorkspaces, resolveMemoryRemDreamingConfig, } from "../../memory-host-sdk/dreaming.js"; -import { getActiveMemorySearchManager } from "../../plugins/memory-runtime.js"; +import { getActiveMemorySearchManagerCore } from "../../plugins/memory-runtime.js"; import { normalizeAgentId } from "../../routing/session-key.js"; import { formatError } from "../server-utils.js"; import { @@ -707,7 +707,7 @@ export const doctorHandlers: GatewayRequestHandlers = { return; } const { cfg, agentId, requestedAgentId } = resolved; - const { manager, error } = await getActiveMemorySearchManager({ + const { manager, error } = await getActiveMemorySearchManagerCore({ cfg, agentId, purpose: "status", diff --git a/src/gateway/server-methods/memory-search.test.ts b/src/gateway/server-methods/memory-search.test.ts index 0fcf43d17940..f4019d3c6fd1 100644 --- a/src/gateway/server-methods/memory-search.test.ts +++ b/src/gateway/server-methods/memory-search.test.ts @@ -8,10 +8,10 @@ import { } from "../../test-utils/openclaw-test-state.js"; import type { GatewayRequestContext, RespondFn } from "./types.js"; -const getActiveMemorySearchManager = vi.hoisted(() => vi.fn()); +const getActiveMemorySearchManagerCore = vi.hoisted(() => vi.fn()); const resolveDefaultAgentId = vi.hoisted(() => vi.fn(() => "main")); -vi.mock("../../plugins/memory-runtime.js", () => ({ getActiveMemorySearchManager })); +vi.mock("../../plugins/memory-runtime.js", () => ({ getActiveMemorySearchManagerCore })); vi.mock("../../agents/agent-scope.js", async (importOriginal) => ({ ...(await importOriginal()), resolveDefaultAgentId, @@ -71,7 +71,7 @@ describe("memory.search gateway method", () => { label: "gateway-memory-search", layout: "state-only", }); - getActiveMemorySearchManager.mockReset(); + getActiveMemorySearchManagerCore.mockReset(); resolveDefaultAgentId.mockClear(); }); @@ -93,7 +93,7 @@ describe("memory.search gateway method", () => { }), ); } - expect(getActiveMemorySearchManager).not.toHaveBeenCalled(); + expect(getActiveMemorySearchManagerCore).not.toHaveBeenCalled(); }); it.each([ @@ -102,7 +102,7 @@ describe("memory.search gateway method", () => { ])("clamps maxResults=$requested to $expected", async ({ requested, expected }) => { const cfg = createConfig(testState.workspaceDir); const manager = createStubManager(); - getActiveMemorySearchManager.mockResolvedValue({ manager }); + getActiveMemorySearchManagerCore.mockResolvedValue({ manager }); await invokeMemorySearch({ query: "lantern", maxResults: requested, minScore: 0.42 }, cfg); @@ -126,7 +126,7 @@ describe("memory.search gateway method", () => { message: "unknown agentId", }), ); - expect(getActiveMemorySearchManager).not.toHaveBeenCalled(); + expect(getActiveMemorySearchManagerCore).not.toHaveBeenCalled(); }); it("rejects a non-string agentId without acquiring a manager", async () => { @@ -142,7 +142,7 @@ describe("memory.search gateway method", () => { message: "agentId must be a string", }), ); - expect(getActiveMemorySearchManager).not.toHaveBeenCalled(); + expect(getActiveMemorySearchManagerCore).not.toHaveBeenCalled(); }); it.each([" ", "---", "ſ"])( @@ -161,7 +161,7 @@ describe("memory.search gateway method", () => { }), ); expect(resolveDefaultAgentId).not.toHaveBeenCalled(); - expect(getActiveMemorySearchManager).not.toHaveBeenCalled(); + expect(getActiveMemorySearchManagerCore).not.toHaveBeenCalled(); }, ); @@ -184,11 +184,11 @@ describe("memory.search gateway method", () => { }; const manager = createStubManager(); manager.search.mockResolvedValue([result]); - getActiveMemorySearchManager.mockResolvedValue({ manager }); + getActiveMemorySearchManagerCore.mockResolvedValue({ manager }); const respond = await invokeMemorySearch({ query: "lantern", agentId: requested }, cfg); - expect(getActiveMemorySearchManager).toHaveBeenCalledWith({ + expect(getActiveMemorySearchManagerCore).toHaveBeenCalledWith({ cfg, agentId: configured, purpose: "cli", @@ -208,7 +208,7 @@ describe("memory.search gateway method", () => { it("returns unavailable when no memory manager is configured", async () => { const cfg: OpenClawConfig = {}; - getActiveMemorySearchManager.mockResolvedValue({ + getActiveMemorySearchManagerCore.mockResolvedValue({ manager: null, error: "memory plugin unavailable", }); @@ -235,7 +235,7 @@ describe("memory.search gateway method", () => { dirty: true, custom: { searchMode: "fts-only" }, }); - getActiveMemorySearchManager.mockResolvedValue({ manager }); + getActiveMemorySearchManagerCore.mockResolvedValue({ manager }); const respond = await invokeMemorySearch({ query: "hidden codeword" }, cfg); diff --git a/src/gateway/server-methods/memory-search.ts b/src/gateway/server-methods/memory-search.ts index c96f772d7e9d..52037c1bb204 100644 --- a/src/gateway/server-methods/memory-search.ts +++ b/src/gateway/server-methods/memory-search.ts @@ -7,7 +7,7 @@ import type { MemorySearchResult, } from "../../memory-host-sdk/host/types.js"; import { resolveMemorySearchStaleness } from "../../memory-host-sdk/host/types.js"; -import { getActiveMemorySearchManager } from "../../plugins/memory-runtime.js"; +import { getActiveMemorySearchManagerCore } from "../../plugins/memory-runtime.js"; import { normalizeAgentId } from "../../routing/session-key.js"; import type { GatewayRequestHandlers } from "./types.js"; @@ -108,11 +108,11 @@ export const memorySearchHandlers: GatewayRequestHandlers = { return; } const agentId = requestedAgentId ?? resolveDefaultAgentId(cfg); - let acquired: Awaited>; + let acquired: Awaited>; try { // Use the transient CLI lifecycle so request cleanup cannot close a shared manager. // manager.search owns the same lazy/on-search sync behavior as the existing CLI path. - acquired = await getActiveMemorySearchManager({ + acquired = await getActiveMemorySearchManagerCore({ cfg, agentId, purpose: "cli", diff --git a/src/gateway/server-restart-sentinel-notice.ts b/src/gateway/server-restart-sentinel-notice.ts index ccab2a95cf2f..beefd3c96385 100644 --- a/src/gateway/server-restart-sentinel-notice.ts +++ b/src/gateway/server-restart-sentinel-notice.ts @@ -25,7 +25,7 @@ import { } from "../infra/outbound/delivery-queue-storage.js"; import { ackDelivery, - drainPendingDeliveries, + drainPendingDeliveriesCore, failDelivery, failDeliveryAfterPlatformSend, failDeliveryBeforePlatformSend, @@ -188,7 +188,7 @@ async function drainFailedRestartSentinelNotice(params: { if (attemptCount < RESTART_NOTICE_MAX_ATTEMPTS) { await waitForRecoveryDrain(); } - await drainPendingDeliveries({ + await drainPendingDeliveriesCore({ drainKey: `restart-recovery:${params.queueId}`, logLabel: `${params.summary}: restart notice recovery`, cfg: params.cfg, diff --git a/src/gateway/server-restart-sentinel.test.ts b/src/gateway/server-restart-sentinel.test.ts index 1234d37ac074..5702cc10224c 100644 --- a/src/gateway/server-restart-sentinel.test.ts +++ b/src/gateway/server-restart-sentinel.test.ts @@ -309,7 +309,7 @@ vi.mock("../infra/outbound/delivery-queue.js", () => ({ failDelivery: mocks.failDelivery, failDeliveryAfterPlatformSend: mocks.failDeliveryAfterPlatformSend, failDeliveryBeforePlatformSend: mocks.failDeliveryBeforePlatformSend, - drainPendingDeliveries: mocks.drainPendingDeliveries, + drainPendingDeliveriesCore: mocks.drainPendingDeliveries, withActiveDeliveryClaim: mocks.withActiveDeliveryClaim, })); diff --git a/src/gateway/server-restart-sentinel.ts b/src/gateway/server-restart-sentinel.ts index db7e675a13e1..cafe7cadcd62 100644 --- a/src/gateway/server-restart-sentinel.ts +++ b/src/gateway/server-restart-sentinel.ts @@ -7,7 +7,7 @@ import { } from "../agents/subagents/completion/subagent-completion-delivery.js"; import { REPLY_RUN_STILL_SHUTTING_DOWN_TEXT } from "../auto-reply/reply/get-reply-run-queue.js"; import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js"; -import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js"; +import { dispatchReplyWithBufferedBlockDispatcherCore } from "../auto-reply/reply/provider-dispatcher.js"; import type { ChatType } from "../channels/chat-type.js"; import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.js"; import { recordInboundSession } from "../channels/session.js"; @@ -297,7 +297,7 @@ export async function deliverQueuedSessionDelivery(params: { storePath, ctxPayload, recordInboundSession, - dispatchReplyWithBufferedBlockDispatcher, + dispatchReplyWithBufferedBlockDispatcher: dispatchReplyWithBufferedBlockDispatcherCore, replyOptions: { sourceReplyDeliveryMode: "message_tool_only", }, diff --git a/src/gateway/server-runtime-services.test.ts b/src/gateway/server-runtime-services.test.ts index ed75691a351f..173e37f0ba57 100644 --- a/src/gateway/server-runtime-services.test.ts +++ b/src/gateway/server-runtime-services.test.ts @@ -19,7 +19,7 @@ function waitForFast( type StartSessionDeliveryRuntime = typeof import("../infra/session-delivery-queue-runtime.js").startSessionDeliveryRuntime; type DrainPendingDeliveries = - typeof import("../infra/outbound/delivery-queue.js").drainPendingDeliveries; + typeof import("../infra/outbound/delivery-queue.js").drainPendingDeliveriesCore; type RecoverPendingDeliveries = typeof import("../infra/outbound/delivery-queue.js").recoverPendingDeliveries; @@ -82,7 +82,7 @@ vi.mock("../infra/outbound/deliver.js", () => ({ vi.mock("../infra/outbound/delivery-queue.js", () => ({ recoverPendingDeliveries: hoisted.recoverPendingDeliveries, - drainPendingDeliveries: hoisted.drainPendingDeliveries, + drainPendingDeliveriesCore: hoisted.drainPendingDeliveries, })); vi.mock("../infra/session-delivery-queue-runtime.js", () => ({ diff --git a/src/gateway/server-runtime-services.ts b/src/gateway/server-runtime-services.ts index 6d34c432e659..edc009681d47 100644 --- a/src/gateway/server-runtime-services.ts +++ b/src/gateway/server-runtime-services.ts @@ -201,7 +201,7 @@ function startPendingOutboundDeliveryRecovery(params: { if (stopped) { return; } - const { drainPendingDeliveries, recoverPendingDeliveries } = + const { drainPendingDeliveriesCore, recoverPendingDeliveries } = await import("../infra/outbound/delivery-queue.js"); const { deliverOutboundPayloadsInternal } = await import("../infra/outbound/deliver.js"); if (stopped) { @@ -218,7 +218,7 @@ function startPendingOutboundDeliveryRecovery(params: { } // Startup migration runs once. Normal retries use fresh config so revoked // accounts cannot inherit the authority captured at gateway startup. - await drainPendingDeliveries({ + await drainPendingDeliveriesCore({ drainKey: "gateway:outbound", logLabel: "Outbound delivery retry", cfg: getRuntimeConfig(), diff --git a/src/gateway/server.chat.gateway-server-chat-b.test.ts b/src/gateway/server.chat.gateway-server-chat-b.test.ts index 7304be716022..3c98f8cc1e4b 100644 --- a/src/gateway/server.chat.gateway-server-chat-b.test.ts +++ b/src/gateway/server.chat.gateway-server-chat-b.test.ts @@ -56,7 +56,7 @@ import { connectOk, createGatewaySuiteHarness, dispatchInboundMessageMock, - getReplyFromConfig, + gatewayReplyMock, installGatewayTestHooks, mockGetReplyFromConfigOnce, onceMessage, @@ -4794,7 +4794,7 @@ describe("gateway server chat", () => { test("chat.send does not force-disable block streaming", async () => { await withGatewayChatHarness(async ({ ws, createSessionDir }) => { - const spy = getReplyFromConfig; + const spy = gatewayReplyMock; await connectOk(ws); await createSessionDir(); @@ -4837,7 +4837,7 @@ describe("gateway server chat", () => { try { await withGatewayChatHarness( async ({ ws, createSessionDir }) => { - const spy = getReplyFromConfig; + const spy = gatewayReplyMock; await connectOk(ws, makeGatewayWebchatClient()); await createSessionDir(); @@ -4966,7 +4966,7 @@ describe("gateway server chat", () => { test("chat.send forwards Control UI reconnect resume internally", async () => { await withGatewayChatHarness( async ({ ws, createSessionDir }) => { - const spy = getReplyFromConfig; + const spy = gatewayReplyMock; await connectOk(ws, makeGatewayWebchatClient()); await createSessionDir(); @@ -5005,7 +5005,7 @@ describe("gateway server chat", () => { test("chat.send forwards one-turn queue mode overrides internally", async () => { await withGatewayChatHarness( async ({ ws, createSessionDir }) => { - const spy = getReplyFromConfig; + const spy = gatewayReplyMock; await connectOk(ws, makeGatewayWebchatClient()); await createSessionDir(); @@ -6386,7 +6386,7 @@ describe("gateway server chat", () => { test("smoke: supports abort and idempotent completion", async () => { await withGatewayChatHarness(async ({ ws, createSessionDir }) => { - const spy = getReplyFromConfig; + const spy = gatewayReplyMock; let aborted = false; await connectOk(ws); diff --git a/src/gateway/server.plugin-frame-auth.test.ts b/src/gateway/server.plugin-frame-auth.test.ts index 82c7eeab082c..ef64da4a4069 100644 --- a/src/gateway/server.plugin-frame-auth.test.ts +++ b/src/gateway/server.plugin-frame-auth.test.ts @@ -17,7 +17,7 @@ import { dispatchRequest, withGatewayServer, } from "./server-http.test-harness.js"; -import { createTestRegistry } from "./server/__tests__/test-utils.js"; +import { createGatewayTestRegistry } from "./server/__tests__/test-utils.js"; import { createGatewayPluginRequestHandler } from "./server/plugins-http.js"; import { resolveSharedGatewaySessionGeneration } from "./server/ws-shared-generation.js"; @@ -61,7 +61,7 @@ function createRuntimeScopeRecorderHandler(params: { match?: "exact" | "prefix"; }) { return createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ { pluginId: params.pluginId, @@ -196,7 +196,7 @@ describe("control ui plugin frame auth route boundaries", () => { test("does not broaden an exact-route grant to child paths", async () => { const childHandler = vi.fn(async () => true); const handlePluginRequest = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ { pluginId: "exact-plugin", @@ -242,7 +242,7 @@ describe("control ui plugin frame auth route boundaries", () => { const outerHandler = vi.fn(async () => true); const adminHandler = vi.fn(async () => true); const handlePluginRequest = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ { pluginId: "same-plugin", @@ -488,7 +488,7 @@ describe("control ui plugin frame auth route boundaries", () => { const outerHandler = vi.fn(async () => true); const nestedHandler = vi.fn(async () => true); const handlePluginRequest = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ { pluginId: "outer-plugin", @@ -588,7 +588,7 @@ describe("control ui plugin frame auth route boundaries", () => { const nestedHandler = vi.fn(async () => true); const outerHandler = vi.fn(async () => true); const handlePluginRequest = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ { pluginId: "outer-plugin", @@ -653,7 +653,7 @@ describe("control ui plugin frame auth route boundaries", () => { const nestedHandler = vi.fn(async () => false); const outerHandler = vi.fn(async () => true); const handlePluginRequest = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ { pluginId: "nested-plugin", diff --git a/src/gateway/server.plugin-http-auth.test.ts b/src/gateway/server.plugin-http-auth.test.ts index 57b8974ab674..d184cf388e11 100644 --- a/src/gateway/server.plugin-http-auth.test.ts +++ b/src/gateway/server.plugin-http-auth.test.ts @@ -20,7 +20,7 @@ import { withGatewayServer, withGatewayTempConfig, } from "./server-http.test-harness.js"; -import { createTestRegistry } from "./server/__tests__/test-utils.js"; +import { createGatewayTestRegistry } from "./server/__tests__/test-utils.js"; import { createGatewayPluginRequestHandler } from "./server/plugins-http.js"; import { withTempConfig } from "./test-temp-config.js"; @@ -119,7 +119,7 @@ function createRuntimeScopeRecorderHandler(params: { match?: "exact" | "prefix"; }) { return createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ { pluginId: params.pluginId, diff --git a/src/gateway/server/__tests__/test-utils.ts b/src/gateway/server/__tests__/test-utils.ts index ac25c7363cc3..2346b690e2b8 100644 --- a/src/gateway/server/__tests__/test-utils.ts +++ b/src/gateway/server/__tests__/test-utils.ts @@ -5,7 +5,9 @@ import type { PluginRegistry } from "../../../plugins/registry.js"; /** * Shared plugin-registry fixtures for gateway server tests. */ -export const createTestRegistry = (overrides: Partial = {}): PluginRegistry => { +export const createGatewayTestRegistry = ( + overrides: Partial = {}, +): PluginRegistry => { const registry = createEmptyPluginRegistry(); for (const key of Object.keys(overrides) as Array) { const value = overrides[key]; diff --git a/src/gateway/server/plugins-http.runtime-scopes.test.ts b/src/gateway/server/plugins-http.runtime-scopes.test.ts index 788b9e192ed0..c0183c28d791 100644 --- a/src/gateway/server/plugins-http.runtime-scopes.test.ts +++ b/src/gateway/server/plugins-http.runtime-scopes.test.ts @@ -13,7 +13,7 @@ import { authorizeOperatorScopesForMethod, CLI_DEFAULT_OPERATOR_SCOPES } from ". import { isApprovalRecordVisibleToClient } from "../server-methods/approval-shared.js"; import type { GatewayRequestContext } from "../server-methods/types.js"; import { makeMockHttpResponse } from "../test-http-response.js"; -import { createTestRegistry } from "./__tests__/test-utils.js"; +import { createGatewayTestRegistry } from "./__tests__/test-utils.js"; import { createGatewayPluginRequestHandler } from "./plugins-http.js"; const SECURE_HOOK_PATH = "/secure-hook"; @@ -80,11 +80,11 @@ function assertAdminHelperAllowed() { function createPluginRequestHandler(params: { routes: PluginHttpRoute[]; log?: SubsystemLogger; - getRouteRegistry?: () => ReturnType; + getRouteRegistry?: () => ReturnType; getGatewayRequestContext?: () => GatewayRequestContext; }) { return createGatewayPluginRequestHandler({ - registry: createTestRegistry({ httpRoutes: params.routes }), + registry: createGatewayTestRegistry({ httpRoutes: params.routes }), ...(params.getRouteRegistry ? { getRouteRegistry: params.getRouteRegistry } : {}), log: params.log ?? createMockLogger(), ...(params.getGatewayRequestContext @@ -247,7 +247,7 @@ describe("plugin HTTP route runtime scopes", () => { const serverAContext = { label: "server-a" } as unknown as GatewayRequestContext; const serverBContext = { label: "server-b" } as unknown as GatewayRequestContext; const observed: Array<{ route: string; context?: GatewayRequestContext }> = []; - const serverARegistry = createTestRegistry({ + const serverARegistry = createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: SECURE_HOOK_PATH, @@ -260,7 +260,7 @@ describe("plugin HTTP route runtime scopes", () => { }), ], }); - const serverBRegistry = createTestRegistry({ + const serverBRegistry = createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: SECURE_HOOK_PATH, @@ -418,7 +418,7 @@ describe("plugin HTTP route runtime scopes", () => { const observed: Array<{ route: "exact" | "prefix"; scopes: string[] }> = []; const log = createMockLogger(); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/secure/admin-hook", @@ -487,7 +487,7 @@ describe("plugin HTTP route runtime scopes", () => { async ({ auth, gatewayAuthSatisfied, gatewayRequestOperatorScopes, path, expectedScopes }) => { let observedScopes: string[] | undefined; const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path, diff --git a/src/gateway/server/plugins-http.suspension-admission.test.ts b/src/gateway/server/plugins-http.suspension-admission.test.ts index 30757f0420ab..1dfc89325b71 100644 --- a/src/gateway/server/plugins-http.suspension-admission.test.ts +++ b/src/gateway/server/plugins-http.suspension-admission.test.ts @@ -16,7 +16,7 @@ import { } from "../../process/gateway-work-admission.js"; import type { GatewayRequestContext } from "../server-methods/types.js"; import { makeMockHttpResponse } from "../test-http-response.js"; -import { createTestRegistry } from "./__tests__/test-utils.js"; +import { createGatewayTestRegistry } from "./__tests__/test-utils.js"; import { createGatewayPluginRequestHandler, createGatewayPluginUpgradeHandler, @@ -99,7 +99,7 @@ function createRequestHandler( getGatewayRequestContext?: () => GatewayRequestContext, ) { return createGatewayPluginRequestHandler({ - registry: createTestRegistry({ httpRoutes: routes }), + registry: createGatewayTestRegistry({ httpRoutes: routes }), log: createLog(), ...(getGatewayRequestContext ? { getGatewayRequestContext } : {}), }); @@ -107,7 +107,7 @@ function createRequestHandler( function createUpgradeHandler(routes: PluginHttpRouteRegistration[]) { return createGatewayPluginUpgradeHandler({ - registry: createTestRegistry({ httpRoutes: routes }), + registry: createGatewayTestRegistry({ httpRoutes: routes }), log: createLog(), }); } diff --git a/src/gateway/server/plugins-http.test.ts b/src/gateway/server/plugins-http.test.ts index 7375d5113b43..9dfdb086faf3 100644 --- a/src/gateway/server/plugins-http.test.ts +++ b/src/gateway/server/plugins-http.test.ts @@ -6,7 +6,7 @@ import { createEmptyPluginRegistry } from "../../plugins/registry.js"; import { setActivePluginRegistry } from "../../plugins/runtime.js"; import { getPluginRuntimeGatewayRequestScope } from "../../plugins/runtime/gateway-request-scope.js"; import { makeMockHttpResponse } from "../test-http-response.js"; -import { createTestRegistry } from "./__tests__/test-utils.js"; +import { createGatewayTestRegistry } from "./__tests__/test-utils.js"; import { createGatewayPluginUpgradeHandler, createGatewayPluginRequestHandler, @@ -72,7 +72,7 @@ function createSecurePluginRouteHandler(params: { prefixGatewayHandler: () => boolean | Promise; }) { return createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/plugin/secure/report", @@ -123,7 +123,7 @@ async function invokeRouteAndCollectRuntimeScopes(params: { }) { let observedScopes: string[] | undefined; const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: params.path, @@ -150,7 +150,7 @@ async function invokeRouteAndCollectRuntimeScopes(params: { async function invokeCanvasGatewayUpgrade(params: { gatewayAuthSatisfied: boolean }) { const routeUpgradeHandler = vi.fn(async () => true); const handler = createGatewayPluginUpgradeHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: CANVAS_WS_PATH, @@ -208,7 +208,7 @@ describe("createGatewayPluginRequestHandler", () => { it("returns false when no routes are registered", async () => { const log = createPluginLog(); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry(), + registry: createGatewayTestRegistry(), log, }); const { res } = makeMockHttpResponse(); @@ -221,7 +221,7 @@ describe("createGatewayPluginRequestHandler", () => { res.statusCode = 200; }); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [createRoute({ path: "/demo", handler: routeHandler })], }), log: createPluginLog(), @@ -239,7 +239,7 @@ describe("createGatewayPluginRequestHandler", () => { }); const prefixHandler = vi.fn(async () => true); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/api", match: "prefix", handler: prefixHandler }), createRoute({ path: "/api/demo", match: "exact", handler: exactHandler }), @@ -259,7 +259,7 @@ describe("createGatewayPluginRequestHandler", () => { const first = vi.fn(async () => false); const second = vi.fn(async () => true); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/hook", match: "exact", handler: first }), createRoute({ path: "/hook", match: "prefix", handler: second }), @@ -288,7 +288,7 @@ describe("createGatewayPluginRequestHandler", () => { const warn = vi.fn(); const log = { warn } as unknown as PluginHandlerLog; const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [createRoute({ path: "/webhooks/sms", auth: "gateway" })], }), log, @@ -337,7 +337,7 @@ describe("createGatewayPluginRequestHandler", () => { res.statusCode = 200; }); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [createRoute({ path: "/api/demo", handler: routeHandler })], }), log: createPluginLog(), @@ -354,7 +354,7 @@ describe("createGatewayPluginRequestHandler", () => { res.statusCode = 200; return true; }); - const explicitRegistry = createTestRegistry({ + const explicitRegistry = createGatewayTestRegistry({ httpRoutes: [createRoute({ path: "/demo", auth: "plugin", handler: explicitRouteHandler })], }); @@ -372,7 +372,7 @@ describe("createGatewayPluginRequestHandler", () => { it("logs and responds with 500 when a route throws", async () => { const log = createPluginLog(); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/boom", @@ -397,7 +397,7 @@ describe("createGatewayPluginRequestHandler", () => { it("ends a plugin route response when the route throws after sending headers", async () => { const log = createPluginLog(); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/partial", @@ -477,7 +477,7 @@ describe("createGatewayPluginRequestHandler", () => { async ({ setContentLength }) => { const log = createPluginLog(); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/incomplete", @@ -522,7 +522,7 @@ describe("createGatewayPluginRequestHandler", () => { it("does not end a response the plugin already destroyed before throwing", async () => { const log = createPluginLog(); const handler = createGatewayPluginRequestHandler({ - registry: createTestRegistry({ + registry: createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/destroyed", @@ -581,7 +581,7 @@ describe("plugin HTTP route auth checks", () => { const decodeOverflowPublicPath = `/googlechat${buildRepeatedEncodedSlash(40)}public`; it("detects registered route paths", () => { - const registry = createTestRegistry({ + const registry = createGatewayTestRegistry({ httpRoutes: [createRoute({ path: "/demo" })], }); expect(isRegisteredPluginHttpRoutePath(registry, "/demo")).toBe(true); @@ -589,7 +589,7 @@ describe("plugin HTTP route auth checks", () => { }); it("matches canonicalized variants of registered route paths", () => { - const registry = createTestRegistry({ + const registry = createGatewayTestRegistry({ httpRoutes: [createRoute({ path: "/api/demo" })], }); expect(isRegisteredPluginHttpRoutePath(registry, "/api//demo")).toBe(true); @@ -598,7 +598,7 @@ describe("plugin HTTP route auth checks", () => { }); it("enforces auth for protected and gateway-auth routes", () => { - const registry = createTestRegistry({ + const registry = createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/googlechat", match: "prefix", auth: "plugin" }), createRoute({ path: "/api/demo", auth: "gateway" }), @@ -613,7 +613,7 @@ describe("plugin HTTP route auth checks", () => { }); it("enforces auth when any overlapping matched route requires gateway auth", () => { - const registry = createTestRegistry({ + const registry = createGatewayTestRegistry({ httpRoutes: [ createRoute({ path: "/plugin/secure/report", match: "exact", auth: "plugin" }), createRoute({ path: "/plugin/secure", match: "prefix", auth: "gateway" }), diff --git a/src/gateway/test-helpers.runtime-state.ts b/src/gateway/test-helpers.runtime-state.ts index e5809d7df148..9e0e8ed3f78e 100644 --- a/src/gateway/test-helpers.runtime-state.ts +++ b/src/gateway/test-helpers.runtime-state.ts @@ -160,9 +160,9 @@ export const agentDiscoveryMock = gatewayTestHoisted.agentDiscoveryMock; export const cronIsolatedRun = gatewayTestHoisted.cronIsolatedRun; export const agentCommand = gatewayTestHoisted.agentCommand; export const dispatchInboundMessageMock = gatewayTestHoisted.dispatchInboundMessage; -export const getReplyFromConfig = gatewayTestHoisted.getReplyFromConfig; +export const gatewayReplyMock = gatewayTestHoisted.getReplyFromConfig; export const mockGetReplyFromConfigOnce = (impl: GetReplyFromConfigFn) => { - getReplyFromConfig.mockImplementationOnce(impl); + gatewayReplyMock.mockImplementationOnce(impl); }; export const sendWhatsAppMock = gatewayTestHoisted.sendWhatsAppMock; export const testState = gatewayTestHoisted.testState; diff --git a/src/gateway/test-helpers.server.ts b/src/gateway/test-helpers.server.ts index 5d338504342c..02793b98c175 100644 --- a/src/gateway/test-helpers.server.ts +++ b/src/gateway/test-helpers.server.ts @@ -66,7 +66,7 @@ import { agentCommand, cronIsolatedRun, embeddedRunMock, - getReplyFromConfig, + gatewayReplyMock, agentDiscoveryMock, sendWhatsAppMock, setTestConfigRoot, @@ -443,8 +443,8 @@ async function resetGatewayTestState(options: { uniqueConfigRoot: boolean }) { cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "ok" }); agentCommand.mockReset(); agentCommand.mockResolvedValue(undefined); - getReplyFromConfig.mockReset(); - getReplyFromConfig.mockResolvedValue(undefined); + gatewayReplyMock.mockReset(); + gatewayReplyMock.mockResolvedValue(undefined); sendWhatsAppMock.mockReset(); sendWhatsAppMock.mockResolvedValue({ messageId: "msg-1", toJid: "jid-1" }); embeddedRunMock.activeIds.clear(); @@ -540,8 +540,8 @@ async function resetGatewayTestRuntimeOnly() { cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "ok" }); agentCommand.mockReset(); agentCommand.mockResolvedValue(undefined); - getReplyFromConfig.mockReset(); - getReplyFromConfig.mockResolvedValue(undefined); + gatewayReplyMock.mockReset(); + gatewayReplyMock.mockResolvedValue(undefined); sendWhatsAppMock.mockReset(); sendWhatsAppMock.mockResolvedValue({ messageId: "msg-1", toJid: "jid-1" }); embeddedRunMock.activeIds.clear(); diff --git a/src/gateway/test-helpers.ts b/src/gateway/test-helpers.ts index 9e137e9aa9b2..7c3f73544f09 100644 --- a/src/gateway/test-helpers.ts +++ b/src/gateway/test-helpers.ts @@ -6,7 +6,7 @@ export { cronIsolatedRun, dispatchInboundMessageMock, embeddedRunMock, - getReplyFromConfig, + gatewayReplyMock, mockGetReplyFromConfigOnce, agentDiscoveryMock, testState, diff --git a/src/infra/fixed-window-rate-limit.test.ts b/src/infra/fixed-window-rate-limit.test.ts index 9a11af8dda8c..172af0ed094e 100644 --- a/src/infra/fixed-window-rate-limit.test.ts +++ b/src/infra/fixed-window-rate-limit.test.ts @@ -1,6 +1,6 @@ // Covers fixed-window rate limiter boundaries. import { describe, expect, it } from "vitest"; -import { createFixedWindowRateLimiter } from "./fixed-window-rate-limit.js"; +import { createFixedWindowBudget } from "./fixed-window-rate-limit.js"; function expectConsumeResult( result: { allowed: boolean; remaining: number; retryAfterMs: number }, @@ -14,7 +14,7 @@ function expectConsumeResult( describe("fixed-window rate limiter", () => { it("blocks after max requests until window reset", () => { let nowMs = 1_000; - const limiter = createFixedWindowRateLimiter({ + const limiter = createFixedWindowBudget({ maxRequests: 2, windowMs: 1_000, now: () => nowMs, @@ -30,7 +30,7 @@ describe("fixed-window rate limiter", () => { it("clamps maxRequests and windowMs to at least one", () => { let nowMs = 100; - const limiter = createFixedWindowRateLimiter({ + const limiter = createFixedWindowBudget({ maxRequests: 0.2, windowMs: 0.4, now: () => nowMs, @@ -45,7 +45,7 @@ describe("fixed-window rate limiter", () => { it("falls back to minimums for non-finite required values", () => { let nowMs = 100; - const limiter = createFixedWindowRateLimiter({ + const limiter = createFixedWindowBudget({ maxRequests: Number.NaN, windowMs: Number.POSITIVE_INFINITY, now: () => nowMs, @@ -60,7 +60,7 @@ describe("fixed-window rate limiter", () => { it("reports the remaining retry window after later blocked attempts", () => { let nowMs = 1_000; - const limiter = createFixedWindowRateLimiter({ + const limiter = createFixedWindowBudget({ maxRequests: 1, windowMs: 1_000, now: () => nowMs, @@ -73,7 +73,7 @@ describe("fixed-window rate limiter", () => { }); it("supports explicit reset", () => { - const limiter = createFixedWindowRateLimiter({ + const limiter = createFixedWindowBudget({ maxRequests: 1, windowMs: 10_000, }); diff --git a/src/infra/fixed-window-rate-limit.ts b/src/infra/fixed-window-rate-limit.ts index f5333e088a25..b42c30c22e2e 100644 --- a/src/infra/fixed-window-rate-limit.ts +++ b/src/infra/fixed-window-rate-limit.ts @@ -30,7 +30,7 @@ export function resolveFixedWindowRateLimitInteger( } /** Creates a fixed-window counter that reports allowance, remaining quota, and retry delay. */ -export function createFixedWindowRateLimiter(params: { +export function createFixedWindowBudget(params: { /** Maximum successful consume calls allowed per window. */ maxRequests: number; /** Fixed window duration in milliseconds. */ diff --git a/src/infra/outbound/deliver.queue-integration.test-support.ts b/src/infra/outbound/deliver.queue-integration.test-support.ts index 8f73fe3a9213..6b0c16e40174 100644 --- a/src/infra/outbound/deliver.queue-integration.test-support.ts +++ b/src/infra/outbound/deliver.queue-integration.test-support.ts @@ -1,6 +1,6 @@ import type { ChannelOutboundAdapter } from "../../channels/plugins/types.public.js"; import type { OpenClawConfig } from "../../config/config.js"; -import { drainPendingDeliveries, type DeliverFn } from "./delivery-queue.js"; +import { drainPendingDeliveriesCore, type DeliverFn } from "./delivery-queue.js"; import { createRecoveryLog } from "./delivery-queue.test-helpers.js"; export const boundedCronCompletionRetention = { @@ -55,7 +55,7 @@ export async function drainMatrixReconnect(opts: { deliver: DeliverFn; stateDir: string; }): Promise { - await drainPendingDeliveries({ + await drainPendingDeliveriesCore({ drainKey: "matrix:reconnect-test", logLabel: "Matrix reconnect drain", cfg: {} as OpenClawConfig, diff --git a/src/infra/outbound/delivery-queue-media-directive-durability.test.ts b/src/infra/outbound/delivery-queue-media-directive-durability.test.ts index 9cd4de656546..137657d49c26 100644 --- a/src/infra/outbound/delivery-queue-media-directive-durability.test.ts +++ b/src/infra/outbound/delivery-queue-media-directive-durability.test.ts @@ -13,7 +13,7 @@ import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/c import { PlatformMessageNotDispatchedError } from "./deliver-types.js"; import { collectEntrySpoolPaths } from "./delivery-queue-media-spool.js"; import { loadPendingDeliveries } from "./delivery-queue-storage.js"; -import { drainPendingDeliveries, type DeliverFn } from "./delivery-queue.js"; +import { drainPendingDeliveriesCore, type DeliverFn } from "./delivery-queue.js"; import { createRecoveryLog, installDeliveryQueueTmpDirHooks, @@ -151,7 +151,7 @@ describe("delivery-queue MEDIA-directive durability (end-to-end)", () => { const recovered: RecoveredSend[] = []; installMatrixAdapter(recoveryPhaseAdapter(recovered, spoolRoot)); const deliver = vi.fn(async (params) => deliverOutboundPayloads(params)); - await drainPendingDeliveries({ + await drainPendingDeliveriesCore({ drainKey: "media-directive-test", logLabel: "media-directive drain", cfg, diff --git a/src/infra/outbound/delivery-queue-recovery.ts b/src/infra/outbound/delivery-queue-recovery.ts index b723c8303c83..dc655357b1cb 100644 --- a/src/infra/outbound/delivery-queue-recovery.ts +++ b/src/infra/outbound/delivery-queue-recovery.ts @@ -1213,7 +1213,7 @@ async function drainQueuedEntry(opts: { } } -export async function drainPendingDeliveries(opts: { +export async function drainPendingDeliveriesCore(opts: { drainKey: string; logLabel: string; cfg: OpenClawConfig; diff --git a/src/infra/outbound/delivery-queue.reconnect-drain.test.ts b/src/infra/outbound/delivery-queue.reconnect-drain.test.ts index d8cc84680077..0eca96bc9092 100644 --- a/src/infra/outbound/delivery-queue.reconnect-drain.test.ts +++ b/src/infra/outbound/delivery-queue.reconnect-drain.test.ts @@ -14,7 +14,7 @@ import { } from "./delivery-queue-storage.js"; import { type DeliverFn, - drainPendingDeliveries, + drainPendingDeliveriesCore, enqueueDelivery, failDelivery, type RecoveryLogger, @@ -89,7 +89,7 @@ async function drainDirectChatReconnectPending(opts: { stateDir: string; }) { const normalizedAccountId = normalizeReconnectAccountIdForTest(opts.accountId); - await drainPendingDeliveries({ + await drainPendingDeliveriesCore({ drainKey: `directchat:${normalizedAccountId}`, logLabel: "DirectChat reconnect drain", cfg: stubCfg, @@ -143,7 +143,7 @@ async function enqueueFailedDirectChatDelivery(params: { return id; } -describe("drainPendingDeliveries for reconnect", () => { +describe("drainPendingDeliveriesCore for reconnect", () => { let tmpDir: string; const fixtures = installDeliveryQueueTmpDirHooks(); @@ -200,7 +200,7 @@ describe("drainPendingDeliveries for reconnect", () => { { channel: entry.channel, messageId: `${entry.channel}-delivered` }, ]); const drain = () => - drainPendingDeliveries({ + drainPendingDeliveriesCore({ drainKey: "gateway:outbound", logLabel: "Outbound delivery retry", cfg: stubCfg, @@ -266,7 +266,7 @@ describe("drainPendingDeliveries for reconnect", () => { }); const deliver = vi.fn(async () => []); - await drainPendingDeliveries({ + await drainPendingDeliveriesCore({ drainKey: "gateway:outbound", logLabel: "Outbound delivery retry", cfg, @@ -644,7 +644,7 @@ describe("drainPendingDeliveries for reconnect", () => { const id = await enqueueFailedDirectChatDelivery({ accountId: "acct1", stateDir: tmpDir }); let mutated = false; - await drainPendingDeliveries({ + await drainPendingDeliveriesCore({ drainKey: "directchat:acct1", logLabel: "DirectChat reconnect drain", cfg: stubCfg, diff --git a/src/infra/outbound/delivery-queue.ts b/src/infra/outbound/delivery-queue.ts index 38d4feea3b3a..0aa8f01c976e 100644 --- a/src/infra/outbound/delivery-queue.ts +++ b/src/infra/outbound/delivery-queue.ts @@ -15,7 +15,7 @@ export { } from "./delivery-queue-platform-lease.js"; export type { QueuedReplyPayloadSendingHook } from "./delivery-queue-storage.js"; export { - drainPendingDeliveries, + drainPendingDeliveriesCore, recoverPendingDeliveries, withActiveDeliveryClaim, } from "./delivery-queue-recovery.js"; diff --git a/src/infra/state-migrations.doctor.ts b/src/infra/state-migrations.doctor.ts index 9e3b8f49fd51..8a08cde36c0c 100644 --- a/src/infra/state-migrations.doctor.ts +++ b/src/infra/state-migrations.doctor.ts @@ -74,7 +74,7 @@ import { } from "./state-migrations.exec-approvals.js"; import { existsDir, - fileExists, + migrationFileExists, readSessionStoreJson5, safeReadDir, } from "./state-migrations.fs.js"; @@ -289,7 +289,7 @@ async function detectManagedWorktreeStateMigration(params: { }): Promise { const rawRoot = path.join(params.stateDir, "worktrees"); const stateEnv = { ...params.env, OPENCLAW_STATE_DIR: params.stateDir }; - const databaseExists = fileExists(resolveOpenClawStateSqlitePath(stateEnv)); + const databaseExists = migrationFileExists(resolveOpenClawStateSqlitePath(stateEnv)); const hasCurrentSchema = params.stateSchemaMigrations.length === 0; const hasLegacy = params.doctorOnlyStateMigrations === true && @@ -388,10 +388,10 @@ export async function detectLegacyStateMigrations(params: { const { preserveForeignMainAliases } = sessionStoreOwnership; const legacySessionEntries = safeReadDir(sessionsLegacyDir); const hasLegacySessions = - fileExists(sessionsLegacyStorePath) || + migrationFileExists(sessionsLegacyStorePath) || legacySessionEntries.some((e) => e.isFile() && e.name.endsWith(".jsonl")); - const targetSessionParsed = fileExists(sessionsTargetStorePath) + const targetSessionParsed = migrationFileExists(sessionsTargetStorePath) ? readSessionStoreJson5(sessionsTargetStorePath) : { store: {}, ok: true }; const legacySessionSurfaces = params.legacySessionSurfaces; @@ -423,13 +423,13 @@ export async function detectLegacyStateMigrations(params: { const targetAgentDir = path.join(stateDir, "agents", targetAgentId, "agent"); const hasLegacyAgentDir = existsDir(legacyAgentDir); const pluginStateSidecarPath = resolveLegacyPluginStateSidecarPath(stateDir); - const hasPluginStateSidecar = fileExists(pluginStateSidecarPath); + const hasPluginStateSidecar = migrationFileExists(pluginStateSidecarPath); const hasPendingPluginStateSidecarArchive = hasPendingSqliteSidecarArchive( pluginStateSidecarPath, PLUGIN_STATE_SQLITE_SIDECAR_SUFFIXES, ); const pluginInstallIndexPath = resolveLegacyInstalledPluginIndexStorePath({ stateDir }); - const hasPluginInstallIndex = fileExists(pluginInstallIndexPath); + const hasPluginInstallIndex = migrationFileExists(pluginInstallIndexPath); const debugProxyCaptureSidecar = detectLegacyDebugProxyCaptureSidecar(stateDir, env); const stateSchemaMigrations = detectOpenClawStateDatabaseSchemaMigrations({ env: { ...env, OPENCLAW_STATE_DIR: stateDir }, @@ -451,8 +451,8 @@ export async function detectLegacyStateMigrations(params: { TASK_STATE_SQLITE_SIDECAR_SUFFIXES, ); const hasTaskStateSidecars = - fileExists(taskRunsSidecarPath) || - fileExists(flowRunsSidecarPath) || + migrationFileExists(taskRunsSidecarPath) || + migrationFileExists(flowRunsSidecarPath) || hasPendingTaskRunsSidecarArchive || hasPendingFlowRunsSidecarArchive; const deliveryQueuePaths = { @@ -468,25 +468,28 @@ export async function detectLegacyStateMigrations(params: { triggersPath: resolveLegacyVoiceWakeTriggersPath(stateDir), routingPath: resolveLegacyVoiceWakeRoutingPath(stateDir), }; - const hasVoiceWake = fileExists(voiceWake.triggersPath) || fileExists(voiceWake.routingPath); + const hasVoiceWake = + migrationFileExists(voiceWake.triggersPath) || migrationFileExists(voiceWake.routingPath); const updateCheck = { sourcePath: resolveLegacyUpdateCheckPath(stateDir), }; - const hasUpdateCheck = fileExists(updateCheck.sourcePath); + const hasUpdateCheck = migrationFileExists(updateCheck.sourcePath); const configHealth = { sourcePath: resolveLegacyConfigHealthPath(stateDir), }; - const hasConfigHealth = fileExists(configHealth.sourcePath); + const hasConfigHealth = migrationFileExists(configHealth.sourcePath); const pluginBindingApprovals = { sourcePath: resolveLegacyPluginBindingApprovalsPath(env, homedir), }; const hasPluginBindingApprovals = path.resolve(path.dirname(pluginBindingApprovals.sourcePath)) === path.resolve(stateDir) && - fileExists(pluginBindingApprovals.sourcePath); + migrationFileExists(pluginBindingApprovals.sourcePath); const currentConversationBindings = { sourcePath: resolveLegacyCurrentConversationBindingsPath(stateDir), }; - const hasCurrentConversationBindings = fileExists(currentConversationBindings.sourcePath); + const hasCurrentConversationBindings = migrationFileExists( + currentConversationBindings.sourcePath, + ); const detectDoctorOwnedState = ( detect: (options: { stateDir: string; doctorOnlyStateMigrations?: boolean }) => TDetection, ): TDetection => @@ -640,12 +643,12 @@ export async function detectLegacyStateMigrations(params: { `- Managed worktrees: canonicalize ${worktrees.pathRewrites.length} persisted ${worktrees.pathRewrites.length === 1 ? "path" : "paths"} for symlinked state directories`, ); } - if (fileExists(taskRunsSidecarPath)) { + if (migrationFileExists(taskRunsSidecarPath)) { preview.push(`- Task registry sidecar: ${taskRunsSidecarPath} → shared SQLite state`); } else if (hasPendingTaskRunsSidecarArchive) { preview.push(`- Task registry sidecar: finish archive cleanup for ${taskRunsSidecarPath}`); } - if (fileExists(flowRunsSidecarPath)) { + if (migrationFileExists(flowRunsSidecarPath)) { preview.push(`- Task flow sidecar: ${flowRunsSidecarPath} → shared SQLite state`); } else if (hasPendingFlowRunsSidecarArchive) { preview.push(`- Task flow sidecar: finish archive cleanup for ${flowRunsSidecarPath}`); diff --git a/src/infra/state-migrations.fs.test.ts b/src/infra/state-migrations.fs.test.ts index d0843483d0c5..b2c62b5e540a 100644 --- a/src/infra/state-migrations.fs.test.ts +++ b/src/infra/state-migrations.fs.test.ts @@ -6,7 +6,7 @@ import { withTempDir } from "../test-helpers/temp-dir.js"; import { ensureMigrationDir, existsDir, - fileExists, + migrationFileExists, readSessionStoreJson5, safeReadDir, } from "./state-migrations.fs.js"; @@ -33,9 +33,9 @@ describe("state migration fs helpers", () => { fs.writeFileSync(filePath, "{}", "utf8"); fs.mkdirSync(dirPath); - expect(fileExists(filePath)).toBe(true); - expect(fileExists(dirPath)).toBe(false); - expect(fileExists(path.join(base, "missing.json"))).toBe(false); + expect(migrationFileExists(filePath)).toBe(true); + expect(migrationFileExists(dirPath)).toBe(false); + expect(migrationFileExists(path.join(base, "missing.json"))).toBe(false); }); }); diff --git a/src/infra/state-migrations.fs.ts b/src/infra/state-migrations.fs.ts index 39345b54ee39..76e65754d4d7 100644 --- a/src/infra/state-migrations.fs.ts +++ b/src/infra/state-migrations.fs.ts @@ -32,7 +32,7 @@ export function ensureMigrationDir(dir: string) { } /** Returns whether a path exists and resolves to a regular file. */ -export function fileExists(p: string): boolean { +export function migrationFileExists(p: string): boolean { try { return fs.existsSync(p) && fs.statSync(p).isFile(); } catch { diff --git a/src/infra/state-migrations.legacy-sessions.ts b/src/infra/state-migrations.legacy-sessions.ts index 7c26ad78c31c..f1a2a0757bb0 100644 --- a/src/infra/state-migrations.legacy-sessions.ts +++ b/src/infra/state-migrations.legacy-sessions.ts @@ -4,7 +4,7 @@ import type { SessionEntry } from "../config/sessions.js"; import { buildAgentMainSessionKey } from "../routing/session-key.js"; import { ensureMigrationDir, - fileExists, + migrationFileExists, readSessionStoreJson5, safeReadDir, type SessionEntryLike, @@ -71,10 +71,10 @@ export async function migrateLegacySessions( ensureMigrationDir(detected.sessions.targetDir); - const legacyParsed = fileExists(detected.sessions.legacyStorePath) + const legacyParsed = migrationFileExists(detected.sessions.legacyStorePath) ? readSessionStoreJson5(detected.sessions.legacyStorePath) : { store: {}, ok: true }; - const targetParsed = fileExists(detected.sessions.targetStorePath) + const targetParsed = migrationFileExists(detected.sessions.targetStorePath) ? readSessionStoreJson5(detected.sessions.targetStorePath) : { store: {}, ok: true }; const legacyStore = legacyParsed.store; @@ -192,7 +192,7 @@ export async function migrateLegacySessions( ); } - const targetExists = fileExists(detected.sessions.targetStorePath); + const targetExists = migrationFileExists(detected.sessions.targetStorePath); let targetReadable = !targetExists || targetParsed.ok; if (!targetReadable) { if (options.recoverCorruptTargetStore) { @@ -257,7 +257,7 @@ export async function migrateLegacySessions( } const from = path.join(detected.sessions.legacyDir, entry.name); let to = path.join(detected.sessions.targetDir, entry.name); - if (fileExists(to)) { + if (migrationFileExists(to)) { const parsed = path.parse(entry.name); to = path.join(detected.sessions.targetDir, `${parsed.name}.legacy-${now()}${parsed.ext}`); } @@ -271,7 +271,7 @@ export async function migrateLegacySessions( if (legacyParsed.ok && targetReadable) { try { - if (fileExists(detected.sessions.legacyStorePath)) { + if (migrationFileExists(detected.sessions.legacyStorePath)) { fs.rmSync(detected.sessions.legacyStorePath, { force: true }); } } catch { diff --git a/src/infra/state-migrations.plugin-state.ts b/src/infra/state-migrations.plugin-state.ts index 07180759120f..c1c2485fbcc6 100644 --- a/src/infra/state-migrations.plugin-state.ts +++ b/src/infra/state-migrations.plugin-state.ts @@ -20,7 +20,7 @@ import { executeSqliteQueryTakeFirstSync, getNodeSqliteKysely, } from "./kysely-sync.js"; -import { ensureMigrationDir, fileExists } from "./state-migrations.fs.js"; +import { ensureMigrationDir, migrationFileExists } from "./state-migrations.fs.js"; import { PLUGIN_STATE_SQLITE_SIDECAR_SUFFIXES, archiveLegacyImportSource, @@ -45,7 +45,7 @@ export async function migrateLegacyPluginStateSidecar(params: { stateDir: string; }): Promise<{ changes: string[]; warnings: string[] }> { const sourcePath = resolveLegacyPluginStateSidecarPath(params.stateDir); - if (!fileExists(sourcePath)) { + if (!migrationFileExists(sourcePath)) { const changes: string[] = []; const warnings: string[] = []; if (hasPendingSqliteSidecarArchive(sourcePath, PLUGIN_STATE_SQLITE_SIDECAR_SUFFIXES)) { @@ -171,7 +171,7 @@ export async function migrateLegacyInstalledPluginIndex(params: { stateDir: string; }): Promise { const sourcePath = resolveLegacyInstalledPluginIndexStorePath({ stateDir: params.stateDir }); - if (!fileExists(sourcePath)) { + if (!migrationFileExists(sourcePath)) { return { changes: [], warnings: [] }; } @@ -254,7 +254,7 @@ export function preflightLegacyInstalledPluginIndexMigration(params: { return `State dir migration skipped because persisted plugin install records in ${params.stateDir} are invalid`; } const sourcePath = resolveLegacyInstalledPluginIndexStorePath(params); - if (fileExists(sourcePath) && !readLegacyInstalledPluginIndex(sourcePath)) { + if (migrationFileExists(sourcePath) && !readLegacyInstalledPluginIndex(sourcePath)) { return `State dir migration skipped because plugin install index ${sourcePath} is invalid`; } return null; @@ -504,7 +504,11 @@ export async function runLegacyMigrationPlans( cleanupKeys.has(resolvePluginStateImportTargetKey(plan.scopeKey, key)) && !failedTargetKeys.has(resolvePluginStateImportTargetKey(plan.scopeKey, key)), )); - if (allEntriesCovered && plan.cleanupSource === "rename" && fileExists(plan.sourcePath)) { + if ( + allEntriesCovered && + plan.cleanupSource === "rename" && + migrationFileExists(plan.sourcePath) + ) { archiveLegacyImportSource({ sourcePath: plan.sourcePath, label: plan.label, @@ -512,7 +516,11 @@ export async function runLegacyMigrationPlans( warnings, }); } - if (allEntriesCovered && plan.cleanupSource === "remove" && fileExists(plan.sourcePath)) { + if ( + allEntriesCovered && + plan.cleanupSource === "remove" && + migrationFileExists(plan.sourcePath) + ) { try { fs.unlinkSync(plan.sourcePath); changes.push(`Removed ${plan.label} legacy source (${plan.sourcePath})`); @@ -531,7 +539,7 @@ export async function runLegacyMigrationPlans( }); continue; } - if (fileExists(plan.targetPath)) { + if (migrationFileExists(plan.targetPath)) { continue; } try { diff --git a/src/infra/state-migrations.runtime-state.ts b/src/infra/state-migrations.runtime-state.ts index 1198ec76bd5f..3280c284e2e6 100644 --- a/src/infra/state-migrations.runtime-state.ts +++ b/src/infra/state-migrations.runtime-state.ts @@ -13,7 +13,7 @@ import { } from "./kysely-sync.js"; import { normalizeConversationRef } from "./outbound/session-binding-normalization.js"; import type { SessionBindingRecord } from "./outbound/session-binding.types.js"; -import { fileExists } from "./state-migrations.fs.js"; +import { migrationFileExists } from "./state-migrations.fs.js"; import { archiveLegacyImportSource } from "./state-migrations.storage.js"; import type { LegacyStateDetection, MigrationMessages } from "./state-migrations.types.js"; import { normalizeVoiceWakeRoutingConfig } from "./voicewake-routing.js"; @@ -64,7 +64,7 @@ export function migrateLegacyJsonState(params: { }): MigrationMessages { const changes: string[] = []; const warnings: string[] = []; - if (!fileExists(params.sourcePath)) { + if (!migrationFileExists(params.sourcePath)) { return { changes, warnings }; } @@ -413,7 +413,7 @@ function retireLegacyConfigHealthSource(params: { warnings: string[]; }): void { const archivedPath = `${params.sourcePath}.migrated`; - if (!fileExists(archivedPath)) { + if (!migrationFileExists(archivedPath)) { archiveLegacyImportSource({ sourcePath: params.sourcePath, label: "config health state", @@ -621,7 +621,7 @@ export function migrateLegacyPluginBindingApprovals(params: { detected: LegacyStateDetection["pluginBindingApprovals"]; stateDir: string; }): MigrationMessages { - // Detection requires the source to belong to this state root; fileExists + // Detection requires the source to belong to this state root; migrationFileExists // re-checks for races before the import mutates the same trust scope. if (!params.detected.hasLegacy) { return { changes: [], warnings: [] }; diff --git a/src/infra/state-migrations.session-store.ts b/src/infra/state-migrations.session-store.ts index 1fdfa6028827..35ead7184bb1 100644 --- a/src/infra/state-migrations.session-store.ts +++ b/src/infra/state-migrations.session-store.ts @@ -35,7 +35,7 @@ import { expandHomePrefix } from "./home-dir.js"; import { isWithinDir } from "./path-safety.js"; import { existsDir, - fileExists, + migrationFileExists, parseSessionStoreJson5, readSessionStoreJson5, safeReadDir, @@ -407,14 +407,18 @@ export function resolveStaleLegacySessionFile(params: { ? path.resolve(rawSessionFile) : path.resolve(params.legacyDir, rawSessionFile); const relative = path.relative(path.resolve(params.legacyDir), legacySessionFile); - if (relative.startsWith("..") || path.isAbsolute(relative) || fileExists(legacySessionFile)) { + if ( + relative.startsWith("..") || + path.isAbsolute(relative) || + migrationFileExists(legacySessionFile) + ) { return undefined; } const legacyBackupHasTranscript = safeReadDir(path.dirname(params.legacyDir)).some( (dirent) => dirent.isDirectory() && dirent.name.startsWith(`${path.basename(params.legacyDir)}.legacy-`) && - fileExists( + migrationFileExists( path.join(path.dirname(params.legacyDir), dirent.name, path.basename(legacySessionFile)), ), ); @@ -432,7 +436,7 @@ export function resolveStaleLegacySessionFile(params: { return undefined; } const targetSessionFile = path.join(params.targetDir, path.basename(legacySessionFile)); - if (!fileExists(targetSessionFile) || typeof entry.sessionId !== "string") { + if (!migrationFileExists(targetSessionFile) || typeof entry.sessionId !== "string") { return undefined; } const readFirstLine = () => { @@ -678,7 +682,7 @@ export async function migrateOrphanedSessionKeys(params: { // An unknown relationship may have grouped a readable store behind an // inaccessible pathname. Read from a usable alias so the group still gets // the unresolved-identity warning before any rewrite is attempted. - const storePath = [...storePaths].find((candidate) => fileExists(candidate)); + const storePath = [...storePaths].find((candidate) => migrationFileExists(candidate)); if (!storePath) { continue; } @@ -854,7 +858,7 @@ export async function migrateLegacyAcpSessionMetadata(params: { }> = []; for (const target of targets) { - if (!fileExists(target.storePath)) { + if (!migrationFileExists(target.storePath)) { continue; } const group = storeGroups.find(({ target: existing }) => @@ -1048,7 +1052,7 @@ function isManagedLegacySessionStorePathSafe(storePath: string): boolean { if (!agentsDir) { return true; } - if (!fileExists(resolvedStorePath)) { + if (!migrationFileExists(resolvedStorePath)) { return true; } diff --git a/src/infra/state-migrations.storage.ts b/src/infra/state-migrations.storage.ts index 0e14ecb806a1..036fd678f279 100644 --- a/src/infra/state-migrations.storage.ts +++ b/src/infra/state-migrations.storage.ts @@ -20,7 +20,7 @@ import { import { runOpenClawStateWriteTransaction } from "../state/openclaw-state-db.js"; import { openNodeSqliteDatabase } from "./node-sqlite.js"; import { parseRegistryNpmSpec } from "./npm-registry-spec.js"; -import { fileExists, safeReadDir } from "./state-migrations.fs.js"; +import { migrationFileExists, safeReadDir } from "./state-migrations.fs.js"; import { insertTaskDeliveryRowSql, insertTaskRunRowSql, @@ -121,9 +121,9 @@ export function hasPendingSqliteSidecarArchive( suffixes: readonly string[], ): boolean { return ( - !fileExists(sourcePath) && - fileExists(`${sourcePath}.migrated`) && - suffixes.some((suffix) => suffix !== "" && fileExists(`${sourcePath}${suffix}`)) + !migrationFileExists(sourcePath) && + migrationFileExists(`${sourcePath}.migrated`) && + suffixes.some((suffix) => suffix !== "" && migrationFileExists(`${sourcePath}${suffix}`)) ); } @@ -149,7 +149,7 @@ function archiveLegacyFileSource(params: { }): LegacyArchiveResolution | null { const archivedPath = `${params.sourcePath}.migrated`; try { - if (fileExists(archivedPath)) { + if (migrationFileExists(archivedPath)) { // Import has already committed before archival. Identical archive bytes // preserve the same snapshot, so the leftover source can be removed. if (fs.readFileSync(params.sourcePath).equals(fs.readFileSync(archivedPath))) { @@ -190,7 +190,7 @@ function archiveLegacySqliteSidecar(params: { }): void { const existingSources = PLUGIN_STATE_SQLITE_SIDECAR_SUFFIXES.map( (suffix) => `${params.sourcePath}${suffix}`, - ).filter(fileExists); + ).filter(migrationFileExists); if (existingSources.length === 0) { return; } @@ -632,7 +632,7 @@ async function migrateLegacyTaskRunsSidecar(params: { stateDir: string; }): Promise<{ changes: string[]; warnings: string[] }> { const sourcePath = resolveLegacyTaskRunsSidecarPath(params.stateDir); - if (!fileExists(sourcePath)) { + if (!migrationFileExists(sourcePath)) { const changes: string[] = []; const warnings: string[] = []; if (hasPendingSqliteSidecarArchive(sourcePath, TASK_STATE_SQLITE_SIDECAR_SUFFIXES)) { @@ -770,7 +770,7 @@ async function migrateLegacyFlowRunsSidecar(params: { stateDir: string; }): Promise<{ changes: string[]; warnings: string[] }> { const sourcePath = resolveLegacyFlowRunsSidecarPath(params.stateDir); - if (!fileExists(sourcePath)) { + if (!migrationFileExists(sourcePath)) { const changes: string[] = []; const warnings: string[] = []; if (hasPendingSqliteSidecarArchive(sourcePath, TASK_STATE_SQLITE_SIDECAR_SUFFIXES)) { diff --git a/src/media-understanding/fs.ts b/src/media-understanding/fs.ts index 6eea976563e5..fcc7de50623a 100644 --- a/src/media-understanding/fs.ts +++ b/src/media-understanding/fs.ts @@ -2,6 +2,6 @@ import { pathExists } from "../infra/fs-safe.js"; /** Safely checks optional media file paths without throwing on empty input. */ -export async function fileExists(filePath?: string | null): Promise { +export async function optionalPathExists(filePath?: string | null): Promise { return filePath ? await pathExists(filePath) : false; } diff --git a/src/media-understanding/local-audio.ts b/src/media-understanding/local-audio.ts index 5fcc89c3b8dd..9f2607eb9931 100644 --- a/src/media-understanding/local-audio.ts +++ b/src/media-understanding/local-audio.ts @@ -4,7 +4,7 @@ import path from "node:path"; import type { MediaUnderstandingModelConfig } from "../config/types.tools.js"; import { runExec } from "../process/exec.js"; import { getOrCreatePromise } from "../shared/lazy-promise.js"; -import { fileExists } from "./fs.js"; +import { optionalPathExists } from "./fs.js"; type LocalAudioCandidate = { id: "parakeet-mlx" | "whisper-cli" | "sherpa-onnx-offline" | "whisper"; @@ -283,8 +283,9 @@ export async function inspectLocalAudioSelection( const envModel = env.WHISPER_CPP_MODEL?.trim(); const defaultWhisperModel = "/opt/homebrew/share/whisper-cpp/for-tests-ggml-tiny.bin"; - const whisperModel = envModel && (await fileExists(envModel)) ? envModel : defaultWhisperModel; - const whisperReady = Boolean(whisperCommand) && (await fileExists(whisperModel)); + const whisperModel = + envModel && (await optionalPathExists(envModel)) ? envModel : defaultWhisperModel; + const whisperReady = Boolean(whisperCommand) && (await optionalPathExists(whisperModel)); const whisperBackend = whisperCommand ? await inspectWhisperBackend({ command: whisperCommand, @@ -306,7 +307,7 @@ export async function inspectLocalAudioSelection( const sherpaReady = Boolean(sherpaCommand) && sherpaFiles.length === 4 && - (await Promise.all(sherpaFiles.map(fileExists))).every(Boolean); + (await Promise.all(sherpaFiles.map(optionalPathExists))).every(Boolean); const parakeetReady = Boolean(parakeetCommand) && platform === "darwin" && arch === "arm64"; const parakeetArgs = [ "{{AttachmentPath}}", diff --git a/src/plugin-sdk/acp-binding-runtime.ts b/src/plugin-sdk/acp-binding-runtime.ts index 0c3e20dfda5f..d5dddcfef68a 100644 --- a/src/plugin-sdk/acp-binding-runtime.ts +++ b/src/plugin-sdk/acp-binding-runtime.ts @@ -1,5 +1,5 @@ // Deprecated compatibility for released @openclaw/matrix packages. // Remove next major after supported Matrix versions no longer import this lazy lifecycle surface. -export { ensureConfiguredAcpBindingReady } from "../acp/persistent-bindings.lifecycle.js"; +export { ensureConfiguredAcpBindingReadyCore as ensureConfiguredAcpBindingReady } from "../acp/persistent-bindings.lifecycle.js"; export { resolveConfiguredAcpBindingRecord } from "../acp/persistent-bindings.resolve.js"; diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index 8c7c73dff485..8c46dab73f47 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -28,7 +28,7 @@ import { type EmbeddedAgentQueueMessageOptions, } from "../agents/embedded-agent-runner/runs.js"; import type { SandboxFsBridge } from "../agents/sandbox/fs-bridge.js"; -import { formatToolDetail, resolveToolDisplay } from "../agents/tool-display.js"; +import { inferToolMetaFromArgsCore } from "../agents/tool-display.js"; import { buildWatchedSessionsPromptLines, prepareWatchedSessionsPrompt, @@ -537,8 +537,7 @@ export function inferToolMetaFromArgs( args: unknown, options?: { detailMode?: ToolProgressDetailMode }, ): string | undefined { - const display = resolveToolDisplay({ name: toolName, args, detailMode: options?.detailMode }); - return formatToolDetail(display); + return inferToolMetaFromArgsCore(toolName, args, options); } /** diff --git a/src/plugin-sdk/channel-config-helpers.ts b/src/plugin-sdk/channel-config-helpers.ts index a6198c6d6eed..58394e9fd68e 100644 --- a/src/plugin-sdk/channel-config-helpers.ts +++ b/src/plugin-sdk/channel-config-helpers.ts @@ -5,16 +5,12 @@ * export Zod schema builders, while this subpath owns config CRUD adapters, * config-write authorization, and DM access/policy resolution for accounts. */ -import { normalizeOptionalLowercaseString } from "../../packages/normalization-core/src/string-coerce.js"; import { normalizeStringEntries } from "../../packages/normalization-core/src/string-normalization.js"; import { deleteAccountFromConfigSection as deleteAccountFromConfigSectionInSection, setAccountEnabledInConfigSection as setAccountEnabledInConfigSectionInSection, } from "../channels/plugins/config-helpers.js"; import { - authorizeConfigWriteShared, - canBypassConfigWritePolicyShared, - formatConfigWriteDeniedMessageShared, resolveChannelConfigWritesShared, type ConfigWriteAuthorizationResultLike, type ConfigWriteScopeLike, @@ -39,8 +35,6 @@ export { type DmAccessRecord, } from "../channels/plugins/dm-access.js"; -const INTERNAL_MESSAGE_CHANNEL = "webchat"; - /** Origin scope used when authorizing channel config writes. */ export type ConfigWriteScope = ConfigWriteScopeLike; /** Target account/channel for a config write authorization check. */ @@ -80,35 +74,11 @@ export function resolveChannelConfigWrites(params: { return resolveChannelConfigWritesShared(params); } -/** Authorizes a channel config mutation against origin and target policy. */ -export function authorizeConfigWrite(params: { - cfg: OpenClawConfig; - origin?: ConfigWriteScope; - target?: ConfigWriteTarget; - allowBypass?: boolean; -}): ConfigWriteAuthorizationResult { - return authorizeConfigWriteShared(params); -} - -/** Returns true when trusted internal message scopes can bypass config write policy. */ -export function canBypassConfigWritePolicy(params: { - channel?: string | null; - gatewayClientScopes?: string[] | null; -}): boolean { - return canBypassConfigWritePolicyShared({ - ...params, - isInternalMessageChannel: (channel) => - normalizeOptionalLowercaseString(channel) === INTERNAL_MESSAGE_CHANNEL, - }); -} - -/** Formats the denial message shown when config write authorization fails. */ -export function formatConfigWriteDeniedMessage(params: { - result: Exclude; - fallbackChannelId?: string | null; -}): string { - return formatConfigWriteDeniedMessageShared(params); -} +export { + authorizeConfigWrite, + canBypassConfigWritePolicy, + formatConfigWriteDeniedMessage, +} from "../channels/plugins/config-writes.js"; type ChannelConfigAccessorParams = { cfg: Config; diff --git a/src/plugin-sdk/channel-outbound.ts b/src/plugin-sdk/channel-outbound.ts index 0f051da9183b..e5322c3d1d51 100644 --- a/src/plugin-sdk/channel-outbound.ts +++ b/src/plugin-sdk/channel-outbound.ts @@ -172,10 +172,10 @@ export type { } from "../channels/message/index.js"; /** Lazily forwards inbound reply delivery through the channel turn durable-delivery module. */ -export const deliverInboundReplyWithMessageSendContext: ChannelDurableDeliveryModule["deliverInboundReplyWithMessageSendContext"] = +export const deliverInboundReplyWithMessageSendContext: ChannelDurableDeliveryModule["deliverInboundReplyWithMessageSendContextCore"] = async (...args) => { const mod = await import("../channels/turn/durable-delivery.js"); - return await mod.deliverInboundReplyWithMessageSendContext(...args); + return await mod.deliverInboundReplyWithMessageSendContextCore(...args); }; /** Sends a durable message batch without eager-loading channel message runtime internals. */ diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 570b2ca3cc90..126bcfad939b 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -1,7 +1,9 @@ -import { expectDefined } from "@openclaw/normalization-core"; import { normalizeLowercaseStringOrEmpty } from "../../packages/normalization-core/src/string-coerce.js"; import type { ResolvedConfiguredAcpBinding } from "../acp/persistent-bindings.types.js"; -import { buildChatChannelMetaById } from "../channels/chat-meta-shared.js"; +import { + findChatChannelMeta, + getChatChannelMeta as getBuiltInChatChannelMeta, +} from "../channels/chat-meta.js"; import type { ChatChannelId } from "../channels/ids.js"; import { emptyChannelConfigSchema } from "../channels/plugins/config-schema.js"; import { buildAccountScopedDmSecurityPolicy } from "../channels/plugins/helpers.js"; @@ -28,7 +30,6 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { buildOutboundBaseSessionKey } from "../infra/outbound/base-session-key.js"; import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js"; import { normalizeOutboundThreadId } from "../infra/outbound/thread-id.js"; -import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js"; import type { PluginRuntime } from "../plugins/runtime/types.js"; import type { OpenClawPluginApi } from "../plugins/types.js"; import { resolveThreadSessionKeys } from "../routing/session-key.js"; @@ -292,7 +293,7 @@ export async function ensureConfiguredAcpBindingReady(params: { configuredBinding: ResolvedConfiguredAcpBinding | null; }): Promise<{ ok: true } | { ok: false; error: string }> { const runtime = await import("../acp/persistent-bindings.lifecycle.js"); - return runtime.ensureConfiguredAcpBindingReady(params); + return runtime.ensureConfiguredAcpBindingReadyCore(params); } export { @@ -315,30 +316,11 @@ export type ChannelOutboundSessionRouteParams = Parameters< NonNullable >[0]; -let cachedSdkChatChannelMeta: - | { - cacheKey: string; - metaById: ReturnType; - } - | undefined; - -function resolveSdkChatChannelMeta(id: string) { - const cacheKey = resolveBundledPluginsDir(process.env) ?? ""; - if (cachedSdkChatChannelMeta?.cacheKey !== cacheKey) { - cachedSdkChatChannelMeta = { - cacheKey, - metaById: buildChatChannelMetaById(), - }; - } - // Optional by design: createChannelPluginBase serves external plugin ids that - // are never in the bundled catalog; their meta comes entirely from params.meta. - return cachedSdkChatChannelMeta.metaById[id]; +function getChatChannelMetaForSdk(id: ChatChannelId): ChannelMeta { + return getBuiltInChatChannelMeta(id); } -/** Resolve bundled chat channel metadata while respecting the active bundled-plugin directory. */ -export function getChatChannelMeta(id: ChatChannelId): ChannelMeta { - return expectDefined(resolveSdkChatChannelMeta(id), `chat channel metadata: ${id}`); -} +export { getChatChannelMetaForSdk as getChatChannelMeta }; /** Remove one of the known provider prefixes from a free-form target string. */ export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string { @@ -858,7 +840,7 @@ export function createChannelPluginBase( return { id: params.id, meta: { - ...resolveSdkChatChannelMeta(params.id), + ...findChatChannelMeta(params.id as ChatChannelId), ...params.meta, id: params.id, }, diff --git a/src/plugin-sdk/delivery-queue-runtime.test.ts b/src/plugin-sdk/delivery-queue-runtime.test.ts index ca90469d2f63..779e5114e32b 100644 --- a/src/plugin-sdk/delivery-queue-runtime.test.ts +++ b/src/plugin-sdk/delivery-queue-runtime.test.ts @@ -17,7 +17,7 @@ const mocks = vi.hoisted(() => ({ })); vi.mock("../infra/outbound/delivery-queue.js", () => ({ - drainPendingDeliveries: mocks.coreDrainPendingDeliveries, + drainPendingDeliveriesCore: mocks.coreDrainPendingDeliveries, })); vi.mock("../infra/outbound/deliver-runtime.js", () => { diff --git a/src/plugin-sdk/delivery-queue-runtime.ts b/src/plugin-sdk/delivery-queue-runtime.ts index 033382440a8a..6029d583528f 100644 --- a/src/plugin-sdk/delivery-queue-runtime.ts +++ b/src/plugin-sdk/delivery-queue-runtime.ts @@ -1,13 +1,10 @@ // Delivery queue runtime helpers persist and replay outbound plugin delivery work. -import { - drainPendingDeliveries as coreDrainPendingDeliveries, - type DeliverFn, -} from "../infra/outbound/delivery-queue.js"; +import { drainPendingDeliveriesCore, type DeliverFn } from "../infra/outbound/delivery-queue.js"; import { runWithGatewayIndependentRootWorkAdmission } from "../process/gateway-work-admission.js"; import { createLazyRuntimeModule } from "../shared/lazy-runtime.js"; type DrainPendingDeliveriesOptions = Omit< - Parameters[0], + Parameters[0], "deliver" > & { /** Optional delivery implementation for tests or plugin-owned send paths. */ @@ -28,7 +25,7 @@ export async function drainPendingDeliveries(opts: DrainPendingDeliveriesOptions // Keep lazy resolution and draining in one lease so suspension cannot split the handoff. const deliver = opts.deliver ?? (await loadOutboundDeliverRuntime()).deliverOutboundPayloadsInternal; - await coreDrainPendingDeliveries({ + await drainPendingDeliveriesCore({ ...opts, deliver, }); diff --git a/src/plugin-sdk/inbound-reply-dispatch.ts b/src/plugin-sdk/inbound-reply-dispatch.ts index d082bc907637..c30473b2fed8 100644 --- a/src/plugin-sdk/inbound-reply-dispatch.ts +++ b/src/plugin-sdk/inbound-reply-dispatch.ts @@ -6,7 +6,7 @@ import type { GetReplyOptions } from "../auto-reply/get-reply-options.types.js"; import type { DispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.types.js"; import type { FinalizedMsgContext } from "../auto-reply/templating.js"; import { - deliverInboundReplyWithMessageSendContext, + deliverInboundReplyWithMessageSendContextCore, isDurableInboundReplyDeliveryHandled, throwIfDurableInboundReplyDeliveryFailed, type DurableInboundReplyDeliveryOptions, @@ -90,7 +90,7 @@ async function recordInboundSessionAndDispatchReply( payload && typeof payload === "object" ? normalizeOutboundReplyPayload(payload) : {}, deliver: async (payload, info) => { if (params.durable) { - const durable = await deliverInboundReplyWithMessageSendContext({ + const durable = await deliverInboundReplyWithMessageSendContextCore({ cfg: params.cfg, channel: params.channel, accountId: params.accountId, diff --git a/src/plugin-sdk/memory-host-search.runtime.ts b/src/plugin-sdk/memory-host-search.runtime.ts index 9241d3c8a26f..20bc6340b120 100644 --- a/src/plugin-sdk/memory-host-search.runtime.ts +++ b/src/plugin-sdk/memory-host-search.runtime.ts @@ -2,8 +2,8 @@ * Runtime SDK subpath for active memory search manager operations. */ export { - closeActiveMemorySearchManager, - closeActiveMemorySearchManagers, - getActiveMemorySearchManager, + closeActiveMemorySearchManagerCore as closeActiveMemorySearchManager, + closeActiveMemorySearchManagersCore as closeActiveMemorySearchManagers, + getActiveMemorySearchManagerCore as getActiveMemorySearchManager, resolveActiveMemoryBackendConfig, } from "../plugins/memory-runtime.js"; diff --git a/src/plugin-sdk/provider-auth-runtime.ts b/src/plugin-sdk/provider-auth-runtime.ts index ca9a8779bddc..a21b9fd3e189 100644 --- a/src/plugin-sdk/provider-auth-runtime.ts +++ b/src/plugin-sdk/provider-auth-runtime.ts @@ -109,10 +109,12 @@ export function buildOAuthCallbackOriginResolver( /** * Generates a high-entropy OAuth state token for local callback validation. */ -export function generateOAuthState(): string { +function generateHexOAuthState(): string { return crypto.randomBytes(32).toString("hex"); } +export { generateHexOAuthState as generateOAuthState }; + /** * Parses a pasted OAuth redirect URL into callback code/state fields. */ @@ -230,7 +232,7 @@ function isHttpOrigin(value: string): boolean { type ResolveApiKeyForProvider = typeof import("../agents/model-auth.js").resolveApiKeyForProvider; type GetRuntimeAuthForModel = - typeof import("../plugins/runtime/runtime-model-auth.runtime.js").getRuntimeAuthForModel; + typeof import("../plugins/runtime/runtime-model-auth.runtime.js").getRuntimeAuthForModelCore; type RuntimeModelAuthModule = typeof import("../plugins/runtime/runtime-model-auth.runtime.js"); const RUNTIME_MODEL_AUTH_CANDIDATES = [ "./runtime-model-auth.runtime", @@ -277,7 +279,7 @@ export async function getRuntimeAuthForModel( /** Concrete model auth request forwarded to the runtime auth module. */ params: Parameters[0], ): Promise>> { - const { getRuntimeAuthForModel: getRuntimeAuthForModelLocal } = + const { getRuntimeAuthForModelCore: getRuntimeAuthForModelLocal } = await loadRuntimeModelAuthModule(); return getRuntimeAuthForModelLocal(params); } diff --git a/src/plugin-sdk/provider-enable-config.ts b/src/plugin-sdk/provider-enable-config.ts index a5ef4200e759..ac774c03c07c 100644 --- a/src/plugin-sdk/provider-enable-config.ts +++ b/src/plugin-sdk/provider-enable-config.ts @@ -33,7 +33,7 @@ export type PluginEnableResult = { * Enables provider plugins for provider contract setup without applying channel * normalization from the core plugin enable path. */ -export function enablePluginInConfig( +function enableProviderPluginInConfig( /** Provider setup config object to update without channel normalization. */ cfg: TConfig, /** Provider plugin id to enable and allowlist. */ @@ -68,3 +68,5 @@ export function enablePluginInConfig { expect(module.isQaRuntimeAvailable()).toBe(false); }); + it("rethrows non-absence loader failures that mention the qa-lab runtime path", async () => { + loadBundledPluginPublicSurfaceModuleSync.mockImplementation(() => { + throw new Error("Failed to evaluate qa-lab/runtime-api.js: invalid runtime export"); + }); + + const module = await import("./qa-runtime.js"); + + expect(() => module.isQaRuntimeAvailable()).toThrow( + "Failed to evaluate qa-lab/runtime-api.js: invalid runtime export", + ); + }); + it("runs a plugin-owned transport through the private QA suite host", async () => { const runLiveTransportQaSuiteCommand = vi.fn(async () => {}); loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ diff --git a/src/plugin-sdk/qa-runtime.ts b/src/plugin-sdk/qa-runtime.ts index 56436979ec39..bb302c452141 100644 --- a/src/plugin-sdk/qa-runtime.ts +++ b/src/plugin-sdk/qa-runtime.ts @@ -72,8 +72,7 @@ export function loadQaRuntimeModule(): QaRuntimeSurface { }); } -/** Check whether the bundled QA lab runtime surface is present without hiding other load errors. */ -export function isQaRuntimeAvailable(): boolean { +function isQaRuntimeAvailableStrict(): boolean { try { loadQaRuntimeModule(); return true; @@ -85,6 +84,8 @@ export function isQaRuntimeAvailable(): boolean { } } +export { isQaRuntimeAvailableStrict as isQaRuntimeAvailable }; + /** Docker command runner abstraction used by QA Docker helpers and tests. */ export type QaDockerRunCommand = ( command: string, diff --git a/src/plugin-sdk/reply-dispatch-runtime.ts b/src/plugin-sdk/reply-dispatch-runtime.ts index 4289a6bb9316..062c3fef76c9 100644 --- a/src/plugin-sdk/reply-dispatch-runtime.ts +++ b/src/plugin-sdk/reply-dispatch-runtime.ts @@ -25,13 +25,13 @@ const loadProviderDispatcherRuntimeModule = createLazyPromise( /** Dispatches a reply with buffered block support after lazy-loading the runtime dispatcher. */ export const dispatchReplyWithBufferedBlockDispatcher: DispatchReplyWithBufferedBlockDispatcher = async (params) => { - const { dispatchReplyWithBufferedBlockDispatcher: dispatch } = + const { dispatchReplyWithBufferedBlockDispatcherCore: dispatch } = await loadProviderDispatcherRuntimeModule(); return await dispatch(params); }; /** Dispatches a reply through the provider dispatcher after lazy-loading runtime code. */ export const dispatchReplyWithDispatcher: DispatchReplyWithDispatcher = async (params) => { - const { dispatchReplyWithDispatcher: dispatch } = await loadProviderDispatcherRuntimeModule(); + const { dispatchReplyWithDispatcherCore: dispatch } = await loadProviderDispatcherRuntimeModule(); return await dispatch(params); }; diff --git a/src/plugin-sdk/reply-runtime.ts b/src/plugin-sdk/reply-runtime.ts index 56d42a7925f4..a859436a21c2 100644 --- a/src/plugin-sdk/reply-runtime.ts +++ b/src/plugin-sdk/reply-runtime.ts @@ -38,8 +38,8 @@ export { resolveInboundDebounceMs, } from "../auto-reply/inbound-debounce.js"; export { - dispatchReplyWithBufferedBlockDispatcher, - dispatchReplyWithDispatcher, + dispatchReplyWithBufferedBlockDispatcherCore as dispatchReplyWithBufferedBlockDispatcher, + dispatchReplyWithDispatcherCore as dispatchReplyWithDispatcher, } from "../auto-reply/reply/provider-dispatcher.js"; export { createReplyDispatcher, diff --git a/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts index 2dfd1baf9767..20cabf0c3d3f 100644 --- a/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts @@ -4,12 +4,12 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import ts from "typescript"; import { describe, expect, it } from "vitest"; -import { bundledPluginFile, getBundledPluginRoots } from "./test-helpers/bundled-plugin-roots.js"; +import { contractPluginPath, getBundledPluginRoots } from "./test-helpers/bundled-plugin-roots.js"; const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); function runtimeApiPluginFile(pluginId: string): string { - return bundledPluginFile({ rootDir: ROOT_DIR, pluginId, relativePath: "runtime-api.ts" }); + return contractPluginPath({ rootDir: ROOT_DIR, pluginId, relativePath: "runtime-api.ts" }); } const UNGUARDED_RUNTIME_API_PLUGIN_IDS = [ @@ -43,7 +43,7 @@ const UNGUARDED_RUNTIME_API_PLUGIN_IDS = [ ] as const; const RUNTIME_API_EXPORT_GUARDS: Record = { - [bundledPluginFile({ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "diagnostics-otel", relativePath: "runtime-api.ts", @@ -51,14 +51,15 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { createDiagnosticsOtelService } from "./src/service.js";', 'export type { OpenClawPluginServiceContext } from "./api.js";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "discord", relativePath: "runtime-api.ts" })]: [ - 'export { discordMessageActions, handleDiscordAction, isDiscordModerationAction, readDiscordChannelCreateParams, readDiscordChannelEditParams, readDiscordChannelMoveParams, readDiscordModerationCommand, readDiscordParentIdParam, requiredGuildPermissionForModerationAction, type DiscordModerationAction, type DiscordModerationCommand } from "./runtime-api.actions.js";', - 'export { auditDiscordChannelPermissions, collectDiscordAuditChannelIds, fetchDiscordApplicationId, fetchDiscordApplicationSummary, listDiscordDirectoryGroupsLive, listDiscordDirectoryPeersLive, parseApplicationIdFromToken, probeDiscord, resolveDiscordChannelAllowlist, resolveDiscordPrivilegedIntentsFromFlags, resolveDiscordUserAllowlist, setDiscordRuntime, type DiscordApplicationSummary, type DiscordChannelResolution, type DiscordPrivilegedIntentsSummary, type DiscordPrivilegedIntentStatus, type DiscordProbe, type DiscordUserResolution } from "./runtime-api.lookup.js";', - 'export { DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS, DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, DISCORD_DEFAULT_INBOUND_WORKER_TIMEOUT_MS, DISCORD_DEFAULT_LISTENER_TIMEOUT_MS, allowListMatches, clearGateways, clearPresences, createDiscordGatewayPlugin, createDiscordMessageHandler, createDiscordNativeCommand, getGateway, getPresence, isAbortError, isDiscordGroupAllowedByPolicy, monitorDiscordProvider, normalizeDiscordAllowList, normalizeDiscordInboundWorkerTimeoutMs, normalizeDiscordListenerTimeoutMs, normalizeDiscordSlug, presenceCacheSize, registerDiscordListener, registerGateway, resolveDiscordChannelConfig, resolveDiscordChannelConfigWithFallback, resolveDiscordCommandAuthorized, resolveDiscordGatewayIntents, resolveDiscordGuildEntry, resolveDiscordReplyTarget, resolveDiscordShouldRequireMention, resolveGroupDmAllow, runDiscordTaskWithTimeout, sanitizeDiscordThreadName, setPresence, shouldEmitDiscordReactionNotification, unregisterGateway, waitForDiscordGatewayPluginRegistration, type DiscordAllowList, type DiscordChannelConfigResolved, type DiscordGuildEntryResolved, type DiscordMessageEvent, type DiscordMessageHandler, type MonitorDiscordOpts } from "./runtime-api.monitor.js";', - 'export { DiscordSendError, addRoleDiscord, banMemberDiscord, createChannelDiscord, createScheduledEventDiscord, createThreadDiscord, deleteChannelDiscord, deleteMessageDiscord, editChannelDiscord, editDiscordComponentMessage, editMessageDiscord, fetchChannelInfoDiscord, fetchChannelPermissionsDiscord, fetchMemberGuildPermissionsDiscord, fetchMemberInfoDiscord, fetchMessageDiscord, fetchReactionsDiscord, fetchRoleInfoDiscord, fetchVoiceStatusDiscord, hasAllGuildPermissionsDiscord, hasAnyGuildPermissionDiscord, kickMemberDiscord, listGuildChannelsDiscord, listGuildEmojisDiscord, listPinsDiscord, listScheduledEventsDiscord, listThreadsDiscord, moveChannelDiscord, pinMessageDiscord, reactMessageDiscord, readMessagesDiscord, registerBuiltDiscordComponentMessage, removeChannelPermissionDiscord, removeOwnReactionsDiscord, removeReactionDiscord, removeRoleDiscord, resolveDiscordOutboundSessionRoute, resolveEventCoverImage, searchMessagesDiscord, sendDiscordComponentMessage, sendMessageDiscord, sendPollDiscord, sendStickerDiscord, sendTypingDiscord, sendVoiceMessageDiscord, sendWebhookMessageDiscord, setChannelPermissionDiscord, timeoutMemberDiscord, unpinMessageDiscord, uploadEmojiDiscord, uploadStickerDiscord, type DiscordChannelCreate, type DiscordChannelEdit, type DiscordChannelMove, type DiscordChannelPermissionSet, type DiscordEmojiUpload, type DiscordMessageEdit, type DiscordMessageQuery, type DiscordModerationTarget, type DiscordPermissionsSummary, type DiscordReactionRuntimeContext, type DiscordReactionSummary, type DiscordReactionUser, type DiscordReactOpts, type DiscordRoleChange, type DiscordRuntimeAccountContext, type DiscordSearchQuery, type DiscordSendResult, type DiscordStickerUpload, type DiscordThreadCreate, type DiscordThreadList, type DiscordTimeoutTarget, type ResolveDiscordOutboundSessionRouteParams } from "./runtime-api.send.js";', - 'export { testing as __testing, testing, autoBindSpawnedDiscordSubagent, createNoopThreadBindingManager, createThreadBindingManager, formatThreadBindingDurationLabel, getThreadBindingManager, listThreadBindingsBySessionKey, listThreadBindingsForAccount, reconcileAcpThreadBindingsOnStartup, resolveDiscordThreadBindingIdleTimeoutMs, resolveDiscordThreadBindingMaxAgeMs, resolveThreadBindingIdleTimeoutMs, resolveThreadBindingInactivityExpiresAt, resolveThreadBindingIntroText, resolveThreadBindingMaxAgeExpiresAt, resolveThreadBindingMaxAgeMs, resolveThreadBindingPersona, resolveThreadBindingPersonaFromRecord, resolveThreadBindingsEnabled, resolveThreadBindingThreadName, setThreadBindingIdleTimeoutBySessionKey, setThreadBindingMaxAgeBySessionKey, unbindThreadBindingsBySessionKey, type AcpThreadBindingReconciliationResult, type ThreadBindingManager, type ThreadBindingRecord, type ThreadBindingTargetKind } from "./runtime-api.threads.js";', - ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "imessage", relativePath: "runtime-api.ts" })]: + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "discord", relativePath: "runtime-api.ts" })]: + [ + 'export { discordMessageActions, handleDiscordAction, isDiscordModerationAction, readDiscordChannelCreateParams, readDiscordChannelEditParams, readDiscordChannelMoveParams, readDiscordModerationCommand, readDiscordParentIdParam, requiredGuildPermissionForModerationAction, type DiscordModerationAction, type DiscordModerationCommand } from "./runtime-api.actions.js";', + 'export { auditDiscordChannelPermissions, collectDiscordAuditChannelIds, fetchDiscordApplicationId, fetchDiscordApplicationSummary, listDiscordDirectoryGroupsLive, listDiscordDirectoryPeersLive, parseApplicationIdFromToken, probeDiscord, resolveDiscordChannelAllowlist, resolveDiscordPrivilegedIntentsFromFlags, resolveDiscordUserAllowlist, setDiscordRuntime, type DiscordApplicationSummary, type DiscordChannelResolution, type DiscordPrivilegedIntentsSummary, type DiscordPrivilegedIntentStatus, type DiscordProbe, type DiscordUserResolution } from "./runtime-api.lookup.js";', + 'export { DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS, DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, DISCORD_DEFAULT_INBOUND_WORKER_TIMEOUT_MS, DISCORD_DEFAULT_LISTENER_TIMEOUT_MS, allowListMatches, clearGateways, clearPresences, createDiscordGatewayPlugin, createDiscordMessageHandler, createDiscordNativeCommand, getGateway, getPresence, isAbortError, isDiscordGroupAllowedByPolicy, monitorDiscordProvider, normalizeDiscordAllowList, normalizeDiscordInboundWorkerTimeoutMs, normalizeDiscordListenerTimeoutMs, normalizeDiscordSlug, presenceCacheSize, registerDiscordListener, registerGateway, resolveDiscordChannelConfig, resolveDiscordChannelConfigWithFallback, resolveDiscordCommandAuthorized, resolveDiscordGatewayIntents, resolveDiscordGuildEntry, resolveDiscordReplyTarget, resolveDiscordShouldRequireMention, resolveGroupDmAllow, runDiscordTaskWithTimeout, sanitizeDiscordThreadName, setPresence, shouldEmitDiscordReactionNotification, unregisterGateway, waitForDiscordGatewayPluginRegistration, type DiscordAllowList, type DiscordChannelConfigResolved, type DiscordGuildEntryResolved, type DiscordMessageEvent, type DiscordMessageHandler, type MonitorDiscordOpts } from "./runtime-api.monitor.js";', + 'export { DiscordSendError, addRoleDiscord, banMemberDiscord, createChannelDiscord, createScheduledEventDiscord, createThreadDiscord, deleteChannelDiscord, deleteMessageDiscord, editChannelDiscord, editDiscordComponentMessage, editMessageDiscord, fetchChannelInfoDiscord, fetchChannelPermissionsDiscord, fetchMemberGuildPermissionsDiscord, fetchMemberInfoDiscord, fetchMessageDiscord, fetchReactionsDiscord, fetchRoleInfoDiscord, fetchVoiceStatusDiscord, hasAllGuildPermissionsDiscord, hasAnyGuildPermissionDiscord, kickMemberDiscord, listGuildChannelsDiscord, listGuildEmojisDiscord, listPinsDiscord, listScheduledEventsDiscord, listThreadsDiscord, moveChannelDiscord, pinMessageDiscord, reactMessageDiscord, readMessagesDiscord, registerBuiltDiscordComponentMessage, removeChannelPermissionDiscord, removeOwnReactionsDiscord, removeReactionDiscord, removeRoleDiscord, resolveDiscordOutboundSessionRoute, resolveEventCoverImage, searchMessagesDiscord, sendDiscordComponentMessage, sendMessageDiscord, sendPollDiscord, sendStickerDiscord, sendTypingDiscord, sendVoiceMessageDiscord, sendWebhookMessageDiscord, setChannelPermissionDiscord, timeoutMemberDiscord, unpinMessageDiscord, uploadEmojiDiscord, uploadStickerDiscord, type DiscordChannelCreate, type DiscordChannelEdit, type DiscordChannelMove, type DiscordChannelPermissionSet, type DiscordEmojiUpload, type DiscordMessageEdit, type DiscordMessageQuery, type DiscordModerationTarget, type DiscordPermissionsSummary, type DiscordReactionRuntimeContext, type DiscordReactionSummary, type DiscordReactionUser, type DiscordReactOpts, type DiscordRoleChange, type DiscordRuntimeAccountContext, type DiscordSearchQuery, type DiscordSendResult, type DiscordStickerUpload, type DiscordThreadCreate, type DiscordThreadList, type DiscordTimeoutTarget, type ResolveDiscordOutboundSessionRouteParams } from "./runtime-api.send.js";', + 'export { testing as __testing, testing, autoBindSpawnedDiscordSubagent, createNoopThreadBindingManager, createThreadBindingManager, formatThreadBindingDurationLabel, getThreadBindingManager, listThreadBindingsBySessionKey, listThreadBindingsForAccount, reconcileAcpThreadBindingsOnStartup, resolveDiscordThreadBindingIdleTimeoutMs, resolveDiscordThreadBindingMaxAgeMs, resolveThreadBindingIdleTimeoutMs, resolveThreadBindingInactivityExpiresAt, resolveThreadBindingIntroText, resolveThreadBindingMaxAgeExpiresAt, resolveThreadBindingMaxAgeMs, resolveThreadBindingPersona, resolveThreadBindingPersonaFromRecord, resolveThreadBindingsEnabled, resolveThreadBindingThreadName, setThreadBindingIdleTimeoutBySessionKey, setThreadBindingMaxAgeBySessionKey, unbindThreadBindingsBySessionKey, type AcpThreadBindingReconciliationResult, type ThreadBindingManager, type ThreadBindingRecord, type ThreadBindingTargetKind } from "./runtime-api.threads.js";', + ], + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "imessage", relativePath: "runtime-api.ts" })]: [ 'export { DEFAULT_ACCOUNT_ID, getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/core";', 'export { buildChannelConfigSchema, IMessageConfigSchema } from "./config-api.js";', @@ -79,7 +80,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { chunkTextForOutbound } from "./src/channel-api.js";', 'export type IMessageAccountConfig = Omit< NonNullable["imessage"]>, "accounts" | "defaultAccount" >;', ], - [bundledPluginFile({ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "googlechat", relativePath: "runtime-api.ts", @@ -107,40 +108,41 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { createWebhookInFlightLimiter, readJsonWebhookBodyOrReject, type WebhookInFlightLimiter } from "openclaw/plugin-sdk/webhook-request-guards";', 'export { setGoogleChatRuntime } from "./src/runtime.js";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "msteams", relativePath: "runtime-api.ts" })]: [ - 'export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";', - 'export type { AllowlistMatch } from "openclaw/plugin-sdk/allow-from";', - 'export { mergeAllowlist, resolveAllowlistMatchSimple, summarizeMapping } from "openclaw/plugin-sdk/allow-from";', - 'export type { BaseProbeResult, ChannelDirectoryEntry, ChannelGroupContext, ChannelMessageActionName, ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-contract";', - 'export type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";', - 'export { logTypingFailure } from "openclaw/plugin-sdk/channel-outbound";', - 'export { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";', - 'export { resolveToolsBySender } from "openclaw/plugin-sdk/channel-policy";', - 'export { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";', - 'export { PAIRING_APPROVED_MESSAGE, buildProbeChannelStatusSummary, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/channel-status";', - 'export { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback, resolveNestedAllowlistDecision } from "openclaw/plugin-sdk/channel-targets";', - 'export type { GroupPolicy, GroupToolPolicyConfig, MSTeamsChannelConfig, MSTeamsCloudName, MSTeamsConfig, MSTeamsReplyStyle, MSTeamsTeamConfig, MarkdownTableMode, OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";', - 'export { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";', - 'export { resolveDefaultGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";', - 'export { withFileLock } from "openclaw/plugin-sdk/file-lock";', - 'export { keepHttpServerTaskAlive } from "openclaw/plugin-sdk/channel-outbound";', - 'export { detectMime, extensionForMime, extractOriginalFilename, getFileExtension, resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";', - 'export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";', - 'export { buildMediaPayload } from "openclaw/plugin-sdk/reply-payload";', - 'export type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload";', - 'export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";', - 'export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";', - 'export type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";', - 'export { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";', - 'export { normalizeStringEntries } from "openclaw/plugin-sdk/string-normalization-runtime";', - 'export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";', - 'export { DEFAULT_WEBHOOK_MAX_BODY_BYTES } from "openclaw/plugin-sdk/webhook-ingress";', - 'export { setMSTeamsRuntime } from "./src/runtime.js";', - ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "irc", relativePath: "runtime-api.ts" })]: [ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "msteams", relativePath: "runtime-api.ts" })]: + [ + 'export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";', + 'export type { AllowlistMatch } from "openclaw/plugin-sdk/allow-from";', + 'export { mergeAllowlist, resolveAllowlistMatchSimple, summarizeMapping } from "openclaw/plugin-sdk/allow-from";', + 'export type { BaseProbeResult, ChannelDirectoryEntry, ChannelGroupContext, ChannelMessageActionName, ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-contract";', + 'export type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";', + 'export { logTypingFailure } from "openclaw/plugin-sdk/channel-outbound";', + 'export { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";', + 'export { resolveToolsBySender } from "openclaw/plugin-sdk/channel-policy";', + 'export { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";', + 'export { PAIRING_APPROVED_MESSAGE, buildProbeChannelStatusSummary, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/channel-status";', + 'export { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback, resolveNestedAllowlistDecision } from "openclaw/plugin-sdk/channel-targets";', + 'export type { GroupPolicy, GroupToolPolicyConfig, MSTeamsChannelConfig, MSTeamsCloudName, MSTeamsConfig, MSTeamsReplyStyle, MSTeamsTeamConfig, MarkdownTableMode, OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";', + 'export { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";', + 'export { resolveDefaultGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";', + 'export { withFileLock } from "openclaw/plugin-sdk/file-lock";', + 'export { keepHttpServerTaskAlive } from "openclaw/plugin-sdk/channel-outbound";', + 'export { detectMime, extensionForMime, extractOriginalFilename, getFileExtension, resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";', + 'export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";', + 'export { buildMediaPayload } from "openclaw/plugin-sdk/reply-payload";', + 'export type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload";', + 'export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";', + 'export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";', + 'export type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";', + 'export { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";', + 'export { normalizeStringEntries } from "openclaw/plugin-sdk/string-normalization-runtime";', + 'export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";', + 'export { DEFAULT_WEBHOOK_MAX_BODY_BYTES } from "openclaw/plugin-sdk/webhook-ingress";', + 'export { setMSTeamsRuntime } from "./src/runtime.js";', + ], + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "irc", relativePath: "runtime-api.ts" })]: [ 'export { setIrcRuntime } from "./src/runtime.js";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "matrix", relativePath: "runtime-api.ts" })]: [ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "matrix", relativePath: "runtime-api.ts" })]: [ 'export { type MatrixResolvedStringField, type MatrixResolvedStringValues, resolveMatrixAccountStringValues } from "./src/auth-precedence.js";', 'export { requiresExplicitMatrixDefaultAccount, resolveMatrixDefaultOrOnlyAccountId } from "./src/account-selection.js";', 'export { findMatrixAccountEntry, resolveConfiguredMatrixAccountIds, resolveMatrixChannelConfig } from "./src/account-selection.js";', @@ -159,7 +161,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export type { WizardPrompter } from "openclaw/plugin-sdk/setup";', 'export function chunkTextForOutbound(text: string, limit: number): string[] { if (text.length === 0) { return [""]; } if (Number.isFinite(limit) && limit > 0 && !Number.isInteger(limit)) { return chunkTextForOutboundSdk(text, limit); } const chunks: string[] = []; let remaining = text; while (remaining.length > limit) { const window = remaining.slice(0, limit); const splitAt = Math.max(window.lastIndexOf("\\n"), window.lastIndexOf(" ")); const breakAt = splitAt > 0 ? splitAt : limit; chunks.push(remaining.slice(0, breakAt).trimEnd()); remaining = remaining.slice(breakAt).trimStart(); } if (remaining.length > 0) { chunks.push(remaining); } return chunks; }', ], - [bundledPluginFile({ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "nextcloud-talk", relativePath: "runtime-api.ts", @@ -179,16 +181,16 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";', 'export { setNextcloudTalkRuntime } from "./src/runtime.js";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "nostr", relativePath: "runtime-api.ts" })]: [ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "nostr", relativePath: "runtime-api.ts" })]: [ 'export type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";', 'export { getPluginRuntimeGatewayRequestScope } from "openclaw/plugin-sdk/plugin-runtime";', 'export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "signal", relativePath: "runtime-api.ts" })]: [ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "signal", relativePath: "runtime-api.ts" })]: [ 'export { applyAccountNameToChannelSection, buildBaseAccountStatusSnapshot, buildBaseChannelStatusSummary, buildChannelConfigSchema, type ChannelMessageActionAdapter, type ChannelPlugin, chunkText, collectStatusIssuesFromLastError, createDefaultChannelRuntimeState, DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, detectBinary, emptyPluginConfigSchema, formatCliCommand, formatDocsLink, formatPairingApproveHint, getChatChannelMeta, installSignalCli, listEnabledSignalAccounts, listSignalAccountIds, looksLikeSignalTargetId, migrateBaseNameToDefaultAccount, monitorSignalProvider, normalizeAccountId, normalizeE164, normalizeSignalMessagingTarget, type OpenClawConfig, type OpenClawPluginApi, PAIRING_APPROVED_MESSAGE, type PluginRuntime, probeSignal, removeReactionSignal, resolveAllowlistProviderRuntimeGroupPolicy, resolveChannelMediaMaxBytes, resolveDefaultGroupPolicy, resolveDefaultSignalAccountId, type ResolvedSignalAccount, resolveSignalAccount, resolveSignalReactionLevel, sendMessageSignal, sendReactionSignal, setAccountEnabledInConfigSection, type SignalAccountConfig, SignalConfigSchema, signalMessageActions } from "./src/runtime-api.js";', 'export { setSignalRuntime } from "./src/runtime.js";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "slack", relativePath: "runtime-api.ts" })]: [ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "slack", relativePath: "runtime-api.ts" })]: [ 'export { handleSlackAction, slackActionRuntime, type SlackActionContext } from "./src/action-runtime.js";', 'export { listSlackDirectoryGroupsLive, listSlackDirectoryPeersLive } from "./src/directory-live.js";', 'export { deleteSlackMessage, editSlackMessage, getSlackMemberInfo, listEnabledSlackAccounts, listSlackAccountIds, listSlackEmojis, listSlackPins, listSlackReactions, monitorSlackProvider, pinSlackMessage, probeSlack, reactSlackMessage, readSlackMessages, removeOwnSlackReactions, removeSlackReaction, resolveDefaultSlackAccountId, resolveSlackAccount, resolveSlackAppToken, resolveSlackBotToken, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, sendMessageSlack, sendSlackMessage, unpinSlackMessage } from "./src/index.js";', @@ -197,7 +199,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { registerSlackPluginHttpRoutes } from "./src/http/plugin-routes.js";', 'export { setSlackRuntime } from "./src/runtime.js";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "telegram", relativePath: "runtime-api.ts" })]: + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "telegram", relativePath: "runtime-api.ts" })]: [ 'export type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";', 'export type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract";', @@ -233,7 +235,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { parseTelegramTopicConversation } from "./src/topic-conversation.js";', 'export { resolveTelegramPollVisibility } from "./src/poll-visibility.js";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "twitch", relativePath: "runtime-api.ts" })]: [ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "twitch", relativePath: "runtime-api.ts" })]: [ 'export type { ChannelAccountSnapshot, ChannelCapabilities, ChannelGatewayContext, ChannelLogSink, ChannelMessageActionAdapter, ChannelMessageActionContext, ChannelMeta, ChannelOutboundAdapter, ChannelOutboundContext, ChannelResolveKind, ChannelResolveResult, ChannelStatusAdapter } from "openclaw/plugin-sdk/channel-contract";', 'export type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";', 'export type { OutboundDeliveryResult } from "openclaw/plugin-sdk/channel-send-result";', @@ -241,7 +243,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";', 'export type { WizardPrompter } from "openclaw/plugin-sdk/setup";', ], - [bundledPluginFile({ + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "voice-call", relativePath: "runtime-api.ts", @@ -255,7 +257,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { TtsAutoSchema, TtsConfigSchema, TtsModeSchema, TtsProviderSchema } from "openclaw/plugin-sdk/tts-runtime";', 'export { sleep } from "openclaw/plugin-sdk/runtime-env";', ], - [bundledPluginFile({ rootDir: ROOT_DIR, pluginId: "whatsapp", relativePath: "runtime-api.ts" })]: + [contractPluginPath({ rootDir: ROOT_DIR, pluginId: "whatsapp", relativePath: "runtime-api.ts" })]: [ 'export { getActiveWebListener, resolveWebAccountId, type ActiveWebListener, type ActiveWebSendOptions } from "./src/active-listener.js";', 'export { handleWhatsAppAction, whatsAppActionRuntime } from "./src/action-runtime.js";', @@ -276,7 +278,7 @@ function collectRuntimeApiFiles(): string[] { return [...getBundledPluginRoots().entries()] .filter(([, rootDir]) => existsSync(resolve(rootDir, "runtime-api.ts"))) .map(([pluginId]) => - bundledPluginFile({ + contractPluginPath({ rootDir: ROOT_DIR, pluginId, relativePath: "runtime-api.ts", @@ -409,7 +411,7 @@ describe("runtime api guardrails", () => { // cost of importing the full runtime-api barrel. If a future change // re-broadens this file, this test fails so the perf regression is // surfaced explicitly rather than silently re-introduced. - const setterFile = bundledPluginFile({ + const setterFile = contractPluginPath({ rootDir: ROOT_DIR, pluginId: "slack", relativePath: "runtime-setter-api.ts", @@ -420,7 +422,7 @@ describe("runtime api guardrails", () => { }); it("keeps Matrix's narrow runtime-setter entrypoint pinned to a single export", () => { - const setterFile = bundledPluginFile({ + const setterFile = contractPluginPath({ rootDir: ROOT_DIR, pluginId: "matrix", relativePath: "runtime-setter-api.ts", @@ -431,7 +433,7 @@ describe("runtime api guardrails", () => { }); it("keeps Feishu's narrow runtime-setter entrypoint pinned to a single export", () => { - const setterFile = bundledPluginFile({ + const setterFile = contractPluginPath({ rootDir: ROOT_DIR, pluginId: "feishu", relativePath: "runtime-setter-api.ts", diff --git a/src/plugins/contracts/test-helpers/bundled-plugin-roots.ts b/src/plugins/contracts/test-helpers/bundled-plugin-roots.ts index c3374ebae8c7..f94a8211cc75 100644 --- a/src/plugins/contracts/test-helpers/bundled-plugin-roots.ts +++ b/src/plugins/contracts/test-helpers/bundled-plugin-roots.ts @@ -34,7 +34,7 @@ export function resolveBundledPluginFile(params: { } /** Resolves a bundled plugin file as a path relative to a contract test root. */ -export function bundledPluginFile(params: { +export function contractPluginPath(params: { rootDir: string; pluginId: string; relativePath: string; diff --git a/src/plugins/install-persistence.test.ts b/src/plugins/install-persistence.test.ts index ac98b6f8c118..a25f9cd0ef76 100644 --- a/src/plugins/install-persistence.test.ts +++ b/src/plugins/install-persistence.test.ts @@ -8,7 +8,7 @@ import { buildPluginDiagnosticsReport, buildPluginSnapshotReport, clearPluginRegistryLoadCache, - enablePluginInConfig, + enablePluginInConfigMock, loadPluginManifestRegistry, planPluginUninstall, replaceConfigFile, @@ -101,7 +101,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockImplementation((...args: unknown[]) => { + enablePluginInConfigMock.mockImplementation((...args: unknown[]) => { const [cfg, pluginId] = args as [OpenClawConfig, string]; expect(pluginId).toBe("alpha"); expect(cfg.plugins?.allow).toEqual(["memory-core", "alpha"]); @@ -179,7 +179,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); clearPluginRegistryLoadCache.mockImplementation(() => { throw new Error("cache unavailable"); }); @@ -217,7 +217,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); setInstalledPluginIndexInstallRecords({ codex: { source: "clawhub", @@ -304,7 +304,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); setInstalledPluginIndexInstallRecords({ codex: { source: "npm", @@ -345,7 +345,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-persist-")); const previousProjectRoot = path.join(tempRoot, "npm", "projects", "codex-v1"); const previousInstallPath = path.join( @@ -447,7 +447,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); buildPluginSnapshotReport.mockReturnValue({ plugins: [ { @@ -506,7 +506,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); buildPluginSnapshotReport.mockReturnValue({ plugins: [ { @@ -550,7 +550,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); refreshPluginRegistry.mockRejectedValueOnce(new Error("registry unavailable")); const next = await persistPluginInstall({ @@ -587,7 +587,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); const next = await persistPluginInstall({ snapshot: { @@ -624,7 +624,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockImplementation((...args: unknown[]) => { + enablePluginInConfigMock.mockImplementation((...args: unknown[]) => { const [cfg, pluginId] = args as [OpenClawConfig, string]; expect(pluginId).toBe("alpha"); expect(cfg.plugins?.deny).toEqual(["other"]); @@ -665,7 +665,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); loadPluginManifestRegistry.mockReturnValue({ plugins: [createManifestRecord("legacy-memory")], diagnostics: [], @@ -742,7 +742,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); loadPluginManifestRegistry.mockReturnValue({ plugins: [createManifestRecord("memory-b", { kind: "memory" })], diagnostics: [], @@ -808,7 +808,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - enablePluginInConfig.mockReturnValue({ config: enabledConfig }); + enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); loadPluginManifestRegistry.mockReturnValue({ plugins: [createManifestRecord("plain")], diagnostics: [], @@ -896,7 +896,7 @@ describe("persistPluginInstall", () => { }, }, }); - expect(enablePluginInConfig).not.toHaveBeenCalled(); + expect(enablePluginInConfigMock).not.toHaveBeenCalled(); expect(applyExclusiveSlotSelection).not.toHaveBeenCalled(); expectRuntimeLogIncludes( 'Installed plugin "needs-config" without enabling it because it requires configuration first.', @@ -957,7 +957,7 @@ describe("persistPluginInstall", () => { }), ).rejects.toThrow("has invalid configured settings"); - expect(enablePluginInConfig).not.toHaveBeenCalled(); + expect(enablePluginInConfigMock).not.toHaveBeenCalled(); expect(writePersistedInstalledPluginIndexInstallRecordsWithLease).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); }); @@ -987,7 +987,7 @@ describe("persistPluginInstall", () => { }); expect(next).toEqual(baseConfig); - expect(enablePluginInConfig).not.toHaveBeenCalled(); + expect(enablePluginInConfigMock).not.toHaveBeenCalled(); expect(applyExclusiveSlotSelection).not.toHaveBeenCalled(); const persistedRecords = requireMockCallArg( writePersistedInstalledPluginIndexInstallRecordsWithLease, diff --git a/src/plugins/memory-runtime.test.ts b/src/plugins/memory-runtime.test.ts index e215509be1b9..7d88726ee1bf 100644 --- a/src/plugins/memory-runtime.test.ts +++ b/src/plugins/memory-runtime.test.ts @@ -30,9 +30,9 @@ vi.mock("./memory-state.js", async (importOriginal) => { import { authorizeActiveMemorySearchHits, - closeActiveMemorySearchManager, - closeActiveMemorySearchManagers, - getActiveMemorySearchManager, + closeActiveMemorySearchManagerCore, + closeActiveMemorySearchManagersCore, + getActiveMemorySearchManagerCore, resolveActiveMemoryBackendConfig, } from "./memory-runtime.js"; import { resetStandaloneMemoryRegistrySlot } from "./memory-runtime.test-support.js"; @@ -93,7 +93,7 @@ describe("memory runtime handles", () => { mocks.loadPluginRegistryHandle.mockReturnValue(registry); await expect( - getActiveMemorySearchManager({ cfg: memoryConfig, agentId: "main" }), + getActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "main" }), ).resolves.toEqual({ manager: null, error: "no index" }); expect(mocks.loadPluginRegistryHandle).toHaveBeenCalledWith({ @@ -121,17 +121,17 @@ describe("memory runtime handles", () => { }); expect(hasMemoryRuntime()).toBe(false); - await getActiveMemorySearchManager({ cfg: memoryConfig, agentId: "main" }); + await getActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "main" }); expect(hasMemoryRuntime()).toBe(true); - await closeActiveMemorySearchManagers(); + await closeActiveMemorySearchManagersCore(); expect(hasMemoryRuntime()).toBe(false); - await getActiveMemorySearchManager({ cfg: memoryConfig, agentId: "main" }); + await getActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "main" }); expect(hasMemoryRuntime()).toBe(true); expect(mocks.loadPluginRegistryHandle).toHaveBeenCalledTimes(1); - await closeActiveMemorySearchManagers(); + await closeActiveMemorySearchManagersCore(); expect(runtime.closeAllMemorySearchManagers).toHaveBeenCalledTimes(2); expect(hasMemoryRuntime()).toBe(false); }); @@ -143,14 +143,14 @@ describe("memory runtime handles", () => { .mockReturnValueOnce(main.registry) .mockReturnValueOnce(research.registry); - await getActiveMemorySearchManager({ cfg: memoryConfig, agentId: "main" }); - await getActiveMemorySearchManager({ cfg: memoryConfig, agentId: "research" }); + await getActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "main" }); + await getActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "research" }); expect(hasMemoryRuntime()).toBe(true); - await closeActiveMemorySearchManager({ cfg: memoryConfig, agentId: "main" }); + await closeActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "main" }); expect(hasMemoryRuntime()).toBe(true); - await closeActiveMemorySearchManagers(); + await closeActiveMemorySearchManagersCore(); expect(main.runtime.closeAllMemorySearchManagers).toHaveBeenCalledTimes(1); expect(research.runtime.closeAllMemorySearchManagers).toHaveBeenCalledTimes(1); expect(hasMemoryRuntime()).toBe(false); @@ -164,17 +164,17 @@ describe("memory runtime handles", () => { ); await expect( - getActiveMemorySearchManager({ cfg: memoryConfig, agentId: "main" }), + getActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "main" }), ).rejects.toThrow("manager initialization failed"); expect(hasMemoryRuntime()).toBe(true); runtime.closeAllMemorySearchManagers.mockRejectedValueOnce( new Error("manager teardown failed"), ); - await expect(closeActiveMemorySearchManagers()).rejects.toThrow("manager teardown failed"); + await expect(closeActiveMemorySearchManagersCore()).rejects.toThrow("manager teardown failed"); expect(hasMemoryRuntime()).toBe(true); - await closeActiveMemorySearchManagers(); + await closeActiveMemorySearchManagersCore(); expect(hasMemoryRuntime()).toBe(false); }); @@ -212,7 +212,7 @@ describe("memory runtime handles", () => { }, ])("does not load a disabled memory selection", async (cfg) => { await expect( - getActiveMemorySearchManager({ cfg: cfg as never, agentId: "main" }), + getActiveMemorySearchManagerCore({ cfg: cfg as never, agentId: "main" }), ).resolves.toEqual({ manager: null, error: "memory plugin unavailable" }); expect(mocks.loadPluginRegistryHandle).not.toHaveBeenCalled(); }); @@ -322,8 +322,8 @@ describe("memory runtime handles", () => { resolveActiveMemoryBackendConfig({ cfg: memoryConfig, agentId: "research" }); mocks.loadPluginRegistryHandle.mockClear(); - await closeActiveMemorySearchManager({ cfg: memoryConfig, agentId: "main" }); - await closeActiveMemorySearchManagers(memoryConfig); + await closeActiveMemorySearchManagerCore({ cfg: memoryConfig, agentId: "main" }); + await closeActiveMemorySearchManagersCore(memoryConfig); for (const { runtime } of [main, research]) { expect(runtime.closeMemorySearchManager).toHaveBeenCalledWith({ diff --git a/src/plugins/memory-runtime.ts b/src/plugins/memory-runtime.ts index 8c2e4c9d54e1..b622233cdd41 100644 --- a/src/plugins/memory-runtime.ts +++ b/src/plugins/memory-runtime.ts @@ -120,7 +120,7 @@ function ensureMemoryRuntime(params?: { } /** Returns the active plugin-backed memory search manager for an agent. */ -export async function getActiveMemorySearchManager(params: { +export async function getActiveMemorySearchManagerCore(params: { cfg: OpenClawConfig; agentId: string; purpose?: "default" | "status" | "cli"; @@ -165,7 +165,7 @@ export function resolveActiveMemoryBackendConfig(params: { cfg: OpenClawConfig; } /** Closes all active plugin-backed memory search managers. */ -export async function closeActiveMemorySearchManagers(cfg?: OpenClawConfig): Promise { +export async function closeActiveMemorySearchManagersCore(cfg?: OpenClawConfig): Promise { void cfg; await Promise.all( listCurrentMemoryRuntimeOwners().map((owner) => @@ -179,7 +179,7 @@ export async function closeActiveMemorySearchManagers(cfg?: OpenClawConfig): Pro } /** Closes the plugin-backed memory search manager for one agent. */ -export async function closeActiveMemorySearchManager(params: { +export async function closeActiveMemorySearchManagerCore(params: { cfg: OpenClawConfig; agentId: string; }): Promise { diff --git a/src/plugins/runtime/index.test.ts b/src/plugins/runtime/index.test.ts index 60c3ee81171b..77cd25f46394 100644 --- a/src/plugins/runtime/index.test.ts +++ b/src/plugins/runtime/index.test.ts @@ -14,7 +14,7 @@ import { VERSION } from "../../version.js"; const runtimeModelAuthMocks = vi.hoisted(() => ({ getApiKeyForModel: vi.fn(), - getRuntimeAuthForModel: vi.fn(), + getRuntimeAuthForModelCore: vi.fn(), resolveApiKeyForProvider: vi.fn(), })); const sandboxContextMocks = vi.hoisted(() => ({ @@ -102,7 +102,7 @@ describe("plugin runtime command execution", () => { beforeEach(() => { vi.restoreAllMocks(); runtimeModelAuthMocks.getApiKeyForModel.mockReset(); - runtimeModelAuthMocks.getRuntimeAuthForModel.mockReset(); + runtimeModelAuthMocks.getRuntimeAuthForModelCore.mockReset(); runtimeModelAuthMocks.resolveApiKeyForProvider.mockReset(); sandboxContextMocks.resolveSandboxContext.mockReset(); resetConfigRuntimeState(); diff --git a/src/plugins/runtime/index.ts b/src/plugins/runtime/index.ts index cbe3306de46b..17156c5bde01 100644 --- a/src/plugins/runtime/index.ts +++ b/src/plugins/runtime/index.ts @@ -142,7 +142,7 @@ function createRuntimeModelAuth(): PluginRuntime["modelAuth"] { ); const getRuntimeAuthForModel = createLazyRuntimeMethod( loadModelAuthRuntime, - (runtime) => runtime.getRuntimeAuthForModel, + (runtime) => runtime.getRuntimeAuthForModelCore, ); const resolveApiKeyForProvider = createLazyRuntimeMethod( loadModelAuthRuntime, diff --git a/src/plugins/runtime/runtime-channel.ts b/src/plugins/runtime/runtime-channel.ts index 5f3bac12dbb4..e3e0dcd0670e 100644 --- a/src/plugins/runtime/runtime-channel.ts +++ b/src/plugins/runtime/runtime-channel.ts @@ -29,7 +29,7 @@ import { matchesMentionPatterns, matchesMentionWithExplicit, } from "../../auto-reply/reply/mentions.js"; -import { dispatchReplyWithBufferedBlockDispatcher } from "../../auto-reply/reply/provider-dispatcher.js"; +import { dispatchReplyWithBufferedBlockDispatcherCore } from "../../auto-reply/reply/provider-dispatcher.js"; import { createReplyDispatcherWithTyping } from "../../auto-reply/reply/reply-dispatcher.js"; import { createAckReactionHandle, @@ -105,7 +105,7 @@ export function createRuntimeChannel(): PluginRuntime["channel"] { convertMarkdownTables, }, reply: { - dispatchReplyWithBufferedBlockDispatcher, + dispatchReplyWithBufferedBlockDispatcher: dispatchReplyWithBufferedBlockDispatcherCore, createReplyDispatcherWithTyping, resolveEffectiveMessagesConfig, resolveHumanDelayConfig, diff --git a/src/plugins/runtime/runtime-model-auth.runtime.test.ts b/src/plugins/runtime/runtime-model-auth.runtime.test.ts index d3733b2df30d..dfa6232bc43e 100644 --- a/src/plugins/runtime/runtime-model-auth.runtime.test.ts +++ b/src/plugins/runtime/runtime-model-auth.runtime.test.ts @@ -17,7 +17,7 @@ vi.mock("../provider-runtime.runtime.js", () => ({ })); let getApiKeyForModel: typeof import("./runtime-model-auth.runtime.js").getApiKeyForModel; -let getRuntimeAuthForModel: typeof import("./runtime-model-auth.runtime.js").getRuntimeAuthForModel; +let getRuntimeAuthForModelCore: typeof import("./runtime-model-auth.runtime.js").getRuntimeAuthForModelCore; let resolveApiKeyForProvider: typeof import("./runtime-model-auth.runtime.js").resolveApiKeyForProvider; const MODEL = { @@ -29,7 +29,7 @@ const MODEL = { describe("runtime-model-auth.runtime", () => { beforeAll(async () => { - ({ getApiKeyForModel, getRuntimeAuthForModel, resolveApiKeyForProvider } = + ({ getApiKeyForModel, getRuntimeAuthForModelCore, resolveApiKeyForProvider } = await import("./runtime-model-auth.runtime.js")); }); @@ -53,7 +53,7 @@ describe("runtime-model-auth.runtime", () => { }); await expect( - getRuntimeAuthForModel({ + getRuntimeAuthForModelCore({ model: MODEL as never, }), ).resolves.toEqual({ @@ -92,7 +92,7 @@ describe("runtime-model-auth.runtime", () => { hoisted.prepareProviderRuntimeAuth.mockResolvedValue(undefined); await expect( - getRuntimeAuthForModel({ + getRuntimeAuthForModelCore({ model: { ...MODEL, id: "openai/gpt-5.4", @@ -113,7 +113,7 @@ describe("runtime-model-auth.runtime", () => { }); await expect( - getRuntimeAuthForModel({ + getRuntimeAuthForModelCore({ model: { ...MODEL, id: "bedrock/claude-sonnet", diff --git a/src/plugins/runtime/runtime-model-auth.runtime.ts b/src/plugins/runtime/runtime-model-auth.runtime.ts index 22c39127dc81..310b5ee27726 100644 --- a/src/plugins/runtime/runtime-model-auth.runtime.ts +++ b/src/plugins/runtime/runtime-model-auth.runtime.ts @@ -24,7 +24,7 @@ export async function resolveApiKeyForProvider( * Resolve request-ready auth for a runtime model, applying any provider-owned * `prepareRuntimeAuth` exchange on top of the standard credential lookup. */ -export async function getRuntimeAuthForModel(params: { +export async function getRuntimeAuthForModelCore(params: { model: Model; cfg?: OpenClawConfig; workspaceDir?: string; diff --git a/src/skills/loading/session.ts b/src/skills/loading/session.ts index cf8b2df081d7..4a5942fcbac1 100644 --- a/src/skills/loading/session.ts +++ b/src/skills/loading/session.ts @@ -9,7 +9,7 @@ import { addIgnoreRules, toPosixPath, type IgnoreMatcher } from "../../shared/ig import { expandTildePath } from "../../shared/tilde-path.js"; import { getArchivedSkillFiles } from "../workshop/curator.js"; import { parseFrontmatter, resolveSkillInvocationPolicy } from "./frontmatter.js"; -import { formatSkillsForPrompt as formatSkillContractForPrompt } from "./skill-contract.js"; +import { formatSkillsForPromptCore } from "./skill-contract.js"; import { computeSkillPromptVersion } from "./skill-version.js"; /** Max name length per spec */ @@ -270,7 +270,7 @@ function loadSkillFromFile( */ export function formatSkillsForPrompt(skills: Skill[]): string { const visibleSkills = skills.filter((s) => !s.disableModelInvocation); - return formatSkillContractForPrompt(visibleSkills); + return formatSkillsForPromptCore(visibleSkills); } interface LoadSkillsOptions { diff --git a/src/skills/loading/skill-contract.test.ts b/src/skills/loading/skill-contract.test.ts index 8d33c05cff1e..ed7f5e0c3198 100644 --- a/src/skills/loading/skill-contract.test.ts +++ b/src/skills/loading/skill-contract.test.ts @@ -2,7 +2,7 @@ import { formatSkillsForPrompt as upstreamFormatSkillsForPrompt } from "openclaw/plugin-sdk/agent-sessions"; import { describe, expect, it } from "vitest"; import { createCanonicalFixtureSkill } from "../test-support/test-helpers.js"; -import { formatSkillsForPrompt, type Skill } from "./skill-contract.js"; +import { formatSkillsForPromptCore, type Skill } from "./skill-contract.js"; import { formatSkillsCompactForPrompt as formatSkillsCompact } from "./skill-contract.js"; function makeSkill(name: string, desc = "A skill", filePath = `/skills/${name}/SKILL.md`): Skill { @@ -21,12 +21,12 @@ describe("formatSkillsCompact", () => { { ...makeSkill("weather", "Get weather & forecasts"), promptVersion: "sha256:abc123" }, makeSkill("notes", "Summarize notes", "/tmp/notes/SKILL.md"), ]; - expect(formatSkillsForPrompt(skills)).toBe(upstreamFormatSkillsForPrompt(skills)); + expect(formatSkillsForPromptCore(skills)).toBe(upstreamFormatSkillsForPrompt(skills)); }); it("renders all passed skills in the full formatter without reapplying visibility policy", () => { const hidden: Skill = { ...makeSkill("hidden"), disableModelInvocation: true }; - const out = formatSkillsForPrompt([makeSkill("visible"), hidden]); + const out = formatSkillsForPromptCore([makeSkill("visible"), hidden]); expect(out).toContain("visible"); expect(out).toContain("hidden"); }); @@ -91,6 +91,6 @@ describe("formatSkillsCompact", () => { it("is significantly smaller than full format", () => { const skills = Array.from({ length: 50 }, (_, i) => makeSkill(`skill-${i}`, "A".repeat(800))); const compact = formatSkillsCompact(skills); - expect(compact.length).toBeLessThan(formatSkillsForPrompt(skills).length / 2); + expect(compact.length).toBeLessThan(formatSkillsForPromptCore(skills).length / 2); }); }); diff --git a/src/skills/loading/skill-contract.ts b/src/skills/loading/skill-contract.ts index 3ab5e89bedf1..6295b9c4f4f9 100644 --- a/src/skills/loading/skill-contract.ts +++ b/src/skills/loading/skill-contract.ts @@ -49,7 +49,7 @@ function truncateSkillDescription(description: string, maxChars: number): string * package root on the cold skills path. Visibility policy is applied upstream * before calling this helper. */ -export function formatSkillsForPrompt(skills: Skill[]): string { +export function formatSkillsForPromptCore(skills: Skill[]): string { if (skills.length === 0) { return ""; } diff --git a/src/skills/loading/workspace-skill-prompt.test.ts b/src/skills/loading/workspace-skill-prompt.test.ts index 13a767b13e36..87de66da227d 100644 --- a/src/skills/loading/workspace-skill-prompt.test.ts +++ b/src/skills/loading/workspace-skill-prompt.test.ts @@ -14,7 +14,7 @@ import { createCanonicalFixtureSkill } from "../test-support/test-helpers.js"; import type { SkillEntry } from "../types.js"; import { formatSkillsCompactForPrompt as formatSkillsCompact, - formatSkillsForPrompt, + formatSkillsForPromptCore, type Skill, } from "./skill-contract.js"; import { buildSkillSnapshot } from "./workspace-skill-prompt.js"; @@ -118,7 +118,7 @@ describe("applySkillsPromptLimits (via buildWorkspaceSkillsPrompt)", () => { it("tier 2: compact when full exceeds budget but compact fits", () => { const skills = Array.from({ length: 20 }, (_, i) => makeSkill(`skill-${i}`, "A".repeat(800))); - const fullLen = formatSkillsForPrompt(skills).length; + const fullLen = formatSkillsForPromptCore(skills).length; const compactLen = formatSkillsCompact(skills).length; const budget = `${COMPACT_SHORTENED_NOTICE}\n${formatSkillsCompact(skills)}`.length; expect(fullLen).toBeGreaterThan(budget); @@ -146,7 +146,7 @@ describe("applySkillsPromptLimits (via buildWorkspaceSkillsPrompt)", () => { const skills = Array.from({ length: 50 }, (_, i) => makeSkill(`skill-${i}`, "A".repeat(800))); const identityCatalog = formatSkillsCompact(skills, { descriptionMaxChars: 0 }); const budget = `${COMPACT_OMITTED_NOTICE}\n${identityCatalog}`.length; - expect(formatSkillsForPrompt(skills).length).toBeGreaterThan(budget); + expect(formatSkillsForPromptCore(skills).length).toBeGreaterThan(budget); const prompt = buildPrompt(skills, { maxChars: budget }); @@ -176,7 +176,7 @@ describe("applySkillsPromptLimits (via buildWorkspaceSkillsPrompt)", () => { // 30 skills but maxCount=10, and full format of 10 exceeds budget const skills = Array.from({ length: 30 }, (_, i) => makeSkill(`skill-${i}`, "A".repeat(800))); const tenSkills = skills.slice(0, 10); - const fullLen = formatSkillsForPrompt(tenSkills).length; + const fullLen = formatSkillsForPromptCore(tenSkills).length; const truncatedNotice = "⚠️ Skills truncated: included 10 of 30 (compact format, descriptions shortened). Run `openclaw skills check` to audit."; const budget = `${truncatedNotice}\n${formatSkillsCompact(tenSkills)}`.length; @@ -206,7 +206,7 @@ describe("applySkillsPromptLimits (via buildWorkspaceSkillsPrompt)", () => { it("drops an oversized optional remote note before discarding a complete fitting skill catalog", () => { const skill = makeSkill("weather", "Get weather data"); - const maxChars = formatSkillsForPrompt([skill]).length; + const maxChars = formatSkillsForPromptCore([skill]).length; const remoteNote = `REMOTE_NOTE_${"x".repeat(maxChars + 512)}`; const prompt = buildWorkspaceSkillsPrompt("/fake", { entries: [makeEntry(skill)], diff --git a/src/skills/loading/workspace-skill-prompt.ts b/src/skills/loading/workspace-skill-prompt.ts index 91b4828136f9..a90ced23f470 100644 --- a/src/skills/loading/workspace-skill-prompt.ts +++ b/src/skills/loading/workspace-skill-prompt.ts @@ -9,7 +9,7 @@ import { resolveSkillKey } from "./frontmatter.js"; import { escapeSkillXml, formatSkillsCompactForPrompt, - formatSkillsForPrompt, + formatSkillsForPromptCore, type Skill, } from "./skill-contract.js"; import { compactPromptSkills } from "./skill-paths.js"; @@ -87,7 +87,7 @@ function buildRenderedSkillsPrompt(params: { ? formatSkillsCompactForPrompt(params.skills, { descriptionMaxChars: params.format.descriptionMaxChars, }) - : formatSkillsForPrompt(params.skills); + : formatSkillsForPromptCore(params.skills); return [params.remoteNote, limitNote, catalog].filter(Boolean).join("\n"); } diff --git a/src/talk/fast-context-runtime.test.ts b/src/talk/fast-context-runtime.test.ts index 05e64296cf81..3a8b8bc53560 100644 --- a/src/talk/fast-context-runtime.test.ts +++ b/src/talk/fast-context-runtime.test.ts @@ -4,12 +4,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ authorizeActiveMemorySearchHits: vi.fn(), - getActiveMemorySearchManager: vi.fn(), + getActiveMemorySearchManagerCore: vi.fn(), })); vi.mock("../plugins/memory-runtime.js", () => ({ authorizeActiveMemorySearchHits: mocks.authorizeActiveMemorySearchHits, - getActiveMemorySearchManager: mocks.getActiveMemorySearchManager, + getActiveMemorySearchManagerCore: mocks.getActiveMemorySearchManagerCore, })); import { resolveRealtimeVoiceFastContextConsult } from "./fast-context-runtime.js"; @@ -17,7 +17,7 @@ import { resolveRealtimeVoiceFastContextConsult } from "./fast-context-runtime.j describe("resolveRealtimeVoiceFastContextConsult", () => { beforeEach(() => { mocks.authorizeActiveMemorySearchHits.mockReset().mockImplementation(async ({ hits }) => hits); - mocks.getActiveMemorySearchManager.mockReset(); + mocks.getActiveMemorySearchManagerCore.mockReset(); }); afterEach(() => { @@ -27,7 +27,7 @@ describe("resolveRealtimeVoiceFastContextConsult", () => { it("caps oversized fast-context timeouts before scheduling Node timers", async () => { const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); - mocks.getActiveMemorySearchManager.mockResolvedValue({ + mocks.getActiveMemorySearchManagerCore.mockResolvedValue({ manager: { search: vi.fn().mockResolvedValue([]), }, @@ -56,7 +56,7 @@ describe("resolveRealtimeVoiceFastContextConsult", () => { it("preserves the fast-context timeout error and clears the timer", async () => { vi.useFakeTimers(); const logger = { debug: vi.fn() }; - mocks.getActiveMemorySearchManager.mockResolvedValue({ + mocks.getActiveMemorySearchManagerCore.mockResolvedValue({ manager: { search: vi.fn(() => new Promise(() => {})), }, @@ -88,7 +88,7 @@ describe("resolveRealtimeVoiceFastContextConsult", () => { it("does not split a surrogate pair at the fast-context snippet limit", async () => { const safePrefix = "x".repeat(698); - mocks.getActiveMemorySearchManager.mockResolvedValue({ + mocks.getActiveMemorySearchManagerCore.mockResolvedValue({ manager: { search: vi.fn().mockResolvedValue([ { @@ -146,7 +146,7 @@ describe("resolveRealtimeVoiceFastContextConsult", () => { score: 1, }, ]; - mocks.getActiveMemorySearchManager.mockResolvedValue({ + mocks.getActiveMemorySearchManagerCore.mockResolvedValue({ manager: { search: vi.fn().mockResolvedValue(hits) }, }); mocks.authorizeActiveMemorySearchHits.mockResolvedValue([hits[0]]); diff --git a/src/talk/fast-context-runtime.ts b/src/talk/fast-context-runtime.ts index 044ecac47abf..45eb60248c7a 100644 --- a/src/talk/fast-context-runtime.ts +++ b/src/talk/fast-context-runtime.ts @@ -12,7 +12,7 @@ import { formatErrorMessage } from "../infra/errors.js"; import type { MemorySearchResult } from "../memory-host-sdk/host/types.js"; import { authorizeActiveMemorySearchHits, - getActiveMemorySearchManager, + getActiveMemorySearchManagerCore, } from "../plugins/memory-runtime.js"; import { withTimeout } from "../utils/with-timeout.js"; import type { RealtimeVoiceAgentConsultResult } from "./agent-consult-runtime.js"; @@ -111,7 +111,7 @@ async function lookupFastContext(params: { }): Promise { // The memory runtime owns whether memory/session search is active for this // agent. Talk only consumes the current manager when it is already available. - const memory = await getActiveMemorySearchManager({ + const memory = await getActiveMemorySearchManagerCore({ cfg: params.cfg, agentId: params.agentId, }); diff --git a/src/tts/tts-provider-resolution.ts b/src/tts/tts-provider-resolution.ts index 5513c750c2d4..a2f048c18c1c 100644 --- a/src/tts/tts-provider-resolution.ts +++ b/src/tts/tts-provider-resolution.ts @@ -22,7 +22,6 @@ import { DEFAULT_TTS_TIMEOUT_MS, asProviderConfig, asProviderConfigMap, - hasOwnProperty, normalizeConfiguredSpeechProviderId, readTtsPrefs as readPrefs, resolveTtsPersonaFromPrefs, @@ -164,10 +163,10 @@ export function resolvePersonaProviderConfig( return undefined; } const normalized = normalizeConfiguredSpeechProviderId(providerId) ?? providerId; - if (hasOwnProperty(persona.providers, normalized)) { + if (Object.hasOwn(persona.providers, normalized)) { return persona.providers[normalized]; } - if (hasOwnProperty(persona.providers, providerId)) { + if (Object.hasOwn(persona.providers, providerId)) { return persona.providers[providerId]; } return undefined; diff --git a/src/tts/tts-settings.ts b/src/tts/tts-settings.ts index b851a488f9ec..4feb3c091d37 100644 --- a/src/tts/tts-settings.ts +++ b/src/tts/tts-settings.ts @@ -134,10 +134,6 @@ export function asProviderConfigMap(value: unknown): Record { : {}; } -export function hasOwnProperty(value: object, key: string): boolean { - return Object.hasOwn(value, key); -} - function normalizeProviderConfigMap( value: unknown, ): Record | undefined { @@ -281,7 +277,7 @@ function resolveTtsPersonaIdFromPrefs( config: ResolvedTtsConfig, prefs: TtsUserPrefs, ): string | undefined { - if (prefs.tts && hasOwnProperty(prefs.tts, "persona")) { + if (prefs.tts && Object.hasOwn(prefs.tts, "persona")) { return normalizeTtsPersonaId(prefs.tts.persona); } return normalizeTtsPersonaId(config.persona); diff --git a/src/tui/tui-event-handlers.ts b/src/tui/tui-event-handlers.ts index b2b813cf0436..80d1312d23fa 100644 --- a/src/tui/tui-event-handlers.ts +++ b/src/tui/tui-event-handlers.ts @@ -9,7 +9,7 @@ import { extractTextFromMessage, extractTuiAbortedText, formatTuiAbortDiagnostic, - isCommandMessage, + isCommandMarkedMessage, } from "./tui-formatters.js"; import { createTuiRunLifecycle } from "./tui-run-lifecycle.js"; import { matchesSelectedTuiSession, readTuiSessionUserMessage } from "./tui-session-events.js"; @@ -300,7 +300,7 @@ export function createEventHandlers(context: EventHandlerContext) { tui.requestRender(true); return; } - if (isCommandMessage(evt.message)) { + if (isCommandMarkedMessage(evt.message)) { maybeRefreshHistoryForRun(evt.runId, { wasPendingChatRun: isPendingChatRun }); const text = extractTextFromMessage(evt.message); if (text) { diff --git a/src/tui/tui-formatters.test.ts b/src/tui/tui-formatters.test.ts index 9d30915d696c..b351b4282b46 100644 --- a/src/tui/tui-formatters.test.ts +++ b/src/tui/tui-formatters.test.ts @@ -11,7 +11,7 @@ import { formatTuiErrorMessage, isolateRtlRenderedLine, isTerminalSafeAutocompleteValue, - isCommandMessage, + isCommandMarkedMessage, sanitizeMarkdownSource, sanitizeRenderableLine, sanitizeRenderableText, @@ -642,11 +642,11 @@ describe("extractContentFromMessage", () => { }); }); -describe("isCommandMessage", () => { +describe("isCommandMarkedMessage", () => { it("detects command-marked messages", () => { - expect(isCommandMessage({ command: true })).toBe(true); - expect(isCommandMessage({ command: false })).toBe(false); - expect(isCommandMessage({})).toBe(false); + expect(isCommandMarkedMessage({ command: true })).toBe(true); + expect(isCommandMarkedMessage({ command: false })).toBe(false); + expect(isCommandMarkedMessage({})).toBe(false); }); }); diff --git a/src/tui/tui-formatters.ts b/src/tui/tui-formatters.ts index 1c56440a8dfe..b541e58845b1 100644 --- a/src/tui/tui-formatters.ts +++ b/src/tui/tui-formatters.ts @@ -680,7 +680,7 @@ export function extractTuiAbortedText(message: unknown, includeThinking: boolean return extractTextFromMessage(message, { includeThinking, includeAttachments: false }); } -export function isCommandMessage(message: unknown): boolean { +export function isCommandMarkedMessage(message: unknown): boolean { if (!message || typeof message !== "object") { return false; } diff --git a/src/tui/tui-session-actions.ts b/src/tui/tui-session-actions.ts index fa5554a78d15..5bf5c755b159 100644 --- a/src/tui/tui-session-actions.ts +++ b/src/tui/tui-session-actions.ts @@ -17,7 +17,7 @@ import { formatPrimitiveString, extractTextFromMessage, formatTuiErrorMessage, - isCommandMessage, + isCommandMarkedMessage, } from "./tui-formatters.js"; import { readTuiSessionUserMessage } from "./tui-session-events.js"; import { @@ -492,7 +492,7 @@ export function createSessionActions(context: SessionActionContext) { chatLog.addSystem(`session ${state.currentSessionKey}`); for (const entry of projection.entries) { const message = entry.message as Record; - if (isCommandMessage(message)) { + if (isCommandMarkedMessage(message)) { const text = extractTextFromMessage(message); if (text) { chatLog.addSystem(text);