diff --git a/docs/.generated/plugin-sdk-api-baseline.jsonl b/docs/.generated/plugin-sdk-api-baseline.jsonl index 4606827d2f6d..e4e21ed5a025 100644 --- a/docs/.generated/plugin-sdk-api-baseline.jsonl +++ b/docs/.generated/plugin-sdk-api-baseline.jsonl @@ -3,8 +3,8 @@ {"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":"f30d89dee0fb94ebfe1571fe2bdf7e117e6426f3a5b03f0415e6b539ca12b943","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} -{"contentHash":"f18c65eb2db4ddb54b5e501594688e820487aa945c72dd5d7e9d06afa91aa12a","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} +{"contentHash":"bb0856cd7a223d74401fc188fa9f081a5b82e776ef6498dba296da8d2a0b2864","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} +{"contentHash":"b5497b38577a038b606a6177b76cdbea4c4ea695a0dd5bfd1adc4de48bbaed05","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":"b065a946eca85d03276bc0f6b1c04af1e44ee4ce441e5a5477f13d13aa3de17b","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} {"contentHash":"1f0e51c8b1a58fff13ab7d19806d97bf07fca61a5c7edeb3437cd805775a8320","entrypoint":"agent-scope-runtime","importSpecifier":"openclaw/plugin-sdk/agent-scope-runtime"} @@ -26,19 +26,19 @@ {"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":"b9e5e152f530124b86dac1b02a3cb1271e37dd3eb835af2da676c43de7dfdd35","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} +{"contentHash":"042a11e9ae14f1293a0efc5f14dd98dc71e5677af19aa6adb9f26dcee3ce631b","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} {"contentHash":"b2d90f161ca88ef2d5ff35f87f160c772bd784a4ebda91b2ab33bf16b6abd843","entrypoint":"channel-dm-policy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy"} -{"contentHash":"d2d8bf000c52d751fdbb3bd08f9215e9a7b4d6f4632ad09d91f60f1139da8f09","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} +{"contentHash":"1ab53e153d928e4be99e298da4a41919d3db56ce33dcd7fd9139587449752ec3","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} {"contentHash":"8bc8b784c78a70ee58731aae0be9731c9af01705db00acbafa25de9745a23601","entrypoint":"channel-feedback","importSpecifier":"openclaw/plugin-sdk/channel-feedback"} {"contentHash":"6da95ea5bb2850b714f47387e1e970b761ac64d02e40531e797d0584e2c6434d","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":"0aad801d7505694ad46a46e4f54852153ae22be7bd2c9c64dc44a1b642d974d8","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} -{"contentHash":"bda40bf304b4d38209aba40586b19737b253398d0e57812fda34188bc556519e","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} +{"contentHash":"cf5e7b2211fe15e9a9255c8bff718fe9601f71581bfba4a9e3bf435675ff9ac8","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} +{"contentHash":"60339c394cd46438541a215e1994ad6b112d474ce6e8085348233a51ad60749c","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} {"contentHash":"fee97710da33e100a8987aed897be17ab3ec4ece9fa09b7eb871a90461c8fca3","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"} -{"contentHash":"65365aece1a592090c027df963f085dc866c49d5f3df45075aa93b49bfac424c","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} +{"contentHash":"2552565320d568841872f44e1c67a308ac2c1491d80ca90c1f37e04638546316","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":"5b4062252b1aff65fb307749c5c8a26a262d0706f155ccba450f03667e8a558e","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} +{"contentHash":"dd0e6af04e79380a243d607bdac2087eec0cf7ba0b61962123c2537e9bfb932a","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":"3da69ae53cb439a6fc479243a0b7284e810e2bc8d6fdc624434728c39221041a","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} +{"contentHash":"6a17cccf6d87c29e973dd0ca744c4233e8bb63ea4ed14d0323829b3a6a6aab82","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,7 +75,7 @@ {"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":"88c7ab1e598b1a7c2a8abfc0eb4ca4ed94a874f212716c89a21304b1b5245e54","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} +{"contentHash":"d87dd19703ed402cdc76e2bd23cb4a03424f45d6760508e4a7be63a7e39302ed","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"} @@ -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":"f52f40d1b2e237c3cee606ac938363c3fd7239ac147f3e178cf1c87205ca33c4","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} +{"contentHash":"f3dbb0428db5445bab1798b7bcdb027d7a6ddfc7180ae1638429e596fb07e5a6","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,10 +101,10 @@ {"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":"c8d26e90a94c5b5c863ef447e6ee3a24d2959b159c83e183b577febd44a43328","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} -{"contentHash":"2bb9e09fd216479e0bc2d57913cd22875b0c828535a7ae38a91b794b76e6ef6d","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} +{"contentHash":"197b3312b655a8d1f0413e1fe1225d89a47bc2de9012c62bc503249c8b24ab1d","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} +{"contentHash":"dbfe38427c6ac1a571e7f4ad7319ec493f5171f8ff11fcde1141e9792733faa3","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} {"contentHash":"d5d21f2a0883eac72d6642d93306c36e2de0f3851286eb3edbe3a2d9d6122dcc","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"} -{"contentHash":"ecc430c95b12731f3b0af98d902b29a5e1361c286893c49c4def196def29636f","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} +{"contentHash":"d89b0f5353b489b80f2689f37930f4369fa29d57e5209883c89020f9d024f464","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"} @@ -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":"14aaeec8a1c4de5efb08f31aa30fca3174885b8c5e59704922617e78ca2bfcc9","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} +{"contentHash":"423f237d4e9bf6683fe46d23b2456f970efcb72e7b352acd46aa7ec43ee4c249","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":"3ae569776c2e6812f86c99e62d6091ef1a3554da73c4f70a0337636f4b4c1e28","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} +{"contentHash":"c21e3d7b10c804125eb871726167fc8d63a45aae3a7dacebc23602e28ede7cae","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/scripts/lib/export-name-collision-baseline.json b/scripts/lib/export-name-collision-baseline.json index 932adde69960..49f198224619 100644 --- a/scripts/lib/export-name-collision-baseline.json +++ b/scripts/lib/export-name-collision-baseline.json @@ -1,88 +1,4 @@ [ - { - "name": "agentCommand", - "files": [ - "src/agents/agent-command.ts", - "src/gateway/test-helpers.runtime-state.ts" - ] - }, - { - "name": "applyExclusiveSlotSelection", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/slots.ts" - ] - }, - { - "name": "applyFinalEffectiveToolPolicy", - "files": [ - "src/agents/embedded-agent-runner/effective-tool-policy.ts", - "src/gateway/server-methods/__mocks__/tools-effective.runtime.ts" - ] - }, - { - "name": "applyPluginUninstallDirectoryRemoval", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/uninstall.ts" - ] - }, - { - "name": "buildBundleMcpToolsFromCatalog", - "files": [ - "src/agents/agent-bundle-mcp-materialize.ts", - "src/gateway/server-methods/__mocks__/tools-effective.runtime.ts" - ] - }, - { - "name": "buildPluginDiagnosticsReport", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/status.ts" - ] - }, - { - "name": "buildPluginInspectReport", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/status.ts" - ] - }, - { - "name": "buildPluginRegistrySnapshotReport", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/status-snapshot.ts" - ] - }, - { - "name": "buildPluginSnapshotReport", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/status.ts" - ] - }, - { - "name": "callGateway", - "files": [ - "src/cli/program.test-mocks.ts", - "src/gateway/call.ts" - ] - }, - { - "name": "callGatewayCli", - "files": [ - "src/cli/nodes-cli/rpc.ts", - "src/gateway/call.ts" - ] - }, - { - "name": "classifySessionKind", - "files": [ - "src/agents/tools/sessions-helpers.ts", - "src/sessions/classify-session-kind.ts" - ] - }, { "name": "cleanupSessionLifecycleArtifacts", "files": [ @@ -91,13 +7,6 @@ ], "sdk": true }, - { - "name": "clearPluginRegistryLoadCache", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/loader-cache.ts" - ] - }, { "name": "clearSecretsRuntimeSnapshot", "files": [ @@ -105,20 +14,6 @@ "src/secrets/runtime.ts" ] }, - { - "name": "containsConfigIncludeDirective", - "files": [ - "src/config/io.read-helpers.ts", - "src/plugins/install-persistence.ts" - ] - }, - { - "name": "createGatewayStartupTrace", - "files": [ - "src/cli/startup-trace.ts", - "src/gateway/server-startup-trace.ts" - ] - }, { "name": "createPluginLoaderLogger", "files": [ @@ -126,146 +21,6 @@ "src/plugins/logger.ts" ] }, - { - "name": "createSuiteTempRootTracker", - "files": [ - "src/plugins/test-helpers/fs-fixtures.ts", - "src/test-helpers/temp-dir.ts" - ] - }, - { - "name": "doctorCommand", - "files": [ - "src/commands/doctor.ts", - "src/flows/doctor-health.ts" - ] - }, - { - "name": "extractAssistantVisibleText", - "files": [ - "src/agents/embedded-agent-utils.ts", - "src/shared/chat-message-content.ts" - ] - }, - { - "name": "extractMessageText", - "files": [ - "src/auto-reply/reply/commands-subagents-text.ts", - "src/gateway/session-transcript-readers.ts" - ] - }, - { - "name": "formatConfigPath", - "files": [ - "src/commands/doctor-config-analysis.ts", - "src/config/logging.ts" - ] - }, - { - "name": "getActivePluginChannelRegistryVersion", - "files": [ - "src/gateway/server-methods/__mocks__/tools-effective.runtime.ts", - "src/plugins/runtime.ts" - ] - }, - { - "name": "getActivePluginRegistryVersion", - "files": [ - "src/gateway/server-methods/__mocks__/tools-effective.runtime.ts", - "src/plugins/runtime.ts" - ] - }, - { - "name": "getFreePort", - "files": [ - "src/gateway/test-helpers.server.ts", - "src/test-utils/ports.ts" - ] - }, - { - "name": "inspectPluginRegistry", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/plugin-registry-snapshot.ts" - ] - }, - { - "name": "inspectPortUsage", - "files": [ - "src/daemon/test-helpers/schtasks-fixtures.ts", - "src/infra/ports-inspect.ts" - ] - }, - { - "name": "installHooksFromNpmSpec", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/hooks/install.ts" - ] - }, - { - "name": "installHooksFromPath", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/hooks/install.ts" - ] - }, - { - "name": "installPluginFromClawHub", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/clawhub.ts" - ] - }, - { - "name": "installPluginFromGitSpec", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/git-install.ts" - ] - }, - { - "name": "installPluginFromMarketplace", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/marketplace.ts" - ] - }, - { - "name": "installPluginFromNpmPackArchive", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/install-npm-pack.ts" - ] - }, - { - "name": "installPluginFromNpmSpec", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/install-npm.ts" - ] - }, - { - "name": "installPluginFromPath", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/install-package.ts" - ] - }, - { - "name": "killProcessTree", - "files": [ - "src/agents/shell-utils.ts", - "src/daemon/test-helpers/schtasks-fixtures.ts" - ] - }, - { - "name": "listManagedPluginNpmRoots", - "files": [ - "src/commands/doctor-plugin-host-links.ts", - "src/plugins/npm-project-roots.ts" - ] - }, { "name": "listMemoryEmbeddingProviders", "files": [ @@ -322,13 +77,6 @@ ], "sdk": true }, - { - "name": "loadGatewayTlsRuntime", - "files": [ - "src/gateway/server/tls.ts", - "src/infra/tls/gateway.ts" - ] - }, { "name": "loadJsonFile", "files": [ @@ -377,23 +125,6 @@ ], "sdk": true }, - { - "name": "log", - "files": [ - "src/agents/auth-profiles/constants.ts", - "src/agents/embedded-agent-runner/logger.ts", - "src/agents/main-session-recovery/main-session-restart-recovery-shared.ts", - "src/system-agent/setup-inference-core.ts", - "src/tasks/task-registry-state.ts" - ] - }, - { - "name": "makeTempDir", - "files": [ - "src/infra/exec-approvals-test-helpers.ts", - "src/plugins/loader.test-fixtures.ts" - ] - }, { "name": "materializeRequesterScopedMcpToolsForHarnessRun", "files": [ @@ -410,13 +141,6 @@ ], "sdk": true }, - { - "name": "MAX_TIMER_DELAY_MS", - "files": [ - "src/cron/service/timer-execution-timeout.ts", - "src/gateway/probe.ts" - ] - }, { "name": "maybeApplyTtsToPayload", "files": [ diff --git a/scripts/lib/wrapper-shadowing-baseline.json b/scripts/lib/wrapper-shadowing-baseline.json index 61cddbfdcc59..ab560c2a7e92 100644 --- a/scripts/lib/wrapper-shadowing-baseline.json +++ b/scripts/lib/wrapper-shadowing-baseline.json @@ -162,11 +162,6 @@ "wrapped": "src/config/sessions/combined-store-gateway.ts", "wrapper": "src/plugin-sdk/session-transcript-hit.ts" }, - { - "name": "loadGatewayTlsRuntime", - "wrapped": "src/infra/tls/gateway.ts", - "wrapper": "src/gateway/server/tls.ts" - }, { "name": "loadPluginManifestRegistry", "wrapped": "src/plugins/manifest-registry.ts", diff --git a/src/agents/auth-profiles/constants.ts b/src/agents/auth-profiles/constants.ts index b5f60a287d4e..b07d2cc175d9 100644 --- a/src/agents/auth-profiles/constants.ts +++ b/src/agents/auth-profiles/constants.ts @@ -49,4 +49,4 @@ export const OAUTH_REFRESH_CALL_TIMEOUT_MS = 120_000; export const EXTERNAL_CLI_SYNC_TTL_MS = 15 * 60 * 1000; /** Auth profile subsystem logger. */ -export const log = createSubsystemLogger("agents/auth-profiles"); +export const authProfilesLog = createSubsystemLogger("agents/auth-profiles"); diff --git a/src/agents/auth-profiles/external-cli-sync.ts b/src/agents/auth-profiles/external-cli-sync.ts index db1363d355b1..991408e7700c 100644 --- a/src/agents/auth-profiles/external-cli-sync.ts +++ b/src/agents/auth-profiles/external-cli-sync.ts @@ -15,7 +15,7 @@ import { MINIMAX_CLI_PROFILE_ID, OPENAI_CODEX_DEFAULT_PROFILE_ID, } from "./constants.js"; -import { log } from "./constants.js"; +import { authProfilesLog } from "./constants.js"; import { hasUsableOAuthCredential } from "./credential-state.js"; import { isSafeToCopyOAuthIdentity } from "./oauth-identity.js"; import { @@ -337,7 +337,7 @@ export function resolveExternalCliAuthProfiles( ? existing : undefined; if (existing && !existingOAuth) { - log.debug("kept explicit local auth over external cli bootstrap", { + authProfilesLog.debug("kept explicit local auth over external cli bootstrap", { profileId, provider: providerConfig.provider, localType: existing.type, @@ -350,7 +350,7 @@ export function resolveExternalCliAuthProfiles( existingOAuth && hasInlineOAuthTokenMaterial(existingOAuth) ) { - log.debug("kept local oauth over external cli bootstrap-only provider", { + authProfilesLog.debug("kept local oauth over external cli bootstrap-only provider", { profileId, provider: providerConfig.provider, }); @@ -383,7 +383,7 @@ export function resolveExternalCliAuthProfiles( continue; } if (existingOAuth && !isSafeToUseExternalCliCredential(existingOAuth, creds)) { - log.warn("refused external cli oauth bootstrap: identity mismatch", { + authProfilesLog.warn("refused external cli oauth bootstrap: identity mismatch", { profileId, provider: providerConfig.provider, }); @@ -394,10 +394,13 @@ export function resolveExternalCliAuthProfiles( !isSafeToAdoptBootstrapOAuthIdentity(existingOAuth, creds) && !areOAuthCredentialsEquivalent(existingOAuth, creds) ) { - log.warn("refused external cli oauth bootstrap: identity mismatch or missing binding", { - profileId, - provider: providerConfig.provider, - }); + authProfilesLog.warn( + "refused external cli oauth bootstrap: identity mismatch or missing binding", + { + profileId, + provider: providerConfig.provider, + }, + ); continue; } if ( @@ -408,7 +411,7 @@ export function resolveExternalCliAuthProfiles( }) ) { if (existingOAuth) { - log.debug("kept usable local oauth over external cli bootstrap", { + authProfilesLog.debug("kept usable local oauth over external cli bootstrap", { profileId, provider: providerConfig.provider, localExpires: existingOAuth.expires, @@ -417,12 +420,15 @@ export function resolveExternalCliAuthProfiles( } continue; } - log.debug("used external cli oauth bootstrap because local oauth was missing or unusable", { - profileId, - provider: providerConfig.provider, - localExpires: existingOAuth?.expires, - externalExpires: creds.expires, - }); + authProfilesLog.debug( + "used external cli oauth bootstrap because local oauth was missing or unusable", + { + profileId, + provider: providerConfig.provider, + localExpires: existingOAuth?.expires, + externalExpires: creds.expires, + }, + ); profiles.push({ profileId, credential: creds, diff --git a/src/agents/auth-profiles/oauth-manager.ts b/src/agents/auth-profiles/oauth-manager.ts index ac989c8c51fd..3de2653d659b 100644 --- a/src/agents/auth-profiles/oauth-manager.ts +++ b/src/agents/auth-profiles/oauth-manager.ts @@ -10,7 +10,11 @@ import { withFileLock } from "../../infra/file-lock.js"; import { redactSensitiveText } from "../../logging/redact.js"; import { KeyedAsyncQueue } from "../../plugin-sdk/keyed-async-queue.js"; import { asDateTimestampMs } from "../../shared/number-coercion.js"; -import { OAUTH_REFRESH_CALL_TIMEOUT_MS, OAUTH_REFRESH_LOCK_OPTIONS, log } from "./constants.js"; +import { + OAUTH_REFRESH_CALL_TIMEOUT_MS, + OAUTH_REFRESH_LOCK_OPTIONS, + authProfilesLog, +} from "./constants.js"; import { hasUsableOAuthCredential } from "./credential-state.js"; import { shouldMirrorRefreshedOAuthCredential } from "./oauth-identity.js"; import { OAuthRefreshFailureError } from "./oauth-refresh-failure.js"; @@ -273,7 +277,7 @@ export function resolveEffectiveOAuthCredentialCore(params: { return params.credential; } if (hasUsableOAuthCredential(params.credential)) { - log.debug("resolved oauth credential from canonical local store", { + authProfilesLog.debug("resolved oauth credential from canonical local store", { profileId: params.profileId, provider: params.credential.provider, localExpires: params.credential.expires, @@ -282,10 +286,13 @@ export function resolveEffectiveOAuthCredentialCore(params: { return params.credential; } if (!isSafeToAdoptBootstrapOAuthIdentity(params.credential, imported)) { - log.warn("refused external oauth bootstrap credential: identity mismatch or missing binding", { - profileId: params.profileId, - provider: params.credential.provider, - }); + authProfilesLog.warn( + "refused external oauth bootstrap credential: identity mismatch or missing binding", + { + profileId: params.profileId, + provider: params.credential.provider, + }, + ); return params.credential; } const shouldBootstrap = shouldBootstrapFromExternalCliCredential({ @@ -293,7 +300,7 @@ export function resolveEffectiveOAuthCredentialCore(params: { imported, }); if (shouldBootstrap) { - log.debug("resolved oauth credential from external cli bootstrap", { + authProfilesLog.debug("resolved oauth credential from external cli bootstrap", { profileId: params.profileId, provider: imported.provider, localExpires: params.credential.expires, @@ -333,7 +340,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { isSafeToAdoptMainStoreOAuthIdentity(params.credential, mainCred) ) { params.store.profiles[params.profileId] = { ...mainCred }; - log.info("adopted newer OAuth credentials from main agent", { + authProfilesLog.info("adopted newer OAuth credentials from main agent", { profileId: params.profileId, agentDir: params.agentDir, expires: new Date(mainCred.expires).toISOString(), @@ -341,7 +348,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { return mainCred; } } catch (err) { - log.debug("adoptNewerMainOAuthCredential failed", { + authProfilesLog.debug("adoptNewerMainOAuthCredential failed", { profileId: params.profileId, error: formatErrorMessage(err), }); @@ -390,14 +397,17 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { }); if (!decision.shouldMirror) { if (decision.reason === "identity-mismatch-or-regression") { - log.warn("refused to mirror OAuth credential: identity mismatch or regression", { - profileId: params.profileId, - }); + authProfilesLog.warn( + "refused to mirror OAuth credential: identity mismatch or regression", + { + profileId: params.profileId, + }, + ); } return false; } store.profiles[params.profileId] = { ...params.refreshed }; - log.debug("mirrored refreshed OAuth credential to main agent store", { + authProfilesLog.debug("mirrored refreshed OAuth credential to main agent store", { profileId: params.profileId, expires: Number.isFinite(params.refreshed.expires) ? new Date(params.refreshed.expires).toISOString() @@ -407,7 +417,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { }, }); } catch (err) { - log.debug("mirrorRefreshedCredentialIntoMainStore failed", { + authProfilesLog.debug("mirrorRefreshedCredentialIntoMainStore failed", { profileId: params.profileId, error: formatErrorMessage(err), }); @@ -432,7 +442,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { existing?.type !== "oauth" || !expectedCredentials.some((expected) => areOAuthCredentialsEquivalent(existing, expected)) ) { - log.debug("skipped OAuth credential write because stored profile changed", { + authProfilesLog.debug("skipped OAuth credential write because stored profile changed", { profileId: params.profileId, }); return false; @@ -441,7 +451,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { !isSafeToAdoptBootstrapOAuthIdentity(existing, params.credential) || !shouldReplaceStoredOAuthCredential(existing, params.credential) ) { - log.debug("skipped OAuth credential write because stored profile changed", { + authProfilesLog.debug("skipped OAuth credential write because stored profile changed", { profileId: params.profileId, }); return false; @@ -527,11 +537,14 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { isSafeToAdoptMainStoreOAuthIdentity(cred, mainCred) ) { store.profiles[params.profileId] = { ...mainCred }; - log.info("adopted fresh OAuth credential from main store (under refresh lock)", { - profileId: params.profileId, - agentDir: params.agentDir, - expires: new Date(mainCred.expires).toISOString(), - }); + authProfilesLog.info( + "adopted fresh OAuth credential from main store (under refresh lock)", + { + profileId: params.profileId, + agentDir: params.agentDir, + expires: new Date(mainCred.expires).toISOString(), + }, + ); return { apiKey: await adapter.buildApiKey(mainCred.provider, mainCred, { cfg: params.cfg, @@ -545,13 +558,16 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { hasUsableOAuthCredential(mainCred) && !isSafeToAdoptMainStoreOAuthIdentity(cred, mainCred) ) { - log.warn("refused to adopt fresh main-store OAuth credential: identity mismatch", { - profileId: params.profileId, - agentDir: params.agentDir, - }); + authProfilesLog.warn( + "refused to adopt fresh main-store OAuth credential: identity mismatch", + { + profileId: params.profileId, + agentDir: params.agentDir, + }, + ); } } catch (err) { - log.debug("inside-lock main-store adoption failed; proceeding to refresh", { + authProfilesLog.debug("inside-lock main-store adoption failed; proceeding to refresh", { profileId: params.profileId, error: formatErrorMessage(err), }); @@ -565,12 +581,12 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { }); if (externallyManaged) { if (externallyManaged.provider !== cred.provider) { - log.warn("refused external oauth bootstrap credential: provider mismatch", { + authProfilesLog.warn("refused external oauth bootstrap credential: provider mismatch", { profileId: params.profileId, provider: cred.provider, }); } else if (!isSafeToAdoptBootstrapOAuthIdentity(cred, externallyManaged)) { - log.warn( + authProfilesLog.warn( "refused external oauth bootstrap credential: identity mismatch or missing binding", { profileId: params.profileId, @@ -817,7 +833,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) { isSafeToAdoptMainStoreOAuthIdentity(params.credential, mainCred) ) { refreshedStore.profiles[params.profileId] = { ...mainCred }; - log.info("inherited fresh OAuth credentials from main agent", { + authProfilesLog.info("inherited fresh OAuth credentials from main agent", { profileId: params.profileId, agentDir: params.agentDir, expires: new Date(mainCred.expires).toISOString(), diff --git a/src/agents/auth-profiles/persisted.ts b/src/agents/auth-profiles/persisted.ts index 923a73a874f1..9e40a3d777e3 100644 --- a/src/agents/auth-profiles/persisted.ts +++ b/src/agents/auth-profiles/persisted.ts @@ -9,7 +9,7 @@ import { uniqueStrings } from "@openclaw/normalization-core/string-normalization import { coerceSecretRef } from "../../config/types.secrets.js"; import type { OpenClawAgentDatabase } from "../../state/openclaw-agent-db.js"; import { asBoolean } from "../../utils/boolean.js"; -import { AUTH_STORE_VERSION, log } from "./constants.js"; +import { AUTH_STORE_VERSION, authProfilesLog } from "./constants.js"; import { hasUsableOAuthCredential } from "./credential-state.js"; import { isLegacyOAuthRef } from "./legacy-oauth-ref.js"; import { @@ -253,7 +253,7 @@ function warnRejectedCredentialEntries(source: string, rejected: RejectedCredent }, {}, ); - log.warn("ignored invalid auth profile entries during store load", { + authProfilesLog.warn("ignored invalid auth profile entries during store load", { source, dropped: rejected.length, reasons, diff --git a/src/agents/auth-profiles/store.ts b/src/agents/auth-profiles/store.ts index b59a79b8dc2a..fc8b3f033288 100644 --- a/src/agents/auth-profiles/store.ts +++ b/src/agents/auth-profiles/store.ts @@ -13,7 +13,7 @@ import { } from "../../state/openclaw-agent-db.js"; import { isRecord } from "../../utils.js"; import { cloneAuthProfileStore } from "./clone.js"; -import { AUTH_STORE_VERSION, log } from "./constants.js"; +import { AUTH_STORE_VERSION, authProfilesLog } from "./constants.js"; import { listRuntimeExternalAuthProfiles, overlayExternalAuthProfiles, @@ -210,7 +210,9 @@ function publishRuntimeSnapshotsAfterCommit(publish: (() => void) | undefined): return true; } catch (err) { clearRuntimeAuthProfileStoreSnapshots(); - log.warn("auth profile store committed but runtime snapshot publication failed", { err }); + authProfilesLog.warn("auth profile store committed but runtime snapshot publication failed", { + err, + }); return false; } } @@ -415,9 +417,12 @@ function maybeSyncPersistedExternalCliAuthProfiles(params: { const previous = params.store.profiles[profileId]; const latest = latestStore.profiles[profileId]; if (!isDeepStrictEqual(latest, previous)) { - log.debug("skipped persisted external cli auth sync for concurrently changed profile", { - profileId, - }); + authProfilesLog.debug( + "skipped persisted external cli auth sync for concurrently changed profile", + { + profileId, + }, + ); continue; } latestStore.profiles[profileId] = credential; @@ -436,9 +441,12 @@ function maybeSyncPersistedExternalCliAuthProfiles(params: { return { store: latestStore, cacheable: true }; }); } catch (err) { - log.warn("skipped persisted external cli auth sync because auth store write failed", { - err, - }); + authProfilesLog.warn( + "skipped persisted external cli auth sync because auth store write failed", + { + err, + }, + ); return { store: params.store, cacheable: false }; } return publishRuntimeSnapshotsAfterCommit(publishRuntimeSnapshots) @@ -933,7 +941,7 @@ export async function updateAuthProfileStoreWithLock(params: { ); } catch (error) { const message = error instanceof Error ? error.message : String(error); - log.warn(`auth profile store update failed: ${message}`, { + authProfilesLog.warn(`auth profile store update failed: ${message}`, { agentDir, error: message, }); diff --git a/src/agents/bash-tools.exec-host-node.test.ts b/src/agents/bash-tools.exec-host-node.test.ts index a78a5f43b3d4..39ce59fc6a2e 100644 --- a/src/agents/bash-tools.exec-host-node.test.ts +++ b/src/agents/bash-tools.exec-host-node.test.ts @@ -7,9 +7,9 @@ import crypto from "node:crypto"; import { setImmediate } from "node:timers/promises"; import { expectDefined } from "@openclaw/normalization-core"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../../packages/gateway-client/src/timeouts.js"; import { createDeferred } from "../../test/helpers/promise.js"; import type { ExecAllowlistEntry } from "../infra/exec-approvals.types.js"; -import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../utils/timer-delay.js"; import type { ExecuteNodeHostCommandParams } from "./bash-tools.exec-host-node.types.js"; type StrictInlineEvalBoundary = diff --git a/src/agents/bash-tools.exec-runtime-output.test.ts b/src/agents/bash-tools.exec-runtime-output.test.ts index e7d9f4631679..8d8494fa3f0e 100644 --- a/src/agents/bash-tools.exec-runtime-output.test.ts +++ b/src/agents/bash-tools.exec-runtime-output.test.ts @@ -1,8 +1,8 @@ import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../../packages/gateway-client/src/timeouts.js"; import type { ManagedRun } from "../process/supervisor/index.js"; import type { SpawnInput } from "../process/supervisor/types.js"; -import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../utils/timer-delay.js"; const requestHeartbeatMock = vi.hoisted(() => vi.fn()); const enqueueSystemEventWithReceiptMock = vi.hoisted(() => vi.fn()); diff --git a/src/agents/embedded-agent-runner/context-engine-maintenance.ts b/src/agents/embedded-agent-runner/context-engine-maintenance.ts index b1264752a956..4915b4adc5fb 100644 --- a/src/agents/embedded-agent-runner/context-engine-maintenance.ts +++ b/src/agents/embedded-agent-runner/context-engine-maintenance.ts @@ -469,7 +469,9 @@ function scheduleDeferredTurnMaintenance( sessionKey, }); if (!task) { - log.warn("[context-engine] failed to create deferred turn maintenance task", { sessionKey }); + log.warn("[context-engine] failed to create deferred turn maintenance task", { + sessionKey, + }); return undefined; } const lane = `${TURN_MAINTENANCE_LANE_PREFIX}${sessionKey}`; diff --git a/src/agents/main-session-recovery/main-session-restart-recovery-checkpoint.ts b/src/agents/main-session-recovery/main-session-restart-recovery-checkpoint.ts index 2672d5a8d779..34be5b40f58b 100644 --- a/src/agents/main-session-recovery/main-session-restart-recovery-checkpoint.ts +++ b/src/agents/main-session-recovery/main-session-restart-recovery-checkpoint.ts @@ -20,7 +20,10 @@ import { } from "../embedded-agent-runner/message-visibility.js"; import { buildMainSessionRecoveryClearPatch } from "./main-session-recovery-clear.js"; import { isRestartAbortTailArtifact } from "./main-session-restart-recovery-resume-policy.js"; -import { buildRestartRecoveryExpectedState, log } from "./main-session-restart-recovery-shared.js"; +import { + buildRestartRecoveryExpectedState, + mainSessionRecoveryLog, +} from "./main-session-restart-recovery-shared.js"; export function hasOnlyAnnounceRecoveryRuns(entry: SessionEntry): boolean { const runs = entry.restartRecoveryRuns; @@ -78,7 +81,9 @@ export async function reconcileInterruptedCompletionReport(params: { { requireWriteSuccess: true }, ); if (didReconcile) { - log.info(`reconciled interrupted completion report to non-running: ${params.sessionKey}`); + mainSessionRecoveryLog.info( + `reconciled interrupted completion report to non-running: ${params.sessionKey}`, + ); return { outcome: "reconciled" }; } return { outcome: "changed", entry: current }; @@ -460,7 +465,9 @@ export async function markSessionCompletedAfterRecoveryCheckpoint(params: { ); const completed = persisted.sessionEntry?.status === "done"; if (completed) { - log.info(`reconciled delivered terminal reply after restart: ${params.sessionKey}`); + mainSessionRecoveryLog.info( + `reconciled delivered terminal reply after restart: ${params.sessionKey}`, + ); } return { outcome: completed ? "completed" : "changed" }; } @@ -491,7 +498,7 @@ export async function markSessionCompletedAfterRecoveryCheckpoint(params: { }, }); if (marked) { - log.info( + mainSessionRecoveryLog.info( params.reason === "delivered-terminal" || params.reason === "delivered-terminal-receipt" ? `reconciled delivered terminal reply after restart: ${params.sessionKey}` : `reconciled handled silent reply after restart: ${params.sessionKey}`, diff --git a/src/agents/main-session-recovery/main-session-restart-recovery-failure.ts b/src/agents/main-session-recovery/main-session-restart-recovery-failure.ts index 969f2ff28f61..392cda344c63 100644 --- a/src/agents/main-session-recovery/main-session-restart-recovery-failure.ts +++ b/src/agents/main-session-recovery/main-session-restart-recovery-failure.ts @@ -13,7 +13,10 @@ import type { DeliveryContext } from "../../utils/delivery-context.shared.js"; import type { MainSessionRecoveryObservation } from "./main-session-recovery-state.js"; import { commitMainSessionRecovery } from "./main-session-recovery-store.js"; import { resolveRestartRecoveryDeliveryContext } from "./main-session-restart-dispatch.js"; -import { buildRestartRecoveryExpectedState, log } from "./main-session-restart-recovery-shared.js"; +import { + buildRestartRecoveryExpectedState, + mainSessionRecoveryLog, +} from "./main-session-restart-recovery-shared.js"; const TOMBSTONED_SESSION_NOTICE = "I couldn't continue this session after a gateway restart. " + @@ -43,9 +46,11 @@ async function sendRestartRecoveryTombstoneNotice(params: { text: TOMBSTONED_SESSION_NOTICE, idempotencyKey: buildRestartRecoveryTombstoneNoticeKey(params.entry), }); - log.info(`sent restart recovery tombstone notice: ${params.sessionKey} (${params.reason})`); + mainSessionRecoveryLog.info( + `sent restart recovery tombstone notice: ${params.sessionKey} (${params.reason})`, + ); } catch (error) { - log.warn( + mainSessionRecoveryLog.warn( `failed to send restart recovery tombstone notice ${params.sessionKey}: ${String(error)}`, ); } @@ -70,7 +75,7 @@ async function writeRestartRecoveryTombstoneNotice(params: { idempotencyKey: buildRestartRecoveryTombstoneNoticeKey(params.entry), }).catch((error: unknown) => ({ ok: false as const, reason: String(error) })); if (!result.ok) { - log.warn( + mainSessionRecoveryLog.warn( `failed to write restart recovery tombstone notice ${params.sessionKey}: ${result.reason}`, ); } @@ -100,7 +105,9 @@ async function claimMainRestartRecoveryTombstone(params: { if (claim.transition.kind !== "tombstoned" || !claim.entry) { return null; } - log.warn(`tombstoned main-session restart recovery: ${params.sessionKey} (${params.reason})`); + mainSessionRecoveryLog.warn( + `tombstoned main-session restart recovery: ${params.sessionKey} (${params.reason})`, + ); return claim.entry; } diff --git a/src/agents/main-session-recovery/main-session-restart-recovery-marking.ts b/src/agents/main-session-recovery/main-session-restart-recovery-marking.ts index 5c80f4e98545..d91d15bf1e55 100644 --- a/src/agents/main-session-recovery/main-session-restart-recovery-marking.ts +++ b/src/agents/main-session-recovery/main-session-restart-recovery-marking.ts @@ -23,7 +23,7 @@ import { } from "./main-session-recovery-state.js"; import { hasCurrentProcessOwner, - log, + mainSessionRecoveryLog, normalizeFiniteTimestamp, normalizeStringSet, resolveRestartRecoveryStorePaths, @@ -125,7 +125,9 @@ export async function markRestartAbortedMainSessions(params: { storePaths.add(path.resolve(target.storePath)); } } catch (err) { - log.warn(`failed to resolve configured session stores for restart marker: ${String(err)}`); + mainSessionRecoveryLog.warn( + `failed to resolve configured session stores for restart marker: ${String(err)}`, + ); } for (const sessionKey of sessionKeys) { try { @@ -141,7 +143,7 @@ export async function markRestartAbortedMainSessions(params: { } } } catch (err) { - log.warn( + mainSessionRecoveryLog.warn( `failed to resolve session store for restart marker ${sessionKey}: ${String(err)}`, ); } @@ -203,7 +205,7 @@ export async function markRestartAbortedMainSessions(params: { } if (result.marked > 0) { - log.warn( + mainSessionRecoveryLog.warn( `marked ${result.marked} interrupted main session(s) for restart recovery${ params.reason ? ` (${params.reason})` : "" }`, @@ -269,7 +271,9 @@ export async function markStartupOrphanedMainSessionsForRecovery(params: { } if (result.marked > 0) { - log.warn(`marked ${result.marked} startup-orphaned main session(s) for restart recovery`); + mainSessionRecoveryLog.warn( + `marked ${result.marked} startup-orphaned main session(s) for restart recovery`, + ); } return result; } diff --git a/src/agents/main-session-recovery/main-session-restart-recovery-runtime.ts b/src/agents/main-session-recovery/main-session-restart-recovery-runtime.ts index e80199b0c3b7..3e07b35dd501 100644 --- a/src/agents/main-session-recovery/main-session-restart-recovery-runtime.ts +++ b/src/agents/main-session-recovery/main-session-restart-recovery-runtime.ts @@ -15,7 +15,7 @@ import { DEFAULT_RECOVERY_DELAY_MS, type ExhaustedRestartRecoveryTarget, type ExpectedRestartRecoveryTarget, - log, + mainSessionRecoveryLog, MAX_RECOVERY_RETRIES, RETRY_BACKOFF_MULTIPLIER, resolveRestartRecoveryStorePaths, @@ -100,7 +100,7 @@ export async function recoverRestartAbortedMainSessions(params: { } if (result.recovered > 0 || result.failed > 0) { - log.info( + mainSessionRecoveryLog.info( `main-session restart recovery complete: recovered=${result.recovered} failed=${result.failed} skipped=${result.skipped}`, ); } @@ -258,7 +258,7 @@ export function scheduleRestartAbortedMainSessionRecoveryAfterOwnerRelease(param }, onError: (error, finalAttempt) => { if (finalAttempt) { - log.warn(`main-session owner-release recovery failed: ${String(error)}`); + mainSessionRecoveryLog.warn(`main-session owner-release recovery failed: ${String(error)}`); } }, }); @@ -339,7 +339,9 @@ export function scheduleRestartAbortedMainSessionRecovery(params: { ); for (const outcome of outcomes) { if (outcome.status === "rejected") { - log.warn(`main-session exhaustion reconciliation failed: ${String(outcome.reason)}`); + mainSessionRecoveryLog.warn( + `main-session exhaustion reconciliation failed: ${String(outcome.reason)}`, + ); } } }; @@ -369,10 +371,10 @@ export function scheduleRestartAbortedMainSessionRecovery(params: { }, onError: async (err, finalAttempt) => { if (finalAttempt) { - log.warn(`main-session restart recovery gave up: ${String(err)}`); + mainSessionRecoveryLog.warn(`main-session restart recovery gave up: ${String(err)}`); await reconcileExhaustedTargets(exhaustedTargets.values()); } else { - log.warn(`main-session restart recovery failed: ${String(err)}`); + mainSessionRecoveryLog.warn(`main-session restart recovery failed: ${String(err)}`); } }, }); diff --git a/src/agents/main-session-recovery/main-session-restart-recovery-shared.ts b/src/agents/main-session-recovery/main-session-restart-recovery-shared.ts index 236c12ba2d8a..ccd6795f0883 100644 --- a/src/agents/main-session-recovery/main-session-restart-recovery-shared.ts +++ b/src/agents/main-session-recovery/main-session-restart-recovery-shared.ts @@ -14,7 +14,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { resolveAgentSessionDirs } from "../session-dirs.js"; -export const log = createSubsystemLogger("main-session-restart-recovery"); +export const mainSessionRecoveryLog = createSubsystemLogger("main-session-restart-recovery"); export const DEFAULT_RECOVERY_DELAY_MS = 5_000; export const MAX_RECOVERY_RETRIES = 3; export const RETRY_BACKOFF_MULTIPLIER = 2; diff --git a/src/agents/main-session-recovery/main-session-restart-recovery-store.ts b/src/agents/main-session-recovery/main-session-restart-recovery-store.ts index 291cc2fe30c4..320c352fb3cb 100644 --- a/src/agents/main-session-recovery/main-session-restart-recovery-store.ts +++ b/src/agents/main-session-recovery/main-session-restart-recovery-store.ts @@ -45,7 +45,7 @@ import { type ExhaustedRestartRecoveryTarget, type ExpectedRestartRecoveryTarget, hasCurrentProcessOwner, - log, + mainSessionRecoveryLog, MAX_RECOVERY_RETRIES, normalizeStringSet, } from "./main-session-restart-recovery-shared.js"; @@ -199,7 +199,9 @@ function resolveRecoveryDispatchSessionKey(params: { ? target.canonicalKey : undefined; } catch (err) { - log.warn(`failed to resolve recovery store for ${params.sessionKey}: ${String(err)}`); + mainSessionRecoveryLog.warn( + `failed to resolve recovery store for ${params.sessionKey}: ${String(err)}`, + ); return undefined; } } @@ -267,7 +269,7 @@ export async function recoverStore(params: { entries = listSessionEntriesByStatus({ storePath: params.storePath }, ["running"]); } } catch (err) { - log.warn(`failed to load session store ${params.storePath}: ${String(err)}`); + mainSessionRecoveryLog.warn(`failed to load session store ${params.storePath}: ${String(err)}`); result.failed++; return result; } @@ -531,7 +533,7 @@ export async function recoverStore(params: { return result; } if (entry.pendingFinalDelivery?.kind === "replayable") { - log.warn( + mainSessionRecoveryLog.warn( `transcript unavailable for ${sessionKey}; resuming its durable pending final delivery`, ); await resumeCurrent({ @@ -539,7 +541,7 @@ export async function recoverStore(params: { }); continue; } - log.warn(`failed to read transcript for ${sessionKey}: ${String(err)}`); + mainSessionRecoveryLog.warn(`failed to read transcript for ${sessionKey}: ${String(err)}`); result.failed++; continue; } diff --git a/src/agents/runtime-plugins.context-engine.integration.test.ts b/src/agents/runtime-plugins.context-engine.integration.test.ts index 558d41667805..e84cac5478f1 100644 --- a/src/agents/runtime-plugins.context-engine.integration.test.ts +++ b/src/agents/runtime-plugins.context-engine.integration.test.ts @@ -3,7 +3,7 @@ import { afterAll, afterEach, expect, it } from "vitest"; import { loadAndActivateRootPluginRegistry } from "../plugins/loader.js"; import { cleanupPluginLoaderFixturesForTest, - makeTempDir, + makePluginLoaderTempDir, resetPluginLoaderTestStateForTest, useNoBundledPlugins, writePlugin, @@ -49,7 +49,7 @@ it("keeps the configured context engine active in a prepared agent registry", as const activeRegistry = loadAndActivateRootPluginRegistry({ cache: false, config, - workspaceDir: makeTempDir(), + workspaceDir: makePluginLoaderTempDir(), onlyPluginIds: [engineId], }); const preparedRegistry = loadAgentRuntimePluginRegistryHandle({ diff --git a/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts b/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts index 6dadf57ba242..68b8c148c058 100644 --- a/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts +++ b/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts @@ -34,7 +34,7 @@ vi.mock("../../../config/sessions/session-accessor.js", () => ({ })); vi.mock("../../../gateway/session-transcript-readers.js", () => ({ extractMessageRole: (message: { role?: string }) => message?.role, - extractMessageText: (message: { content?: string }) => message?.content ?? null, + extractSessionTranscriptText: (message: { content?: string }) => message?.content ?? null, readSessionMessagesAsync: mocks.readSessionMessages, })); diff --git a/src/agents/subagents/registry/subagent-registry-restart-recovery.ts b/src/agents/subagents/registry/subagent-registry-restart-recovery.ts index 9bdbe4316199..d4a6fa462336 100644 --- a/src/agents/subagents/registry/subagent-registry-restart-recovery.ts +++ b/src/agents/subagents/registry/subagent-registry-restart-recovery.ts @@ -4,7 +4,7 @@ import { loadSessionEntry, patchSessionEntry } from "../../../config/sessions/se import type { GatewayRecoveryRuntime } from "../../../gateway/server-instance-runtime.types.js"; import { extractMessageRole, - extractMessageText, + extractSessionTranscriptText, readSessionMessagesAsync, } from "../../../gateway/session-transcript-readers.js"; import * as agentEvents from "../../../infra/agent-events.js"; @@ -473,14 +473,14 @@ export async function recoverInterruptedSubagentRow( if (!isRecoverySourceCurrent()) { return { status: "handled" }; } - const lastHumanMessage = extractMessageText( + const lastHumanMessage = extractSessionTranscriptText( [...messages].toReversed().find((message) => extractMessageRole(message) === "user"), ); const configChanged = messages.some( (message) => extractMessageRole(message) === "assistant" && /openclaw\.json|openclaw gateway restart|config\.patch/i.test( - extractMessageText(message) ?? "", + extractSessionTranscriptText(message) ?? "", ), ); const sessionId = sessionEntry.sessionId; diff --git a/src/agents/tools/sessions-helpers.ts b/src/agents/tools/sessions-helpers.ts index 22cd23ee8ca4..1deaaa527f6c 100644 --- a/src/agents/tools/sessions-helpers.ts +++ b/src/agents/tools/sessions-helpers.ts @@ -133,7 +133,7 @@ export function resolveSessionToolContext(opts?: { } /** Classifies a session key/gateway kind into the row category used by tools. */ -export function classifySessionKind(params: { +export function classifySessionListKind(params: { key: string; gatewayKind?: string | null; alias: string; diff --git a/src/agents/tools/sessions-list-tool.ts b/src/agents/tools/sessions-list-tool.ts index 21e7431423df..d0e486f62619 100644 --- a/src/agents/tools/sessions-list-tool.ts +++ b/src/agents/tools/sessions-list-tool.ts @@ -43,7 +43,7 @@ import { import { createAgentToAgentPolicy, createSessionVisibilityRowChecker, - classifySessionKind, + classifySessionListKind, deriveChannel, resolveDisplaySessionKey, resolveEffectiveSessionToolsVisibility, @@ -284,7 +284,7 @@ export function createSessionsListTool(opts?: { } const gatewayKind = typeof entry.kind === "string" ? entry.kind : undefined; - const kind = classifySessionKind({ key, gatewayKind, alias, mainKey }); + const kind = classifySessionListKind({ key, gatewayKind, alias, mainKey }); if (allowedKinds && !allowedKinds.has(kind)) { continue; } diff --git a/src/auto-reply/reply/commands-subagents-text.ts b/src/auto-reply/reply/commands-subagents-text.ts index b48ecb79f3b0..7da74ad5b13c 100644 --- a/src/auto-reply/reply/commands-subagents-text.ts +++ b/src/auto-reply/reply/commands-subagents-text.ts @@ -9,7 +9,9 @@ export type ChatMessage = { }; /** Extracts sanitized display text from a subagent chat message. */ -export function extractMessageText(message: ChatMessage): { role: string; text: string } | null { +export function extractSubagentMessageText( + message: ChatMessage, +): { role: string; text: string } | null { const role = typeof message.role === "string" ? message.role : ""; const shouldSanitize = role === "assistant"; const text = extractTextFromChatContent(message.content, { diff --git a/src/auto-reply/reply/commands-subagents.test.ts b/src/auto-reply/reply/commands-subagents.test.ts index 5bd38df07e94..5d8db8fab9df 100644 --- a/src/auto-reply/reply/commands-subagents.test.ts +++ b/src/auto-reply/reply/commands-subagents.test.ts @@ -23,7 +23,7 @@ import { createTaskRecord } from "../../tasks/task-registry.js"; import { resetTaskRegistryForTests } from "../../tasks/task-runtime.test-helpers.js"; import type { ReplyPayload } from "../types.js"; import { buildSubagentsStatusLine } from "./commands-status-subagents.js"; -import { extractMessageText } from "./commands-subagents-text.js"; +import { extractSubagentMessageText } from "./commands-subagents-text.js"; import { handleSubagentsInfoAction } from "./commands-subagents/action-info.js"; import { handleSubagentsListAction } from "./commands-subagents/action-list.js"; import { handleSubagentsLogAction } from "./commands-subagents/action-log.js"; @@ -522,7 +522,7 @@ describe("subagents log", () => { }); }); -describe("extractMessageText", () => { +describe("extractSubagentMessageText", () => { it("preserves user markers and sanitizes assistant markers", () => { const cases = [ { @@ -536,7 +536,7 @@ describe("extractMessageText", () => { ] as const; for (const testCase of cases) { - const result = extractMessageText(testCase.message); + const result = extractSubagentMessageText(testCase.message); expect(result?.text).toBe(testCase.expectedText); } }); diff --git a/src/auto-reply/reply/commands-subagents/shared.ts b/src/auto-reply/reply/commands-subagents/shared.ts index e2e1b956f8dc..f64930fec14f 100644 --- a/src/auto-reply/reply/commands-subagents/shared.ts +++ b/src/auto-reply/reply/commands-subagents/shared.ts @@ -17,7 +17,7 @@ import { isSubagentSessionKey } from "../../../routing/session-key.js"; import { looksLikeSessionId } from "../../../sessions/session-id.js"; import { isNativeCommandTurn, resolveCommandTurnContext } from "../../command-turn-context.js"; import { commandReply } from "../command-gates.js"; -import { extractMessageText, type ChatMessage } from "../commands-subagents-text.js"; +import { extractSubagentMessageText, type ChatMessage } from "../commands-subagents-text.js"; import type { CommandHandler, CommandHandlerResult } from "../commands-types.js"; import { formatRunLabel, @@ -248,7 +248,7 @@ export function buildSubagentsHelp() { export function formatLogLines(messages: ChatMessage[]) { const lines: string[] = []; for (const msg of messages) { - const extracted = extractMessageText(msg); + const extracted = extractSubagentMessageText(msg); if (!extracted) { continue; } diff --git a/src/channels/plugins/read-only.test.ts b/src/channels/plugins/read-only.test.ts index b0febc9aa80a..c95b5955a5ea 100644 --- a/src/channels/plugins/read-only.test.ts +++ b/src/channels/plugins/read-only.test.ts @@ -5,7 +5,7 @@ import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; import { cleanupPluginLoaderFixturesForTest, EMPTY_PLUGIN_SCHEMA, - makeTempDir, + makePluginLoaderTempDir, resetPluginLoaderTestStateForTest, useNoBundledPlugins, } from "../../plugins/loader.test-fixtures.js"; @@ -102,7 +102,7 @@ function writeExternalSetupChannelPlugin( } = {}, ) { useNoBundledPlugins(); - const pluginDir = options.pluginDir ?? makeTempDir(); + const pluginDir = options.pluginDir ?? makePluginLoaderTempDir(); const pluginId = options.pluginId ?? "external-chat"; const channelId = options.channelId ?? "external-chat"; const manifestChannelIds = options.manifestChannelIds ?? [channelId]; @@ -274,7 +274,7 @@ function writeBundledSetupChannelPlugin( envVar?: string; } = {}, ) { - const bundledRoot = makeTempDir(); + const bundledRoot = makePluginLoaderTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledRoot; const pluginId = options.pluginId ?? "bundled-chat"; const channelId = options.channelId ?? pluginId; @@ -1108,7 +1108,7 @@ describe("listReadOnlyChannelPluginsForConfig", () => { }); it("discovers trusted external channel plugins from the default agent workspace", () => { - const workspaceDir = makeTempDir(); + const workspaceDir = makePluginLoaderTempDir(); const pluginDir = path.join(workspaceDir, ".openclaw", "extensions", "external-chat-plugin"); fs.mkdirSync(pluginDir, { recursive: true }); const { fullMarker, setupMarker } = writeExternalSetupChannelPlugin({ diff --git a/src/cli/nodes-cli.coverage.test.ts b/src/cli/nodes-cli.coverage.test.ts index 7c8a6ac6c9e9..04572edf487b 100644 --- a/src/cli/nodes-cli.coverage.test.ts +++ b/src/cli/nodes-cli.coverage.test.ts @@ -2,7 +2,7 @@ import { Command } from "commander"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { registerNodesCli } from "./nodes-cli.js"; -import { callGatewayCli } from "./nodes-cli/rpc.js"; +import { callNodesGatewayCli } from "./nodes-cli/rpc.js"; type NodeInvokeCall = { method?: string; @@ -431,7 +431,7 @@ describe("nodes-cli coverage", () => { idempotencyKey: "rk_test", }; - await callGatewayCli("node.invoke", { timeout: "10000", json: true }, params); + await callNodesGatewayCli("node.invoke", { timeout: "10000", json: true }, params); expect(getNodeInvokeCall()).toMatchObject({ method: "node.invoke", diff --git a/src/cli/nodes-cli/register.camera.test.ts b/src/cli/nodes-cli/register.camera.test.ts index fe4bea886b93..6f2b8975d388 100644 --- a/src/cli/nodes-cli/register.camera.test.ts +++ b/src/cli/nodes-cli/register.camera.test.ts @@ -41,7 +41,7 @@ vi.mock("./rpc.js", async () => { platform: "ios", remoteIp: "198.51.100.42", })), - callGatewayCli: vi.fn(async (_method, _opts, invokeParams) => { + callNodesGatewayCli: vi.fn(async (_method, _opts, invokeParams) => { capturedInvokeParams.push(invokeParams as Record); return { payload: { @@ -93,7 +93,7 @@ describe("nodes camera snap CLI option forwarding", () => { const nodes = buildRootCommand(); await nodes.parseAsync(cameraSnapArgs(["--node", "test-node", "--device-id", "camera-device"])); - expect(rpc.callGatewayCli).toHaveBeenCalledTimes(1); + expect(rpc.callNodesGatewayCli).toHaveBeenCalledTimes(1); expect(capturedInvokeParams).toHaveLength(1); expect(capturedInvokeParams[0]).toMatchObject({ command: "camera.snap", diff --git a/src/cli/nodes-cli/register.camera.ts b/src/cli/nodes-cli/register.camera.ts index ac84e5158791..c7cd43d6fc9a 100644 --- a/src/cli/nodes-cli/register.camera.ts +++ b/src/cli/nodes-cli/register.camera.ts @@ -22,7 +22,7 @@ import { parseDurationMs } from "../parse-duration.js"; import { getNodesTheme, runNodesCommand } from "./cli-utils.js"; import { buildNodeInvokeParams, - callGatewayCli, + callNodesGatewayCli, nodesCallOpts, parseOptionalNodeFiniteNumber, parseOptionalNodeNonNegativeInteger, @@ -58,7 +58,7 @@ export function registerNodesCameraCommands(nodes: Command) { .action(async (opts: NodesRpcOpts) => { await runNodesCommand("camera list", async () => { const nodeId = await resolveNodeId(opts, opts.node ?? ""); - const raw = await callGatewayCli( + const raw = await callNodesGatewayCli( "node.invoke", opts, buildNodeInvokeParams({ @@ -186,7 +186,7 @@ export function registerNodesCameraCommands(nodes: Command) { timeoutMs, }); - const raw = await callGatewayCli("node.invoke", opts, invokeParams); + const raw = await callNodesGatewayCli("node.invoke", opts, invokeParams); const payload = parseCameraSnapPayload(getGatewayInvokePayload(raw)); const filePath = cameraTempPath({ kind: "snap", @@ -258,7 +258,7 @@ export function registerNodesCameraCommands(nodes: Command) { timeoutMs, }); - const raw = await callGatewayCli("node.invoke", opts, invokeParams); + const raw = await callNodesGatewayCli("node.invoke", opts, invokeParams); const payload = parseCameraClipPayload(getGatewayInvokePayload(raw)); const filePath = await writeCameraClipPayloadToFile({ payload, diff --git a/src/cli/nodes-cli/register.invoke.approval-transport-timeout.test.ts b/src/cli/nodes-cli/register.invoke.approval-transport-timeout.test.ts index ff905a40b9e3..4365b8acb1e2 100644 --- a/src/cli/nodes-cli/register.invoke.approval-transport-timeout.test.ts +++ b/src/cli/nodes-cli/register.invoke.approval-transport-timeout.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { DEFAULT_EXEC_APPROVAL_TIMEOUT_MS } from "../../infra/exec-approvals.js"; import { parseTimeoutMs } from "../parse-timeout.js"; -import { callGatewayCli, callNodePairApprovalGatewayCli } from "./rpc.js"; +import { callNodesGatewayCli, callNodePairApprovalGatewayCli } from "./rpc.js"; /** * Regression test for #12098: @@ -11,8 +11,8 @@ import { callGatewayCli, callNodePairApprovalGatewayCli } from "./rpc.js"; * must be at least as long as the approval timeout so the gateway has enough * time to collect the user's decision. * - * The root cause: callGatewayCli reads opts.timeout for the transport timeout. - * Before the fix, node exec flows called callGatewayCli("exec.approval.request", + * The root cause: callNodesGatewayCli reads opts.timeout for the transport timeout. + * Before the fix, node exec flows called callNodesGatewayCli("exec.approval.request", * opts, ...) without overriding opts.timeout, so the 35s CLI default raced * against the 120s approval wait on the gateway side. The CLI always lost. * @@ -51,8 +51,8 @@ describe("exec approval transport timeout (#12098)", () => { callGatewaySpy.mockResolvedValue({ decision: "allow-once" }); }); - it("callGatewayCli forwards opts.timeout as the transport timeoutMs", async () => { - await callGatewayCli("exec.approval.request", { timeout: "35000" } as never, { + it("callNodesGatewayCli forwards opts.timeout as the transport timeoutMs", async () => { + await callNodesGatewayCli("exec.approval.request", { timeout: "35000" } as never, { timeoutMs: 120_000, }); @@ -62,9 +62,9 @@ describe("exec approval transport timeout (#12098)", () => { expect(callOpts.timeoutMs).toBe(35_000); }); - it("callGatewayCli rejects invalid opts.timeout instead of forwarding NaN", async () => { + it("callNodesGatewayCli rejects invalid opts.timeout instead of forwarding NaN", async () => { await expect( - callGatewayCli("exec.approval.request", { timeout: "nope" } as never, { + callNodesGatewayCli("exec.approval.request", { timeout: "nope" } as never, { timeoutMs: 120_000, }), ).rejects.toThrow("Invalid --timeout"); @@ -91,7 +91,7 @@ describe("exec approval transport timeout (#12098)", () => { const transportTimeoutMs = Math.max(parseTimeoutMs("35000") ?? 0, approvalTransportFloorMs); expect(transportTimeoutMs).toBe(approvalTransportFloorMs); - await callGatewayCli( + await callNodesGatewayCli( "exec.approval.request", { timeout: "35000" } as never, { timeoutMs: approvalTimeoutMs }, @@ -114,7 +114,7 @@ describe("exec approval transport timeout (#12098)", () => { ); expect(transportTimeoutMs).toBe(approvalTransportFloorMs); - await callGatewayCli( + await callNodesGatewayCli( "exec.approval.request", { timeout: String(userTimeout) } as never, { timeoutMs: approvalTimeoutMs }, @@ -132,7 +132,7 @@ describe("exec approval transport timeout (#12098)", () => { const transportTimeoutMs = Math.max(parseTimeoutMs("foo") ?? 0, approvalTransportFloorMs); expect(transportTimeoutMs).toBe(approvalTransportFloorMs); - await callGatewayCli( + await callNodesGatewayCli( "exec.approval.request", { timeout: "foo" } as never, { timeoutMs: approvalTimeoutMs }, diff --git a/src/cli/nodes-cli/register.invoke.ts b/src/cli/nodes-cli/register.invoke.ts index 5fbbed1c9841..160c1d554b97 100644 --- a/src/cli/nodes-cli/register.invoke.ts +++ b/src/cli/nodes-cli/register.invoke.ts @@ -8,7 +8,7 @@ import { randomIdempotencyKey } from "../../gateway/call.js"; import { defaultRuntime } from "../../runtime.js"; import { getNodesTheme, runNodesCommand } from "./cli-utils.js"; import { - callGatewayCli, + callNodesGatewayCli, nodesCallOpts, parseOptionalNodePositiveInteger, resolveNodeId, @@ -68,7 +68,7 @@ export function registerNodesInvokeCommands(nodes: Command) { invokeParams.timeoutMs = timeoutMs; } - const result = await callGatewayCli("node.invoke", opts, invokeParams); + const result = await callNodesGatewayCli("node.invoke", opts, invokeParams); defaultRuntime.writeJson(result); }); }), diff --git a/src/cli/nodes-cli/register.location.ts b/src/cli/nodes-cli/register.location.ts index f3ef787e281f..8b8d5ea5d59f 100644 --- a/src/cli/nodes-cli/register.location.ts +++ b/src/cli/nodes-cli/register.location.ts @@ -5,7 +5,7 @@ import { randomIdempotencyKey } from "../../gateway/call.js"; import { defaultRuntime } from "../../runtime.js"; import { runNodesCommand } from "./cli-utils.js"; import { - callGatewayCli, + callNodesGatewayCli, nodesCallOpts, parseOptionalNodeNonNegativeInteger, parseOptionalNodePositiveInteger, @@ -63,7 +63,7 @@ export function registerNodesLocationCommands(nodes: Command) { invokeParams.timeoutMs = invokeTimeoutMs; } - const raw = await callGatewayCli("node.invoke", opts, invokeParams); + const raw = await callNodesGatewayCli("node.invoke", opts, invokeParams); const res = typeof raw === "object" && raw !== null ? (raw as { payload?: unknown }) : {}; const payload = res.payload && typeof res.payload === "object" diff --git a/src/cli/nodes-cli/register.notify.ts b/src/cli/nodes-cli/register.notify.ts index 7bd55b1bc9c3..0744a5bad5af 100644 --- a/src/cli/nodes-cli/register.notify.ts +++ b/src/cli/nodes-cli/register.notify.ts @@ -5,7 +5,7 @@ import { randomIdempotencyKey } from "../../gateway/call.js"; import { defaultRuntime } from "../../runtime.js"; import { getNodesTheme, runNodesCommand } from "./cli-utils.js"; import { - callGatewayCli, + callNodesGatewayCli, nodesCallOpts, parseOptionalNodePositiveInteger, resolveNodeId, @@ -63,7 +63,7 @@ export function registerNodesNotifyCommand(nodes: Command) { invokeParams.timeoutMs = invokeTimeout; } - const result = await callGatewayCli("node.invoke", opts, invokeParams); + const result = await callNodesGatewayCli("node.invoke", opts, invokeParams); if (opts.json) { defaultRuntime.writeJson(result); return; diff --git a/src/cli/nodes-cli/register.pairing.ts b/src/cli/nodes-cli/register.pairing.ts index c12400639f8e..cc79dae9a9a4 100644 --- a/src/cli/nodes-cli/register.pairing.ts +++ b/src/cli/nodes-cli/register.pairing.ts @@ -10,7 +10,7 @@ import { formatConnectionFlagReminder, getNodesTheme, runNodesCommand } from "./ import { parsePairingList } from "./format.js"; import { renderPendingPairingRequestsTable } from "./pairing-render.js"; import { - callGatewayCli, + callNodesGatewayCli, callNodePairApprovalGatewayCli, nodesCallOpts, resolveNodeId, @@ -129,7 +129,7 @@ export function registerNodesPairingCommands(nodes: Command) { .description("List pending pairing requests") .action(async (opts: NodesRpcOpts) => { await runNodesCommand("pending", async () => { - const result = await callGatewayCli("node.pair.list", opts, {}); + const result = await callNodesGatewayCli("node.pair.list", opts, {}); const { pending } = parsePairingList(result); if (opts.json) { defaultRuntime.writeJson(pending); @@ -192,7 +192,7 @@ export function registerNodesPairingCommands(nodes: Command) { await runNodesCommand("reject", async () => { let result: unknown; try { - result = await callGatewayCli("node.pair.reject", opts, { + result = await callNodesGatewayCli("node.pair.reject", opts, { requestId, }); } catch (error) { @@ -218,7 +218,7 @@ export function registerNodesPairingCommands(nodes: Command) { defaultRuntime.exit(1); return; } - const result = await callGatewayCli("node.pair.remove", opts, { nodeId }); + const result = await callNodesGatewayCli("node.pair.remove", opts, { nodeId }); if (opts.json) { defaultRuntime.writeJson(result); return; @@ -246,7 +246,7 @@ export function registerNodesPairingCommands(nodes: Command) { defaultRuntime.exit(1); return; } - const result = await callGatewayCli("node.rename", opts, { + const result = await callNodesGatewayCli("node.rename", opts, { nodeId, displayName: name, }); diff --git a/src/cli/nodes-cli/register.push.ts b/src/cli/nodes-cli/register.push.ts index 05d1030ea740..a031d04b8910 100644 --- a/src/cli/nodes-cli/register.push.ts +++ b/src/cli/nodes-cli/register.push.ts @@ -7,7 +7,7 @@ import type { Command } from "commander"; import type { PushTestResult } from "../../../packages/gateway-protocol/src/index.js"; import { defaultRuntime } from "../../runtime.js"; import { getNodesTheme, runNodesCommand } from "./cli-utils.js"; -import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js"; +import { callNodesGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js"; import type { NodesRpcOpts } from "./types.js"; type NodesPushOpts = NodesRpcOpts & { @@ -57,7 +57,7 @@ export function registerNodesPushCommand(nodes: Command) { params.environment = environment; } - const result = await callGatewayCli("push.test", opts, params); + const result = await callNodesGatewayCli("push.test", opts, params); const parsed = typeof result === "object" && result !== null ? (result as Partial) diff --git a/src/cli/nodes-cli/register.screen.ts b/src/cli/nodes-cli/register.screen.ts index ce386334ee4d..bb3948a6a6b2 100644 --- a/src/cli/nodes-cli/register.screen.ts +++ b/src/cli/nodes-cli/register.screen.ts @@ -11,7 +11,7 @@ import { parseDurationMs } from "../parse-duration.js"; import { runNodesCommand } from "./cli-utils.js"; import { buildNodeInvokeParams, - callGatewayCli, + callNodesGatewayCli, nodesCallOpts, parseOptionalNodeFiniteNumber, parseOptionalNodeNonNegativeInteger, @@ -63,7 +63,7 @@ export function registerNodesScreenCommands(nodes: Command) { timeoutMs, }); - const raw = await callGatewayCli("node.invoke", opts, invokeParams); + const raw = await callNodesGatewayCli("node.invoke", opts, invokeParams); const res = typeof raw === "object" && raw !== null ? (raw as { payload?: unknown }) : {}; const parsed = parseScreenRecordPayload(res.payload); const filePath = opts.out ?? screenRecordTempPath({ ext: parsed.format || "mp4" }); diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 8ed283223fc2..192f9758f26c 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -18,7 +18,7 @@ import { formatConnectionFlagReminder, getNodesTheme, runNodesCommand } from "./ import { formatPermissions, parseNodeList, parsePairingList } from "./format.js"; import { renderPendingPairingRequestsTable } from "./pairing-render.js"; import { - callGatewayCli, + callNodesGatewayCli, callNodeDiagnosticsGatewayCli, nodesCallOpts, resolveNodeDiagnosticsId, @@ -231,7 +231,7 @@ function mergePairedNodesWithEffectiveNodes( async function tryReadNodeList(opts: NodesRpcOpts): Promise { try { - return parseNodeList(await callGatewayCli("node.list", opts, {})); + return parseNodeList(await callNodesGatewayCli("node.list", opts, {})); } catch { return null; } @@ -259,9 +259,9 @@ export function registerNodesStatusCommands(nodes: Command) { const lastConnectedById = sinceMs !== undefined ? new Map( - parsePairingList(await callGatewayCli("node.pair.list", opts, {})).paired.map( - (entry) => [entry.nodeId, entry], - ), + parsePairingList( + await callNodesGatewayCli("node.pair.list", opts, {}), + ).paired.map((entry) => [entry.nodeId, entry]), ) : null; const filtered = nodesLocal.filter((n) => { @@ -526,7 +526,7 @@ export function registerNodesStatusCommands(nodes: Command) { await runNodesCommand("list", async () => { const connectedOnly = Boolean(opts.connected); const sinceMs = parseSinceMs(opts.lastConnected, "Invalid --last-connected"); - const result = await callGatewayCli("node.pair.list", opts, {}); + const result = await callNodesGatewayCli("node.pair.list", opts, {}); const { pending, paired } = parsePairingList(result); const { heading, muted, warn } = getNodesTheme(); const tableWidth = getTerminalTableWidth(); @@ -534,7 +534,7 @@ export function registerNodesStatusCommands(nodes: Command) { const hasFilters = connectedOnly || sinceMs !== undefined; const pendingRows = hasFilters ? [] : pending; const effectiveNodes = hasFilters - ? parseNodeList(await callGatewayCli("node.list", opts, {})) + ? parseNodeList(await callNodesGatewayCli("node.list", opts, {})) : await tryReadNodeList(opts); const effectivePairedRows = mergePairedNodesWithEffectiveNodes(paired, effectiveNodes); const filteredPaired = effectivePairedRows.filter((node) => { diff --git a/src/cli/nodes-cli/rpc.ts b/src/cli/nodes-cli/rpc.ts index 2b59942176cf..fc0ab8d35726 100644 --- a/src/cli/nodes-cli/rpc.ts +++ b/src/cli/nodes-cli/rpc.ts @@ -91,7 +91,7 @@ export const nodesCallOpts = (cmd: Command, defaults?: { timeoutMs?: number }) = .option("--json", "Output JSON", false); /** Call a Gateway method through the lazily loaded node CLI RPC runtime. */ -export const callGatewayCli = async ( +export const callNodesGatewayCli = async ( method: string, opts: NodesRpcOpts, params?: unknown, @@ -132,7 +132,7 @@ export const callNodeDiagnosticsGatewayCli = async ( params?: unknown, ) => { try { - return await callGatewayCli(method, opts, params, { + return await callNodesGatewayCli(method, opts, params, { useStoredDeviceAuth: true, requiredStoredDeviceAuthScopes: ["operator.read", "operator.pairing"], }); @@ -142,7 +142,7 @@ export const callNodeDiagnosticsGatewayCli = async ( } } try { - return await callGatewayCli(method, opts, params, { + return await callNodesGatewayCli(method, opts, params, { scopes: ["operator.read", "operator.pairing"], useLocalBackendSharedAuth: true, }); @@ -151,7 +151,7 @@ export const callNodeDiagnosticsGatewayCli = async ( throw error; } } - return await callGatewayCli(method, opts, params); + return await callNodesGatewayCli(method, opts, params); }; /** Call pairing approval methods with explicit operator scopes. */ @@ -292,10 +292,10 @@ export async function resolveNodeDiagnosticsId(opts: NodesRpcOpts, query: string export async function resolveNode(opts: NodesRpcOpts, query: string): Promise { let nodes: NodeListNode[]; try { - const res = await callGatewayCli("node.list", opts, {}); + const res = await callNodesGatewayCli("node.list", opts, {}); nodes = parseNodeList(res); } catch { - const res = await callGatewayCli("node.pair.list", opts, {}); + const res = await callNodesGatewayCli("node.pair.list", opts, {}); const { paired } = parsePairingList(res); nodes = paired.map((n) => ({ nodeId: n.nodeId, diff --git a/src/cli/plugins-cli-test-helpers.ts b/src/cli/plugins-cli-test-helpers.ts index 134aff34693c..2b52c3422382 100644 --- a/src/cli/plugins-cli-test-helpers.ts +++ b/src/cli/plugins-cli-test-helpers.ts @@ -97,8 +97,8 @@ export const replaceConfigFile: AsyncUnknownMock = vi.fn( async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig), ) as AsyncUnknownMock; const resolveStateDir: Mock<() => string> = vi.fn(() => "/tmp/openclaw-state"); -export const installPluginFromMarketplace: Mock = vi.fn(); -export const installPluginFromGitSpec: Mock = vi.fn(); +export const installPluginFromMarketplaceMock: Mock = vi.fn(); +export const installPluginFromGitSpecMock: Mock = vi.fn(); const parseGitPluginSpec: Mock = vi.fn(); const listMarketplacePlugins: Mock = vi.fn(); export const resolveMarketplaceInstallShortcut: Mock = vi.fn(); @@ -133,19 +133,19 @@ export const restorePersistedInstalledPluginIndexIfCurrent: Mock = vi.fn(); export const updateNpmInstalledHookPacks: Mock = vi.fn(); export const promptYesNoMock: AsyncUnknownMock = vi.fn(); @@ -156,16 +156,16 @@ export class PromptInputClosedError extends Error { this.name = "PromptInputClosedError"; } } -export const installPluginFromNpmSpec: AsyncUnknownMock = vi.fn(); -export const installPluginFromNpmPackArchive: AsyncUnknownMock = vi.fn(); -export const installPluginFromPath: AsyncUnknownMock = vi.fn(); -export const installPluginFromClawHub: AsyncUnknownMock = vi.fn(); +export const installPluginFromNpmSpecMock: AsyncUnknownMock = vi.fn(); +export const installPluginFromNpmPackArchiveMock: AsyncUnknownMock = vi.fn(); +export const installPluginFromPathMock: AsyncUnknownMock = vi.fn(); +export const installPluginFromClawHubMock: AsyncUnknownMock = vi.fn(); export const parseClawHubPluginSpec: Mock = vi.fn(); export const reportClawHubPluginInstallTelemetry: Mock = vi.fn(async () => undefined); export const findBundledPluginSourceMock: UnknownMock = vi.fn(); -export const installHooksFromNpmSpec: AsyncUnknownMock = vi.fn(); -export const installHooksFromPath: AsyncUnknownMock = vi.fn(); +export const installHooksFromNpmSpecMock: AsyncUnknownMock = vi.fn(); +export const installHooksFromPathMock: AsyncUnknownMock = vi.fn(); export const recordHookInstall: UnknownMock = vi.fn(); const { defaultRuntime, pluginsCliRuntimeLogs, runtimeErrors, resetRuntimeCapture } = vi.hoisted( @@ -309,7 +309,7 @@ vi.mock("../config/paths.js", async (importOriginal) => { vi.mock("../plugins/marketplace.js", () => ({ installPluginFromMarketplace: ((...args: Parameters) => - installPluginFromMarketplace(...args)) as InstallPluginFromMarketplaceFn, + installPluginFromMarketplaceMock(...args)) as InstallPluginFromMarketplaceFn, listMarketplacePlugins: ((...args: Parameters) => listMarketplacePlugins(...args)) as ListMarketplacePluginsFn, resolveMarketplaceInstallShortcut: ((...args: Parameters) => @@ -425,7 +425,7 @@ vi.mock("../plugins/status.js", () => ({ Parameters<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]>, ReturnType<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]> >( - buildPluginSnapshotReport, + buildPluginSnapshotReportMock, ...args, )) as (typeof import("../plugins/status.js"))["buildPluginSnapshotReport"], buildPluginRegistrySnapshotReport: (( @@ -437,7 +437,7 @@ vi.mock("../plugins/status.js", () => ({ Parameters<(typeof import("../plugins/status.js"))["buildPluginRegistrySnapshotReport"]>, ReturnType<(typeof import("../plugins/status.js"))["buildPluginRegistrySnapshotReport"]> >( - buildPluginRegistrySnapshotReport, + buildPluginRegistrySnapshotReportMock, ...args, )) as (typeof import("../plugins/status.js"))["buildPluginRegistrySnapshotReport"], buildPluginInspectReport: (( @@ -447,7 +447,7 @@ vi.mock("../plugins/status.js", () => ({ Parameters<(typeof import("../plugins/status.js"))["buildPluginInspectReport"]>, ReturnType<(typeof import("../plugins/status.js"))["buildPluginInspectReport"]> >( - buildPluginInspectReport, + buildPluginInspectReportMock, ...args, )) as (typeof import("../plugins/status.js"))["buildPluginInspectReport"], buildAllPluginInspectReports: (( @@ -467,7 +467,7 @@ vi.mock("../plugins/status.js", () => ({ Parameters<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]>, ReturnType<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]> >( - buildPluginDiagnosticsReport, + buildPluginDiagnosticsReportMock, ...args, )) as (typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"], buildPluginCompatibilityNotices: (( @@ -497,7 +497,7 @@ vi.mock("../plugins/status-snapshot.js", () => ({ (typeof import("../plugins/status-snapshot.js"))["buildPluginRegistrySnapshotReport"] > >( - buildPluginRegistrySnapshotReport, + buildPluginRegistrySnapshotReportMock, ...args, )) as (typeof import("../plugins/status-snapshot.js"))["buildPluginRegistrySnapshotReport"], })); @@ -519,7 +519,7 @@ vi.mock("../plugins/plugin-registry.js", () => ({ Parameters<(typeof import("../plugins/plugin-registry.js"))["inspectPluginRegistry"]>, ReturnType<(typeof import("../plugins/plugin-registry.js"))["inspectPluginRegistry"]> >( - inspectPluginRegistry, + inspectPluginRegistryMock, ...args, )) as (typeof import("../plugins/plugin-registry.js"))["inspectPluginRegistry"], refreshPluginRegistry: (( @@ -536,7 +536,7 @@ vi.mock("../plugins/plugin-registry.js", () => ({ vi.mock("../plugins/loader.js", () => ({ clearPluginRegistryLoadCache: ((...args: unknown[]) => - invokeMock(clearPluginRegistryLoadCache, ...args)) as ( + invokeMock(clearPluginRegistryLoadCacheMock, ...args)) as ( ...args: unknown[] ) => unknown, })); @@ -552,7 +552,7 @@ vi.mock("../plugins/slots.js", async (importOriginal) => { [Parameters<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]>[0]], ReturnType<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]> >( - applyExclusiveSlotSelection, + applyExclusiveSlotSelectionMock, params, )) as (typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"], }; @@ -585,7 +585,7 @@ vi.mock("../plugins/uninstall.js", async (importOriginal) => { (typeof import("../plugins/uninstall.js"))["applyPluginUninstallDirectoryRemoval"] > >( - applyPluginUninstallDirectoryRemoval, + applyPluginUninstallDirectoryRemovalMock, ...args, )) as (typeof import("../plugins/uninstall.js"))["applyPluginUninstallDirectoryRemoval"], }; @@ -649,7 +649,7 @@ vi.mock("../plugins/install.js", () => ({ Parameters<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]>, ReturnType<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]> >( - installPluginFromNpmSpec, + installPluginFromNpmSpecMock, ...args, )) as (typeof import("../plugins/install.js"))["installPluginFromNpmSpec"], installPluginFromNpmPackArchive: (( @@ -659,7 +659,7 @@ vi.mock("../plugins/install.js", () => ({ Parameters<(typeof import("../plugins/install.js"))["installPluginFromNpmPackArchive"]>, ReturnType<(typeof import("../plugins/install.js"))["installPluginFromNpmPackArchive"]> >( - installPluginFromNpmPackArchive, + installPluginFromNpmPackArchiveMock, ...args, )) as (typeof import("../plugins/install.js"))["installPluginFromNpmPackArchive"], installPluginFromPath: (( @@ -669,7 +669,7 @@ vi.mock("../plugins/install.js", () => ({ Parameters<(typeof import("../plugins/install.js"))["installPluginFromPath"]>, ReturnType<(typeof import("../plugins/install.js"))["installPluginFromPath"]> >( - installPluginFromPath, + installPluginFromPathMock, ...args, )) as (typeof import("../plugins/install.js"))["installPluginFromPath"], })); @@ -702,7 +702,7 @@ vi.mock("../plugins/git-install.js", () => ({ Parameters<(typeof import("../plugins/git-install.js"))["installPluginFromGitSpec"]>, ReturnType<(typeof import("../plugins/git-install.js"))["installPluginFromGitSpec"]> >( - installPluginFromGitSpec, + installPluginFromGitSpecMock, ...args, )) as (typeof import("../plugins/git-install.js"))["installPluginFromGitSpec"], parseGitPluginSpec: (( @@ -729,7 +729,7 @@ vi.mock("../hooks/install.js", () => ({ Parameters<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]>, ReturnType<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]> >( - installHooksFromNpmSpec, + installHooksFromNpmSpecMock, ...args, )) as (typeof import("../hooks/install.js"))["installHooksFromNpmSpec"], installHooksFromPath: (( @@ -739,7 +739,7 @@ vi.mock("../hooks/install.js", () => ({ Parameters<(typeof import("../hooks/install.js"))["installHooksFromPath"]>, ReturnType<(typeof import("../hooks/install.js"))["installHooksFromPath"]> >( - installHooksFromPath, + installHooksFromPathMock, ...args, )) as (typeof import("../hooks/install.js"))["installHooksFromPath"], resolveHookInstallDir: (hookId: string) => `/tmp/hooks/${hookId}`, @@ -769,7 +769,7 @@ vi.mock("../plugins/clawhub.js", () => ({ Parameters<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]>, ReturnType<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]> >( - installPluginFromClawHub, + installPluginFromClawHubMock, ...args, )) as (typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"], })); @@ -824,7 +824,7 @@ export function resetPluginsCliTestState() { writeConfigFile.mockReset(); replaceConfigFile.mockReset(); resolveStateDir.mockReset(); - installPluginFromMarketplace.mockReset(); + installPluginFromMarketplaceMock.mockReset(); listMarketplacePlugins.mockReset(); resolveMarketplaceInstallShortcut.mockReset(); enablePluginInConfigMock.mockReset(); @@ -837,35 +837,35 @@ export function resetPluginsCliTestState() { readPersistedInstalledPluginIndex.mockReset(); restorePersistedInstalledPluginIndexIfCurrent.mockReset(); loadPluginManifestRegistry.mockReset(); - buildPluginSnapshotReport.mockReset(); - buildPluginRegistrySnapshotReport.mockReset(); - buildPluginInspectReport.mockReset(); - buildPluginDiagnosticsReport.mockReset(); + buildPluginSnapshotReportMock.mockReset(); + buildPluginRegistrySnapshotReportMock.mockReset(); + buildPluginInspectReportMock.mockReset(); + buildPluginDiagnosticsReportMock.mockReset(); buildPluginCompatibilityNotices.mockReset(); - inspectPluginRegistry.mockReset(); + inspectPluginRegistryMock.mockReset(); refreshPluginRegistry.mockReset(); notifyGatewayPluginMetadataChangedMock.mockReset(); - clearPluginRegistryLoadCache.mockReset(); - applyExclusiveSlotSelection.mockReset(); + clearPluginRegistryLoadCacheMock.mockReset(); + applyExclusiveSlotSelectionMock.mockReset(); planPluginUninstall.mockReset(); - applyPluginUninstallDirectoryRemoval.mockReset(); + applyPluginUninstallDirectoryRemovalMock.mockReset(); updateNpmInstalledPlugins.mockReset(); updateNpmInstalledHookPacks.mockReset(); mockHookInstallRecords = {}; promptText.mockReset(); promptYesNoMock.mockReset(); - installPluginFromGitSpec.mockReset(); + installPluginFromGitSpecMock.mockReset(); parseGitPluginSpec.mockReset(); - installPluginFromNpmSpec.mockReset(); - installPluginFromNpmPackArchive.mockReset(); - installPluginFromPath.mockReset(); - installPluginFromClawHub.mockReset(); + installPluginFromNpmSpecMock.mockReset(); + installPluginFromNpmPackArchiveMock.mockReset(); + installPluginFromPathMock.mockReset(); + installPluginFromClawHubMock.mockReset(); parseClawHubPluginSpec.mockReset(); reportClawHubPluginInstallTelemetry.mockReset(); reportClawHubPluginInstallTelemetry.mockResolvedValue(undefined); findBundledPluginSourceMock.mockReset(); - installHooksFromNpmSpec.mockReset(); - installHooksFromPath.mockReset(); + installHooksFromNpmSpecMock.mockReset(); + installHooksFromPathMock.mockReset(); recordHookInstall.mockReset(); loadConfig.mockReturnValue({} as OpenClawConfig); @@ -905,7 +905,7 @@ export function resetPluginsCliTestState() { ); resolveStateDir.mockReturnValue("/tmp/openclaw-state"); resolveMarketplaceInstallShortcut.mockResolvedValue(null); - installPluginFromMarketplace.mockResolvedValue({ + installPluginFromMarketplaceMock.mockResolvedValue({ ok: false, error: "marketplace install failed", }); @@ -951,13 +951,13 @@ export function resetPluginsCliTestState() { plugins: [], diagnostics: [], }; - buildPluginSnapshotReport.mockReturnValue(defaultPluginReport); - buildPluginRegistrySnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue(defaultPluginReport); + buildPluginRegistrySnapshotReportMock.mockReturnValue({ ...defaultPluginReport, registrySource: "derived", registryDiagnostics: [], }); - buildPluginDiagnosticsReport.mockReturnValue(defaultPluginReport); + buildPluginDiagnosticsReportMock.mockReturnValue(defaultPluginReport); buildPluginCompatibilityNotices.mockReturnValue([]); const defaultRegistryIndex = { version: 1, @@ -969,7 +969,7 @@ export function resetPluginsCliTestState() { plugins: [], diagnostics: [], }; - inspectPluginRegistry.mockResolvedValue({ + inspectPluginRegistryMock.mockResolvedValue({ state: "fresh", refreshReasons: [], persisted: defaultRegistryIndex, @@ -977,7 +977,7 @@ export function resetPluginsCliTestState() { }); refreshPluginRegistry.mockResolvedValue(defaultRegistryIndex); notifyGatewayPluginMetadataChangedMock.mockResolvedValue(true); - applyExclusiveSlotSelection.mockImplementation((({ config }: { config: OpenClawConfig }) => ({ + applyExclusiveSlotSelectionMock.mockImplementation((({ config }: { config: OpenClawConfig }) => ({ config, warnings: [], })) as (...args: unknown[]) => unknown); @@ -994,7 +994,7 @@ export function resetPluginsCliTestState() { actions: createEmptyUninstallActions(), directoryRemoval: null, })) as (...args: unknown[]) => unknown); - applyPluginUninstallDirectoryRemoval.mockResolvedValue({ + applyPluginUninstallDirectoryRemovalMock.mockResolvedValue({ directoryRemoved: false, warnings: [], }); @@ -1010,8 +1010,11 @@ export function resetPluginsCliTestState() { }); promptYesNoMock.mockResolvedValue(true); promptText.mockResolvedValue("demo"); - installPluginFromPath.mockResolvedValue({ ok: false, error: "path install disabled in test" }); - installPluginFromGitSpec.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ + ok: false, + error: "path install disabled in test", + }); + installPluginFromGitSpecMock.mockResolvedValue({ ok: false, error: "git install disabled in test", }); @@ -1031,20 +1034,20 @@ export function resetPluginsCliTestState() { normalizedSpec: trimmed, }; }); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "npm install disabled in test", }); - installPluginFromClawHub.mockResolvedValue({ + installPluginFromClawHubMock.mockResolvedValue({ ok: false, error: "clawhub install disabled in test", }); parseClawHubPluginSpec.mockReturnValue(null); - installHooksFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue({ ok: false, error: "hook path install disabled in test", }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "hook npm install disabled in test", }); diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index 6daa0063052e..89f649a98e0b 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -12,19 +12,19 @@ import { resolveOfficialExternalPluginInstall, } from "../plugins/official-external-plugin-catalog.js"; import { - applyExclusiveSlotSelection, - buildPluginSnapshotReport, - clearPluginRegistryLoadCache, + applyExclusiveSlotSelectionMock, + buildPluginSnapshotReportMock, + clearPluginRegistryLoadCacheMock, enablePluginInConfigMock, findBundledPluginSourceMock, - installHooksFromNpmSpec, - installHooksFromPath, - installPluginFromNpmPackArchive, - installPluginFromClawHub, - installPluginFromGitSpec, - installPluginFromMarketplace, - installPluginFromNpmSpec, - installPluginFromPath, + installHooksFromNpmSpecMock, + installHooksFromPathMock, + installPluginFromNpmPackArchiveMock, + installPluginFromClawHubMock, + installPluginFromGitSpecMock, + installPluginFromMarketplaceMock, + installPluginFromNpmSpecMock, + installPluginFromPathMock, loadConfig, loadPluginManifestRegistry, readConfigFileSnapshot, @@ -107,7 +107,7 @@ function createClawHubInstallResult(params: { checkedAt?: string; acknowledgedAt?: string; }; -}): Awaited> { +}): Awaited> { return { ok: true, pluginId: params.pluginId, @@ -149,7 +149,7 @@ function createClawHubInstallResult(params: { function createNpmPluginInstallResult( pluginId = "demo", -): Awaited> { +): Awaited> { return { ok: true, pluginId, @@ -165,7 +165,7 @@ function createNpmPluginInstallResult( function createNpmPackPluginInstallResult( pluginId = "demo", -): Awaited> { +): Awaited> { return { ok: true, pluginId, @@ -187,7 +187,7 @@ function createNpmPackPluginInstallResult( function createGitPluginInstallResult( pluginId = "demo", -): Awaited> { +): Awaited> { return { ok: true, pluginId, @@ -204,7 +204,7 @@ function createGitPluginInstallResult( } function mockClawHubPackageNotFound(packageName: string) { - installPluginFromClawHub.mockResolvedValue({ + installPluginFromClawHubMock.mockResolvedValue({ ok: false, error: `ClawHub /api/v1/packages/${packageName} failed (404): Package not found`, code: "package_not_found", @@ -217,10 +217,10 @@ function primeNpmPluginFallback(pluginId = "demo") { loadConfig.mockReturnValue(cfg); mockClawHubPackageNotFound(pluginId); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult(pluginId)); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult(pluginId)); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); - applyExclusiveSlotSelection.mockReturnValue({ + applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], }); @@ -235,7 +235,7 @@ function primeSuccessfulPluginPersistence(pluginId = "demo") { loadConfig.mockReturnValue(cfg); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); - applyExclusiveSlotSelection.mockReturnValue({ + applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], }); @@ -254,7 +254,7 @@ function primeSuccessfulClawHubPluginInstall( name: "demo", ...(params.explicitVersion ? { version: "1.2.3" } : {}), }); - installPluginFromClawHub.mockResolvedValue( + installPluginFromClawHubMock.mockResolvedValue( createClawHubInstallResult({ pluginId: "demo", packageName: "demo", @@ -321,11 +321,11 @@ function primeHookPackNpmFallback() { loadConfig.mockReturnValue(cfg); mockClawHubPackageNotFound("@acme/demo-hooks"); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.plugin.json", }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ...createHookPackInstallResult("/tmp/hooks/demo-hooks"), npmResolution: { name: "@acme/demo-hooks", @@ -346,11 +346,11 @@ function primeHookPackPathFallback(params: { const installedCfg = createPathHookPackInstalledConfig(params.tmpRoot); loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromPath.mockResolvedValueOnce({ + installPluginFromPathMock.mockResolvedValueOnce({ ok: false, error: params.pluginInstallError, }); - installHooksFromPath.mockResolvedValueOnce(createHookPackInstallResult(params.tmpRoot)); + installHooksFromPathMock.mockResolvedValueOnce(createHookPackInstallResult(params.tmpRoot)); recordHookInstall.mockReturnValue(installedCfg); return installedCfg; @@ -398,35 +398,35 @@ function mockCallArg(mock: MockWithCalls, callIndex = 0, argIndex = 0): unknown } function marketplaceInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installPluginFromMarketplace, callIndex) as PluginInstallCall; + return mockCallArg(installPluginFromMarketplaceMock, callIndex) as PluginInstallCall; } function clawHubInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installPluginFromClawHub, callIndex) as PluginInstallCall; + return mockCallArg(installPluginFromClawHubMock, callIndex) as PluginInstallCall; } function npmInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installPluginFromNpmSpec, callIndex) as PluginInstallCall; + return mockCallArg(installPluginFromNpmSpecMock, callIndex) as PluginInstallCall; } function npmPackInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installPluginFromNpmPackArchive, callIndex) as PluginInstallCall; + return mockCallArg(installPluginFromNpmPackArchiveMock, callIndex) as PluginInstallCall; } function gitInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installPluginFromGitSpec, callIndex) as PluginInstallCall; + return mockCallArg(installPluginFromGitSpecMock, callIndex) as PluginInstallCall; } function pathInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installPluginFromPath, callIndex) as PluginInstallCall; + return mockCallArg(installPluginFromPathMock, callIndex) as PluginInstallCall; } function hookPathInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installHooksFromPath, callIndex) as PluginInstallCall; + return mockCallArg(installHooksFromPathMock, callIndex) as PluginInstallCall; } function hookNpmInstallCall(callIndex = 0): PluginInstallCall { - return mockCallArg(installHooksFromNpmSpec, callIndex) as PluginInstallCall; + return mockCallArg(installHooksFromNpmSpecMock, callIndex) as PluginInstallCall; } function persistedInstallRecords(callIndex = 0): Record { @@ -649,9 +649,9 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/demo"]), ).rejects.toThrow("OPENCLAW_NIX_MODE=1"); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); - expect(installPluginFromPath).not.toHaveBeenCalled(); - expect(installPluginFromMarketplace).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); + expect(installPluginFromMarketplaceMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); }); @@ -659,7 +659,7 @@ describe("plugins cli install", () => { "fails closed before installing blocked ambiguous npm plugin spec %s", async (spec) => { primeBlockedPluginConfigMutation(); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", }); @@ -668,9 +668,9 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", spec]), ).rejects.toThrow("__exit__:1"); - expect(installHooksFromNpmSpec).toHaveBeenCalledTimes(1); + expect(installHooksFromNpmSpecMock).toHaveBeenCalledTimes(1); expect(hookNpmInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", @@ -692,7 +692,7 @@ describe("plugins cli install", () => { }, } as OpenClawConfig; primeBlockedPluginConfigMutation(); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: true, hookPackId: "demo-hooks", hooks: ["command-audit"], @@ -710,8 +710,8 @@ describe("plugins cli install", () => { await runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/demo-hooks"]); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); - expect(installHooksFromNpmSpec).toHaveBeenCalledTimes(2); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); + expect(installHooksFromNpmSpecMock).toHaveBeenCalledTimes(2); expect(hookNpmInstallCall().inspection).toBe("package-kind"); expect(hookNpmInstallCall(1).expectedIntegrity).toBe("sha256-demo"); expect(hookNpmInstallCall(1).expectedPackageKind).toBe("hook-only"); @@ -720,7 +720,7 @@ describe("plugins cli install", () => { it("blocks npm package inspection when plugin and hook config are include-owned", async () => { primeBlockedPluginConfigMutation({ blockHooks: true }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ...createHookPackInstallResult("/tmp/hooks/demo-hooks"), npmResolution: { name: "@acme/demo-hooks", @@ -734,8 +734,8 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/demo-hooks"]), ).rejects.toThrow("__exit__:1"); - expect(installHooksFromNpmSpec).not.toHaveBeenCalled(); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installHooksFromNpmSpecMock).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config hooks are stored in an external or unresolved top-level $include", @@ -744,7 +744,7 @@ describe("plugins cli install", () => { it("blocks a proven npm hook pack before plugin installer side effects when only hooks config is include-owned", async () => { primeBlockedHookConfigMutation(); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ...createHookPackInstallResult("/tmp/hooks/demo-hooks"), npmResolution: { name: "@acme/demo-hooks", @@ -758,9 +758,9 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/demo-hooks"]), ).rejects.toThrow("__exit__:1"); - expect(installHooksFromNpmSpec).toHaveBeenCalledTimes(1); + expect(installHooksFromNpmSpecMock).toHaveBeenCalledTimes(1); expect(hookNpmInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config hooks are stored in an external or unresolved top-level $include", @@ -770,8 +770,8 @@ describe("plugins cli install", () => { it("blocks local package inspection when plugin and hook config are include-owned", async () => { const localPath = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-hook-pack-")); primeBlockedPluginConfigMutation({ blockHooks: true }); - installHooksFromPath.mockResolvedValue(createHookPackInstallResult(localPath)); - installPluginFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue(createHookPackInstallResult(localPath)); + installPluginFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.extensions", code: "missing_openclaw_extensions", @@ -785,7 +785,7 @@ describe("plugins cli install", () => { fs.rmSync(localPath, { recursive: true, force: true }); } - expect(installHooksFromPath).not.toHaveBeenCalled(); + expect(installHooksFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config hooks are stored in an external or unresolved top-level $include", @@ -795,7 +795,7 @@ describe("plugins cli install", () => { it("blocks a proven local hook pack before plugin installer side effects when only hooks config is include-owned", async () => { const localPath = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-hook-pack-")); primeBlockedHookConfigMutation(); - installHooksFromPath.mockResolvedValue(createHookPackInstallResult(localPath)); + installHooksFromPathMock.mockResolvedValue(createHookPackInstallResult(localPath)); try { await expect( @@ -805,9 +805,9 @@ describe("plugins cli install", () => { fs.rmSync(localPath, { recursive: true, force: true }); } - expect(installHooksFromPath).toHaveBeenCalledTimes(1); + expect(installHooksFromPathMock).toHaveBeenCalledTimes(1); expect(hookPathInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromPath).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config hooks are stored in an external or unresolved top-level $include", @@ -833,12 +833,12 @@ describe("plugins cli install", () => { fs.mkdirSync(localPath); primeBlockedPluginConfigMutation(); parseClawHubPluginSpec.mockReturnValue({ name: "demo-hooks" }); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.extensions", code: "missing_openclaw_extensions", }); - installHooksFromPath.mockResolvedValue(createHookPackInstallResult(localPath)); + installHooksFromPathMock.mockResolvedValue(createHookPackInstallResult(localPath)); recordHookInstall.mockReturnValue(installedCfg); try { @@ -851,18 +851,18 @@ describe("plugins cli install", () => { fs.rmSync(localPath, { recursive: true, force: true }); } - expect(installPluginFromPath).not.toHaveBeenCalled(); - expect(installHooksFromPath).toHaveBeenCalledTimes(2); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); + expect(installHooksFromPathMock).toHaveBeenCalledTimes(2); expect(hookPathInstallCall().inspection).toBe("package-kind"); expect(hookPathInstallCall(1).expectedPackageKind).toBe("hook-only"); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(writeConfigFile).toHaveBeenCalledWith(installedCfg); }, ); it("fails closed for ambiguous npm plugins when the whole config is include-owned", async () => { primeBlockedRootConfigMutation(); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", }); @@ -871,8 +871,8 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/demo-plugin"]), ).rejects.toThrow("__exit__:1"); - expect(installHooksFromNpmSpec).not.toHaveBeenCalled(); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installHooksFromNpmSpecMock).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("unsupported $include shape at the root"); }); @@ -880,7 +880,7 @@ describe("plugins cli install", () => { it("fails closed for ambiguous local plugins when the whole config is include-owned", async () => { const localPath = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-demo-plugin-")); primeBlockedRootConfigMutation(); - installHooksFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", }); @@ -893,8 +893,8 @@ describe("plugins cli install", () => { fs.rmSync(localPath, { recursive: true, force: true }); } - expect(installHooksFromPath).not.toHaveBeenCalled(); - expect(installPluginFromPath).not.toHaveBeenCalled(); + expect(installHooksFromPathMock).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("unsupported $include shape at the root"); }); @@ -903,7 +903,7 @@ describe("plugins cli install", () => { const archivePath = path.join(os.tmpdir(), `openclaw-plugin-${process.pid}.tgz`); fs.writeFileSync(archivePath, "not-an-archive"); primeBlockedPluginConfigMutation(); - installHooksFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", }); @@ -916,9 +916,9 @@ describe("plugins cli install", () => { fs.rmSync(archivePath, { force: true }); } - expect(installHooksFromPath).toHaveBeenCalledTimes(1); + expect(installHooksFromPathMock).toHaveBeenCalledTimes(1); expect(hookPathInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromPath).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", @@ -927,7 +927,7 @@ describe("plugins cli install", () => { it("fails closed when an npm hook probe finds a plugin-capable package", async () => { primeBlockedPluginConfigMutation(); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ...createHookPackInstallResult("/tmp/hooks/demo-hooks"), packageKind: "plugin-capable", }); @@ -936,9 +936,9 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/dual-package"]), ).rejects.toThrow("__exit__:1"); - expect(installHooksFromNpmSpec).toHaveBeenCalledTimes(1); + expect(installHooksFromNpmSpecMock).toHaveBeenCalledTimes(1); expect(hookNpmInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", @@ -948,7 +948,7 @@ describe("plugins cli install", () => { it("fails closed when a local hook probe finds a plugin-capable package", async () => { const localPath = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-dual-package-")); primeBlockedPluginConfigMutation(); - installHooksFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue({ ...createHookPackInstallResult(localPath), packageKind: "plugin-capable", }); @@ -961,9 +961,9 @@ describe("plugins cli install", () => { fs.rmSync(localPath, { recursive: true, force: true }); } - expect(installHooksFromPath).toHaveBeenCalledTimes(1); + expect(installHooksFromPathMock).toHaveBeenCalledTimes(1); expect(hookPathInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromPath).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", @@ -973,7 +973,7 @@ describe("plugins cli install", () => { it("fails closed for a local bundle plugin instead of installing its hooks", async () => { const localPath = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundle-plugin-")); primeBlockedPluginConfigMutation(); - installHooksFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue({ ...createHookPackInstallResult(localPath), packageKind: "plugin-capable", }); @@ -986,9 +986,9 @@ describe("plugins cli install", () => { fs.rmSync(localPath, { recursive: true, force: true }); } - expect(installHooksFromPath).toHaveBeenCalledTimes(1); + expect(installHooksFromPathMock).toHaveBeenCalledTimes(1); expect(hookPathInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromPath).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", @@ -997,15 +997,15 @@ describe("plugins cli install", () => { it("fails closed when a blocked-config npm hook probe throws", async () => { primeBlockedPluginConfigMutation(); - installHooksFromNpmSpec.mockRejectedValue(new Error("hook validation exploded")); + installHooksFromNpmSpecMock.mockRejectedValue(new Error("hook validation exploded")); await expect( runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/demo-plugin"]), ).rejects.toThrow("__exit__:1"); - expect(installHooksFromNpmSpec).toHaveBeenCalledTimes(1); + expect(installHooksFromNpmSpecMock).toHaveBeenCalledTimes(1); expect(hookNpmInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", ); @@ -1014,7 +1014,7 @@ describe("plugins cli install", () => { it("fails closed when a blocked-config local hook probe throws", async () => { const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-plugin-")); primeBlockedPluginConfigMutation(); - installHooksFromPath.mockRejectedValue(new Error("hook validation exploded")); + installHooksFromPathMock.mockRejectedValue(new Error("hook validation exploded")); try { await expect( @@ -1024,9 +1024,9 @@ describe("plugins cli install", () => { fs.rmSync(localPluginDir, { recursive: true, force: true }); } - expect(installHooksFromPath).toHaveBeenCalledTimes(1); + expect(installHooksFromPathMock).toHaveBeenCalledTimes(1); expect(hookPathInstallCall().inspection).toBe("package-kind"); - expect(installPluginFromPath).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", ); @@ -1036,9 +1036,9 @@ describe("plugins cli install", () => { { label: "marketplace", args: ["plugins", "install", "demo", "--marketplace", "local/repo"], - installer: installPluginFromMarketplace, + installer: installPluginFromMarketplaceMock, setup: () => - installPluginFromMarketplace.mockResolvedValue({ + installPluginFromMarketplaceMock.mockResolvedValue({ ok: true, pluginId: "demo", targetDir: cliInstallPath("demo"), @@ -1052,23 +1052,23 @@ describe("plugins cli install", () => { { label: "git", args: ["plugins", "install", "git:github.com/acme/demo"], - installer: installPluginFromGitSpec, - setup: () => installPluginFromGitSpec.mockResolvedValue(createGitPluginInstallResult()), + installer: installPluginFromGitSpecMock, + setup: () => installPluginFromGitSpecMock.mockResolvedValue(createGitPluginInstallResult()), }, { label: "npm-pack", args: ["plugins", "install", "npm-pack:/tmp/demo.tgz"], - installer: installPluginFromNpmPackArchive, + installer: installPluginFromNpmPackArchiveMock, setup: () => - installPluginFromNpmPackArchive.mockResolvedValue(createNpmPackPluginInstallResult()), + installPluginFromNpmPackArchiveMock.mockResolvedValue(createNpmPackPluginInstallResult()), }, { label: "ClawHub", args: ["plugins", "install", "clawhub:demo"], - installer: installPluginFromClawHub, + installer: installPluginFromClawHubMock, setup: () => { parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); - installPluginFromClawHub.mockResolvedValue( + installPluginFromClawHubMock.mockResolvedValue( createClawHubInstallResult({ pluginId: "demo", packageName: "demo", @@ -1102,7 +1102,7 @@ describe("plugins cli install", () => { await expect(runPluginsCommand(["plugins", "install", "brave"])).rejects.toThrow("__exit__:1"); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", @@ -1119,7 +1119,7 @@ describe("plugins cli install", () => { await expect(runPluginsCommand(["plugins", "install", pluginId])).rejects.toThrow("__exit__:1"); - expect(installPluginFromPath).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "Config plugins are stored in an external or unresolved top-level $include", @@ -1129,7 +1129,7 @@ describe("plugins cli install", () => { it("blocks explicit plugins through nested include config before installer side effects", async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-nested-")); primeNestedPluginConfigMutation(tempRoot); - installPluginFromMarketplace.mockResolvedValue({ + installPluginFromMarketplaceMock.mockResolvedValue({ ok: true, pluginId: "demo", targetDir: cliInstallPath("demo"), @@ -1154,7 +1154,7 @@ describe("plugins cli install", () => { fs.rmSync(tempRoot, { recursive: true, force: true }); } - expect(installPluginFromMarketplace).not.toHaveBeenCalled(); + expect(installPluginFromMarketplaceMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("nested $include"); }); @@ -1173,7 +1173,7 @@ describe("plugins cli install", () => { expect(runtimeErrors.at(-1)).toContain("--link is not supported with --marketplace."); expect(runtimeErrors.at(-1)).toContain("openclaw plugins install --link --force"); - expect(installPluginFromMarketplace).not.toHaveBeenCalled(); + expect(installPluginFromMarketplaceMock).not.toHaveBeenCalled(); }); it("exits when marketplace install fails", async () => { @@ -1237,8 +1237,8 @@ describe("plugins cli install", () => { expect(runtimeErrors.at(-1)).toContain( "Config invalid; run `openclaw doctor --fix` before installing plugins.", ); - expect(installPluginFromMarketplace).not.toHaveBeenCalled(); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromMarketplaceMock).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); }); @@ -1258,7 +1258,7 @@ describe("plugins cli install", () => { }, } as OpenClawConfig; loadConfig.mockReturnValue(cfg); - installPluginFromMarketplace.mockResolvedValue({ + installPluginFromMarketplaceMock.mockResolvedValue({ ok: true, pluginId: "alpha", targetDir: cliInstallPath("alpha"), @@ -1269,7 +1269,7 @@ describe("plugins cli install", () => { marketplacePlugin: "alpha", }); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", kind: "provider" }], diagnostics: [], }); @@ -1292,7 +1292,7 @@ describe("plugins cli install", () => { ], diagnostics: [], }); - applyExclusiveSlotSelection.mockReturnValue({ + applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: ["slot adjusted"], }); @@ -1312,7 +1312,7 @@ describe("plugins cli install", () => { expect(replaceConfigCall().nextConfig).toBe(enabledCfg); expect(runtimeLogsContain("slot adjusted")).toBe(true); expect(runtimeLogsContain("Installed plugin: alpha")).toBe(true); - expect(clearPluginRegistryLoadCache).not.toHaveBeenCalled(); + expect(clearPluginRegistryLoadCacheMock).not.toHaveBeenCalled(); }); it("passes force through as overwrite mode for marketplace installs", async () => { @@ -1335,13 +1335,13 @@ describe("plugins cli install", () => { it("requires acknowledgement for noninteractive non-ClawHub plugin installs", async () => { setTty(false); primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await expect(runPluginsCommand(["plugins", "install", "npm:demo"])).rejects.toThrow( "__exit__:1", ); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("outside ClawHub review"); expect(runtimeErrors.at(-1)).toContain(NON_CLAWHUB_INSTALL_FORCE_FLAG); }); @@ -1352,14 +1352,15 @@ describe("plugins cli install", () => { prepare: () => ({ args: ["plugins", "install", "npm-pack:/tmp/demo.tgz"], expectNoInstallerSideEffects: () => - expect(installPluginFromNpmPackArchive).not.toHaveBeenCalled(), + expect(installPluginFromNpmPackArchiveMock).not.toHaveBeenCalled(), }), }, { label: "git", prepare: () => ({ args: ["plugins", "install", "git:github.com/acme/demo@v1.2.3"], - expectNoInstallerSideEffects: () => expect(installPluginFromGitSpec).not.toHaveBeenCalled(), + expectNoInstallerSideEffects: () => + expect(installPluginFromGitSpecMock).not.toHaveBeenCalled(), }), }, { @@ -1367,7 +1368,7 @@ describe("plugins cli install", () => { prepare: () => ({ args: ["plugins", "install", "demo", "--marketplace", "local/repo"], expectNoInstallerSideEffects: () => - expect(installPluginFromMarketplace).not.toHaveBeenCalled(), + expect(installPluginFromMarketplaceMock).not.toHaveBeenCalled(), }), }, { @@ -1377,7 +1378,8 @@ describe("plugins cli install", () => { return { args: ["plugins", "install", localPath], cleanup: () => fs.rmSync(localPath, { recursive: true, force: true }), - expectNoInstallerSideEffects: () => expect(installPluginFromPath).not.toHaveBeenCalled(), + expectNoInstallerSideEffects: () => + expect(installPluginFromPathMock).not.toHaveBeenCalled(), }; }, }, @@ -1393,7 +1395,8 @@ describe("plugins cli install", () => { fs.rmSync(archivePath, { force: true }); fs.rmSync(tempRoot, { recursive: true, force: true }); }, - expectNoInstallerSideEffects: () => expect(installPluginFromPath).not.toHaveBeenCalled(), + expectNoInstallerSideEffects: () => + expect(installPluginFromPathMock).not.toHaveBeenCalled(), }; }, }, @@ -1430,7 +1433,7 @@ describe("plugins cli install", () => { : undefined; }); primeSuccessfulPluginPersistence("demo"); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: true, pluginId: "demo", targetDir: cliInstallPath("demo"), @@ -1446,14 +1449,14 @@ describe("plugins cli install", () => { expect(promptYesNoMock).not.toHaveBeenCalled(); expect(runtimeErrors.join("\n")).not.toContain("outside ClawHub review"); - expect(installPluginFromPath).toHaveBeenCalledTimes(1); + expect(installPluginFromPathMock).toHaveBeenCalledTimes(1); }); it("prompts interactive users before non-ClawHub plugin installs and cancels on no", async () => { setTty(true); promptYesNoMock.mockResolvedValueOnce(false); primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await expect(runPluginsCommand(["plugins", "install", "npm:demo"])).rejects.toThrow( "__exit__:1", @@ -1462,21 +1465,21 @@ describe("plugins cli install", () => { expect(promptYesNoMock).toHaveBeenCalledWith("Install this non-ClawHub plugin source?"); expect(runtimeLogsContain("Installing plugin from npm registry")).toBe(true); expect(runtimeLogsContain("outside ClawHub review")).toBe(true); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); }); it("prompts interactive users before non-ClawHub plugin installs and proceeds on yes", async () => { setTty(true); promptYesNoMock.mockResolvedValueOnce(true); primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await runPluginsCommand(["plugins", "install", "npm:demo"]); expect(promptYesNoMock).toHaveBeenCalledWith("Install this non-ClawHub plugin source?"); expect(runtimeLogsContain("Installing plugin from npm registry")).toBe(true); expect(runtimeLogsContain("outside ClawHub review")).toBe(true); - expect(installPluginFromNpmSpec).toHaveBeenCalledTimes(1); + expect(installPluginFromNpmSpecMock).toHaveBeenCalledTimes(1); expect(persistedInstallRecord("demo").source).toBe("npm"); }); @@ -1487,7 +1490,7 @@ describe("plugins cli install", () => { expectedSource: "npm registry", setup: () => { primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); }, }, { @@ -1496,7 +1499,9 @@ describe("plugins cli install", () => { expectedSource: "local npm-pack archive", setup: () => { primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmPackArchive.mockResolvedValue(createNpmPackPluginInstallResult("demo")); + installPluginFromNpmPackArchiveMock.mockResolvedValue( + createNpmPackPluginInstallResult("demo"), + ); }, }, { @@ -1505,7 +1510,7 @@ describe("plugins cli install", () => { expectedSource: "Git repository", setup: () => { primeSuccessfulPluginPersistence("demo"); - installPluginFromGitSpec.mockResolvedValue(createGitPluginInstallResult("demo")); + installPluginFromGitSpecMock.mockResolvedValue(createGitPluginInstallResult("demo")); }, }, { @@ -1514,7 +1519,7 @@ describe("plugins cli install", () => { expectedSource: "marketplace source", setup: () => { primeSuccessfulPluginPersistence("demo"); - installPluginFromMarketplace.mockResolvedValue({ + installPluginFromMarketplaceMock.mockResolvedValue({ ok: true, pluginId: "demo", targetDir: cliInstallPath("demo"), @@ -1558,7 +1563,7 @@ describe("plugins cli install", () => { fs.writeFileSync(localSource, "archive"); } primeSuccessfulPluginPersistence("demo"); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: true, pluginId: "demo", targetDir: cliInstallPath("demo"), @@ -1585,7 +1590,7 @@ describe("plugins cli install", () => { await runPluginsCommand(["plugins", "install", "clawhub:demo"]); expect(runtimeLogsContain("outside ClawHub review")).toBe(false); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); }); it("installs ClawHub plugins and persists source metadata", async () => { @@ -1618,7 +1623,7 @@ describe("plugins cli install", () => { packageName: "demo", version: "1.2.3", }); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); }); it("does not report a ClawHub install when durable persistence fails", async () => { @@ -1645,7 +1650,7 @@ describe("plugins cli install", () => { await runPluginsCommand(["plugins", "install", "clawhub:demo", "--acknowledge-clawhub-risk"]); - expect(installPluginFromClawHub).toHaveBeenCalledWith( + expect(installPluginFromClawHubMock).toHaveBeenCalledWith( expect.objectContaining({ spec: "clawhub:demo", acknowledgeClawHubRisk: true, @@ -1662,7 +1667,7 @@ describe("plugins cli install", () => { it("prints acknowledgement guidance for unacknowledged ClawHub plugin installs", async () => { loadConfig.mockReturnValue(createEmptyPluginConfig()); parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); - installPluginFromClawHub.mockResolvedValue({ + installPluginFromClawHubMock.mockResolvedValue({ ok: false, code: "clawhub_risk_acknowledgement_required", error: @@ -1680,7 +1685,7 @@ describe("plugins cli install", () => { it("prints blocked ClawHub download failures when no trust warning was emitted", async () => { loadConfig.mockReturnValue(createEmptyPluginConfig()); parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); - installPluginFromClawHub.mockResolvedValue({ + installPluginFromClawHubMock.mockResolvedValue({ ok: false, code: "clawhub_download_blocked", error: @@ -1749,7 +1754,7 @@ describe("plugins cli install", () => { expect(String(record.sourcePath)).toContain(pluginId); expect(String(record.installPath)).toContain(pluginId); expect(enablePluginInConfigMock).not.toHaveBeenCalled(); - expect(applyExclusiveSlotSelection).not.toHaveBeenCalled(); + expect(applyExclusiveSlotSelectionMock).not.toHaveBeenCalled(); expect(runtimeLogsContain("requires configuration first")).toBe(true); }); @@ -1857,14 +1862,14 @@ describe("plugins cli install", () => { it("resolves exact official external plugin ids through their npm package", async () => { const { enabledCfg } = primeSuccessfulPluginPersistence("brave"); findBundledPluginSourceMock.mockReturnValue(undefined); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("brave")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("brave")); await runPluginsCommand(["plugins", "install", "brave"]); expect(findBundledPluginSourceMock).toHaveBeenCalledWith({ lookup: { kind: "pluginId", value: "brave" }, }); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(npmInstallCall().spec).toBe("@openclaw/brave-plugin"); expect(npmInstallCall().expectedPluginId).toBe("brave"); expect(npmInstallCall().trustedSourceLinkedOfficialInstall).toBe(true); @@ -1880,7 +1885,7 @@ describe("plugins cli install", () => { it("passes third-party external catalog integrity with catalog install trust", async () => { primeSuccessfulPluginPersistence("wecom-openclaw-plugin"); findBundledPluginSourceMock.mockReturnValue(undefined); - installPluginFromNpmSpec.mockResolvedValue( + installPluginFromNpmSpecMock.mockResolvedValue( createNpmPluginInstallResult("wecom-openclaw-plugin"), ); @@ -1899,14 +1904,14 @@ describe("plugins cli install", () => { async ({ pluginId, npmSpec }) => { primeSuccessfulPluginPersistence(pluginId); findBundledPluginSourceMock.mockReturnValue(undefined); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult(pluginId)); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult(pluginId)); await runPluginsCommand(["plugins", "install", pluginId]); expect(findBundledPluginSourceMock).toHaveBeenCalledWith({ lookup: { kind: "pluginId", value: pluginId }, }); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(npmInstallCall().spec).toBe(npmSpec); expect(npmInstallCall().expectedPluginId).toBe(pluginId); expect(npmInstallCall().trustedSourceLinkedOfficialInstall).toBe(true); @@ -1917,12 +1922,12 @@ describe("plugins cli install", () => { it("passes third-party external catalog integrity to hook-pack fallback", async () => { loadConfig.mockReturnValue(createEmptyPluginConfig()); findBundledPluginSourceMock.mockReturnValue(undefined); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.extensions", code: "missing_openclaw_extensions", }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "aborted: npm package integrity drift detected for @wecom/wecom-openclaw-plugin@2026.5.7", @@ -1939,11 +1944,11 @@ describe("plugins cli install", () => { it("installs ordinary bare plugin specs through npm without ClawHub lookup", async () => { const { enabledCfg } = primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await runAcknowledgedPluginsInstallCommand(["plugins", "install", "demo"]); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(npmInstallCall().spec).toBe("demo"); expect(runtimeLogsContain("Installing plugin from npm registry")).toBe(true); expect(runtimeLogsContain("outside ClawHub review")).toBe(true); @@ -1957,7 +1962,7 @@ describe("plugins cli install", () => { it("stores npm resolution metadata without changing the active plugin install selector", async () => { primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: true, pluginId: "demo", targetDir: cliInstallPath("demo"), @@ -1979,17 +1984,17 @@ describe("plugins cli install", () => { it("passes bare npm selectors through npm without ClawHub lookup", async () => { primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await runAcknowledgedPluginsInstallCommand(["plugins", "install", "demo@beta"]); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(npmInstallCall().spec).toBe("demo@beta"); }); it("installs directly from npm when npm: prefix is used", async () => { const { enabledCfg } = primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await runAcknowledgedPluginsInstallCommand(["plugins", "install", "npm:demo"]); @@ -1997,7 +2002,7 @@ describe("plugins cli install", () => { expect(npmInstallCall().mode).toBe("update"); expect(runtimeLogsContain("Installing plugin from npm registry")).toBe(true); expect(runtimeLogsContain("outside ClawHub review")).toBe(true); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(persistedInstallRecord("demo").source).toBe("npm"); expect(persistedInstallRecord("demo").spec).toBe("demo"); expect(persistedInstallRecord("demo").installPath).toBe(cliInstallPath("demo")); @@ -2007,14 +2012,14 @@ describe("plugins cli install", () => { it("installs npm-pack archives through npm install semantics", async () => { const { enabledCfg } = primeSuccessfulPluginPersistence("demo"); const archivePath = "/tmp/openclaw-demo-1.2.3.tgz"; - installPluginFromNpmPackArchive.mockResolvedValue(createNpmPackPluginInstallResult("demo")); + installPluginFromNpmPackArchiveMock.mockResolvedValue(createNpmPackPluginInstallResult("demo")); await runAcknowledgedPluginsInstallCommand(["plugins", "install", `npm-pack:${archivePath}`]); expect(npmPackInstallCall().archivePath).toBe(archivePath); expect(npmPackInstallCall().mode).toBe("update"); - expect(installPluginFromPath).not.toHaveBeenCalled(); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); const record = persistedInstallRecord("demo"); expect(record.source).toBe("npm"); expect(record.spec).toBe("@openclaw/demo@1.2.3"); @@ -2031,7 +2036,7 @@ describe("plugins cli install", () => { it("keeps npm-prefixed official plugin ids on explicit npm semantics", async () => { primeSuccessfulPluginPersistence("brave"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("brave")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("brave")); await runAcknowledgedPluginsInstallCommand(["plugins", "install", "npm:brave"]); @@ -2040,12 +2045,12 @@ describe("plugins cli install", () => { expect(npmInstallCall().trustedSourceLinkedOfficialInstall).toBeUndefined(); expect(runtimeLogsContain("Installing plugin from npm registry")).toBe(true); expect(runtimeLogsContain("outside ClawHub review")).toBe(true); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); }); it("marks explicit official npm package installs as trusted", async () => { primeSuccessfulPluginPersistence("discord"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("discord")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("discord")); await runPluginsCommand(["plugins", "install", "npm:@openclaw/discord"]); @@ -2053,20 +2058,20 @@ describe("plugins cli install", () => { expect(npmInstallCall().expectedPluginId).toBe("discord"); expect(npmInstallCall().trustedSourceLinkedOfficialInstall).toBe(true); expect(runtimeLogsContain("outside ClawHub review")).toBe(false); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); }); it("marks scoped official npm package installs as trusted", async () => { primeSuccessfulPluginPersistence("discord"); findBundledPluginSourceMock.mockReturnValue(undefined); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("discord")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("discord")); await runPluginsCommand(["plugins", "install", "@openclaw/discord"]); expect(npmInstallCall().spec).toBe("@openclaw/discord"); expect(npmInstallCall().expectedPluginId).toBe("discord"); expect(npmInstallCall().trustedSourceLinkedOfficialInstall).toBe(true); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); }); it("uses bundled OpenClaw package specs instead of pinning stale managed npm overrides", async () => { @@ -2093,7 +2098,7 @@ describe("plugins cli install", () => { "--force", ]); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(findBundledPluginSourceMock).toHaveBeenCalledWith({ lookup: { kind: "npmSpec", value: "@openclaw/discord@2026.5.20" }, }); @@ -2112,7 +2117,7 @@ describe("plugins cli install", () => { it("marks catalog npm package installs with alternate selectors as trusted", async () => { primeSuccessfulPluginPersistence("wecom-openclaw-plugin"); findBundledPluginSourceMock.mockReturnValue(undefined); - installPluginFromNpmSpec.mockResolvedValue( + installPluginFromNpmSpecMock.mockResolvedValue( createNpmPluginInstallResult("wecom-openclaw-plugin"), ); @@ -2125,13 +2130,13 @@ describe("plugins cli install", () => { expect(npmInstallCall().trustedSourceLinkedOfficialInstall).toBe(true); expect(npmInstallCall().expectedIntegrity).toBeUndefined(); expect(runtimeLogsContain("outside ClawHub review")).toBe(false); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); }); it("passes the active profile extensions dir to npm installs", async () => { const extensionsDir = useProfileExtensionsDir(); primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await runAcknowledgedPluginsInstallCommand(["plugins", "install", "npm:demo"]); @@ -2141,7 +2146,7 @@ describe("plugins cli install", () => { it("passes npm: prefix installs through npm options without ClawHub lookup", async () => { primeSuccessfulPluginPersistence("demo"); - installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("demo")); + installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult("demo")); await runAcknowledgedPluginsInstallCommand([ "plugins", @@ -2159,16 +2164,16 @@ describe("plugins cli install", () => { "--dangerously-force-unsafe-install is deprecated and no longer affects plugin installs", ), ).toBe(true); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); }); it("reports npm install failures without trying ClawHub when npm: prefix is used", async () => { loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "npm install failed", }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", code: "missing_openclaw_hooks", @@ -2178,18 +2183,18 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "npm:demo"]), ).rejects.toThrow("__exit__:1"); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("npm install failed"); expect(runtimeErrors.at(-1)).not.toContain("Also not a valid hook pack"); }); it("keeps actionable hook-pack fallback details", async () => { loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "npm install failed", }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "HOOK.md missing in /tmp/demo-hook", }); @@ -2206,7 +2211,7 @@ describe("plugins cli install", () => { it("adds a Git PATH hint when npm plugin dependency install cannot spawn git", async () => { loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: [ "npm install failed:", @@ -2215,7 +2220,7 @@ describe("plugins cli install", () => { "npm error path git", ].join("\n"), }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", code: "missing_openclaw_hooks", @@ -2225,7 +2230,7 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "npm:@openclaw/whatsapp"]), ).rejects.toThrow("__exit__:1"); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain( "one of this plugin's npm dependencies is fetched from a git URL", ); @@ -2235,12 +2240,12 @@ describe("plugins cli install", () => { it("does not resolve npm: prefixed bundled plugin ids through bundled installs", async () => { loadConfig.mockReturnValue({ plugins: { load: { paths: [] } } } as OpenClawConfig); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "Package not found on npm: memory-lancedb.", code: "npm_package_not_found", }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", code: "missing_openclaw_hooks", @@ -2251,7 +2256,7 @@ describe("plugins cli install", () => { ).rejects.toThrow("__exit__:1"); expect(npmInstallCall().spec).toBe("memory-lancedb"); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("Package not found on npm: memory-lancedb."); expect(runtimeErrors.at(-1)).not.toContain("Also not a valid hook pack"); @@ -2264,14 +2269,14 @@ describe("plugins cli install", () => { runAcknowledgedPluginsInstallCommand(["plugins", "install", "npm:"]), ).rejects.toThrow("__exit__:1"); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("Unsupported npm plugin spec: missing package."); }); it("installs directly from git when git: prefix is used", async () => { const { enabledCfg } = primeSuccessfulPluginPersistence("demo"); - installPluginFromGitSpec.mockResolvedValue(createGitPluginInstallResult("demo")); + installPluginFromGitSpecMock.mockResolvedValue(createGitPluginInstallResult("demo")); await runAcknowledgedPluginsInstallCommand([ "plugins", @@ -2281,8 +2286,8 @@ describe("plugins cli install", () => { expect(gitInstallCall().spec).toBe("git:github.com/acme/demo@v1.2.3"); expect(gitInstallCall().mode).toBe("update"); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); const record = persistedInstallRecord("demo"); expect(record.source).toBe("git"); expect(record.spec).toBe("git:github.com/acme/demo@v1.2.3"); @@ -2305,7 +2310,7 @@ describe("plugins cli install", () => { ]), ).rejects.toThrow("__exit__:1"); - expect(installPluginFromGitSpec).not.toHaveBeenCalled(); + expect(installPluginFromGitSpecMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("openclaw plugins install git:@ --force"); }); @@ -2350,7 +2355,7 @@ describe("plugins cli install", () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-link-")); loadConfig.mockReturnValue(cfg); - installPluginFromPath.mockResolvedValueOnce({ + installPluginFromPathMock.mockResolvedValueOnce({ ok: true, pluginId: "demo", targetDir: tmpRoot, @@ -2359,7 +2364,7 @@ describe("plugins cli install", () => { }); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); - applyExclusiveSlotSelection.mockReturnValue({ + applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], }); @@ -2412,7 +2417,7 @@ describe("plugins cli install", () => { const pluginInstallError = "plugin blocked by security scan"; loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: false, error: pluginInstallError, code: "security_scan_blocked", @@ -2426,7 +2431,7 @@ describe("plugins cli install", () => { fs.rmSync(localPluginDir, { recursive: true, force: true }); } - expect(installHooksFromPath).not.toHaveBeenCalled(); + expect(installHooksFromPathMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain(pluginInstallError); expect(runtimeErrors.at(-1)).not.toContain("Also not a valid hook pack"); }); @@ -2458,7 +2463,7 @@ describe("plugins cli install", () => { const extensionsDir = useProfileExtensionsDir(); const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-plugin-")); primeSuccessfulPluginPersistence("demo"); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: true, pluginId: "demo", targetDir: path.join(extensionsDir, "demo"), @@ -2486,12 +2491,12 @@ describe("plugins cli install", () => { it("suggests update or --force when npm plugin install target already exists", async () => { loadConfig.mockReturnValue({} as OpenClawConfig); mockClawHubPackageNotFound("@example/lossless-claw"); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "plugin already exists: /home/openclaw/.openclaw/extensions/lossless-claw (delete it first)", }); - installHooksFromNpmSpec.mockResolvedValue({ + installHooksFromNpmSpecMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", }); @@ -2510,11 +2515,11 @@ describe("plugins cli install", () => { const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-plugin-")); loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: false, error: "Invalid path: must stay within extensions directory", }); - installHooksFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.hooks", }); @@ -2544,7 +2549,7 @@ describe("plugins cli install", () => { const enabledCfg = createEnabledPluginConfig("demo"); loadConfig.mockReturnValue(cfg); - installPluginFromPath.mockImplementation(async (...args: unknown[]) => { + installPluginFromPathMock.mockImplementation(async (...args: unknown[]) => { const [params] = args as [ { logger?: { warn?: (message: string) => void }; @@ -2564,7 +2569,7 @@ describe("plugins cli install", () => { }); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); recordPluginInstall.mockReturnValue(enabledCfg); - applyExclusiveSlotSelection.mockReturnValue({ + applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], }); @@ -2617,7 +2622,7 @@ describe("plugins cli install", () => { ] as const)("does not fall back to hook pack for local path when $name", async (testCase) => { const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-plugin-")); loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: false, error: testCase.error, code: testCase.code, @@ -2636,7 +2641,7 @@ describe("plugins cli install", () => { fs.rmSync(localPluginDir, { recursive: true, force: true }); } - expect(installHooksFromPath).not.toHaveBeenCalled(); + expect(installHooksFromPathMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain(testCase.error); expect(runtimeErrors.at(-1)).not.toContain("Also not a valid hook pack"); }); @@ -2667,7 +2672,7 @@ describe("plugins cli install", () => { ] as const)("does not fall back to hook pack for npm installs when $name", async (testCase) => { loadConfig.mockReturnValue({} as OpenClawConfig); mockClawHubPackageNotFound(testCase.spec); - installPluginFromNpmSpec.mockResolvedValue({ + installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: testCase.error, code: testCase.code, @@ -2682,7 +2687,7 @@ describe("plugins cli install", () => { ]), ).rejects.toThrow("__exit__:1"); - expect(installHooksFromNpmSpec).not.toHaveBeenCalled(); + expect(installHooksFromNpmSpecMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain(testCase.error); expect(runtimeErrors.at(-1)).not.toContain("Also not a valid hook pack"); }); @@ -2690,12 +2695,12 @@ describe("plugins cli install", () => { it("still falls back to local hook pack when dangerous force unsafe install is set for non-security errors", async () => { const localHookDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-hook-pack-")); loadConfig.mockReturnValue({} as OpenClawConfig); - installPluginFromPath.mockResolvedValue({ + installPluginFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.plugin.json", code: "missing_openclaw_extensions", }); - installHooksFromPath.mockResolvedValue({ + installHooksFromPathMock.mockResolvedValue({ ok: true, hookPackId: "demo-hooks", hooks: ["command-audit"], @@ -2735,7 +2740,7 @@ describe("plugins cli install", () => { it("does not fall back to npm when explicit ClawHub rejects a real package", async () => { parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); - installPluginFromClawHub.mockResolvedValue({ + installPluginFromClawHubMock.mockResolvedValue({ ok: false, error: 'Use "openclaw skills install demo" instead.', code: "skill_package", @@ -2745,7 +2750,7 @@ describe("plugins cli install", () => { "__exit__:1", ); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain('Use "openclaw skills install demo" instead.'); }); diff --git a/src/cli/plugins-cli.list.test.ts b/src/cli/plugins-cli.list.test.ts index 7ef19360bdda..95b370fde59b 100644 --- a/src/cli/plugins-cli.list.test.ts +++ b/src/cli/plugins-cli.list.test.ts @@ -8,11 +8,11 @@ import type { import { createPluginRecord } from "../plugins/status.test-fixtures.js"; import { withEnvAsync } from "../test-utils/env.js"; import { - buildPluginDiagnosticsReport, - buildPluginInspectReport, - buildPluginRegistrySnapshotReport, - buildPluginSnapshotReport, - inspectPluginRegistry, + buildPluginDiagnosticsReportMock, + buildPluginInspectReportMock, + buildPluginRegistrySnapshotReportMock, + buildPluginSnapshotReportMock, + inspectPluginRegistryMock, loadConfig, loadPluginManifestRegistry, readConfigFileSnapshot, @@ -57,7 +57,7 @@ async function mockPluginDoctorValidationWarnings(warnings: ConfigValidationIssu })), diagnostics: [], }); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "google", enabled: false, status: "disabled" })], diagnostics: [], }); @@ -74,7 +74,7 @@ describe("plugins cli list", () => { }); it("includes imported state in JSON output", async () => { - buildPluginRegistrySnapshotReport.mockReturnValue({ + buildPluginRegistrySnapshotReportMock.mockReturnValue({ workspaceDir: "/workspace", registrySource: "persisted", registryDiagnostics: [], @@ -91,8 +91,8 @@ describe("plugins cli list", () => { await runPluginsCommand(["plugins", "list", "--json"]); - expect(buildPluginRegistrySnapshotReport).toHaveBeenCalledTimes(1); - const [reportOptions] = buildPluginRegistrySnapshotReport.mock.calls[0] as [ + expect(buildPluginRegistrySnapshotReportMock).toHaveBeenCalledTimes(1); + const [reportOptions] = buildPluginRegistrySnapshotReportMock.mock.calls[0] as [ { config?: unknown; logger?: { info?: unknown; warn?: unknown; error?: unknown }; @@ -126,14 +126,17 @@ describe("plugins cli list", () => { }); it("keeps doctor on a module-loading snapshot", async () => { - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); await runPluginsCommand(["plugins", "doctor"]); - expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ config: {}, effectiveOnly: true }); + expect(buildPluginDiagnosticsReportMock).toHaveBeenCalledWith({ + config: {}, + effectiveOnly: true, + }); expect(pluginsCliRuntimeLogs).toContain(cleanDoctorMessage); }); @@ -243,7 +246,7 @@ describe("plugins cli list", () => { it("emits one sanitized JSON doctor report without human decoration", async () => { const homeDir = "/tmp/openclaw-plugin-doctor-home"; - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [ createPluginRecord({ id: "broken", @@ -340,7 +343,7 @@ describe("plugins cli list", () => { ])( "reports actionable discovery warnings when $description", async ({ diagnostic, expected }) => { - buildPluginDiagnosticsReport.mockReturnValue({ plugins: [], diagnostics: [diagnostic] }); + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [diagnostic] }); await runPluginsCommand(["plugins", "doctor"]); @@ -352,7 +355,7 @@ describe("plugins cli list", () => { ); it("keeps actionable discovery warnings alongside existing errors", async () => { - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [ { level: "error", pluginId: "broken", message: "plugin manifest invalid" }, @@ -396,7 +399,7 @@ describe("plugins cli list", () => { warnings: [], legacyIssues: [], }); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -448,7 +451,7 @@ describe("plugins cli list", () => { warnings: [], legacyIssues: [], }); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -473,7 +476,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -500,7 +503,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -523,7 +526,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "acpx", enabled: false, status: "disabled" })], diagnostics: [], }); @@ -548,7 +551,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -573,7 +576,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "codex" })], diagnostics: [], }); @@ -596,7 +599,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "codex", enabled: false, status: "disabled" })], diagnostics: [], }); @@ -627,7 +630,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -661,7 +664,7 @@ describe("plugins cli list", () => { }, }; loadConfig.mockReturnValue(sourceConfig); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -678,7 +681,7 @@ describe("plugins cli list", () => { }); it("reports config-selected plugin source shadowing in doctor output", async () => { - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [ createPluginRecord({ id: "discord", @@ -712,7 +715,7 @@ describe("plugins cli list", () => { }); it("does not report healthy config-selected plugin source shadowing as doctor issue", async () => { - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [ createPluginRecord({ id: "discord", @@ -738,7 +741,7 @@ describe("plugins cli list", () => { }); it("reports persisted plugin registry state without refreshing", async () => { - inspectPluginRegistry.mockResolvedValue({ + inspectPluginRegistryMock.mockResolvedValue({ state: "stale", refreshReasons: ["stale-manifest"], persisted: { @@ -754,7 +757,7 @@ describe("plugins cli list", () => { await runPluginsCommand(["plugins", "registry"]); - expect(inspectPluginRegistry).toHaveBeenCalledWith({ config: {} }); + expect(inspectPluginRegistryMock).toHaveBeenCalledWith({ config: {} }); expect(refreshPluginRegistry).not.toHaveBeenCalled(); expect(pluginsCliRuntimeLogs.join("\n")).toContain("State:"); expect(pluginsCliRuntimeLogs.join("\n")).toContain("stale"); @@ -776,7 +779,7 @@ describe("plugins cli list", () => { config: {}, reason: "manual", }); - expect(inspectPluginRegistry).not.toHaveBeenCalled(); + expect(inspectPluginRegistryMock).not.toHaveBeenCalled(); expect(pluginsCliRuntimeLogs.join("\n")).toContain("Plugin registry refreshed: 1/2 enabled"); }); @@ -800,11 +803,11 @@ describe("plugins cli list", () => { clawpackSize: 4096, }, }); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "openclaw-mem0", name: "Mem0" })], diagnostics: [], }); - buildPluginInspectReport.mockReturnValue({ + buildPluginInspectReportMock.mockReturnValue({ workspaceDir: "/workspace", plugin: createPluginRecord({ id: "openclaw-mem0", name: "Mem0" }), shape: "hook-only", @@ -838,7 +841,7 @@ describe("plugins cli list", () => { await runPluginsCommand(["plugins", "inspect", "openclaw-mem0"]); - expect(buildPluginDiagnosticsReport).not.toHaveBeenCalled(); + expect(buildPluginDiagnosticsReportMock).not.toHaveBeenCalled(); expect(pluginsCliRuntimeLogs.join("\n")).toContain("Policy"); expect(pluginsCliRuntimeLogs.join("\n")).toContain("allowConversationAccess: true"); expect(pluginsCliRuntimeLogs.join("\n")).toContain("ClawHub package: openclaw-mem0"); @@ -855,11 +858,11 @@ describe("plugins cli list", () => { }); it("runtime-inspects without repairing deps", async () => { - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "openclaw-mem0", name: "Mem0" })], diagnostics: [], }); - buildPluginInspectReport.mockReturnValue({ + buildPluginInspectReportMock.mockReturnValue({ workspaceDir: "/workspace", plugin: createPluginRecord({ id: "openclaw-mem0", name: "Mem0" }), shape: "hook-only", @@ -888,14 +891,14 @@ describe("plugins cli list", () => { await runPluginsCommand(["plugins", "inspect", "openclaw-mem0", "--runtime"]); - expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ + expect(buildPluginDiagnosticsReportMock).toHaveBeenCalledWith({ config: {}, onlyPluginIds: ["openclaw-mem0"], }); }); it("does not runtime-load plugins when inspect target is missing", async () => { - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -904,8 +907,8 @@ describe("plugins cli list", () => { "__exit__:1", ); - expect(buildPluginSnapshotReport).toHaveBeenCalledWith({ config: {} }); - expect(buildPluginDiagnosticsReport).not.toHaveBeenCalled(); + expect(buildPluginSnapshotReportMock).toHaveBeenCalledWith({ config: {} }); + expect(buildPluginDiagnosticsReportMock).not.toHaveBeenCalled(); expect(runtimeErrors.at(-1)).toContain("Plugin not found: missing-plugin"); }); @@ -922,7 +925,7 @@ describe("plugins cli list", () => { message: 'Skill Workshop is active, but "skill_workshop" is hidden for agent "main": tools.profile: "messaging" does not include "skill_workshop". Add tools.alsoAllow: ["skill_workshop"].', }); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); diff --git a/src/cli/plugins-cli.policy.test.ts b/src/cli/plugins-cli.policy.test.ts index d897a00d78ad..60eb4c6fb4a3 100644 --- a/src/cli/plugins-cli.policy.test.ts +++ b/src/cli/plugins-cli.policy.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { - buildPluginRegistrySnapshotReport, + buildPluginRegistrySnapshotReportMock, enablePluginInConfigMock, loadConfig, replaceConfigFile, @@ -35,7 +35,7 @@ describe("plugins cli policy mutations", () => { }); function mockPluginRegistry(ids: string[]) { - buildPluginRegistrySnapshotReport.mockReturnValue({ + buildPluginRegistrySnapshotReportMock.mockReturnValue({ plugins: ids.map((id) => ({ id })), diagnostics: [], registrySource: "derived", diff --git a/src/cli/plugins-cli.uninstall.test.ts b/src/cli/plugins-cli.uninstall.test.ts index c2bda7fb28bd..0f94e37c1c87 100644 --- a/src/cli/plugins-cli.uninstall.test.ts +++ b/src/cli/plugins-cli.uninstall.test.ts @@ -7,9 +7,9 @@ import type { ClawAddPlan } from "../claws/types.js"; import type { OpenClawConfig } from "../config/config.js"; import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js"; import { - applyPluginUninstallDirectoryRemoval, - buildPluginDiagnosticsReport, - buildPluginSnapshotReport, + applyPluginUninstallDirectoryRemovalMock, + buildPluginDiagnosticsReportMock, + buildPluginSnapshotReportMock, createTestInstalledPluginIndex, loadConfig, planPluginUninstall, @@ -121,7 +121,7 @@ describe("plugins cli uninstall", () => { } expect(planPluginUninstall).not.toHaveBeenCalled(); - expect(applyPluginUninstallDirectoryRemoval).not.toHaveBeenCalled(); + expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); }); @@ -146,7 +146,7 @@ describe("plugins cli uninstall", () => { }, }, } as OpenClawConfig); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); @@ -154,8 +154,8 @@ describe("plugins cli uninstall", () => { await runPluginsCommand(["plugins", "uninstall", "alpha", "--dry-run"]); - expect(buildPluginSnapshotReport).toHaveBeenCalledTimes(1); - expect(buildPluginDiagnosticsReport).not.toHaveBeenCalled(); + expect(buildPluginSnapshotReportMock).toHaveBeenCalledTimes(1); + expect(buildPluginDiagnosticsReportMock).not.toHaveBeenCalled(); expect(planPluginUninstall).toHaveBeenCalledTimes(1); expect(writeConfigFile).not.toHaveBeenCalled(); expect(refreshPluginRegistry).not.toHaveBeenCalled(); @@ -187,7 +187,7 @@ describe("plugins cli uninstall", () => { loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); @@ -252,7 +252,7 @@ describe("plugins cli uninstall", () => { loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ { id: "unrelated-plugin", name: "calendar" }, { id: "calendar", name: "Real Calendar" }, @@ -292,7 +292,7 @@ describe("plugins cli uninstall", () => { loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ { id: "calendar-one", name: "calendar" }, { id: "calendar-two", name: "calendar" }, @@ -307,7 +307,7 @@ describe("plugins cli uninstall", () => { expect(runtimeErrors.at(-1)).toContain('Plugin uninstall target "calendar" is ambiguous'); expect(planPluginUninstall).not.toHaveBeenCalled(); expect(promptYesNoMock).not.toHaveBeenCalled(); - expect(applyPluginUninstallDirectoryRemoval).not.toHaveBeenCalled(); + expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); expect(writePersistedInstalledPluginIndexInstallRecordsWithLease).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(refreshPluginRegistry).not.toHaveBeenCalled(); @@ -333,7 +333,7 @@ describe("plugins cli uninstall", () => { } as OpenClawConfig; loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords({ alpha: installRecord }); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); @@ -387,7 +387,7 @@ describe("plugins cli uninstall", () => { } as OpenClawConfig; loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); @@ -404,7 +404,7 @@ describe("plugins cli uninstall", () => { expect(writePersistedInstalledPluginIndexInstallRecordsWithLease).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); expect(refreshPluginRegistry).not.toHaveBeenCalled(); - expect(applyPluginUninstallDirectoryRemoval).not.toHaveBeenCalled(); + expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); }); it("restores install records when the config write rejects during uninstall", async () => { @@ -437,7 +437,7 @@ describe("plugins cli uninstall", () => { loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); readPersistedInstalledPluginIndex.mockResolvedValue(previousPersistedIndex); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); @@ -458,7 +458,7 @@ describe("plugins cli uninstall", () => { }), ); expect(refreshPluginRegistry).not.toHaveBeenCalled(); - expect(applyPluginUninstallDirectoryRemoval).not.toHaveBeenCalled(); + expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); }); it("removes plugin files only after config and index commit succeeds", async () => { @@ -486,12 +486,12 @@ describe("plugins cli uninstall", () => { loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); primeUninstallPlan(nextConfig, { directoryRemoval: { target: ALPHA_INSTALL_PATH } }); - applyPluginUninstallDirectoryRemoval.mockResolvedValue({ + applyPluginUninstallDirectoryRemovalMock.mockResolvedValue({ directoryRemoved: true, warnings: [], }); @@ -500,18 +500,19 @@ describe("plugins cli uninstall", () => { const configWriteOrder = writeConfigFile.mock.invocationCallOrder[0] ?? 0; const deleteOrder = - applyPluginUninstallDirectoryRemoval.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER; + applyPluginUninstallDirectoryRemovalMock.mock.invocationCallOrder[0] ?? + Number.MAX_SAFE_INTEGER; const finalConfigWriteOrder = writeConfigFile.mock.invocationCallOrder[1] ?? Number.MAX_SAFE_INTEGER; const refreshOrder = refreshPluginRegistry.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER; expect(writeConfigFile).toHaveBeenCalledTimes(2); - expect(applyPluginUninstallDirectoryRemoval).toHaveBeenCalledTimes(1); + expect(applyPluginUninstallDirectoryRemovalMock).toHaveBeenCalledTimes(1); expect(refreshPluginRegistry).toHaveBeenCalledTimes(1); expect(deleteOrder).toBeGreaterThan(configWriteOrder); expect(finalConfigWriteOrder).toBeGreaterThan(deleteOrder); expect(refreshOrder).toBeGreaterThan(finalConfigWriteOrder); - expect(applyPluginUninstallDirectoryRemoval).toHaveBeenCalledWith({ + expect(applyPluginUninstallDirectoryRemovalMock).toHaveBeenCalledWith({ target: ALPHA_INSTALL_PATH, }); }); @@ -535,14 +536,14 @@ describe("plugins cli uninstall", () => { } as OpenClawConfig; loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); primeUninstallPlan({ plugins: { entries: {}, installs: {} } } as OpenClawConfig, { directoryRemoval: { target: installPath }, }); - applyPluginUninstallDirectoryRemoval.mockResolvedValue({ + applyPluginUninstallDirectoryRemovalMock.mockResolvedValue({ directoryRemoved: false, warnings: ["simulated removal failure"], }); @@ -577,7 +578,7 @@ describe("plugins cli uninstall", () => { } as OpenClawConfig; loadConfig.mockReturnValue(baseConfig); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -609,7 +610,7 @@ describe("plugins cli uninstall", () => { const nextConfig = {} as OpenClawConfig; loadConfig.mockReturnValue(baseConfig); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -665,7 +666,7 @@ describe("plugins cli uninstall", () => { loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ { id: pluginId, name: pluginId, status, channelIds }, { @@ -742,7 +743,7 @@ describe("plugins cli uninstall", () => { loadConfig.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -768,7 +769,7 @@ describe("plugins cli uninstall", () => { installs: {}, }, } as OpenClawConfig); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); diff --git a/src/cli/plugins-install-preflight.test.ts b/src/cli/plugins-install-preflight.test.ts index c523532efdb6..99e13da6ca53 100644 --- a/src/cli/plugins-install-preflight.test.ts +++ b/src/cli/plugins-install-preflight.test.ts @@ -1,14 +1,14 @@ // Invalid plugin install requests must fail before persistent state or source execution. import { beforeEach, describe, expect, it, vi } from "vitest"; import { - installHooksFromNpmSpec, - installHooksFromPath, - installPluginFromClawHub, - installPluginFromGitSpec, - installPluginFromMarketplace, - installPluginFromNpmPackArchive, - installPluginFromNpmSpec, - installPluginFromPath, + installHooksFromNpmSpecMock, + installHooksFromPathMock, + installPluginFromClawHubMock, + installPluginFromGitSpecMock, + installPluginFromMarketplaceMock, + installPluginFromNpmPackArchiveMock, + installPluginFromNpmSpecMock, + installPluginFromPathMock, parseClawHubPluginSpec, promptYesNoMock, readConfigFileSnapshotForWrite, @@ -32,14 +32,14 @@ function expectNoPluginInstallSideEffects(): void { expect(withPluginLifecycleLeaseMock).not.toHaveBeenCalled(); expect(readConfigFileSnapshotForWrite).not.toHaveBeenCalled(); expect(promptYesNoMock).not.toHaveBeenCalled(); - expect(installPluginFromClawHub).not.toHaveBeenCalled(); - expect(installPluginFromGitSpec).not.toHaveBeenCalled(); - expect(installPluginFromMarketplace).not.toHaveBeenCalled(); - expect(installPluginFromNpmPackArchive).not.toHaveBeenCalled(); - expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); - expect(installPluginFromPath).not.toHaveBeenCalled(); - expect(installHooksFromNpmSpec).not.toHaveBeenCalled(); - expect(installHooksFromPath).not.toHaveBeenCalled(); + expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); + expect(installPluginFromGitSpecMock).not.toHaveBeenCalled(); + expect(installPluginFromMarketplaceMock).not.toHaveBeenCalled(); + expect(installPluginFromNpmPackArchiveMock).not.toHaveBeenCalled(); + expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); + expect(installPluginFromPathMock).not.toHaveBeenCalled(); + expect(installHooksFromNpmSpecMock).not.toHaveBeenCalled(); + expect(installHooksFromPathMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); } diff --git a/src/cli/plugins-update-command.ts b/src/cli/plugins-update-command.ts index a7f930476346..0d17035b158a 100644 --- a/src/cli/plugins-update-command.ts +++ b/src/cli/plugins-update-command.ts @@ -12,6 +12,7 @@ import { formatInvalidConfigDetails, } from "../config/io.invalid-config.js"; import type { ConfigWriteOptions } from "../config/io.js"; +import { containsConfigIncludeDirective } from "../config/io.read-helpers.js"; import { createMergePatch } from "../config/merge-patch.js"; import { applyMergePatch } from "../config/merge-patch.js"; import { ConfigMutationConflictError } from "../config/mutate.js"; @@ -21,7 +22,6 @@ import { readHookInstalls } from "../hooks/installs.js"; import { updateNpmInstalledHookPacks } from "../hooks/update.js"; import { normalizeUpdateChannel, resolveRegistryUpdateChannel } from "../infra/update-channels.js"; import { - containsConfigIncludeDirective, resolveCombinedPluginAndHookConfigMutationPreflight, resolveInstallConfigMutationPreflights, selectInstallMutationWriteOptions, diff --git a/src/cli/program.nodes-basic.e2e.test.ts b/src/cli/program.nodes-basic.e2e.test.ts index 7fdcf7ac20b5..782fc92d4078 100644 --- a/src/cli/program.nodes-basic.e2e.test.ts +++ b/src/cli/program.nodes-basic.e2e.test.ts @@ -2,7 +2,7 @@ import { Command } from "commander"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { createIosNodeListResponse } from "./program.nodes-test-helpers.js"; -import { callGateway, runtime } from "./program.test-mocks.js"; +import { programGatewayCallMock, runtime } from "./program.test-mocks.js"; let registerNodesCli: typeof import("./nodes-cli.js").registerNodesCli; @@ -54,7 +54,7 @@ describe("cli program (nodes basics)", () => { } function gatewayRequests(): GatewayCallRequest[] { - return callGateway.mock.calls.map(([request]) => request as GatewayCallRequest); + return programGatewayCallMock.mock.calls.map(([request]) => request as GatewayCallRequest); } function writeJsonArgAt(index: number): unknown { @@ -75,7 +75,7 @@ describe("cli program (nodes basics)", () => { } function mockGatewayWithIosNodeListAnd(method: "node.describe" | "node.invoke", result: unknown) { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.list") { return createIosNodeListResponse(); @@ -95,7 +95,7 @@ describe("cli program (nodes basics)", () => { it("runs nodes list with the effective paired node view while preserving paired metadata", async () => { const now = Date.now(); - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.pair.list") { return { @@ -204,7 +204,7 @@ describe("cli program (nodes basics)", () => { }); it("runs unfiltered nodes list with pairing data when node.list is unavailable", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.pair.list") { return { @@ -233,7 +233,7 @@ describe("cli program (nodes basics)", () => { it("sanitizes untrusted nodes list table fields while preserving JSON values", async () => { const now = Date.now(); - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.pair.list") { return { @@ -286,7 +286,7 @@ describe("cli program (nodes basics)", () => { it("runs nodes list --connected and filters to connected nodes", async () => { const now = Date.now(); - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.pair.list") { return { @@ -326,7 +326,7 @@ describe("cli program (nodes basics)", () => { }); it("counts catalog-only paired nodes in the filtered list total", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.pair.list") { return { @@ -360,7 +360,7 @@ describe("cli program (nodes basics)", () => { it("runs nodes status --last-connected and filters by age", async () => { const now = Date.now(); - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.list") { return { @@ -487,7 +487,7 @@ describe("cli program (nodes basics)", () => { ], }, ])("runs nodes status and renders $label", async ({ node, expectedOutput }) => { - callGateway.mockResolvedValue({ + programGatewayCallMock.mockResolvedValue({ ts: Date.now(), nodes: [node], }); @@ -524,7 +524,7 @@ describe("cli program (nodes basics)", () => { rejectedPath: "path: /one:/two:/three:/four", }, ])("renders $platform node PATH entries with their platform delimiter", async (fixture) => { - callGateway.mockResolvedValue({ + programGatewayCallMock.mockResolvedValue({ ts: Date.now(), nodes: [ { @@ -546,7 +546,7 @@ describe("cli program (nodes basics)", () => { }); it("keeps connection age adjacent to connection status before pending approval", async () => { - callGateway.mockResolvedValue({ + programGatewayCallMock.mockResolvedValue({ ts: Date.now(), nodes: [ { @@ -609,7 +609,7 @@ describe("cli program (nodes basics)", () => { }); it("keeps explicit gateway options in node reapproval guidance without leaking auth", async () => { - callGateway.mockResolvedValue({ + programGatewayCallMock.mockResolvedValue({ ts: Date.now(), nodes: [ { @@ -644,7 +644,7 @@ describe("cli program (nodes basics)", () => { }); it("falls back to read-only node status when pairing diagnostics are unavailable", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string; scopes?: string[]; @@ -698,7 +698,7 @@ describe("cli program (nodes basics)", () => { }); it("keeps remote explicit diagnostic credentials on the read-only path", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string; requireLocalBackendSharedAuth?: boolean; @@ -751,7 +751,7 @@ describe("cli program (nodes basics)", () => { }); it("does not retry node diagnostics after a transport failure", async () => { - callGateway.mockRejectedValue(new Error("gateway timed out")); + programGatewayCallMock.mockRejectedValue(new Error("gateway timed out")); await expect(runProgram(["nodes", "status"])).rejects.toThrow("exit"); @@ -761,7 +761,7 @@ describe("cli program (nodes basics)", () => { }); it("falls back to configured auth after stored device auth is rejected", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string; useStoredDeviceAuth?: boolean }; if (opts.method === "node.list" && opts.useStoredDeviceAuth) { throw Object.assign(new Error("unauthorized: device token mismatch"), { @@ -795,7 +795,7 @@ describe("cli program (nodes basics)", () => { }); it("falls back to configured auth when stored device auth lacks read scope", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string; scopes?: string[]; @@ -839,7 +839,7 @@ describe("cli program (nodes basics)", () => { }); it("describes pending-only nodes through the pairing diagnostics view", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string; params?: { nodeId?: string }; @@ -883,7 +883,7 @@ describe("cli program (nodes basics)", () => { }); it("describes nodes through the paired-node fallback on older gateways", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string; params?: { nodeId?: string }; @@ -937,7 +937,7 @@ describe("cli program (nodes basics)", () => { }); it("runs nodes approve with the pending request approval scopes", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.pair.list") { return { @@ -978,7 +978,7 @@ describe("cli program (nodes basics)", () => { }); it("falls back to command-derived nodes approve scopes", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.pair.list") { return { @@ -1021,11 +1021,11 @@ describe("cli program (nodes basics)", () => { { scopes: ["operator.admin"] }, ), ).rejects.toThrow("unsupported node pair approval gateway method: node.invoke"); - expect(callGateway).not.toHaveBeenCalled(); + expect(programGatewayCallMock).not.toHaveBeenCalled(); }); it("runs nodes remove and calls node.pair.remove", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.list") { return { diff --git a/src/cli/program.nodes-media.e2e.test.ts b/src/cli/program.nodes-media.e2e.test.ts index 29e883e2dbd8..b1afc271680f 100644 --- a/src/cli/program.nodes-media.e2e.test.ts +++ b/src/cli/program.nodes-media.e2e.test.ts @@ -4,7 +4,7 @@ import { expectDefined } from "@openclaw/normalization-core"; import { Command } from "commander"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { IOS_NODE, createIosNodeListResponse } from "./program.nodes-test-helpers.js"; -import { callGateway, runtime } from "./program.test-mocks.js"; +import { programGatewayCallMock, runtime } from "./program.test-mocks.js"; let registerNodesCli: typeof import("./nodes-cli.js").registerNodesCli; @@ -34,7 +34,7 @@ async function expectLoggedSingleMediaFile(params?: { } function mockNodeGateway(command?: string, payload?: Record) { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.list") { return createIosNodeListResponse(); @@ -56,7 +56,7 @@ function nodeInvokeCalls(): Array<{ params: Record; commandParams: Record; }> { - return callGateway.mock.calls + return programGatewayCallMock.mock.calls .map((call) => call[0] as { method?: unknown; params?: Record }) .filter((call) => call.method === "node.invoke") .map((call) => { @@ -166,7 +166,7 @@ describe("cli program (nodes media)", () => { expectedPathPattern: /openclaw-camera-snap-unknown-.*\.jpg$/, }); - callGateway.mockClear(); + programGatewayCallMock.mockClear(); await runNodesCommand(["nodes", "camera", "snap", "--node", "ios-node", "--facing", "both"]); const invokeCalls = nodeInvokeCalls(); @@ -206,7 +206,7 @@ describe("cli program (nodes media)", () => { }); it("runs one unknown-position camera snap for a Linux node", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.list") { return { @@ -279,7 +279,7 @@ describe("cli program (nodes media)", () => { }); it("runs an unknown-position camera clip for a Linux node", async () => { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.list") { return { diff --git a/src/cli/program.nodes-push.e2e.test.ts b/src/cli/program.nodes-push.e2e.test.ts index 292e891090a3..085e821628fc 100644 --- a/src/cli/program.nodes-push.e2e.test.ts +++ b/src/cli/program.nodes-push.e2e.test.ts @@ -3,7 +3,7 @@ import { Command } from "commander"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { PushTestResult } from "../../packages/gateway-protocol/src/index.js"; import { createIosNodeListResponse } from "./program.nodes-test-helpers.js"; -import { callGateway, runtime } from "./program.test-mocks.js"; +import { programGatewayCallMock, runtime } from "./program.test-mocks.js"; let registerNodesCli: typeof import("./nodes-cli.js").registerNodesCli; @@ -12,7 +12,7 @@ describe("cli program (nodes push)", () => { let previousExitCode: NodeJS.Process["exitCode"]; function mockPushResult(result: PushTestResult) { - callGateway.mockImplementation(async (...args: unknown[]) => { + programGatewayCallMock.mockImplementation(async (...args: unknown[]) => { const opts = (args[0] ?? {}) as { method?: string }; if (opts.method === "node.list") { return createIosNodeListResponse(); diff --git a/src/cli/program.smoke.test.ts b/src/cli/program.smoke.test.ts index e0ce945e099e..565e5c7b2851 100644 --- a/src/cli/program.smoke.test.ts +++ b/src/cli/program.smoke.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { buildProgram } from "./program.js"; import { - callGateway, + programGatewayCallMock, configureCommand, ensureConfigReadyMock, runSystemAgentWithInference, @@ -68,11 +68,11 @@ describe("cli program (smoke)", () => { }); it("resolves a positional tui short reference before launch", async () => { - callGateway.mockResolvedValue({ ok: true, key: "agent:main:thread:resolved" }); + programGatewayCallMock.mockResolvedValue({ ok: true, key: "agent:main:thread:resolved" }); await runProgram(["tui", "movies-a1166b81"]); - expect(callGateway).toHaveBeenCalledWith( + expect(programGatewayCallMock).toHaveBeenCalledWith( expect.objectContaining({ method: "sessions.resolve", params: { shortId: "a1166b81", slugHint: "movies" }, @@ -85,7 +85,7 @@ describe("cli program (smoke)", () => { }); it("preserves a global-scope URL main session when launching tui", async () => { - callGateway.mockResolvedValue({ + programGatewayCallMock.mockResolvedValue({ defaultId: "main", mainKey: "main", scope: "global", @@ -94,7 +94,7 @@ describe("cli program (smoke)", () => { await runProgram(["tui", "https://gateway.example/dashboard/ops"]); - expect(callGateway).toHaveBeenCalledWith( + expect(programGatewayCallMock).toHaveBeenCalledWith( expect.objectContaining({ method: "agents.list", params: {}, diff --git a/src/cli/program.test-mocks.ts b/src/cli/program.test-mocks.ts index 840b763adea5..f1bbf676d7d1 100644 --- a/src/cli/program.test-mocks.ts +++ b/src/cli/program.test-mocks.ts @@ -41,7 +41,7 @@ const programMocks = vi.hoisted(() => { export const configureCommand = programMocks.configureCommand as AnyMock; export const setupCommand = programMocks.setupCommand as AnyMock; export const setupWizardCommand = programMocks.setupWizardCommand as AnyMock; -export const callGateway = programMocks.callGateway as AnyMock; +export const programGatewayCallMock = programMocks.callGateway as AnyMock; export const runTui = programMocks.runTui as AnyMock; export const runSystemAgentWithInference = programMocks.runSystemAgentWithInference as AnyMock; export const ensureConfigReadyMock = programMocks.ensureConfigReady as AnyMock; diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 085dbee58547..e659af9eb807 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -68,7 +68,7 @@ import { import { registerSignalExitBarrier, waitForSignalExitBarriers } from "./signal-exit-barrier.js"; import { configureGatewayStartupTraceConsoleFormatting, - createGatewayStartupTrace, + createGatewayDispatchStartupTrace, } from "./startup-trace.js"; import { normalizeWindowsArgv } from "./windows-argv.js"; @@ -161,7 +161,7 @@ function isGatewayRunInvocationArgv(argv: string[]): boolean { async function tryRunGatewayRunFastPath( argv: string[], - startupTrace: ReturnType, + startupTrace: ReturnType, ): Promise { if (!isGatewayRunFastPathArgv(argv)) { return false; @@ -1025,7 +1025,7 @@ async function resolveUnownedCliPrimaryMessage(params: { } async function bootstrapCliProxyCaptureAndDispatcher( - startupTrace: ReturnType, + startupTrace: ReturnType, options: { ensureDispatcher?: boolean } = {}, ): Promise { const [ @@ -1047,7 +1047,7 @@ async function bootstrapCliProxyCaptureAndDispatcher( export async function runCli( argv: string[] = process.argv, options: { - additionalStartupTrace?: ReturnType; + additionalStartupTrace?: ReturnType; retainConsoleRoutingUntilProcessExit?: boolean; } = {}, ) { @@ -1067,11 +1067,11 @@ export async function runCli( async function runCliWithPreparedOutputMode( originalArgv: string[], options: { - additionalStartupTrace?: ReturnType; + additionalStartupTrace?: ReturnType; builtInMachineOutput: boolean; }, ) { - const startupTrace = createGatewayStartupTrace(originalArgv, "cli.main"); + const startupTrace = createGatewayDispatchStartupTrace(originalArgv, "cli.main"); const earlyProfile = parseCliProfileArgs(originalArgv); if (earlyProfile.ok && earlyProfile.profile) { applyCliProfileEnv({ profile: earlyProfile.profile }); @@ -1222,7 +1222,7 @@ async function runCliWithPreparedOutputMode( return await bestEffortConfigPromise; }; const startupTraces = [startupTrace, options.additionalStartupTrace].filter( - (trace): trace is ReturnType => Boolean(trace), + (trace): trace is ReturnType => Boolean(trace), ); if ( !isDatabaseInvocation && diff --git a/src/cli/startup-trace.test.ts b/src/cli/startup-trace.test.ts index 4c81a224fa01..969e35378782 100644 --- a/src/cli/startup-trace.test.ts +++ b/src/cli/startup-trace.test.ts @@ -3,7 +3,7 @@ import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { createGatewayStartupTrace } from "./startup-trace.js"; +import { createGatewayDispatchStartupTrace } from "./startup-trace.js"; function readTimelineEvents(timelinePath: string): Record[] { return fs @@ -24,7 +24,10 @@ describe("CLI startup trace", () => { vi.stubEnv("OPENCLAW_DIAGNOSTICS", "timeline"); vi.stubEnv("OPENCLAW_DIAGNOSTICS_TIMELINE_PATH", timelinePath); - const trace = createGatewayStartupTrace(["node", "openclaw", "agent", "--local"], "entry"); + const trace = createGatewayDispatchStartupTrace( + ["node", "openclaw", "agent", "--local"], + "entry", + ); trace.mark("bootstrap"); await trace.measure("run-main-import", async () => { await Promise.resolve(); @@ -71,7 +74,10 @@ describe("CLI startup trace", () => { vi.stubEnv("OPENCLAW_DIAGNOSTICS", ""); vi.stubEnv("OPENCLAW_DIAGNOSTICS_TIMELINE_PATH", timelinePath); - const trace = createGatewayStartupTrace(["node", "openclaw", "agent", "--local"], "entry"); + const trace = createGatewayDispatchStartupTrace( + ["node", "openclaw", "agent", "--local"], + "entry", + ); trace.mark("bootstrap"); await trace.measure("run-main-import", async () => "loaded"); diff --git a/src/cli/startup-trace.ts b/src/cli/startup-trace.ts index cc463be0966e..7b02e73ba916 100644 --- a/src/cli/startup-trace.ts +++ b/src/cli/startup-trace.ts @@ -28,7 +28,7 @@ function hasDiagnosticsTimelinePath(env: NodeJS.ProcessEnv): boolean { return Boolean(env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH?.trim()); } -export function createGatewayStartupTrace( +export function createGatewayDispatchStartupTrace( argv: string[], source: GatewayStartupTraceSource, ): { @@ -252,7 +252,7 @@ export function createGatewayStartupTrace( } export async function configureGatewayStartupTraceConsoleFormatting( - trace: ReturnType, + trace: ReturnType, ): Promise { if (!trace.enabled) { return; diff --git a/src/commands/channels.status.external-env.test.ts b/src/commands/channels.status.external-env.test.ts index 53c9953fc77f..80e1c4f61fd2 100644 --- a/src/commands/channels.status.external-env.test.ts +++ b/src/commands/channels.status.external-env.test.ts @@ -6,7 +6,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { cleanupPluginLoaderFixturesForTest, EMPTY_PLUGIN_SCHEMA, - makeTempDir, + makePluginLoaderTempDir, resetPluginLoaderTestStateForTest, useNoBundledPlugins, } from "../plugins/loader.test-fixtures.js"; @@ -53,7 +53,7 @@ vi.mock("../cli/progress.js", () => ({ function writeExternalEnvChannelPlugin() { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const fullMarker = path.join(pluginDir, "full-loaded.txt"); fs.writeFileSync( path.join(pluginDir, "package.json"), diff --git a/src/commands/doctor-config-analysis.test.ts b/src/commands/doctor-config-analysis.test.ts index 4bdfba8000a4..7be705167200 100644 --- a/src/commands/doctor-config-analysis.test.ts +++ b/src/commands/doctor-config-analysis.test.ts @@ -4,7 +4,7 @@ import { resolveAgentModelFallbackValues } from "../config/model-input.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { OpenClawSchema } from "../config/zod-schema.js"; import { - formatConfigPath, + formatConfigKeyPath, noteImplicitFallbackClobberWarnings, noteOpencodeProviderOverrides, noteSandboxOriginProxyWarning, @@ -85,8 +85,8 @@ describe("doctor config analysis helpers", () => { }); it("formats config paths predictably", () => { - expect(formatConfigPath([])).toBe(""); - expect(formatConfigPath(["channels", "slack", "accounts", 0, "token"])).toBe( + expect(formatConfigKeyPath([])).toBe(""); + expect(formatConfigKeyPath(["channels", "slack", "accounts", 0, "token"])).toBe( "channels.slack.accounts[0].token", ); }); @@ -204,7 +204,7 @@ describe("doctor config analysis helpers", () => { const result = stripUnknownConfigKeys(config as never); expect(result.removed).toContain("unexpected"); - expect(result.removed).not.toContain(formatConfigPath([...path, "$include"])); + expect(result.removed).not.toContain(formatConfigKeyPath([...path, "$include"])); expect(resolveConfigPathTarget(result.config, path)).toMatchObject({ $include: expect.any(String), }); diff --git a/src/commands/doctor-config-analysis.ts b/src/commands/doctor-config-analysis.ts index be1933866e2d..23afa70d75b8 100644 --- a/src/commands/doctor-config-analysis.ts +++ b/src/commands/doctor-config-analysis.ts @@ -24,7 +24,7 @@ function isUnrecognizedKeysIssue(issue: ZodIssue): issue is UnrecognizedKeysIssu } /** Formats a parsed config issue path into a user-facing dotted path. */ -export function formatConfigPath(parts: Array): string { +export function formatConfigKeyPath(parts: Array): string { if (parts.length === 0) { return ""; } @@ -122,7 +122,7 @@ export function stripUnknownConfigKeys(config: OpenClawConfig): { continue; } delete record[key]; - removed.push(formatConfigPath([...issuePath, key])); + removed.push(formatConfigKeyPath([...issuePath, key])); } } diff --git a/src/commands/doctor-config-flow.test.ts b/src/commands/doctor-config-flow.test.ts index a14be9094920..6153303c8dd4 100644 --- a/src/commands/doctor-config-flow.test.ts +++ b/src/commands/doctor-config-flow.test.ts @@ -1514,7 +1514,7 @@ vi.mock("./doctor-config-preflight.js", async () => { }); vi.mock("./doctor-config-analysis.js", () => { - function formatConfigPath(parts: Array): string { + function formatConfigKeyPath(parts: Array): string { if (parts.length === 0) { return ""; } @@ -1549,7 +1549,7 @@ vi.mock("./doctor-config-analysis.js", () => { return { collectImplicitFallbackClobberWarnings: collectImplicitFallbackClobberWarningsMock, - formatConfigPath, + formatConfigKeyPath, noteImplicitFallbackClobberWarnings: noteImplicitFallbackClobberWarningsMock, noteIncludeConfinementWarning: vi.fn(), noteOpencodeProviderOverrides: vi.fn(), diff --git a/src/commands/doctor-config-preflight.process.test.ts b/src/commands/doctor-config-preflight.process.test.ts index c3dab7ae425c..87a4e3d9058f 100644 --- a/src/commands/doctor-config-preflight.process.test.ts +++ b/src/commands/doctor-config-preflight.process.test.ts @@ -654,8 +654,8 @@ describe("gateway startup-migration refusal", () => { manifestPluginCount: metadata?.plugins.length ?? -1, scoped: metadata?.pluginIds !== undefined, })); - const { doctorCommand } = await import(${JSON.stringify(doctorHealthUrl)}); - await doctorCommand({ + const { runDoctorHealthFlow } = await import(${JSON.stringify(doctorHealthUrl)}); + await runDoctorHealthFlow({ log: () => {}, error: () => {}, exit: (code) => { throw new Error("doctor exited " + code); }, diff --git a/src/commands/doctor-plugin-host-links.ts b/src/commands/doctor-plugin-host-links.ts index 69f6e4fdb387..795d70691d46 100644 --- a/src/commands/doctor-plugin-host-links.ts +++ b/src/commands/doctor-plugin-host-links.ts @@ -47,7 +47,7 @@ function resolveRegisteredPluginExtensionsRoot( } /** Resolves all managed npm roots from the doctor state override or environment. */ -export function listManagedPluginNpmRoots( +export function resolveDoctorPluginNpmRoots( params: InstalledPluginIndexRecordStoreOptions, ): string[] { const npmRoot = params.stateDir @@ -63,7 +63,7 @@ export async function listPluginOpenClawHostLinkIssues( const packageReadFailures: PluginPackageReadFailure[] = []; const registeredPackageReadFailures: PluginPackageReadFailure[] = []; const audits = await Promise.all( - listManagedPluginNpmRoots(params).map((npmRoot) => + resolveDoctorPluginNpmRoots(params).map((npmRoot) => auditOpenClawPeerDependenciesInManagedNpmRoot({ npmRoot, onPackageReadError: (error, packageDir) => { @@ -99,7 +99,7 @@ export async function listPluginOpenClawHostLinkIssues( export async function maybeRepairPluginOpenClawHostLinks( params: PluginHostLinkDoctorParams, ): Promise { - const npmRoots = listManagedPluginNpmRoots(params); + const npmRoots = resolveDoctorPluginNpmRoots(params); if (!params.prompter.shouldRepair) { const audit = await listPluginOpenClawHostLinkIssues(params); if (audit.peerLinkIssues.length > 0) { diff --git a/src/commands/doctor-plugin-registry.ts b/src/commands/doctor-plugin-registry.ts index a98e8ae85d0e..d1008cacaf75 100644 --- a/src/commands/doctor-plugin-registry.ts +++ b/src/commands/doctor-plugin-registry.ts @@ -31,7 +31,7 @@ import { type StaleManagedNpmInstallGenerationIssue, } from "./doctor-plugin-generations.js"; import { - listManagedPluginNpmRoots, + resolveDoctorPluginNpmRoots, listPluginOpenClawHostLinkIssues, maybeRepairPluginOpenClawHostLinks, } from "./doctor-plugin-host-links.js"; @@ -154,7 +154,7 @@ function listStaleManagedNpmBundledPlugins( ); const stale: StaleManagedNpmBundledPlugin[] = []; - for (const npmRoot of listManagedPluginNpmRoots(params)) { + for (const npmRoot of resolveDoctorPluginNpmRoots(params)) { const npmPackageJsonPath = path.join(npmRoot, "package.json"); const dependencies = readStringMap(readJsonObject(npmPackageJsonPath)?.dependencies); for (const packageName of Object.keys(dependencies).toSorted((left, right) => diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index eb2b7bbf5b25..77380bd7e166 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -156,7 +156,7 @@ export async function doctorCommand(runtime?: RuntimeEnv, options?: DoctorOption return; } const doctorHealth = await import("../flows/doctor-health.js"); - await doctorHealth.doctorCommand(runtime, options); + await doctorHealth.runDoctorHealthFlow(runtime, options); } async function maybeCreateSessionSqliteGithubIssue( diff --git a/src/commands/doctor/shared/legacy-oauth-sidecar.ts b/src/commands/doctor/shared/legacy-oauth-sidecar.ts index fa8bb9ef8cf8..af803679bee5 100644 --- a/src/commands/doctor/shared/legacy-oauth-sidecar.ts +++ b/src/commands/doctor/shared/legacy-oauth-sidecar.ts @@ -6,7 +6,7 @@ import os from "node:os"; import path from "node:path"; import { isRecord } from "@openclaw/normalization-core/record-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; -import { log } from "../../../agents/auth-profiles/constants.js"; +import { authProfilesLog } from "../../../agents/auth-profiles/constants.js"; import { LEGACY_OAUTH_REF_PROVIDER } from "../../../agents/auth-profiles/legacy-oauth-ref.js"; import type { LegacyOAuthRef } from "../../../agents/auth-profiles/legacy-oauth-ref.js"; import { resolveOAuthDir, resolveStateDir } from "../../../config/paths.js"; @@ -302,7 +302,7 @@ function emitKeychainOnlyMigrationHintOnce(profileId: string): void { return; } keychainOnlyMigrationHintEmitted = true; - log.warn( + authProfilesLog.warn( "Legacy Codex OAuth credentials are stored only in macOS Keychain on this host. " + "Headless paths cannot prompt for Keychain access; run `openclaw doctor --fix` " + "from an interactive terminal to migrate them back to inline auth-profiles.json credentials.", diff --git a/src/commands/doctor/shared/legacy-tools-by-sender.ts b/src/commands/doctor/shared/legacy-tools-by-sender.ts index 98d47c2447bc..bf0b18c53419 100644 --- a/src/commands/doctor/shared/legacy-tools-by-sender.ts +++ b/src/commands/doctor/shared/legacy-tools-by-sender.ts @@ -2,7 +2,7 @@ import { sanitizeForLog } from "../../../../packages/terminal-core/src/ansi.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import { parseToolsBySenderTypedKey } from "../../../config/types.tools.js"; -import { formatConfigPath, resolveConfigPathTarget } from "../../doctor-config-analysis.js"; +import { formatConfigKeyPath, resolveConfigPathTarget } from "../../doctor-config-analysis.js"; import { asObjectRecord } from "./object.js"; type LegacyToolsBySenderKeyHit = { @@ -35,7 +35,7 @@ function collectLegacyToolsBySenderKeyHits( const toolsBySender = asObjectRecord(record.toolsBySender); if (toolsBySender) { const path = [...pathParts, "toolsBySender"]; - const pathLabel = formatConfigPath(path); + const pathLabel = formatConfigKeyPath(path); for (const rawKey of Object.keys(toolsBySender)) { const trimmed = rawKey.trim(); if (!trimmed || trimmed === "*" || parseToolsBySenderTypedKey(trimmed)) { diff --git a/src/commands/setup.test.ts b/src/commands/setup.test.ts index 042a737be13d..bd6058a19247 100644 --- a/src/commands/setup.test.ts +++ b/src/commands/setup.test.ts @@ -27,7 +27,7 @@ function createSetupDeps(home: string) { dir: params?.dir ?? path.join(home, ".openclaw", "workspace"), }), ), - formatConfigPath: (value: string) => value, + formatConfigFilePath: (value: string) => value, logConfigUpdated: vi.fn( (runtime: { log: (message: string) => void }, opts: { path?: string; suffix?: string }) => { const suffix = opts.suffix ? ` ${opts.suffix}` : ""; @@ -202,7 +202,7 @@ describe("setupCommand", () => { ); const deps = { ensureAgentWorkspace: vi.fn(async () => ({ dir: workspace })), - formatConfigPath: (value: string) => value, + formatConfigFilePath: (value: string) => value, mkdir: vi.fn(async () => {}), resolveSessionTranscriptsDir: vi.fn(() => path.join(home, "sessions")), }; @@ -276,7 +276,7 @@ describe("setupCommand", () => { ); const deps = { ensureAgentWorkspace: vi.fn(async () => ({ dir: workspace })), - formatConfigPath: (value: string) => value, + formatConfigFilePath: (value: string) => value, mkdir: vi.fn(async () => {}), resolveSessionTranscriptsDir: vi.fn(() => path.join(home, "ops-sessions")), }; @@ -308,7 +308,7 @@ describe("setupCommand", () => { await fs.writeFile(includePath, JSON.stringify(included)); const deps = { ensureAgentWorkspace: vi.fn(async () => ({ dir: nextWorkspace })), - formatConfigPath: (value: string) => value, + formatConfigFilePath: (value: string) => value, mkdir: vi.fn(async () => {}), resolveSessionTranscriptsDir: vi.fn(() => path.join(home, "ops-sessions")), }; @@ -346,7 +346,7 @@ describe("setupCommand", () => { await setupCommand({ workspace: nextWorkspace }, runtime, { ensureAgentWorkspace: vi.fn(async () => ({ dir: nextWorkspace })), - formatConfigPath: (value: string) => value, + formatConfigFilePath: (value: string) => value, mkdir: vi.fn(async () => {}), resolveSessionTranscriptsDir: vi.fn(() => path.join(home, "ops-sessions")), }); @@ -383,7 +383,7 @@ describe("setupCommand", () => { ); const deps = { ensureAgentWorkspace: vi.fn(async () => ({ dir: workspace })), - formatConfigPath: (value: string) => value, + formatConfigFilePath: (value: string) => value, mkdir: vi.fn(async () => {}), resolveSessionTranscriptsDir: vi.fn(() => path.join(home, "sessions")), }; diff --git a/src/commands/setup.ts b/src/commands/setup.ts index 67b666f7f8a5..8336d23c26a9 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -47,7 +47,7 @@ type SetupCommandDeps = { createConfigIO?: () => ConfigIO; defaultAgentWorkspaceDir?: string | (() => string | Promise); ensureAgentWorkspace?: EnsureAgentWorkspace; - formatConfigPath?: (path: string) => string; + formatConfigFilePath?: (path: string) => string; logConfigUpdated?: ( runtime: RuntimeEnv, opts: { path?: string; suffix?: string }, @@ -115,8 +115,8 @@ async function writeDefaultConfigFile(params: Parameters[0]): } async function formatDefaultConfigPath(configPath: string): Promise { - const { formatConfigPath } = await loadConfigLoggingModule(); - return formatConfigPath(configPath); + const { formatConfigFilePath } = await loadConfigLoggingModule(); + return formatConfigFilePath(configPath); } async function logDefaultConfigUpdated( @@ -148,9 +148,9 @@ export async function setupCommand( const prepared = await io.readConfigFileSnapshotForWrite(); const snapshot = prepared.snapshot; if (snapshot.exists && !snapshot.valid) { - const formatConfigPath = deps.formatConfigPath ?? formatDefaultConfigPath; + const formatConfigFilePath = deps.formatConfigFilePath ?? formatDefaultConfigPath; runtime.error( - `Config invalid at ${await formatConfigPath(configPath)}. Run \`${formatCliCommand("openclaw doctor")}\` to repair it, then re-run setup.`, + `Config invalid at ${await formatConfigFilePath(configPath)}. Run \`${formatCliCommand("openclaw doctor")}\` to repair it, then re-run setup.`, ); runtime.exit(1); return; @@ -255,8 +255,8 @@ export async function setupCommand( }); configStatus = snapshot.exists ? "updated" : "created"; if (!opts?.json && !snapshot.exists) { - const formatConfigPath = deps.formatConfigPath ?? formatDefaultConfigPath; - runtime.log(`Wrote ${await formatConfigPath(configPath)}`); + const formatConfigFilePath = deps.formatConfigFilePath ?? formatDefaultConfigPath; + runtime.log(`Wrote ${await formatConfigFilePath(configPath)}`); } else if (!opts?.json) { const updates: string[] = []; if (shouldWriteWorkspace) { @@ -274,8 +274,8 @@ export async function setupCommand( } else { configStatus = "unchanged"; if (!opts?.json) { - const formatConfigPath = deps.formatConfigPath ?? formatDefaultConfigPath; - runtime.log(`Config OK: ${await formatConfigPath(configPath)}`); + const formatConfigFilePath = deps.formatConfigFilePath ?? formatDefaultConfigPath; + runtime.log(`Config OK: ${await formatConfigFilePath(configPath)}`); } } diff --git a/src/config/logging.test.ts b/src/config/logging.test.ts index 5c3a854e99d6..75b471e45602 100644 --- a/src/config/logging.test.ts +++ b/src/config/logging.test.ts @@ -14,12 +14,12 @@ vi.mock("./io.js", () => ({ createConfigIO: mocks.createConfigIO, })); -let formatConfigPath: typeof import("./logging.js").formatConfigPath; +let formatConfigFilePath: typeof import("./logging.js").formatConfigFilePath; let formatConfigUpdatedMessage: typeof import("./logging.js").formatConfigUpdatedMessage; let logConfigUpdated: typeof import("./logging.js").logConfigUpdated; beforeAll(async () => { - ({ formatConfigPath, formatConfigUpdatedMessage, logConfigUpdated } = + ({ formatConfigFilePath, formatConfigUpdatedMessage, logConfigUpdated } = await import("./logging.js")); }); @@ -29,7 +29,7 @@ beforeEach(() => { describe("config logging", () => { it("formats the live config path when no explicit path is provided", () => { - expect(formatConfigPath()).toBe("/tmp/openclaw-dev/openclaw.json"); + expect(formatConfigFilePath()).toBe("/tmp/openclaw-dev/openclaw.json"); }); it("logs the live config path when no explicit path is provided", () => { diff --git a/src/config/logging.ts b/src/config/logging.ts index 8c5d52c65d50..f4e5894b9a01 100644 --- a/src/config/logging.ts +++ b/src/config/logging.ts @@ -12,7 +12,7 @@ type LogConfigUpdatedOptions = { }; /** Formats a config path for operator-facing log output. */ -export function formatConfigPath(path: string = createConfigIO().configPath): string { +export function formatConfigFilePath(path: string = createConfigIO().configPath): string { return displayPath(path); } @@ -21,14 +21,14 @@ export function formatConfigUpdatedMessage( path: string, opts: LogConfigUpdatedOptions = {}, ): string { - const displayConfigPath = theme.muted(formatConfigPath(path)); + const displayConfigPath = theme.muted(formatConfigFilePath(path)); const suffix = opts.suffix ? ` ${opts.suffix}` : ""; const backupPath = opts.backupPath === undefined ? `${path}.bak` : opts.backupPath; const lines = [`Updated config: ${displayConfigPath}${suffix}`]; if (backupPath && fs.existsSync(backupPath)) { // Only mention backups that were actually written; callers can pass `false` for flows that // intentionally skip backup creation. - lines.push(` Backup: ${theme.muted(formatConfigPath(backupPath))}`); + lines.push(` Backup: ${theme.muted(formatConfigFilePath(backupPath))}`); } return lines.join("\n"); } diff --git a/src/config/sessions/session-accessor.sqlite-message-cut.ts b/src/config/sessions/session-accessor.sqlite-message-cut.ts index f89d03963584..90dce3d26cd7 100644 --- a/src/config/sessions/session-accessor.sqlite-message-cut.ts +++ b/src/config/sessions/session-accessor.sqlite-message-cut.ts @@ -3,7 +3,7 @@ import { asOptionalRecord as asRecord } from "@openclaw/normalization-core/recor import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { executeSqliteQueryTakeFirstSync } from "../../infra/kysely-sync.js"; import { pruneMapToMaxSize } from "../../infra/map-size.js"; -import { extractAssistantVisibleText } from "../../shared/chat-message-content.js"; +import { extractAssistantPhaseText } from "../../shared/chat-message-content.js"; import { openOpenClawAgentDatabase, runOpenClawAgentWriteTransaction, @@ -436,7 +436,7 @@ function extractHeadlineText(messageValue: unknown): string | undefined { } const text = message.role === "assistant" - ? extractAssistantVisibleText(message) + ? extractAssistantPhaseText(message) : extractEditorText(message.content ?? message.text); const normalized = text?.replace(/\s+/g, " ").trim(); return normalized || undefined; diff --git a/src/config/sessions/session-accessor.sqlite-read.ts b/src/config/sessions/session-accessor.sqlite-read.ts index d8e2f868055f..01e56ecc6562 100644 --- a/src/config/sessions/session-accessor.sqlite-read.ts +++ b/src/config/sessions/session-accessor.sqlite-read.ts @@ -5,7 +5,7 @@ import { iterateSqliteQuerySync, } from "../../infra/kysely-sync.js"; import { runSqliteDeferredTransactionSync } from "../../infra/sqlite-transaction.js"; -import { extractAssistantVisibleText } from "../../shared/chat-message-content.js"; +import { extractAssistantPhaseText } from "../../shared/chat-message-content.js"; import { isTranscriptOnlyOpenClawAssistantModel } from "../../shared/transcript-only-openclaw-assistant.js"; import { openOpenClawAgentDatabase, @@ -323,7 +323,7 @@ function parseLatestAssistantText( latest: LatestTranscriptAssistantMessage, ): LatestTranscriptAssistantText | undefined { const message = latest.message as { timestamp?: unknown }; - const text = extractAssistantVisibleText(latest.message)?.trim(); + const text = extractAssistantPhaseText(latest.message)?.trim(); if (!text) { return undefined; } diff --git a/src/config/sessions/transcript.ts b/src/config/sessions/transcript.ts index a2de43038d90..87439a9a3870 100644 --- a/src/config/sessions/transcript.ts +++ b/src/config/sessions/transcript.ts @@ -11,7 +11,7 @@ import { scopeLegacySessionKeyToAgent, } from "../../routing/session-key.js"; import { - extractAssistantVisibleText, + extractAssistantPhaseText, extractFirstTextBlock, } from "../../shared/chat-message-content.js"; import { @@ -171,7 +171,7 @@ function parseAssistantTranscriptText( ) { return undefined; } - const text = extractAssistantVisibleText(message)?.trim(); + const text = extractAssistantPhaseText(message)?.trim(); if (!text) { return undefined; } @@ -232,7 +232,7 @@ function parseRecentConversationText( } const text = message.role === "assistant" - ? extractAssistantVisibleText(message) + ? extractAssistantPhaseText(message) : (upstreamUserText ?? extractFirstTextBlock(message)?.trim()); if (!text) { return undefined; diff --git a/src/cron/service/timer-execution-timeout.ts b/src/cron/service/timer-execution-timeout.ts index b767ccc6867a..5ba31f7cbc92 100644 --- a/src/cron/service/timer-execution-timeout.ts +++ b/src/cron/service/timer-execution-timeout.ts @@ -15,7 +15,7 @@ import type { } from "../types.js"; import type { CronServiceState } from "./state.js"; -export const MAX_TIMER_DELAY_MS = 60_000; +export const MAX_CRON_TIMER_DELAY_MS = 60_000; export const HEARTBEAT_SKIP_DISABLED = "disabled"; diff --git a/src/cron/service/timer-scheduler.ts b/src/cron/service/timer-scheduler.ts index 68c140222d56..2c871cba97bc 100644 --- a/src/cron/service/timer-scheduler.ts +++ b/src/cron/service/timer-scheduler.ts @@ -24,7 +24,7 @@ import type { CronServiceState, DeferredCronNotifications } from "./state.js"; import { ensureLoaded, persistOrRestore, snapshotStoreForRollback } from "./store.js"; import { resolveCronJobTimeoutMs } from "./timeout-policy.js"; import { - MAX_TIMER_DELAY_MS, + MAX_CRON_TIMER_DELAY_MS, MIN_REFIRE_GAP_MS, type TimedCronRunOutcome, } from "./timer-execution-timeout.js"; @@ -70,7 +70,7 @@ export function armTimer(state: CronServiceState) { if (enabledCount > 0) { armRunningRecheckTimer(state); state.deps.log.debug( - { jobCount, enabledCount, withNextRun, delayMs: MAX_TIMER_DELAY_MS }, + { jobCount, enabledCount, withNextRun, delayMs: MAX_CRON_TIMER_DELAY_MS }, "cron: timer armed for maintenance recheck", ); return; @@ -94,13 +94,13 @@ export function armTimer(state: CronServiceState) { const flooredDelay = delay === 0 ? MIN_REFIRE_GAP_MS : delay; // Wake at least once a minute to avoid schedule drift and recover quickly // when the process was paused or wall-clock time jumps. - const clampedDelay = Math.min(flooredDelay, MAX_TIMER_DELAY_MS); + const clampedDelay = Math.min(flooredDelay, MAX_CRON_TIMER_DELAY_MS); // Intentionally avoid an `async` timer callback: // Vitest's fake-timer helpers can await async callbacks, which would block // tests that simulate long-running jobs. Runtime behavior is unchanged. setCronTimer(state, clampedDelay); state.deps.log.debug( - { nextAt, delayMs: clampedDelay, clamped: delay > MAX_TIMER_DELAY_MS }, + { nextAt, delayMs: clampedDelay, clamped: delay > MAX_CRON_TIMER_DELAY_MS }, "cron: timer armed", ); } @@ -112,7 +112,7 @@ function armRunningRecheckTimer(state: CronServiceState) { if (state.timer) { clearTimeout(state.timer); } - setCronTimer(state, MAX_TIMER_DELAY_MS); + setCronTimer(state, MAX_CRON_TIMER_DELAY_MS); } function setCronTimer(state: CronServiceState, delayMs: number): void { @@ -155,11 +155,11 @@ async function onAdmittedTimer(state: CronServiceState) { if (state.running) { // Re-arm the timer so the scheduler keeps ticking even when a job is // still executing. Without this, a long-running job (e.g. an agentTurn - // exceeding MAX_TIMER_DELAY_MS) causes the clamped 60 s timer to fire + // exceeding MAX_CRON_TIMER_DELAY_MS) causes the clamped 60 s timer to fire // while `running` is true. The early return then leaves no timer set, // silently killing the scheduler until the next gateway restart. // - // We use MAX_TIMER_DELAY_MS as a fixed re-check interval to avoid a + // We use MAX_CRON_TIMER_DELAY_MS as a fixed re-check interval to avoid a // zero-delay hot-loop when past-due jobs are waiting for the current // execution to finish. // See: https://github.com/openclaw/openclaw/issues/12025 diff --git a/src/daemon/schtasks.startup-fallback.test.ts b/src/daemon/schtasks.startup-fallback.test.ts index 5ffe68a49c2a..d754f29fd979 100644 --- a/src/daemon/schtasks.startup-fallback.test.ts +++ b/src/daemon/schtasks.startup-fallback.test.ts @@ -24,8 +24,8 @@ vi.mock("../infra/windows-encoding.js", async () => { }); import { - inspectPortUsage, - killProcessTree, + inspectPortUsageMock, + killProcessTreeMock, resetSchtasksBaseMocks, schtasksCalls, schtasksResponses, @@ -217,10 +217,10 @@ function expectStartupFallbackSpawn() { function expectGatewayTermination(pid: number) { if (process.platform === "win32") { - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); return; } - expect(killProcessTree).toHaveBeenCalledWith(pid, { graceMs: 300 }); + expect(killProcessTreeMock).toHaveBeenCalledWith(pid, { graceMs: 300 }); } function useListenerBackedFallbackOwnership(): void { @@ -346,7 +346,7 @@ beforeEach(() => { vi.spyOn(process, "platform", "get").mockReturnValue("linux"); findVerifiedGatewayListenerPidsOnPortSync.mockReset(); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([]); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "free", listeners: [], @@ -493,7 +493,7 @@ describe("Windows startup fallback", () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { const startupEntryPath = await writeStartupFallbackEntry(env); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]); - inspectPortUsage + inspectPortUsageMock .mockResolvedValueOnce({ port: 18789, status: "busy", @@ -560,7 +560,7 @@ describe("Windows startup fallback", () => { } return makeSpawnSyncResult(); }); - inspectPortUsage + inspectPortUsageMock .mockResolvedValueOnce({ port: 18789, status: "busy", @@ -609,7 +609,7 @@ describe("Windows startup fallback", () => { ? makeSpawnSyncResult({ status: 1 }) : makeSpawnSyncResult(), ); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "free", listeners: [], @@ -717,7 +717,7 @@ describe("Windows startup fallback", () => { useListenerBackedFallbackOwnership(); await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { const startupEntryPath = await writeStartupFallbackEntry(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [{ pid: 4242, command: "other.exe" }], @@ -727,7 +727,7 @@ describe("Windows startup fallback", () => { "listener is not a verified gateway process", ); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); await expect(fs.access(startupEntryPath)).resolves.toBeUndefined(); }); }); @@ -760,7 +760,7 @@ describe("Windows startup fallback", () => { } return makeSpawnSyncResult(); }); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [ @@ -778,7 +778,7 @@ describe("Windows startup fallback", () => { "gateway listener on port 18789 does not match the persisted command", ); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); await expect(fs.access(startupEntryPath)).resolves.toBeUndefined(); }); }); @@ -790,7 +790,7 @@ describe("Windows startup fallback", () => { await writeGatewayScript(env); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]); let portInspections = 0; - inspectPortUsage.mockImplementation(async (port) => { + inspectPortUsageMock.mockImplementation(async (port) => { schtasksResponses.length = 0; schtasksResponses.push({ code: 1, stdout: "", stderr: "restart denied" }); return portInspections++ === 0 @@ -825,7 +825,7 @@ describe("Windows startup fallback", () => { const startupEntryPath = await writeStartupFallbackEntry(env); await writeGatewayScript(env, 18789); env.OPENCLAW_GATEWAY_PORT = "19433"; - inspectPortUsage.mockImplementation(async (port) => ({ + inspectPortUsageMock.mockImplementation(async (port) => ({ port, status: port === 18789 ? "busy" : "free", listeners: @@ -852,7 +852,7 @@ describe("Windows startup fallback", () => { await installGatewayScheduledTask(env, new PassThrough(), "19433"); - expect(inspectPortUsage).toHaveBeenCalledWith(18789, { + expect(inspectPortUsageMock).toHaveBeenCalledWith(18789, { probeHosts: ["127.0.0.1"], }); expectGatewayTermination(4242); @@ -894,7 +894,7 @@ describe("Windows startup fallback", () => { } return makeSpawnSyncResult(); }); - inspectPortUsage.mockImplementation(async (port) => ({ + inspectPortUsageMock.mockImplementation(async (port) => ({ port, status: "free", listeners: [], @@ -949,7 +949,7 @@ describe("Windows startup fallback", () => { } return makeSpawnSyncResult(); }); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 19433, status: "busy", listeners: [ @@ -1016,7 +1016,7 @@ describe("Windows startup fallback", () => { }) : makeSpawnSyncResult(), ); - inspectPortUsage.mockImplementation(async (port) => + inspectPortUsageMock.mockImplementation(async (port) => port === 19433 ? { port, @@ -1069,7 +1069,7 @@ describe("Windows startup fallback", () => { }) : makeSpawnSyncResult(), ); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 19433, status: "unknown", listeners: [], @@ -1119,7 +1119,7 @@ describe("Windows startup fallback", () => { } return makeSpawnSyncResult(); }); - inspectPortUsage + inspectPortUsageMock .mockResolvedValueOnce({ port: 18789, status: "busy", @@ -1209,7 +1209,7 @@ describe("Windows startup fallback", () => { await writeGatewayScript(env, 18789); env.OPENCLAW_GATEWAY_PORT = "19433"; let oldPortProbes = 0; - inspectPortUsage.mockImplementation(async (port) => { + inspectPortUsageMock.mockImplementation(async (port) => { if (port !== 18789) { return { port, status: "free", listeners: [], hints: [] }; } @@ -1254,7 +1254,7 @@ describe("Windows startup fallback", () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { const startupEntryPath = await writeStartupFallbackEntry(env); await writeGatewayScript(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "free", listeners: [], @@ -1304,7 +1304,7 @@ describe("Windows startup fallback", () => { const startupEntryPath = await writeStartupFallbackEntry(hiddenEnv); await writeGatewayScript(hiddenEnv); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]); - inspectPortUsage.mockImplementation(async () => + inspectPortUsageMock.mockImplementation(async () => schtasksCalls.some((call) => call[0] === "/Run") ? { port: 18789, @@ -1471,7 +1471,7 @@ describe("Windows startup fallback", () => { fastForwardTaskStartWait(); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]); let portInspections = 0; - inspectPortUsage.mockImplementation(async (port) => + inspectPortUsageMock.mockImplementation(async (port) => portInspections++ === 0 ? { port, status: "free", listeners: [], hints: [] } : { @@ -1627,7 +1627,7 @@ describe("Windows startup fallback", () => { await writeGatewayScript(env); vi.spyOn(process, "platform", "get").mockReturnValue("win32"); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [ @@ -1700,7 +1700,7 @@ describe("Windows startup fallback", () => { expect(runtime.pid).toBe(4242); expect(runtime.detail).toContain("Gateway process detected"); expect(findVerifiedGatewayListenerPidsOnPortSync).not.toHaveBeenCalled(); - expect(inspectPortUsage).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); }); }); @@ -1776,14 +1776,14 @@ describe("Windows startup fallback", () => { expect(runtime.status).toBe("running"); expect(runtime.pid).toBe(5151); expect(findVerifiedGatewayListenerPidsOnPortSync).not.toHaveBeenCalled(); - expect(inspectPortUsage).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); }); }); it("does not trust an unverified busy port when schtasks still says not-yet-run", async () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { await writeGatewayScript(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [{ pid: 4242, command: "node.exe" }], @@ -1864,7 +1864,7 @@ describe("Windows startup fallback", () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { addStartupFallbackMissingResponses(); await writeStartupFallbackEntry(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [ @@ -1888,7 +1888,7 @@ describe("Windows startup fallback", () => { const nodeEnv = makeNodeServiceEnv(env); addStartupFallbackMissingResponses(); await writeStartupFallbackEntry(nodeEnv); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [{ pid: 4242, command: "node.exe" }], @@ -1898,7 +1898,7 @@ describe("Windows startup fallback", () => { const runtime = await readScheduledTaskRuntime(nodeEnv); expect(runtime.status).not.toBe("running"); expect(runtime.pid).toBeUndefined(); - expect(inspectPortUsage).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); }); }); @@ -1908,7 +1908,7 @@ describe("Windows startup fallback", () => { vi.spyOn(process, "platform", "get").mockReturnValue("win32"); addStartupFallbackMissingResponses(); await writeStartupFallbackEntry(nodeEnv); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [ @@ -1923,8 +1923,8 @@ describe("Windows startup fallback", () => { await stopScheduledTask({ env: nodeEnv, stdout: new PassThrough() }); - expect(inspectPortUsage).not.toHaveBeenCalled(); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); }); }); @@ -1933,7 +1933,7 @@ describe("Windows startup fallback", () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { addStartupFallbackMissingResponses(); await writeStartupFallbackEntry(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [{ pid: 5151, command: "other.exe" }], @@ -1943,7 +1943,7 @@ describe("Windows startup fallback", () => { await expect(stopScheduledTask({ env, stdout: new PassThrough() })).rejects.toThrow( "not a verified gateway process", ); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); }); }); @@ -1957,7 +1957,7 @@ describe("Windows startup fallback", () => { await stopScheduledTask({ env: nodeEnv, stdout: new PassThrough() }); - expect(inspectPortUsage).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); expectTaskkillPid(5151); }); }); @@ -1976,7 +1976,7 @@ describe("Windows startup fallback", () => { await stopScheduledTask({ env: nodeEnv, stdout: new PassThrough() }); - expect(inspectPortUsage).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); expectTaskkillPid(5151); }); }); @@ -1994,7 +1994,7 @@ describe("Windows startup fallback", () => { await stopScheduledTask({ env: nodeEnv, stdout: new PassThrough() }); - expect(inspectPortUsage).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); expectTaskkillPid(5151); }); }); @@ -2008,7 +2008,7 @@ describe("Windows startup fallback", () => { ]); await writeGatewayScript(env); await writeStartupFallbackEntry(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [ @@ -2039,7 +2039,7 @@ describe("Windows startup fallback", () => { ]); await writeGatewayScript(env); await writeStartupFallbackEntry(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [ @@ -2071,7 +2071,7 @@ describe("Windows startup fallback", () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { addStartupFallbackMissingResponses(); await writeStartupFallbackEntry(env); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "busy", listeners: [{ pid: 5151, command: "other.exe" }], @@ -2081,7 +2081,7 @@ describe("Windows startup fallback", () => { await expect(restartScheduledTask({ env, stdout: new PassThrough() })).rejects.toThrow( "not a verified gateway process", ); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); expect(spawn).not.toHaveBeenCalled(); }); }); @@ -2092,7 +2092,7 @@ describe("Windows startup fallback", () => { sleepMock.mockImplementationOnce(async () => { timeState.now += 15_000; }); - inspectPortUsage.mockResolvedValue({ + inspectPortUsageMock.mockResolvedValue({ port: 18789, status: "free", listeners: [], @@ -2124,7 +2124,7 @@ describe("Windows startup fallback", () => { await writeGatewayScript(env); await writeStartupFallbackEntry(env); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([5151]); - inspectPortUsage + inspectPortUsageMock .mockResolvedValueOnce({ port: 18789, status: "busy", diff --git a/src/daemon/schtasks.stop.test.ts b/src/daemon/schtasks.stop.test.ts index 1ce4bb2be1a9..4a5410dea293 100644 --- a/src/daemon/schtasks.stop.test.ts +++ b/src/daemon/schtasks.stop.test.ts @@ -6,8 +6,8 @@ import { expectDefined } from "@openclaw/normalization-core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import "./test-helpers/schtasks-base-mocks.js"; import { - inspectPortUsage, - killProcessTree, + inspectPortUsageMock, + killProcessTreeMock, resetSchtasksBaseMocks, schtasksCalls, schtasksResponses, @@ -112,10 +112,10 @@ function busyPortUsage( function expectGatewayTermination(pid: number) { if (process.platform === "win32") { - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); return; } - expect(killProcessTree).toHaveBeenCalledWith(pid, { graceMs: 300 }); + expect(killProcessTreeMock).toHaveBeenCalledWith(pid, { graceMs: 300 }); } function setTaskStateProbeResult(state: number) { @@ -159,7 +159,7 @@ beforeEach(() => { status: 1, signal: null, }); - inspectPortUsage.mockResolvedValue(freePortUsage()); + inspectPortUsageMock.mockResolvedValue(freePortUsage()); }); afterEach(() => { @@ -459,7 +459,7 @@ describe("Scheduled Task stop/restart cleanup", () => { const onMutation = vi.fn(); pushSuccessfulSchtasksResponses(3); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]); - inspectPortUsage + inspectPortUsageMock .mockResolvedValueOnce(busyPortUsage(4242, { commandLine: INSTALLED_GATEWAY_COMMAND_LINE })) .mockResolvedValueOnce(freePortUsage()); @@ -467,8 +467,8 @@ describe("Scheduled Task stop/restart cleanup", () => { expect(findVerifiedGatewayListenerPidsOnPortSync).not.toHaveBeenCalled(); expectGatewayTermination(4242); - expect(inspectPortUsage).toHaveBeenCalledTimes(2); - expect(inspectPortUsage).toHaveBeenCalledWith(GATEWAY_PORT, { + expect(inspectPortUsageMock).toHaveBeenCalledTimes(2); + expect(inspectPortUsageMock).toHaveBeenCalledWith(GATEWAY_PORT, { probeHosts: ["127.0.0.1"], }); expect(onMutation).toHaveBeenCalledWith({ mode: "schtasks-stop" }); @@ -487,7 +487,7 @@ describe("Scheduled Task stop/restart cleanup", () => { await expect(resolveScheduledTaskOwnedGatewayPids(env)).resolves.toEqual([]); - expect(inspectPortUsage).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); }); }); @@ -495,7 +495,7 @@ describe("Scheduled Task stop/restart cleanup", () => { await withPreparedGatewayTask(async ({ env, stdout }) => { vi.spyOn(process, "platform", "get").mockReturnValue("win32"); pushSuccessfulSchtasksResponses(3); - inspectPortUsage.mockResolvedValue(freePortUsage()); + inspectPortUsageMock.mockResolvedValue(freePortUsage()); let forced = false; spawnSync.mockImplementation((command, args) => { const executable = command.toLowerCase(); @@ -558,7 +558,7 @@ describe("Scheduled Task stop/restart cleanup", () => { ["/F", "/T", "/PID", "4242"], ]); expect(taskkillCalls.flat()).not.toContain("3131"); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); }); }); @@ -614,11 +614,11 @@ describe("Scheduled Task stop/restart cleanup", () => { it("does not kill an unrelated listener when the owned process leaves another required host busy", async () => { await withPreparedGatewayTask(async ({ env, stdout }) => { pushSuccessfulSchtasksResponses(3); - inspectPortUsage.mockResolvedValueOnce( + inspectPortUsageMock.mockResolvedValueOnce( busyPortUsage(4242, { commandLine: INSTALLED_GATEWAY_COMMAND_LINE }), ); for (let i = 0; i < 20; i += 1) { - inspectPortUsage.mockResolvedValueOnce(busyPortUsage(5252)); + inspectPortUsageMock.mockResolvedValueOnce(busyPortUsage(5252)); } await expect(stopScheduledTask({ env, stdout })).rejects.toThrow( @@ -626,12 +626,12 @@ describe("Scheduled Task stop/restart cleanup", () => { ); if (process.platform !== "win32") { - expect(killProcessTree).toHaveBeenCalledOnce(); - expect(killProcessTree).toHaveBeenCalledWith(4242, { graceMs: 300 }); + expect(killProcessTreeMock).toHaveBeenCalledOnce(); + expect(killProcessTreeMock).toHaveBeenCalledWith(4242, { graceMs: 300 }); } else { - expect(killProcessTree).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); } - expect(killProcessTree).not.toHaveBeenCalledWith(5252, { graceMs: 300 }); + expect(killProcessTreeMock).not.toHaveBeenCalledWith(5252, { graceMs: 300 }); }); }); @@ -639,7 +639,7 @@ describe("Scheduled Task stop/restart cleanup", () => { await withPreparedGatewayTask(async ({ env, stdout }) => { pushSuccessfulSchtasksResponses(3); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([]); - inspectPortUsage + inspectPortUsageMock .mockResolvedValueOnce( busyPortUsage(6262, { commandLine: @@ -651,7 +651,7 @@ describe("Scheduled Task stop/restart cleanup", () => { await stopScheduledTask({ env, stdout }); expectGatewayTermination(6262); - expect(inspectPortUsage).toHaveBeenCalledTimes(2); + expect(inspectPortUsageMock).toHaveBeenCalledTimes(2); }); }); @@ -661,13 +661,13 @@ describe("Scheduled Task stop/restart cleanup", () => { env.OPENCLAW_SERVICE_KIND = "node"; env.OPENCLAW_WINDOWS_TASK_NAME = "OpenClaw Node"; findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([4242]); - inspectPortUsage.mockResolvedValue(busyPortUsage(4242)); + inspectPortUsageMock.mockResolvedValue(busyPortUsage(4242)); await stopScheduledTask({ env, stdout }); expect(findVerifiedGatewayListenerPidsOnPortSync).not.toHaveBeenCalled(); - expect(inspectPortUsage).not.toHaveBeenCalled(); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); expect(schtasksCalls).toEqual([ ["/Query"], ["/Query", "/TN", "OpenClaw Node"], @@ -681,7 +681,7 @@ describe("Scheduled Task stop/restart cleanup", () => { const onMutation = vi.fn(); pushSuccessfulSchtasksResponses(4); findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([5151]); - inspectPortUsage + inspectPortUsageMock .mockResolvedValueOnce(busyPortUsage(5151, { commandLine: INSTALLED_GATEWAY_COMMAND_LINE })) .mockResolvedValueOnce(freePortUsage()); @@ -691,8 +691,8 @@ describe("Scheduled Task stop/restart cleanup", () => { expect(findVerifiedGatewayListenerPidsOnPortSync).not.toHaveBeenCalled(); expectGatewayTermination(5151); - expect(inspectPortUsage.mock.calls.length).toBeGreaterThanOrEqual(2); - expect(inspectPortUsage).toHaveBeenCalledWith(GATEWAY_PORT, { + expect(inspectPortUsageMock.mock.calls.length).toBeGreaterThanOrEqual(2); + expect(inspectPortUsageMock).toHaveBeenCalledWith(GATEWAY_PORT, { probeHosts: ["127.0.0.1"], }); expect(onMutation).toHaveBeenCalledWith({ mode: "schtasks-restart" }); @@ -715,15 +715,15 @@ describe("Scheduled Task stop/restart cleanup", () => { env.OPENCLAW_SERVICE_KIND = "node"; env.OPENCLAW_WINDOWS_TASK_NAME = "OpenClaw Node"; findVerifiedGatewayListenerPidsOnPortSync.mockReturnValue([5151]); - inspectPortUsage.mockResolvedValue(busyPortUsage(5151)); + inspectPortUsageMock.mockResolvedValue(busyPortUsage(5151)); await expect(restartScheduledTask({ env, stdout })).resolves.toEqual({ outcome: "completed", }); expect(findVerifiedGatewayListenerPidsOnPortSync).not.toHaveBeenCalled(); - expect(inspectPortUsage).not.toHaveBeenCalled(); - expect(killProcessTree).not.toHaveBeenCalled(); + expect(inspectPortUsageMock).not.toHaveBeenCalled(); + expect(killProcessTreeMock).not.toHaveBeenCalled(); expect(schtasksCalls).toEqual([ ["/Query"], ["/Query", "/TN", "OpenClaw Node"], diff --git a/src/daemon/test-helpers/schtasks-base-mocks.ts b/src/daemon/test-helpers/schtasks-base-mocks.ts index bc5659d478b2..8e8215485e09 100644 --- a/src/daemon/test-helpers/schtasks-base-mocks.ts +++ b/src/daemon/test-helpers/schtasks-base-mocks.ts @@ -1,8 +1,8 @@ /** Base Vitest mocks for Windows schtasks daemon tests. */ import { vi } from "vitest"; import { - inspectPortUsage, - killProcessTree, + inspectPortUsageMock, + killProcessTreeMock, resolveGatewayServiceProbeHosts, schtasksCalls, schtasksResponses, @@ -18,7 +18,7 @@ vi.mock("../schtasks-exec.js", () => ({ vi.mock("../../infra/ports-inspect.js", () => ({ inspectPortUsage: (port: number, options?: { probeHosts?: readonly string[] }) => - inspectPortUsage(port, options), + inspectPortUsageMock(port, options), })); vi.mock("../gateway-service-probe-hosts.js", () => ({ @@ -26,7 +26,7 @@ vi.mock("../gateway-service-probe-hosts.js", () => ({ })); vi.mock("../../process/kill-tree.js", () => ({ - killProcessTree: (pid: number, opts?: { graceMs?: number }) => killProcessTree(pid, opts), + killProcessTree: (pid: number, opts?: { graceMs?: number }) => killProcessTreeMock(pid, opts), })); // Launcher encode/decode must not depend on the dev or CI machine's code page; diff --git a/src/daemon/test-helpers/schtasks-fixtures.ts b/src/daemon/test-helpers/schtasks-fixtures.ts index 5b21650db6b8..1e31a2c1debe 100644 --- a/src/daemon/test-helpers/schtasks-fixtures.ts +++ b/src/daemon/test-helpers/schtasks-fixtures.ts @@ -11,11 +11,11 @@ import { resolveTaskScriptPath } from "../schtasks.js"; export const schtasksResponses: Array<{ code: number; stdout: string; stderr: string }> = []; export const schtasksCalls: string[][] = []; -export const inspectPortUsage: MockFn< +export const inspectPortUsageMock: MockFn< (port: number, options?: { probeHosts?: readonly string[] }) => Promise > = vi.fn(); export const resolveGatewayServiceProbeHosts: MockFn<() => Promise> = vi.fn(); -export const killProcessTree: MockFn = vi.fn(); +export const killProcessTreeMock: MockFn = vi.fn(); /** Runs a test with Windows-like daemon environment paths and cleans the temp dir. */ export async function withWindowsEnv( @@ -39,10 +39,10 @@ export async function withWindowsEnv( export function resetSchtasksBaseMocks() { schtasksResponses.length = 0; schtasksCalls.length = 0; - inspectPortUsage.mockReset(); + inspectPortUsageMock.mockReset(); resolveGatewayServiceProbeHosts.mockReset(); resolveGatewayServiceProbeHosts.mockResolvedValue(["127.0.0.1"]); - killProcessTree.mockReset(); + killProcessTreeMock.mockReset(); } export async function writeGatewayScript( diff --git a/src/entry.ts b/src/entry.ts index 30f938c68013..b29749754b0b 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -16,7 +16,7 @@ import type { RootHelpRenderOptions } from "./cli/program/root-help.js"; import { isNativeHookRelayArgv } from "./cli/respawn-policy.js"; import { configureGatewayStartupTraceConsoleFormatting, - createGatewayStartupTrace, + createGatewayDispatchStartupTrace, } from "./cli/startup-trace.js"; import { normalizeWindowsArgv } from "./cli/windows-argv.js"; import { @@ -95,7 +95,7 @@ function shouldForceReadOnlyAuthStore(argv: string[]): boolean { return false; } -const gatewayEntryStartupTrace = createGatewayStartupTrace(process.argv, "entry"); +const gatewayEntryStartupTrace = createGatewayDispatchStartupTrace(process.argv, "entry"); // Guard: only run entry-point logic when this file is the main module. // The bundler may import entry.js as a shared dependency when dist/index.js diff --git a/src/flows/doctor-health.ts b/src/flows/doctor-health.ts index a4d92045f5a2..f2b2efc06ef8 100644 --- a/src/flows/doctor-health.ts +++ b/src/flows/doctor-health.ts @@ -43,7 +43,7 @@ function stateDirectoryExistsAtDoctorStart(): boolean { } /** Runs the full interactive doctor flow against the provided or default runtime. */ -export async function doctorCommand(runtime?: RuntimeEnv, options: DoctorOptions = {}) { +export async function runDoctorHealthFlow(runtime?: RuntimeEnv, options: DoctorOptions = {}) { const effectiveRuntime = runtime ?? (await import("../runtime.js")).defaultRuntime; // Config loading can initialize SQLite-backed state before integrity runs. // Preserve the entry fact so doctor can report that automatic initialization. diff --git a/src/gateway/agent-command.test-helpers.ts b/src/gateway/agent-command.test-helpers.ts index 08275d508713..9f58b2ec6502 100644 --- a/src/gateway/agent-command.test-helpers.ts +++ b/src/gateway/agent-command.test-helpers.ts @@ -2,12 +2,12 @@ // Waits for mocked agent command dispatches in async gateway tests. import { vi } from "vitest"; import { sleep } from "../utils/sleep.js"; -import { agentCommand } from "./test-helpers.runtime-state.js"; +import { agentCommandMock } from "./test-helpers.runtime-state.js"; type AgentCommandCall = Record; function agentCommandCalls(): Array<[AgentCommandCall]> { - return vi.mocked(agentCommand).mock.calls as unknown as Array<[AgentCommandCall]>; + return vi.mocked(agentCommandMock).mock.calls as unknown as Array<[AgentCommandCall]>; } /** Waits until the mocked `agentCommand` receives a call for a specific run id. */ diff --git a/src/gateway/embeddings-http.test.ts b/src/gateway/embeddings-http.test.ts index 1f8c2c75b850..afab9a3d749b 100644 --- a/src/gateway/embeddings-http.test.ts +++ b/src/gateway/embeddings-http.test.ts @@ -21,7 +21,7 @@ import { createPluginRegistry } from "../plugins/registry.js"; import type { PluginRuntime } from "../plugins/runtime/types.js"; import { startOpenAiCompatGatewayServer } from "./openai-compatible-http.test-helpers.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, resetTestPluginRegistry, setTestPluginRegistry, @@ -172,7 +172,7 @@ beforeAll(async () => { }, }; ({ startGatewayServer } = await import("./server.js")); - enabledPort = await getFreePort(); + enabledPort = await getGatewayTestPort(); enabledServer = await startOpenAiCompatGatewayServer({ startGatewayServer, port: enabledPort, @@ -598,7 +598,7 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => { }); it("rejects x-openclaw-model for trusted write-only callers", async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startOpenAiCompatGatewayServer({ startGatewayServer, port, diff --git a/src/gateway/handshake-timeouts.test.ts b/src/gateway/handshake-timeouts.test.ts index 48185b0e76da..7be486cd89b9 100644 --- a/src/gateway/handshake-timeouts.test.ts +++ b/src/gateway/handshake-timeouts.test.ts @@ -9,7 +9,7 @@ import { MIN_CONNECT_CHALLENGE_TIMEOUT_MS, resolveConnectChallengeTimeoutMs, } from "../../packages/gateway-client/src/timeouts.js"; -import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../utils/timer-delay.js"; +import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../../packages/gateway-client/src/timeouts.js"; import { resolvePreauthHandshakeTimeoutMs } from "./handshake-timeouts.js"; describe("gateway handshake timeouts", () => { diff --git a/src/gateway/models-http.test.ts b/src/gateway/models-http.test.ts index 08df7430488e..103224be7fe7 100644 --- a/src/gateway/models-http.test.ts +++ b/src/gateway/models-http.test.ts @@ -2,7 +2,7 @@ // authorization, ordering, and disabled-surface responses. import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { startOpenAiCompatGatewayServer } from "./openai-compatible-http.test-helpers.js"; -import { getFreePort, installGatewayTestHooks } from "./test-helpers.js"; +import { getGatewayTestPort, installGatewayTestHooks } from "./test-helpers.js"; installGatewayTestHooks({ scope: "suite" }); @@ -14,7 +14,7 @@ let enabledPort: number; beforeAll(async () => { ({ startGatewayServer } = await import("./server.js")); - enabledPort = await getFreePort(); + enabledPort = await getGatewayTestPort(); enabledServer = await startOpenAiCompatGatewayServer({ startGatewayServer, port: enabledPort, @@ -114,7 +114,7 @@ describe("OpenAI-compatible models HTTP API (e2e)", () => { }); it("rejects when disabled", async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startOpenAiCompatGatewayServer({ startGatewayServer, port, @@ -132,7 +132,7 @@ describe("OpenAI-compatible models HTTP API (e2e)", () => { }); it("treats shared-secret bearer auth as full compat operator access", async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startOpenAiCompatGatewayServer({ startGatewayServer, port, diff --git a/src/gateway/openai-http.test.ts b/src/gateway/openai-http.test.ts index 53341ceedc7e..af5fd48caed3 100644 --- a/src/gateway/openai-http.test.ts +++ b/src/gateway/openai-http.test.ts @@ -26,8 +26,8 @@ import { import { withEnvAsync } from "../test-utils/env.js"; import { buildAssistantDeltaResult } from "./test-helpers.agent-results.js"; import { - agentCommand, - getFreePort, + agentCommandMock, + getGatewayTestPort, installGatewayTestHooks, startGatewayServerWithRetries, testState, @@ -43,7 +43,7 @@ let enabledPort: number; beforeAll(async () => { ({ startGatewayServer } = await import("./server.js")); const started = await startGatewayServerWithRetries({ - port: await getFreePort(), + port: await getGatewayTestPort(), opts: { host: "127.0.0.1", auth: { mode: "none" }, @@ -164,15 +164,15 @@ type FirstAgentCommandOptions = { }; function firstAgentCommandOptions() { - return agentCommand.mock.calls.at(0)?.[0] as FirstAgentCommandOptions | undefined; + return agentCommandMock.mock.calls.at(0)?.[0] as FirstAgentCommandOptions | undefined; } describe("OpenAI-compatible HTTP API (e2e)", () => { it("handles request validation and routing", async () => { const port = enabledPort; const mockAgentOnce = (payloads: Array<{ text: string }>) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads } as never); }; const expectAgentSessionKeyMatch = async (request: { body: unknown; @@ -182,7 +182,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { mockAgentOnce([{ text: "hello" }]); const res = await postChatCompletions(port, request.body, request.headers); expect(res.status).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(firstAgentCommandOptions()?.sessionKey ?? "").toMatch(request.matcher); await res.text(); }; @@ -202,7 +202,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const getFirstAgentCall = () => firstAgentCommandOptions(); const getFirstAgentMessage = () => getFirstAgentCall()?.message ?? ""; const expectInvalidRequestNoDispatch = async (messages: unknown[]) => { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", messages, @@ -212,7 +212,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const error = json.error as Record | undefined; expect(error?.type).toBe("invalid_request_error"); expect(error?.message).toBe("Invalid image_url content in `messages`."); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); }; const postSyncUserMessage = async (message: string) => { const res = await postChatCompletions(port, { @@ -247,7 +247,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { body: JSON.stringify({ messages: [{ role: "user", content: "hi" }] }), }); expect(res.status).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(getFirstAgentCall()?.messageChannel).toBe("webchat"); await res.text(); } @@ -275,7 +275,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { }); { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions( port, { model: "openclaw", messages: [{ role: "user", content: "hi" }] }, @@ -285,11 +285,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toBe("Unknown agent 'missing-agent'."); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw/missing-agent", messages: [{ role: "user", content: "hi" }], @@ -298,11 +298,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toBe("Unknown agent 'missing-agent'."); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions( port, { model: "openclaw", messages: [{ role: "user", content: "hi" }] }, @@ -316,7 +316,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(json.error?.message).toBe( "`x-openclaw-session-key` cannot use reserved internal session namespaces.", ); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { @@ -336,7 +336,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions( port, { model: "openclaw", messages: [{ role: "user", content: "hi" }] }, @@ -348,7 +348,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(json.error?.message).toBe( "`x-openclaw-session-key` cannot use reserved internal session namespaces.", ); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { @@ -427,7 +427,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openai/", messages: [{ role: "user", content: "hi" }], @@ -438,11 +438,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(json.error?.message).toBe( "Invalid `model`. Use `openclaw` or `openclaw/`.", ); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions( port, { @@ -455,11 +455,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { message?: string; type?: string } }; expect(json.error?.type).toBe("forbidden"); expect(json.error?.message).toBe("missing scope: operator.admin"); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions( port, { @@ -475,7 +475,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toBe("Invalid `x-openclaw-model`."); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { @@ -908,8 +908,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "tool choice function" }], meta: { stopReason: "tool_calls", @@ -960,8 +960,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "tool choice required" }], meta: { stopReason: "tool_calls", @@ -1029,8 +1029,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Calling a different tool." }], meta: { stopReason: "tool_calls", @@ -1067,7 +1067,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", tool_choice: "required", @@ -1077,11 +1077,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message ?? "").toContain("no tools were provided"); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", tool_choice: { type: "function", function: { name: "missing_tool" } }, @@ -1101,11 +1101,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message ?? "").toContain("unknown tool"); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", tool_choice: { @@ -1128,11 +1128,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message ?? "").toContain("allowed_tools"); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", tools: [ @@ -1148,7 +1148,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message ?? "").toContain("tool.function is required"); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { @@ -1214,8 +1214,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockRejectedValueOnce(createClientToolNameConflictError(["exec"])); + agentCommandMock.mockClear(); + agentCommandMock.mockRejectedValueOnce(createClientToolNameConflictError(["exec"])); const res = await postChatCompletions(port, { stream: false, model: "openclaw", @@ -1238,8 +1238,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Let me check that." }], meta: { stopReason: "tool_calls", @@ -1326,8 +1326,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [], meta: { stopReason: "tool_calls", @@ -1381,8 +1381,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; const { session, emit } = createStubSessionHarness(); subscribeEmbeddedAgentSession({ session, runId }); @@ -1415,8 +1415,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "usage basic" }], meta: { agentMeta: { @@ -1436,8 +1436,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "usage cache" }], meta: { agentMeta: { @@ -1460,8 +1460,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "usage total" }], meta: { agentMeta: { @@ -1482,8 +1482,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "usage total only" }], meta: { agentMeta: { @@ -1502,8 +1502,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "usage non-finite" }], meta: { agentMeta: { @@ -1527,8 +1527,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "usage non-finite aggregate fallback" }], meta: { agentMeta: { @@ -1549,8 +1549,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "usage cache-write only" }], meta: { agentMeta: { @@ -1570,8 +1570,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "" }] } as never); const json = await postSyncUserMessage("hi"); const choice0 = (json.choices as Array>)[0] ?? {}; const msg = (choice0.message as Record | undefined) ?? {}; @@ -1598,8 +1598,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("validates and forwards max_completion_tokens and max_tokens into streamParams", async () => { const port = enabledPort; const mockAgentOnce = () => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); }; const getRecordedAgentMaxTokens = () => firstAgentCommandOptions()?.streamParams?.maxTokens; @@ -1641,7 +1641,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { messages: [{ role: "user", content: "hi" }], }); expect(res.status).toBe(200); - expect(agentCommand, testCase.name).toHaveBeenCalledTimes(1); + expect(agentCommandMock, testCase.name).toHaveBeenCalledTimes(1); expect(getRecordedAgentMaxTokens(), testCase.name).toBe(testCase.expected); await res.text(); } @@ -1654,7 +1654,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { max_tokens: null, messages: [{ role: "user", content: "hi" }], }); - expect(agentCommand, "SDK null fields").toHaveBeenCalledTimes(1); + expect(agentCommandMock, "SDK null fields").toHaveBeenCalledTimes(1); expect(getRecordedAgentMaxTokens(), "SDK null fields").toBeUndefined(); }); @@ -1672,7 +1672,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { for (const field of ["max_completion_tokens", "max_tokens"] as const) { for (const testCase of invalidValues) { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", [field]: testCase.value, @@ -1682,10 +1682,10 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toContain(field); - expect(agentCommand, `${field}: ${testCase.name}`).toHaveBeenCalledTimes(0); + expect(agentCommandMock, `${field}: ${testCase.name}`).toHaveBeenCalledTimes(0); } - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postRawChatCompletions( port, `{"model":"openclaw","messages":[{"role":"user","content":"hi"}],"${field}":1e309}`, @@ -1694,7 +1694,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toContain(field); - expect(agentCommand, `${field}: non-finite raw number`).toHaveBeenCalledTimes(0); + expect(agentCommandMock, `${field}: non-finite raw number`).toHaveBeenCalledTimes(0); } const shadowedCases = [ @@ -1710,7 +1710,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { }, ]; for (const testCase of shadowedCases) { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", ...testCase.body, @@ -1720,15 +1720,15 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toContain(testCase.field); - expect(agentCommand, testCase.name).toHaveBeenCalledTimes(0); + expect(agentCommandMock, testCase.name).toHaveBeenCalledTimes(0); } }); it("forwards inbound temperature and top_p into streamParams", async () => { const port = enabledPort; const mockAgentOnce = (payloads: Array<{ text: string }>) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads } as never); }; const getStreamParams = () => firstAgentCommandOptions()?.streamParams; @@ -1771,7 +1771,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", temperature: 999, @@ -1781,11 +1781,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toMatch(/temperature/); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", top_p: 5, @@ -1795,15 +1795,15 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toMatch(/top_p/); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } }); it("forwards inbound penalty and seed params into streamParams", async () => { const port = enabledPort; const mockAgentOnce = (payloads: Array<{ text: string }>) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads } as never); }; const getStreamParams = () => firstAgentCommandOptions()?.streamParams; @@ -1826,7 +1826,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } for (const body of [{ frequency_penalty: 3 }, { presence_penalty: -3 }, { seed: 1.5 }]) { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", ...body, @@ -1835,15 +1835,15 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(res.status).toBe(400); const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } }); it("forwards inbound stop into streamParams", async () => { const port = enabledPort; const mockAgentOnce = (payloads: Array<{ text: string }>) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads } as never); }; const getStreamParams = () => firstAgentCommandOptions()?.streamParams; @@ -1883,7 +1883,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } for (const stop of [["a", "b", "c", "d", "e"], [""], [123], {}]) { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", stop, @@ -1893,15 +1893,15 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toMatch(/stop/); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } }); it("maps provider format failures to OpenAI-compatible 400 errors", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockRejectedValueOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockRejectedValueOnce( new FailoverError( "LLM request failed: provider rejected the request schema or tool payload.", { @@ -1925,14 +1925,14 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.code).toBe("decimal_above_max_value"); expect(json.error?.message).toContain("Invalid 'temperature'"); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); it("forwards response_format into streamParams", async () => { const port = enabledPort; const mockAgentOnce = (payloads: Array<{ text: string }>) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads } as never); }; const getStreamParams = () => firstAgentCommandOptions()?.streamParams; @@ -1989,7 +1989,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postChatCompletions(port, { model: "openclaw", response_format: { type: "xml" }, @@ -1999,7 +1999,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toMatch(/response_format/); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } }); @@ -2057,8 +2057,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expected: "", }, ])("preserves $name through the official OpenAI SDK SSE stream", async ({ deltas, expected }) => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; const { session, emit } = createStubSessionHarness(); subscribeEmbeddedAgentSession({ session, runId }); @@ -2087,13 +2087,13 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(content.join("")).toBe(expected); expect(finishReasons.at(-1)).toBe("stop"); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); it("preserves buffered leading content in cumulative assistant snapshots", async () => { const expected = "milk tea"; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, @@ -2127,8 +2127,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { }); it("flushes same-turn assistant microtasks before the SSE done frame", async () => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce(((opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce(((opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, stream: "assistant", data: { delta: "start " } }); const result = Promise.resolve({ payloads: [{ text: "start finish" }] }); @@ -2174,8 +2174,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { async ({ reject }) => { const idleRootCount = getActiveGatewayRootWorkCount(); const wireResponse = createDeferred(); - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming chat-completion run ID"); @@ -2243,7 +2243,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { { error: { message: "All model fallback candidates failed", type: "api_error" } }, ]); expect(data.at(-1)).toBe("[DONE]"); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); await vi.waitFor(() => expect(getActiveGatewayRootWorkCount()).toBe(idleRootCount)); }, ); @@ -2253,8 +2253,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { { name: "shortened", replacementText: "dra" }, { name: "cleared", replacementText: "" }, ])("fails an official SDK stream when streamed text is $name", async ({ replacementText }) => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming chat-completion run ID"); @@ -2292,7 +2292,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { type: "api_error", }); expect(deliveredContent).toEqual(["draft answer"]); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); it.each([ @@ -2314,8 +2314,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { ])( "keeps official SDK text consistent for $name", async ({ previousDelta, replacementDelta }) => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming chat-completion run ID"); @@ -2358,7 +2358,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } expect(deliveredContent.join("")).toBe("final answer"); expect(finishReasons.at(-1)).toBe("stop"); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }, ); @@ -2419,8 +2419,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { terminalAdmission.resolve({ active, drained }); }); }); - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { activeRunId = (opts as { runId?: string }).runId; if (!activeRunId) { throw new Error("expected a streaming chat-completion run ID"); @@ -2461,7 +2461,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { messages: [{ role: "user", content: "Return a complete streamed response." }], stream: true, }); - await vi.waitFor(() => expect(agentCommand).toHaveBeenCalledTimes(1)); + await vi.waitFor(() => expect(agentCommandMock).toHaveBeenCalledTimes(1)); await new Promise((resolve) => { setImmediate(resolve); }); @@ -2518,8 +2518,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const port = enabledPort; try { { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => buildAssistantDeltaResult({ opts, emit: emitAgentEvent, @@ -2566,8 +2566,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => buildAssistantDeltaResult({ opts, emit: emitAgentEvent, @@ -2595,8 +2595,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }], } as never); @@ -2612,8 +2612,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => buildAssistantDeltaResult({ opts, emit: emitAgentEvent, @@ -2645,8 +2645,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Let me check that." }], meta: { stopReason: "tool_calls", @@ -2712,8 +2712,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Let me check that." }], meta: { stopReason: "tool_calls", @@ -2760,7 +2760,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); let resolveLateToolCall: | ((result: { payloads: Array<{ text: string }>; @@ -2770,7 +2770,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { }; }) => void) | undefined; - agentCommand.mockImplementationOnce( + agentCommandMock.mockImplementationOnce( ((opts: unknown) => new Promise((resolve) => { resolveLateToolCall = resolve; @@ -2841,8 +2841,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockRejectedValueOnce(createClientToolNameConflictError(["exec"])); + agentCommandMock.mockClear(); + agentCommandMock.mockRejectedValueOnce(createClientToolNameConflictError(["exec"])); const toolConflictRes = await postChatCompletions(port, { stream: true, @@ -2883,8 +2883,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { } { - agentCommand.mockClear(); - agentCommand.mockRejectedValueOnce(new Error("boom")); + agentCommandMock.mockClear(); + agentCommandMock.mockRejectedValueOnce(new Error("boom")); const errorRes = await postChatCompletions(port, { stream: true, @@ -2929,8 +2929,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const expectedArguments = JSON.stringify({ value }); expect(expectedArguments.indexOf(astral)).toBe(boundary); - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Calling the tool." }], meta: { stopReason: "tool_calls", @@ -2991,8 +2991,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const port = enabledPort; let serverAbortSignal: AbortSignal | undefined; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce( (opts: unknown) => new Promise((resolve) => { const signal = (opts as { abortSignal?: AbortSignal } | undefined)?.abortSignal; @@ -3051,7 +3051,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { await expect(firstChunk).resolves.toContain('"role":"assistant"'); await vi.waitFor(() => { - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); await vi.waitFor( @@ -3066,8 +3066,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("keeps streamed agent work admitted after the HTTP handler returns", async () => { const idleRootCount = getActiveGatewayRootWorkCount(); const continueAgent = createDeferred(); - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async () => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async () => { await continueAgent.promise; const queued = await enqueueCommandInLane("openai-http-admission-probe", async () => true); return { payloads: [{ text: queued ? "answer queued" : "unreachable" }] }; @@ -3091,8 +3091,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("buffers replaceable assistant events for streaming chat completions", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, @@ -3132,8 +3132,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("prefers final result text over buffered replaceable chat drafts", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, @@ -3166,8 +3166,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("includes usage in final stream chunk when stream_options.include_usage=true", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, stream: "assistant", data: { delta: "he" } }); emitAgentEvent({ runId, stream: "assistant", data: { delta: "llo" } }); @@ -3214,8 +3214,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("keeps aggregate-only usage total in final stream usage chunk", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, stream: "assistant", data: { delta: "hello" } }); return { @@ -3254,8 +3254,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("finalizes stream when lifecycle end arrives before usage is available", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce( ((opts: unknown) => new Promise((resolve) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; @@ -3303,8 +3303,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const port = enabledPort; let serverAbortSignal: AbortSignal | undefined; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce( (opts: unknown) => new Promise((resolve) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; @@ -3341,7 +3341,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { ); await vi.waitFor(() => { - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); clientReq.destroy(); @@ -3357,8 +3357,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it("does not require usage to finalize when include_usage is not requested", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce( ((opts: unknown) => new Promise((resolve) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; @@ -3393,8 +3393,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { { scopes: "operator.write", senderIsOwner: false }, { scopes: "operator.admin, operator.write", senderIsOwner: true }, ]) { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); const res = await postChatCompletions( enabledPort, @@ -3411,7 +3411,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(res.status).toBe(200); await res.text(); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(firstAgentCommandOptions()?.senderIsOwner).toBe(senderIsOwner); } } @@ -3421,7 +3421,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { await withEnvAsync( { OPENCLAW_GATEWAY_TOKEN: undefined, OPENCLAW_GATEWAY_PASSWORD: undefined }, async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); let server: Awaited> | undefined; const previousGatewayAuth = testState.gatewayAuth; const trustedProxyAuth = { @@ -3453,8 +3453,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { { scopes: "operator.write", senderIsOwner: false }, { scopes: "operator.admin, operator.write", senderIsOwner: true }, ]) { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); const res = await postChatCompletions( port, @@ -3473,12 +3473,12 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(res.status).toBe(200); await res.text(); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(firstAgentCommandOptions()?.senderIsOwner).toBe(senderIsOwner); } } - agentCommand.mockClear(); + agentCommandMock.mockClear(); const unauthorized = await postChatCompletions( port, { model: "openclaw", messages: [{ role: "user", content: "hi" }] }, @@ -3490,7 +3490,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { ); expect(unauthorized.status).toBe(401); await unauthorized.text(); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); } finally { await server?.close({ reason: "openai trusted-proxy auth owner test done" }); testState.gatewayAuth = previousGatewayAuth; @@ -3504,12 +3504,12 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { it.each(["token", "password"] as const)( "preserves owner identity for streaming and non-streaming %s-authenticated callers", async (mode) => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startSharedSecretServer(port, mode); try { for (const stream of [false, true]) { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); const res = await postChatCompletions( port, @@ -3527,11 +3527,11 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { expect(res.status).toBe(200); await res.text(); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(firstAgentCommandOptions()?.senderIsOwner).toBe(true); } - agentCommand.mockClear(); + agentCommandMock.mockClear(); const unauthorized = await postChatCompletions( port, { model: "openclaw", messages: [{ role: "user", content: "hi" }] }, @@ -3539,7 +3539,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { ); expect(unauthorized.status).toBe(401); await unauthorized.text(); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); } finally { await server.close({ reason: `openai ${mode} auth owner test done` }); } @@ -3554,8 +3554,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const cleanupAdmissionClosed = createDeferred(); let serverAbortSignal: AbortSignal | undefined; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce(async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce(async (opts: unknown) => { const signal = (opts as { abortSignal?: AbortSignal } | undefined)?.abortSignal; serverAbortSignal = signal; if (signal?.aborted) { @@ -3589,7 +3589,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { ); await vi.waitFor(() => { - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); try { @@ -3622,8 +3622,8 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { const port = enabledPort; let serverAbortSignal: AbortSignal | undefined; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce( (opts: unknown) => new Promise((resolve) => { const signal = (opts as { abortSignal?: AbortSignal } | undefined)?.abortSignal; @@ -3655,7 +3655,7 @@ describe("OpenAI-compatible HTTP API (e2e)", () => { ); await vi.waitFor(() => { - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); clientReq.destroy(); diff --git a/src/gateway/openresponses-http.server.file-only.test.ts b/src/gateway/openresponses-http.server.file-only.test.ts index 8b70a0e8e999..85180e3f9e97 100644 --- a/src/gateway/openresponses-http.server.file-only.test.ts +++ b/src/gateway/openresponses-http.server.file-only.test.ts @@ -12,8 +12,8 @@ vi.mock("../media/input-files.js", async () => { }); import { - agentCommand, - getFreePort, + agentCommandMock, + getGatewayTestPort, installGatewayTestHooks, startGatewayServerWithRetries, } from "./test-helpers.js"; @@ -28,7 +28,7 @@ let port: number; beforeAll(async () => { const started = await startGatewayServerWithRetries({ - port: await getFreePort(), + port: await getGatewayTestPort(), opts: { host: "127.0.0.1", auth: { mode: "none" }, @@ -87,7 +87,7 @@ describe("OpenResponses file-only input that renders to images", () => { { type: "image", data: Buffer.alloc(8, 1).toString("base64"), mimeType: "image/png" }, ], }); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses({ model: "openclaw", @@ -112,8 +112,8 @@ describe("OpenResponses file-only input that renders to images", () => { }); expect(res.status).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); - const opts = agentCommand.mock.calls[0]?.[0] as { message?: string; images?: unknown[] }; + expect(agentCommandMock).toHaveBeenCalledTimes(1); + const opts = agentCommandMock.mock.calls[0]?.[0] as { message?: string; images?: unknown[] }; expect(opts.message ?? "").not.toBe(""); expect(opts.images?.length).toBe(1); await res.text(); @@ -125,7 +125,7 @@ describe("OpenResponses file-only input that renders to images", () => { text: "", images: [], }); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses({ model: "openclaw", @@ -150,8 +150,8 @@ describe("OpenResponses file-only input that renders to images", () => { const body = await res.text(); expect(res.status, body).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); - const opts = agentCommand.mock.calls[0]?.[0] as { extraSystemPrompt?: string }; + expect(agentCommandMock).toHaveBeenCalledTimes(1); + const opts = agentCommandMock.mock.calls[0]?.[0] as { extraSystemPrompt?: string }; expect(opts.extraSystemPrompt).toContain(''); expect(opts.extraSystemPrompt).toContain("[No extractable text]"); }); @@ -177,7 +177,7 @@ describe("OpenResponses file-only input that renders to images", () => { text: "historical file contents", images: [], }); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses({ model: "openclaw", @@ -195,8 +195,8 @@ describe("OpenResponses file-only input that renders to images", () => { const body = await res.text(); expect(res.status, body).toBe(200); expect(extractFileContentFromSourceMock).not.toHaveBeenCalled(); - expect(agentCommand).toHaveBeenCalledTimes(1); - const opts = agentCommand.mock.calls[0]?.[0] as { + expect(agentCommandMock).toHaveBeenCalledTimes(1); + const opts = agentCommandMock.mock.calls[0]?.[0] as { message?: string; images?: unknown[]; extraSystemPrompt?: string; @@ -215,7 +215,7 @@ describe("OpenResponses file-only input that renders to images", () => { images: [], }), ); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses({ model: "openclaw", @@ -245,7 +245,7 @@ describe("OpenResponses file-only input that renders to images", () => { const body = await res.text(); expect(res.status, body).toBe(200); expect(extractFileContentFromSourceMock).toHaveBeenCalledTimes(1); - const opts = agentCommand.mock.calls[0]?.[0] as { + const opts = agentCommandMock.mock.calls[0]?.[0] as { images?: unknown[]; extraSystemPrompt?: string; }; @@ -262,7 +262,7 @@ describe("OpenResponses file-only input that renders to images", () => { text: "should not become current input", images: [], }); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses({ model: "openclaw", @@ -283,7 +283,7 @@ describe("OpenResponses file-only input that renders to images", () => { const body = await res.text(); expect(res.status, body).toBe(200); expect(extractFileContentFromSourceMock).not.toHaveBeenCalled(); - const opts = agentCommand.mock.calls[0]?.[0] as { + const opts = agentCommandMock.mock.calls[0]?.[0] as { images?: unknown[]; extraSystemPrompt?: string; }; @@ -295,7 +295,7 @@ describe("OpenResponses file-only input that renders to images", () => { it.each(["input_image", "input_file"] as const)( "does not fetch a historical %s URL on a newer text-only turn", async (type) => { - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses({ model: "openclaw", @@ -311,7 +311,7 @@ describe("OpenResponses file-only input that renders to images", () => { const body = await res.text(); expect(res.status, body).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(extractFileContentFromSourceMock).not.toHaveBeenCalled(); }, ); @@ -331,7 +331,7 @@ describe("OpenResponses file-only input that renders to images", () => { }); expect(res.status).toBe(400); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); expect(extractFileContentFromSourceMock).not.toHaveBeenCalled(); await res.text(); }); diff --git a/src/gateway/openresponses-http.test.ts b/src/gateway/openresponses-http.test.ts index fa8bf68a921b..b45f1edeea16 100644 --- a/src/gateway/openresponses-http.test.ts +++ b/src/gateway/openresponses-http.test.ts @@ -22,8 +22,8 @@ import { withEnvAsync } from "../test-utils/env.js"; import { IMAGE_ONLY_USER_MESSAGE } from "./agent-prompt.js"; import { buildAssistantDeltaResult } from "./test-helpers.agent-results.js"; import { - agentCommand, - getFreePort, + agentCommandMock, + getGatewayTestPort, installGatewayTestHooks, startGatewayServerWithRetries, testState, @@ -53,7 +53,7 @@ let openResponsesTesting: { beforeAll(async () => { ({ testing: openResponsesTesting } = await import("./openresponses-http.js")); const started = await startGatewayServerWithRetries({ - port: await getFreePort(), + port: await getGatewayTestPort(), opts: { host: "127.0.0.1", auth: { mode: "none" }, @@ -191,7 +191,7 @@ function requireSessionKey(value: string | undefined, label: string): string { } function firstAgentOpts(callIndex = 0): Record { - const call = agentCommand.mock.calls[callIndex]; + const call = agentCommandMock.mock.calls[callIndex]; if (!call) { throw new Error(`expected agentCommand call #${callIndex + 1}`); } @@ -313,8 +313,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it.each([false, true])( "accepts the official OpenAI SDK plain-text response format (stream: %s)", async (stream) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "SDK plain-text response" }], } as never); @@ -348,7 +348,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(response.output_text).toBe("SDK plain-text response"); } - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }, ); @@ -359,8 +359,8 @@ describe("OpenResponses HTTP API (e2e)", () => { ])( "fails an official SDK Responses stream when streamed text is $name", async ({ replacementText }) => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming response run ID"); @@ -430,7 +430,7 @@ describe("OpenResponses HTTP API (e2e)", () => { }, ]); expect(completionCount).toBe(0); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }, ); @@ -456,8 +456,8 @@ describe("OpenResponses HTTP API (e2e)", () => { ])( "keeps official SDK Responses text consistent for $name", async ({ previousDelta, replacementDelta, expectedDeltas }) => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming response run ID"); @@ -517,7 +517,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(deltas.join("")).toBe(expectedDeltas); expect(doneTexts).toEqual(["final answer"]); expect(completedTexts).toEqual(["final answer"]); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }, ); @@ -538,7 +538,7 @@ describe("OpenResponses HTTP API (e2e)", () => { text: { format: { type: "text" }, verbosity: "high" }, }, ])("rejects unsupported SDK text options ($name)", async ({ text }) => { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postResponses(enabledPort, { model: "openclaw", @@ -547,14 +547,14 @@ describe("OpenResponses HTTP API (e2e)", () => { }); await expectInvalidRequest(res, /text/); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); }); it("handles OpenResponses request parsing and validation", async () => { const port = enabledPort; const mockAgentOnce = (payloads: Array<{ text: string }>, meta?: unknown) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads, meta } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads, meta } as never); }; try { @@ -584,7 +584,7 @@ describe("OpenResponses HTTP API (e2e)", () => { ); await ensureResponseConsumed(resMissingModel); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const resInvalidModel = await postResponses(port, { model: "openai/", input: "hi" }); expect(resInvalidModel.status).toBe(400); const invalidModelJson = (await resInvalidModel.json()) as { @@ -594,7 +594,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(invalidModelJson.error?.message).toBe( "Invalid `model`. Use `openclaw` or `openclaw/`.", ); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); await ensureResponseConsumed(resInvalidModel); mockAgentOnce([{ text: "hello" }]); @@ -628,7 +628,7 @@ describe("OpenResponses HTTP API (e2e)", () => { ); await ensureResponseConsumed(resSessionOverride); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const resReservedSessionOverride = await postResponses( port, { model: "openclaw", input: "hi" }, @@ -642,7 +642,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(reservedSessionJson.error?.message).toBe( "`x-openclaw-session-key` cannot use reserved internal session namespaces.", ); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); const resHarnessSessionOverride = await postResponses( port, @@ -659,7 +659,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(harnessSessionJson.error?.message).toBe( "`x-openclaw-session-key` cannot use reserved internal session namespaces.", ); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); mockAgentOnce([{ text: "hello" }]); const resModel = await postResponses(port, { model: "openclaw/beta", input: "hi" }); @@ -680,7 +680,7 @@ describe("OpenResponses HTTP API (e2e)", () => { await ensureResponseConsumed(resDefaultAlias); { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postResponses( port, { model: "openclaw", input: "hi" }, @@ -690,17 +690,17 @@ describe("OpenResponses HTTP API (e2e)", () => { const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toBe("Unknown agent 'missing-agent'."); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postResponses(port, { model: "openclaw/missing-agent", input: "hi" }); expect(res.status).toBe(400); const json = (await res.json()) as { error?: { type?: string; message?: string } }; expect(json.error?.type).toBe("invalid_request_error"); expect(json.error?.message).toBe("Unknown agent 'missing-agent'."); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); } mockAgentOnce([{ text: "hello" }]); @@ -733,7 +733,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect((optsModelOverride as { model?: string } | undefined)?.model).toBe("openai/gpt-5.4"); await ensureResponseConsumed(resModelOverride); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const resInvalidOverride = await postResponses( port, { model: "openclaw", input: "hi" }, @@ -748,10 +748,10 @@ describe("OpenResponses HTTP API (e2e)", () => { }; expect(invalidOverrideJson.error?.type).toBe("invalid_request_error"); expect(invalidOverrideJson.error?.message).toBe("Invalid `x-openclaw-model`."); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); await ensureResponseConsumed(resInvalidOverride); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const resWriteOnlyOverride = await postResponses( port, { model: "openclaw", input: "hi" }, @@ -763,11 +763,11 @@ describe("OpenResponses HTTP API (e2e)", () => { }; expect(writeOnlyJson.error?.type).toBe("forbidden"); expect(writeOnlyJson.error?.message).toBe("missing scope: operator.admin"); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); await ensureResponseConsumed(resWriteOnlyOverride); - agentCommand.mockClear(); - agentCommand.mockRejectedValueOnce(createClientToolNameConflictError(["exec"])); + agentCommandMock.mockClear(); + agentCommandMock.mockRejectedValueOnce(createClientToolNameConflictError(["exec"])); const resToolConflict = await postResponses(port, { model: "openclaw", input: "hi", @@ -1109,7 +1109,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(samplingStreamParams?.topP).toBe(0.9); await ensureResponseConsumed(resSampling); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const resInvalidTemperature = await postResponses(port, { model: "openclaw", input: "hi", @@ -1121,9 +1121,9 @@ describe("OpenResponses HTTP API (e2e)", () => { }; expect(invalidTemperatureJson.error?.type).toBe("invalid_request_error"); expect(invalidTemperatureJson.error?.message ?? "").toMatch(/temperature/i); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const resInvalidTopP = await postResponses(port, { model: "openclaw", input: "hi", @@ -1135,7 +1135,7 @@ describe("OpenResponses HTTP API (e2e)", () => { }; expect(invalidTopPJson.error?.type).toBe("invalid_request_error"); expect(invalidTopPJson.error?.message ?? "").toMatch(/top_p/i); - expect(agentCommand).toHaveBeenCalledTimes(0); + expect(agentCommandMock).toHaveBeenCalledTimes(0); mockAgentOnce([{ text: "ok" }], { agentMeta: { @@ -1217,8 +1217,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("streams OpenResponses SSE events", async () => { const port = enabledPort; try { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => buildAssistantDeltaResult({ opts, emit: emitAgentEvent, @@ -1265,8 +1265,8 @@ describe("OpenResponses HTTP API (e2e)", () => { ).response?.output; expect(completedDeltaOutput?.[0]?.phase).toBe("final_answer"); - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }], } as never); @@ -1280,8 +1280,8 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(fallbackText).toContain("[DONE]"); expect(fallbackText).toContain("hello"); - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }], } as never); @@ -1310,8 +1310,8 @@ describe("OpenResponses HTTP API (e2e)", () => { { name: "missing aggregate", usage: undefined }, { name: "zero aggregate", usage: { input: 0, output: 0, total: 0 } }, ])("uses last-call usage in the terminal SSE response for $name", async ({ usage }) => { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }], meta: { agentMeta: { @@ -1352,8 +1352,8 @@ describe("OpenResponses HTTP API (e2e)", () => { }); it("flushes same-turn assistant microtasks before completing an official SDK stream", async () => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce(((opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce(((opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming response run ID"); @@ -1391,13 +1391,13 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(deltas.join("")).toBe("start finish!"); expect(response.status).toBe("completed"); expect(response.output_text).toBe("start finish!"); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); it("flushes post-lifecycle assistant microtasks after usage is already available", async () => { let unsubscribe = () => {}; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce(((opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce(((opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming response run ID"); @@ -1427,7 +1427,7 @@ describe("OpenResponses HTTP API (e2e)", () => { .finalResponse(); expect(response.output_text).toBe("start finish"); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); } finally { unsubscribe(); } @@ -1435,8 +1435,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("fails conflicting assistant replacements queued after lifecycle completion", async () => { let unsubscribe = () => {}; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce(((opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce(((opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming response run ID"); @@ -1490,8 +1490,8 @@ describe("OpenResponses HTTP API (e2e)", () => { }); it("fails an official SDK stream when an error lifecycle precedes a resolved run", async () => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming response run ID"); @@ -1545,7 +1545,7 @@ describe("OpenResponses HTTP API (e2e)", () => { output_tokens: 7, total_tokens: 18, }); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); it.each([ @@ -1615,8 +1615,8 @@ describe("OpenResponses HTTP API (e2e)", () => { }); }); - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { activeRunId = (opts as { runId?: string }).runId; if (!activeRunId) { throw new Error("expected a streaming response run ID"); @@ -1659,7 +1659,7 @@ describe("OpenResponses HTTP API (e2e)", () => { stream.on("response.completed", () => terminalEvents.push("response.completed")); stream.on("response.failed", () => terminalEvents.push("response.failed")); const finalResponse = stream.finalResponse(); - await vi.waitFor(() => expect(agentCommand).toHaveBeenCalledTimes(1)); + await vi.waitFor(() => expect(agentCommandMock).toHaveBeenCalledTimes(1)); await new Promise((resolve) => { setImmediate(resolve); }); @@ -1687,8 +1687,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("maps provider format failures to OpenResponses 400 failed responses", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockRejectedValueOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockRejectedValueOnce( new FailoverError( "LLM request failed: provider rejected the request schema or tool payload.", { @@ -1713,7 +1713,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(json.status).toBe("failed"); expect(json.error?.code).toBe("invalid_request_error"); expect(json.error?.message).toContain("Invalid 'top_p'"); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }); it.each( @@ -1732,8 +1732,8 @@ describe("OpenResponses HTTP API (e2e)", () => { "closes the response stream for $label without reporting completion", async ({ createError, emitErrorLifecycle, expectedCode, expectedMessage, tools }) => { const idleRootCount = getActiveGatewayRootWorkCount(); - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { if (emitErrorLifecycle) { const runId = (opts as { runId?: string }).runId; if (!runId) { @@ -1772,7 +1772,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(failedResponse?.status).toBe("failed"); expect(failedResponse?.error?.code).toBe(expectedCode); expect(failedResponse?.error?.message).toContain(expectedMessage); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); await vi.waitFor(() => expect(getActiveGatewayRootWorkCount()).toBe(idleRootCount)); }, ); @@ -1784,8 +1784,8 @@ describe("OpenResponses HTTP API (e2e)", () => { { scopes: "operator.write", senderIsOwner: false }, { scopes: "operator.admin, operator.write", senderIsOwner: true }, ]) { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); const res = await postResponses( port, @@ -1801,7 +1801,7 @@ describe("OpenResponses HTTP API (e2e)", () => { if (stream) { expect(parseSseEvents(body).map((event) => event.event)).toContain("response.completed"); } - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(firstAgentOpts().senderIsOwner).toBe(senderIsOwner); } } @@ -1811,7 +1811,7 @@ describe("OpenResponses HTTP API (e2e)", () => { await withEnvAsync( { OPENCLAW_GATEWAY_TOKEN: undefined, OPENCLAW_GATEWAY_PASSWORD: undefined }, async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const { startGatewayServer } = await import("./server.js"); let server: Awaited> | undefined; const previousGatewayAuth = testState.gatewayAuth; @@ -1844,8 +1844,8 @@ describe("OpenResponses HTTP API (e2e)", () => { { scopes: "operator.write", senderIsOwner: false }, { scopes: "operator.admin, operator.write", senderIsOwner: true }, ]) { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); const res = await postResponses( port, @@ -1860,13 +1860,13 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(res.status).toBe(200); await ensureResponseConsumed(res); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(firstAgentOpts().senderIsOwner).toBe(senderIsOwner); } } - agentCommand.mockClear(); - agentCommand.mockResolvedValue({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValue({ payloads: [{ text: "hello" }] } as never); const forwardedHeaders = { "x-forwarded-proto": "https", authorization: "Bearer forwarded-untrusted", @@ -1915,7 +1915,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(firstAgentOpts(2).sessionKey).not.toBe(aliceSessionKey); expect(firstAgentOpts(1).sessionKey).toBe(aliceSessionKey); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const unauthorized = await postResponses( port, { model: "openclaw", input: "hi" }, @@ -1927,7 +1927,7 @@ describe("OpenResponses HTTP API (e2e)", () => { ); expect(unauthorized.status).toBe(401); await ensureResponseConsumed(unauthorized); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); } finally { await server?.close({ reason: "openresponses trusted-proxy auth owner test done" }); testState.gatewayAuth = previousGatewayAuth; @@ -1941,12 +1941,12 @@ describe("OpenResponses HTTP API (e2e)", () => { it.each(["token", "password"] as const)( "preserves owner identity for streaming and non-streaming %s-authenticated callers", async (mode) => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startSharedSecretServer(port, mode); try { for (const stream of [false, true]) { - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "hello" }] } as never); const res = await postResponses( port, @@ -1960,11 +1960,11 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(res.status).toBe(200); await ensureResponseConsumed(res); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); expect(firstAgentOpts().senderIsOwner).toBe(true); } - agentCommand.mockClear(); + agentCommandMock.mockClear(); const unauthorized = await postResponses( port, { model: "openclaw", input: "hi" }, @@ -1972,7 +1972,7 @@ describe("OpenResponses HTTP API (e2e)", () => { ); expect(unauthorized.status).toBe(401); await ensureResponseConsumed(unauthorized); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); } finally { await server.close({ reason: `openresponses ${mode} auth owner test done` }); } @@ -1982,8 +1982,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("keeps streamed agent work admitted after the HTTP handler returns", async () => { const idleRootCount = getActiveGatewayRootWorkCount(); const continueAgent = createDeferred(); - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async () => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async () => { await continueAgent.promise; const queued = await enqueueCommandInLane( "openresponses-http-admission-probe", @@ -2011,8 +2011,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("preserves assistant text alongside non-stream function_call output", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Let me check that." }], meta: { stopReason: "tool_calls", @@ -2052,8 +2052,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("rejects an unsatisfied required tool_choice on the non-streaming path", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "plain text despite required" }], } as never); @@ -2078,8 +2078,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("returns the tool call when a required tool_choice is satisfied (non-streaming)", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Calling the tool." }], meta: { stopReason: "tool_calls", @@ -2112,8 +2112,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("rejects an unsatisfied function tool_choice on the non-streaming path", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "I can answer without the weather tool." }], } as never); @@ -2138,8 +2138,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("rejects a non-streaming function tool_choice when the agent calls a different tool", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Calling another tool." }], meta: { stopReason: "tool_calls", @@ -2179,8 +2179,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("rejects an unsatisfied required tool_choice on the streaming path without leaking text", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => buildAssistantDeltaResult({ opts, emit: emitAgentEvent, @@ -2218,8 +2218,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("rejects a streaming function tool_choice when the agent calls a different tool", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Calling another tool." }], meta: { stopReason: "tool_calls", @@ -2266,8 +2266,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("emits the tool call when a streaming required tool_choice is satisfied", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, stream: "assistant", data: { delta: "Calling " } }); emitAgentEvent({ runId, stream: "assistant", data: { delta: "the tool." } }); @@ -2314,8 +2314,8 @@ describe("OpenResponses HTTP API (e2e)", () => { ])( "buffers $name until the required client tool is validated", async ({ previousDelta, replacementDelta }) => { - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string }).runId; if (!runId) { throw new Error("expected a streaming response run ID"); @@ -2380,8 +2380,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("buffers replaceable assistant events for streaming responses", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, @@ -2424,8 +2424,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("prefers final result text over buffered replaceable response drafts", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce((async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce((async (opts: unknown) => { const runId = (opts as { runId?: string } | undefined)?.runId ?? ""; emitAgentEvent({ runId, @@ -2457,8 +2457,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("falls back to payload text for streamed function_call responses", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Let me check that." }], meta: { stopReason: "tool_calls", @@ -2510,8 +2510,8 @@ describe("OpenResponses HTTP API (e2e)", () => { // alongside the assistant text. This locks in the contract for callers // who run multi-tool agents (graph orchestration, planners, etc.). const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Calling all three tools now." }], meta: { stopReason: "tool_calls", @@ -2572,8 +2572,8 @@ describe("OpenResponses HTTP API (e2e)", () => { // - the final `response.completed` payload contains the assistant // message followed by all three function_call items in order. const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Calling all three tools now." }], meta: { stopReason: "tool_calls", @@ -2649,8 +2649,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("reuses the prior session when previous_response_id is provided", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Let me check that." }], meta: { stopReason: "tool_calls", @@ -2676,7 +2676,7 @@ describe("OpenResponses HTTP API (e2e)", () => { expect(firstJson.id).toMatch(/^resp_/); const firstSessionKey = requireSessionKey(firstOpts?.sessionKey, "first response"); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "It is sunny." }], } as never); @@ -2694,8 +2694,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("reuses prior sessions across different user values when auth scope matches", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "First turn." }], } as never); @@ -2710,7 +2710,7 @@ describe("OpenResponses HTTP API (e2e)", () => { const firstOpts = firstAgentOpts() as { sessionKey?: string } | undefined; expect(firstOpts?.sessionKey ?? "").toContain("openresponses-user:alice"); - agentCommand.mockResolvedValueOnce({ + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "Second turn." }], } as never); @@ -2729,10 +2729,10 @@ describe("OpenResponses HTTP API (e2e)", () => { it("stores response session mappings when the response is emitted", async () => { const port = enabledPort; - agentCommand.mockClear(); + agentCommandMock.mockClear(); let release: ((value: { payloads: Array<{ text: string }> }) => void) | undefined; - agentCommand.mockImplementationOnce( + agentCommandMock.mockImplementationOnce( () => new Promise<{ payloads: Array<{ text: string }> }>((resolve) => { release = resolve; @@ -2746,7 +2746,7 @@ describe("OpenResponses HTTP API (e2e)", () => { }); await vi.waitFor(() => { - expect(agentCommand.mock.calls).toHaveLength(1); + expect(agentCommandMock.mock.calls).toHaveLength(1); }); expect(openResponsesTesting.getResponseSessionIds()).toStrictEqual([]); @@ -2794,7 +2794,7 @@ describe("OpenResponses HTTP API (e2e)", () => { it("blocks unsafe URL-based file/image inputs", async () => { const port = enabledPort; - agentCommand.mockClear(); + agentCommandMock.mockClear(); const blockedPrivate = await postResponses(port, { model: "openclaw", @@ -2822,7 +2822,7 @@ describe("OpenResponses HTTP API (e2e)", () => { }), }); await expectInvalidRequest(blockedScheme, /invalid request|http or https/i); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); }); it("accepts image-only input without text, matching /v1/chat/completions", async () => { @@ -2831,8 +2831,8 @@ describe("OpenResponses HTTP API (e2e)", () => { const pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses(port, { model: "openclaw", @@ -2851,7 +2851,7 @@ describe("OpenResponses HTTP API (e2e)", () => { }); expect(res.status).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); const opts = firstAgentOpts(); // Image-only turn carries a non-empty placeholder so the agent command runs, // with the real image attached via `images` (parity with /v1/chat/completions). @@ -2862,8 +2862,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("accepts file-only input without text, matching image-only", async () => { const port = enabledPort; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses(port, { model: "openclaw", @@ -2888,7 +2888,7 @@ describe("OpenResponses HTTP API (e2e)", () => { }); expect(res.status).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); const opts = firstAgentOpts(); expect((opts as { message?: string }).message ?? "").not.toBe(""); const extraSystemPrompt = (opts as { extraSystemPrompt?: string }).extraSystemPrompt ?? ""; @@ -2900,8 +2900,8 @@ describe("OpenResponses HTTP API (e2e)", () => { it("keeps base64 input_file text truncation UTF-16 safe", async () => { const port = enabledPort; const text = `${"a".repeat(59_999)}😀tail`; - agentCommand.mockClear(); - agentCommand.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); + agentCommandMock.mockClear(); + agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }] } as never); const res = await postResponses(port, { model: "openclaw", @@ -2925,7 +2925,7 @@ describe("OpenResponses HTTP API (e2e)", () => { }); expect(res.status).toBe(200); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); const opts = firstAgentOpts(); const extraSystemPrompt = (opts as { extraSystemPrompt?: string }).extraSystemPrompt ?? ""; expect(extraSystemPrompt).toContain(''); @@ -2937,7 +2937,7 @@ describe("OpenResponses HTTP API (e2e)", () => { it("still rejects input with neither text nor image", async () => { const port = enabledPort; - agentCommand.mockClear(); + agentCommandMock.mockClear(); const res = await postResponses(port, { model: "openclaw", @@ -2945,17 +2945,17 @@ describe("OpenResponses HTTP API (e2e)", () => { }); await expectInvalidRequest(res, /Missing user message/i); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); }); it("enforces URL allowlist and URL part cap for responses inputs", async () => { const allowlistConfig = buildResponsesUrlPolicyConfig(1); await writeGatewayConfig(allowlistConfig); - const allowlistPort = await getFreePort(); + const allowlistPort = await getGatewayTestPort(); const allowlistServer = await startServer(allowlistPort, { openResponsesEnabled: true }); try { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const allowlistBlocked = await postResponses(allowlistPort, { model: "openclaw", @@ -2973,10 +2973,10 @@ describe("OpenResponses HTTP API (e2e)", () => { const capConfig = buildResponsesUrlPolicyConfig(0); await writeGatewayConfig(capConfig); - const capPort = await getFreePort(); + const capPort = await getGatewayTestPort(); const capServer = await startServer(capPort, { openResponsesEnabled: true }); try { - agentCommand.mockClear(); + agentCommandMock.mockClear(); const maxUrlBlocked = await postResponses(capPort, { model: "openclaw", input: buildUrlInputMessage({ @@ -2989,7 +2989,7 @@ describe("OpenResponses HTTP API (e2e)", () => { maxUrlBlocked, /invalid request|Too many URL-based input sources/i, ); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); } finally { await capServer.close({ reason: "responses url cap hardening test done" }); } @@ -3003,8 +3003,8 @@ describe("OpenResponses HTTP API (e2e)", () => { const cleanupAdmissionClosed = createDeferred(); let serverAbortSignal: AbortSignal | undefined; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce(async (opts: unknown) => { + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce(async (opts: unknown) => { const signal = (opts as { abortSignal?: AbortSignal } | undefined)?.abortSignal; serverAbortSignal = signal; if (signal?.aborted) { @@ -3039,7 +3039,7 @@ describe("OpenResponses HTTP API (e2e)", () => { await vi.waitFor( () => { - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }, { timeout: 5_000, interval: 50 }, ); @@ -3074,8 +3074,8 @@ describe("OpenResponses HTTP API (e2e)", () => { const port = enabledPort; let serverAbortSignal: AbortSignal | undefined; - agentCommand.mockClear(); - agentCommand.mockImplementationOnce( + agentCommandMock.mockClear(); + agentCommandMock.mockImplementationOnce( (opts: unknown) => new Promise((resolve) => { const signal = (opts as { abortSignal?: AbortSignal } | undefined)?.abortSignal; @@ -3108,7 +3108,7 @@ describe("OpenResponses HTTP API (e2e)", () => { await vi.waitFor( () => { - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); }, { timeout: 5_000, interval: 50 }, ); diff --git a/src/gateway/probe.ts b/src/gateway/probe.ts index 605edd6e2eb0..0e303f992686 100644 --- a/src/gateway/probe.ts +++ b/src/gateway/probe.ts @@ -14,7 +14,7 @@ import { import { loadDeviceAuthToken, loadOriginDeviceToken } from "../infra/device-auth-store.js"; import { formatErrorMessage } from "../infra/errors.js"; import type { SystemPresence } from "../infra/system-presence.js"; -import { MAX_SAFE_TIMEOUT_DELAY_MS, resolveSafeTimeoutDelayMs } from "../utils/timer-delay.js"; +import { resolveSafeTimeoutDelayMs } from "../utils/timer-delay.js"; import { startGatewayClientWhenEventLoopReady } from "./client-start-readiness.js"; import { GatewayClient, GatewayClientRequestError } from "./client.js"; import { READ_SCOPE } from "./method-scopes.js"; @@ -69,7 +69,6 @@ export type GatewayProbeResult = { type GatewayProbeDetailLevel = "none" | "presence" | "config" | "full"; const MIN_PROBE_TIMEOUT_MS = 250; -export const MAX_TIMER_DELAY_MS = MAX_SAFE_TIMEOUT_DELAY_MS; const OPERATOR_READ_SCOPE = "operator.read"; const OPERATOR_WRITE_SCOPE = "operator.write"; const OPERATOR_ADMIN_SCOPE = "operator.admin"; diff --git a/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts b/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts index 25e8bd6ca3c9..061064a120c0 100644 --- a/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts +++ b/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts @@ -60,9 +60,11 @@ export const resolveSessionMcpConfigSummary = vi.fn(() => ({ fingerprint: "mcp:0", serverNames: [] as string[], })); -export const buildBundleMcpToolsFromCatalog = vi.fn(() => []); -export const applyFinalEffectiveToolPolicy = vi.fn( - (params: { bundledTools: unknown[] }) => params.bundledTools, -); -export const getActivePluginRegistryVersion = vi.fn(() => 1); -export const getActivePluginChannelRegistryVersion = vi.fn(() => 1); +export const toolsEffectiveRuntimeMockModule = { + buildBundleMcpToolsFromCatalog: vi.fn(() => []), + applyFinalEffectiveToolPolicy: vi.fn( + (params: { bundledTools: unknown[] }) => params.bundledTools, + ), + getActivePluginRegistryVersion: vi.fn(() => 1), + getActivePluginChannelRegistryVersion: vi.fn(() => 1), +}; diff --git a/src/gateway/server-methods/tools-effective.global-agent.integration.test.ts b/src/gateway/server-methods/tools-effective.global-agent.integration.test.ts index e6be281a9e4e..1a641eb3925e 100644 --- a/src/gateway/server-methods/tools-effective.global-agent.integration.test.ts +++ b/src/gateway/server-methods/tools-effective.global-agent.integration.test.ts @@ -7,7 +7,10 @@ import { getGatewayConfigModule, sessionStoreEntry } from "../test/server-sessio import { toolsEffectiveGlobalAgentRuntimeMocks as inventoryMocks } from "./__mocks__/tools-effective.runtime.js"; import { testing, toolsEffectiveHandlers } from "./tools-effective.js"; -vi.mock("./tools-effective.runtime.js"); +vi.mock("./tools-effective.runtime.js", async () => { + const mockModule = await import("./__mocks__/tools-effective.runtime.js"); + return { ...mockModule, ...mockModule.toolsEffectiveRuntimeMockModule }; +}); installGatewayTestHooks(); diff --git a/src/gateway/server-runtime-state-prepare.ts b/src/gateway/server-runtime-state-prepare.ts index bd76820c0143..263c39458120 100644 --- a/src/gateway/server-runtime-state-prepare.ts +++ b/src/gateway/server-runtime-state-prepare.ts @@ -6,6 +6,7 @@ import { createDefaultDeps } from "../cli/deps.js"; import { getRuntimeConfig } from "../config/io.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isTruthyEnvValue } from "../infra/env.js"; +import { loadGatewayTlsRuntime } from "../infra/tls/gateway.js"; import type { createSubsystemLogger } from "../logging/subsystem.js"; import { runtimeForLogger } from "../logging/subsystem.js"; import { isGatewayDraining } from "../process/command-queue.js"; @@ -29,7 +30,6 @@ import { createWizardSessionTracker } from "./server-wizard-sessions.js"; import { createGatewayEventLoopHealthMonitor } from "./server/event-loop-health.js"; import { resolveHookClientIpConfig } from "./server/hook-client-ip-config.js"; import { createReadinessChecker } from "./server/readiness.js"; -import { loadGatewayTlsRuntime } from "./server/tls.js"; import { resolveSharedGatewaySessionGeneration } from "./server/ws-shared-generation.js"; type GatewayBootstrap = Awaited>; diff --git a/src/gateway/server-runtime-state.ts b/src/gateway/server-runtime-state.ts index 779d1c8dca7e..fba39e3a33e7 100644 --- a/src/gateway/server-runtime-state.ts +++ b/src/gateway/server-runtime-state.ts @@ -13,6 +13,7 @@ import { resolveSandboxHostPort } from "../agents/sandbox-host.js"; import { isCoreCanvasHostEnabled } from "../canvas/config.js"; import { resolveCanvasNodeCapability } from "../canvas/constants.js"; import type { CliDeps } from "../cli/deps.types.js"; +import type { GatewayTlsRuntime } from "../infra/tls/gateway.js"; import type { createSubsystemLogger } from "../logging/subsystem.js"; import type { PluginRegistry } from "../plugins/registry.js"; import type { AuthRateLimiter } from "./auth-rate-limit.js"; @@ -41,7 +42,6 @@ import { type PreauthConnectionBudget, } from "./server/preauth-connection-budget.js"; import type { ReadinessChecker } from "./server/readiness.js"; -import type { GatewayTlsRuntime } from "./server/tls.js"; import type { GatewayWsClient } from "./server/ws-types.js"; import type { WorkerDesktopTunnels } from "./worker-environments/desktop-tunnel.js"; diff --git a/src/gateway/server-startup-copied-auth-secretref.test.ts b/src/gateway/server-startup-copied-auth-secretref.test.ts index 015408e2834b..fd4a87a172d9 100644 --- a/src/gateway/server-startup-copied-auth-secretref.test.ts +++ b/src/gateway/server-startup-copied-auth-secretref.test.ts @@ -12,7 +12,11 @@ import { clearSecretsRuntimeSnapshot, getActiveSecretsRuntimeSnapshot, } from "../secrets/runtime.js"; -import { getFreePort, installGatewayTestHooks, startTestGatewayServer } from "./test-helpers.js"; +import { + getGatewayTestPort, + installGatewayTestHooks, + startTestGatewayServer, +} from "./test-helpers.js"; installGatewayTestHooks({ scope: "suite" }); @@ -56,7 +60,7 @@ describe("Gateway startup copied auth SecretRef isolation", () => { }); await writeConfigFile(config); - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { auth: { mode: "none" } }); const ready = await fetch(`http://127.0.0.1:${port}/readyz`); diff --git a/src/gateway/server-startup-plugin-quarantine.test.ts b/src/gateway/server-startup-plugin-quarantine.test.ts index 2d48f48e59a9..f0b8e2eb6f59 100644 --- a/src/gateway/server-startup-plugin-quarantine.test.ts +++ b/src/gateway/server-startup-plugin-quarantine.test.ts @@ -10,7 +10,7 @@ import { setActiveDegradedPlugins, } from "../plugins/runtime-degraded-state.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, setTestPluginRegistry, startTestGatewayServer, @@ -157,7 +157,7 @@ describe("Gateway startup plugin quarantine", () => { plugins: pluginConfig, }); - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { auth: { mode: "none" } }); const ready = await fetch(`http://127.0.0.1:${port}/readyz`); diff --git a/src/gateway/server-startup-secret-owner-isolation.test.ts b/src/gateway/server-startup-secret-owner-isolation.test.ts index ab75248df6fd..84c9922680f0 100644 --- a/src/gateway/server-startup-secret-owner-isolation.test.ts +++ b/src/gateway/server-startup-secret-owner-isolation.test.ts @@ -16,7 +16,7 @@ import { setActiveDegradedSecretOwners } from "../secrets/runtime-degraded-state import { getActiveSecretsRuntimeSnapshot } from "../secrets/runtime.js"; import { withEnvAsync } from "../test-utils/env.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, startTestGatewayServer, testState, @@ -271,7 +271,7 @@ describe("Gateway startup SecretRef owner isolation", () => { }); testState.gatewayAuth = undefined; - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port); const ready = await fetch(`http://127.0.0.1:${port}/readyz`); @@ -434,7 +434,7 @@ describe("Gateway startup SecretRef owner isolation", () => { }, }); - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { auth: { mode: "none" } }); const ready = await fetch(`http://127.0.0.1:${port}/readyz`); @@ -507,7 +507,7 @@ describe("Gateway startup SecretRef owner isolation", () => { }, }); - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { auth: { mode: "none" } }); const ready = await fetch(`http://127.0.0.1:${port}/readyz`); @@ -581,7 +581,7 @@ describe("Gateway startup SecretRef owner isolation", () => { ); await writeConfig(config); - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { auth: { mode: "none" } }); const ready = await fetch(`http://127.0.0.1:${port}/readyz`); expect(ready.status).toBe(200); @@ -631,7 +631,7 @@ describe("Gateway startup SecretRef owner isolation", () => { }); testState.gatewayAuth = undefined; - await expect(startTestGatewayServer(await getFreePort())).rejects.toThrow( + await expect(startTestGatewayServer(await getGatewayTestPort())).rejects.toThrow( /Startup failed: required secrets are unavailable/, ); }); diff --git a/src/gateway/server-startup-web-fetch-bind.test.ts b/src/gateway/server-startup-web-fetch-bind.test.ts index 7b7e13cc76c4..191073fcd267 100644 --- a/src/gateway/server-startup-web-fetch-bind.test.ts +++ b/src/gateway/server-startup-web-fetch-bind.test.ts @@ -4,7 +4,11 @@ import http from "node:http"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; -import { getFreePort, installGatewayTestHooks, startTestGatewayServer } from "./test-helpers.js"; +import { + getGatewayTestPort, + installGatewayTestHooks, + startTestGatewayServer, +} from "./test-helpers.js"; import { readClientResponseBody } from "./test-http-response.js"; const webFetchProviderDiscovery = vi.hoisted(() => ({ @@ -121,7 +125,7 @@ describe("gateway startup web fetch config", () => { }, } as OpenClawConfig); - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { auth: { mode: "none" }, }); diff --git a/src/gateway/server.agent.deleted-agent-gateway.test.ts b/src/gateway/server.agent.deleted-agent-gateway.test.ts index 762b58b5395c..6349b66b4cd3 100644 --- a/src/gateway/server.agent.deleted-agent-gateway.test.ts +++ b/src/gateway/server.agent.deleted-agent-gateway.test.ts @@ -1,7 +1,7 @@ import path from "node:path"; import { expect, test, vi } from "vitest"; import { ErrorCodes } from "../../packages/gateway-protocol/src/index.js"; -import { agentCommand, rpcReq, testState, writeSessionStore } from "./test-helpers.js"; +import { agentCommandMock, rpcReq, testState, writeSessionStore } from "./test-helpers.js"; import { sessionStoreEntry, setupGatewaySessionsTestHarness, @@ -35,7 +35,7 @@ test("agent RPC rejects deleted-agent session keys before dispatch", async () => }, }); - vi.mocked(agentCommand).mockClear(); + vi.mocked(agentCommandMock).mockClear(); const { ws } = await openClient(); try { const blocked = await rpcReq(ws, "agent", { @@ -48,7 +48,7 @@ test("agent RPC rejects deleted-agent session keys before dispatch", async () => code: ErrorCodes.INVALID_REQUEST, message: 'Agent "deleted-agent" no longer exists in configuration', }); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); } finally { ws.close(); resetSessionStoreFixture(); @@ -69,7 +69,7 @@ test("agent RPC rejects archived session keys before dispatch", async () => { }, }); - vi.mocked(agentCommand).mockClear(); + vi.mocked(agentCommandMock).mockClear(); const { ws } = await openClient(); try { const blocked = await rpcReq(ws, "agent", { @@ -83,7 +83,7 @@ test("agent RPC rejects archived session keys before dispatch", async () => { message: 'Session "agent:main:subagent:archived" is archived. Restore it before starting new work.', }); - expect(agentCommand).not.toHaveBeenCalled(); + expect(agentCommandMock).not.toHaveBeenCalled(); } finally { ws.close(); resetSessionStoreFixture(); @@ -103,7 +103,7 @@ test("agent RPC still dispatches for configured-agent session keys", async () => }, }); - vi.mocked(agentCommand).mockClear(); + vi.mocked(agentCommandMock).mockClear(); const { ws } = await openClient(); try { const accepted = await rpcReq(ws, "agent", { @@ -114,7 +114,7 @@ test("agent RPC still dispatches for configured-agent session keys", async () => expect(accepted.ok).toBe(true); expect(accepted.payload?.status).toBe("accepted"); expect(accepted.payload?.runId).toBe("proof-main-agent"); - await vi.waitFor(() => expect(agentCommand).toHaveBeenCalled()); + await vi.waitFor(() => expect(agentCommandMock).toHaveBeenCalled()); } finally { ws.close(); resetSessionStoreFixture(); diff --git a/src/gateway/server.agent.gateway-server-agent-a.test.ts b/src/gateway/server.agent.gateway-server-agent-a.test.ts index 83cd629aeeb3..5ad24e2f4c47 100644 --- a/src/gateway/server.agent.gateway-server-agent-a.test.ts +++ b/src/gateway/server.agent.gateway-server-agent-a.test.ts @@ -29,7 +29,7 @@ import { setRegistry } from "./server.agent.gateway-server-agent.mocks.js"; import { createRegistry } from "./server.e2e-registry-helpers.js"; import { installConnectedSessionStoreGatewaySuite } from "./test-helpers.connected-session-store.js"; import { - agentCommand, + agentCommandMock, installGatewayTestHooks, agentDiscoveryMock, rpcReq, @@ -236,7 +236,7 @@ const defaultRegistry = createRegistry([ describe("gateway server agent", () => { beforeEach(() => { - vi.mocked(agentCommand).mockClear(); + vi.mocked(agentCommandMock).mockClear(); testState.agentsConfig = undefined; testState.allowFrom = undefined; setRegistry(defaultRegistry); @@ -257,7 +257,7 @@ describe("gateway server agent", () => { }, }); let subordinateAdmissionClosed: boolean | undefined; - vi.mocked(agentCommand).mockImplementationOnce(async () => { + vi.mocked(agentCommandMock).mockImplementationOnce(async () => { const suspension = tryBeginGatewaySuspendAdmission(() => {}); expect(suspension).not.toBeNull(); try { @@ -455,7 +455,7 @@ describe("gateway server agent", () => { const executionFinished = new Promise((resolve) => { finishExecution = resolve; }); - vi.mocked(agentCommand).mockImplementationOnce(async (rawOpts) => { + vi.mocked(agentCommandMock).mockImplementationOnce(async (rawOpts) => { const opts = rawOpts as { runId: string; operationalRunInstance: OperationalRunInstanceRef; @@ -516,7 +516,7 @@ describe("gateway server agent", () => { expect(res.ok).toBe(false); expect(res.error?.message).toContain("unknown channel"); - const spy = vi.mocked(agentCommand); + const spy = vi.mocked(agentCommandMock); expect(spy).not.toHaveBeenCalled(); }); @@ -531,7 +531,7 @@ describe("gateway server agent", () => { expect(res.ok).toBe(false); expect(res.error?.message).toContain("does not match session key agent"); - const spy = vi.mocked(agentCommand); + const spy = vi.mocked(agentCommandMock); expect(spy).not.toHaveBeenCalled(); }); @@ -544,7 +544,7 @@ describe("gateway server agent", () => { expect(res.ok).toBe(false); expect(res.error?.message).toContain("malformed session key"); - const spy = vi.mocked(agentCommand); + const spy = vi.mocked(agentCommandMock); expect(spy).not.toHaveBeenCalled(); }); @@ -706,7 +706,7 @@ describe("gateway server agent", () => { expect(res.ok).toBe(false); expect(res.error?.code).toBe("INVALID_REQUEST"); expect(res.error?.message).toContain("Channel is required"); - expect(vi.mocked(agentCommand)).not.toHaveBeenCalled(); + expect(vi.mocked(agentCommandMock)).not.toHaveBeenCalled(); } finally { testState.allowFrom = undefined; } diff --git a/src/gateway/server.agent.gateway-server-agent-b.test.ts b/src/gateway/server.agent.gateway-server-agent-b.test.ts index 12ef2322ad95..95da6886c522 100644 --- a/src/gateway/server.agent.gateway-server-agent-b.test.ts +++ b/src/gateway/server.agent.gateway-server-agent-b.test.ts @@ -18,7 +18,7 @@ import { readAgentCommandCall } from "./agent-command.test-helpers.js"; import { setRegistry } from "./server.agent.gateway-server-agent.mocks.js"; import { createRegistry } from "./server.e2e-registry-helpers.js"; import { - agentCommand, + agentCommandMock, connectOk, connectWebchatClient, installGatewayTestHooks, @@ -190,7 +190,7 @@ afterAll(() => { describe("gateway server agent", () => { beforeEach(() => { - vi.mocked(agentCommand).mockClear(); + vi.mocked(agentCommandMock).mockClear(); testState.allowFrom = undefined; setRegistry(defaultRegistry); }); @@ -432,7 +432,7 @@ describe("gateway server agent", () => { expect(directReset.ok).toBe(false); expect(directReset.error?.message).toContain("missing scope: operator.admin"); - vi.mocked(agentCommand).mockClear(); + vi.mocked(agentCommandMock).mockClear(); const viaAgent = await rpcReq(writeWs, "agent", { message: "/reset", sessionKey: "main", @@ -451,7 +451,7 @@ describe("gateway server agent", () => { const stored = loadSessionEntry({ sessionKey: "agent:main:main", storePath }); expect(stored?.sessionId).toBe("sess-main-before-write-reset"); - expect(vi.mocked(agentCommand)).not.toHaveBeenCalled(); + expect(vi.mocked(agentCommandMock)).not.toHaveBeenCalled(); writeWs.close(); }); @@ -487,7 +487,7 @@ describe("gateway server agent", () => { test("agent final response surfaces redacted ACP runtime cause details", async () => { const token = "sk-abcdefghijklmnopqrstuvwxyz123456"; - vi.mocked(agentCommand).mockRejectedValueOnce( + vi.mocked(agentCommandMock).mockRejectedValueOnce( new AcpRuntimeError("ACP_TURN_FAILED", "Internal error", { cause: new Error(`upstream rejected token=${token}`), }), diff --git a/src/gateway/server.agent.rpc-contracts.test.ts b/src/gateway/server.agent.rpc-contracts.test.ts index 44932edb4fb8..e6fcc66332e8 100644 --- a/src/gateway/server.agent.rpc-contracts.test.ts +++ b/src/gateway/server.agent.rpc-contracts.test.ts @@ -4,7 +4,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from "vit import type { RawData, WebSocket } from "ws"; import { createDeferred } from "../../test/helpers/promise.js"; import { startGatewayServerHarness, type GatewayServerHarness } from "./server.e2e-ws-harness.js"; -import { agentCommand, installGatewayTestHooks, onceMessage } from "./test-helpers.js"; +import { agentCommandMock, installGatewayTestHooks, onceMessage } from "./test-helpers.js"; installGatewayTestHooks({ scope: "suite" }); @@ -33,7 +33,7 @@ beforeAll(async () => { }); beforeEach(() => { - vi.mocked(agentCommand).mockReset(); + vi.mocked(agentCommandMock).mockReset(); }); afterAll(async () => { @@ -65,7 +65,7 @@ function sendAgentRequest(params: { describe("gateway agent RPC contracts", () => { test("preserves requested delivery status across ordered final response and replay", async () => { const runCompletion = createDeferred(); - vi.mocked(agentCommand).mockImplementationOnce(async () => { + vi.mocked(agentCommandMock).mockImplementationOnce(async () => { await runCompletion.promise; return { payloads: [{ text: "assistant reply" }], @@ -115,8 +115,8 @@ describe("gateway agent RPC contracts", () => { }); await acceptedPromise; - await vi.waitFor(() => expect(agentCommand).toHaveBeenCalledTimes(1)); - expect(vi.mocked(agentCommand).mock.calls[0]?.[0]).toMatchObject({ + await vi.waitFor(() => expect(agentCommandMock).toHaveBeenCalledTimes(1)); + expect(vi.mocked(agentCommandMock).mock.calls[0]?.[0]).toMatchObject({ runId: idempotencyKey, channel: "webchat", messageChannel: "webchat", @@ -182,7 +182,7 @@ describe("gateway agent RPC contracts", () => { attempted: false, reason: "channel_resolved_to_internal", }); - expect(agentCommand).toHaveBeenCalledTimes(1); + expect(agentCommandMock).toHaveBeenCalledTimes(1); } finally { second.ws.close(); } diff --git a/src/gateway/server.auth.compat-baseline.test.ts b/src/gateway/server.auth.compat-baseline.test.ts index 4d240f2392b7..8d07fe1b5fe2 100644 --- a/src/gateway/server.auth.compat-baseline.test.ts +++ b/src/gateway/server.auth.compat-baseline.test.ts @@ -10,7 +10,7 @@ import { CONTROL_UI_CLIENT, ConnectErrorDetailCodes, createSignedDevice, - getFreePort, + getGatewayTestPort, GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, readConnectChallengeNonce, @@ -145,7 +145,7 @@ describe("gateway auth compatibility baseline", () => { prevToken = process.env.OPENCLAW_GATEWAY_TOKEN; testState.gatewayAuth = { mode: "token", token: "secret" }; process.env.OPENCLAW_GATEWAY_TOKEN = "secret"; - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port); }); @@ -307,7 +307,7 @@ describe("gateway auth compatibility baseline", () => { prevToken = process.env.OPENCLAW_GATEWAY_TOKEN; testState.gatewayAuth = { mode: "password", password: "secret" }; delete process.env.OPENCLAW_GATEWAY_TOKEN; - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port); }); @@ -364,7 +364,7 @@ describe("gateway auth compatibility baseline", () => { prevToken = process.env.OPENCLAW_GATEWAY_TOKEN; testState.gatewayAuth = { mode: "none" }; delete process.env.OPENCLAW_GATEWAY_TOKEN; - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { controlUiEnabled: true }); }); diff --git a/src/gateway/server.auth.default-token.suite.ts b/src/gateway/server.auth.default-token.suite.ts index 9dcc748d061e..4f78aeaf3290 100644 --- a/src/gateway/server.auth.default-token.suite.ts +++ b/src/gateway/server.auth.default-token.suite.ts @@ -12,7 +12,7 @@ import { ConnectErrorDetailCodes, createSignedDevice, expectHelloOkServerVersion, - getFreePort, + getGatewayTestPort, GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, MIN_PROBE_PROTOCOL_VERSION, @@ -38,7 +38,7 @@ export function registerDefaultAuthTokenSuite(): void { let port: number; beforeAll(async () => { - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port); }); diff --git a/src/gateway/server.auth.modes.suite.ts b/src/gateway/server.auth.modes.suite.ts index 9357193e8eac..16bd6088826a 100644 --- a/src/gateway/server.auth.modes.suite.ts +++ b/src/gateway/server.auth.modes.suite.ts @@ -5,7 +5,7 @@ import { connectReq, CONTROL_UI_CLIENT, ConnectErrorDetailCodes, - getFreePort, + getGatewayTestPort, openTailscaleWs, openWs, originForPort, @@ -31,7 +31,7 @@ export function registerAuthModesSuite(): void { beforeAll(async () => { testState.gatewayAuth = { mode: "password", password: "secret" }; // pragma: allowlist secret - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { openAiChatCompletionsEnabled: true }); }); @@ -89,7 +89,7 @@ export function registerAuthModesSuite(): void { prevToken = process.env.OPENCLAW_GATEWAY_TOKEN; process.env.OPENCLAW_GATEWAY_TOKEN = "secret"; testState.gatewayAuth = { mode: "token", token: "secret" }; - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { openAiChatCompletionsEnabled: true }); }); @@ -179,7 +179,7 @@ export function registerAuthModesSuite(): void { prevToken = process.env.OPENCLAW_GATEWAY_TOKEN; delete process.env.OPENCLAW_GATEWAY_TOKEN; testState.gatewayAuth = { mode: "none" }; - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port); }); @@ -224,7 +224,7 @@ export function registerAuthModesSuite(): void { ? { mode: "token" as const, token: "", allowTailscale: false } : { mode: "password" as const, password: "", allowTailscale: false }; testState.gatewayAuth = auth; - const port = await getFreePort(); + const port = await getGatewayTestPort(); try { await expect(startTestGatewayServer(port, { auth })).rejects.toThrow(testCase.expected); @@ -239,7 +239,7 @@ export function registerAuthModesSuite(): void { test("rejects non-loopback exposure without effective auth before listening", async () => { testState.gatewayAuth = { mode: "none" }; - const port = await getFreePort(); + const port = await getGatewayTestPort(); await expect( startTestGatewayServer(port, { @@ -272,7 +272,7 @@ export function registerAuthModesSuite(): void { }, afterWrite: { mode: "auto" }, }); - port = await getFreePort(); + port = await getGatewayTestPort(); server = await startTestGatewayServer(port); }); diff --git a/src/gateway/server.auth.test-helpers.ts b/src/gateway/server.auth.test-helpers.ts index 892543450514..27560042b91a 100644 --- a/src/gateway/server.auth.test-helpers.ts +++ b/src/gateway/server.auth.test-helpers.ts @@ -14,7 +14,7 @@ import { buildDeviceAuthPayload } from "./device-auth.js"; import { connectReq, getTrackedConnectChallengeNonce, - getFreePort, + getGatewayTestPort, installGatewayTestHooks, onceMessage, rpcReq, @@ -380,7 +380,7 @@ export { createSignedDevice, ensurePairedDeviceTokenForCurrentIdentity, expectHelloOkServerVersion, - getFreePort, + getGatewayTestPort, installGatewayTestHooks, MIN_PROBE_PROTOCOL_VERSION, NODE_CLIENT, diff --git a/src/gateway/server.board-generated-identity.test.ts b/src/gateway/server.board-generated-identity.test.ts index 457f235e9def..bfad635aa0af 100644 --- a/src/gateway/server.board-generated-identity.test.ts +++ b/src/gateway/server.board-generated-identity.test.ts @@ -11,7 +11,11 @@ import { import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js"; import { READ_SCOPE, WRITE_SCOPE } from "./method-scopes.js"; import { connectGatewayClient, disconnectGatewayClient } from "./test-helpers.e2e.js"; -import { getFreePort, installGatewayTestHooks, startTestGatewayServer } from "./test-helpers.js"; +import { + getGatewayTestPort, + installGatewayTestHooks, + startTestGatewayServer, +} from "./test-helpers.js"; installGatewayTestHooks({ scope: "suite" }); @@ -47,7 +51,7 @@ it( const events: string[] = []; const start = async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startTestGatewayServer(port, { auth: { mode: "none" }, bind: "loopback", diff --git a/src/gateway/server.chat.gateway-server-chat.test.ts b/src/gateway/server.chat.gateway-server-chat.test.ts index 0f73e453fd87..017ea60f7f88 100644 --- a/src/gateway/server.chat.gateway-server-chat.test.ts +++ b/src/gateway/server.chat.gateway-server-chat.test.ts @@ -33,7 +33,7 @@ import { withGatewayServer, writeSessionStore, } from "./test-helpers.js"; -import { agentCommand } from "./test-helpers.runtime-state.js"; +import { agentCommandMock } from "./test-helpers.runtime-state.js"; import { installConnectedControlUiServerSuite } from "./test-with-server.js"; function createGatewayHistoryText(role: "user" | "assistant", text: unknown, timestamp: number) { @@ -685,7 +685,7 @@ describe("gateway server chat", () => { }, }); - vi.mocked(agentCommand).mockClear(); + vi.mocked(agentCommandMock).mockClear(); const agentAllowedRes = await rpcReq(ws, "agent", { sessionKey: "cron:job-1", message: "hi", @@ -694,7 +694,7 @@ describe("gateway server chat", () => { expect(agentAllowedRes.ok).toBe(true); expect(agentAllowedRes.payload?.status).toBe("accepted"); expect(agentAllowedRes.payload?.runId).toBe("idem-2"); - await waitForFast(() => expect(agentCommand).toHaveBeenCalled()); + await waitForFast(() => expect(agentCommandMock).toHaveBeenCalled()); testState.sessionStorePath = undefined; testState.sessionConfig = undefined; @@ -1594,7 +1594,7 @@ describe("gateway server chat", () => { test("routes chat.send slash commands without agent runs", async () => { await withMainSessionStore(async () => { - const spy = vi.mocked(agentCommand); + const spy = vi.mocked(agentCommandMock); const callsBefore = spy.mock.calls.length; const eventPromise = onceMessage( ws, @@ -2115,7 +2115,7 @@ describe("gateway server chat", () => { const blockedAgentRun = new Promise((resolve) => { resolveAgentRun = resolve; }); - const agentSpy = vi.mocked(agentCommand); + const agentSpy = vi.mocked(agentCommandMock); agentSpy.mockImplementationOnce(async () => { await blockedAgentRun; return undefined; diff --git a/src/gateway/server.context-prewarm.remote-proof.test.ts b/src/gateway/server.context-prewarm.remote-proof.test.ts index 50d4c25d414b..a5b20ab40bde 100644 --- a/src/gateway/server.context-prewarm.remote-proof.test.ts +++ b/src/gateway/server.context-prewarm.remote-proof.test.ts @@ -11,7 +11,11 @@ import { resetContextWindowCacheForTest } from "../agents/context-runtime-state. import { resetPreparedModelRuntimeSnapshotsForTest } from "../agents/prepared-model-runtime.test-support.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { connectGatewayClient, disconnectGatewayClient } from "./test-helpers.e2e.js"; -import { getFreePort, installGatewayTestHooks, startTestGatewayServer } from "./test-helpers.js"; +import { + getGatewayTestPort, + installGatewayTestHooks, + startTestGatewayServer, +} from "./test-helpers.js"; installGatewayTestHooks(); @@ -33,7 +37,7 @@ describe("Gateway context cache remote proof", () => { contextWindow: baseWindow + (index % 17), maxTokens: 8_192, })); - const port = await getFreePort(); + const port = await getGatewayTestPort(); const token = "context-prewarm-proof-token"; const server = await startTestGatewayServer(port, { bind: "loopback", diff --git a/src/gateway/server.e2e-ws-harness.ts b/src/gateway/server.e2e-ws-harness.ts index b8aa09d74f41..14eb5ecb42ab 100644 --- a/src/gateway/server.e2e-ws-harness.ts +++ b/src/gateway/server.e2e-ws-harness.ts @@ -4,7 +4,7 @@ import { WebSocket } from "ws"; import { captureEnv } from "../test-utils/env.js"; import { connectOk, - getFreePort, + getGatewayTestPort, startTestGatewayServer, trackConnectChallengeNonce, } from "./test-helpers.js"; @@ -26,7 +26,7 @@ export async function startGatewayServerHarness(): Promise const envSnapshot = captureEnv(["OPENCLAW_GATEWAY_TOKEN"]); const clients = new Set(); delete process.env.OPENCLAW_GATEWAY_TOKEN; - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startTestGatewayServer(port, { auth: { mode: "none" }, bind: "loopback", diff --git a/src/gateway/server.minimal-channel-pin.test.ts b/src/gateway/server.minimal-channel-pin.test.ts index 53e5973bc90d..af4002f05c83 100644 --- a/src/gateway/server.minimal-channel-pin.test.ts +++ b/src/gateway/server.minimal-channel-pin.test.ts @@ -10,7 +10,11 @@ import { } from "../plugins/runtime.js"; import { createOutboundTestPlugin } from "../test-utils/channel-plugins.js"; import { createRegistry } from "./server.e2e-registry-helpers.js"; -import { getFreePort, installGatewayTestHooks, startTestGatewayServer } from "./test-helpers.js"; +import { + getGatewayTestPort, + installGatewayTestHooks, + startTestGatewayServer, +} from "./test-helpers.js"; installGatewayTestHooks({ scope: "suite" }); @@ -43,7 +47,7 @@ test("minimal gateway tracks later channel registry updates", async () => { const prevVitest = process.env.VITEST; resetPluginRuntimeStateForTest(); process.env.VITEST = "1"; - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startTestGatewayServer(port); try { expect(getChannelPlugin("whatsapp")).not.toBe(replacementPlugin); diff --git a/src/gateway/server.models-voicewake-misc.test.ts b/src/gateway/server.models-voicewake-misc.test.ts index d6b825d7d424..1e08f147724f 100644 --- a/src/gateway/server.models-voicewake-misc.test.ts +++ b/src/gateway/server.models-voicewake-misc.test.ts @@ -16,7 +16,7 @@ import { resetPreparedModelCatalogStateForTest } from "./server-model-catalog.js import { createRegistry } from "./server.e2e-registry-helpers.js"; import { connectOk, - getFreePort, + getGatewayTestPort, installGatewayTestHooks, onceMessage, agentDiscoveryMock, @@ -738,7 +738,7 @@ describe("gateway server misc", () => { }); test("releases port after close", async () => { - const releasePort = await getFreePort(); + const releasePort = await getGatewayTestPort(); const releaseServer = await startTestGatewayServer(releasePort); await releaseServer.close(); diff --git a/src/gateway/server.sessions-send.test.ts b/src/gateway/server.sessions-send.test.ts index 9ca85a1934c8..54b4b2429af3 100644 --- a/src/gateway/server.sessions-send.test.ts +++ b/src/gateway/server.sessions-send.test.ts @@ -16,8 +16,8 @@ import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/chan import { captureEnv } from "../test-utils/env.js"; import { runDirectSessionAnnounceScenario } from "./server.sessions-send.direct-announce.test-support.js"; import { - agentCommand, - getFreePort, + agentCommandMock, + getGatewayTestPort, installGatewayTestHooks, startTestGatewayServer, setTestPluginRegistry, @@ -118,7 +118,7 @@ async function emitLifecycleAssistantReply(params: { beforeAll(async () => { envSnapshot = captureEnv(["OPENCLAW_GATEWAY_PORT", "OPENCLAW_GATEWAY_TOKEN"]); - gatewayPort = await getFreePort(); + gatewayPort = await getGatewayTestPort(); const { approveDevicePairing, requestDevicePairing } = await import("../infra/device-pairing.js"); const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem } = await import("../infra/device-identity.js"); @@ -154,7 +154,7 @@ afterAll(async () => { describe("sessions_send gateway loopback", () => { it("returns reply when lifecycle ends before agent.wait", async () => { - const spy = agentCommand as unknown as Mock<(opts: unknown) => Promise>; + const spy = agentCommandMock as unknown as Mock<(opts: unknown) => Promise>; spy.mockImplementation(async (opts: unknown) => emitLifecycleAssistantReply({ opts, @@ -528,7 +528,7 @@ describe("sessions_send label lookup", () => { "utf-8", ); - const spy = agentCommand as unknown as Mock<(opts: unknown) => Promise>; + const spy = agentCommandMock as unknown as Mock<(opts: unknown) => Promise>; spy.mockImplementation(async (opts: unknown) => emitLifecycleAssistantReply({ opts, @@ -610,7 +610,7 @@ describe("sessions_send agent targeting", () => { }, }); - const spy = agentCommand as unknown as Mock<(opts: unknown) => Promise>; + const spy = agentCommandMock as unknown as Mock<(opts: unknown) => Promise>; spy.mockImplementation(async (opts: unknown) => emitLifecycleAssistantReply({ opts, @@ -749,7 +749,7 @@ describe("sessions_send direct-message requester routing", () => { }, }); - const spy = agentCommand as unknown as Mock<(opts: unknown) => Promise>; + const spy = agentCommandMock as unknown as Mock<(opts: unknown) => Promise>; spy.mockReset(); spy.mockImplementation(async (opts: unknown) => emitLifecycleAssistantReply({ diff --git a/src/gateway/server.sessions.create.test.ts b/src/gateway/server.sessions.create.test.ts index 1591a4a0fc21..e05a8b782eee 100644 --- a/src/gateway/server.sessions.create.test.ts +++ b/src/gateway/server.sessions.create.test.ts @@ -40,7 +40,7 @@ import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; import { resolveGatewaySessionStoreTarget } from "./session-utils.js"; import { - agentCommand, + agentCommandMock, agentDiscoveryMock, dispatchInboundMessageMock, embeddedRunMock, @@ -1168,7 +1168,7 @@ test("sessions.create provisions and reuses a session worktree for later runs", ), ).toHaveLength(1); - agentCommand.mockClear(); + agentCommandMock.mockClear(); const { ws } = await openClient(); const run = await rpcReq(ws, "agent", { message: "verify worktree cwd", @@ -1176,8 +1176,8 @@ test("sessions.create provisions and reuses a session worktree for later runs", idempotencyKey: "session-worktree-cwd", }); expect(run.ok, JSON.stringify(run)).toBe(true); - await waitForFast(() => expect(agentCommand).toHaveBeenCalled()); - expect(agentCommand.mock.calls.at(-1)?.[0]).toMatchObject({ + await waitForFast(() => expect(agentCommandMock).toHaveBeenCalled()); + expect(agentCommandMock.mock.calls.at(-1)?.[0]).toMatchObject({ cwd: worktree?.path, workspaceDir: worktree?.path, }); diff --git a/src/gateway/server.shared-auth-rotation.test.ts b/src/gateway/server.shared-auth-rotation.test.ts index d58d415b998a..059f149db33d 100644 --- a/src/gateway/server.shared-auth-rotation.test.ts +++ b/src/gateway/server.shared-auth-rotation.test.ts @@ -13,7 +13,7 @@ import { } from "./shared-auth.test-helpers.js"; import { connectOk, - getFreePort, + getGatewayTestPort, installGatewayTestHooks, rpcReq, startTestGatewayServer, @@ -257,7 +257,7 @@ describe("gateway shared auth rotation", () => { }; beforeAll(async () => { - port = await getFreePort(); + port = await getGatewayTestPort(); testState.gatewayAuth = { mode: "token", token: OLD_TOKEN }; server = await startTestGatewayServer(port, { controlUiEnabled: true }); @@ -338,7 +338,7 @@ describe("gateway shared auth rotation with unchanged SecretRefs", () => { if (!configPath) { throw new Error("OPENCLAW_CONFIG_PATH missing in gateway test environment"); } - secretRefPort = await getFreePort(); + secretRefPort = await getGatewayTestPort(); testState.gatewayAuth = undefined; setTestEnvValue(SECRET_REF_TOKEN_ID, OLD_TOKEN); await fs.mkdir(path.dirname(configPath), { recursive: true }); diff --git a/src/gateway/server.shared-token-hot-reload.test.ts b/src/gateway/server.shared-token-hot-reload.test.ts index 4a50112b66dd..db32bd987023 100644 --- a/src/gateway/server.shared-token-hot-reload.test.ts +++ b/src/gateway/server.shared-token-hot-reload.test.ts @@ -6,7 +6,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { deleteTestEnvValue, setTestEnvValue } from "../test-utils/env.js"; import { openAuthenticatedGatewayWs, waitForGatewayWsClose } from "./shared-auth.test-helpers.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, rpcReq, startTestGatewayServer, @@ -42,7 +42,7 @@ beforeAll(async () => { if (!configPath) { throw new Error("OPENCLAW_CONFIG_PATH missing in gateway test environment"); } - port = await getFreePort(); + port = await getGatewayTestPort(); testState.gatewayAuth = undefined; setTestEnvValue(SECRET_REF_TOKEN_ID, OLD_TOKEN); await fs.writeFile( diff --git a/src/gateway/server.shared-token-session-rotation.test.ts b/src/gateway/server.shared-token-session-rotation.test.ts index f748074a0d3d..740303cc0000 100644 --- a/src/gateway/server.shared-token-session-rotation.test.ts +++ b/src/gateway/server.shared-token-session-rotation.test.ts @@ -10,7 +10,7 @@ import { waitForGatewayWsClose, } from "./shared-auth.test-helpers.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, rpcReq, startTestGatewayServer, @@ -35,7 +35,7 @@ beforeAll(async () => { if (!configPath) { throw new Error("OPENCLAW_CONFIG_PATH missing in gateway test environment"); } - port = await getFreePort(); + port = await getGatewayTestPort(); testState.gatewayAuth = undefined; await fs.writeFile( configPath, diff --git a/src/gateway/server.startup-websocket-race.test.ts b/src/gateway/server.startup-websocket-race.test.ts index 2e0e5f7e7af8..d2a4cc2f223a 100644 --- a/src/gateway/server.startup-websocket-race.test.ts +++ b/src/gateway/server.startup-websocket-race.test.ts @@ -3,7 +3,11 @@ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { WebSocket } from "ws"; import { tryListenOnPort } from "../infra/ports-probe.js"; -import { getFreePort, installGatewayTestHooks, startTestGatewayServer } from "./test-helpers.js"; +import { + getGatewayTestPort, + installGatewayTestHooks, + startTestGatewayServer, +} from "./test-helpers.js"; import { createGatewayRuntimeStateForTest } from "./test-helpers.server-runtime-state.js"; type StartGatewayServer = typeof import("./test-helpers.js").startTestGatewayServer; @@ -80,7 +84,7 @@ describe("gateway startup websocket readiness", () => { let server: GatewayServerForTest | undefined; let client: WebSocket | undefined; try { - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { auth: { mode: "none" }, }); @@ -111,7 +115,7 @@ describe("gateway startup websocket readiness", () => { let server: GatewayServerForTest | undefined; const clients: WebSocket[] = []; try { - const port = await getFreePort(); + const port = await getGatewayTestPort(); server = await startTestGatewayServer(port, { host: "127.0.0.2", auth: { mode: "none" }, @@ -135,7 +139,7 @@ describe("gateway startup websocket readiness", () => { }); it("releases the loopback alias when the selected bind fails", async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); await expect( startTestGatewayServer(port, { diff --git a/src/gateway/server/tls.ts b/src/gateway/server/tls.ts deleted file mode 100644 index f7d5ea604807..000000000000 --- a/src/gateway/server/tls.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Gateway TLS boundary loads listener certificate material from gateway config. -import type { GatewayTlsConfig } from "../../config/types.gateway.js"; -import { - type GatewayTlsRuntime, - loadGatewayTlsRuntime as loadGatewayTlsRuntimeConfig, -} from "../../infra/tls/gateway.js"; - -/** - * Gateway TLS runtime loader boundary. - */ -export type { GatewayTlsRuntime } from "../../infra/tls/gateway.js"; - -/** Loads certificate/key material for the gateway listener from config. */ -export async function loadGatewayTlsRuntime( - cfg: GatewayTlsConfig | undefined, - log?: { info?: (msg: string) => void; warn?: (msg: string) => void }, -): Promise { - return await loadGatewayTlsRuntimeConfig(cfg, log); -} diff --git a/src/gateway/server/ws-connection/authenticated-request-dispatch.server.test.ts b/src/gateway/server/ws-connection/authenticated-request-dispatch.server.test.ts index 724c55dd926a..be2f2bf8a9f9 100644 --- a/src/gateway/server/ws-connection/authenticated-request-dispatch.server.test.ts +++ b/src/gateway/server/ws-connection/authenticated-request-dispatch.server.test.ts @@ -11,7 +11,7 @@ import { createEmptyPluginRegistry } from "../../../plugins/registry-empty.js"; import type { AgentRuntimeIdentity } from "../../agent-runtime-identity-token.js"; import { connectOk, - getFreePort, + getGatewayTestPort, installGatewayTestHooks, onceMessage, startTestGatewayServer, @@ -377,7 +377,7 @@ describe("authenticated WebSocket request trace dispatch", () => { setTestPluginRegistry(registry); const token = "gateway-request-trace-test-token"; - const port = await getFreePort(); + const port = await getGatewayTestPort(); const server = await startTestGatewayServer(port, { auth: { mode: "token", token }, bind: "loopback", diff --git a/src/gateway/session-transcript-readers.ts b/src/gateway/session-transcript-readers.ts index fc37294701a3..62fc8a1642db 100644 --- a/src/gateway/session-transcript-readers.ts +++ b/src/gateway/session-transcript-readers.ts @@ -234,7 +234,7 @@ export function extractMessageRole(message: unknown): string | undefined { : undefined; } -export function extractMessageText(message: unknown): string | null { +export function extractSessionTranscriptText(message: unknown): string | null { if (!message || typeof message !== "object" || Array.isArray(message)) { return null; } diff --git a/src/gateway/session-transcript-title-reader.ts b/src/gateway/session-transcript-title-reader.ts index f4531db5b3ce..583644d9aac8 100644 --- a/src/gateway/session-transcript-title-reader.ts +++ b/src/gateway/session-transcript-title-reader.ts @@ -13,7 +13,7 @@ import { pruneMapToMaxSize } from "../infra/map-size.js"; import { hasInterSessionUserProvenance } from "../sessions/input-provenance.js"; import { extractMessageRole, - extractMessageText, + extractSessionTranscriptText, resolveTranscriptReadTarget, sqliteMessageEventWithSeq, toTranscriptReadScope, @@ -96,8 +96,11 @@ function findFirstTitleUserMessage( function findLastMessageText(entries: readonly SessionTranscriptMessageEvent[]): string | null { return ( - entries.toReversed().map(sqliteMessageEventWithSeq).map(extractMessageText).find(Boolean) ?? - null + entries + .toReversed() + .map(sqliteMessageEventWithSeq) + .map(extractSessionTranscriptText) + .find(Boolean) ?? null ); } @@ -158,7 +161,7 @@ function readSqliteTitleFields( ); } fields = { - firstUserMessage: firstUser ? extractMessageText(firstUser) : null, + firstUserMessage: firstUser ? extractSessionTranscriptText(firstUser) : null, lastMessagePreview: lastText, }; } catch (error) { @@ -277,7 +280,7 @@ function readSessionTitleFieldsFromTranscriptBatchCurrent( continue; } const fields = { - firstUserMessage: firstUser ? extractMessageText(firstUser) : null, + firstUserMessage: firstUser ? extractSessionTranscriptText(firstUser) : null, lastMessagePreview: lastText, }; const fieldsByVariant = diff --git a/src/gateway/session-utils.fs.ts b/src/gateway/session-utils.fs.ts index 6d75fb256519..f778bbec1a1c 100644 --- a/src/gateway/session-utils.fs.ts +++ b/src/gateway/session-utils.fs.ts @@ -22,7 +22,7 @@ import { selectSessionTranscriptActiveEntries } from "../config/sessions/transcr import { readFileWindowFully } from "../infra/file-read.js"; import { jsonUtf8Bytes } from "../infra/json-utf8-bytes.js"; import { pruneMapToMaxSize } from "../infra/map-size.js"; -import { extractAssistantVisibleText } from "../shared/chat-message-content.js"; +import { extractAssistantPhaseText } from "../shared/chat-message-content.js"; import { truncateUtf16Safe } from "../utils.js"; import { estimateStringChars, estimateTokensFromChars } from "../utils/cjk-chars.js"; import { stripInlineDirectiveTagsForDisplay } from "../utils/directive-tags.js"; @@ -1327,7 +1327,7 @@ function truncatePreviewText(text: string, maxChars: number): string { function extractPreviewText(message: TranscriptPreviewMessage): string | null { const role = normalizeLowercaseStringOrEmpty(message.role); if (role === "assistant") { - const assistantText = extractAssistantVisibleText(message); + const assistantText = extractAssistantPhaseText(message); if (assistantText) { const normalized = stripInlineDirectiveTagsForDisplay(assistantText).text.trim(); return normalized ? normalized : null; diff --git a/src/gateway/test-helpers.mocks.ts b/src/gateway/test-helpers.mocks.ts index a3f6b52a483e..cf128386219b 100644 --- a/src/gateway/test-helpers.mocks.ts +++ b/src/gateway/test-helpers.mocks.ts @@ -5,7 +5,7 @@ import { vi } from "vitest"; import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js"; import { getTestPluginRegistry } from "./test-helpers.plugin-registry.js"; import { - agentCommand, + agentCommandMock, cronIsolatedRun, embeddedRunMock, type GetReplyFromConfigFn, @@ -249,9 +249,9 @@ vi.mock("../status/summary.js", () => ({ getStatusSummary: vi.fn().mockResolvedValue({ ok: true }), })); vi.mock("../commands/agent.js", () => ({ - agentCommand, - agentCommandFromGatewayIngress: agentCommand, - agentCommandFromIngress: agentCommand, + agentCommand: agentCommandMock, + agentCommandFromGatewayIngress: agentCommandMock, + agentCommandFromIngress: agentCommandMock, })); vi.mock("../agents/btw.js", () => ({ runBtwSideQuestion: (...args: Parameters) => diff --git a/src/gateway/test-helpers.runtime-state.ts b/src/gateway/test-helpers.runtime-state.ts index 9e0e8ed3f78e..259ad9f822f5 100644 --- a/src/gateway/test-helpers.runtime-state.ts +++ b/src/gateway/test-helpers.runtime-state.ts @@ -158,7 +158,7 @@ export const testTailnetIPv4 = gatewayTestHoisted.testTailnetIPv4; export const testTailscaleWhois = gatewayTestHoisted.testTailscaleWhois; export const agentDiscoveryMock = gatewayTestHoisted.agentDiscoveryMock; export const cronIsolatedRun = gatewayTestHoisted.cronIsolatedRun; -export const agentCommand = gatewayTestHoisted.agentCommand; +export const agentCommandMock = gatewayTestHoisted.agentCommand; export const dispatchInboundMessageMock = gatewayTestHoisted.dispatchInboundMessage; export const gatewayReplyMock = gatewayTestHoisted.getReplyFromConfig; export const mockGetReplyFromConfigOnce = (impl: GetReplyFromConfigFn) => { diff --git a/src/gateway/test-helpers.server.ts b/src/gateway/test-helpers.server.ts index 02793b98c175..a552a6d2bfb4 100644 --- a/src/gateway/test-helpers.server.ts +++ b/src/gateway/test-helpers.server.ts @@ -63,7 +63,7 @@ import { invalidateSessionSharingSnapshot } from "./session-sharing.js"; import { GATEWAY_STARTUP_MUTATED_ENV_KEYS } from "./test-helpers.env.js"; import { resetTestPluginRegistry } from "./test-helpers.plugin-registry.js"; import { - agentCommand, + agentCommandMock, cronIsolatedRun, embeddedRunMock, gatewayReplyMock, @@ -441,8 +441,8 @@ async function resetGatewayTestState(options: { uniqueConfigRoot: boolean }) { testIsNixMode.value = false; cronIsolatedRun.mockReset(); cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "ok" }); - agentCommand.mockReset(); - agentCommand.mockResolvedValue(undefined); + agentCommandMock.mockReset(); + agentCommandMock.mockResolvedValue(undefined); gatewayReplyMock.mockReset(); gatewayReplyMock.mockResolvedValue(undefined); sendWhatsAppMock.mockReset(); @@ -538,8 +538,8 @@ async function resetGatewayTestRuntimeOnly() { testIsNixMode.value = false; cronIsolatedRun.mockReset(); cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "ok" }); - agentCommand.mockReset(); - agentCommand.mockResolvedValue(undefined); + agentCommandMock.mockReset(); + agentCommandMock.mockResolvedValue(undefined); gatewayReplyMock.mockReset(); gatewayReplyMock.mockResolvedValue(undefined); sendWhatsAppMock.mockReset(); @@ -619,7 +619,7 @@ export function installGatewayTestHooks(options?: { scope?: "test" | "suite" }) }); } -export async function getFreePort(): Promise { +export async function getGatewayTestPort(): Promise { return await getDeterministicFreePortBlock({ offsets: [0, 1, 2, 3, 4] }); } @@ -754,7 +754,7 @@ export async function startGatewayServerWithRetries(params: { if (code !== "EADDRINUSE") { throw err; } - port = await getFreePort(); + port = await getGatewayTestPort(); } } throw new Error("failed to start gateway server after retries"); @@ -808,7 +808,7 @@ export async function withGatewayServer( opts?: { port?: number; serverOptions?: GatewayServerOptions }, ): Promise { const started = await startGatewayServerWithRetries({ - port: opts?.port ?? (await getFreePort()), + port: opts?.port ?? (await getGatewayTestPort()), opts: opts?.serverOptions, }); try { @@ -828,7 +828,7 @@ export async function createGatewaySuiteHarness(opts?: { close: () => Promise; }> { const started = await startGatewayServerWithRetries({ - port: opts?.port ?? (await getFreePort()), + port: opts?.port ?? (await getGatewayTestPort()), opts: opts?.serverOptions, }); return { @@ -847,7 +847,7 @@ export async function createGatewaySuiteHarness(opts?: { } export async function startServer(token?: string, opts?: GatewayServerOptions) { - let port = await getFreePort(); + let port = await getGatewayTestPort(); const envSnapshot = captureEnv(["OPENCLAW_GATEWAY_TOKEN"]); const prev = process.env.OPENCLAW_GATEWAY_TOKEN; if (typeof token === "string") { diff --git a/src/gateway/test-helpers.ts b/src/gateway/test-helpers.ts index 7c3f73544f09..da7843ea7074 100644 --- a/src/gateway/test-helpers.ts +++ b/src/gateway/test-helpers.ts @@ -2,7 +2,7 @@ * Public barrel for gateway integration test helpers. */ export { - agentCommand, + agentCommandMock, cronIsolatedRun, dispatchInboundMessageMock, embeddedRunMock, @@ -18,7 +18,7 @@ export { connectReq, connectWebchatClient, createGatewaySuiteHarness, - getFreePort, + getGatewayTestPort, getTrackedConnectChallengeNonce, installGatewayTestHooks, onceMessage, diff --git a/src/infra/exec-allow-always-persistence.test.ts b/src/infra/exec-allow-always-persistence.test.ts index 6517437eb0ec..b7c79ed6bad3 100644 --- a/src/infra/exec-allow-always-persistence.test.ts +++ b/src/infra/exec-allow-always-persistence.test.ts @@ -1,7 +1,11 @@ // Tests shared allow-always persistence decisions for command authorization plans. import { describe, expect, it } from "vitest"; import { resolveCommandResolutionFromArgv } from "./exec-approvals-analysis.js"; -import { makeExecutable, makePathEnv, makeTempDir } from "./exec-approvals-test-helpers.js"; +import { + makeExecutable, + makePathEnv, + makeExecApprovalsTempDir, +} from "./exec-approvals-test-helpers.js"; import { resolveAllowAlwaysPersistenceDecision, resolveExecApprovalAllowedDecisions, @@ -16,7 +20,7 @@ function plannedSegments(plan: Awaited describe("resolveAllowAlwaysPersistenceDecision", () => { it("chooses reusable patterns for allow-always planner candidates", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const gitPath = makeExecutable(dir, "git"); const env = makePathEnv(dir); const plan = await planShellAuthorization({ command: "git status", cwd: dir, env }); @@ -38,7 +42,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { }); it("persists package-manager exec approvals against the inner executable", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "pnpm"); const tsxPath = makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -62,7 +66,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { }); it("keeps pnpm cwd exec approvals one-shot", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "pnpm"); makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -87,7 +91,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["env --", "nice"])( "persists dispatch-wrapped package-manager exec approvals against the inner executable: %s", async (wrapper) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); for (const executable of ["env", "nice", "pnpm"]) { makeExecutable(dir, executable); } @@ -116,7 +120,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["--package=tsx", "--package tsx", "--workspace=a", "--workspace a", "--workspaces"])( "keeps npm workspace exec approvals one-shot: %s", async (workspaceOption) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "npm"); makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -140,7 +144,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { ); it("keeps npm cwd exec approvals one-shot", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "npm"); makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -170,7 +174,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { "exec tsx ./run.ts -C ./package", "x --workspaces --", ])("keeps npm post-subcommand context approvals one-shot: %s", async (npmExec) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "npm"); makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -193,7 +197,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { }); it("keeps pnpm dlx allow-build approvals one-shot", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "pnpm"); makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -218,7 +222,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["-C ./package", "--workspace-root", "-w"])( "keeps post-dlx pnpm context approvals one-shot: %s", async (contextOption) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "pnpm"); makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -244,7 +248,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["--allow-build=tsx", "--package=tsx", "--config ./npmrc"])( "keeps leading pnpm dlx context approvals one-shot: %s", async (contextOption) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "pnpm"); makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -268,7 +272,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { ); it("persists npm x approvals against the inner executable", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "npm"); const tsxPath = makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -292,7 +296,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { }); it("persists chained package-manager exec approvals against the final inner executable", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); for (const executable of ["pnpm", "npm"]) { makeExecutable(dir, executable); } @@ -320,7 +324,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["exec --", "dlx"])( "persists yarn %s approvals against the inner executable", async (subcommand) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "yarn"); const tsxPath = makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -345,7 +349,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { ); it("keeps package-manager shell carriers one-shot", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "pnpm"); makeExecutable(dir, "sh"); makeExecutable(dir, "echo"); @@ -375,7 +379,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["--workspace=a", "--workspace a", "--workspaces"])( "keeps npm workspace shell carriers one-shot: %s", async (workspaceOption) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); for (const executable of ["npm", "sh", "echo"]) { makeExecutable(dir, executable); } @@ -400,7 +404,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { ); it("keeps npm x shell carriers one-shot", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); for (const executable of ["npm", "sh", "echo"]) { makeExecutable(dir, executable); } @@ -424,7 +428,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { }); it("keeps chained package-manager shell carriers one-shot", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); for (const executable of ["pnpm", "npm", "sh", "echo"]) { makeExecutable(dir, executable); } @@ -450,7 +454,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["yarn run sh -c 'echo warmup-ok'", "yarn sh -c 'echo warmup-ok'"])( "keeps yarn script or bin fallback carriers one-shot: %s", async (command) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "yarn"); for (const executable of ["sh", "echo"]) { makeExecutable(dir, executable); @@ -477,7 +481,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { it.each(["env --", "nice"])( "keeps dispatch-wrapped package-manager shell carriers one-shot: %s", async (wrapper) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); for (const executable of ["env", "nice", "pnpm", "sh"]) { makeExecutable(dir, executable); } @@ -510,7 +514,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { ])( "keeps pnpm shell-mode exec approvals one-shot: $wrapper pnpm exec $flag", async ({ flag, wrapper }) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); for (const executable of ["env", "pnpm"]) { makeExecutable(dir, executable); } @@ -535,7 +539,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { ); it("keeps package-manager shell-call modes one-shot", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "npx"); const env = makePathEnv(dir); const command = "npx --call \"sh -c 'echo warmup-ok'\""; @@ -557,7 +561,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { }); it("keeps shell wrappers without reusable patterns one-shot", async () => { - const cwd = makeTempDir(); + const cwd = makeExecApprovalsTempDir(); const command = "sh -c './scripts/run.sh'"; const plan = await planShellAuthorization({ command, cwd }); @@ -649,7 +653,7 @@ describe("resolveAllowAlwaysPersistenceDecision", () => { }); it("keeps failed authorization plans one-shot even when fallback segments have patterns", async () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const env = makePathEnv(dir); makeExecutable(dir, "git"); const command = 'echo "$HOME"; git status'; diff --git a/src/infra/exec-approvals-allow-always.test.ts b/src/infra/exec-approvals-allow-always.test.ts index 398b3a333bbe..61b730dacdc6 100644 --- a/src/infra/exec-approvals-allow-always.test.ts +++ b/src/infra/exec-approvals-allow-always.test.ts @@ -8,7 +8,7 @@ import { makeMockCommandResolution, makeMockExecutableResolution, makePathEnv, - makeTempDir, + makeExecApprovalsTempDir, } from "./exec-approvals-test-helpers.js"; import { analyzeArgvCommand, @@ -95,7 +95,7 @@ describe("resolveAllowAlwaysPatterns", () => { } function createShellScriptFixture() { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const scriptsDir = path.join(dir, "scripts"); fs.mkdirSync(scriptsDir, { recursive: true }); const script = path.join(scriptsDir, "save_crystal.sh"); @@ -161,7 +161,7 @@ describe("resolveAllowAlwaysPatterns", () => { expectAllowlisted?: boolean; changedCommand?: string; }) { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const touch = makeExecutable(dir, "touch"); const env = { PATH: `${dir}${path.delimiter}${process.env.PATH ?? ""}` }; const safeBins = resolveSafeBins(undefined); @@ -282,7 +282,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const curl = makeExecutable(dir, "curl"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -345,7 +345,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const awk = makeExecutable(dir, "awk"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -377,7 +377,7 @@ describe("resolveAllowAlwaysPatterns", () => { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const bash = makeExecutable(dir, "bash"); const scriptsDir = path.join(dir, "scripts"); fs.mkdirSync(scriptsDir, { recursive: true }); @@ -490,7 +490,7 @@ describe("resolveAllowAlwaysPatterns", () => { ])( "persists allow-always patterns for $name", ({ argvPrefix, fileFlag, scriptArgs, expectedArgPattern }) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "env"); makeExecutable(dir, "pwsh"); const scriptPath = path.join(dir, "script.ps1"); @@ -528,7 +528,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "awk"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -547,7 +547,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "zsh"); const whoami = makeExecutable(dir, "whoami"); const { persisted } = await resolvePersistedPatterns({ @@ -563,7 +563,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "zsh"); const whoami = makeExecutable(dir, "whoami"); const ls = makeExecutable(dir, "ls"); @@ -646,7 +646,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const tool = makeExecutable(dir, "openclaw-ok"); makeExecutable(dir, "yash"); const env = { PATH: `${dir}${path.delimiter}${process.env.PATH ?? ""}` }; @@ -707,7 +707,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const touch = makeExecutable(dir, "touch"); makeExecutable(dir, "sh"); const env = makePathEnv(dir); @@ -763,7 +763,7 @@ describe("resolveAllowAlwaysPatterns", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "touch"); makeExecutable(dir, "sh"); const env = makePathEnv(dir); @@ -811,7 +811,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const touch = makeExecutable(dir, "touch"); const env = { PATH: `${dir}${path.delimiter}${process.env.PATH ?? ""}` }; const safeBins = resolveSafeBins(undefined); @@ -888,7 +888,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const patterns = resolveAllowAlwaysPatterns({ segments: [ { @@ -914,7 +914,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "nice"); makeExecutable(dir, "zsh"); makeExecutable(dir, "whoami"); @@ -931,7 +931,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "time"); makeExecutable(dir, "zsh"); makeExecutable(dir, "whoami"); @@ -948,7 +948,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "busybox"); makeExecutable(dir, "toybox"); makeExecutable(dir, "sh"); @@ -967,7 +967,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const busybox = makeExecutable(dir, "busybox"); const patterns = resolveAllowAlwaysPatterns({ segments: [ @@ -1014,7 +1014,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const busybox = makeExecutable(dir, "busybox"); const echo = makeExecutable(dir, "echo"); makeExecutable(dir, "id"); @@ -1035,7 +1035,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const shell = makeExecutable(dir, shellName); makeExecutable(dir, "id"); const env = makePathEnv(dir); @@ -1067,7 +1067,7 @@ $0 \\"$1\\"" touch {marker}`, ); it("prevents Windows fallback from allowlisting opaque shell inline payloads", () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const shell = makeExecutable(dir, "nu.exe"); const safeTool = makeExecutable(dir, "safe-tool.exe"); const env = makePathEnv(dir); @@ -1114,7 +1114,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const shell = makeExecutable(dir, "nu"); makeExecutable(dir, "id"); const env = makePathEnv(dir); @@ -1146,7 +1146,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const shell = makeExecutable(dir, "nu"); const config = path.join(dir, "allowed.nu"); fs.writeFileSync(config, ""); @@ -1176,7 +1176,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const shell = makeExecutable(dir, "xonsh"); const rcFile = path.join(dir, "allowed.xsh"); fs.writeFileSync(rcFile, ""); @@ -1206,7 +1206,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const shell = makeExecutable(dir, "nu"); const pluginList = path.join(dir, "allowed-plugins.nuon"); fs.writeFileSync(pluginList, ""); @@ -1249,7 +1249,7 @@ $0 \\"$1\\"" touch {marker}`, decoyName: "pipefail", }, ])("does not bind option values as shell script allowlist targets for $name", (testCase) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, testCase.argv[0] ?? "sh"); const script = path.join(dir, "run.sh"); fs.writeFileSync(script, "#!/bin/sh\necho ok\n"); @@ -1325,7 +1325,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const echo = makeExecutable(dir, "echo"); makeExecutable(dir, "id"); const env = makePathEnv(dir); @@ -1343,7 +1343,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "pnpm"); makeExecutable(dir, "sh"); const echo = makeExecutable(dir, "echo"); @@ -1364,7 +1364,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pnpmPath = makeExecutable(dir, "pnpm"); makeExecutable(dir, "sh"); makeExecutable(dir, "id"); @@ -1398,7 +1398,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const npmPath = makeExecutable(dir, "npm"); makeExecutable(dir, "sh"); makeExecutable(dir, "id"); @@ -1431,7 +1431,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pnpmPath = makeExecutable(dir, "pnpm"); makeExecutable(dir, "sh"); makeExecutable(dir, "id"); @@ -1463,7 +1463,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const npmPath = makeExecutable(dir, "npm"); makeExecutable(dir, "sh"); makeExecutable(dir, "id"); @@ -1495,7 +1495,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pnpmPath = makeExecutable(dir, "pnpm"); const npmPath = makeExecutable(dir, "npm"); makeExecutable(dir, "sh"); @@ -1531,7 +1531,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const yarnPath = makeExecutable(dir, "yarn"); makeExecutable(dir, "sh"); makeExecutable(dir, "id"); @@ -1571,7 +1571,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const executablePath = makeExecutable(dir, executable); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -1611,7 +1611,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pnpmPath = makeExecutable(dir, "pnpm"); makeExecutable(dir, "eslint"); const env = makePathEnv(dir); @@ -1642,7 +1642,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pnpmPath = makeExecutable(dir, "pnpm"); makeExecutable(dir, "eslint"); const env = makePathEnv(dir); @@ -1675,7 +1675,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const yarnPath = makeExecutable(dir, "yarn"); makeExecutable(dir, "eslint"); const env = makePathEnv(dir); @@ -1840,7 +1840,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pnpmPath = makeExecutable(dir, "pnpm"); const tsxPath = makeExecutable(dir, "tsx"); const env = makePathEnv(dir); @@ -2006,7 +2006,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const echo = makeExecutable(dir, "echo"); makeExecutable(dir, "id"); const env = makePathEnv(dir); @@ -2025,7 +2025,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "command"); const echo = makeExecutable(dir, "echo"); makeExecutable(dir, "id"); @@ -2043,7 +2043,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "command"); const echo = makeExecutable(dir, "echo"); const env = makePathEnv(dir); @@ -2072,7 +2072,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "echo"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -2089,7 +2089,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform !== "darwin") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const echo = makeExecutable(dir, "echo"); makeExecutable(dir, "id"); const env = makePathEnv(dir); @@ -2115,7 +2115,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "awk"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -2319,7 +2319,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, executable); const env = makePathEnv(dir); const marker = path.join(dir, `${executable}-marker`); @@ -2338,7 +2338,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "awk"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -2366,7 +2366,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform !== "darwin" && process.platform !== "freebsd") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); makeExecutable(dir, "echo"); makeExecutable(dir, "id"); const env = makePathEnv(dir); @@ -2402,7 +2402,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const benign = makeExecutable(dir, "benign"); makeExecutable(dir, "payload"); const env = makePathEnv(dir); @@ -2419,7 +2419,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const envPath = makeExecutable(dir, "env"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -2447,7 +2447,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const bashPath = makeExecutable(dir, "bash"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); @@ -2475,7 +2475,7 @@ $0 \\"$1\\"" touch {marker}`, if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const xargsPath = makeExecutable(dir, "xargs"); const env = makePathEnv(dir); const safeBins = resolveSafeBins(undefined); diff --git a/src/infra/exec-approvals-analysis.test.ts b/src/infra/exec-approvals-analysis.test.ts index 5357c427cba0..d9ba731ae496 100644 --- a/src/infra/exec-approvals-analysis.test.ts +++ b/src/infra/exec-approvals-analysis.test.ts @@ -10,7 +10,11 @@ import { resolvePlannedSegmentArgv, windowsEscapeArg, } from "./exec-approvals-analysis.js"; -import { makeExecutable, makePathEnv, makeTempDir } from "./exec-approvals-test-helpers.js"; +import { + makeExecutable, + makePathEnv, + makeExecApprovalsTempDir, +} from "./exec-approvals-test-helpers.js"; describe("exec argv analysis", () => { it("parses argv commands", () => { @@ -32,7 +36,7 @@ describe("exec argv analysis", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const busybox = path.join(dir, "busybox"); fs.writeFileSync(busybox, ""); fs.chmodSync(busybox, 0o755); @@ -191,7 +195,7 @@ describe("Windows enforced shell command rendering", () => { // Resolve from a fixture PATH: host python3 locations (e.g. Homebrew's // python@3.x Cellar path) contain characters that trigger quoting and make // the rendered command host-dependent. - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const python3 = makeExecutable(dir, "python3"); const analysis = analyzeWindowsShellCommand({ command: "python3 a.py", @@ -254,7 +258,7 @@ describe("windowsEscapeArg", () => { describe("Windows inline allowlist analysis", () => { it("evaluates inline cmd payloads against the inner executable", () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const cmdPath = path.join(dir, "cmd.exe"); const nodePath = path.join(dir, "node.exe"); for (const file of [cmdPath, nodePath]) { diff --git a/src/infra/exec-approvals-config.test.ts b/src/infra/exec-approvals-config.test.ts index 6fb2383425c1..ed90840d833a 100644 --- a/src/infra/exec-approvals-config.test.ts +++ b/src/infra/exec-approvals-config.test.ts @@ -2,7 +2,7 @@ import { existsSync } from "node:fs"; import { describe, expect, it } from "vitest"; import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js"; -import { makeTempDir } from "./exec-approvals-test-helpers.js"; +import { makeExecApprovalsTempDir } from "./exec-approvals-test-helpers.js"; import { isSafeBinUsage, matchAllowlist, @@ -20,7 +20,7 @@ describe.sequential("exec approval temp fixture cleanup", () => { let cleanupProbeRoot = ""; it("creates a disposable fixture root", () => { - cleanupProbeRoot = makeTempDir(); + cleanupProbeRoot = makeExecApprovalsTempDir(); expect(existsSync(cleanupProbeRoot)).toBe(true); }); @@ -31,7 +31,7 @@ describe.sequential("exec approval temp fixture cleanup", () => { describe("exec approvals wildcard agent", () => { it("merges wildcard allowlist entries with agent entries", () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const prevOpenClawHome = process.env.OPENCLAW_HOME; try { diff --git a/src/infra/exec-approvals-safe-bins.test.ts b/src/infra/exec-approvals-safe-bins.test.ts index c7b391336081..2da03142f26d 100644 --- a/src/infra/exec-approvals-safe-bins.test.ts +++ b/src/infra/exec-approvals-safe-bins.test.ts @@ -6,7 +6,7 @@ import { makeMockCommandResolution, makeMockExecutableResolution, makePathEnv, - makeTempDir, + makeExecApprovalsTempDir, } from "./exec-approvals-test-helpers.js"; import { evaluateExecAllowlist, @@ -386,7 +386,7 @@ describe("exec approvals safe bins", () => { ]; it.runIf(process.platform !== "win32").each(cases)("$name", (testCase) => { - const cwd = testCase.cwd ?? makeTempDir(); + const cwd = testCase.cwd ?? makeExecApprovalsTempDir(); testCase.setup?.(cwd); const executableName = testCase.executableName ?? "jq"; const rawExecutable = testCase.rawExecutable ?? executableName; @@ -562,7 +562,7 @@ describe("exec approvals safe bins", () => { if (process.platform === "win32") { return; } - const cwd = makeTempDir(); + const cwd = makeExecApprovalsTempDir(); fs.writeFileSync(path.join(cwd, "existing.txt"), "x"); const resolution = { rawExecutable: "sort", @@ -633,7 +633,7 @@ describe("exec approvals safe bins", () => { if (process.platform === "win32") { return; } - const tmp = makeTempDir(); + const tmp = makeExecApprovalsTempDir(); const fakeDir = path.join(tmp, "fake-bin"); fs.mkdirSync(fakeDir, { recursive: true }); const fakeHead = path.join(fakeDir, "head"); diff --git a/src/infra/exec-approvals-test-helpers.ts b/src/infra/exec-approvals-test-helpers.ts index 14b92ec6caaf..23da165fdaf4 100644 --- a/src/infra/exec-approvals-test-helpers.ts +++ b/src/infra/exec-approvals-test-helpers.ts @@ -22,7 +22,7 @@ export function makePathEnv(binDir: string): NodeJS.ProcessEnv { } /** Create a real temp directory for exec-approval tests that need filesystem paths. */ -export function makeTempDir(): string { +export function makeExecApprovalsTempDir(): string { const tempDir = fs.realpathSync( fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-exec-approvals-")), ); diff --git a/src/infra/exec-authorization-allowlist.test.ts b/src/infra/exec-authorization-allowlist.test.ts index 30b7aaed139f..6b33282cc59c 100644 --- a/src/infra/exec-authorization-allowlist.test.ts +++ b/src/infra/exec-authorization-allowlist.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vitest"; import { detectPolicyInlineEval } from "./command-analysis/policy.js"; -import { makeExecutable, makePathEnv, makeTempDir } from "./exec-approvals-test-helpers.js"; +import { + makeExecutable, + makePathEnv, + makeExecApprovalsTempDir, +} from "./exec-approvals-test-helpers.js"; import { evaluateShellAllowlistWithAuthorization, requiresExecApproval, @@ -66,7 +70,7 @@ describe("authorization-backed exec allowlist", () => { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pythonPath = makeExecutable(dir, "python3"); const env = makePathEnv(dir); const command = "python3 -c 'print(1)'"; @@ -115,7 +119,7 @@ describe("authorization-backed exec allowlist", () => { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pythonPath = makeExecutable(dir, "python3"); const env = makePathEnv(dir); const command = "python3 -xcprint"; diff --git a/src/infra/exec-authorization-render.test.ts b/src/infra/exec-authorization-render.test.ts index 02ac3df7e18a..2321649f94fb 100644 --- a/src/infra/exec-authorization-render.test.ts +++ b/src/infra/exec-authorization-render.test.ts @@ -1,7 +1,11 @@ import fs from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { makeExecutable, makePathEnv, makeTempDir } from "./exec-approvals-test-helpers.js"; +import { + makeExecutable, + makePathEnv, + makeExecApprovalsTempDir, +} from "./exec-approvals-test-helpers.js"; import { planShellAuthorization } from "./exec-authorization-plan.js"; import { buildAuthorizedShellCommandFromPlan } from "./exec-authorization-render.js"; @@ -256,7 +260,7 @@ describe("exec authorization renderer", () => { }); it("fails closed when shell-wrapper safe-bin rewrites would need outer quote escaping", async () => { - const dir = path.join(makeTempDir(), "safe bin dir"); + const dir = path.join(makeExecApprovalsTempDir(), "safe bin dir"); fs.mkdirSync(dir); makeExecutable(dir, "head"); const plan = await planShellAuthorization({ diff --git a/src/infra/exec-command-resolution.test.ts b/src/infra/exec-command-resolution.test.ts index 79da75d7b201..287d30df8a90 100644 --- a/src/infra/exec-command-resolution.test.ts +++ b/src/infra/exec-command-resolution.test.ts @@ -3,7 +3,11 @@ import fs from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { withMockedPlatform } from "../test-utils/vitest-spies.js"; -import { makeExecutable, makePathEnv, makeTempDir } from "./exec-approvals-test-helpers.js"; +import { + makeExecutable, + makePathEnv, + makeExecApprovalsTempDir, +} from "./exec-approvals-test-helpers.js"; import { evaluateExecAllowlist, resolvePlannedSegmentArgv, @@ -56,7 +60,7 @@ function createPathExecutableFixture(params?: { executable?: string }): { exePath: string; binDir: string; } { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const binDir = path.join(dir, "bin"); fs.mkdirSync(binDir, { recursive: true }); const baseName = params?.executable ?? "rg"; @@ -118,7 +122,7 @@ describe("exec-command-resolution", () => { { name: "relative executable", setup: (): CommandResolutionFixture => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const cwd = path.join(dir, "project"); const scriptName = process.platform === "win32" ? "run.cmd" : "run.sh"; const script = path.join(cwd, "scripts", scriptName); @@ -136,7 +140,7 @@ describe("exec-command-resolution", () => { { name: "quoted executable", setup: (): CommandResolutionFixture => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const cwd = path.join(dir, "project"); const scriptName = process.platform === "win32" ? "tool.cmd" : "tool"; const script = path.join(cwd, "bin", scriptName); @@ -221,7 +225,7 @@ describe("exec-command-resolution", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const busybox = path.join(dir, "busybox"); fs.writeFileSync(busybox, ""); fs.chmodSync(busybox, 0o755); @@ -269,7 +273,7 @@ describe("exec-command-resolution", () => { if (process.platform === "win32") { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const busybox = path.join(dir, "busybox"); fs.writeFileSync(busybox, ""); fs.chmodSync(busybox, 0o755); @@ -312,7 +316,7 @@ describe("exec-command-resolution", () => { return; } - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const binDir = path.join(dir, "bin"); fs.mkdirSync(binDir, { recursive: true }); const envPath = path.join(binDir, "env"); @@ -371,7 +375,7 @@ describe("exec-command-resolution", () => { "watch", "xvfb-run", ])("blocks opaque dispatch wrapper allowlist matches: %s", (wrapperName) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const wrapperPath = makeExecutable(dir, wrapperName); const pythonPath = makeExecutable(dir, "python3"); const env = makePathEnv(dir); @@ -480,7 +484,7 @@ describe("exec-command-resolution", () => { ] as const)( "keeps execution and policy targets coherent across wrapper classes: $name", (testCase) => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const binDir = path.join(dir, "bin"); fs.mkdirSync(binDir, { recursive: true }); const envPath = path.join(binDir, "env"); @@ -523,7 +527,7 @@ describe("exec-command-resolution", () => { ); it("keeps package-manager exec as the planned execution argv", () => { - const dir = makeTempDir(); + const dir = makeExecApprovalsTempDir(); const pnpmPath = makeExecutable(dir, "pnpm"); makeExecutable(dir, "eslint"); const env = makePathEnv(dir); diff --git a/src/plugin-sdk/session-transcript-runtime.ts b/src/plugin-sdk/session-transcript-runtime.ts index 1237237f287b..466214a1dc50 100644 --- a/src/plugin-sdk/session-transcript-runtime.ts +++ b/src/plugin-sdk/session-transcript-runtime.ts @@ -39,7 +39,7 @@ export type { } from "../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { normalizeAgentId } from "../routing/session-key.js"; -import { extractAssistantVisibleText } from "../shared/chat-message-content.js"; +import { extractAssistantPhaseText } from "../shared/chat-message-content.js"; import type { AgentMessage } from "./agent-core.js"; import { withProjectedSessionTranscriptWriteLock } from "./session-transcript-lock-runtime.js"; import { @@ -518,7 +518,7 @@ function extractAssistantMirrorComparableText( message as Parameters[0], config, ) as SessionTranscriptAssistantMessage; - return extractAssistantVisibleText(redacted)?.trim() || undefined; + return extractAssistantPhaseText(redacted)?.trim() || undefined; } function isDeliveryMirrorAssistantMessage(message: SessionTranscriptAssistantMessage): boolean { diff --git a/src/plugins/contracts/plugin-sdk-index.bundle.test.ts b/src/plugins/contracts/plugin-sdk-index.bundle.test.ts index ed6f0f167567..097a1c2ae466 100644 --- a/src/plugins/contracts/plugin-sdk-index.bundle.test.ts +++ b/src/plugins/contracts/plugin-sdk-index.bundle.test.ts @@ -5,13 +5,13 @@ import path from "node:path"; import { pathToFileURL } from "node:url"; import { afterAll, describe, expect, it } from "vitest"; import { buildPluginSdkEntrySources, pluginSdkEntrypoints } from "../../plugin-sdk/entrypoints.js"; -import { createSuiteTempRootTracker } from "../test-helpers/fs-fixtures.js"; +import { createSyncSuiteTempRootTracker } from "../test-helpers/fs-fixtures.js"; import { resolveBundledPluginFile } from "./test-helpers/bundled-plugin-roots.js"; const require = createRequire(import.meta.url); const tsdownModuleUrl = pathToFileURL(require.resolve("tsdown")).href; const bundledRepresentativeEntrypoints = ["browser-config"] as const; -const bundleTempRootTracker = createSuiteTempRootTracker( +const bundleTempRootTracker = createSyncSuiteTempRootTracker( "openclaw-plugin-sdk-build", path.join(process.cwd(), "node_modules", ".cache"), ); diff --git a/src/plugins/contracts/scheduled-turns.contract.test.ts b/src/plugins/contracts/scheduled-turns.contract.test.ts index 6b55685c47bd..48fb0dbf4169 100644 --- a/src/plugins/contracts/scheduled-turns.contract.test.ts +++ b/src/plugins/contracts/scheduled-turns.contract.test.ts @@ -23,7 +23,11 @@ import { unschedulePluginSessionTurnsByTag, } from "../host-hook-scheduled-turns.js"; import { loadOpenClawPlugins } from "../loader.js"; -import { clearPluginLoaderCache, makeTempDir, writePlugin } from "../loader.test-fixtures.js"; +import { + clearPluginLoaderCache, + makePluginLoaderTempDir, + writePlugin, +} from "../loader.test-fixtures.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../runtime.js"; import { createPluginRecord } from "../status.test-helpers.js"; import type { OpenClawPluginApi } from "../types.js"; @@ -587,7 +591,7 @@ describe("plugin scheduled turns", () => { }); it("allows bundled plugins to schedule turns during real plugin registration", async () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); writePlugin({ id: "loader-scheduler", dir: bundledDir, @@ -661,7 +665,7 @@ describe("plugin scheduled turns", () => { }); it("keeps late scheduled-turn helpers callable from real plugin gateway handlers", async () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); writePlugin({ id: "loader-scheduler-runtime", dir: bundledDir, diff --git a/src/plugins/install-persistence.test.ts b/src/plugins/install-persistence.test.ts index a25f9cd0ef76..043373692030 100644 --- a/src/plugins/install-persistence.test.ts +++ b/src/plugins/install-persistence.test.ts @@ -4,10 +4,10 @@ import os from "node:os"; import path from "node:path"; import { beforeEach, describe, expect, it } from "vitest"; import { - applyExclusiveSlotSelection, - buildPluginDiagnosticsReport, - buildPluginSnapshotReport, - clearPluginRegistryLoadCache, + applyExclusiveSlotSelectionMock, + buildPluginDiagnosticsReportMock, + buildPluginSnapshotReportMock, + clearPluginRegistryLoadCacheMock, enablePluginInConfigMock, loadPluginManifestRegistry, planPluginUninstall, @@ -18,7 +18,7 @@ import { setInstalledPluginIndexInstallRecords, writeConfigFile, writePersistedInstalledPluginIndexInstallRecordsWithLease, - applyPluginUninstallDirectoryRemoval, + applyPluginUninstallDirectoryRemovalMock, } from "../cli/plugins-cli-test-helpers.js"; import type { OpenClawConfig } from "../config/config.js"; import { hasRetainedManagedNpmInstallMarker } from "./managed-npm-retention.js"; @@ -162,7 +162,7 @@ describe("persistPluginInstall", () => { installPath: "/tmp/alpha", installedAt: "2026-04-25T00:00:00.000Z", }); - expect(clearPluginRegistryLoadCache).toHaveBeenCalledTimes(1); + expect(clearPluginRegistryLoadCacheMock).toHaveBeenCalledTimes(1); }); it("persists installs even when runtime cache invalidation fails", async () => { @@ -180,7 +180,7 @@ describe("persistPluginInstall", () => { }, } as OpenClawConfig; enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); - clearPluginRegistryLoadCache.mockImplementation(() => { + clearPluginRegistryLoadCacheMock.mockImplementation(() => { throw new Error("cache unavailable"); }); @@ -244,7 +244,7 @@ describe("persistPluginInstall", () => { target: "/tmp/openclaw/extensions/codex", }, }); - applyPluginUninstallDirectoryRemoval.mockResolvedValueOnce({ + applyPluginUninstallDirectoryRemovalMock.mockResolvedValueOnce({ directoryRemoved: true, warnings: [], }); @@ -278,11 +278,12 @@ describe("persistPluginInstall", () => { pluginId: "codex", deleteFiles: true, }); - expect(applyPluginUninstallDirectoryRemoval).toHaveBeenCalledWith({ + expect(applyPluginUninstallDirectoryRemovalMock).toHaveBeenCalledWith({ target: "/tmp/openclaw/extensions/codex", }); const cleanupOrder = - applyPluginUninstallDirectoryRemoval.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER; + applyPluginUninstallDirectoryRemovalMock.mock.invocationCallOrder[0] ?? + Number.MAX_SAFE_INTEGER; const refreshOrder = refreshPluginRegistry.mock.invocationCallOrder[0] ?? 0; expect(cleanupOrder).toBeLessThan(refreshOrder); expect(pluginsCliRuntimeLogs.join("\n")).toContain( @@ -328,7 +329,7 @@ describe("persistPluginInstall", () => { }); expect(planPluginUninstall).not.toHaveBeenCalled(); - expect(applyPluginUninstallDirectoryRemoval).not.toHaveBeenCalled(); + expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); }); it("preserves replaced npm install directories across generation updates", async () => { @@ -426,7 +427,7 @@ describe("persistPluginInstall", () => { pluginId: "codex", deleteFiles: true, }); - expect(applyPluginUninstallDirectoryRemoval).not.toHaveBeenCalled(); + expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); expect(hasRetainedManagedNpmInstallMarker(previousInstallPath)).toBe(true); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); @@ -448,7 +449,7 @@ describe("persistPluginInstall", () => { }, } as OpenClawConfig; enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ { id: "discord", @@ -475,7 +476,7 @@ describe("persistPluginInstall", () => { }); expect(next).toEqual(enabledConfig); - expect(buildPluginSnapshotReport).toHaveBeenCalledWith({ + expect(buildPluginSnapshotReportMock).toHaveBeenCalledWith({ config: enabledConfig, effectiveOnly: true, onlyPluginIds: ["discord"], @@ -507,7 +508,7 @@ describe("persistPluginInstall", () => { }, } as OpenClawConfig; enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ { id: "discord", @@ -569,7 +570,7 @@ describe("persistPluginInstall", () => { expect(next).toEqual(enabledConfig); expect(refreshPluginRegistry).toHaveBeenCalledTimes(1); - expect(clearPluginRegistryLoadCache).toHaveBeenCalledTimes(1); + expect(clearPluginRegistryLoadCacheMock).toHaveBeenCalledTimes(1); expectRuntimeLogIncludes("Plugin registry refresh failed"); }); @@ -606,7 +607,7 @@ describe("persistPluginInstall", () => { expect(next).toEqual(enabledConfig); expect(refreshPluginRegistry).toHaveBeenCalledTimes(1); - expect(clearPluginRegistryLoadCache).not.toHaveBeenCalled(); + expect(clearPluginRegistryLoadCacheMock).not.toHaveBeenCalled(); }); it("removes stale denylist entries before enabling installed plugins", async () => { @@ -670,11 +671,11 @@ describe("persistPluginInstall", () => { plugins: [createManifestRecord("legacy-memory")], diagnostics: [], }); - buildPluginDiagnosticsReport.mockReturnValueOnce({ + buildPluginDiagnosticsReportMock.mockReturnValueOnce({ plugins: [{ id: "legacy-memory", kind: "memory" }], diagnostics: [], }); - applyExclusiveSlotSelection.mockImplementation(((params: { + applyExclusiveSlotSelectionMock.mockImplementation(((params: { config: OpenClawConfig; selectedId: string; selectedKind?: string; @@ -713,8 +714,8 @@ describe("persistPluginInstall", () => { }, }); - expect(buildPluginDiagnosticsReport).toHaveBeenCalledTimes(1); - expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ + expect(buildPluginDiagnosticsReportMock).toHaveBeenCalledTimes(1); + expect(buildPluginDiagnosticsReportMock).toHaveBeenCalledWith({ config: enabledConfig, onlyPluginIds: ["legacy-memory"], }); @@ -747,7 +748,7 @@ describe("persistPluginInstall", () => { plugins: [createManifestRecord("memory-b", { kind: "memory" })], diagnostics: [], }); - applyExclusiveSlotSelection.mockImplementation(((params: { + applyExclusiveSlotSelectionMock.mockImplementation(((params: { config: OpenClawConfig; selectedId: string; selectedKind?: string; @@ -786,7 +787,7 @@ describe("persistPluginInstall", () => { }, }); - expect(buildPluginDiagnosticsReport).not.toHaveBeenCalled(); + expect(buildPluginDiagnosticsReportMock).not.toHaveBeenCalled(); expect( requireMockCallArg(loadPluginManifestRegistry, "loadPluginManifestRegistry", 1).config, ).toBe(enabledConfig); @@ -813,11 +814,11 @@ describe("persistPluginInstall", () => { plugins: [createManifestRecord("plain")], diagnostics: [], }); - buildPluginDiagnosticsReport.mockReturnValue({ + buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [{ id: "plain" }], diagnostics: [], }); - applyExclusiveSlotSelection.mockReturnValue({ + applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledConfig, warnings: [], changed: false, @@ -837,8 +838,8 @@ describe("persistPluginInstall", () => { }, }); - expect(buildPluginDiagnosticsReport).toHaveBeenCalledTimes(1); - expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ + expect(buildPluginDiagnosticsReportMock).toHaveBeenCalledTimes(1); + expect(buildPluginDiagnosticsReportMock).toHaveBeenCalledWith({ config: enabledConfig, onlyPluginIds: ["plain"], }); @@ -897,7 +898,7 @@ describe("persistPluginInstall", () => { }, }); expect(enablePluginInConfigMock).not.toHaveBeenCalled(); - expect(applyExclusiveSlotSelection).not.toHaveBeenCalled(); + expect(applyExclusiveSlotSelectionMock).not.toHaveBeenCalled(); expectRuntimeLogIncludes( 'Installed plugin "needs-config" without enabling it because it requires configuration first.', ); @@ -988,7 +989,7 @@ describe("persistPluginInstall", () => { expect(next).toEqual(baseConfig); expect(enablePluginInConfigMock).not.toHaveBeenCalled(); - expect(applyExclusiveSlotSelection).not.toHaveBeenCalled(); + expect(applyExclusiveSlotSelectionMock).not.toHaveBeenCalled(); const persistedRecords = requireMockCallArg( writePersistedInstalledPluginIndexInstallRecordsWithLease, "writePersistedInstalledPluginIndexInstallRecordsWithLease", diff --git a/src/plugins/install-persistence.ts b/src/plugins/install-persistence.ts index 08387c8bc1fa..58d64a93baa4 100644 --- a/src/plugins/install-persistence.ts +++ b/src/plugins/install-persistence.ts @@ -9,6 +9,7 @@ import { resolveConfigIncludeWritePath, } from "../config/includes.js"; import type { ConfigWriteOptions } from "../config/io.js"; +import { containsConfigIncludeDirective } from "../config/io.read-helpers.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { PluginInstallRecord } from "../config/types.plugins.js"; import { isPathInside } from "../infra/path-guards.js"; @@ -94,19 +95,6 @@ export type ConfigMutationPreflight = const CONFIG_MUTATION_ALLOWED = { mode: "allowed" } as const; -export function containsConfigIncludeDirective(value: unknown): boolean { - if (Array.isArray(value)) { - return value.some((entry) => containsConfigIncludeDirective(entry)); - } - if (!isRecord(value)) { - return false; - } - return ( - Object.hasOwn(value, "$include") || - Object.values(value).some((entry) => containsConfigIncludeDirective(entry)) - ); -} - export function supportsInstallConfigSingleTopLevelIncludeShape(authoredSection: unknown): boolean { if (!containsConfigIncludeDirective(authoredSection)) { return true; diff --git a/src/plugins/install-persistence.warning-sink.test.ts b/src/plugins/install-persistence.warning-sink.test.ts index 7654c4eee3db..8ccf59d4bb6c 100644 --- a/src/plugins/install-persistence.warning-sink.test.ts +++ b/src/plugins/install-persistence.warning-sink.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { - applyExclusiveSlotSelection, - applyPluginUninstallDirectoryRemoval, - buildPluginSnapshotReport, + applyExclusiveSlotSelectionMock, + applyPluginUninstallDirectoryRemovalMock, + buildPluginSnapshotReportMock, loadPluginManifestRegistry, planPluginUninstall, refreshPluginRegistry, @@ -85,7 +85,11 @@ describe("plugin install persistence warning audiences", () => { ], diagnostics: [], }); - applyExclusiveSlotSelection.mockReturnValue({ config: {}, warnings: [warning], changed: true }); + applyExclusiveSlotSelectionMock.mockReturnValue({ + config: {}, + warnings: [warning], + changed: true, + }); await persistPluginInstall({ snapshot, @@ -119,12 +123,12 @@ describe("plugin install persistence warning audiences", () => { actions: {}, directoryRemoval: { target: "/private/previous-source/workboard" }, }); - applyPluginUninstallDirectoryRemoval.mockResolvedValueOnce({ + applyPluginUninstallDirectoryRemovalMock.mockResolvedValueOnce({ directoryRemoved: false, warnings: [cleanupDetail], }); refreshPluginRegistry.mockRejectedValueOnce(new Error(refreshDetail)); - buildPluginSnapshotReport.mockReturnValue({ + buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "workboard", origin: "config", source: configuredSource }], diagnostics: [], }); diff --git a/src/plugins/install.npm-spec.test.ts b/src/plugins/install.npm-spec.test.ts index 05cf7e91c96e..e28f89cf114c 100644 --- a/src/plugins/install.npm-spec.test.ts +++ b/src/plugins/install.npm-spec.test.ts @@ -16,7 +16,7 @@ import { hasRetainedManagedNpmInstallMarker, markRetainedManagedNpmInstall, } from "./managed-npm-retention.js"; -import { createSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; +import { createSyncSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; const runCommandWithTimeoutMock = vi.fn(); const resolveOpenClawPackageRootSyncMock = vi.fn(); @@ -37,7 +37,7 @@ const { installPluginFromNpmPackArchive, installPluginFromNpmSpec, PLUGIN_INSTAL const { classifyNpmManagedOverrideCompatibilityError } = await import("./install-managed-npm-state.js"); -const suiteTempRootTracker = createSuiteTempRootTracker("openclaw-plugin-install-npm-spec"); +const suiteTempRootTracker = createSyncSuiteTempRootTracker("openclaw-plugin-install-npm-spec"); let previousNpmGlobalConfig: string | undefined; let npmGlobalConfigPath: string; let npmPackArchiveInstallCase: { diff --git a/src/plugins/install.path.test.ts b/src/plugins/install.path.test.ts index f1609dc887de..30b855cd6682 100644 --- a/src/plugins/install.path.test.ts +++ b/src/plugins/install.path.test.ts @@ -5,7 +5,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vites import { runCommandWithTimeout } from "../process/exec.js"; import { installPluginFromPath, PLUGIN_INSTALL_ERROR_CODE } from "./install.js"; import { packToArchive } from "./test-helpers/archive-fixtures.js"; -import { createSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; +import { createSyncSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; import { createBundleInstallFixtureFactory, createDualFormatInstallFixtureFactory, @@ -15,7 +15,7 @@ vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: vi.fn(), })); -const suiteTempRootTracker = createSuiteTempRootTracker("openclaw-plugin-install-path"); +const suiteTempRootTracker = createSyncSuiteTempRootTracker("openclaw-plugin-install-path"); const setupBundleInstallFixture = createBundleInstallFixtureFactory( suiteTempRootTracker.makeTempDir, ); diff --git a/src/plugins/install.test.ts b/src/plugins/install.test.ts index 27a9bee0b24c..29be56a858f2 100644 --- a/src/plugins/install.test.ts +++ b/src/plugins/install.test.ts @@ -31,7 +31,7 @@ import { } from "./install.js"; import { markRetainedManagedNpmInstall } from "./managed-npm-retention.js"; import { packToArchive } from "./test-helpers/archive-fixtures.js"; -import { createSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; +import { createSyncSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; import { createBundleInstallFixtureFactory, createDualFormatInstallFixtureFactory, @@ -77,7 +77,7 @@ const archiveFixturePathCache = new Map(); const dynamicArchiveTemplatePathCache = new Map(); let installPluginFromDirTemplateDir = ""; let manifestInstallTemplateDir = ""; -const suiteTempRootTracker = createSuiteTempRootTracker("openclaw-plugin-install"); +const suiteTempRootTracker = createSyncSuiteTempRootTracker("openclaw-plugin-install"); const setupBundleInstallFixture = createBundleInstallFixtureFactory( suiteTempRootTracker.makeTempDir, ); diff --git a/src/plugins/loader.activation.test-utils.ts b/src/plugins/loader.activation.test-utils.ts index 897a8c8169c7..699c1df46744 100644 --- a/src/plugins/loader.activation.test-utils.ts +++ b/src/plugins/loader.activation.test-utils.ts @@ -9,7 +9,7 @@ import { writePersistedInstalledPluginIndexInstallRecordsSync } from "./installe import { loadOpenClawPlugins } from "./loader.js"; import { EMPTY_PLUGIN_SCHEMA, - makeTempDir, + makePluginLoaderTempDir, mkdirSafe, useNoBundledPlugins, writePlugin, @@ -48,9 +48,9 @@ describe("loadOpenClawPlugins", () => { name: "does not reuse cached registries when env-resolved install paths change", setup: () => { useNoBundledPlugins(); - const openclawHome = makeTempDir(); - const ignoredHome = makeTempDir(); - const stateDir = makeTempDir(); + const openclawHome = makePluginLoaderTempDir(); + const ignoredHome = makePluginLoaderTempDir(); + const stateDir = makePluginLoaderTempDir(); const pluginDir = path.join(openclawHome, "plugins", "tracked-install-cache"); mkdirSafe(pluginDir); const plugin = writePlugin({ @@ -80,7 +80,7 @@ describe("loadOpenClawPlugins", () => { }, }; - const secondHome = makeTempDir(); + const secondHome = makePluginLoaderTempDir(); return { loadFirst: () => loadOpenClawPlugins({ @@ -178,7 +178,7 @@ describe("loadOpenClawPlugins", () => { }); it("normalizes bundled plugin env overrides against the provided env", () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const homeDir = path.dirname(bundledDir); const override = `~/${path.basename(bundledDir)}`; const plugin = writePlugin({ @@ -211,10 +211,10 @@ describe("loadOpenClawPlugins", () => { }); it("prefers OPENCLAW_HOME over HOME for env-expanded load paths", () => { - const ignoredHome = makeTempDir(); - const openclawHome = makeTempDir(); - const stateDir = makeTempDir(); - const bundledDir = makeTempDir(); + const ignoredHome = makePluginLoaderTempDir(); + const openclawHome = makePluginLoaderTempDir(); + const stateDir = makePluginLoaderTempDir(); + const bundledDir = makePluginLoaderTempDir(); const plugin = writePlugin({ id: "openclaw-home-demo", dir: path.join(openclawHome, "plugins", "openclaw-home-demo"), @@ -1104,7 +1104,7 @@ describe("loadOpenClawPlugins", () => { it("loads bundled channel entries through nested default export wrappers", () => { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const fullMarker = path.join(pluginDir, "full-loaded.txt"); fs.writeFileSync( @@ -1239,7 +1239,7 @@ describe("loadOpenClawPlugins", () => { it("does not setup-load an explicitly disabled channel plugin even when the caller scopes to it", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "lazy-channel-imported.txt"); + const marker = path.join(makePluginLoaderTempDir(), "lazy-channel-imported.txt"); const plugin = writePlugin({ id: "lazy-channel-plugin", filename: "lazy-channel.cjs", @@ -1332,7 +1332,7 @@ describe("loadOpenClawPlugins", () => { it("blocks untrusted setup-only workspace channel plugins when explicitly scoped", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "workspace-setup-only-loaded.txt"); + const marker = path.join(makePluginLoaderTempDir(), "workspace-setup-only-loaded.txt"); const { workspaceDir, workspacePluginDir } = writeWorkspacePlugin({ id: "workspace-shadow", body: `require("node:fs").writeFileSync(${JSON.stringify(marker)}, "loaded", "utf-8"); @@ -1398,7 +1398,7 @@ describe("loadOpenClawPlugins", () => { it("keeps trusted setup-only workspace channel plugins available when explicitly scoped", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "trusted-workspace-setup-only-loaded.txt"); + const marker = path.join(makePluginLoaderTempDir(), "trusted-workspace-setup-only-loaded.txt"); const { workspaceDir, workspacePluginDir } = writeWorkspacePlugin({ id: "trusted-workspace-shadow", body: `require("node:fs").writeFileSync(${JSON.stringify(marker)}, "loaded", "utf-8"); @@ -1466,7 +1466,7 @@ describe("loadOpenClawPlugins", () => { it("does not setup-load an untrusted config-origin channel plugin when the caller scopes to it", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "untrusted-load-path-channel-imported.txt"); + const marker = path.join(makePluginLoaderTempDir(), "untrusted-load-path-channel-imported.txt"); const plugin = writePlugin({ id: "untrusted-load-path-channel", filename: "untrusted-load-path-channel.cjs", @@ -1531,7 +1531,10 @@ describe("loadOpenClawPlugins", () => { it("does not setup-load a denylisted config-origin channel plugin even when explicitly allowed", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "denylisted-load-path-channel-imported.txt"); + const marker = path.join( + makePluginLoaderTempDir(), + "denylisted-load-path-channel-imported.txt", + ); const plugin = writePlugin({ id: "denylisted-load-path-channel", filename: "denylisted-load-path-channel.cjs", @@ -1598,7 +1601,7 @@ describe("loadOpenClawPlugins", () => { it("does not setup-load an untrusted global channel plugin when the caller scopes to it", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "untrusted-global-channel-imported.txt"); + const marker = path.join(makePluginLoaderTempDir(), "untrusted-global-channel-imported.txt"); withStateDir((stateDir) => { const globalDir = path.join(stateDir, "extensions", "untrusted-global-channel"); mkdirSafe(globalDir); diff --git a/src/plugins/loader.base.test-utils.ts b/src/plugins/loader.base.test-utils.ts index 552a8afdf98f..130c6deb4a36 100644 --- a/src/plugins/loader.base.test-utils.ts +++ b/src/plugins/loader.base.test-utils.ts @@ -28,7 +28,7 @@ import { resolvePluginRegistryLoadCacheKey } from "./loader-cache.js"; import { loadOpenClawPlugins, resolveRuntimePluginRegistry } from "./loader.js"; import { EMPTY_PLUGIN_SCHEMA, - makeTempDir, + makePluginLoaderTempDir, type PluginLoadConfig, useNoBundledPlugins, writePlugin, @@ -507,7 +507,7 @@ describe("loadOpenClawPlugins", () => { it("loads installed plugin packages discovered from persisted install records", () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); const plugin = writePlugin({ id: "installed-record-plugin", body: `module.exports = { id: "installed-record-plugin", register() {} };`, @@ -545,7 +545,7 @@ describe("loadOpenClawPlugins", () => { }); it("disables bundled plugins by default", () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); writePlugin({ id: "bundled", body: `module.exports = { id: "bundled", register() {} };`, @@ -568,7 +568,7 @@ describe("loadOpenClawPlugins", () => { }); it("loads bundled plugins with plugin-sdk imports from a package dist root", () => { - const packageRoot = makeTempDir(); + const packageRoot = makePluginLoaderTempDir(); const bundledDir = path.join(packageRoot, "dist", "extensions"); const pluginRoot = path.join(bundledDir, "discord"); fs.mkdirSync(path.join(packageRoot, "dist", "plugin-sdk"), { recursive: true }); @@ -695,7 +695,7 @@ describe("loadOpenClawPlugins", () => { it("loads multiple manifestless standalone files from one configured directory", () => { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const alpha = path.join(pluginDir, "alpha.cjs"); const beta = path.join(pluginDir, "beta.cjs"); fs.writeFileSync(alpha, `module.exports = { id: "alpha", register() {} };`, "utf-8"); @@ -1188,7 +1188,7 @@ describe("loadOpenClawPlugins", () => { filename: "allowed-scoped-only.cjs", body: `module.exports = { id: "allowed-scoped-only", register() {} };`, }); - const skippedMarker = path.join(makeTempDir(), "skipped-loaded.txt"); + const skippedMarker = path.join(makePluginLoaderTempDir(), "skipped-loaded.txt"); const skipped = writePlugin({ id: "skipped-scoped-only", filename: "skipped-scoped-only.cjs", @@ -1215,7 +1215,7 @@ describe("loadOpenClawPlugins", () => { label: "can build a manifest-only snapshot without importing plugin modules", run: () => { useNoBundledPlugins(); - const importedMarker = path.join(makeTempDir(), "manifest-only-imported.txt"); + const importedMarker = path.join(makePluginLoaderTempDir(), "manifest-only-imported.txt"); const plugin = writePlugin({ id: "manifest-only-plugin", filename: "manifest-only-plugin.cjs", @@ -1247,7 +1247,10 @@ describe("loadOpenClawPlugins", () => { label: "includes manifest-owned surfaces in manifest-only snapshots", run: () => { useNoBundledPlugins(); - const importedMarker = path.join(makeTempDir(), "manifest-surfaces-imported.txt"); + const importedMarker = path.join( + makePluginLoaderTempDir(), + "manifest-surfaces-imported.txt", + ); const plugin = writePlugin({ id: "manifest-surfaces-plugin", filename: "manifest-surfaces-plugin.cjs", @@ -1398,7 +1401,7 @@ describe("loadOpenClawPlugins", () => { reenterFnMarker, (options: Parameters[0]) => loadOpenClawPlugins(options), ); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const pluginFile = path.join(pluginDir, "reentrant-snapshot.cjs"); const nestedOptions = { cache: false, @@ -1477,7 +1480,7 @@ describe("loadOpenClawPlugins", () => { (options: Parameters[0]) => resolveRuntimePluginRegistry(options), ); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const pluginFile = path.join(pluginDir, "runtime-registry-reentry.cjs"); const nestedOptions = { cache: false, @@ -1769,7 +1772,7 @@ describe("loadOpenClawPlugins", () => { loadOpenClawPlugins({ cache: false, - workspaceDir: makeTempDir(), + workspaceDir: makePluginLoaderTempDir(), config: { plugins: { allow: [], diff --git a/src/plugins/loader.bundle.test.ts b/src/plugins/loader.bundle.test.ts index 703328601d9b..8baa6ceefaa6 100644 --- a/src/plugins/loader.bundle.test.ts +++ b/src/plugins/loader.bundle.test.ts @@ -7,7 +7,7 @@ import { loadOpenClawPlugins } from "./loader.js"; import { cleanupPluginLoaderFixturesForTest, loadBundleFixture, - makeTempDir, + makePluginLoaderTempDir, mkdirSafe, resetPluginLoaderTestStateForTest, useNoBundledPlugins, @@ -37,8 +37,8 @@ afterAll(() => { describe("bundle plugins", () => { it("reports Codex bundles as loaded bundle plugins without importing runtime code", () => { useNoBundledPlugins(); - const workspaceDir = makeTempDir(); - const stateDir = makeTempDir(); + const workspaceDir = makePluginLoaderTempDir(); + const stateDir = makePluginLoaderTempDir(); const bundleRoot = path.join(workspaceDir, ".openclaw", "extensions", "sample-bundle"); mkdirSafe(path.join(bundleRoot, ".codex-plugin")); mkdirSafe(path.join(bundleRoot, "skills")); @@ -159,7 +159,7 @@ describe("bundle plugins", () => { }); it("accepts bundle HTTP MCP and warns only for incomplete configs", () => { - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); const registry = loadBundleFixture({ pluginId: "claude-mcp-url", env: { diff --git a/src/plugins/loader.cli-metadata.test.ts b/src/plugins/loader.cli-metadata.test.ts index ac986959c0c8..09bee7bfef0f 100644 --- a/src/plugins/loader.cli-metadata.test.ts +++ b/src/plugins/loader.cli-metadata.test.ts @@ -12,7 +12,7 @@ import { cleanupPluginLoaderFixturesForTest, EMPTY_PLUGIN_SCHEMA, inlineChannelPluginEntryFactorySource, - makeTempDir, + makePluginLoaderTempDir, resetPluginLoaderTestStateForTest, useNoBundledPlugins, writePlugin, @@ -81,7 +81,7 @@ describe("plugin loader CLI metadata", () => { it("suppresses trust warning logs during CLI metadata loads", async () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); const globalDir = path.join(stateDir, "extensions", "rogue"); fs.mkdirSync(globalDir, { recursive: true }); writePlugin({ @@ -189,7 +189,7 @@ describe("plugin loader CLI metadata", () => { it("uses the real channel entry in cli-metadata mode for CLI metadata capture", async () => { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const fullMarker = path.join(pluginDir, "full-loaded.txt"); const modeMarker = path.join(pluginDir, "registration-mode.txt"); const runtimeMarker = path.join(pluginDir, "runtime-set.txt"); @@ -294,7 +294,7 @@ module.exports = { }); it("skips bundled channel full entries that do not provide a dedicated cli-metadata entry", async () => { - const bundledRoot = makeTempDir(); + const bundledRoot = makePluginLoaderTempDir(); const pluginDir = path.join(bundledRoot, "bundled-skip-channel"); const fullMarker = path.join(pluginDir, "full-loaded.txt"); @@ -361,7 +361,7 @@ module.exports = { }); it("prefers bundled channel cli-metadata entries over full channel entries", async () => { - const bundledRoot = makeTempDir(); + const bundledRoot = makePluginLoaderTempDir(); const pluginDir = path.join(bundledRoot, "bundled-cli-channel"); const fullMarker = path.join(pluginDir, "full-loaded.txt"); const cliMarker = path.join(pluginDir, "cli-loaded.txt"); @@ -453,7 +453,7 @@ module.exports = { }); it("skips bundled non-channel full entries that do not provide a dedicated cli-metadata entry", async () => { - const bundledRoot = makeTempDir(); + const bundledRoot = makePluginLoaderTempDir(); const pluginDir = path.join(bundledRoot, "bundled-skip-provider"); const fullMarker = path.join(pluginDir, "full-loaded.txt"); @@ -520,7 +520,7 @@ module.exports = { it("collects channel CLI metadata during full plugin loads", () => { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const modeMarker = path.join(pluginDir, "registration-mode.txt"); const fullMarker = path.join(pluginDir, "full-loaded.txt"); @@ -616,7 +616,7 @@ module.exports = { it("collects channel CLI metadata during discovery plugin loads", () => { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const modeMarker = path.join(pluginDir, "registration-mode.txt"); const fullMarker = path.join(pluginDir, "full-loaded.txt"); const runtimeMarker = path.join(pluginDir, "runtime-set.txt"); @@ -723,7 +723,7 @@ module.exports = { it("can force channel runtime entries for CLI registration when setup entries exist", () => { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const modeMarker = path.join(pluginDir, "registration-mode.txt"); const setupMarker = path.join(pluginDir, "setup-loaded.txt"); @@ -827,7 +827,7 @@ module.exports = { }); it("sets bundled channel runtime before discovery CLI metadata registration", () => { - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const runtimeMarker = path.join(pluginDir, "runtime-set.txt"); const channelPluginPath = path.join(pluginDir, "channel.cjs"); const runtimePath = path.join(pluginDir, "runtime.cjs"); diff --git a/src/plugins/loader.discovery-and-security.test-utils.ts b/src/plugins/loader.discovery-and-security.test-utils.ts index 63ced00bdb83..16722cf7a1b5 100644 --- a/src/plugins/loader.discovery-and-security.test-utils.ts +++ b/src/plugins/loader.discovery-and-security.test-utils.ts @@ -11,7 +11,7 @@ import { loadOpenClawPluginCliRegistry, loadOpenClawPlugins } from "./loader.js" import { clearPluginLoaderCache, EMPTY_PLUGIN_SCHEMA, - makeTempDir, + makePluginLoaderTempDir, mkdirSafe, type PluginLoadConfig, type PluginRegistry, @@ -49,7 +49,7 @@ afterAll(globalAfterAll1); describe("loadOpenClawPlugins", () => { it("loads every entry in a multi-entry package pack under its derived id", () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => { const packageDir = path.join(stateDir, "extensions", "pack"); mkdirSafe(packageDir); @@ -192,7 +192,7 @@ describe("loadOpenClawPlugins", () => { { label: "skips importing bundled memory plugins that are disabled by memory slot", loadRegistry: () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const memoryADir = path.join(bundledDir, "memory-a"); const memoryBDir = path.join(bundledDir, "memory-b"); mkdirSafe(memoryADir); @@ -408,7 +408,7 @@ describe("loadOpenClawPlugins", () => { label: "loads dreaming engine alongside a different memory slot plugin when dreaming is enabled", loadRegistry: () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const memoryCoreDir = path.join(bundledDir, "memory-core"); const memoryLanceDir = path.join(bundledDir, "memory-lancedb"); mkdirSafe(memoryCoreDir); @@ -472,7 +472,7 @@ describe("loadOpenClawPlugins", () => { { label: "excludes dreaming engine when dreaming is disabled and it is not the slot", loadRegistry: () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const memoryCoreDir = path.join(bundledDir, "memory-core"); const memoryLanceDir = path.join(bundledDir, "memory-lancedb"); mkdirSafe(memoryCoreDir); @@ -540,7 +540,7 @@ describe("loadOpenClawPlugins", () => { { label: 'keeps memory slot "none" disabled even with stale memory-core dreaming config', loadRegistry: () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const memoryCoreDir = path.join(bundledDir, "memory-core"); mkdirSafe(memoryCoreDir); writePlugin({ @@ -1117,7 +1117,7 @@ describe("loadOpenClawPlugins", () => { it("distinguishes load permission from capability trust in the untracked-provenance warning", () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => { const globalDir = path.join(stateDir, "extensions", "warn-untracked-remediation"); mkdirSafe(globalDir); @@ -1401,7 +1401,7 @@ describe("loadOpenClawPlugins", () => { { label: "warns when loaded non-bundled plugin has no provenance and no allowlist is set", loadRegistry: () => { - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); return withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => { const globalDir = path.join(stateDir, "extensions", "rogue"); mkdirSafe(globalDir); @@ -1490,7 +1490,7 @@ describe("loadOpenClawPlugins", () => { label: "does not warn when install paths resolve through a symlinked state root", loadRegistry: () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); const realHome = path.join(stateDir, "real-home"); const linkedHome = path.join(stateDir, "linked-home"); mkdirSafe(realHome); @@ -1572,7 +1572,7 @@ describe("loadOpenClawPlugins", () => { it("uses the source runtime snapshot allowlist for plugin trust checks", () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => { const globalDir = path.join(stateDir, "extensions", "trusted-plugin"); mkdirSafe(globalDir); @@ -1655,11 +1655,11 @@ describe("loadOpenClawPlugins", () => { if (process.platform === "win32") { return; } - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const pluginDir = path.join(bundledDir, "hardlinked-bundled"); mkdirSafe(pluginDir); - const outsideDir = makeTempDir(); + const outsideDir = makePluginLoaderTempDir(); const outsideEntry = path.join(outsideDir, "outside.cjs"); fs.writeFileSync( outsideEntry, @@ -1703,7 +1703,7 @@ describe("loadOpenClawPlugins", () => { it("preserves runtime reflection semantics when runtime is lazily initialized", () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); const plugin = writePlugin({ id: "runtime-introspection", filename: "runtime-introspection.cjs", @@ -1742,7 +1742,7 @@ describe("loadOpenClawPlugins", () => { it("suppresses trust warning logs for non-activating snapshot loads", () => { useNoBundledPlugins(); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => { const globalDir = path.join(stateDir, "extensions", "rogue"); mkdirSafe(globalDir); diff --git a/src/plugins/loader.hooks-and-runtime.test-utils.ts b/src/plugins/loader.hooks-and-runtime.test-utils.ts index 744e6f16f9e7..d989e1bd4275 100644 --- a/src/plugins/loader.hooks-and-runtime.test-utils.ts +++ b/src/plugins/loader.hooks-and-runtime.test-utils.ts @@ -7,7 +7,7 @@ import { createHookRunner } from "./hooks.js"; import { loadOpenClawPlugins } from "./loader.js"; import { EMPTY_PLUGIN_SCHEMA, - makeTempDir, + makePluginLoaderTempDir, mkdirSafe, useNoBundledPlugins, writePlugin, @@ -79,7 +79,7 @@ function createSetupFailureFixture(params: { channelId?: string; setupEntrySource: string; }) { - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); writeFixtureJson(pluginDir, "package.json", { name: `@openclaw/${params.id}`, openclaw: { @@ -151,9 +151,11 @@ type BuiltArtifactScenario = { }; function loadBuiltArtifactScenario(scenario: BuiltArtifactScenario) { - const repoRoot = makeTempDir(); + const repoRoot = makePluginLoaderTempDir(); const pluginDir = - scenario.origin === "bundled" ? path.join(repoRoot, "extensions", scenario.id) : makeTempDir(); + scenario.origin === "bundled" + ? path.join(repoRoot, "extensions", scenario.id) + : makePluginLoaderTempDir(); const packageManifest = scenario.packageEntry ? { openclaw: { extensions: [scenario.packageEntry] } } : undefined; @@ -201,7 +203,7 @@ function loadBuiltArtifactScenario(scenario: BuiltArtifactScenario) { describe("loadOpenClawPlugins", () => { it("setup-loads a trusted global channel plugin when the caller scopes to it", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "trusted-global-channel-imported.txt"); + const marker = path.join(makePluginLoaderTempDir(), "trusted-global-channel-imported.txt"); withStateDir((stateDir) => { const globalDir = path.join(stateDir, "extensions", "trusted-global-channel"); mkdirSafe(globalDir); @@ -256,7 +258,10 @@ ${channelPluginSource({ it("does not setup-load an auto-enabled config-origin channel plugin without explicit trust", () => { useNoBundledPlugins(); - const marker = path.join(makeTempDir(), "auto-enabled-load-path-channel-imported.txt"); + const marker = path.join( + makePluginLoaderTempDir(), + "auto-enabled-load-path-channel-imported.txt", + ); const plugin = writePlugin({ id: "auto-enabled-load-path-channel", filename: "auto-enabled-load-path-channel.cjs", @@ -410,7 +415,10 @@ ${channelPluginSource({ setupBlurb: "setup runtime bundled contract runtime", configured: false, useBundledSetupEntryContract: true, - bundledSetupRuntimeMarker: path.join(makeTempDir(), "setup-runtime-applied.txt"), + bundledSetupRuntimeMarker: path.join( + makePluginLoaderTempDir(), + "setup-runtime-applied.txt", + ), }, loadOptions: { setupIntent: true }, expectFullLoaded: true, @@ -429,7 +437,10 @@ ${channelPluginSource({ configured: false, useBundledFullEntryContract: true, useBundledSetupEntryContract: true, - bundledFullRuntimeMarker: path.join(makeTempDir(), "bundled-runtime-applied.txt"), + bundledFullRuntimeMarker: path.join( + makePluginLoaderTempDir(), + "bundled-runtime-applied.txt", + ), }, loadOptions: { setupIntent: true }, expectFullLoaded: true, @@ -509,7 +520,7 @@ ${channelPluginSource({ ); it("applies the bundled runtime setter before loading the merged setup-runtime plugin", () => { - const runtimeMarker = path.join(makeTempDir(), "setup-runtime-before-load.txt"); + const runtimeMarker = path.join(makePluginLoaderTempDir(), "setup-runtime-before-load.txt"); const built = createSetupEntryChannelPluginFixture({ id: "setup-runtime-order-test", label: "Setup Runtime Order Test", @@ -633,7 +644,7 @@ ${channelPluginSource({ }); it("rejects mismatched bundled runtime entry ids before applying setup-runtime setters", () => { - const runtimeMarker = path.join(makeTempDir(), "setup-runtime-mismatch.txt"); + const runtimeMarker = path.join(makePluginLoaderTempDir(), "setup-runtime-mismatch.txt"); const built = createSetupEntryChannelPluginFixture({ id: "setup-runtime-mismatch-test", bundledFullEntryId: "wrong-runtime-id", @@ -663,7 +674,7 @@ ${channelPluginSource({ }); it("rejects mismatched bundled setup export ids before loading setup-runtime entry code", () => { - const runtimeMarker = path.join(makeTempDir(), "setup-runtime-mismatch.txt"); + const runtimeMarker = path.join(makePluginLoaderTempDir(), "setup-runtime-mismatch.txt"); const built = createSetupEntryChannelPluginFixture({ id: "setup-export-mismatch-test", bundledSetupEntryId: "wrong-setup-id", @@ -853,7 +864,7 @@ ${channelPluginSource({ }); it("ignores built artifacts when the bundled source plugin opts out of core dist", () => { - const repoRoot = makeTempDir(); + const repoRoot = makePluginLoaderTempDir(); const sourceDir = path.join(repoRoot, "extensions", "source-only-artifact-test"); const builtPluginDir = path.join(repoRoot, "dist", "extensions", "source-only-artifact-test"); mkdirSafe(path.join(repoRoot, ".git")); @@ -984,8 +995,8 @@ ${channelPluginSource({ it("keeps package-local dist artifacts inside the plugin root boundary", () => { useNoBundledPlugins(); - const pluginDir = makeTempDir(); - const outsideDistDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); + const outsideDistDir = makePluginLoaderTempDir(); writeFixtureJson(pluginDir, "package.json", { openclaw: { extensions: ["./src/index.mts"] }, }); diff --git a/src/plugins/loader.prefer-over.test.ts b/src/plugins/loader.prefer-over.test.ts index 9737f9b6c0f3..de25d6e6c4ec 100644 --- a/src/plugins/loader.prefer-over.test.ts +++ b/src/plugins/loader.prefer-over.test.ts @@ -9,7 +9,7 @@ import { resetPluginRuntimeStateForTest } from "./runtime.js"; const tempDirs: string[] = []; -function makeTempDir(): string { +function makePluginLoaderTempDir(): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-prefer-over-")); if (process.platform !== "win32") { fs.chmodSync(dir, 0o755); @@ -97,14 +97,14 @@ afterEach(() => { describe("plugin loader preferOver activation", () => { it("loads the preferred external channel plugin without the replaced bundled plugin tools", () => { - const bundledRoot = makeTempDir(); + const bundledRoot = makePluginLoaderTempDir(); writeChannelToolPlugin({ rootDir: bundledRoot, id: "qqbot", channelId: "qqbot", enabledByDefault: true, }); - const externalRoot = makeTempDir(); + const externalRoot = makePluginLoaderTempDir(); const externalPluginDir = writeChannelToolPlugin({ rootDir: externalRoot, id: "openclaw-qqbot", @@ -112,7 +112,7 @@ describe("plugin loader preferOver activation", () => { preferOver: ["qqbot"], }); const env = { - OPENCLAW_STATE_DIR: makeTempDir(), + OPENCLAW_STATE_DIR: makePluginLoaderTempDir(), OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot, }; const rawConfig = { @@ -142,21 +142,21 @@ describe("plugin loader preferOver activation", () => { }); it("blocks tools from a plugin that loses a duplicate channel registration", () => { - const bundledRoot = makeTempDir(); + const bundledRoot = makePluginLoaderTempDir(); writeChannelToolPlugin({ rootDir: bundledRoot, id: "qqbot", channelId: "qqbot", enabledByDefault: true, }); - const externalRoot = makeTempDir(); + const externalRoot = makePluginLoaderTempDir(); const externalPluginDir = writeChannelToolPlugin({ rootDir: externalRoot, id: "openclaw-qqbot", channelId: "qqbot", }); const env = { - OPENCLAW_STATE_DIR: makeTempDir(), + OPENCLAW_STATE_DIR: makePluginLoaderTempDir(), OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot, }; diff --git a/src/plugins/loader.registration.test-utils.ts b/src/plugins/loader.registration.test-utils.ts index 888c420eb26e..92a547599a66 100644 --- a/src/plugins/loader.registration.test-utils.ts +++ b/src/plugins/loader.registration.test-utils.ts @@ -32,7 +32,7 @@ import { } from "./interactive-state.js"; import { loadOpenClawPlugins } from "./loader.js"; import { - makeTempDir, + makePluginLoaderTempDir, mkdirSafe, useNoBundledPlugins, writePlugin, @@ -551,7 +551,7 @@ describe("loadOpenClawPlugins", () => { }); it("can scope bundled provider loads without hanging", () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const scopedDir = path.join(bundledDir, "scoped-provider"); mkdirSafe(scopedDir); fs.writeFileSync( @@ -625,7 +625,7 @@ describe("loadOpenClawPlugins", () => { }); it("allows bundled plugins to supply system.notify without opening the command to external plugins", () => { - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const bundledPluginDir = path.join(bundledDir, "notify-host"); mkdirSafe(bundledPluginDir); fs.writeFileSync( @@ -1287,7 +1287,7 @@ describe("loadOpenClawPlugins", () => { it("restores cached memory capability public artifacts on cache hits", async () => { useNoBundledPlugins(); - const workspaceDir = makeTempDir(); + const workspaceDir = makePluginLoaderTempDir(); const absolutePath = path.join(workspaceDir, "MEMORY.md"); fs.writeFileSync(absolutePath, "# Memory\n"); const plugin = writePlugin({ @@ -1354,7 +1354,7 @@ describe("loadOpenClawPlugins", () => { it("preserves previously registered memory capability across activate:false snapshot loads", async () => { useNoBundledPlugins(); - const workspaceDir = makeTempDir(); + const workspaceDir = makePluginLoaderTempDir(); const absolutePath = path.join(workspaceDir, "MEMORY.md"); fs.writeFileSync(absolutePath, "# Memory\n"); const memoryPlugin = writePlugin({ @@ -1755,8 +1755,8 @@ describe("loadOpenClawPlugins", () => { name: "does not reuse cached bundled plugin registries across env changes", pluginId: "cache-root", setup: () => { - const bundledA = makeTempDir(); - const bundledB = makeTempDir(); + const bundledA = makePluginLoaderTempDir(); + const bundledB = makePluginLoaderTempDir(); const pluginA = writePlugin({ id: "cache-root", dir: path.join(bundledA, "cache-root"), @@ -1807,10 +1807,10 @@ describe("loadOpenClawPlugins", () => { name: "does not reuse cached load-path plugin registries across env home changes", pluginId: "demo", setup: () => { - const homeA = makeTempDir(); - const homeB = makeTempDir(); - const stateDir = makeTempDir(); - const bundledDir = makeTempDir(); + const homeA = makePluginLoaderTempDir(); + const homeB = makePluginLoaderTempDir(); + const stateDir = makePluginLoaderTempDir(); + const bundledDir = makePluginLoaderTempDir(); const pluginA = writePlugin({ id: "demo", dir: path.join(homeA, "plugins", "demo"), diff --git a/src/plugins/loader.runtime-registry.test.ts b/src/plugins/loader.runtime-registry.test.ts index 406a2bf3d2b5..43a56926bd22 100644 --- a/src/plugins/loader.runtime-registry.test.ts +++ b/src/plugins/loader.runtime-registry.test.ts @@ -16,7 +16,10 @@ import { loadPluginRegistryHandle, resolveRuntimePluginRegistry, } from "./loader.js"; -import { makeTempDir, resetPluginLoaderTestStateForTest } from "./loader.test-fixtures.js"; +import { + makePluginLoaderTempDir, + resetPluginLoaderTestStateForTest, +} from "./loader.test-fixtures.js"; import { getRegisteredMemoryEmbeddingProvider, registerMemoryEmbeddingProvider, @@ -55,7 +58,7 @@ function setLoaderMetadataSnapshot(params: { pluginIds?: readonly string[] } = { }, }; const env = process.env; - const workspaceDir = makeTempDir(); + const workspaceDir = makePluginLoaderTempDir(); const installRecords: Record = { demo: { source: "npm", @@ -133,7 +136,7 @@ describe("resolvePluginLoadCacheContext", () => { }); it("loads a custom profile's install records instead of reusing the process snapshot", () => { - const profileEnv = { ...process.env, OPENCLAW_STATE_DIR: makeTempDir() }; + const profileEnv = { ...process.env, OPENCLAW_STATE_DIR: makePluginLoaderTempDir() }; const profileInstallRecords: Record = { demo: { source: "npm", @@ -215,7 +218,8 @@ describe("resolvePluginLoadCacheContext", () => { const { config, env } = setLoaderMetadataSnapshot(); expect( - resolvePluginLoadCacheContext({ config, env, workspaceDir: makeTempDir() }).installRecords, + resolvePluginLoadCacheContext({ config, env, workspaceDir: makePluginLoaderTempDir() }) + .installRecords, ).toEqual(loadInstalledPluginIndexInstallRecordsSync({ env })); }); diff --git a/src/plugins/loader.test-fixtures.ts b/src/plugins/loader.test-fixtures.ts index ab146825e91f..3a935a1a42c0 100644 --- a/src/plugins/loader.test-fixtures.ts +++ b/src/plugins/loader.test-fixtures.ts @@ -74,7 +74,7 @@ export function inlineChannelPluginEntryFactorySource(): string { `; } -export function makeTempDir() { +export function makePluginLoaderTempDir() { const dir = path.join(fixtureRoot, `case-${tempDirIndex++}`); mkdirSafe(dir); return dir; @@ -87,7 +87,7 @@ export function writePlugin(params: { filename?: string; configSchema?: Record; }): TempPlugin { - const dir = params.dir ?? makeTempDir(); + const dir = params.dir ?? makePluginLoaderTempDir(); const filename = params.filename ?? `${params.id}.cjs`; mkdirSafe(dir); const file = path.join(dir, filename); @@ -119,8 +119,8 @@ export function loadBundleFixture(params: { onlyPluginIds?: string[]; }) { useNoBundledPlugins(); - const workspaceDir = makeTempDir(); - const stateDir = makeTempDir(); + const workspaceDir = makePluginLoaderTempDir(); + const stateDir = makePluginLoaderTempDir(); const bundleRoot = path.join(workspaceDir, ".openclaw", "extensions", params.pluginId); params.build(bundleRoot); return withEnv({ OPENCLAW_STATE_DIR: stateDir, ...params.env }, () => diff --git a/src/plugins/loader.test-harness.ts b/src/plugins/loader.test-harness.ts index 21dc262a2e46..47b3b8763f4d 100644 --- a/src/plugins/loader.test-harness.ts +++ b/src/plugins/loader.test-harness.ts @@ -17,7 +17,7 @@ import { loadOpenClawPlugins, type PluginLoadOptions } from "./loader.js"; import { cleanupPluginLoaderFixturesForTest, EMPTY_PLUGIN_SCHEMA, - makeTempDir, + makePluginLoaderTempDir, mkdirSafe, type PluginRegistry, resetPluginLoaderTestStateForTest, @@ -133,7 +133,7 @@ export function setupBundledDreamingMemoryPlugins(params?: { coreBody?: string; }) { const selectedId = params?.selectedId ?? "memory-lancedb"; - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); const memoryCoreDir = path.join(bundledDir, "memory-core"); const selectedMemoryDir = path.join(bundledDir, selectedId); mkdirSafe(memoryCoreDir); @@ -191,7 +191,7 @@ export function writeBundledPlugin(params: { filename?: string; bundledDir?: string; }) { - const bundledDir = params.bundledDir ?? makeTempDir(); + const bundledDir = params.bundledDir ?? makePluginLoaderTempDir(); const plugin = writePlugin({ id: params.id, dir: bundledDir, @@ -204,7 +204,7 @@ export function writeBundledPlugin(params: { } export function makeOpenClawDevSourceRoot() { - const root = makeTempDir(); + const root = makePluginLoaderTempDir(); fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "openclaw" }), "utf-8"); mkdirSafe(path.join(root, "src")); mkdirSafe(path.join(root, "extensions")); @@ -217,7 +217,7 @@ export function writeWorkspacePlugin(params: { filename?: string; workspaceDir?: string; }) { - const workspaceDir = params.workspaceDir ?? makeTempDir(); + const workspaceDir = params.workspaceDir ?? makePluginLoaderTempDir(); const workspacePluginDir = path.join(workspaceDir, ".openclaw", "extensions", params.id); mkdirSafe(workspacePluginDir); const plugin = writePlugin({ @@ -230,7 +230,7 @@ export function writeWorkspacePlugin(params: { } export function withStateDir(run: (stateDir: string) => T) { - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); return withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => run(stateDir)); } @@ -254,7 +254,7 @@ export function loadBundledMemoryPluginRegistry(options?: { }); } - const bundledDir = makeTempDir(); + const bundledDir = makePluginLoaderTempDir(); let pluginDir = bundledDir; let pluginFilename = options?.pluginFilename ?? "memory-core.cjs"; @@ -306,7 +306,7 @@ export function loadBundledMemoryPluginRegistry(options?: { export function setupBundledTelegramPlugin() { if (!cachedBundledTelegramDir) { - cachedBundledTelegramDir = makeTempDir(); + cachedBundledTelegramDir = makePluginLoaderTempDir(); writePlugin({ id: "telegram", body: BUNDLED_TELEGRAM_PLUGIN_BODY, @@ -597,8 +597,8 @@ export function createErrorLogger(errors: string[]) { } function createEscapingEntryFixture(params: { id: string; sourceBody: string }) { - const pluginDir = makeTempDir(); - const outsideDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); + const outsideDir = makePluginLoaderTempDir(); const outsideEntry = path.join(outsideDir, "outside.cjs"); const linkedEntry = path.join(pluginDir, "entry.cjs"); fs.writeFileSync(outsideEntry, params.sourceBody, "utf-8"); @@ -676,7 +676,7 @@ export function createSetupEntryChannelPluginFixture(params: { requireBundledFullRuntimeBeforeLoad?: boolean; }) { useNoBundledPlugins(); - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const fullMarker = path.join(pluginDir, "full-loaded.txt"); const setupMarker = path.join(pluginDir, "setup-loaded.txt"); const listAccountIds = params.configured ? '["default"]' : "[]"; @@ -888,9 +888,9 @@ module.exports = { export function createEnvResolvedPluginFixture(pluginId: string) { useNoBundledPlugins(); - const openclawHome = makeTempDir(); - const ignoredHome = makeTempDir(); - const stateDir = makeTempDir(); + const openclawHome = makePluginLoaderTempDir(); + const ignoredHome = makePluginLoaderTempDir(); + const stateDir = makePluginLoaderTempDir(); const pluginDir = path.join(openclawHome, "plugins", pluginId); mkdirSafe(pluginDir); const plugin = writePlugin({ diff --git a/src/plugins/plugin-graceful-init-failure.test.ts b/src/plugins/plugin-graceful-init-failure.test.ts index f841d994ea6a..2405546ca349 100644 --- a/src/plugins/plugin-graceful-init-failure.test.ts +++ b/src/plugins/plugin-graceful-init-failure.test.ts @@ -13,7 +13,7 @@ afterAll(() => { cleanupTrackedTempDirs(fixtureTempDirs); }); -function makeTempDir() { +function makePluginLoaderTempDir() { const dir = path.join(fixtureRoot, `case-${tempDirIndex++}`); fs.mkdirSync(dir, { recursive: true }); return dir; @@ -24,7 +24,7 @@ function writePlugin(params: { id: string; body: string; dir?: string }): { file: string; dir: string; } { - const dir = params.dir ?? makeTempDir(); + const dir = params.dir ?? makePluginLoaderTempDir(); fs.mkdirSync(dir, { recursive: true }); const filename = `${params.id}.cjs`; const file = path.join(dir, filename); diff --git a/src/plugins/providers.setup-trust.test.ts b/src/plugins/providers.setup-trust.test.ts index f97fc4565440..1250c7feb402 100644 --- a/src/plugins/providers.setup-trust.test.ts +++ b/src/plugins/providers.setup-trust.test.ts @@ -18,7 +18,7 @@ afterEach(() => { resetPluginLoaderTestStateForTest(); }); -function makeTempDir() { +function makePluginLoaderTempDir() { return makeTrackedTempDir("openclaw-provider-setup-trust", tempDirs); } @@ -89,7 +89,7 @@ module.exports = { describe("setup provider workspace trust", () => { it("does not import untrusted workspace provider plugins during default setup discovery", () => { - const runRoot = makeTempDir(); + const runRoot = makePluginLoaderTempDir(); const workspaceDir = path.join(runRoot, "workspace"); const stateDir = path.join(runRoot, "state"); const markerDir = path.join(runRoot, "markers"); @@ -130,7 +130,7 @@ describe("setup provider workspace trust", () => { }); it("loads explicitly trusted workspace provider plugins during setup discovery", () => { - const runRoot = makeTempDir(); + const runRoot = makePluginLoaderTempDir(); const workspaceDir = path.join(runRoot, "workspace"); const stateDir = path.join(runRoot, "state"); const markerDir = path.join(runRoot, "markers"); diff --git a/src/plugins/status.compatibility.integration.test.ts b/src/plugins/status.compatibility.integration.test.ts index 9eaaf42c4af5..5bbb7ab2717e 100644 --- a/src/plugins/status.compatibility.integration.test.ts +++ b/src/plugins/status.compatibility.integration.test.ts @@ -6,7 +6,7 @@ import { withEnv } from "../test-utils/env.js"; import { cleanupPluginLoaderFixturesForTest, loadOpenClawPlugins, - makeTempDir, + makePluginLoaderTempDir, resetPluginLoaderTestStateForTest, useNoBundledPlugins, writePlugin, @@ -24,7 +24,7 @@ function addStartupActivation(pluginDir: string, onStartup: boolean): void { } function buildSnapshotCompatibilityNoticeCodes(plugin: { dir: string; file: string; id: string }) { - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); return withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => { useNoBundledPlugins(); return buildPluginCompatibilitySnapshotNotices({ @@ -69,14 +69,14 @@ describe("plugin compatibility snapshot notices", () => { }); it("reports actual hook-only registrations without activating cold plugin modules", () => { - const pluginDir = makeTempDir(); + const pluginDir = makePluginLoaderTempDir(); const runtimeMarker = path.join(pluginDir, "runtime-loaded"); const plugin = writePlugin({ id: "runtime-hook-only", dir: pluginDir, body: `module.exports = { id: "runtime-hook-only", register(api) { require("node:fs").writeFileSync(${JSON.stringify(runtimeMarker)}, "loaded"); api.on("message_received", () => {}); } };\n`, }); - const stateDir = makeTempDir(); + const stateDir = makePluginLoaderTempDir(); const config = { plugins: { load: { paths: [plugin.file] }, diff --git a/src/plugins/test-helpers/fs-fixtures.ts b/src/plugins/test-helpers/fs-fixtures.ts index 9d9ee13206a5..fbbc32f6cb13 100644 --- a/src/plugins/test-helpers/fs-fixtures.ts +++ b/src/plugins/test-helpers/fs-fixtures.ts @@ -58,7 +58,7 @@ export async function cleanupTrackedTempDirsAsync(trackedDirs: string[]) { } /** Creates a per-suite temp-root tracker with deterministic case directory names. */ -export function createSuiteTempRootTracker(prefix: string, baseDir = os.tmpdir()) { +export function createSyncSuiteTempRootTracker(prefix: string, baseDir = os.tmpdir()) { let suiteTempRoot = ""; let tempDirCounter = 0; diff --git a/src/shared/chat-message-content.test.ts b/src/shared/chat-message-content.test.ts index 35a28d3b4bd7..575ee653bb9f 100644 --- a/src/shared/chat-message-content.test.ts +++ b/src/shared/chat-message-content.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { extractAssistantTextForPhase, - extractAssistantVisibleText, + extractAssistantPhaseText, extractFirstTextBlock, parseAssistantTextSignature, resolveAssistantMessagePhase, @@ -55,7 +55,7 @@ describe("shared/chat-message-content", () => { }); }); -describe("extractAssistantVisibleText", () => { +describe("extractAssistantPhaseText", () => { it("preserves boundary spacing when joining adjacent final_answer text blocks", () => { expect( extractAssistantTextForPhase( @@ -81,7 +81,7 @@ describe("extractAssistantVisibleText", () => { it("prefers final_answer text over commentary text", () => { expect( - extractAssistantVisibleText({ + extractAssistantPhaseText({ role: "assistant", content: [ { @@ -101,7 +101,7 @@ describe("extractAssistantVisibleText", () => { it("does not fall back to commentary-only text", () => { expect( - extractAssistantVisibleText({ + extractAssistantPhaseText({ role: "assistant", content: [ { @@ -116,7 +116,7 @@ describe("extractAssistantVisibleText", () => { it("does not fall back to unphased legacy text when final_answer is empty", () => { expect( - extractAssistantVisibleText({ + extractAssistantPhaseText({ role: "assistant", content: [ { type: "text", text: "Legacy answer" }, @@ -132,7 +132,7 @@ describe("extractAssistantVisibleText", () => { it("falls back to unphased legacy text", () => { expect( - extractAssistantVisibleText({ + extractAssistantPhaseText({ role: "assistant", content: [{ type: "text", text: "Legacy answer" }], }), @@ -141,7 +141,7 @@ describe("extractAssistantVisibleText", () => { it("extracts persisted Responses output_text blocks as assistant-visible text", () => { expect( - extractAssistantVisibleText({ + extractAssistantPhaseText({ role: "assistant", content: [{ type: "output_text", text: "Persisted assistant answer" }], }), @@ -150,7 +150,7 @@ describe("extractAssistantVisibleText", () => { it("extracts persisted Responses assistant input_text blocks", () => { expect( - extractAssistantVisibleText({ + extractAssistantPhaseText({ role: "assistant", content: [{ type: "input_text", text: "Persisted assistant input" }], }), @@ -159,7 +159,7 @@ describe("extractAssistantVisibleText", () => { it("does not mix unphased legacy text into final_answer output", () => { expect( - extractAssistantVisibleText({ + extractAssistantPhaseText({ role: "assistant", phase: "final_answer", content: [ diff --git a/src/shared/chat-message-content.ts b/src/shared/chat-message-content.ts index af4ed953dacf..cee1f9d00ed3 100644 --- a/src/shared/chat-message-content.ts +++ b/src/shared/chat-message-content.ts @@ -216,7 +216,7 @@ export function extractAssistantTextForPhase( } /** Returns user-visible assistant text, preferring final answers over legacy unphased text. */ -export function extractAssistantVisibleText(message: unknown): string | undefined { +export function extractAssistantPhaseText(message: unknown): string | undefined { const finalAnswerText = extractAssistantTextForPhase(message, { phase: "final_answer" }); if (finalAnswerText) { return finalAnswerText; diff --git a/src/system-agent/setup-inference-activate-persist.ts b/src/system-agent/setup-inference-activate-persist.ts index 5754977ad79e..6f2e5b8cf49c 100644 --- a/src/system-agent/setup-inference-activate-persist.ts +++ b/src/system-agent/setup-inference-activate-persist.ts @@ -19,7 +19,7 @@ import { createSystemAgentModelSelectionUpdater } from "./setup-apply.js"; import { SetupInferenceActivationIndeterminateError, SetupInferenceActivationUnavailableError, - log, + setupInferenceLog, throwIfSetupInferenceCancelled, type ActivateSetupInferenceDeps, type ActivateSetupInferenceParams, @@ -361,7 +361,9 @@ export async function persistActivatedSetupInference(input: { throw error; } committedConfig = reconciledSnapshot?.sourceConfig ?? reconciledRuntime; - log.warn("Inference activation committed successfully despite a post-write cleanup error."); + setupInferenceLog.warn( + "Inference activation committed successfully despite a post-write cleanup error.", + ); } state.committedConfig = committedConfig; diff --git a/src/system-agent/setup-inference-core.ts b/src/system-agent/setup-inference-core.ts index 6f3d9e656801..6d09c1996c6e 100644 --- a/src/system-agent/setup-inference-core.ts +++ b/src/system-agent/setup-inference-core.ts @@ -38,7 +38,7 @@ import { type SystemAgentVerifiedInferenceDeps, } from "./verified-inference.js"; -export const log = createSubsystemLogger("system-agent/setup-inference"); +export const setupInferenceLog = createSubsystemLogger("system-agent/setup-inference"); /** * Inference is the one required onboarding step (docs/cli/setup.md diff --git a/src/system-agent/setup-inference-detect.ts b/src/system-agent/setup-inference-detect.ts index 7686a758f8c9..65a21cbeccc5 100644 --- a/src/system-agent/setup-inference-detect.ts +++ b/src/system-agent/setup-inference-detect.ts @@ -25,7 +25,7 @@ import { type SetupInferenceDetection, type SetupInferenceUnavailableCandidate, invalidSetupConfigError, - log, + setupInferenceLog, resolveCandidatePresentation, resolveSetupInferenceWorkspace, toProviderAutoSetupKind, @@ -223,7 +223,7 @@ export async function detectSetupInference( !ref.model || normalizeProviderId(ref.provider) !== normalizeProviderId(choice.providerId) ) { - log.warn( + setupInferenceLog.warn( `Ignoring invalid app-guided model ${candidate.modelRef} from ${choice.choiceId}.`, ); return null; @@ -242,7 +242,7 @@ export async function detectSetupInference( choice.website ? { website: choice.website } : {}, ); } catch (error) { - log.debug( + setupInferenceLog.debug( `App-guided discovery failed for ${choice.choiceId}: ${formatErrorMessage(error)}`, ); return null; diff --git a/src/system-agent/setup-inference-persist.ts b/src/system-agent/setup-inference-persist.ts index 9a46be6cf1c0..46306c876da0 100644 --- a/src/system-agent/setup-inference-persist.ts +++ b/src/system-agent/setup-inference-persist.ts @@ -24,7 +24,7 @@ import { SETUP_INFERENCE_TEST_TIMEOUT_MS, SetupInferenceCancelledError, type SetupInferenceFailureStatus, - log, + setupInferenceLog, } from "./setup-inference-core.js"; import { type RunResult, @@ -51,7 +51,7 @@ export async function cleanupSetupInferenceTempDir(params: { } catch { // Windows cannot remove an open SQLite file. Keep cleanup nonfatal, but // always try the directory removal so callers do not retain probe secrets. - log.warn("Could not dispose the temporary inference auth database."); + setupInferenceLog.warn("Could not dispose the temporary inference auth database."); } try { await ( @@ -63,7 +63,7 @@ export async function cleanupSetupInferenceTempDir(params: { params.runtime?.error?.( `Could not remove temporary AI setup files: ${formatErrorMessage(error)}`, ); - log.warn("Could not remove the temporary inference test directory."); + setupInferenceLog.warn("Could not remove the temporary inference test directory."); } } @@ -107,13 +107,13 @@ export async function retainUnownedCodexInstall(params: { reason: "openclaw-inference-activation-not-committed", }); if (!marked) { - log.warn("Could not retain the uncommitted Codex runtime package generation."); + setupInferenceLog.warn("Could not retain the uncommitted Codex runtime package generation."); } return marked; } catch { // Retention is best effort and marker-after-adoption is non-destructive. // A later install or GC may still reuse or remove the unowned generation. - log.warn("Could not retain the uncommitted Codex runtime package generation."); + setupInferenceLog.warn("Could not retain the uncommitted Codex runtime package generation."); return false; } finally { await clearUnownedCodexInstallCaches(params.deps); @@ -128,7 +128,9 @@ async function clearUnownedCodexInstallCaches(deps: ActivateSetupInferenceDeps): .clearLoadInstalledPluginIndexInstallRecordsCache; clearInstallRecords(); } catch { - log.warn("Could not clear the plugin install-record cache after failed Codex activation."); + setupInferenceLog.warn( + "Could not clear the plugin install-record cache after failed Codex activation.", + ); } try { const clearPluginMetadata = @@ -136,16 +138,18 @@ async function clearUnownedCodexInstallCaches(deps: ActivateSetupInferenceDeps): (await import("../plugins/plugin-metadata-lifecycle.js")).clearPluginMetadataLifecycleCaches; clearPluginMetadata(); } catch { - log.warn("Could not clear plugin metadata caches after failed Codex activation."); + setupInferenceLog.warn("Could not clear plugin metadata caches after failed Codex activation."); } try { const invalidateRuntimeDiscovery = deps.invalidatePluginRuntimeDiscoveryAfterConfigMutation ?? (await import("../plugins/registry-refresh.js")) .invalidatePluginRuntimeDiscoveryAfterConfigMutation; - await invalidateRuntimeDiscovery({ logger: log }); + await invalidateRuntimeDiscovery({ logger: setupInferenceLog }); } catch { - log.warn("Could not clear plugin runtime discovery after failed Codex activation."); + setupInferenceLog.warn( + "Could not clear plugin runtime discovery after failed Codex activation.", + ); } } @@ -160,7 +164,9 @@ export async function reloadCodexRegistryAfterActivation(params: { try { snapshot = await params.readSnapshot(); } catch { - log.warn("Could not read config while reloading the plugin registry after Codex activation."); + setupInferenceLog.warn( + "Could not read config while reloading the plugin registry after Codex activation.", + ); return false; } const runtimeConfig = @@ -179,10 +185,12 @@ export async function reloadCodexRegistryAfterActivation(params: { config: sourceConfig, reason: "source-changed", workspaceDir: params.workspaceDir, - logger: log, + logger: setupInferenceLog, }); } catch { - log.warn("Could not refresh persisted plugin registry metadata after Codex activation."); + setupInferenceLog.warn( + "Could not refresh persisted plugin registry metadata after Codex activation.", + ); } try { const ensurePluginRegistryLoaded = @@ -196,7 +204,9 @@ export async function reloadCodexRegistryAfterActivation(params: { }); return true; } catch { - log.warn("Could not reload the active plugin registry after Codex inference activation."); + setupInferenceLog.warn( + "Could not reload the active plugin registry after Codex inference activation.", + ); return false; } } diff --git a/src/tasks/task-registry-delivery.ts b/src/tasks/task-registry-delivery.ts index dbf94c568126..ad574bbc7503 100644 --- a/src/tasks/task-registry-delivery.ts +++ b/src/tasks/task-registry-delivery.ts @@ -31,7 +31,7 @@ import { ensureTaskRegistryReady, getPeerTasksForDelivery, loadTaskRegistryDeliveryRuntime, - log, + taskRegistryLog, pickPreferredRunIdTask, taskDeliveryStates, tasks, @@ -290,7 +290,7 @@ async function maybeDeliverTaskTerminalUpdateUnderAdmission( lastEventAt: Date.now(), }); } catch (error) { - log.warn("Failed to queue background task session delivery", { + taskRegistryLog.warn("Failed to queue background task session delivery", { taskId, ownerKey: latest.ownerKey, error, @@ -335,7 +335,7 @@ async function maybeDeliverTaskTerminalUpdateUnderAdmission( lastEventAt: Date.now(), }); } catch (error) { - log.warn("Failed to deliver background task update", { + taskRegistryLog.warn("Failed to deliver background task update", { taskId, ownerKey: ownerSessionKey, requesterOrigin: owner.requesterOrigin, @@ -351,7 +351,7 @@ async function maybeDeliverTaskTerminalUpdateUnderAdmission( queueBlockedTaskFollowup(beforeFallback); } } catch (fallbackError) { - log.warn("Failed to queue background task fallback event", { + taskRegistryLog.warn("Failed to queue background task fallback event", { taskId, ownerKey: latest.ownerKey, error: fallbackError, @@ -443,7 +443,7 @@ async function maybeDeliverTaskStateChangeUpdateUnderAdmission( lastEventAt: Date.now(), }); } catch (error) { - log.warn("Failed to deliver background task state change", { + taskRegistryLog.warn("Failed to deliver background task state change", { taskId, ownerKey: current.ownerKey, error, diff --git a/src/tasks/task-registry-mutation.ts b/src/tasks/task-registry-mutation.ts index 334c1f6e779d..2aa95e8ba312 100644 --- a/src/tasks/task-registry-mutation.ts +++ b/src/tasks/task-registry-mutation.ts @@ -24,7 +24,7 @@ import { deleteParentFlowIdIndex, deleteRelatedSessionKeyIndex, emitTaskRegistryObserverEvent, - log, + taskRegistryLog, rebuildRunIdIndex, taskDeliveryStates, taskFlowSyncRetryTimers, @@ -92,7 +92,7 @@ function scheduleTaskFlowSyncRetry(task: TaskRecord, operation: string, attempt } const delayMs = TASK_FLOW_SYNC_RETRY_DELAYS_MS[attempt]; if (delayMs == null) { - log.warn("Exhausted parent flow sync retries from task", { + taskRegistryLog.warn("Exhausted parent flow sync retries from task", { operation, taskId, flowId: task.parentFlowId, @@ -115,7 +115,7 @@ function scheduleTaskFlowSyncRetry(task: TaskRecord, operation: string, attempt } const result = syncFlowFromTaskResult(current); if (!result.ok) { - log.warn("Failed to retry parent flow sync from task", { + taskRegistryLog.warn("Failed to retry parent flow sync from task", { operation, taskId, flowId: current.parentFlowId, @@ -124,7 +124,7 @@ function scheduleTaskFlowSyncRetry(task: TaskRecord, operation: string, attempt scheduleTaskFlowSyncRetry(current, operation, attempt + 1); } }).catch((error: unknown) => { - log.warn("Failed to admit parent flow sync retry from task", { + taskRegistryLog.warn("Failed to admit parent flow sync retry from task", { operation, taskId, flowId: task.parentFlowId, @@ -141,7 +141,7 @@ export function syncFlowFromTaskAfterTaskMutation(task: TaskRecord, operation: s if (result.ok) { return; } - log.warn("Failed to sync parent flow from task mutation", { + taskRegistryLog.warn("Failed to sync parent flow from task mutation", { operation, taskId: task.taskId, flowId: task.parentFlowId, @@ -209,7 +209,7 @@ export function updateTask(taskId: string, patch: Partial): TaskReco try { syncManagedFlowCancellationFromTask(next); } catch (error) { - log.warn("Failed to finalize managed flow cancellation from task update", { + taskRegistryLog.warn("Failed to finalize managed flow cancellation from task update", { taskId, flowId: next.parentFlowId, error, diff --git a/src/tasks/task-registry-query.ts b/src/tasks/task-registry-query.ts index b0030fa8becb..62b871702a7b 100644 --- a/src/tasks/task-registry-query.ts +++ b/src/tasks/task-registry-query.ts @@ -17,7 +17,7 @@ import { emitTaskRegistryObserverEvent, ensureTaskRegistryReady, getTasksByRunId, - log, + taskRegistryLog, persistTaskRegistry, pickPreferredRunIdTask, rebuildRunIdIndex, @@ -223,7 +223,7 @@ export function listFreshTasksForOwnerKey(ownerKey: string): TaskRecord[] { .toSorted(compareTasksNewestFirst) .map(({ insertionIndex: _, ...task }) => task); } catch (error) { - log.warn("Failed to read fresh owner task registry records", { + taskRegistryLog.warn("Failed to read fresh owner task registry records", { ownerKey: key, error, }); diff --git a/src/tasks/task-registry-state.ts b/src/tasks/task-registry-state.ts index dd5ae00f9d09..de9dfa0f32cd 100644 --- a/src/tasks/task-registry-state.ts +++ b/src/tasks/task-registry-state.ts @@ -18,7 +18,7 @@ import { } from "./task-registry.store.js"; import type { TaskDeliveryState, TaskRecord, TaskRuntime } from "./task-registry.types.js"; -export const log = createSubsystemLogger("tasks/registry"); +export const taskRegistryLog = createSubsystemLogger("tasks/registry"); export const TASK_FLOW_SYNC_RETRY_DELAYS_MS = [1_000, 5_000, 25_000, 120_000, 600_000] as const; const taskRegistryProcessState = getTaskRegistryProcessState(); @@ -117,7 +117,7 @@ export function emitTaskRegistryObserverEvent(createEvent: () => TaskRegistryObs try { observers.onEvent(createEvent()); } catch (error) { - log.warn("Task registry observer failed", { + taskRegistryLog.warn("Task registry observer failed", { event: "task-registry", error, }); @@ -132,7 +132,7 @@ export function persistTaskRegistry(): boolean { }); return true; } catch (error) { - log.warn("Failed to persist task registry snapshot", { error }); + taskRegistryLog.warn("Failed to persist task registry snapshot", { error }); return false; } } @@ -171,7 +171,7 @@ export function tryPersistTaskUpsert( persistTaskUpsert(task, pendingDeliveryState); return true; } catch (error) { - log.warn("Failed to persist task registry upsert", { + taskRegistryLog.warn("Failed to persist task registry upsert", { operation, taskId: task.taskId, runId: task.runId, @@ -213,7 +213,7 @@ export function tryPersistTaskDelete(taskId: string): boolean { persistTaskDelete(taskId); return true; } catch (error) { - log.warn("Failed to persist task registry delete", { + taskRegistryLog.warn("Failed to persist task registry delete", { taskId, error, }); @@ -240,7 +240,7 @@ export function tryPersistTaskDeliveryStateUpsert(state: TaskDeliveryState): boo persistTaskDeliveryStateUpsert(state); return true; } catch (error) { - log.warn("Failed to persist task delivery state", { + taskRegistryLog.warn("Failed to persist task delivery state", { taskId: state.taskId, error, }); @@ -538,7 +538,7 @@ export function restoreTaskRegistryOnce() { const restoreError = new Error(`Task registry restore failed: ${message}`, { cause: error }); taskRegistryRestoreState = { status: "failed", error: restoreError }; // Compact console logs omit structured metadata, so keep the rejected value visible there too. - log.warn("Failed to restore task registry", { + taskRegistryLog.warn("Failed to restore task registry", { error: message, consoleMessage: `Failed to restore task registry: ${message}`, }); diff --git a/src/tui/tui-formatters.ts b/src/tui/tui-formatters.ts index b541e58845b1..a13dcef0d764 100644 --- a/src/tui/tui-formatters.ts +++ b/src/tui/tui-formatters.ts @@ -7,7 +7,7 @@ import type { SessionGoal } from "../config/sessions/types.js"; import { formatErrorMessage } from "../infra/errors.js"; import { isImageMediaFact, readPersistedMediaFacts } from "../media/media-facts.js"; import { formatRawAssistantErrorForUi } from "../shared/assistant-error-format.js"; -import { extractAssistantVisibleText } from "../shared/chat-message-content.js"; +import { extractAssistantPhaseText } from "../shared/chat-message-content.js"; import { chunkTextByBreakResolver } from "../shared/text-chunking.js"; import { formatTokenCount } from "../utils/usage-format.js"; import type { SessionInfo } from "./tui-types.js"; @@ -545,7 +545,7 @@ export function extractContentFromMessage(message: unknown): string { } function extractAssistantRenderableContent(record: Record): string { - const visible = sanitizeRenderableText(extractAssistantVisibleText(record) ?? "").trim(); + const visible = sanitizeRenderableText(extractAssistantPhaseText(record) ?? "").trim(); const pairingQr = extractPairingQrTerminalText(record); const content = [visible, pairingQr].filter(Boolean).join("\n\n").trim(); if (content) { diff --git a/src/utils/fetch-timeout.test.ts b/src/utils/fetch-timeout.test.ts index 9b5e9838e3ea..47d15ac7a17c 100644 --- a/src/utils/fetch-timeout.test.ts +++ b/src/utils/fetch-timeout.test.ts @@ -10,8 +10,8 @@ vi.mock("../logging/subsystem.js", () => ({ })), })); +import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../../packages/gateway-client/src/timeouts.js"; import { buildTimeoutAbortSignal, fetchWithTimeout } from "./fetch-timeout.js"; -import { MAX_SAFE_TIMEOUT_DELAY_MS } from "./timer-delay.js"; function captureTimeoutLogUrl(url: string): Promise> { const { cleanup } = buildTimeoutAbortSignal({ timeoutMs: 25, operation: "unit-test", url }); diff --git a/src/utils/timer-delay.test.ts b/src/utils/timer-delay.test.ts index 10179edc6c31..7c10c766eb9a 100644 --- a/src/utils/timer-delay.test.ts +++ b/src/utils/timer-delay.test.ts @@ -1,6 +1,7 @@ // Timer delay tests cover safe timeout clamping and scheduling behavior. import { describe, expect, it, vi } from "vitest"; -import { MAX_SAFE_TIMEOUT_DELAY_MS, setSafeTimeout } from "./timer-delay.js"; +import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../../packages/gateway-client/src/timeouts.js"; +import { setSafeTimeout } from "./timer-delay.js"; describe("setSafeTimeout", () => { it("arms setTimeout with the clamped delay", () => { diff --git a/src/utils/timer-delay.ts b/src/utils/timer-delay.ts index 261fb3665a98..ecbfe2cf28b4 100644 --- a/src/utils/timer-delay.ts +++ b/src/utils/timer-delay.ts @@ -3,7 +3,6 @@ import { resolveSafeTimeoutDelayMs } from "../../packages/gateway-client/src/tim export { addSafeTimeoutDelayGraceMs, - MAX_SAFE_TIMEOUT_DELAY_MS, resolveSafeTimeoutDelayMs, } from "../../packages/gateway-client/src/timeouts.js"; diff --git a/test/e2e/qa-lab/runtime/gateway-direct-tool-invoke.e2e.test.ts b/test/e2e/qa-lab/runtime/gateway-direct-tool-invoke.e2e.test.ts index e0b50ce43f16..3b48242dd485 100644 --- a/test/e2e/qa-lab/runtime/gateway-direct-tool-invoke.e2e.test.ts +++ b/test/e2e/qa-lab/runtime/gateway-direct-tool-invoke.e2e.test.ts @@ -9,7 +9,7 @@ import { disconnectGatewayClient, } from "../../../../src/gateway/test-helpers.e2e.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, startTestGatewayServer, testState, @@ -90,7 +90,7 @@ describe("Gateway direct tool invoke product proof", () => { "enforces public auth, invocation, approval, and call-id contracts", { timeout: 60_000 }, async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const configPath = createConfigIO().configPath; await fs.mkdir(path.dirname(configPath), { recursive: true }); await fs.writeFile( diff --git a/test/e2e/qa-lab/runtime/gateway-exec-approvals.e2e.test.ts b/test/e2e/qa-lab/runtime/gateway-exec-approvals.e2e.test.ts index 4325e523fb37..a9b9ec08e4b8 100644 --- a/test/e2e/qa-lab/runtime/gateway-exec-approvals.e2e.test.ts +++ b/test/e2e/qa-lab/runtime/gateway-exec-approvals.e2e.test.ts @@ -8,7 +8,7 @@ import { disconnectGatewayClient, } from "../../../../src/gateway/test-helpers.e2e.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, startTestGatewayServer, } from "../../../../src/gateway/test-helpers.js"; @@ -36,7 +36,7 @@ describe("gateway exec approvals QA", () => { }); it("protects policy snapshots and resolves a pending request from another reviewer", async () => { - const port = await getFreePort(); + const port = await getGatewayTestPort(); const token = "gateway-exec-approvals-qa-token"; const stateDir = process.env.OPENCLAW_STATE_DIR; if (!stateDir) { diff --git a/test/e2e/qa-lab/runtime/gateway-http-apis.e2e.test.ts b/test/e2e/qa-lab/runtime/gateway-http-apis.e2e.test.ts index c64efa2651c6..2ec38f1031e7 100644 --- a/test/e2e/qa-lab/runtime/gateway-http-apis.e2e.test.ts +++ b/test/e2e/qa-lab/runtime/gateway-http-apis.e2e.test.ts @@ -7,8 +7,8 @@ import { describe, expect, it } from "vitest"; import { createConfigIO, resetConfigRuntimeState } from "../../../../src/config/config.js"; import { resolveMainSessionKeyFromConfig } from "../../../../src/config/sessions.js"; import { - agentCommand, - getFreePort, + agentCommandMock, + getGatewayTestPort, installGatewayTestHooks, startTestGatewayServer, testState, @@ -118,11 +118,11 @@ describe("Gateway HTTP API product proof", () => { }, }; testState.hooksConfig = { enabled: true, token: HOOK_TOKEN }; - agentCommand + agentCommandMock .mockResolvedValueOnce({ payloads: [{ text: "qa chat response" }] } as never) .mockResolvedValueOnce({ payloads: [{ text: "qa responses response" }] } as never); - const port = await getFreePort(); + const port = await getGatewayTestPort(); gateway = await startTestGatewayServer(port, { host: "127.0.0.1", auth: { mode: "token", token: GATEWAY_TOKEN }, @@ -174,8 +174,8 @@ describe("Gateway HTTP API product proof", () => { type: "output_text", text: "qa responses response", }); - expect(agentCommand).toHaveBeenCalledTimes(2); - expect(agentCommand.mock.calls.map((call) => call[0])).toEqual([ + expect(agentCommandMock).toHaveBeenCalledTimes(2); + expect(agentCommandMock.mock.calls.map((call) => call[0])).toEqual([ expect.objectContaining({ message: "qa chat request", sessionKey: expect.stringMatching(/^agent:main:openai:/), diff --git a/test/e2e/qa-lab/runtime/gateway-plugin-approvals.e2e.test.ts b/test/e2e/qa-lab/runtime/gateway-plugin-approvals.e2e.test.ts index d32531a47bde..5ef45566b9ef 100644 --- a/test/e2e/qa-lab/runtime/gateway-plugin-approvals.e2e.test.ts +++ b/test/e2e/qa-lab/runtime/gateway-plugin-approvals.e2e.test.ts @@ -9,7 +9,7 @@ import { disconnectGatewayClient, } from "../../../../src/gateway/test-helpers.e2e.js"; import { - getFreePort, + getGatewayTestPort, installGatewayTestHooks, startTestGatewayServer, } from "../../../../src/gateway/test-helpers.js"; @@ -77,7 +77,7 @@ describe("gateway plugin approvals QA", () => { expect(reviewerIdentity.deviceId).not.toBe(requesterIdentity.deviceId); markStage("gateway start"); - const port = await getFreePort(); + const port = await getGatewayTestPort(); const token = "gateway-plugin-approvals-qa-token"; const url = `ws://127.0.0.1:${port}`; const server = await startTestGatewayServer(port, { diff --git a/ui/src/lib/chat/message-extract.ts b/ui/src/lib/chat/message-extract.ts index 7f873b0671c1..7a8ae3938c00 100644 --- a/ui/src/lib/chat/message-extract.ts +++ b/ui/src/lib/chat/message-extract.ts @@ -3,7 +3,7 @@ import { stripInternalRuntimeContext } from "../../../../src/agents/internal-run import { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js"; import { readPersistedMediaFacts } from "../../../../src/media/media-facts.js"; import { stripEnvelope } from "../../../../src/shared/chat-envelope.js"; -import { extractAssistantVisibleText as extractSharedAssistantVisibleText } from "../../../../src/shared/chat-message-content.js"; +import { extractAssistantPhaseText } from "../../../../src/shared/chat-message-content.js"; import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts"; import { stripThinkingTags } from "../strip-thinking-tags.ts"; @@ -37,8 +37,7 @@ export function extractText(message: unknown): string | null { } const m = message as Record; const role = typeof m.role === "string" ? m.role : ""; - const raw = - role === "assistant" ? extractSharedAssistantVisibleText(message) : extractRawText(message); + const raw = role === "assistant" ? extractAssistantPhaseText(message) : extractRawText(message); if (!raw) { return null; }