diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 8b4268117a0e..9abc3cdc1c10 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,12 +1,12 @@ -9d7085b7385dd5023db6acc8acd8b7d6da3b0660149e43aa83bfc3ee77291ebe module/account-core +f3366ad66e4747b1a0e09e291386945ce49822b1c87a69ebb8a67d3b7dd1bf82 module/account-core 011992a0d8c35afc03326a104a40107d3701cc1eddb26198a1e38fbcd5bb37d3 module/account-helpers 71522995185b956a0cc4927a472cc8d1153e5e998874bfd9a750513175174713 module/account-id -d768139934447ff3ecf15470dc1fe613d36509fc5b93fc47ef09d20829cefa57 module/account-resolution +3caf3676d1fd724e34af9c0314c58acd30156cc6e0a84b1342bdbf180957db3f module/account-resolution 4fbb1c87e99399f842a20d75d5e35a4b7064a1b7f02115c23f9a2a7cdcfb57ee module/agent-config-primitives -c478693b37d1a500a090755abcc47e495fd38682ffcb499c73278f5e0230b038 module/agent-harness -1d92235699fb9af065d0b2d604d481f933b5e382bcef61615b7e85413498fc30 module/agent-harness-runtime +f9ce48d3fa171903ec343e22c3c1b1eca7e7134bcc093ae77702f97df2c5c2de module/agent-harness +76d0ebfb20e10a27f56a2cd5b0ea0d75f1622d5c774abc2ce6cc5f6d99317663 module/agent-harness-runtime d6097cfa1b410f4b5267a56a7bd19c2a33fbaf6642683dae6aec68e998e48f6c module/agent-media-payload -9d2625e7359beaa6126a0cc4de942802c8fc567b34ab0a032e86d58aad80ee52 module/agent-runtime +bbd04b6475d269f5fd5449f4938a95a56b3ee598558e454fcd72ce7d27937228 module/agent-runtime b57a3cb274a9977c48c5387772df7707e50dde9a6132e532e7367a27225dc142 module/agent-scope-runtime 8fecb210e22bce4532b6ab649b09465f0bd2c857a44abf40db7d683d6491e6da module/allow-from bf66a0447f3d1e16a757843d068e56275f4781e9b427221fca26a854c3597467 module/allowlist-config-edit @@ -26,33 +26,33 @@ afad33fdaada25984db53504dc6f665ff0478c12f68b79f36a39d28ceb13355e module/channel c2cc71d5070b6071c51248b0648d1ad1a9468d3737df890adc77ec02025e8853 module/channel-config-primitives a6cca5706f3aba6abb2178b175a0986d921ade98c2c05d2a54451e2fb7e16825 module/channel-config-schema 37925e2b8c74b4444a14ea85b831ab569df9d46efebe89714527ee638719c100 module/channel-contract -324bf658bb7b57c479d054a181e6530dd45c5168be139337209aaaee972083a3 module/channel-core +4e5c2fa406a73c4fa4cc7615cd68cc0ff448118d73633744bb02743ffbe2d0f7 module/channel-core f4a9870d37f3b4e824bc7b0f634e4eb868ae7dd4c5a9693f0105a6677c2ff5f9 module/channel-dm-policy -8cb2934109b2c28e2cb0fa6a18ee9e8156c4acd08d4fbca7415c728afdc351b7 module/channel-entry-contract +44721dfc97c6bd1df570513116af2261bd1635fc583098f60c25ca3e8a664235 module/channel-entry-contract 47cf8765e76c151ae7d2991d41beca62922a8837c1521e10a3fd23f9992c2d7c module/channel-feedback -0c2c890c0c32d1f1c817b74da17470d405f969d993369f00b86696c94f7ba4af module/channel-inbound +e8c24a0f3aa634dca68820c9f9c22e51885414b0e9ef8ec58045c25e20e353b0 module/channel-inbound 3115366026efa38e07bfe0bfecd483e93454cc7bcad432385139c956777accff module/channel-inbound-debounce bc59c696ee45fb500d105c619c0ec81b8186bebe99135f23dc818ac16004deb0 module/channel-ingress-runtime bae1492066e55ea4f41b6400ab6d2ee454d56ed99b9027a484659fbd1257c46f module/channel-lifecycle 0e47457e38d1df0bd572e1408cde2ca6a788b65205f43c585316b5ad3a8f2f16 module/channel-logging -067819774936133c2e36d8875bf8d0ac7444aa5accce90cefcab2775e3e17ffa module/channel-message -9467897584924d519d3a49f6c67c6ed770488285367c3c0ff97ef6cd20b4ae01 module/channel-outbound -3dd4598811f30fde94247e5fb0321f0cc3c1766b15662e6bdcba314e3b4f077c module/channel-pairing -62c26ad97925427fda9e91d4ff30093fb6cf259eb0c11b0daf03335d69dcbd5b module/channel-plugin-common +d109eb6190219d1dca3795698f475ce55ad28fbd2006c340c90a8ef8dce48cfe module/channel-message +b16b96f02cfb78c7902628917135ad9f62ae45212fd9cc98e585fb6c68dd2b5f module/channel-outbound +d4c406fd7358ada983263ee1c50f0bc8f147429f04d1be918d4ea765ba03a635 module/channel-pairing +87d2695a0e421cda1e6107aaaa2cb4819b52f97ca5f287daf3e3775f939b2da2 module/channel-plugin-common 1786ca2cb6867de9fe386b4d2869c2adada32ddf7b047699aba56fa6dfc00ea9 module/channel-policy 1dbee3d6f0053f5ace9c23f61ffee9d5f82e5b62cdb1155085533fbd606dc99f module/channel-reply-pipeline 482370e60135db9bfaf07f24bab549e5fde09ab265a6061a1f587c5d93929e91 module/channel-runtime-context 241050b8979ed905727789e99011535631dd8c205aa6967401677d78281900de module/channel-secret-basic-runtime 2edd63d294a8ee128a18c1815f6ae53be915569c49ebbad0dd139eb60475b0e6 module/channel-secret-runtime 5e1adc00534c3e182f42b253a824a0096b2d3945998598dc0a4f961eeda05731 module/channel-send-result -b2fcc6f55310e17d13cacf15a8c9250f3c834a3795fa2fed998abf940c543ec0 module/channel-setup -2abcf0cba7e06ed6aae6bd4d35ff7f4c7de11332d5532bc2c61f5e666fcb8d28 module/channel-status +acc47ed08b068f0abcaeed943140c74743ce4f8fcba298d50ede1880958c3553 module/channel-setup +37fbc85717fd07fb62fbd92ae87d4e6c5464e012d97bb3d9c37c8ba2e25e8e4f module/channel-status f6c25ae55d49d90431682ca53c9dd836304942a738425d6ff028146a0c6d2c97 module/channel-streaming b2f920ff4a6b4190e6d6ea0a3effb001751e092f0e3ac0cf296721ff8c383d86 module/channel-streaming-config fdeffe356c7c4edeec9f8fd03edcadc375eabc7a9412e582b10c3180e3ef40fc module/cli-argv ad12670dbfe538f8d0ebf4fb2b68080e93a760278278e6b1ce9bb129d4b2d533 module/collection-runtime -dabcbd78b3d8997bba036e059ca7ad194545c09708ef20b1098d1cb07be0c93c module/command-auth -92d9eacb1ef1a3f262a326e3e7d3d700eb41e2cb55257696117a85b81bf7982e module/command-auth-native +4bc282df81a9e083027dc9f555b1a3456fe0bd1bc0f44e65b3a7cd7667abe919 module/command-auth +2f35b2d562b63992253062cf58aa47ae9e3c7cbb6e1c1cb6d725f3e77bac70e9 module/command-auth-native 50c24235bca2c1d3c011f6bc266b57078b78a76d2d27ee12e7b3245f2947b493 module/command-detection e24382c2cca7fd2cf69ac353e258153dab80b279daf9f0ef54a727981a7dff3c module/command-primitives-runtime 1d6ebcdc843b7072a7fc4475eadadb90a1111d49c1da1e4326775800873cf9f1 module/command-status @@ -60,12 +60,12 @@ ab86235fcfff7c7cf0021fafeca6e92afe2c257ccf6a4a38441e0d41532998ff module/config- 0d99f5cb8c4978ed760e5fb4e476543759fbd8fd5bf73cc50a50c1d550203826 module/config-mutation 74fd2b3e3db22e70eee5e801af9619281ecd2e4ef5d05d25cad05e8a310015aa module/config-runtime d9e5f2ae27e29a40a6d6084c4d59e4811b4669235faa6f0d6a4fbfdd62e51ca3 module/conversation-runtime -b03d0851a1dbe9236fb2fd1e6bd382b36df85283051854f32e120a49140559f6 module/core -e29d95bc1339dda81070d571e73eb15149b889af2e956545082d23985b1c37ec module/dedupe-runtime +97e4061b612b2554aca8742d803ea80c554f95115993de9ad54f1fa47ee5b976 module/core +fa5ac6de172a7b44640e71342e5171dcfb8a4baf683de3d86218bb4e878a19c8 module/dedupe-runtime ebef0e650ab45e44c9335e2b3e15588c968cea6dadd125364a076f9c50ad1e8c module/device-bootstrap 21d86413166ef815581d606f678b6a216a1cc73ffe470b841f5bc4a131bff6df module/diagnostic-runtime 2af0c3b8867148d3deaf0b6d112743a42b206014ed1b5a5cf4ef939f1976d420 module/directory-runtime -ba3296177f39256e32ab14a58d0af8d3a7a8128486c9e7fad3208468483071e0 module/discord +11abbdd49619fb11d83f88113d822d8f8ca7ef42d81ed02f17802c8a557ebb7a module/discord 39fe343ed2119de714757c365eef2ccec89c2c82a0876c60a4bef8ce469f8c8a module/error-runtime ce4f1602bf5b5de968ca97cede4f498b6ae709a0a59393b5ce9255cf0e6a9d5e module/extension-shared dd9f6e0fd33cc88b22543c1ee30cc09cf4de4d8f30dff7b7f9cebef885c21543 module/gateway-method-runtime @@ -75,7 +75,7 @@ dd9f6e0fd33cc88b22543c1ee30cc09cf4de4d8f30dff7b7f9cebef885c21543 module/gateway a35bf7d756fc781b860d131bac3710e4d3b2dd4403f7f98313899ddf80b564d3 module/hook-runtime a953bd0c23c562e29fc512bc2354ed45c497728169d8201cb2cdb75d9e056073 module/inbound-envelope 4928af5d2509f696b896f53ac790303a0742202dbcdae3e44fe6d1b434a9c1ba module/inbound-event-delivery -254a3b4bc0d397729ed9ff3493e94e6c5f1f30fd1578dee434b96e1010556682 module/inbound-reply-dispatch +7b582cd1e442337e78914bf50c2c3b2a8effb9386a1c7044e1f5eb24362885d1 module/inbound-reply-dispatch a15bfe49affb71eb05f126803e5222a44c725ce9813aeca467369cfa72635498 module/infra-runtime ce73721421f1b903dd04ead4df173582e59ea3e9990248102c448b419cc6d272 module/ingress-effect-once 93aa5d6a73719dd9ba6cc65a57e6d48b6c6a1191e3979b84a7d0ffd2c467191f module/interactive-runtime @@ -89,63 +89,63 @@ fd6970804756de775451862050e5efea0c91dfa71d18dbbc9c1ad9e9d5ee7491 module/media-r 6a52f93107335f88751704352cc01e62add06f854a5b7d765e2a5ee87c0313b6 module/media-store b7e71516842300c041d2423d822da0080881db6ddc6b9b9ce38cabd8d546676b module/media-understanding a206a1486f6a6bed3091795b324e95dd710b4b3b87a4a8f18788e6158a48a922 module/media-understanding-runtime -ebb4371dba9ba573634904a213b3e8d219ab80c07dce6c0923ccc00d3679f361 module/meeting-runtime +1bc434bd0f9cb368f46ea18dd0838e4bb95aa277990a967d0edeb5551a945e90 module/meeting-runtime 3312468e2e8f3423b765fac6bb17944b800ea2c84acffeb64342c99040b2f482 module/memory-core-host-engine-foundation -7d283635e90d863242479e29ed123e1a24f77a929db7267ad6bc1ae9173a71eb module/memory-host-core +071d151e34f846977c47f6758b438be9b99573f56a272a0006cdaeaac0415f6e module/memory-host-core 1efa0aadc4261d1c6073058cbf3dcc9fa681424819bdd14333e19b249bbc4b18 module/messaging-targets 9724b247264b528f65891ba34e71f4b87303cfe0db8d76f779252e01901d78d3 module/model-session-runtime -1c10a67d48cf73dcaeacea1ad7ab7c84ee2ad995ba509dc31b59553406bdb247 module/models-provider-runtime +fe44ec692b47f80aced06db7c6cdffc8cd1cc1291971609743cf3764716338d3 module/models-provider-runtime 843158613b23664aea805911d372a6c88d160d3a93f44b76291f47423aee1665 module/native-command-config-runtime f6ffada942145ca2fcec4df90740b4578afa09e81d46cc09122a65d6e345fe02 module/native-command-registry a6b5532576fa4cfd609d0966927eec12806424f2e1efce9fe47d903dfeb8e4dd module/param-readers ca7a56bb1a6169b4cf9befbf5aa21da280a8086fdc49fca4eec520a7a7c98549 module/persistent-dedupe b31f5d86904097993a55377fed7973cd298b0e1f49cade7e37a1e28f6e724108 module/plugin-config-runtime -b945a8a679d91cf23237b577a643d4719641bf8b8023abd3824505073417f6a8 module/plugin-entry -ee3d12073fde3ddf6167b2740e7f610e524f8026fa0204f4ea730c917e59f380 module/plugin-runtime -aca0ca054e26eb47033e8b7a0d1efc75d386532804035f2005930947be44a5fc module/provider-auth -1a0be8feee072a7198b0f217b70bb21ac7678f2a8a0d054a037e6a53d40e9fa3 module/provider-catalog-runtime +8c6b338b650f7d151c4e8c898d02b48957f5526786be9f7774c96e97822e1ea5 module/plugin-entry +13d8054033cd241c7599925a35b3b09f7b283586fa68a33101575a0c1a69daa4 module/plugin-runtime +49e5b01d5d6a104a789a72ecb36a5aa293b19713723088d2cec638860e2a7dd4 module/provider-auth +6a4fd49d417b541f59f5b2479188df4ebf0daeafa052c9605378c1ce4e161fac module/provider-catalog-runtime 8131147d699394bd06503e2ea2f5f1a50b1594a87dded6d118b74a8d0328c8f6 module/proxy-capture f7b2b9626160234ad848947f172269bb3d8daca069b5dfdea8ebb2a952a28996 module/question-gateway-runtime 1171a76ea0485b36f77c9e12601c44a0e858043140e1d4669bb86d1f9e35930b module/reply-chunking -18eb69bf54875ff3be5e770d658af3f89a7dcae2fff92f698c8974f375dc22d4 module/reply-dispatch-runtime +acf1e028586ad23af855fb9b7a043578c8d57e5d7d5e112623f281a82b65064a module/reply-dispatch-runtime 73f861fa3179d5af1159853c5acab0eec7a6c8f9398dcb75ea770e784fca6727 module/reply-history fedbd80588bfc407af75dec097f52d90794e64165e79209253beec5ff615a3a8 module/reply-payload -441b1b5e72c437fc17db970012d0278207c329cc57088f4381f9767c65c6a514 module/reply-runtime +f6647a184e16bcf64e82cf636c386ca9f4dc586eac4fdb411dcfed6598e3e87e module/reply-runtime aa07d85d99fdd2b1e0cbe9975fb6dcae66b8bdce2607c6bd5402ae68bb15118c module/root-walk ad6c5c5b16e22f8b06994f69ecfafbc9a9622b07c14670f3c4e8ee81cf3b8c4d module/routing 7877a7e58fa32a64107154e5b714c6d165e96989d4aa5f43e0afac085a187af0 module/run-command 26b25c9406a3d34405ca2c87b29882fcc757ce83541d64cf9c0a1a5c3e54359f module/runtime 0570a20fac6020880900a0cf9a889d8f28f47b89d0e1d17862265b557236d440 module/runtime-config-snapshot -52174d47f6a7edad215456ada8e7497c29a492a89caa4861f0799d453fd7547d module/runtime-env +bfe087e249a3c0adda4d24727220a65bcc159f53d6a4d6d8cd9b252627ef8e98 module/runtime-env 49e9b6a8195c89704eaa80656f176444af7cacbf639b759f41f2c78ae6bfcfd9 module/runtime-group-policy -3886ba451857f0041c108faa0c4fb9cacfbc2d6c28fe3ed8118ad9d61d9d773d module/runtime-store +1573e82ff514b33b7e516d221b6e7b23f80be1196c4ab359c7ddfe7041da71ee module/runtime-store d17862c40825af1ddf0257b44f1e1cbb9c375e8e5ed668fae75d530d1a465cf9 module/secret-file 8e2ac4d3973d8d8ce4478e3440d66ee5c0d9213b0fe9e927c421d14fd31e5e86 module/secret-input b1b0229280d7cc4a880e4db75130714eb6adc9d6aea31240dc9a8ff6fda55219 module/secret-input-runtime 0cbc3908bd9e1d4527585023a1692eba6c0bfe82f107f49a926a8d1c79aee2a0 module/secret-ref-runtime 66f58d7e35cd7db412f2091e5733729a7a6ff8dfde55270624391d7cc194c9b0 module/security-runtime -996b80d35a5f40026be77a64d110958f3d8e436aef2bc4a72e08d54761e4ae9e module/session-catalog -3b11a0040606bba5ed038749cf1d5398896e2d20f8711efb05b763aaa2faebe3 module/session-discussion +ee843fb17238a4c488328de8348c503ead408b8108e1d4317270ffcef77ab04a module/session-catalog +576ef5c407057fb06ed49f87f85b758d86812a49fa7bdc6f324a1ae85c524407 module/session-discussion ff4ccadf6691b300cb446959093306a75206d3eed04f4de90a6823aec33516f3 module/session-store-runtime -fb0af0a51ba93e070d8862701c016375cf8576c47be1d25e20537ec86b0bcf73 module/setup +16b0b417cdb9d7f045337a3ddf550b4c27c30f2faab35f727ef5c636e2975ffb module/setup 3b37b8371bee9239682a4b2e9c066ef423ffba129d06ded0d7030359ec0dd68e module/setup-runtime -44d37e0d9131ad2859f41068f2604090c784e65f1bd6ebda8e051b6f2e5e1660 module/setup-tools +d0cb4c5abb7484352088f556c1ba7c7b147d7b57977b8246e5cb7187937768b7 module/setup-tools 24c9881447c86adb87d6872763c33d6f56e7560aa3e1a4557c84efff807b8017 module/skill-commands-runtime -d27dbcae52b2d81efdc67dcaa3b7422748986d0e30f5e45aa1c997b4bc83c0b4 module/speech-settings +fad7b6bd876659f232a1365f2a434f48e3faeaa0c21ad90977b920804cb0e75c module/speech-settings 0b3de0b219e431d26298c83688ffe53c262d58a336ca910e29619b5d0447266e module/ssrf-policy 6639ba57aacbb620a2f6ff802ecd94fc90b1969292e0306598546b5b4b9261cc module/ssrf-runtime 3855f0a23281d21063762f5b3be2b7485c8bdb3b4cd1692de4f2a17d4eeae496 module/state-paths -21fb493eaeb6e46441863c27a3838b52fc13494fff8f7c90c12b72a3d373b16b module/status-helpers +bd7f898a57648208bcd19359d308ceead5a43155562c65503376c864a9142cc9 module/status-helpers f097d0096b21c8a052f0f649b7512ecf2aba4744ae6956f001950e053828b309 module/string-coerce-runtime 9a02a0744c0a73e575c8980cd0e4256866b6d436636061c7774e2e712636466c module/telegram-account aef35bee2502cd6ed8765409b758e452aff8ac9469fd773e6a2a44c9a1bc3f66 module/temp-path 87fa81b9e58d8fc04a4b4202d2d37fca339615f5225687d9db905151439e0f4d module/text-chunking -d05a2db4a97844950ff3bc30f90f07e7f45600b1219e08a19264174b399817dc module/text-runtime -eaa933c519b15e13d21e1213e1b160541951bfddcaa52478e8654afa038a6940 module/tool-plugin +fcdaf13ecea269f1a471ba4fe14958f3c8e198eaa20f354546a648408ed74064 module/text-runtime +3c4e17c223f60d9c9f466cef3ec888360e04daec28ffa73e5095bc3284063616 module/tool-plugin dc1a073c59ab61e2789533b777b3f0cb9af689d64a97796b10e8aa82552510db module/tool-results a5eef5a532c439b1489236711f4bc2721abe612a9db3911430e1344c239f9861 module/tool-send cda105b721d498df23a554c6b68be150b8fe66b8b9172185c31a0b3b0646b1dc module/web-media -ec56c0c1baffca53df8b104ba2e7da7267e8633378003f7de272d477118f5908 module/webhook-ingress +7846b0ec398d33c44b5ece0f1ca67cc225934398b2aa24da95473c18427e69f5 module/webhook-ingress e3a199a9ce0b85d203e9e8a29b500db29c6b7af307e3145d0a311e29d598925b module/webhook-request-guards de59e86e126b75d13251cba7ebbe27b44d9b5588785d98df5ff4d6722374c81f module/widget-html 9161b36ec0ab062ea41b363c894fcd672a7727f21cb726739f99f9c184fce69d module/zod diff --git a/scripts/lib/export-name-collision-baseline.json b/scripts/lib/export-name-collision-baseline.json index 3199291eb3f5..9e92a2e7a053 100644 --- a/scripts/lib/export-name-collision-baseline.json +++ b/scripts/lib/export-name-collision-baseline.json @@ -894,14 +894,6 @@ "src/gateway/server-methods/nodes.pending.ts" ] }, - { - "name": "normalizeAccountId", - "files": [ - "src/routing/account-id.ts", - "src/utils/account-id.ts" - ], - "sdk": true - }, { "name": "normalizeChannelId", "files": [ @@ -1262,20 +1254,6 @@ "src/gateway/test-helpers.assertions.ts" ] }, - { - "name": "requireValidConfig", - "files": [ - "src/commands/agents.command-shared.ts", - "src/commands/channels/shared.ts" - ] - }, - { - "name": "requireValidConfigFileSnapshot", - "files": [ - "src/commands/agents.command-shared.ts", - "src/commands/config-validation.ts" - ] - }, { "name": "resetPreparedModelCatalogForTest", "files": [ @@ -1656,7 +1634,6 @@ { "name": "sleep", "files": [ - "src/agents/utils/sleep.ts", "src/tui/tui-pty-test-support.ts", "src/utils/sleep.ts" ], diff --git a/src/agents/command/run-context.ts b/src/agents/command/run-context.ts index c101b21bc464..dd7bbf2f7d3d 100644 --- a/src/agents/command/run-context.ts +++ b/src/agents/command/run-context.ts @@ -2,7 +2,7 @@ * Resolves channel/account/thread run context for agent command execution. */ import { stringifyRouteThreadId } from "../../plugin-sdk/channel-route.js"; -import { normalizeAccountId } from "../../utils/account-id.js"; +import { normalizeOptionalAccountId } from "../../routing/account-id.js"; import { resolveMessageChannel } from "../../utils/message-channel.js"; import type { AgentCommandOpts, AgentRunContext } from "./types.js"; @@ -18,7 +18,7 @@ export function resolveAgentRunContext(opts: AgentCommandOpts): AgentRunContext merged.messageChannel = normalizedChannel; } - const normalizedAccountId = normalizeAccountId(merged.accountId ?? opts.accountId); + const normalizedAccountId = normalizeOptionalAccountId(merged.accountId ?? opts.accountId); if (normalizedAccountId) { merged.accountId = normalizedAccountId; } diff --git a/src/agents/sessions/agent-session-execution.ts b/src/agents/sessions/agent-session-execution.ts index d55bca55206a..4b4331325d7b 100644 --- a/src/agents/sessions/agent-session-execution.ts +++ b/src/agents/sessions/agent-session-execution.ts @@ -2,7 +2,7 @@ import { isContextOverflow } from "@openclaw/ai/internal/runtime"; import type { AssistantMessage } from "../../llm/types.js"; import { classifyRateLimitWindow } from "../../llm/utils/rate-limit-window.js"; import { isRetryableAssistantError } from "../../llm/utils/retry.js"; -import { sleep } from "../utils/sleep.js"; +import { sleep } from "../../utils/sleep.js"; import { AgentSessionExtensions } from "./agent-session-extensions.js"; import { type BashResult, executeBashWithOperations } from "./bash-executor.js"; import type { BashExecutionMessage } from "./messages.js"; diff --git a/src/agents/tools/computer-tool.node-resolution.test.ts b/src/agents/tools/computer-tool.node-resolution.test.ts index 845d72ea977b..aeebb2a734f7 100644 --- a/src/agents/tools/computer-tool.node-resolution.test.ts +++ b/src/agents/tools/computer-tool.node-resolution.test.ts @@ -23,7 +23,7 @@ vi.mock("./gateway.js", async (importOriginal) => { return { ...actual, callGatewayTool: callGatewayToolMock }; }); -vi.mock("../utils/sleep.js", () => ({ sleep: sleepMock })); +vi.mock("../../utils/sleep.js", () => ({ sleep: sleepMock })); const { createComputerTool } = await import("./computer-tool.js"); diff --git a/src/agents/tools/computer-tool.test.ts b/src/agents/tools/computer-tool.test.ts index 127281511a97..96f96a7ac857 100644 --- a/src/agents/tools/computer-tool.test.ts +++ b/src/agents/tools/computer-tool.test.ts @@ -33,7 +33,7 @@ vi.mock("./gateway.js", async (importOriginal) => { return { ...actual, callGatewayTool: callGatewayToolMock }; }); -vi.mock("../utils/sleep.js", () => ({ sleep: sleepMock })); +vi.mock("../../utils/sleep.js", () => ({ sleep: sleepMock })); const { createComputerTool, invalidateComputerFrameIfMissing } = await import("./computer-tool.js"); const { DEFAULT_IMAGE_MAX_DIMENSION_PX } = await import("../image-sanitization.js"); diff --git a/src/agents/tools/computer-tool.ts b/src/agents/tools/computer-tool.ts index 799aec2f86a9..fa1afe4d111c 100644 --- a/src/agents/tools/computer-tool.ts +++ b/src/agents/tools/computer-tool.ts @@ -14,6 +14,7 @@ import { Type } from "typebox"; import { parseScreenSnapshotPayload } from "../../cli/nodes-screen.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { formatErrorMessage } from "../../infra/errors.js"; +import { sleep } from "../../utils/sleep.js"; import { DEFAULT_IMAGE_MAX_DIMENSION_PX, resolveImageSanitizationLimits, @@ -27,7 +28,6 @@ import { stringEnum, } from "../schema/typebox.js"; import { sanitizeToolResultImages } from "../tool-images.js"; -import { sleep } from "../utils/sleep.js"; import { type AnyAgentTool, readFiniteNumberParam, diff --git a/src/agents/utils/sleep.test.ts b/src/agents/utils/sleep.test.ts deleted file mode 100644 index c84551621b58..000000000000 --- a/src/agents/utils/sleep.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Sleep utility tests cover timer-safe delay clamping and abort-listener cleanup -// for long-running agent waits. -import { describe, expect, it, vi } from "vitest"; -import { isAbortError } from "../../infra/abort-signal.js"; -import { MAX_TIMER_TIMEOUT_MS } from "../../shared/number-coercion.js"; -import { sleep } from "./sleep.js"; - -describe("agents sleep", () => { - it("rejects a pre-aborted zero-duration wait with the canonical abort error", async () => { - const controller = new AbortController(); - const reason = new Error("cancelled"); - controller.abort(reason); - - const error = await sleep(0, controller.signal).catch((caught: unknown) => caught); - - expect(error).toMatchObject({ name: "AbortError", message: "aborted", cause: reason }); - expect(isAbortError(error)).toBe(true); - }); - - it("rejects a pre-aborted positive-duration wait", async () => { - const controller = new AbortController(); - const reason = new Error("cancelled"); - controller.abort(reason); - - await expect(sleep(1, controller.signal)).rejects.toMatchObject({ - name: "AbortError", - message: "aborted", - cause: reason, - }); - }); - - it("resolves a non-aborted zero-duration wait", async () => { - const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); - try { - await expect(sleep(0, new AbortController().signal)).resolves.toBeUndefined(); - expect(setTimeoutSpy).not.toHaveBeenCalled(); - } finally { - setTimeoutSpy.mockRestore(); - } - }); - - it("clamps oversized delays before scheduling", async () => { - vi.useFakeTimers(); - const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); - try { - const sleeper = sleep(Number.MAX_SAFE_INTEGER); - - expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS); - - await vi.advanceTimersByTimeAsync(MAX_TIMER_TIMEOUT_MS); - await expect(sleeper).resolves.toBeUndefined(); - } finally { - setTimeoutSpy.mockRestore(); - vi.useRealTimers(); - } - }); - - it("removes abort listeners after normal resolution", async () => { - vi.useFakeTimers(); - const controller = new AbortController(); - const removeListenerSpy = vi.spyOn(controller.signal, "removeEventListener"); - try { - const sleeper = sleep(5, controller.signal); - - await vi.advanceTimersByTimeAsync(5); - await expect(sleeper).resolves.toBeUndefined(); - - expect(removeListenerSpy).toHaveBeenCalledWith("abort", expect.any(Function)); - } finally { - removeListenerSpy.mockRestore(); - vi.useRealTimers(); - } - }); - - it("rejects cancellation with the canonical abort classification and cause", async () => { - const controller = new AbortController(); - const reason = new Error("stop"); - const sleeper = sleep(60_000, controller.signal); - - controller.abort(reason); - - const error = await sleeper.catch((caught: unknown) => caught); - expect(error).toMatchObject({ name: "AbortError", message: "aborted", cause: reason }); - expect(isAbortError(error)).toBe(true); - }); -}); diff --git a/src/agents/utils/sleep.ts b/src/agents/utils/sleep.ts deleted file mode 100644 index 1cf49d997501..000000000000 --- a/src/agents/utils/sleep.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { sleepWithAbort } from "@openclaw/retry"; -/** - * Sleep helper that respects abort signal. - */ -import { createAbortError } from "../../infra/abort-signal.js"; -import { resolveTimerTimeoutMs } from "../../shared/number-coercion.js"; - -export function sleep(ms: number, signal?: AbortSignal): Promise { - // Cancellation wins even for zero-delay waits so aborted runs cannot - // advance into follow-on work such as computer-tool screenshot capture. - if (signal?.aborted) { - return Promise.reject( - createAbortError("aborted", { cause: signal.reason ?? new Error("aborted") }), - ); - } - return sleepWithAbort(resolveTimerTimeoutMs(ms, 0, 0), signal); -} diff --git a/src/commands/agents.bind.test-support.ts b/src/commands/agents.bind.test-support.ts index eb79400acced..ecd00cec9702 100644 --- a/src/commands/agents.bind.test-support.ts +++ b/src/commands/agents.bind.test-support.ts @@ -34,9 +34,8 @@ vi.mock("../config/config.js", () => ({ replaceConfigFile: (...args: unknown[]) => replaceConfigFileMock(...args), })); -vi.mock("./agents.command-shared.js", () => ({ - createQuietRuntime: (runtime: T) => runtime, - requireValidConfig: async (_runtime: unknown, opts?: unknown) => { +vi.mock("./config-validation.js", () => ({ + requireValidConfigSnapshot: async (_runtime: unknown, opts?: unknown) => { const snapshot = (await readConfigFileSnapshotMock(opts)) as | { config?: OpenClawConfig; sourceConfig?: OpenClawConfig } | undefined; diff --git a/src/commands/agents.command-shared.ts b/src/commands/agents.command-shared.ts index eb5172d35d1f..4f09c24d0b3d 100644 --- a/src/commands/agents.command-shared.ts +++ b/src/commands/agents.command-shared.ts @@ -1,25 +1,7 @@ -// Shared config-loading helpers for agent management commands. -import type { OpenClawConfig } from "../config/types.openclaw.js"; +// Shared runtime helpers for agent management commands. import type { RuntimeEnv } from "../runtime.js"; -import { - requireValidConfigFileSnapshot as requireValidConfigFileSnapshotBase, - requireValidConfigSnapshot, -} from "./config-validation.js"; /** Wrap a runtime so helper setup work stays silent in JSON output paths. */ export function createQuietRuntime(runtime: RuntimeEnv): RuntimeEnv { return { ...runtime, log: () => {} }; } - -/** Load a config file snapshot and surface validation errors through the runtime. */ -export async function requireValidConfigFileSnapshot(runtime: RuntimeEnv) { - return await requireValidConfigFileSnapshotBase(runtime); -} - -/** Load the current runtime config and return null after reporting validation failures. */ -export async function requireValidConfig( - runtime: RuntimeEnv, - opts?: { skipPluginValidation?: boolean }, -): Promise { - return await requireValidConfigSnapshot(runtime, opts); -} diff --git a/src/commands/agents.commands.add.ts b/src/commands/agents.commands.add.ts index 41be31f6dc67..c41ba09a8ee6 100644 --- a/src/commands/agents.commands.add.ts +++ b/src/commands/agents.commands.add.ts @@ -33,10 +33,10 @@ import { resolveUserPath, shortenHomePath } from "../utils.js"; import { createClackPrompter } from "../wizard/clack-prompter.js"; import { WizardCancelledError } from "../wizard/prompts.js"; import { applyAgentBindings, buildChannelBindings, describeBinding } from "./agents.bindings.js"; -import { requireValidConfigFileSnapshot } from "./agents.command-shared.js"; import { applyAgentConfig, listAgentEntries } from "./agents.config.js"; import { promptAuthChoiceGrouped } from "./auth-choice-prompt.js"; import { applyAuthChoice, warnIfModelConfigLooksOff } from "./auth-choice.js"; +import { requireValidConfigFileSnapshot } from "./config-validation.js"; import { ensureOnboardingAgentWorkspace, resolveOnboardingAgentTarget, diff --git a/src/commands/agents.commands.bind.ts b/src/commands/agents.commands.bind.ts index df9b3af29170..370dc88d90fb 100644 --- a/src/commands/agents.commands.bind.ts +++ b/src/commands/agents.commands.bind.ts @@ -11,7 +11,7 @@ import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { describeBinding } from "./agents.binding-format.js"; -import { requireValidConfig, requireValidConfigFileSnapshot } from "./agents.command-shared.js"; +import { requireValidConfigFileSnapshot, requireValidConfigSnapshot } from "./config-validation.js"; type AgentBindingsModule = typeof import("./agents.bindings.js"); @@ -42,7 +42,7 @@ function loadAgentBindingsModule(): Promise { } function resolveAgentId( - cfg: Awaited>, + cfg: Awaited>, agentInput: string | undefined, params?: { fallbackToDefault?: boolean }, ): string | null { @@ -58,7 +58,10 @@ function resolveAgentId( return null; } -function hasAgent(cfg: Awaited>, agentId: string): boolean { +function hasAgent( + cfg: Awaited>, + agentId: string, +): boolean { if (!cfg) { return false; } @@ -75,7 +78,7 @@ function formatBindingOwnerLine(binding: AgentRouteBinding): string { } function resolveTargetAgentIdOrExit(params: { - cfg: Awaited>; + cfg: Awaited>; runtime: RuntimeEnv; agentInput: string | undefined; }): string | null { @@ -109,7 +112,7 @@ function formatBindingConflicts( async function resolveParsedBindingsOrExit(params: { runtime: RuntimeEnv; - cfg: NonNullable>>; + cfg: NonNullable>>; agentId: string; bindValues: string[] | undefined; emptyMessage: string; @@ -154,7 +157,7 @@ async function resolveConfigAndTargetAgentIdOrExit(params: { runtime: RuntimeEnv; agentInput: string | undefined; }): Promise<{ - cfg: NonNullable>>; + cfg: NonNullable>>; agentId: string; baseHash?: string; } | null> { @@ -179,7 +182,7 @@ export async function agentsBindingsCommand( opts: AgentsBindingsListOptions, runtime: RuntimeEnv = defaultRuntime, ) { - const cfg = await requireValidConfig(runtime, { skipPluginValidation: true }); + const cfg = await requireValidConfigSnapshot(runtime, { skipPluginValidation: true }); if (!cfg) { return; } diff --git a/src/commands/agents.commands.delete.ts b/src/commands/agents.commands.delete.ts index 2c5e0a049c67..9cccedd95400 100644 --- a/src/commands/agents.commands.delete.ts +++ b/src/commands/agents.commands.delete.ts @@ -30,8 +30,9 @@ import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; import { createClackPrompter } from "../wizard/clack-prompter.js"; -import { createQuietRuntime, requireValidConfigFileSnapshot } from "./agents.command-shared.js"; +import { createQuietRuntime } from "./agents.command-shared.js"; import { findAgentEntryIndex, listAgentEntries, pruneAgentConfig } from "./agents.config.js"; +import { requireValidConfigFileSnapshot } from "./config-validation.js"; import { moveToTrash } from "./onboard-helpers.js"; type AgentsDeleteOptions = { diff --git a/src/commands/agents.commands.identity.ts b/src/commands/agents.commands.identity.ts index 445f50d3bf45..dd1197a5ab6d 100644 --- a/src/commands/agents.commands.identity.ts +++ b/src/commands/agents.commands.identity.ts @@ -18,13 +18,13 @@ import { normalizeAgentId } from "../routing/session-key.js"; import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import { resolveUserPath, shortenHomePath } from "../utils.js"; -import { requireValidConfigFileSnapshot } from "./agents.command-shared.js"; import { type AgentIdentity, findAgentEntryIndex, listAgentEntries, loadAgentIdentity, } from "./agents.config.js"; +import { requireValidConfigFileSnapshot } from "./config-validation.js"; type AgentsSetIdentityOptions = { agent?: string; diff --git a/src/commands/agents.commands.list.test.ts b/src/commands/agents.commands.list.test.ts index 6138a90e80d5..dd530e13ebf3 100644 --- a/src/commands/agents.commands.list.test.ts +++ b/src/commands/agents.commands.list.test.ts @@ -28,8 +28,8 @@ const { summarizeBindingsMock: vi.fn(), })); -vi.mock("./agents.command-shared.js", () => ({ - requireValidConfig: requireValidConfigMock, +vi.mock("./config-validation.js", () => ({ + requireValidConfigSnapshot: requireValidConfigMock, })); vi.mock("./agents.providers.js", () => ({ diff --git a/src/commands/agents.commands.list.ts b/src/commands/agents.commands.list.ts index 41d255bc92d0..642c50fdd6e4 100644 --- a/src/commands/agents.commands.list.ts +++ b/src/commands/agents.commands.list.ts @@ -7,7 +7,6 @@ import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import { shortenHomePath } from "../utils.js"; import { describeBinding } from "./agents.bindings.js"; -import { requireValidConfig } from "./agents.command-shared.js"; import type { AgentSummary } from "./agents.config.js"; import { buildAgentSummaries } from "./agents.config.js"; import { @@ -16,6 +15,7 @@ import { listProvidersForAgent, summarizeBindings, } from "./agents.providers.js"; +import { requireValidConfigSnapshot } from "./config-validation.js"; type AgentsListOptions = { json?: boolean; @@ -79,7 +79,7 @@ export async function agentsListCommand( opts: AgentsListOptions, runtime: RuntimeEnv = defaultRuntime, ) { - const cfg = await requireValidConfig(runtime); + const cfg = await requireValidConfigSnapshot(runtime); if (!cfg) { return; } diff --git a/src/config/sessions/conversation-identity.ts b/src/config/sessions/conversation-identity.ts index 4bc7358cffb2..d464df793b04 100644 --- a/src/config/sessions/conversation-identity.ts +++ b/src/config/sessions/conversation-identity.ts @@ -2,11 +2,11 @@ import { normalizeOptionalString as normalizeText } from "@openclaw/normalizatio import type { MsgContext } from "../../auto-reply/templating.js"; import { normalizeChatType } from "../../channels/chat-type.js"; import { resolveConversationLabel } from "../../channels/conversation-label.js"; +import { normalizeOptionalAccountId } from "../../routing/account-id.js"; import { buildConversationRef, normalizeConversationPeerId, } from "../../routing/conversation-ref.js"; -import { normalizeAccountId } from "../../utils/account-id.js"; import { deliveryContextFromSession, mergeDeliveryContext, @@ -74,8 +74,8 @@ function resolvePairedOriginPeerId(params: { !originChannel || originChannel !== deliveryChannel || normalizeChatType(origin?.chatType) !== params.kind || - (normalizeAccountId(origin?.accountId) ?? "default") !== - (normalizeAccountId(params.deliveryContext?.accountId) ?? "default") || + (normalizeOptionalAccountId(origin?.accountId) ?? "default") !== + (normalizeOptionalAccountId(params.deliveryContext?.accountId) ?? "default") || normalizeThreadId(origin?.threadId) !== normalizeThreadId(params.deliveryContext?.threadId) ) { return undefined; @@ -112,7 +112,7 @@ export function buildConversationIdentity(params: { if (!deliveryTarget) { return null; } - const accountId = normalizeAccountId(params.accountId) ?? "default"; + const accountId = normalizeOptionalAccountId(params.accountId) ?? "default"; const rawParent = normalizeText(params.parentConversationRef); const parentConversationRef = rawParent ? rawParent.startsWith("conv_") diff --git a/src/infra/outbound/agent-delivery.ts b/src/infra/outbound/agent-delivery.ts index d22ce110b3d0..2f7711e20efc 100644 --- a/src/infra/outbound/agent-delivery.ts +++ b/src/infra/outbound/agent-delivery.ts @@ -10,10 +10,10 @@ import type { import { listRouteBindings } from "../../config/bindings.js"; import type { SessionEntry } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import { normalizeOptionalAccountId } from "../../routing/account-id.js"; import { normalizeRouteBindingChannelId } from "../../routing/binding-scope.js"; import { resolveAgentRoute } from "../../routing/resolve-route.js"; import { buildAgentMainSessionKey, normalizeAgentId } from "../../routing/session-key.js"; -import { normalizeAccountId } from "../../utils/account-id.js"; import { INTERNAL_MESSAGE_CHANNEL, isDeliverableMessageChannel, @@ -100,7 +100,7 @@ function resolveAgentDeliveryPlan(params: { ? normalizedTurnSource : undefined; const turnSourceTo = normalizeOptionalString(params.turnSourceTo) ?? undefined; - const turnSourceAccountId = normalizeAccountId(params.turnSourceAccountId); + const turnSourceAccountId = normalizeOptionalAccountId(params.turnSourceAccountId); const turnSourceThreadId = params.turnSourceThreadId != null && params.turnSourceThreadId !== "" ? params.turnSourceThreadId @@ -145,7 +145,7 @@ function resolveAgentDeliveryPlan(params: { : undefined; const resolvedAccountId = - normalizeAccountId(params.accountId) ?? + normalizeOptionalAccountId(params.accountId) ?? (deliveryTargetMode === "implicit" ? baseDelivery.accountId : undefined); let resolvedTo = explicitTo; diff --git a/src/utils.test.ts b/src/utils.test.ts index 3f8fbd5482ec..6d0123ae147d 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { describe, expect, it, vi } from "vitest"; +import { isAbortError } from "./infra/abort-signal.js"; import { MAX_TIMER_TIMEOUT_MS } from "./shared/number-coercion.js"; import { withTempDir } from "./test-helpers/temp-dir.js"; import { withEnv } from "./test-utils/env.js"; @@ -55,6 +56,68 @@ describe("sleep", () => { vi.useRealTimers(); } }); + + it("rejects a pre-aborted zero-duration wait with the canonical abort error", async () => { + const controller = new AbortController(); + const reason = new Error("cancelled"); + controller.abort(reason); + + const error = await sleep(0, controller.signal).catch((caught: unknown) => caught); + + expect(error).toMatchObject({ name: "AbortError", message: "aborted", cause: reason }); + expect(isAbortError(error)).toBe(true); + }); + + it("rejects a pre-aborted positive-duration wait", async () => { + const controller = new AbortController(); + const reason = new Error("cancelled"); + controller.abort(reason); + + await expect(sleep(1, controller.signal)).rejects.toMatchObject({ + name: "AbortError", + message: "aborted", + cause: reason, + }); + }); + + it("resolves a non-aborted zero-duration wait without scheduling", async () => { + const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); + try { + await expect(sleep(0, new AbortController().signal)).resolves.toBeUndefined(); + expect(setTimeoutSpy).not.toHaveBeenCalled(); + } finally { + setTimeoutSpy.mockRestore(); + } + }); + + it("removes abort listeners after normal resolution", async () => { + vi.useFakeTimers(); + const controller = new AbortController(); + const removeListenerSpy = vi.spyOn(controller.signal, "removeEventListener"); + try { + const promise = sleep(5, controller.signal); + + await vi.advanceTimersByTimeAsync(5); + await expect(promise).resolves.toBeUndefined(); + + expect(removeListenerSpy).toHaveBeenCalledWith("abort", expect.any(Function)); + } finally { + removeListenerSpy.mockRestore(); + vi.useRealTimers(); + } + }); + + it("rejects cancellation with the canonical abort classification and cause", async () => { + const controller = new AbortController(); + const reason = new Error("stop"); + const promise = sleep(60_000, controller.signal); + + controller.abort(reason); + + const error = await promise.catch((caught: unknown) => caught); + expect(error).toMatchObject({ name: "AbortError", message: "aborted", cause: reason }); + expect(isAbortError(error)).toBe(true); + }); }); describe("normalizeE164", () => { diff --git a/src/utils/account-id.ts b/src/utils/account-id.ts deleted file mode 100644 index 1d8008067447..000000000000 --- a/src/utils/account-id.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Account id helpers normalize routing account identifiers for utility callers. -import { normalizeOptionalAccountId } from "../routing/account-id.js"; - -/** - * Compatibility wrapper for account-id normalization. - * - * Runtime code imports this utility when it needs the older utils path while - * the canonical normalization logic lives in routing/account-id. - */ -/** Normalize an optional account id, returning undefined for blank/invalid input. */ -export function normalizeAccountId(value?: string): string | undefined { - return normalizeOptionalAccountId(value); -} diff --git a/src/utils/delivery-context.shared.ts b/src/utils/delivery-context.shared.ts index a426ab88ec6c..fb594f5baaac 100644 --- a/src/utils/delivery-context.shared.ts +++ b/src/utils/delivery-context.shared.ts @@ -12,7 +12,7 @@ import { normalizeChannelRouteTarget, type ChannelRouteRef, } from "../plugin-sdk/channel-route.js"; -import { normalizeAccountId } from "./account-id.js"; +import { normalizeOptionalAccountId } from "../routing/account-id.js"; import type { DeliveryContext } from "./delivery-context.types.js"; import { INTERNAL_MESSAGE_CHANNEL, @@ -48,7 +48,7 @@ export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryCon const normalized: DeliveryContext = { channel: route.channel, to: channelRouteTarget(route), - accountId: normalizeAccountId(route.accountId), + accountId: normalizeOptionalAccountId(route.accountId), }; const threadId = channelRouteThreadId(route); if (threadId != null) { diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts index 3280f2d744ba..5db5da634af1 100644 --- a/src/utils/sleep.ts +++ b/src/utils/sleep.ts @@ -1,8 +1,21 @@ +import { sleepWithAbort } from "@openclaw/retry"; +import { createAbortError } from "../infra/abort-signal.js"; import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; /** Promise-based sleep that clamps timer inputs through the shared timeout resolver. */ -export function sleep(ms: number): Promise { +export function sleep(ms: number, signal?: AbortSignal): Promise { + const delayMs = resolveTimerTimeoutMs(ms, 0, 0); + if (signal) { + // Cancellation wins even for zero-delay waits so aborted runs cannot + // advance into follow-on work such as computer-tool screenshot capture. + if (signal.aborted) { + return Promise.reject( + createAbortError("aborted", { cause: signal.reason ?? new Error("aborted") }), + ); + } + return sleepWithAbort(delayMs, signal); + } return new Promise((resolve) => { - setTimeout(resolve, resolveTimerTimeoutMs(ms, 0, 0)); + setTimeout(resolve, delayMs); }); }