From 00cd59b6799d540f649c86457664a16ad13ba75e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 12 Aug 2026 20:28:30 -0700 Subject: [PATCH] feat(gateway): provision paired node worker installs --- docs/.generated/config-baseline.counts.json | 2 +- docs/.generated/config-baseline.sha256 | 4 +- .../plugin-sdk-api-baseline/account-core.json | 2 +- .../account-helpers.json | 2 +- .../account-resolution.json | 2 +- .../agent-harness-runtime.json | 2 +- .../agent-harness.json | 2 +- .../agent-media-payload.json | 2 +- .../agent-runtime.json | 2 +- .../agent-scope-runtime.json | 2 +- .../allowlist-config-edit.json | 2 +- .../approval-auth-runtime.json | 2 +- .../approval-client-runtime.json | 2 +- .../approval-delivery-runtime.json | 2 +- .../approval-gateway-runtime.json | 2 +- .../approval-handler-adapter-runtime.json | 2 +- .../approval-handler-runtime.json | 2 +- .../approval-native-runtime.json | 2 +- .../approval-runtime.json | 2 +- .../channel-config-helpers.json | 2 +- .../channel-contract.json | 2 +- .../plugin-sdk-api-baseline/channel-core.json | 2 +- .../channel-dm-policy.json | 2 +- .../channel-entry-contract.json | 2 +- .../channel-feedback.json | 2 +- .../channel-inbound-debounce.json | 2 +- .../channel-inbound.json | 2 +- .../channel-ingress-runtime.json | 2 +- .../channel-message.json | 2 +- .../channel-outbound.json | 2 +- .../channel-pairing.json | 2 +- .../channel-plugin-common.json | 2 +- .../channel-policy.json | 2 +- .../channel-reply-pipeline.json | 2 +- .../channel-secret-basic-runtime.json | 2 +- .../channel-send-result.json | 2 +- .../channel-setup.json | 2 +- .../command-auth-native.json | 2 +- .../plugin-sdk-api-baseline/command-auth.json | 2 +- .../command-detection.json | 2 +- .../command-status.json | 2 +- .../config-contracts.json | 2 +- .../config-mutation.json | 2 +- .../config-runtime.json | 2 +- .../conversation-runtime.json | 2 +- .../plugin-sdk-api-baseline/core.json | 2 +- .../diagnostic-runtime.json | 2 +- .../directory-runtime.json | 2 +- .../plugin-sdk-api-baseline/discord.json | 2 +- .../extension-shared.json | 2 +- .../gateway-runtime.json | 2 +- .../plugin-sdk-api-baseline/health.json | 2 +- .../plugin-sdk-api-baseline/hook-runtime.json | 2 +- .../inbound-reply-dispatch.json | 2 +- .../infra-runtime.json | 2 +- .../plugin-sdk-api-baseline/logging-core.json | 2 +- .../media-local-roots.json | 2 +- .../media-runtime.json | 2 +- .../media-understanding-runtime.json | 2 +- .../media-understanding.json | 2 +- .../meeting-runtime.json | 2 +- .../memory-core-host-engine-foundation.json | 2 +- .../memory-host-core.json | 2 +- .../model-session-runtime.json | 2 +- .../models-provider-runtime.json | 2 +- .../native-command-config-runtime.json | 2 +- .../native-command-registry.json | 2 +- .../plugin-command-runtime.json | 2 +- .../plugin-config-runtime.json | 2 +- .../plugin-sdk-api-baseline/plugin-entry.json | 2 +- .../plugin-runtime.json | 2 +- .../provider-auth.json | 2 +- .../provider-catalog-runtime.json | 2 +- .../question-gateway-runtime.json | 2 +- .../reply-chunking.json | 2 +- .../reply-dispatch-runtime.json | 2 +- .../reply-payload.json | 2 +- .../reply-runtime.json | 2 +- .../plugin-sdk-api-baseline/routing.json | 2 +- .../runtime-config-snapshot.json | 2 +- .../runtime-store.json | 2 +- .../plugin-sdk-api-baseline/runtime.json | 2 +- .../secret-input-runtime.json | 2 +- .../secret-ref-runtime.json | 2 +- .../security-runtime.json | 2 +- .../session-catalog.json | 2 +- .../session-store-runtime.json | 2 +- .../setup-runtime.json | 2 +- .../plugin-sdk-api-baseline/setup.json | 2 +- .../skill-commands-runtime.json | 2 +- .../speech-settings.json | 2 +- .../plugin-sdk-api-baseline/ssrf-policy.json | 2 +- .../plugin-sdk-api-baseline/ssrf-runtime.json | 2 +- .../status-helpers.json | 2 +- .../telegram-account.json | 2 +- .../plugin-sdk-api-baseline/tool-plugin.json | 2 +- .../webhook-ingress.json | 2 +- .../webhook-request-guards.json | 2 +- docs/nodes/index.md | 23 +++++ packages/gateway-client/src/client.ts | 9 +- .../src/client.watchdog.test.ts | 10 +++ .../gateway-protocol/src/schema/frames.ts | 3 + .../src/schema/frames.worker-runs.test.ts | 24 +++++ src/config/config.node-agent-runs.test.ts | 14 +++ .../schema.help.quality.test-fixtures.ts | 2 + src/config/schema.help.runtime.ts | 4 + src/config/schema.labels.ts | 2 + src/config/schema.tags.ts | 1 + src/config/types.node-host.ts | 5 ++ src/config/zod-schema.node-host.ts | 7 ++ src/config/zod-schema.root-support.ts | 3 +- src/gateway/node-catalog.ts | 1 + src/gateway/node-registry.ts | 5 ++ .../server-methods/environments.test.ts | 11 ++- src/gateway/server-methods/environments.ts | 2 +- .../server-worker-environment-startup.ts | 5 ++ src/gateway/worker-environments/admission.ts | 7 ++ .../worker-environments/bundle.test.ts | 11 +++ src/gateway/worker-environments/bundle.ts | 15 ++-- .../worker-environments/credential-broker.ts | 46 +++++++++- .../device-provider.test.ts | 15 ++-- .../worker-environments/device-provider.ts | 8 +- .../environment-access.test.ts | 15 +++- .../placement-dispatch-device.test.ts | 16 +++- .../worker-environments/provider-lifecycle.ts | 74 ++++++++-------- .../provider-provisioning.test.ts | 87 ++++++++++++++++++- .../service.test-support.ts | 1 + src/gateway/worker-environments/service.ts | 2 + src/gateway/worker-environments/store.test.ts | 29 +++++-- src/gateway/worker-environments/store.ts | 38 +++++--- .../worker-environments/worker-turn-rpc.ts | 5 +- src/node-host/node-worker-build.ts | 42 +++++++++ src/node-host/runner.test.ts | 23 +++++ src/node-host/runner.ts | 2 + src/node-host/runtime.ts | 13 ++- src/shared/node-list-types.ts | 2 + src/shared/worker-bundle-hash.ts | 20 +++++ .../openclaw-database-maintenance.test.ts | 1 + .../openclaw-state-db-additive-columns.ts | 1 + .../openclaw-state-db-schema-additive.ts | 1 + src/state/openclaw-state-db.generated.d.ts | 1 + src/state/openclaw-state-db.test.ts | 3 + .../openclaw-state-schema-compatibility.ts | 2 + src/state/openclaw-state-schema.sql | 1 + 144 files changed, 617 insertions(+), 193 deletions(-) create mode 100644 packages/gateway-protocol/src/schema/frames.worker-runs.test.ts create mode 100644 src/node-host/node-worker-build.ts create mode 100644 src/shared/worker-bundle-hash.ts diff --git a/docs/.generated/config-baseline.counts.json b/docs/.generated/config-baseline.counts.json index d738d76fab05..42031111a6c1 100644 --- a/docs/.generated/config-baseline.counts.json +++ b/docs/.generated/config-baseline.counts.json @@ -1,5 +1,5 @@ { - "core": 2306, + "core": 2308, "channel": 3575, "plugin": 3997 } diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index 19ae80fd7a29..0566eecce612 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -aa6a639288bfde08ea8b8938f68ae6c414b45a9b976744b1cdb75aa590661cbb config-baseline.json -ce74623d1b19b178aee681a4d1e15eee17297f3a5d939ed1f0fba838abfabe6c config-baseline.core.json +ff3a4f23b20f40e493f2c5d4da1a8c6fba0206cf763a372e6437a3cf515d1099 config-baseline.json +d834d843e6f65a87490964c1e7a089ec8421b546a9274b84c5d2ef35ec14f5a1 config-baseline.core.json 1144184911193a239dd0e6415335a2a95771af27c4b1f5974e64851d6b2ed65d config-baseline.channel.json 250f573a93619d8a2f554028288af84551e572ef8bfdc820a43024572b55175a config-baseline.plugin.json diff --git a/docs/.generated/plugin-sdk-api-baseline/account-core.json b/docs/.generated/plugin-sdk-api-baseline/account-core.json index 89424a690ea3..b961ec52cda4 100644 --- a/docs/.generated/plugin-sdk-api-baseline/account-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/account-core.json @@ -1 +1 @@ -{"contentHash":"ca54bfaaa3b3a4360b8fc5777d74ee94add20d789861ad8f84c564324b72d34a","entrypoint":"account-core","importSpecifier":"openclaw/plugin-sdk/account-core"} +{"contentHash":"76643cb5737109f0cac8ea83133774ba1254dc66eecc5fc8f74fabdd8356796a","entrypoint":"account-core","importSpecifier":"openclaw/plugin-sdk/account-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/account-helpers.json b/docs/.generated/plugin-sdk-api-baseline/account-helpers.json index ac1c60db1537..3302baa0b9e1 100644 --- a/docs/.generated/plugin-sdk-api-baseline/account-helpers.json +++ b/docs/.generated/plugin-sdk-api-baseline/account-helpers.json @@ -1 +1 @@ -{"contentHash":"de22787449794b11be3d6a69fe5e4d33e21369febee2a3c4f1eb62d8105d8fb5","entrypoint":"account-helpers","importSpecifier":"openclaw/plugin-sdk/account-helpers"} +{"contentHash":"f7012931478c6fe373daa77468fa03d52043a0e65c3bd45dd94cb841222d81e2","entrypoint":"account-helpers","importSpecifier":"openclaw/plugin-sdk/account-helpers"} diff --git a/docs/.generated/plugin-sdk-api-baseline/account-resolution.json b/docs/.generated/plugin-sdk-api-baseline/account-resolution.json index a029f429fb30..0ddaac0723b6 100644 --- a/docs/.generated/plugin-sdk-api-baseline/account-resolution.json +++ b/docs/.generated/plugin-sdk-api-baseline/account-resolution.json @@ -1 +1 @@ -{"contentHash":"5011920cb3bc682df7980e84b82f25399e6fd220b569f9b42758b275f3666e66","entrypoint":"account-resolution","importSpecifier":"openclaw/plugin-sdk/account-resolution"} +{"contentHash":"a681a00e79eccd3f3b2fab17c5d929677d94ae942543688b3b128bed988398e2","entrypoint":"account-resolution","importSpecifier":"openclaw/plugin-sdk/account-resolution"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json index dacf37893f6f..402f15eef1bf 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json @@ -1 +1 @@ -{"contentHash":"48e9f1762613441ec71879de8fcfffcf0d382867b55436716ee00459d7b4f49b","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} +{"contentHash":"cd2397913a3eaf950a40f30dabdaa16f6575d2f7dde69d13d279dd9467ca945c","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json index bf3b043d67da..be857ff3856c 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json @@ -1 +1 @@ -{"contentHash":"fbc1f78eb076fb8e8e966f7cbf377303ec162cc3f2c5fa50e146d2e73618560f","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} +{"contentHash":"6a362806e1e6429606b6c4325958b54cd31ec05077587d3c528897518cb52cf9","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-media-payload.json b/docs/.generated/plugin-sdk-api-baseline/agent-media-payload.json index 6f9e1cb94598..184b05ca73b9 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-media-payload.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-media-payload.json @@ -1 +1 @@ -{"contentHash":"7b5bf5348d9f0df4e056c9787ea8952f67c9c1f654922528a196ced5420c1f7f","entrypoint":"agent-media-payload","importSpecifier":"openclaw/plugin-sdk/agent-media-payload"} +{"contentHash":"5bd6b57d5b5fafc9504efd61f1033c0e7d9523fce35de52957523341bed18007","entrypoint":"agent-media-payload","importSpecifier":"openclaw/plugin-sdk/agent-media-payload"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json index c395276b2e6d..51578c1da6f6 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json @@ -1 +1 @@ -{"contentHash":"279ca366d222d694f91fdaa9233c86f0ee6c388bdffffeb2b5093aed27a4ab4c","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} +{"contentHash":"f4354855bd26dc23f5b6e4c3de7a258ebb24d8dde8c5c3b0f81c77682497ee23","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-scope-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-scope-runtime.json index 116869273372..d6c520da670a 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-scope-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-scope-runtime.json @@ -1 +1 @@ -{"contentHash":"4ffa76fb2b34757ef4f6bd0ccc55182f4e963f7b9f055160b26d567584cc9bc6","entrypoint":"agent-scope-runtime","importSpecifier":"openclaw/plugin-sdk/agent-scope-runtime"} +{"contentHash":"31a2629e002e53ad33ccd08e5a2b78523aa24236256e0aec0700b6db8129c623","entrypoint":"agent-scope-runtime","importSpecifier":"openclaw/plugin-sdk/agent-scope-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/allowlist-config-edit.json b/docs/.generated/plugin-sdk-api-baseline/allowlist-config-edit.json index 5788cdd77a64..7fbcdeb2df3a 100644 --- a/docs/.generated/plugin-sdk-api-baseline/allowlist-config-edit.json +++ b/docs/.generated/plugin-sdk-api-baseline/allowlist-config-edit.json @@ -1 +1 @@ -{"contentHash":"4406fa069d6376725fe9c113dd65aeee2ebfcd4accdefb9aa888a989ddab2429","entrypoint":"allowlist-config-edit","importSpecifier":"openclaw/plugin-sdk/allowlist-config-edit"} +{"contentHash":"01b10ef1e03b58d5fd391bd8a4f3b0421be26314c8bca49d8a224ccbcbafbed1","entrypoint":"allowlist-config-edit","importSpecifier":"openclaw/plugin-sdk/allowlist-config-edit"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-auth-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-auth-runtime.json index 2c08e10846bf..4254ba9f2d17 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-auth-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-auth-runtime.json @@ -1 +1 @@ -{"contentHash":"0db493ed0935458bfdba48f1d2f854561331768ecc13bcc65d16581eb212d4f8","entrypoint":"approval-auth-runtime","importSpecifier":"openclaw/plugin-sdk/approval-auth-runtime"} +{"contentHash":"496edef91c62e94fc29d9d666c1b832dec36315a05a383db2f0eea9623cbc464","entrypoint":"approval-auth-runtime","importSpecifier":"openclaw/plugin-sdk/approval-auth-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-client-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-client-runtime.json index 0e434591a4df..97285863191a 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-client-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-client-runtime.json @@ -1 +1 @@ -{"contentHash":"9f38d8917387011027bd285233a15cb53723a9948f6c26472da2d45f397b561d","entrypoint":"approval-client-runtime","importSpecifier":"openclaw/plugin-sdk/approval-client-runtime"} +{"contentHash":"78e308f6e23dbac85cf8b9eb8ecad607fae673502432edab25ef27e650d5b81e","entrypoint":"approval-client-runtime","importSpecifier":"openclaw/plugin-sdk/approval-client-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-delivery-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-delivery-runtime.json index 6267086844ef..c9cdb92518e6 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-delivery-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-delivery-runtime.json @@ -1 +1 @@ -{"contentHash":"c2170d7ee8ba6a9d7b816312b652f131b4fbf3952fd6cbfa649582c0106feda9","entrypoint":"approval-delivery-runtime","importSpecifier":"openclaw/plugin-sdk/approval-delivery-runtime"} +{"contentHash":"ef139545fa43dfa5e14a57dea50bca6c8a650debc0bd8e42cb0fb00564e8e7f0","entrypoint":"approval-delivery-runtime","importSpecifier":"openclaw/plugin-sdk/approval-delivery-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-gateway-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-gateway-runtime.json index da6e3390698d..a3c5c7469c97 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-gateway-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-gateway-runtime.json @@ -1 +1 @@ -{"contentHash":"bd864f089ba285e271f9a98d149a85efad03bd687ddc50fd72da5b970501a492","entrypoint":"approval-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/approval-gateway-runtime"} +{"contentHash":"289ccd9f5a88bceb9e201d7851c1f110f59a8e115c4f07f63d1a2703df20df2a","entrypoint":"approval-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/approval-gateway-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-handler-adapter-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-handler-adapter-runtime.json index 4019eb052d3f..c293199a0819 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-handler-adapter-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-handler-adapter-runtime.json @@ -1 +1 @@ -{"contentHash":"c5bceaafa22ff897cd09dcf90712b4a756979aed8fab1bb1ca13ac9d4471a6a9","entrypoint":"approval-handler-adapter-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-adapter-runtime"} +{"contentHash":"e0c7d7a5e247c45c2f7e169c775542c78055f0acc1eda3b60b84b0f8440bfaf1","entrypoint":"approval-handler-adapter-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-adapter-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-handler-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-handler-runtime.json index cea70721276f..286ab48dd9cb 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-handler-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-handler-runtime.json @@ -1 +1 @@ -{"contentHash":"0f099da585bd9b5fa887c0c3e572512debb5d69ee1ad0f1d011d182ffe300a5c","entrypoint":"approval-handler-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-runtime"} +{"contentHash":"8435538ee0ab09ff5a1d7c6aab5b414439a1d5607210069917c2974acb168764","entrypoint":"approval-handler-runtime","importSpecifier":"openclaw/plugin-sdk/approval-handler-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-native-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-native-runtime.json index 8afd419cd965..ca5da9ed0355 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-native-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-native-runtime.json @@ -1 +1 @@ -{"contentHash":"3600f68d632471ecee191ffe0f2a1b419bb4ba544455d18930c4b96b22d8c2d2","entrypoint":"approval-native-runtime","importSpecifier":"openclaw/plugin-sdk/approval-native-runtime"} +{"contentHash":"e39cb3cbbd7828deec5c7088b48d028f9c46aca215aa18090dabc55a061c428d","entrypoint":"approval-native-runtime","importSpecifier":"openclaw/plugin-sdk/approval-native-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/approval-runtime.json b/docs/.generated/plugin-sdk-api-baseline/approval-runtime.json index df8b69d6ddb1..161918062656 100644 --- a/docs/.generated/plugin-sdk-api-baseline/approval-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/approval-runtime.json @@ -1 +1 @@ -{"contentHash":"e84e112bb8043500c4802580af7f41e73d3371411eae4fa661f28b65474fb026","entrypoint":"approval-runtime","importSpecifier":"openclaw/plugin-sdk/approval-runtime"} +{"contentHash":"dcc0b4622948450eab19bc2b8ae706a1b71a9b4a4969ff78f52027a39d8e606f","entrypoint":"approval-runtime","importSpecifier":"openclaw/plugin-sdk/approval-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-config-helpers.json b/docs/.generated/plugin-sdk-api-baseline/channel-config-helpers.json index 28dc8ccb91ea..980580c70bd8 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-config-helpers.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-config-helpers.json @@ -1 +1 @@ -{"contentHash":"dc308dc325f2fb6192ecead080dbba4f84214091101fdd07c0f3f2297b500cf5","entrypoint":"channel-config-helpers","importSpecifier":"openclaw/plugin-sdk/channel-config-helpers"} +{"contentHash":"bba3b4b62186570b01446bd10ab2a2e97255f2549a50f59acad53ed34a4d73f5","entrypoint":"channel-config-helpers","importSpecifier":"openclaw/plugin-sdk/channel-config-helpers"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-contract.json b/docs/.generated/plugin-sdk-api-baseline/channel-contract.json index fdd99f0d7f7f..8a3696b0f4e0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-contract.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-contract.json @@ -1 +1 @@ -{"contentHash":"44234eb25f54df45dc999e0c4314dbe36bf043b2d972e82732c7f9a0283862ab","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract"} +{"contentHash":"9e07df547f6ac4df96d9f14864c6f1c3cb637b52891d9f9abefa7ccefe8721dd","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-core.json b/docs/.generated/plugin-sdk-api-baseline/channel-core.json index ab4bd4186547..4ec10198b8be 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-core.json @@ -1 +1 @@ -{"contentHash":"02987e8974ddb4d4ac590a9d9fedb620689a7ef198b78c4cb87120dbb546fbd2","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} +{"contentHash":"88be0c4e2ee376ef02e41e26f031a2b6c30adb40ef8d31a79a493f2b189a0398","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-dm-policy.json b/docs/.generated/plugin-sdk-api-baseline/channel-dm-policy.json index 71384f7b926d..5f5262e5b265 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-dm-policy.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-dm-policy.json @@ -1 +1 @@ -{"contentHash":"9aa8d47743a80bd09d292a3b0f21529919e8e51f1aa765be513370b6e80706ef","entrypoint":"channel-dm-policy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy"} +{"contentHash":"6de14733b76cec50b27ddcdcb30222c0bc53eb2bbac9e9bb625c2150df94b70e","entrypoint":"channel-dm-policy","importSpecifier":"openclaw/plugin-sdk/channel-dm-policy"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json index 1a1f41f27f9b..0863ef76b887 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json @@ -1 +1 @@ -{"contentHash":"a531034eea4aa11ef247468055c90ac2be9b90eed15d20bcd621ba89647e61b3","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} +{"contentHash":"5efb7d678592f2889585c633a44c015d9adeaa12afe3feb2e481f32ea93d21e4","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-feedback.json b/docs/.generated/plugin-sdk-api-baseline/channel-feedback.json index 87b079000315..ba55d54a7941 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-feedback.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-feedback.json @@ -1 +1 @@ -{"contentHash":"4f1089cba4f061a6df633d593523eebf7645e19bc88fe152a5c7da5e7f213cbe","entrypoint":"channel-feedback","importSpecifier":"openclaw/plugin-sdk/channel-feedback"} +{"contentHash":"1d4d62e5f9885bf2263192488e721646ceb7183aff7400f3a8a975b5e9f9fcde","entrypoint":"channel-feedback","importSpecifier":"openclaw/plugin-sdk/channel-feedback"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-inbound-debounce.json b/docs/.generated/plugin-sdk-api-baseline/channel-inbound-debounce.json index c1099d46078b..79170a3e7226 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-inbound-debounce.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-inbound-debounce.json @@ -1 +1 @@ -{"contentHash":"b931541242c239ef45e02e794fbeabf2ca69e4071a261266aacc698f738b32bd","entrypoint":"channel-inbound-debounce","importSpecifier":"openclaw/plugin-sdk/channel-inbound-debounce"} +{"contentHash":"68e07ef5558e83fe34a66f9803453cbe81659ce53e8668296e70ac029185d99f","entrypoint":"channel-inbound-debounce","importSpecifier":"openclaw/plugin-sdk/channel-inbound-debounce"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json index 31bd2c3e0d0d..c3b8ecad6c3c 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json @@ -1 +1 @@ -{"contentHash":"a10b2b86f7fec9a99f951656b882414b73d04b5e16313bf0747e883da50c37df","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} +{"contentHash":"b0c40e20c831231029a39c03cb348d840f277460f7bc03cdab6831ed4bfd8261","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-ingress-runtime.json b/docs/.generated/plugin-sdk-api-baseline/channel-ingress-runtime.json index 059ed0270106..cb0d79e414f5 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-ingress-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-ingress-runtime.json @@ -1 +1 @@ -{"contentHash":"5ea7b454922c17117a2a34aae8f2bfd9a45666a7268dff223063bde72b379aff","entrypoint":"channel-ingress-runtime","importSpecifier":"openclaw/plugin-sdk/channel-ingress-runtime"} +{"contentHash":"d932e6132941f12bc6ad57ff99123374dd3947e3a0b4b13da4c4887546a879d2","entrypoint":"channel-ingress-runtime","importSpecifier":"openclaw/plugin-sdk/channel-ingress-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-message.json b/docs/.generated/plugin-sdk-api-baseline/channel-message.json index fea656d693c1..faf1d71f6ff3 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-message.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-message.json @@ -1 +1 @@ -{"contentHash":"db916c141015c7ec52d0f3efc7fcb855084e81d8502d610d63f3eb78f5b7a8e5","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} +{"contentHash":"c733d34b8639a37a120670e76c527ad604ea6070bdc41d0d2a446d3792a11def","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json index 30fc62b0d6da..2f6229c989b1 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json @@ -1 +1 @@ -{"contentHash":"7ac53597410b8bd7a525462e3e1598408010711d9cb2b77d68ad69332059ba7a","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} +{"contentHash":"61b4423fcc49ce4e88b5cdf36da0e93f145645f6d1c7187b9b2d4a4f59865041","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-pairing.json b/docs/.generated/plugin-sdk-api-baseline/channel-pairing.json index 89e60b0ff9fe..036659b11553 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-pairing.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-pairing.json @@ -1 +1 @@ -{"contentHash":"d92b60c0b7109446db7d24172281dfd6f79196d6428b8e6fd59e99a3d4d9f016","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"} +{"contentHash":"2c70a73bf29d787eae1a2b8127eed3643cb853ed55ec27fd2eac7a1e4099ad60","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json index 7744eafba266..6c1baa35c6c9 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json @@ -1 +1 @@ -{"contentHash":"7cf6e6ddd0bbb3c40fb012760ebcdce17834d9347bef430d4e7f2efd93db1f1e","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} +{"contentHash":"5ab2fcaef36345e468b4c9d9373427314a65b955c28da87b2fc02af8558e7ec6","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-policy.json b/docs/.generated/plugin-sdk-api-baseline/channel-policy.json index 4141b8368ddf..801005b62487 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-policy.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-policy.json @@ -1 +1 @@ -{"contentHash":"ce25306e19a7463a8f79bebe822c436a7064088e89fdb3a4f51fdff3d96a9070","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"} +{"contentHash":"101b45f041cfbd36fc54cfe169a00eb52305bd962d9d6866c9d631f974bc80d3","entrypoint":"channel-policy","importSpecifier":"openclaw/plugin-sdk/channel-policy"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-reply-pipeline.json b/docs/.generated/plugin-sdk-api-baseline/channel-reply-pipeline.json index 8e1af49fd205..fba4d863b9a2 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-reply-pipeline.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-reply-pipeline.json @@ -1 +1 @@ -{"contentHash":"d1c85fca4678c83f45606bf39c2304a1ebed29a0b12899b011a7c2d04fcf9553","entrypoint":"channel-reply-pipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline"} +{"contentHash":"df634c3e8c3ea6d44b600fe7f8b76699e55e6bb5725c67a0c05b3b6a9f4d64dc","entrypoint":"channel-reply-pipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-secret-basic-runtime.json b/docs/.generated/plugin-sdk-api-baseline/channel-secret-basic-runtime.json index 0285210bbd37..7703406fb20c 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-secret-basic-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-secret-basic-runtime.json @@ -1 +1 @@ -{"contentHash":"ea6b1a3fe6e37494ff269ead50dd8749a389f5d51db6933a07210dc2d7820e1c","entrypoint":"channel-secret-basic-runtime","importSpecifier":"openclaw/plugin-sdk/channel-secret-basic-runtime"} +{"contentHash":"d3d9c4258a5cdfb5db16610d9312d20e7ee3620869fc78b637cc4f759043589f","entrypoint":"channel-secret-basic-runtime","importSpecifier":"openclaw/plugin-sdk/channel-secret-basic-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-send-result.json b/docs/.generated/plugin-sdk-api-baseline/channel-send-result.json index f628e15337b9..a595ea56b34f 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-send-result.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-send-result.json @@ -1 +1 @@ -{"contentHash":"4dc96cbce5c2379f9bf2b72f124e310e8bc84c740b414d0c5a33a22f6314d17f","entrypoint":"channel-send-result","importSpecifier":"openclaw/plugin-sdk/channel-send-result"} +{"contentHash":"93cabcf483dbb7c28992d9c142ed08ea9233a68f59510215b46a3ab19eb6ec15","entrypoint":"channel-send-result","importSpecifier":"openclaw/plugin-sdk/channel-send-result"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-setup.json b/docs/.generated/plugin-sdk-api-baseline/channel-setup.json index ad03ea2a57fb..7db6925f5252 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-setup.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-setup.json @@ -1 +1 @@ -{"contentHash":"191b5318cf8fae32b4122cf6fcaa2e2a21bb3e93a1e223098bf508bee8c5323b","entrypoint":"channel-setup","importSpecifier":"openclaw/plugin-sdk/channel-setup"} +{"contentHash":"510f3965e8109bba757d69d4b103ad514b4b40c7839d97d34a3cdb683edebdf3","entrypoint":"channel-setup","importSpecifier":"openclaw/plugin-sdk/channel-setup"} diff --git a/docs/.generated/plugin-sdk-api-baseline/command-auth-native.json b/docs/.generated/plugin-sdk-api-baseline/command-auth-native.json index ec2201dffda8..8f8985167c6e 100644 --- a/docs/.generated/plugin-sdk-api-baseline/command-auth-native.json +++ b/docs/.generated/plugin-sdk-api-baseline/command-auth-native.json @@ -1 +1 @@ -{"contentHash":"aebe7a9c65a23ade6f6546e50384ad136e69feb3062ea326f4b9d044af7ab017","entrypoint":"command-auth-native","importSpecifier":"openclaw/plugin-sdk/command-auth-native"} +{"contentHash":"284e36bdc35a14d9d6fcbc54ba330cc8cbb4f776bca22bba18803727704ed750","entrypoint":"command-auth-native","importSpecifier":"openclaw/plugin-sdk/command-auth-native"} diff --git a/docs/.generated/plugin-sdk-api-baseline/command-auth.json b/docs/.generated/plugin-sdk-api-baseline/command-auth.json index a3d66e4736d1..ec21430b4c5e 100644 --- a/docs/.generated/plugin-sdk-api-baseline/command-auth.json +++ b/docs/.generated/plugin-sdk-api-baseline/command-auth.json @@ -1 +1 @@ -{"contentHash":"fc8c5f2fedb947a0911e3496b8a14695a1f697aa801f8c59fdb3dddf0f95b171","entrypoint":"command-auth","importSpecifier":"openclaw/plugin-sdk/command-auth"} +{"contentHash":"3a88f903879a4e8bf99742cb05c56e68f057ebc22283eb0787763559b58343ae","entrypoint":"command-auth","importSpecifier":"openclaw/plugin-sdk/command-auth"} diff --git a/docs/.generated/plugin-sdk-api-baseline/command-detection.json b/docs/.generated/plugin-sdk-api-baseline/command-detection.json index 3d7c5971c7ab..34956c5b95bf 100644 --- a/docs/.generated/plugin-sdk-api-baseline/command-detection.json +++ b/docs/.generated/plugin-sdk-api-baseline/command-detection.json @@ -1 +1 @@ -{"contentHash":"d259a6cdf11d9ec8687ffb426347afc978e259813c47681d81cd61bd68443757","entrypoint":"command-detection","importSpecifier":"openclaw/plugin-sdk/command-detection"} +{"contentHash":"c962c27e1df6e286fa99fdb55266c77c34b56993290ad034066ef8a8b4698307","entrypoint":"command-detection","importSpecifier":"openclaw/plugin-sdk/command-detection"} diff --git a/docs/.generated/plugin-sdk-api-baseline/command-status.json b/docs/.generated/plugin-sdk-api-baseline/command-status.json index e77b838c9c20..2918cbcfc517 100644 --- a/docs/.generated/plugin-sdk-api-baseline/command-status.json +++ b/docs/.generated/plugin-sdk-api-baseline/command-status.json @@ -1 +1 @@ -{"contentHash":"fd6eac0297be07a4ac68eb1a12f091c9fabc36e257c022998f69cd91fb765726","entrypoint":"command-status","importSpecifier":"openclaw/plugin-sdk/command-status"} +{"contentHash":"76ff5bbad5f25af7608995790a386ee3eccd3a34db6eab66827642e180773551","entrypoint":"command-status","importSpecifier":"openclaw/plugin-sdk/command-status"} diff --git a/docs/.generated/plugin-sdk-api-baseline/config-contracts.json b/docs/.generated/plugin-sdk-api-baseline/config-contracts.json index 00eaadd9d4de..efd51f2023ed 100644 --- a/docs/.generated/plugin-sdk-api-baseline/config-contracts.json +++ b/docs/.generated/plugin-sdk-api-baseline/config-contracts.json @@ -1 +1 @@ -{"contentHash":"657d052b745a903daaa80b7ae2525d89cf093e803bc2b69bc91073fd20ceb106","entrypoint":"config-contracts","importSpecifier":"openclaw/plugin-sdk/config-contracts"} +{"contentHash":"8ccbfcb14ef710956045cb6d53bcf566a9b7f6cd202668497d65e87222410b7c","entrypoint":"config-contracts","importSpecifier":"openclaw/plugin-sdk/config-contracts"} diff --git a/docs/.generated/plugin-sdk-api-baseline/config-mutation.json b/docs/.generated/plugin-sdk-api-baseline/config-mutation.json index bdcf1e35f34b..c1510d32a133 100644 --- a/docs/.generated/plugin-sdk-api-baseline/config-mutation.json +++ b/docs/.generated/plugin-sdk-api-baseline/config-mutation.json @@ -1 +1 @@ -{"contentHash":"eabfc0558c4a4b904d69fcb0b77c67eee1b5f09e8f2d7c8ed297aae7978db3b7","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"} +{"contentHash":"669d90ca98dec123fa65880b162c51ec588090c3a6bab155de2ceaca9e5679bd","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"} diff --git a/docs/.generated/plugin-sdk-api-baseline/config-runtime.json b/docs/.generated/plugin-sdk-api-baseline/config-runtime.json index fd036892e527..6e9754a2cabd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/config-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/config-runtime.json @@ -1 +1 @@ -{"contentHash":"0c33e9b6da738eb7627dbcdb324cc90d93c24c844d8777ee7eff9573e5e90a3b","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"} +{"contentHash":"e4805f4aa64049db5f742f0d5a68f5a39b8629551032debcee9dedda04544034","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/conversation-runtime.json b/docs/.generated/plugin-sdk-api-baseline/conversation-runtime.json index e7fbda2e72b6..64d048280cd3 100644 --- a/docs/.generated/plugin-sdk-api-baseline/conversation-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/conversation-runtime.json @@ -1 +1 @@ -{"contentHash":"1f751e446e40a43c6bdf3ef1c13dfd86e6b543d804858a145f7a59d458ce04a9","entrypoint":"conversation-runtime","importSpecifier":"openclaw/plugin-sdk/conversation-runtime"} +{"contentHash":"b2f5a9cffda3217d1ffd41fb811b2ee13c6fe817536e88c07d0db14173029759","entrypoint":"conversation-runtime","importSpecifier":"openclaw/plugin-sdk/conversation-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/core.json b/docs/.generated/plugin-sdk-api-baseline/core.json index 05c8c5f5fcb1..3e9a5616dfc6 100644 --- a/docs/.generated/plugin-sdk-api-baseline/core.json +++ b/docs/.generated/plugin-sdk-api-baseline/core.json @@ -1 +1 @@ -{"contentHash":"0c76b2fd651acded8caf3f8c76f7ee292b530b6e0a052cb4f0688fc0bc00a1c0","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} +{"contentHash":"ba33d41d6b070cdb45c7e67645007f64348c5e8422f9849b474b63e7721f95ae","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/diagnostic-runtime.json b/docs/.generated/plugin-sdk-api-baseline/diagnostic-runtime.json index 83a9ef62e0b3..6806fbcd2353 100644 --- a/docs/.generated/plugin-sdk-api-baseline/diagnostic-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/diagnostic-runtime.json @@ -1 +1 @@ -{"contentHash":"3a22e4de719dcc7055c6cc3771b72b1366b41b2d43613bcd904fd11283ca49a4","entrypoint":"diagnostic-runtime","importSpecifier":"openclaw/plugin-sdk/diagnostic-runtime"} +{"contentHash":"76f03283b5d78aca66035a95c695cf8549e1ae4a6c1c48fd7a00e44422bfe510","entrypoint":"diagnostic-runtime","importSpecifier":"openclaw/plugin-sdk/diagnostic-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/directory-runtime.json b/docs/.generated/plugin-sdk-api-baseline/directory-runtime.json index fe8685d01a85..14bcc27da824 100644 --- a/docs/.generated/plugin-sdk-api-baseline/directory-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/directory-runtime.json @@ -1 +1 @@ -{"contentHash":"5cb3edfc636dd00bf8a6fb5d577c057c6fc38a535fb473fa042244087a2c407b","entrypoint":"directory-runtime","importSpecifier":"openclaw/plugin-sdk/directory-runtime"} +{"contentHash":"5afab775380f00070113c9e463a8bf4b0f1ac1d9b09f8948425aba5a178f2ac5","entrypoint":"directory-runtime","importSpecifier":"openclaw/plugin-sdk/directory-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/discord.json b/docs/.generated/plugin-sdk-api-baseline/discord.json index 33e3e16a9544..8faa3a9f0e65 100644 --- a/docs/.generated/plugin-sdk-api-baseline/discord.json +++ b/docs/.generated/plugin-sdk-api-baseline/discord.json @@ -1 +1 @@ -{"contentHash":"19c444b6b2eb6062adabf88dc92060553042071de32d1113e2dae4a528d24fa6","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} +{"contentHash":"4db2b46463e57bebdabc6dea109643b96b415eb5b59bf0d300e59ba9379fd494","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} diff --git a/docs/.generated/plugin-sdk-api-baseline/extension-shared.json b/docs/.generated/plugin-sdk-api-baseline/extension-shared.json index 8275af47ae0b..d469b1b810b0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/extension-shared.json +++ b/docs/.generated/plugin-sdk-api-baseline/extension-shared.json @@ -1 +1 @@ -{"contentHash":"21749b994b61953b5a3f274b26ae9c497616521659b8dd02b6ab59cecc881f77","entrypoint":"extension-shared","importSpecifier":"openclaw/plugin-sdk/extension-shared"} +{"contentHash":"f404d619a327692c0ff6e6c82ea592e93aa193dd08be98a84c33096951690caa","entrypoint":"extension-shared","importSpecifier":"openclaw/plugin-sdk/extension-shared"} diff --git a/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json b/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json index 9c204f018cfc..84a62eadadd0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json @@ -1 +1 @@ -{"contentHash":"a39ad80ecba63c5458cb12f1621a7d359987cbbf9fb30acd91dfb32e08c72a8d","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"} +{"contentHash":"b68257d5e85a01745ebfd87465ab8b3d2a8047b42b614722c5e605968bb28df7","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/health.json b/docs/.generated/plugin-sdk-api-baseline/health.json index 5f1153addcf2..25bdc58367b2 100644 --- a/docs/.generated/plugin-sdk-api-baseline/health.json +++ b/docs/.generated/plugin-sdk-api-baseline/health.json @@ -1 +1 @@ -{"contentHash":"3f25999ca07382c0014dd474bffffa2410b2e3c403cfba58641eae8d25f7b20c","entrypoint":"health","importSpecifier":"openclaw/plugin-sdk/health"} +{"contentHash":"5305c7c75c86c24e1b445370136422abf1804f85a0a31294ee03af7c38d902ee","entrypoint":"health","importSpecifier":"openclaw/plugin-sdk/health"} diff --git a/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json b/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json index a077570c8236..9e38584cde91 100644 --- a/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/hook-runtime.json @@ -1 +1 @@ -{"contentHash":"a726635b09ec7e6bd252582b7400b234bbd70d9f816675fe632bf1702b0c9f3b","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"} +{"contentHash":"9eeaa0de58ffbd07e63d6c6c25ec345d2178e822d40e1fe58f36872f172436b7","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json index 478d8a377b96..cc4d58d27072 100644 --- a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json +++ b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json @@ -1 +1 @@ -{"contentHash":"ae337f8842b30f3d63ae5dd90963e7b73894cff02a3aea4b6f218ed0715fdd74","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} +{"contentHash":"e926284d729bf1f49e9f546bc3ba44225058a1d92a29fa514c23e23229efcfd0","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} diff --git a/docs/.generated/plugin-sdk-api-baseline/infra-runtime.json b/docs/.generated/plugin-sdk-api-baseline/infra-runtime.json index b20cfea0cc1c..c55428080ea1 100644 --- a/docs/.generated/plugin-sdk-api-baseline/infra-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/infra-runtime.json @@ -1 +1 @@ -{"contentHash":"4d37083b179c1ea8adf47c13a224d6b05ef817a7b23920a02c836b0e64e54f5b","entrypoint":"infra-runtime","importSpecifier":"openclaw/plugin-sdk/infra-runtime"} +{"contentHash":"f467cd02e663b2913d114e43133616aeb493024caf120f6359622e1e9ef41427","entrypoint":"infra-runtime","importSpecifier":"openclaw/plugin-sdk/infra-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/logging-core.json b/docs/.generated/plugin-sdk-api-baseline/logging-core.json index 28b802ad049a..0120066f36dd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/logging-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/logging-core.json @@ -1 +1 @@ -{"contentHash":"0559d15dde2d56ea677302ef074ddfe2c09c0dfbc2b84a5ad1e077d375f53e9a","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"} +{"contentHash":"0da8056fdf977ab74c89cbebb186895b5a1955302853a2f15784d552d7c30487","entrypoint":"logging-core","importSpecifier":"openclaw/plugin-sdk/logging-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/media-local-roots.json b/docs/.generated/plugin-sdk-api-baseline/media-local-roots.json index 97792265d10e..189048ccd0a1 100644 --- a/docs/.generated/plugin-sdk-api-baseline/media-local-roots.json +++ b/docs/.generated/plugin-sdk-api-baseline/media-local-roots.json @@ -1 +1 @@ -{"contentHash":"df3587c8c0300c86bcace11bcd3bd42f263ec1b527dd02fd5dded32496dd804d","entrypoint":"media-local-roots","importSpecifier":"openclaw/plugin-sdk/media-local-roots"} +{"contentHash":"654fa60879292e92a574e62daee2efdc0d26bd10dd1ba954dbf3843d8021b82a","entrypoint":"media-local-roots","importSpecifier":"openclaw/plugin-sdk/media-local-roots"} diff --git a/docs/.generated/plugin-sdk-api-baseline/media-runtime.json b/docs/.generated/plugin-sdk-api-baseline/media-runtime.json index da4711560dc9..4d20a6a2ff64 100644 --- a/docs/.generated/plugin-sdk-api-baseline/media-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/media-runtime.json @@ -1 +1 @@ -{"contentHash":"22eacf52b20db9ad9fe809b750911c6b137c53a4d766d7404ab60013a53ec901","entrypoint":"media-runtime","importSpecifier":"openclaw/plugin-sdk/media-runtime"} +{"contentHash":"3dbf4bdcaf3b0545cca9ac8f4dfcde947d698f6a457d51fa001974598d016c6d","entrypoint":"media-runtime","importSpecifier":"openclaw/plugin-sdk/media-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/media-understanding-runtime.json b/docs/.generated/plugin-sdk-api-baseline/media-understanding-runtime.json index a0f3c24d50b4..68d14ec90196 100644 --- a/docs/.generated/plugin-sdk-api-baseline/media-understanding-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/media-understanding-runtime.json @@ -1 +1 @@ -{"contentHash":"86454131178316a2ba29ac4f871cbfe2223c71f584cfc10baac91f3541d57442","entrypoint":"media-understanding-runtime","importSpecifier":"openclaw/plugin-sdk/media-understanding-runtime"} +{"contentHash":"a7ed309dd80eff0eff856d6c24b924cb15c127e1e2e6a69279e45abbd6120aed","entrypoint":"media-understanding-runtime","importSpecifier":"openclaw/plugin-sdk/media-understanding-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/media-understanding.json b/docs/.generated/plugin-sdk-api-baseline/media-understanding.json index 4d62f05828b6..1597cba95a67 100644 --- a/docs/.generated/plugin-sdk-api-baseline/media-understanding.json +++ b/docs/.generated/plugin-sdk-api-baseline/media-understanding.json @@ -1 +1 @@ -{"contentHash":"957d3fa32636f080a63823c0aec4247ed9d02baf39c7860879296c428e17d48f","entrypoint":"media-understanding","importSpecifier":"openclaw/plugin-sdk/media-understanding"} +{"contentHash":"ebfcda0b59c34586370fc234f07f80152fbc44d1c78bd4032363315826ad6c9b","entrypoint":"media-understanding","importSpecifier":"openclaw/plugin-sdk/media-understanding"} diff --git a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json index b90335be2863..5dc6b2f671bf 100644 --- a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json @@ -1 +1 @@ -{"contentHash":"8c90eea3929b848916e5d4aaa67e8dac636e47e8ab766a5260b888d3f2dd6cfb","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} +{"contentHash":"d0e076f39dabbf3d24abd13fa35f782336f49e92c6fc9fe005715d054704bfc9","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/memory-core-host-engine-foundation.json b/docs/.generated/plugin-sdk-api-baseline/memory-core-host-engine-foundation.json index 23328ba06b88..59332d6f7da0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/memory-core-host-engine-foundation.json +++ b/docs/.generated/plugin-sdk-api-baseline/memory-core-host-engine-foundation.json @@ -1 +1 @@ -{"contentHash":"fe951309f828ff94718b092d0017d40eeabf407663bc01179ce4a49309f60773","entrypoint":"memory-core-host-engine-foundation","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation"} +{"contentHash":"d7ecd46ea448722f37a5e17628d33a30b53d22ed4e2b8daa74f22e939f12fe32","entrypoint":"memory-core-host-engine-foundation","importSpecifier":"openclaw/plugin-sdk/memory-core-host-engine-foundation"} diff --git a/docs/.generated/plugin-sdk-api-baseline/memory-host-core.json b/docs/.generated/plugin-sdk-api-baseline/memory-host-core.json index 5f0c886ca4f0..1a198cae8c0e 100644 --- a/docs/.generated/plugin-sdk-api-baseline/memory-host-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/memory-host-core.json @@ -1 +1 @@ -{"contentHash":"2dcdb96ebaa91c8e938598fad3b3f5ecbd6999802ca7528c3f3c35aedb67c454","entrypoint":"memory-host-core","importSpecifier":"openclaw/plugin-sdk/memory-host-core"} +{"contentHash":"9c179624b39b9a4e7ae1847d9ca42ebfc34668bcf73246d06ea3558591d0f4d1","entrypoint":"memory-host-core","importSpecifier":"openclaw/plugin-sdk/memory-host-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json b/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json index 4c722583f968..716f27e95e63 100644 --- a/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/model-session-runtime.json @@ -1 +1 @@ -{"contentHash":"fa78e49abb6b94784740bffdbe267b310bee1a6fbe12b1204182d749d8bc0c43","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"} +{"contentHash":"06ebd75fd0911b6724de96ea7bcd7c83eab5e895be3d3a6b65dff3e98bc3b0a4","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/models-provider-runtime.json b/docs/.generated/plugin-sdk-api-baseline/models-provider-runtime.json index fac258f0c74d..5117959febfd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/models-provider-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/models-provider-runtime.json @@ -1 +1 @@ -{"contentHash":"5afa8f742a472a31c3cbdbff8bebab674109049148bc24248874e813057a0c46","entrypoint":"models-provider-runtime","importSpecifier":"openclaw/plugin-sdk/models-provider-runtime"} +{"contentHash":"a41b2cb57c392c10e0c16d4b0f8b4d9c9df82c5b8cb1ecac6223881645ce74bb","entrypoint":"models-provider-runtime","importSpecifier":"openclaw/plugin-sdk/models-provider-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/native-command-config-runtime.json b/docs/.generated/plugin-sdk-api-baseline/native-command-config-runtime.json index 3d1be7f693c0..815d77df3641 100644 --- a/docs/.generated/plugin-sdk-api-baseline/native-command-config-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/native-command-config-runtime.json @@ -1 +1 @@ -{"contentHash":"8a51693c575603e3c263703a4162d91019e74c004c1ed26e6a6e6c590a784503","entrypoint":"native-command-config-runtime","importSpecifier":"openclaw/plugin-sdk/native-command-config-runtime"} +{"contentHash":"8f061f10edc2f699b8ad6d7c607c7d69d1a4d9a36d42989ab304c183caba3366","entrypoint":"native-command-config-runtime","importSpecifier":"openclaw/plugin-sdk/native-command-config-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/native-command-registry.json b/docs/.generated/plugin-sdk-api-baseline/native-command-registry.json index 480b6531aa69..f377ef1dbb72 100644 --- a/docs/.generated/plugin-sdk-api-baseline/native-command-registry.json +++ b/docs/.generated/plugin-sdk-api-baseline/native-command-registry.json @@ -1 +1 @@ -{"contentHash":"086b6a9f959c896ecbb529cd2e44054306a69c9ad4c331f323a948b524643188","entrypoint":"native-command-registry","importSpecifier":"openclaw/plugin-sdk/native-command-registry"} +{"contentHash":"b8b59df62269a2619eeb2f64a5714690124ec432d98ba23df4aa037d6971a7a1","entrypoint":"native-command-registry","importSpecifier":"openclaw/plugin-sdk/native-command-registry"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-command-runtime.json b/docs/.generated/plugin-sdk-api-baseline/plugin-command-runtime.json index 81a60130d47d..170a21a37df7 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-command-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-command-runtime.json @@ -1 +1 @@ -{"contentHash":"be5d21b9760636dd2956ac64b786213532e74ebb4cd1cc844a923b7bd5949be7","entrypoint":"plugin-command-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-command-runtime"} +{"contentHash":"567c6274b72539a82646ce8a48fe7cdf463f15c58966ec79eb8d02024ed3b771","entrypoint":"plugin-command-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-command-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-config-runtime.json b/docs/.generated/plugin-sdk-api-baseline/plugin-config-runtime.json index 7db9c5481728..346a01605647 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-config-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-config-runtime.json @@ -1 +1 @@ -{"contentHash":"fc14a640d5328c2d317eef0e9e5e11f40c81f31fb3f44e4f31587f725ba0e22c","entrypoint":"plugin-config-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-config-runtime"} +{"contentHash":"6ddb8caaf90a1087c7144354b07bf2b62cbc83173ffc0cad5f09cad8fc1b6d1e","entrypoint":"plugin-config-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-config-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json index a000e082935a..5fc9a2e91bf1 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json @@ -1 +1 @@ -{"contentHash":"ec039f279134221e3937ed3388e0cc0f065e2ab0a62a80b42384d7a10cbd98c3","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} +{"contentHash":"2440b13669fc52c68ff4fa9ebf0b068e1e9c9f0d559a466ba1e2cb19f062bd26","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json index 33b125630318..b54528965550 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json @@ -1 +1 @@ -{"contentHash":"c86462cf73045c6a2eb37a77be545b3148b58890dd0b112750d24b0829ad6166","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} +{"contentHash":"6062c0e5c9b75ea04815865dd438173a1e9a30b6faed04eb593d722cf32ee41f","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/provider-auth.json b/docs/.generated/plugin-sdk-api-baseline/provider-auth.json index 1fc9d0b01933..cc061b9d8d8f 100644 --- a/docs/.generated/plugin-sdk-api-baseline/provider-auth.json +++ b/docs/.generated/plugin-sdk-api-baseline/provider-auth.json @@ -1 +1 @@ -{"contentHash":"e2ef3ba2f2196a04530aea5fdcdbf02e064f6c4cf193fcf56cbbc84869e69109","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"} +{"contentHash":"482a0adcedff256704674b9231fea56ffe29a585557e378f10d14cfd009687fa","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"} diff --git a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json index f6ec3b491fa3..e047af35d8bd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json @@ -1 +1 @@ -{"contentHash":"9cd0143763f80869c7182fcab934d493a0106d0b358d989fd64270fae540c73c","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} +{"contentHash":"8c67ae63bf2d7a728b2f77748cfabc11f079b8b926898904982869f1502638a5","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/question-gateway-runtime.json b/docs/.generated/plugin-sdk-api-baseline/question-gateway-runtime.json index eb8279990b1e..7ab3fdba9b71 100644 --- a/docs/.generated/plugin-sdk-api-baseline/question-gateway-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/question-gateway-runtime.json @@ -1 +1 @@ -{"contentHash":"1271e5eec56d6d72fcb47498dfa7b0eef0e25f136190f6ee103bc11930e4cdd9","entrypoint":"question-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/question-gateway-runtime"} +{"contentHash":"68b4d6f2a48e54a5db9fe5655a3ca0e46351f7a4caa21d065d7ccf3b628abde3","entrypoint":"question-gateway-runtime","importSpecifier":"openclaw/plugin-sdk/question-gateway-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/reply-chunking.json b/docs/.generated/plugin-sdk-api-baseline/reply-chunking.json index 6f85231f4858..bef0b6de899a 100644 --- a/docs/.generated/plugin-sdk-api-baseline/reply-chunking.json +++ b/docs/.generated/plugin-sdk-api-baseline/reply-chunking.json @@ -1 +1 @@ -{"contentHash":"12c9cb08e664eca320085e6ab274fd70008686e709da50f8d0b1db60ed706f90","entrypoint":"reply-chunking","importSpecifier":"openclaw/plugin-sdk/reply-chunking"} +{"contentHash":"7059cece2048a14ea093cdcaf9d45ce5f578f1d8522b1b600a5aeb60376a66ae","entrypoint":"reply-chunking","importSpecifier":"openclaw/plugin-sdk/reply-chunking"} diff --git a/docs/.generated/plugin-sdk-api-baseline/reply-dispatch-runtime.json b/docs/.generated/plugin-sdk-api-baseline/reply-dispatch-runtime.json index 25f11c76f48c..936afa00b8d7 100644 --- a/docs/.generated/plugin-sdk-api-baseline/reply-dispatch-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/reply-dispatch-runtime.json @@ -1 +1 @@ -{"contentHash":"8dc220fc0bdf4ab69de389162dfe792335b83ddc36b900868fda4d418e3e0586","entrypoint":"reply-dispatch-runtime","importSpecifier":"openclaw/plugin-sdk/reply-dispatch-runtime"} +{"contentHash":"35c993147ea09db79c62c87822fb923039f87323d244ece52d872a2b5f19a3cd","entrypoint":"reply-dispatch-runtime","importSpecifier":"openclaw/plugin-sdk/reply-dispatch-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/reply-payload.json b/docs/.generated/plugin-sdk-api-baseline/reply-payload.json index 5a6bb25bc50f..253247ad6b52 100644 --- a/docs/.generated/plugin-sdk-api-baseline/reply-payload.json +++ b/docs/.generated/plugin-sdk-api-baseline/reply-payload.json @@ -1 +1 @@ -{"contentHash":"560a2f2591955d9d1bf27498825ce6585ecca0528dcfecd02ea81a55e06164a1","entrypoint":"reply-payload","importSpecifier":"openclaw/plugin-sdk/reply-payload"} +{"contentHash":"aa91cf06f0ac5fb25ded9c937a3cf3e6e7ba692da0c8d4c1ceb659f13e17e706","entrypoint":"reply-payload","importSpecifier":"openclaw/plugin-sdk/reply-payload"} diff --git a/docs/.generated/plugin-sdk-api-baseline/reply-runtime.json b/docs/.generated/plugin-sdk-api-baseline/reply-runtime.json index a73fd6f36326..05ef4a6f03ac 100644 --- a/docs/.generated/plugin-sdk-api-baseline/reply-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/reply-runtime.json @@ -1 +1 @@ -{"contentHash":"664a2e42bd0e6ed65f9f8ae72bb79a7d0b89b3bd30e0f01390f3b5f4075cbf7a","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"} +{"contentHash":"88e867535fca9357f158882f981694d76d9d29034b1a591c66a709c7e3e7a9fe","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/routing.json b/docs/.generated/plugin-sdk-api-baseline/routing.json index 8a2c39c6fb5b..444fc60dcbf4 100644 --- a/docs/.generated/plugin-sdk-api-baseline/routing.json +++ b/docs/.generated/plugin-sdk-api-baseline/routing.json @@ -1 +1 @@ -{"contentHash":"d012d30a5588fbca2356796ab5fa4b88b1cca27343e04a44c0a989e3b5d405d3","entrypoint":"routing","importSpecifier":"openclaw/plugin-sdk/routing"} +{"contentHash":"e6978c1ead017ec2ff17d275e71efde9cce987b47704ea0641d9f21f9a2bd848","entrypoint":"routing","importSpecifier":"openclaw/plugin-sdk/routing"} diff --git a/docs/.generated/plugin-sdk-api-baseline/runtime-config-snapshot.json b/docs/.generated/plugin-sdk-api-baseline/runtime-config-snapshot.json index 3f49ac1afcf2..bd3bb32c6b77 100644 --- a/docs/.generated/plugin-sdk-api-baseline/runtime-config-snapshot.json +++ b/docs/.generated/plugin-sdk-api-baseline/runtime-config-snapshot.json @@ -1 +1 @@ -{"contentHash":"bfdc7c3120a5284944f6e4afbe639797832db915e9a6c4a6cca1446e59c03257","entrypoint":"runtime-config-snapshot","importSpecifier":"openclaw/plugin-sdk/runtime-config-snapshot"} +{"contentHash":"a2cc06e6088fd2ab1c87f3feaa067aa004de9683b7ffd6422598cc49a2131ef9","entrypoint":"runtime-config-snapshot","importSpecifier":"openclaw/plugin-sdk/runtime-config-snapshot"} diff --git a/docs/.generated/plugin-sdk-api-baseline/runtime-store.json b/docs/.generated/plugin-sdk-api-baseline/runtime-store.json index 3756a620df98..f1bd830ef6ca 100644 --- a/docs/.generated/plugin-sdk-api-baseline/runtime-store.json +++ b/docs/.generated/plugin-sdk-api-baseline/runtime-store.json @@ -1 +1 @@ -{"contentHash":"01918241de10b9b625379aa143ea2b3bee3ebf53369dde648c3402227d3cb5f7","entrypoint":"runtime-store","importSpecifier":"openclaw/plugin-sdk/runtime-store"} +{"contentHash":"63232a3202e53d259d2ae5dc855edf3911084892b5107ef0e4e35cbd10ebf414","entrypoint":"runtime-store","importSpecifier":"openclaw/plugin-sdk/runtime-store"} diff --git a/docs/.generated/plugin-sdk-api-baseline/runtime.json b/docs/.generated/plugin-sdk-api-baseline/runtime.json index bb4336f3081d..4273e4f1a0e1 100644 --- a/docs/.generated/plugin-sdk-api-baseline/runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/runtime.json @@ -1 +1 @@ -{"contentHash":"678066cdd3d6b3e15384ce4b62c374b533fd92092d72b64e8d2a4659d8dc5dd1","entrypoint":"runtime","importSpecifier":"openclaw/plugin-sdk/runtime"} +{"contentHash":"8ed192bda18455093bac17415bff2e6247482e19b5e39cd4fdcf77ef32225cbe","entrypoint":"runtime","importSpecifier":"openclaw/plugin-sdk/runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/secret-input-runtime.json b/docs/.generated/plugin-sdk-api-baseline/secret-input-runtime.json index b9adef89e83e..a7fa0900a6fc 100644 --- a/docs/.generated/plugin-sdk-api-baseline/secret-input-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/secret-input-runtime.json @@ -1 +1 @@ -{"contentHash":"aedf69e90fe8358e2dea78f2da7612773795c58bf56ee91838428fd8a9b505bc","entrypoint":"secret-input-runtime","importSpecifier":"openclaw/plugin-sdk/secret-input-runtime"} +{"contentHash":"25fef1ad61081a884d7df06225a81e6f0f22e992ffa65b467e906e04f083767b","entrypoint":"secret-input-runtime","importSpecifier":"openclaw/plugin-sdk/secret-input-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/secret-ref-runtime.json b/docs/.generated/plugin-sdk-api-baseline/secret-ref-runtime.json index cacfdf89c53d..ef8d7193acf0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/secret-ref-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/secret-ref-runtime.json @@ -1 +1 @@ -{"contentHash":"6d67b6c12e0697673a14fa7e97c17937c46d09c8c37aab3c7a077d525ee3bbc9","entrypoint":"secret-ref-runtime","importSpecifier":"openclaw/plugin-sdk/secret-ref-runtime"} +{"contentHash":"8ca2614364c682459caab5065e113989494848a4b6b0eb7eae5c58bab4a63732","entrypoint":"secret-ref-runtime","importSpecifier":"openclaw/plugin-sdk/secret-ref-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/security-runtime.json b/docs/.generated/plugin-sdk-api-baseline/security-runtime.json index 44a2936d522f..be4f903ce7e0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/security-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/security-runtime.json @@ -1 +1 @@ -{"contentHash":"26008b5b883abc446107f21c3ebba458c776d63c87eb79200e500d38c07869a7","entrypoint":"security-runtime","importSpecifier":"openclaw/plugin-sdk/security-runtime"} +{"contentHash":"c32351ef7200a68e5cd7237f0a48627cc71a34bad237dbb747d1922d68bee46b","entrypoint":"security-runtime","importSpecifier":"openclaw/plugin-sdk/security-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/session-catalog.json b/docs/.generated/plugin-sdk-api-baseline/session-catalog.json index ce235d30f317..d7c1808333df 100644 --- a/docs/.generated/plugin-sdk-api-baseline/session-catalog.json +++ b/docs/.generated/plugin-sdk-api-baseline/session-catalog.json @@ -1 +1 @@ -{"contentHash":"7bae1f99dfbd5920f9fa2e82abfefdf11e28c3150f15b22ee3fcf115a4dcef19","entrypoint":"session-catalog","importSpecifier":"openclaw/plugin-sdk/session-catalog"} +{"contentHash":"a2bdfb37d7385674a2f112f68acba358685e1fec185342df5a9662ba9003ee17","entrypoint":"session-catalog","importSpecifier":"openclaw/plugin-sdk/session-catalog"} diff --git a/docs/.generated/plugin-sdk-api-baseline/session-store-runtime.json b/docs/.generated/plugin-sdk-api-baseline/session-store-runtime.json index a93b3946b42a..70cf7ce144cd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/session-store-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/session-store-runtime.json @@ -1 +1 @@ -{"contentHash":"a4791b5d0a776811ed8beb14a10450948c7cf2c5be08872c83d4ac09dd8d2154","entrypoint":"session-store-runtime","importSpecifier":"openclaw/plugin-sdk/session-store-runtime"} +{"contentHash":"44c8002745939dda2cadf7e9e88ddcda40cf8684332abd0f7aede307c1d9b462","entrypoint":"session-store-runtime","importSpecifier":"openclaw/plugin-sdk/session-store-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/setup-runtime.json b/docs/.generated/plugin-sdk-api-baseline/setup-runtime.json index ca69597c0829..1f1fda46a879 100644 --- a/docs/.generated/plugin-sdk-api-baseline/setup-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/setup-runtime.json @@ -1 +1 @@ -{"contentHash":"125d008a70654ff2013391d54755c7668bf49293d8629454ef5c9d2ab07d1d18","entrypoint":"setup-runtime","importSpecifier":"openclaw/plugin-sdk/setup-runtime"} +{"contentHash":"7572bc03dcfd8641921d185df393d410282fb3eb95f328449f5250c2c50d8362","entrypoint":"setup-runtime","importSpecifier":"openclaw/plugin-sdk/setup-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/setup.json b/docs/.generated/plugin-sdk-api-baseline/setup.json index a77ef8d3de3c..aa4d968df26d 100644 --- a/docs/.generated/plugin-sdk-api-baseline/setup.json +++ b/docs/.generated/plugin-sdk-api-baseline/setup.json @@ -1 +1 @@ -{"contentHash":"d122c7e96108909e3ea2bb220e49bfe0e1da221655884203ceb07b4a78dc84a4","entrypoint":"setup","importSpecifier":"openclaw/plugin-sdk/setup"} +{"contentHash":"610f0ddda0d6488ec9558eb3c3642aa37157affb8ed502868498c2b8b1929fce","entrypoint":"setup","importSpecifier":"openclaw/plugin-sdk/setup"} diff --git a/docs/.generated/plugin-sdk-api-baseline/skill-commands-runtime.json b/docs/.generated/plugin-sdk-api-baseline/skill-commands-runtime.json index 5af3908d8639..52e60a710a77 100644 --- a/docs/.generated/plugin-sdk-api-baseline/skill-commands-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/skill-commands-runtime.json @@ -1 +1 @@ -{"contentHash":"c78fa67bd8c4bb9bc3d1188876a5d5a8a345c409a1bd55fe74e8b27053858ad8","entrypoint":"skill-commands-runtime","importSpecifier":"openclaw/plugin-sdk/skill-commands-runtime"} +{"contentHash":"6bf95e6b195c9c8df6f4a2aa0629e53028fbecb5bc1f8af9c20a2221fbd0dade","entrypoint":"skill-commands-runtime","importSpecifier":"openclaw/plugin-sdk/skill-commands-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/speech-settings.json b/docs/.generated/plugin-sdk-api-baseline/speech-settings.json index 028b6fe65ae4..a29a1095c5f9 100644 --- a/docs/.generated/plugin-sdk-api-baseline/speech-settings.json +++ b/docs/.generated/plugin-sdk-api-baseline/speech-settings.json @@ -1 +1 @@ -{"contentHash":"1307d56ecd36e77f2583bad440cb5781c064ce56755c41c7d63c846fb7d1d177","entrypoint":"speech-settings","importSpecifier":"openclaw/plugin-sdk/speech-settings"} +{"contentHash":"99c8490f019c8c9ecaca02b94cbe752386eef5ba2e5f02310593588ce6768446","entrypoint":"speech-settings","importSpecifier":"openclaw/plugin-sdk/speech-settings"} diff --git a/docs/.generated/plugin-sdk-api-baseline/ssrf-policy.json b/docs/.generated/plugin-sdk-api-baseline/ssrf-policy.json index fefe4ccb5d22..d63f0d5123b8 100644 --- a/docs/.generated/plugin-sdk-api-baseline/ssrf-policy.json +++ b/docs/.generated/plugin-sdk-api-baseline/ssrf-policy.json @@ -1 +1 @@ -{"contentHash":"646f40b3101481f438b90807e0865d7e4f07d9da951dd7f82b2b3c77eca3e10b","entrypoint":"ssrf-policy","importSpecifier":"openclaw/plugin-sdk/ssrf-policy"} +{"contentHash":"8931852b1eafb6f95d9fe3dac7936436def9d4089815ac0ff8c97e106be4727d","entrypoint":"ssrf-policy","importSpecifier":"openclaw/plugin-sdk/ssrf-policy"} diff --git a/docs/.generated/plugin-sdk-api-baseline/ssrf-runtime.json b/docs/.generated/plugin-sdk-api-baseline/ssrf-runtime.json index 639fcbea1b7f..c6030c1887b7 100644 --- a/docs/.generated/plugin-sdk-api-baseline/ssrf-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/ssrf-runtime.json @@ -1 +1 @@ -{"contentHash":"cabb7f5f5e7516c2948b84da18caf1c9f8af4fba6ac1d8441cfa5b5d215af84a","entrypoint":"ssrf-runtime","importSpecifier":"openclaw/plugin-sdk/ssrf-runtime"} +{"contentHash":"f488c59b2c3ac1aee5d13073f85e986828b6c79ba5ade6ec23ba15c6a4a9433d","entrypoint":"ssrf-runtime","importSpecifier":"openclaw/plugin-sdk/ssrf-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/status-helpers.json b/docs/.generated/plugin-sdk-api-baseline/status-helpers.json index a66ac36464b6..d665bb3815e7 100644 --- a/docs/.generated/plugin-sdk-api-baseline/status-helpers.json +++ b/docs/.generated/plugin-sdk-api-baseline/status-helpers.json @@ -1 +1 @@ -{"contentHash":"0714c3ee4d2388fb476a891d8c0cbefc74b4765fc748846d190bd015b62d3864","entrypoint":"status-helpers","importSpecifier":"openclaw/plugin-sdk/status-helpers"} +{"contentHash":"5d466bf5186356cdb87531a463f5791d7b8b3d74a853a0d40748ff8bc59cc538","entrypoint":"status-helpers","importSpecifier":"openclaw/plugin-sdk/status-helpers"} diff --git a/docs/.generated/plugin-sdk-api-baseline/telegram-account.json b/docs/.generated/plugin-sdk-api-baseline/telegram-account.json index b71943dc7748..b7f9767814be 100644 --- a/docs/.generated/plugin-sdk-api-baseline/telegram-account.json +++ b/docs/.generated/plugin-sdk-api-baseline/telegram-account.json @@ -1 +1 @@ -{"contentHash":"7d4d8f8eb94af9fc28456f6e1c5371ead887c263be8b2254864b1216ec8296a5","entrypoint":"telegram-account","importSpecifier":"openclaw/plugin-sdk/telegram-account"} +{"contentHash":"b2beba95a9528eeb7a9c8713714718f43e952ae477005d952ff6cce6e6371dbe","entrypoint":"telegram-account","importSpecifier":"openclaw/plugin-sdk/telegram-account"} diff --git a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json index 3c0b2305ef7f..d0e8625d01ec 100644 --- a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json +++ b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json @@ -1 +1 @@ -{"contentHash":"90cb8357bc1472f7b5df268aa40850fbc8d33e401e3f9d5b7ea73f7b0842b0ea","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} +{"contentHash":"01ee958e6d401afeba40b900f1c692237c9a986a2f59541b0463760b393e0fd8","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} diff --git a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json index 39eddd7fe39e..8c2a0fbf29d8 100644 --- a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json +++ b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json @@ -1 +1 @@ -{"contentHash":"81022c7a14e82a7d0b50aaec10fa9d6c5494e2cd8398aa1bc4fe4556f5fbfdcf","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} +{"contentHash":"3ec74b540127414926617ea51142f7581ff3ceb0b6d11cc84055fa90e43a1577","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} diff --git a/docs/.generated/plugin-sdk-api-baseline/webhook-request-guards.json b/docs/.generated/plugin-sdk-api-baseline/webhook-request-guards.json index aacf96ee055a..b3489cac77ed 100644 --- a/docs/.generated/plugin-sdk-api-baseline/webhook-request-guards.json +++ b/docs/.generated/plugin-sdk-api-baseline/webhook-request-guards.json @@ -1 +1 @@ -{"contentHash":"431ae89ff59e0131e1019e132a2d1afb0608a23e8ff919cc9b60f69f614cb90a","entrypoint":"webhook-request-guards","importSpecifier":"openclaw/plugin-sdk/webhook-request-guards"} +{"contentHash":"42351fc155ba065d9ee60a84d66abb2d7796ba4b1d67a4b9ff80e6a3e39b4a5f","entrypoint":"webhook-request-guards","importSpecifier":"openclaw/plugin-sdk/webhook-request-guards"} diff --git a/docs/nodes/index.md b/docs/nodes/index.md index 6802062b9b4c..6fed46693de3 100644 --- a/docs/nodes/index.md +++ b/docs/nodes/index.md @@ -406,6 +406,29 @@ Gateway transcript, and reject attachments and images. Claude Desktop rows and nodes that do not advertise the run command remain view-only. The macOS app node does not advertise this command yet, so its rows remain view-only. +### Host OpenClaw sessions + +A headless node host can separately opt into full OpenClaw session hosting from +its local installation: + +```json5 +{ + nodeHost: { + workerRuns: { enabled: true }, + }, +} +``` + +Restart the node host after enabling this setting. At startup it advertises the +exact OpenClaw version, worker-bundle hash, and worker protocol features of its +own installation. The Gateway offers the device as a session host only while +that advertisement is live, and provisioning requires the node and Gateway +versions to match exactly. If they differ, update the node before retrying. + +This setting completes device-environment provisioning but does not yet enable +turn launch on the device. The local-install chain adds supervised launch and +workspace transport in subsequent steps. + See [Anthropic: Claude sessions across computers](/providers/anthropic#claude-sessions-across-computers) for the Control UI behavior and storage sources. diff --git a/packages/gateway-client/src/client.ts b/packages/gateway-client/src/client.ts index a9b956caf1e6..514d342a6167 100644 --- a/packages/gateway-client/src/client.ts +++ b/packages/gateway-client/src/client.ts @@ -265,6 +265,7 @@ export type GatewayClientOptions = { scopes?: string[]; caps?: string[]; commands?: string[]; + workerRuns?: ConnectParams["workerRuns"]; permissions?: Record; pathEnv?: string; env?: NodeJS.ProcessEnv; @@ -458,11 +459,16 @@ export class GatewayClient { }; } - updateNodeManifest(manifest: { caps: string[]; commands: string[] }): void { + updateNodeManifest(manifest: { + caps: string[]; + commands: string[]; + workerRuns?: ConnectParams["workerRuns"]; + }): void { this.opts = { ...this.opts, caps: [...manifest.caps], commands: [...manifest.commands], + workerRuns: manifest.workerRuns ? structuredClone(manifest.workerRuns) : undefined, }; // Node command declarations are connect metadata. Reconnect so the Gateway // can reconcile approval before dispatching a newly available command. @@ -757,6 +763,7 @@ export class GatewayClient { }, caps: Array.isArray(this.opts.caps) ? this.opts.caps : [], commands: Array.isArray(this.opts.commands) ? this.opts.commands : undefined, + workerRuns: useLegacyNodeProtocolEnvelope ? undefined : this.opts.workerRuns, permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions diff --git a/packages/gateway-client/src/client.watchdog.test.ts b/packages/gateway-client/src/client.watchdog.test.ts index 1390ca521ced..51e9574faa64 100644 --- a/packages/gateway-client/src/client.watchdog.test.ts +++ b/packages/gateway-client/src/client.watchdog.test.ts @@ -630,12 +630,22 @@ describe("GatewayClient", () => { client.updateNodeManifest({ caps: ["canvas", "system"], commands: ["canvas.present", "system.run"], + workerRuns: { + bundleHash: "a".repeat(64), + openclawVersion: "2026.8.12", + protocolFeatures: ["worker-heartbeat-v1"], + }, }); expect(close).toHaveBeenCalledWith(1012, "node manifest changed"); expect((client as unknown as { opts: Record }).opts).toMatchObject({ caps: ["canvas", "system"], commands: ["canvas.present", "system.run"], + workerRuns: { + bundleHash: "a".repeat(64), + openclawVersion: "2026.8.12", + protocolFeatures: ["worker-heartbeat-v1"], + }, }); }); diff --git a/packages/gateway-protocol/src/schema/frames.ts b/packages/gateway-protocol/src/schema/frames.ts index bbd3c7002bc4..ee01d97363b4 100644 --- a/packages/gateway-protocol/src/schema/frames.ts +++ b/packages/gateway-protocol/src/schema/frames.ts @@ -5,6 +5,7 @@ import { closedObject } from "./closed-object.js"; import { GatewayClientIdSchema, GatewayClientModeSchema, NonEmptyString } from "./primitives.js"; import { SessionVisibilitySchema } from "./sessions-sharing-values.js"; import { SnapshotSchema, StateVersionSchema } from "./snapshot.js"; +import { WorkerAdmissionHandshakeSchema } from "./worker-admission.js"; export const GATEWAY_SERVER_CAPS = { BOARD_WIDGET_PUT_CANVAS_DOC: "board-widget-put-canvas-doc", @@ -47,6 +48,8 @@ export const ConnectParamsSchema = closedObject({ }), caps: Type.Optional(Type.Array(NonEmptyString, { default: [] })), commands: Type.Optional(Type.Array(NonEmptyString)), + /** Additive node-local worker build identity; presence advertises session hosting. */ + workerRuns: Type.Optional(WorkerAdmissionHandshakeSchema), permissions: Type.Optional(Type.Record(NonEmptyString, Type.Boolean())), pathEnv: Type.Optional(Type.String()), role: Type.Optional(NonEmptyString), diff --git a/packages/gateway-protocol/src/schema/frames.worker-runs.test.ts b/packages/gateway-protocol/src/schema/frames.worker-runs.test.ts new file mode 100644 index 000000000000..de6a4386f5fc --- /dev/null +++ b/packages/gateway-protocol/src/schema/frames.worker-runs.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { validateConnectParams } from "../validator-registry.js"; + +describe("node worker-runs connect manifest", () => { + const connect = { + minProtocol: 1, + maxProtocol: 1, + client: { id: "test", version: "1.0.0", platform: "test", mode: "test" }, + }; + + it("accepts only the exact additive worker build identity", () => { + expect( + validateConnectParams({ + ...connect, + workerRuns: { + bundleHash: "a".repeat(64), + openclawVersion: "2026.8.12", + protocolFeatures: ["worker-heartbeat-v1"], + }, + }), + ).toBe(true); + expect(validateConnectParams({ ...connect, workerRuns: { enabled: true } })).toBe(false); + }); +}); diff --git a/src/config/config.node-agent-runs.test.ts b/src/config/config.node-agent-runs.test.ts index 661666d5f4e7..2b212121db84 100644 --- a/src/config/config.node-agent-runs.test.ts +++ b/src/config/config.node-agent-runs.test.ts @@ -27,4 +27,18 @@ describe("node agent-runs config", () => { ).toBe(true); } }); + + it.each([true, false])("accepts worker session hosting enabled=%s", (enabled) => { + expect(validateConfigObject({ nodeHost: { workerRuns: { enabled } } }).ok).toBe(true); + }); + + it("rejects non-boolean worker session hosting enablement", () => { + const result = validateConfigObject({ nodeHost: { workerRuns: { enabled: "yes" } } }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.issues.some((issue) => issue.path === "nodeHost.workerRuns.enabled")).toBe( + true, + ); + } + }); }); diff --git a/src/config/schema.help.quality.test-fixtures.ts b/src/config/schema.help.quality.test-fixtures.ts index 792f8ff1a8ec..e47df087cd61 100644 --- a/src/config/schema.help.quality.test-fixtures.ts +++ b/src/config/schema.help.quality.test-fixtures.ts @@ -130,6 +130,8 @@ export const TARGET_KEYS = [ "nodeHost.agentRuns", "nodeHost.agentRuns.claude", "nodeHost.agentRuns.claude.enabled", + "nodeHost.workerRuns", + "nodeHost.workerRuns.enabled", "nodeHost.browserProxy", "nodeHost.browserProxy.enabled", "nodeHost.browserProxy.allowProfiles", diff --git a/src/config/schema.help.runtime.ts b/src/config/schema.help.runtime.ts index 57e794f453ee..e38c040df638 100644 --- a/src/config/schema.help.runtime.ts +++ b/src/config/schema.help.runtime.ts @@ -260,6 +260,10 @@ export const RUNTIME_FIELD_HELP: Record = { "Controls whether this headless node host may advertise Claude CLI agent turns to the gateway.", "nodeHost.agentRuns.claude.enabled": "Advertise paired-node Claude session continuation when the local claude binary is available (default: false). Runs still require node exec approval.", + "nodeHost.workerRuns": + "Opt in to full OpenClaw worker session hosting from this headless node's local installation. Disabled by default.", + "nodeHost.workerRuns.enabled": + "Advertise this paired node as a session host and pin its local OpenClaw build identity (default: false). The node version must exactly match the gateway.", "nodeHost.browserProxy": "Groups browser-proxy settings for exposing local browser control through node routing. Enable only when remote node workflows need your local browser profiles.", "nodeHost.browserProxy.enabled": diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts index da69a4603963..45eca7fbab3b 100644 --- a/src/config/schema.labels.ts +++ b/src/config/schema.labels.ts @@ -389,6 +389,8 @@ export const FIELD_LABELS: Record = { "nodeHost.agentRuns": "Node Agent Runs", "nodeHost.agentRuns.claude": "Node Claude Agent Runs", "nodeHost.agentRuns.claude.enabled": "Node Claude Agent Runs Enabled", + "nodeHost.workerRuns": "Node Worker Runs", + "nodeHost.workerRuns.enabled": "Node Worker Runs Enabled", "nodeHost.browserProxy": "Node Browser Proxy", "nodeHost.browserProxy.enabled": "Node Browser Proxy Enabled", "nodeHost.browserProxy.allowProfiles": "Node Browser Proxy Allowed Profiles", diff --git a/src/config/schema.tags.ts b/src/config/schema.tags.ts index b2703f8fafc9..c3f6900350ee 100644 --- a/src/config/schema.tags.ts +++ b/src/config/schema.tags.ts @@ -65,6 +65,7 @@ const TAG_OVERRIDES: Record = { "gateway.nodes.pluginTools.enabled": ["tools", "security", "access", "network", "advanced"], "gateway.nodes.allowSkills": ["tools", "security", "access", "network", "advanced"], "nodeHost.agentRuns.claude.enabled": ["tools", "security", "access", "network", "advanced"], + "nodeHost.workerRuns.enabled": ["tools", "security", "access", "network", "advanced"], "nodeHost.mcp.servers": ["tools", "network", "advanced"], "nodeHost.skills.enabled": ["tools", "network", "advanced"], "proxy.tls.caFile": ["security", "network", "storage", "advanced"], diff --git a/src/config/types.node-host.ts b/src/config/types.node-host.ts index 0e3482c71ba1..6f5077556ea7 100644 --- a/src/config/types.node-host.ts +++ b/src/config/types.node-host.ts @@ -15,6 +15,11 @@ export type NodeHostConfig = { enabled?: boolean; }; }; + /** Full OpenClaw session hosting from this node's local installation. */ + workerRuns?: { + /** Advertise this paired node as a worker session host (default: false). */ + enabled?: boolean; + }; /** Browser proxy settings for node hosts. */ browserProxy?: NodeHostBrowserProxyConfig; /** MCP servers started and exposed by the headless node host. */ diff --git a/src/config/zod-schema.node-host.ts b/src/config/zod-schema.node-host.ts index a0564511c9e4..3f13aa1aa2e7 100644 --- a/src/config/zod-schema.node-host.ts +++ b/src/config/zod-schema.node-host.ts @@ -18,3 +18,10 @@ export const NodeHostAgentRunsSchema = z }) .strict() .optional(); + +export const NodeHostWorkerRunsSchema = z + .object({ + enabled: z.boolean().optional(), + }) + .strict() + .optional(); diff --git a/src/config/zod-schema.root-support.ts b/src/config/zod-schema.root-support.ts index 0c30171664f1..74c128d5ad9b 100644 --- a/src/config/zod-schema.root-support.ts +++ b/src/config/zod-schema.root-support.ts @@ -4,7 +4,7 @@ import { z } from "zod"; import type { GatewayRemoteConfig } from "./types.gateway.js"; import { MemorySearchSchema } from "./zod-schema.agent-runtime.js"; import { SecretInputSchema } from "./zod-schema.core.js"; -import { NodeHostAgentRunsSchema } from "./zod-schema.node-host.js"; +import { NodeHostAgentRunsSchema, NodeHostWorkerRunsSchema } from "./zod-schema.node-host.js"; import { sensitive } from "./zod-schema.sensitive.js"; type ConfigSchemaShape = { @@ -467,6 +467,7 @@ export const McpConfigSchema = z export const NodeHostSchema = z .strictObject({ agentRuns: NodeHostAgentRunsSchema, + workerRuns: NodeHostWorkerRunsSchema, browserProxy: z .strictObject({ enabled: z.boolean().optional(), diff --git a/src/gateway/node-catalog.ts b/src/gateway/node-catalog.ts index 06530e189f11..b54f2047e555 100644 --- a/src/gateway/node-catalog.ts +++ b/src/gateway/node-catalog.ts @@ -281,6 +281,7 @@ function buildEffectiveKnownNode(entry: { commands: live ? uniqueSortedStrings(live.commands) : uniqueSortedStrings(nodePairing?.commands), + sessionHost: live?.workerRuns !== undefined, nodePluginTools: live?.nodePluginTools, pathEnv: live?.pathEnv, permissions: live?.permissions ?? nodePairing?.permissions, diff --git a/src/gateway/node-registry.ts b/src/gateway/node-registry.ts index 515e0a2ca9fb..94fddd41f510 100644 --- a/src/gateway/node-registry.ts +++ b/src/gateway/node-registry.ts @@ -16,6 +16,7 @@ import type { NodePluginToolDescriptor, NodeSkillDescriptor, } from "../../packages/gateway-protocol/src/schema/nodes.js"; +import type { WorkerAdmissionHandshake } from "../../packages/gateway-protocol/src/schema/worker-admission.js"; import { setActiveNodeContext } from "../infra/active-node-context.js"; import type { PairedDeviceNodeBinding } from "../infra/device-pairing-node-state.js"; import { NODE_MCP_TOOLS_CALL_COMMAND } from "../infra/node-commands.js"; @@ -65,6 +66,8 @@ export type NodeSession = { declaredCommands: string[]; sessionCommandsCeiling?: string[]; commands: string[]; + /** Exact node-local build admitted for worker session hosting. */ + workerRuns?: WorkerAdmissionHandshake; declaredNodePluginTools: NodePluginToolDescriptor[]; nodePluginTools: NodePluginToolDescriptor[]; nodeSkills: NodeSkillDescriptor[]; @@ -487,6 +490,7 @@ export class NodeRegistry { typeof (connect as { pathEnv?: string }).pathEnv === "string" ? (connect as { pathEnv?: string }).pathEnv : undefined; + const workerRuns = connect.workerRuns ? structuredClone(connect.workerRuns) : undefined; const declaredNodePluginTools: NodePluginToolDescriptor[] = []; const nodePluginTools: NodePluginToolDescriptor[] = []; const nodeSkills: NodeSkillDescriptor[] = []; @@ -512,6 +516,7 @@ export class NodeRegistry { declaredCommands, sessionCommandsCeiling, commands, + ...(workerRuns ? { workerRuns } : {}), declaredNodePluginTools, nodePluginTools, nodeSkills, diff --git a/src/gateway/server-methods/environments.test.ts b/src/gateway/server-methods/environments.test.ts index c3c2ec949339..ad9faea2ab6e 100644 --- a/src/gateway/server-methods/environments.test.ts +++ b/src/gateway/server-methods/environments.test.ts @@ -67,6 +67,11 @@ function mockContext( platform: "ios", caps: ["camera"], commands: ["system.run"], + workerRuns: { + bundleHash: "a".repeat(64), + openclawVersion: "2026.8.12", + protocolFeatures: ["worker-heartbeat-v1"], + }, connectedAtMs: 123, }, ], @@ -199,7 +204,7 @@ beforeEach(() => { afterEach(() => vi.restoreAllMocks()); describe("environment gateway methods", () => { - it("keeps the existing gateway and node projection unchanged without a worker service", async () => { + it("projects live node session-host capability without a worker service", async () => { const [ok, payload] = await callEnvironmentMethod("environments.list", {}); expect(ok).toBe(true); @@ -221,7 +226,7 @@ describe("environment gateway methods", () => { label: "Live Node", status: "available", platform: "ios", - sessionHost: false, + sessionHost: true, trust: "persistent", capabilities: ["camera", "system.run"], }, @@ -394,7 +399,7 @@ describe("environment gateway methods", () => { label: "Live Node", status: "available", platform: "ios", - sessionHost: false, + sessionHost: true, trust: "persistent", capabilities: ["camera", "system.run"], }); diff --git a/src/gateway/server-methods/environments.ts b/src/gateway/server-methods/environments.ts index 35e5cbd1ca5c..51fd527c366e 100644 --- a/src/gateway/server-methods/environments.ts +++ b/src/gateway/server-methods/environments.ts @@ -86,7 +86,7 @@ function summarizeNodeEnvironment( label: node.displayName ?? node.nodeId, status: node.connected ? "available" : "unavailable", ...(platform ? { platform } : {}), - sessionHost: false, + sessionHost: node.connected === true && node.sessionHost === true, trust: "persistent", ...(desktop ? { desktop: true } : {}), ...(capabilities.length > 0 ? { capabilities } : {}), diff --git a/src/gateway/server-worker-environment-startup.ts b/src/gateway/server-worker-environment-startup.ts index 98481aba3de0..10ae989733e4 100644 --- a/src/gateway/server-worker-environment-startup.ts +++ b/src/gateway/server-worker-environment-startup.ts @@ -183,6 +183,11 @@ export async function createGatewayWorkerEnvironmentRuntime(params: { ? deviceProvider : resolveWorkerProvider(params.getPluginRegistry(), providerId), prepareInstallation, + resolveNodeWorkerBuild: async (deviceId) => { + const connected = await deviceNodeRegistry?.listCurrentConnected(); + const build = connected?.find((node) => node.nodeId === deviceId)?.workerRuns; + return build ? structuredClone(build) : undefined; + }, tunnelManager: workerTunnelManager, resolveWorkerGateway: params.resolveWorkerGateway, applyTranscriptCommit: createWorkerTranscriptCommitter({ diff --git a/src/gateway/worker-environments/admission.ts b/src/gateway/worker-environments/admission.ts index 0aaa46840a8f..b9d13a09cd9d 100644 --- a/src/gateway/worker-environments/admission.ts +++ b/src/gateway/worker-environments/admission.ts @@ -19,6 +19,13 @@ export type ExpectedWorkerBuild = { protocolFeatures: readonly string[]; }; +/** Local-install receipts pin the node's paired-machine claim instead of Gateway bundle bytes. */ +export function resolveLocalWorkerBuild( + receipt: (WorkerAdmissionHandshake & { installKind?: "bundle" | "local" }) | null | undefined, +): ExpectedWorkerBuild | undefined { + return receipt?.installKind === "local" ? receipt : undefined; +} + /** True only for bundles that accept the exact admitted execution carrier. */ export function supportsWorkerExecutionContextLaunch( handshake: Pick | null | undefined, diff --git a/src/gateway/worker-environments/bundle.test.ts b/src/gateway/worker-environments/bundle.test.ts index edfad23d64b2..7d76ff4cf551 100644 --- a/src/gateway/worker-environments/bundle.test.ts +++ b/src/gateway/worker-environments/bundle.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import * as tar from "tar"; import { describe, expect, it, vi } from "vitest"; +import { resolveNodeWorkerBuild } from "../../node-host/node-worker-build.js"; import { runCommandWithTimeout } from "../../process/exec.js"; import { withTestDir } from "../../test-helpers/temp-dir.js"; import { @@ -82,9 +83,19 @@ describe("worker bundle producer", () => { cacheDir: path.join(root, "cache-b"), openclawVersion: "1.2.3", }).prepare(); + const nodeBuild = await resolveNodeWorkerBuild({ + packageRoot: packageA, + openclawVersion: "1.2.3", + protocolFeatures: [], + }); expect(first.bundleHash).toMatch(/^[a-f0-9]{64}$/u); expect(second.bundleHash).toBe(first.bundleHash); + expect(nodeBuild).toEqual({ + bundleHash: first.bundleHash, + openclawVersion: first.openclawVersion, + protocolFeatures: first.protocolFeatures, + }); await expect(listTarball(first.tarballPath)).resolves.toEqual([ "dist/entry.js", "dist/nested/worker.js", diff --git a/src/gateway/worker-environments/bundle.ts b/src/gateway/worker-environments/bundle.ts index 063ddab8fb1c..6af2acc394b0 100644 --- a/src/gateway/worker-environments/bundle.ts +++ b/src/gateway/worker-environments/bundle.ts @@ -8,6 +8,10 @@ import { resolveStateDir } from "../../config/paths.js"; import { isExactSemverVersion, resolveNpmJsonEntries } from "../../infra/npm-registry-spec.js"; import { resolveOpenClawPackageRootSync } from "../../infra/openclaw-root.js"; import { runCommandWithTimeout } from "../../process/exec.js"; +import { + hashWorkerBundleManifest, + WORKER_BUNDLE_MANIFEST_VERSION, +} from "../../shared/worker-bundle-hash.js"; import { VERSION } from "../../version.js"; import { collectWorkerBundleManifest, @@ -15,7 +19,7 @@ import { type WorkerBundleManifestEntry, } from "./bundle-staging.js"; -export const WORKER_BUNDLE_MANIFEST_VERSION = "openclaw-worker-bundle-v1"; +export { WORKER_BUNDLE_MANIFEST_VERSION }; const OPENCLAW_NPM_REGISTRY = "https://registry.npmjs.org/"; const NPM_RELEASE_PROOF_TIMEOUT_MS = 60_000; const NPM_SHA512_INTEGRITY_PATTERN = /^sha512-[A-Za-z0-9+/]{86}==$/u; @@ -271,15 +275,6 @@ async function verifyPublishedNpmRelease(params: { } } -function hashWorkerBundleManifest(entries: readonly WorkerBundleManifestEntry[]): string { - const hash = createHash("sha256"); - hash.update(`${WORKER_BUNDLE_MANIFEST_VERSION}\0`); - for (const entry of entries) { - hash.update(`${entry.path}\0${entry.mode.toString(8)}\0${entry.size}\0${entry.sha256}\0`); - } - return hash.digest("hex"); -} - function manifestsMatch( left: readonly WorkerBundleManifestEntry[], right: readonly WorkerBundleManifestEntry[], diff --git a/src/gateway/worker-environments/credential-broker.ts b/src/gateway/worker-environments/credential-broker.ts index 1270ca142f4b..9b331bf4c639 100644 --- a/src/gateway/worker-environments/credential-broker.ts +++ b/src/gateway/worker-environments/credential-broker.ts @@ -1,5 +1,12 @@ -import { WORKER_RPC_SET_VERSION } from "../../../packages/gateway-protocol/src/schema/worker-admission.js"; -import { verifyWorkerAdmissionHandshake } from "./admission.js"; +import { + type WorkerAdmissionHandshake, + WORKER_RPC_SET_VERSION, +} from "../../../packages/gateway-protocol/src/schema/worker-admission.js"; +import { + resolveLocalWorkerBuild, + verifyWorkerAdmissionHandshake, + type ExpectedWorkerBuild, +} from "./admission.js"; import type { WorkerInstallationArtifact } from "./bundle.js"; import { createWorkerCredentialMaterial, @@ -14,6 +21,7 @@ import type { WorkerEnvironmentState } from "./state.js"; import { type WorkerEnvironmentRecord, type WorkerEnvironmentStore, + type WorkerEnvironmentTransitionPatch, WorkerSessionAlreadyAttachedError, } from "./store.js"; import type { WorkerTunnelManager } from "./tunnel.js"; @@ -114,6 +122,33 @@ export function createWorkerCredentialBroker(options: WorkerCredentialBrokerOpti return grant; }; + const commitReady = ( + record: WorkerEnvironmentRecord, + receipt: WorkerAdmissionHandshake & { installKind: "bundle" | "local" }, + patch: WorkerEnvironmentTransitionPatch = {}, + ) => { + const material = credentialMaterial(); + // Receipt, owner epoch, and credential hash commit together. A failed write leaves the + // durable lease retryable without ever admitting a partial identity. + const ready = move(record, "ready", { + ...patch, + bootstrapReceipt: receipt, + credential: { + credentialHash: material.credentialHash, + sessionId: null, + rpcSetVersion: WORKER_RPC_SET_VERSION, + expiresAtMs: credentialExpiry(), + }, + }); + stageCredential( + grantFrom({ + credential: material.credential, + record: store.getCredential(record.environmentId), + }), + ); + return ready; + }; + const ensurePendingCredential = (record: WorkerEnvironmentRecord, sessionId: string | null) => { const credential = store.getCredential(record.environmentId); const pending = pendingCredentials.get(record.environmentId); @@ -183,9 +218,11 @@ export function createWorkerCredentialBroker(options: WorkerCredentialBrokerOpti if (current.state !== "ready" && current.state !== "idle") { throw serviceError("invalid_state", `Cannot attach worker in state: ${current.state}`); } - let currentBuild: WorkerInstallationArtifact; + let currentBuild: ExpectedWorkerBuild; try { - currentBuild = await options.prepareInstallation("bundle"); + currentBuild = + resolveLocalWorkerBuild(current.bootstrapReceipt) ?? + (await options.prepareInstallation("bundle")); } catch { throw serviceError("invalid_state", "Current worker build identity is unavailable"); } @@ -337,6 +374,7 @@ export function createWorkerCredentialBroker(options: WorkerCredentialBrokerOpti attachSession, clear: () => pendingCredentials.clear(), clearEnvironment: (environmentId: string) => pendingCredentials.delete(environmentId), + commitReady, credentialExpiry, credentialMaterial, ensurePendingCredential, diff --git a/src/gateway/worker-environments/device-provider.test.ts b/src/gateway/worker-environments/device-provider.test.ts index bb628f05a58d..6cb7d67c7ad3 100644 --- a/src/gateway/worker-environments/device-provider.test.ts +++ b/src/gateway/worker-environments/device-provider.test.ts @@ -4,6 +4,11 @@ import { WorkerProviderError } from "../../plugins/types.js"; import { createDeviceWorkerProvider } from "./device-provider.js"; const DEVICE_ID = "device-session-host"; +const WORKER_BUILD = { + bundleHash: "a".repeat(64), + openclawVersion: "2026.8.12", + protocolFeatures: ["worker-heartbeat-v1"], +}; function pairedDevice(deviceId = DEVICE_ID): PairedDevice { return { @@ -28,7 +33,7 @@ describe("device worker provider", () => { it("provisions deterministic node leases only for connected paired session hosts", async () => { const provider = createDeviceWorkerProvider({ getPairedDevice: async (deviceId) => pairedDevice(deviceId), - listConnectedNodes: async () => [{ nodeId: DEVICE_ID, commands: ["system.run"] }], + listConnectedNodes: async () => [{ nodeId: DEVICE_ID, workerRuns: WORKER_BUILD }], }); const first = await provider.provision({ device: DEVICE_ID }, "operation-1"); @@ -48,7 +53,7 @@ describe("device worker provider", () => { { name: "missing pairing", getPairedDevice: async () => null, - listConnectedNodes: async () => [{ nodeId: DEVICE_ID, commands: ["system.run"] }], + listConnectedNodes: async () => [{ nodeId: DEVICE_ID, workerRuns: WORKER_BUILD }], }, { name: "offline device", @@ -56,9 +61,9 @@ describe("device worker provider", () => { listConnectedNodes: async () => [], }, { - name: "connected node without session execution", + name: "connected node without worker session hosting", getPairedDevice: async () => pairedDevice(), - listConnectedNodes: async () => [{ nodeId: DEVICE_ID, commands: [] }], + listConnectedNodes: async () => [{ nodeId: DEVICE_ID }], }, ])("rejects $name during provision", async ({ getPairedDevice, listConnectedNodes }) => { const provider = createDeviceWorkerProvider({ getPairedDevice, listConnectedNodes }); @@ -74,7 +79,7 @@ describe("device worker provider", () => { const provider = createDeviceWorkerProvider({ getPairedDevice: async () => paired, listConnectedNodes: async () => - connected ? [{ nodeId: DEVICE_ID, commands: ["system.run"] }] : [], + connected ? [{ nodeId: DEVICE_ID, workerRuns: WORKER_BUILD }] : [], }); const lease = { leaseId: "device-lease", profile: { device: DEVICE_ID } }; diff --git a/src/gateway/worker-environments/device-provider.ts b/src/gateway/worker-environments/device-provider.ts index 25a7de6db88b..99a54a3460a9 100644 --- a/src/gateway/worker-environments/device-provider.ts +++ b/src/gateway/worker-environments/device-provider.ts @@ -11,7 +11,11 @@ export const DEVICE_WORKER_PROVIDER_ID = "device"; type DeviceWorkerNode = { nodeId: string; - commands: readonly string[]; + workerRuns?: { + bundleHash: string; + openclawVersion: string; + protocolFeatures: readonly string[]; + }; }; type DeviceWorkerProviderOptions = { @@ -28,7 +32,7 @@ function requireDeviceId(profile: WorkerProfile): string { } function isSessionCapableNode(node: DeviceWorkerNode): boolean { - return node.commands.includes("system.run"); + return node.workerRuns !== undefined; } function hasPairedNodeRole(device: PairedDevice | null): device is PairedDevice { diff --git a/src/gateway/worker-environments/environment-access.test.ts b/src/gateway/worker-environments/environment-access.test.ts index c19715f67bf3..55ee74348bc0 100644 --- a/src/gateway/worker-environments/environment-access.test.ts +++ b/src/gateway/worker-environments/environment-access.test.ts @@ -3,6 +3,7 @@ import { closeOpenClawStateDatabaseForTest, openOpenClawStateDatabase, } from "../../state/openclaw-state-db.js"; +import { VERSION } from "../../version.js"; import * as support from "./service.test-support.js"; import { createWorkerEnvironmentStore } from "./store.js"; import type { WorkerTunnelManager } from "./tunnel.js"; @@ -78,9 +79,19 @@ describe("worker environment service", () => { } as unknown as WorkerTunnelManager; const workerService = support.createService( support.createProvider({ - provision: async () => ({ leaseId: "device-lease", node: { deviceId: "device-1" } }), + provision: async () => ({ + leaseId: "device-lease", + node: { deviceId: "device-1" }, + }), }), - { tunnelManager }, + { + tunnelManager, + resolveNodeWorkerBuild: async () => ({ + bundleHash: "c".repeat(64), + openclawVersion: VERSION, + protocolFeatures: ["worker-heartbeat-v1"], + }), + }, ); const environment = await workerService.create("development", "device-tunnel-gate"); diff --git a/src/gateway/worker-environments/placement-dispatch-device.test.ts b/src/gateway/worker-environments/placement-dispatch-device.test.ts index 6fedee75a67e..aef9efd88ba8 100644 --- a/src/gateway/worker-environments/placement-dispatch-device.test.ts +++ b/src/gateway/worker-environments/placement-dispatch-device.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { WORKER_EXECUTION_CONTEXT_PROTOCOL_FEATURE } from "../../../packages/gateway-protocol/src/schema/worker-admission.js"; import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js"; import { closeOpenClawStateDatabaseForTest, @@ -39,7 +40,12 @@ describe("device worker placement dispatch", () => { profileSnapshot: { install: "bundle", settings: { device: "device-1" } }, leaseId: "device-lease-1", sshEndpoint: null, - bootstrapReceipt: null, + bootstrapReceipt: { + bundleHash: "a".repeat(64), + openclawVersion: "2026.8.12", + protocolFeatures: [WORKER_EXECUTION_CONTEXT_PROTOCOL_FEATURE], + installKind: "local", + }, sharedHost: true, tunnelStatus: "stopped", }); @@ -64,9 +70,13 @@ describe("device worker placement dispatch", () => { ); expect(harness.environments.startTunnel).toHaveBeenCalledWith({ environmentId: harness.ready.environmentId, - ownerEpoch: harness.ready.ownerEpoch, + ownerEpoch: expect.any(Number), + }); + expect(harness.environments.attachSession).toHaveBeenCalledWith({ + environmentId: harness.ready.environmentId, + ownerEpoch: harness.ready.ownerEpoch, + sessionId: REQUEST.sessionId, }); - expect(harness.environments.attachSession).not.toHaveBeenCalled(); expect(harness.environments.destroy).toHaveBeenCalledWith(harness.ready.environmentId); expect(harness.placements.current()).toMatchObject({ state: "failed", diff --git a/src/gateway/worker-environments/provider-lifecycle.ts b/src/gateway/worker-environments/provider-lifecycle.ts index 5367e7bbf1fa..4827aef2bb97 100644 --- a/src/gateway/worker-environments/provider-lifecycle.ts +++ b/src/gateway/worker-environments/provider-lifecycle.ts @@ -1,10 +1,7 @@ import { isDeepStrictEqual } from "node:util"; import { expectDefined } from "@openclaw/normalization-core"; import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion"; -import { - type WorkerAdmissionHandshake, - WORKER_RPC_SET_VERSION, -} from "../../../packages/gateway-protocol/src/schema/worker-admission.js"; +import type { WorkerAdmissionHandshake } from "../../../packages/gateway-protocol/src/schema/worker-admission.js"; import type { OpenClawConfig } from "../../config/types.js"; import type { SecretRef } from "../../config/types.secrets.js"; import { validateCloudWorkerProfileSettings } from "../../config/zod-schema.cloud-workers.js"; @@ -17,7 +14,8 @@ import { type WorkerSshEndpoint, type WorkerSshIdentity, } from "../../plugins/types.js"; -import { verifyWorkerAdmissionHandshake } from "./admission.js"; +import { VERSION } from "../../version.js"; +import { resolveLocalWorkerBuild, verifyWorkerAdmissionHandshake } from "./admission.js"; import type { WorkerInstallationArtifact } from "./bundle.js"; import type { WorkerCredentialBroker } from "./credential-broker.js"; import { deriveEnvironmentIntent } from "./service-contract.js"; @@ -53,6 +51,7 @@ type WorkerProviderLifecycleOptions = { profile: WorkerProfile; keyRef: SecretRef; }) => Promise; + resolveNodeWorkerBuild?: (deviceId: string) => Promise; providerCallTimeoutMs?: number; tunnelManager?: WorkerTunnelManager; credentialBroker: WorkerCredentialBroker; @@ -103,13 +102,7 @@ export function createWorkerProviderLifecycle(options: WorkerProviderLifecycleOp const saveError = options.saveError; const serviceError = options.serviceError; const withLock = options.withLock; - const { - credentialExpiry, - credentialMaterial, - ensurePendingCredential, - grantFrom, - stageCredential, - } = options.credentialBroker; + const { commitReady, ensurePendingCredential } = options.credentialBroker; function requireWorkerProfile(value: unknown): WorkerProfile { const error = validateCloudWorkerProfileSettings(value); @@ -239,24 +232,7 @@ export function createWorkerProviderLifecycle(options: WorkerProviderLifecycleOp } catch (error) { return await failBootstrap(record, leaseId, provider, error); } - const material = credentialMaterial(); - // Receipt, owner epoch, and credential hash commit together. A failed write leaves the - // durable lease bootstrapping so reconcile can retry without admitting a partial identity. - const ready = move(record, "ready", { - bootstrapReceipt: receipt, - credential: { - credentialHash: material.credentialHash, - sessionId: null, - rpcSetVersion: WORKER_RPC_SET_VERSION, - expiresAtMs: credentialExpiry(), - }, - }); - const grant = grantFrom({ - credential: material.credential, - record: store.getCredential(record.environmentId), - }); - stageCredential(grant); - return ready; + return commitReady(record, { ...receipt, installKind: "bundle" }); }; const finishProvision = async ( @@ -297,7 +273,24 @@ export function createWorkerProviderLifecycle(options: WorkerProviderLifecycleOp desktop: lease.desktop ?? null, }; if (lease.node) { - return move(record, "ready", { ...patch, sshEndpoint: null }); + const nodeBuild = await options.resolveNodeWorkerBuild?.(lease.node.deviceId); + if (!nodeBuild) { + const detail = `Device worker no longer advertises session hosting: ${lease.node.deviceId}`; + move(record, "failed", { lastError: detail }); + throw serviceError("bootstrap_failure", detail); + } + if (nodeBuild.openclawVersion !== VERSION) { + const detail = `Device worker runs OpenClaw ${nodeBuild.openclawVersion}, but this gateway runs ${VERSION}; update the node to match the gateway, then retry`; + move(record, "failed", { lastError: detail }); + throw serviceError("bootstrap_failure", detail); + } + // Admin pairing already trusts this machine. Pinning its exact claimed hash plus an exact + // version match prevents skew; milestone 7 replaces the claim with Gateway-pushed bytes. + return commitReady( + record, + { ...nodeBuild, installKind: "local" }, + { ...patch, sshEndpoint: null }, + ); } const bootstrapping = move(record, "bootstrapping", { ...patch, @@ -395,16 +388,17 @@ export function createWorkerProviderLifecycle(options: WorkerProviderLifecycleOp } let currentBundle: WorkerInstallationArtifact | undefined; if (record.destroyRequestedAtMs === null && inState(record, "ready", "idle", "attached")) { + const localBuild = resolveLocalWorkerBuild(record.bootstrapReceipt); try { - currentBundle = await options.prepareInstallation("bundle"); - if ( - record.bootstrapReceipt && - verifyWorkerAdmissionHandshake(record.bootstrapReceipt, currentBundle) - ) { - const sessionId = record.state === "attached" ? record.attachedSessionIds[0] : null; - if (record.state !== "attached" || sessionId) { - ensurePendingCredential(record, sessionId ?? null); - record = store.get(record.environmentId) ?? record; + currentBundle = localBuild ? undefined : await options.prepareInstallation("bundle"); + const expectedBuild = localBuild ?? currentBundle; + if (record.bootstrapReceipt && expectedBuild) { + if (verifyWorkerAdmissionHandshake(record.bootstrapReceipt, expectedBuild)) { + const sessionId = record.state === "attached" ? record.attachedSessionIds[0] : null; + if (record.state !== "attached" || sessionId) { + ensurePendingCredential(record, sessionId ?? null); + record = store.get(record.environmentId) ?? record; + } } } } catch { diff --git a/src/gateway/worker-environments/provider-provisioning.test.ts b/src/gateway/worker-environments/provider-provisioning.test.ts index 02464d3e1c31..56552723652c 100644 --- a/src/gateway/worker-environments/provider-provisioning.test.ts +++ b/src/gateway/worker-environments/provider-provisioning.test.ts @@ -7,9 +7,11 @@ import { closeOpenClawStateDatabaseForTest, openOpenClawStateDatabase, } from "../../state/openclaw-state-db.js"; +import { VERSION } from "../../version.js"; import type { GatewaySessionRow } from "../session-utils.types.js"; import { writeSessionStore } from "../test-helpers.js"; import { directSessionReq } from "../test/server-sessions.test-helpers.js"; +import { admitWorkerConnection } from "./admission.js"; import { hashWorkerCredential } from "./credential.js"; import { createWorkerPlacementDispatchService } from "./placement-dispatch.js"; import { createWorkerSessionPlacementStore } from "./placement-store.js"; @@ -76,7 +78,12 @@ describe("worker environment service", () => { expect(workerService.takeMintedCredential(binding)).toBeUndefined(); }); - it("holds a node lease ready without entering SSH bootstrap", async () => { + it("commits a local-install receipt and credential for a node lease", async () => { + const workerBuild = { + bundleHash: "c".repeat(64), + openclawVersion: VERSION, + protocolFeatures: ["worker-heartbeat-v1"], + }; support.testState.prepareInstallation = vi.fn(async () => { throw new Error("node leases must not prepare an SSH installation"); }); @@ -89,6 +96,7 @@ describe("worker environment service", () => { sharedHost: true, }), }), + { resolveNodeWorkerBuild: async () => workerBuild }, ); const result = await workerService.create("development", "request-device"); @@ -97,13 +105,86 @@ describe("worker environment service", () => { state: "ready", leaseId: "device-lease-1", sshEndpoint: null, - bootstrapReceipt: null, + bootstrapReceipt: { ...workerBuild, installKind: "local" }, sharedHost: true, ownerEpoch: 1, }); expect(support.testState.prepareInstallation).not.toHaveBeenCalled(); expect(support.testState.bootstrapWorker).not.toHaveBeenCalled(); - expect(support.testState.store.getCredential(result.environmentId)).toBeUndefined(); + const credential = workerService.takeMintedCredential({ + environmentId: result.environmentId, + ownerEpoch: result.ownerEpoch, + sessionId: null, + }); + expect(credential).toMatchObject({ + credential: support.CREDENTIAL, + bundleHash: "c".repeat(64), + }); + const attachedCredential = await workerService.attachSession({ + environmentId: result.environmentId, + ownerEpoch: result.ownerEpoch, + sessionId: "session-device", + }); + const attached = support.testState.store.get(result.environmentId)!; + const admission = { + environmentId: result.environmentId, + credential: attachedCredential.credential, + ownerEpoch: attached.ownerEpoch, + rpcSetVersion: 1, + sessionId: "session-device", + runId: "run-device", + handshake: workerBuild, + } as const; + expect( + admitWorkerConnection({ + store: support.testState.store, + admission, + expectedBuild: workerBuild, + nowMs: support.testState.nowMs, + }), + ).toMatchObject({ ok: true }); + expect( + admitWorkerConnection({ + store: support.testState.store, + admission: { + ...admission, + handshake: { ...workerBuild, bundleHash: "d".repeat(64) }, + }, + expectedBuild: workerBuild, + nowMs: support.testState.nowMs, + }), + ).toEqual({ ok: false, reason: "bundle-mismatch" }); + }); + + it("fails node provisioning visibly when the node version differs", async () => { + const nodeVersion = "0.0.0-node"; + const workerService = support.createService( + support.createProvider({ + provisionBeforeInstallation: true, + provision: async () => ({ + leaseId: "device-lease-version-mismatch", + node: { deviceId: "device-1" }, + }), + }), + { + resolveNodeWorkerBuild: async () => ({ + bundleHash: "c".repeat(64), + openclawVersion: nodeVersion, + protocolFeatures: ["worker-heartbeat-v1"], + }), + }, + ); + + await expect( + workerService.create("development", "request-device-mismatch"), + ).rejects.toMatchObject({ + code: "bootstrap_failure", + message: expect.stringContaining(`OpenClaw ${nodeVersion}`), + } satisfies Partial); + expect(support.testState.store.list()[0]).toMatchObject({ + state: "failed", + lastError: expect.stringContaining(`gateway runs ${VERSION}`), + }); }); it("creates a nested environment from its parent's snapshot after config drift", async () => { diff --git a/src/gateway/worker-environments/service.test-support.ts b/src/gateway/worker-environments/service.test-support.ts index 91689ec630ef..0121e9d027ec 100644 --- a/src/gateway/worker-environments/service.test-support.ts +++ b/src/gateway/worker-environments/service.test-support.ts @@ -161,6 +161,7 @@ export function createService( | "executeInference" | "providerCallTimeoutMs" | "resolveSshIdentity" + | "resolveNodeWorkerBuild" | "resolveWorkerGateway" | "tunnelManager" | "generateWorkerCredential" diff --git a/src/gateway/worker-environments/service.ts b/src/gateway/worker-environments/service.ts index 15e0dbfc3e3f..0d8d30aac67e 100644 --- a/src/gateway/worker-environments/service.ts +++ b/src/gateway/worker-environments/service.ts @@ -87,6 +87,7 @@ type WorkerEnvironmentServiceOptions = { profile: WorkerProfile; keyRef: SecretRef; }) => Promise; + resolveNodeWorkerBuild?: (deviceId: string) => Promise; tunnelManager?: WorkerTunnelManager; reconcileIntervalMs?: number; providerCallTimeoutMs?: number; @@ -261,6 +262,7 @@ export function createWorkerEnvironmentService(options: WorkerEnvironmentService prepareInstallation: options.prepareInstallation, bootstrapWorker: options.bootstrapWorker, resolveSshIdentity: options.resolveSshIdentity, + resolveNodeWorkerBuild: options.resolveNodeWorkerBuild, providerCallTimeoutMs: options.providerCallTimeoutMs, tunnelManager: options.tunnelManager, credentialBroker, diff --git a/src/gateway/worker-environments/store.test.ts b/src/gateway/worker-environments/store.test.ts index 05d5998fdb51..240f2a1e83c6 100644 --- a/src/gateway/worker-environments/store.test.ts +++ b/src/gateway/worker-environments/store.test.ts @@ -25,7 +25,9 @@ import { type WorkerEnvironmentStore, } from "./store.js"; -type WorkerEnvironmentBootstrapReceipt = WorkerAdmissionHandshake; +type WorkerEnvironmentBootstrapReceipt = WorkerAdmissionHandshake & { + installKind?: "bundle" | "local"; +}; type WorkerEnvironmentProfileSnapshot = WorkerProfile; type WorkerEnvironmentSshEndpoint = WorkerSshEndpoint; @@ -670,7 +672,7 @@ describe("worker environment store", () => { ).toThrow("lease id is immutable"); }); - it("persists a ready node lease without validating SSH metadata", () => { + it("persists a credential-bound local receipt without SSH metadata", () => { createIntent("worker-node", { settings: { device: "device-1" } }); store.transition({ environmentId: "worker-node", from: "requested", to: "provisioning" }); @@ -678,23 +680,38 @@ describe("worker environment store", () => { environmentId: "worker-node", from: "provisioning", to: "ready", - patch: { leaseId: "device-lease-1", sshEndpoint: null, sharedHost: true }, + patch: { + leaseId: "device-lease-1", + sshEndpoint: null, + sharedHost: true, + ...readyPatch({ ...BOOTSTRAP_RECEIPT, installKind: "local" }), + }, }); expect(ready).toMatchObject({ state: "ready", leaseId: "device-lease-1", sshEndpoint: null, - bootstrapReceipt: null, + bootstrapReceipt: { + ...BOOTSTRAP_RECEIPT, + protocolFeatures: ["model-proxy-v1", "workspace-sync-v1"], + installKind: "local", + }, sharedHost: true, ownerEpoch: 1, }); expect(store.get("worker-node")).toEqual(ready); expect( database.db - .prepare("SELECT ssh_host, ssh_host_key FROM worker_environments WHERE environment_id = ?") + .prepare( + "SELECT ssh_host, ssh_host_key, bootstrap_install_kind FROM worker_environments WHERE environment_id = ?", + ) .get("worker-node"), - ).toEqual({ ssh_host: null, ssh_host_key: null }); + ).toEqual({ + ssh_host: null, + ssh_host_key: null, + bootstrap_install_kind: "local", + }); }); it("enforces one credential-bound session and teardown fencing", () => { diff --git a/src/gateway/worker-environments/store.ts b/src/gateway/worker-environments/store.ts index 66b97428926b..5c0ee9fee549 100644 --- a/src/gateway/worker-environments/store.ts +++ b/src/gateway/worker-environments/store.ts @@ -44,7 +44,11 @@ import { type WorkerEnvironmentProfileSnapshot = WorkerProfile; type WorkerEnvironmentSshEndpoint = WorkerSshEndpoint; -type WorkerEnvironmentBootstrapReceipt = WorkerAdmissionHandshake; +type WorkerBootstrapInstallKind = "bundle" | "local"; +type WorkerEnvironmentBootstrapReceipt = WorkerAdmissionHandshake & { + /** Provenance only; admission authority remains the exact stored build identity. */ + installKind?: WorkerBootstrapInstallKind; +}; type WorkerEnvironmentTeardownTerminalState = "destroyed" | "failed"; type RecordIdentity = { environmentId: string; providerId: string; profileId: string }; type RecordBase = RecordIdentity & { @@ -178,6 +182,7 @@ function normalizeBootstrapReceipt(value: { bundleHash: unknown; openclawVersion: unknown; protocolFeatures: unknown; + installKind?: unknown; }): WorkerEnvironmentBootstrapReceipt { const bundleHash = required(value.bundleHash, "bootstrap bundle hash"); if (!WORKER_BUNDLE_HASH_PATTERN.test(bundleHash)) { @@ -195,10 +200,18 @@ function normalizeBootstrapReceipt(value: { ) { throw new Error("Worker environment bootstrap protocol features exceed admission limits"); } + if ( + value.installKind !== undefined && + value.installKind !== "bundle" && + value.installKind !== "local" + ) { + throw new Error("Worker environment bootstrap install kind is invalid"); + } return { bundleHash, openclawVersion: required(value.openclawVersion, "bootstrap OpenClaw version"), protocolFeatures: normalizeSortedUniqueTrimmedStringList(value.protocolFeatures), + ...(value.installKind ? { installKind: value.installKind } : {}), }; } function normalizeCredentialHash(value: unknown): string { @@ -385,6 +398,7 @@ function bootstrapReceiptFrom(row: Row): WorkerEnvironmentBootstrapReceipt | nul bootstrap_bundle_hash: bundleHash, bootstrap_openclaw_version: openclawVersion, bootstrap_protocol_features_json: encodedFeatures, + bootstrap_install_kind: installKind, } = row; if (bundleHash === null && openclawVersion === null && encodedFeatures === null) { return null; @@ -396,6 +410,7 @@ function bootstrapReceiptFrom(row: Row): WorkerEnvironmentBootstrapReceipt | nul bundleHash, openclawVersion, protocolFeatures: JSON.parse(encodedFeatures) as unknown, + ...(installKind === null ? {} : { installKind }), }); } function assertShape( @@ -822,6 +837,7 @@ export function createWorkerEnvironmentStore( bootstrap_bundle_hash: null, bootstrap_openclaw_version: null, bootstrap_protocol_features_json: null, + bootstrap_install_kind: null, owner_epoch: 0, teardown_terminal_state: null, state: "requested", @@ -957,10 +973,10 @@ export function createWorkerEnvironmentStore( : patch.desktop === null ? null : normalizeWorkerDesktopEndpoint(patch.desktop); - const acceptsBootstrapReceipt = from === "bootstrapping" && to === "ready"; - const acceptsDeferredNodeReady = - from === "provisioning" && to === "ready" && sshEndpoint === null; - if (to === "ready" && !acceptsBootstrapReceipt && !acceptsDeferredNodeReady) { + const acceptsBootstrapReceipt = + to === "ready" && + (from === "bootstrapping" || (from === "provisioning" && sshEndpoint === null)); + if (to === "ready" && !acceptsBootstrapReceipt) { throw new Error("Ready worker transition requires bootstrap proof or a node lease"); } if (patch.bootstrapReceipt !== undefined && !acceptsBootstrapReceipt) { @@ -1032,12 +1048,11 @@ export function createWorkerEnvironmentStore( to === "destroyed" || to === "failed" || to === "orphaned"); - const ownerEpoch = - acceptsBootstrapReceipt || acceptsDeferredNodeReady - ? Math.max(1, current.ownerEpoch) - : acceptsAttachedCredential || ownerEndingTransition - ? nextGlobalOwnerEpoch(db) - : current.ownerEpoch; + const ownerEpoch = acceptsBootstrapReceipt + ? Math.max(1, current.ownerEpoch) + : acceptsAttachedCredential || ownerEndingTransition + ? nextGlobalOwnerEpoch(db) + : current.ownerEpoch; updateRow(db, environmentId, from, { lease_id: leaseId, shared_host: sharedHost === null ? null : sharedHost ? 1 : 0, @@ -1052,6 +1067,7 @@ export function createWorkerEnvironmentStore( bootstrap_protocol_features_json: bootstrapReceipt ? json(bootstrapReceipt.protocolFeatures) : null, + bootstrap_install_kind: bootstrapReceipt?.installKind ?? null, owner_epoch: ownerEpoch, state: to, attached_session_ids_json: json(attachedSessionIds), diff --git a/src/gateway/worker-environments/worker-turn-rpc.ts b/src/gateway/worker-environments/worker-turn-rpc.ts index 33ce7d69ff15..b8a2f491b8e8 100644 --- a/src/gateway/worker-environments/worker-turn-rpc.ts +++ b/src/gateway/worker-environments/worker-turn-rpc.ts @@ -20,6 +20,7 @@ import { safeEqualSecret } from "../../security/secret-equal.js"; import type { WorkerSessionToolName } from "../../worker/tool-authority.js"; import { admitWorkerConnection, + resolveLocalWorkerBuild, validateWorkerConnectionIdentity, type ExpectedWorkerBuild, type WorkerConnectionIdentity, @@ -522,7 +523,9 @@ export function createWorkerTurnRpc(options: WorkerTurnRpcOptions) { } let expectedBuild: ExpectedWorkerBuild; try { - expectedBuild = await options.prepareInstallation("bundle"); + expectedBuild = + resolveLocalWorkerBuild(store.get(admission.environmentId)?.bootstrapReceipt) ?? + (await options.prepareInstallation("bundle")); } catch { return { ok: false, reason: "environment-unavailable" } as const; } diff --git a/src/node-host/node-worker-build.ts b/src/node-host/node-worker-build.ts new file mode 100644 index 000000000000..90660886521c --- /dev/null +++ b/src/node-host/node-worker-build.ts @@ -0,0 +1,42 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import type { WorkerAdmissionHandshake } from "../../packages/gateway-protocol/src/schema/worker-admission.js"; +import { WORKER_PROTOCOL_FEATURES } from "../../packages/gateway-protocol/src/schema/worker-admission.js"; +import { collectWorkerBundleManifest } from "../gateway/worker-environments/bundle-staging.js"; +import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js"; +import { hashWorkerBundleManifest } from "../shared/worker-bundle-hash.js"; +import { VERSION } from "../version.js"; + +type NodeWorkerBuildOptions = { + packageRoot?: string; + openclawVersion?: string; + protocolFeatures?: readonly string[]; +}; + +/** Computes the build identity of the node host's own worker-capable installation. */ +export async function resolveNodeWorkerBuild( + options: NodeWorkerBuildOptions = {}, +): Promise { + const packageRoot = + options.packageRoot ?? + resolveOpenClawPackageRootSync({ + moduleUrl: import.meta.url, + argv1: process.argv[1], + cwd: process.cwd(), + }); + if (!packageRoot) { + throw new Error("Unable to locate the running OpenClaw package root for node worker hosting"); + } + const stagingRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-node-worker-build-")); + try { + const manifest = await collectWorkerBundleManifest(packageRoot, stagingRoot); + return { + bundleHash: hashWorkerBundleManifest(manifest), + openclawVersion: options.openclawVersion ?? VERSION, + protocolFeatures: [...(options.protocolFeatures ?? WORKER_PROTOCOL_FEATURES)].toSorted(), + }; + } finally { + await fs.rm(stagingRoot, { recursive: true, force: true }); + } +} diff --git a/src/node-host/runner.test.ts b/src/node-host/runner.test.ts index 65ece1715fcf..bea73bd66e0d 100644 --- a/src/node-host/runner.test.ts +++ b/src/node-host/runner.test.ts @@ -31,6 +31,11 @@ const mocks = vi.hoisted(() => ({ availabilityChanged: undefined as (() => void) | undefined, normalizedPath: null as string | null, resolvedExecutables: new Map(), + nodeWorkerBuild: { + bundleHash: "a".repeat(64), + openclawVersion: "2026.8.12", + protocolFeatures: ["worker-heartbeat-v1"], + }, runtimeClient: undefined as | { request: (method: string, params?: unknown) => Promise } | undefined, @@ -156,6 +161,10 @@ vi.mock("./mcp.js", () => ({ })), })); +vi.mock("./node-worker-build.js", () => ({ + resolveNodeWorkerBuild: vi.fn(async () => structuredClone(mocks.nodeWorkerBuild)), +})); + vi.mock("./skills.js", () => ({ scanNodeHostedSkills: vi.fn(() => mocks.nodeSkillDescriptors), })); @@ -594,6 +603,20 @@ describe("runNodeHost", () => { expect(lastCapturedOptions()?.caps).toContain("mcp"); expect(lastCapturedOptions()?.commands).toContain("mcp.tools.call.v1"); expect(lastCapturedOptions()?.commands).not.toContain("agent.cli.claude.run.v1"); + expect(lastCapturedOptions()?.workerRuns).toBeUndefined(); + }); + + it("advertises the local worker build only after node-local opt-in", async () => { + mocks.getRuntimeConfig.mockReturnValue({ + gateway: { handshakeTimeoutMs: 1_000 }, + nodeHost: { workerRuns: { enabled: true } }, + } as never); + + await expect(runNodeHost({ gatewayHost: "127.0.0.1", gatewayPort: 18789 })).rejects.toThrow( + "event loop readiness timeout", + ); + + expect(lastCapturedOptions()?.workerRuns).toEqual(mocks.nodeWorkerBuild); }); it("advertises Claude agent runs only after node-local opt-in and binary resolution", async () => { diff --git a/src/node-host/runner.ts b/src/node-host/runner.ts index 66dc31ac1909..3ab873808b35 100644 --- a/src/node-host/runner.ts +++ b/src/node-host/runner.ts @@ -229,6 +229,7 @@ export async function runNodeHost(opts: NodeHostRunOptions): Promise { config: cfg, env: process.env, enableAgentRuns: true, + enableWorkerRuns: true, installedAppsSharingEnabled: config.installedAppsSharing, }); const { token, password } = opts.preferGatewayBootstrapToken @@ -477,6 +478,7 @@ export async function runNodeHost(opts: NodeHostRunOptions): Promise { // restart-scoped availability, not a capability upgrade requiring re-pairing. caps: preparedRuntime.manifest.caps, commands: preparedRuntime.manifest.commands, + workerRuns: preparedRuntime.manifest.workerRuns, pathEnv: preparedRuntime.manifest.pathEnv, permissions: undefined, deviceIdentity: loadOrCreateDeviceIdentity(), diff --git a/src/node-host/runtime.ts b/src/node-host/runtime.ts index 403f59b50836..d06ccd239580 100644 --- a/src/node-host/runtime.ts +++ b/src/node-host/runtime.ts @@ -1,5 +1,6 @@ /** Transport-independent CLI node-host runtime shared by Gateway and app workers. */ import fs from "node:fs"; +import type { WorkerAdmissionHandshake } from "../../packages/gateway-protocol/src/schema/worker-admission.js"; import type { OpenClawConfig } from "../config/config.js"; import { getRuntimeConfig } from "../config/config.js"; import type { SkillBinTrustEntry } from "../infra/exec-approvals.js"; @@ -26,6 +27,7 @@ import { handleInvoke, type NodeInvokeRequestPayload, type SkillBinsProvider } f import { startNodeHostMcpManager, type NodeHostMcpManager } from "./mcp.js"; import { buildNodeEventParams } from "./node-event-params.js"; import { createNodeInvokeProgressWriter } from "./node-invoke-progress.js"; +import { resolveNodeWorkerBuild } from "./node-worker-build.js"; import { createNodeWorkerSupervisor } from "./node-worker-supervisor.js"; import { ensureNodeHostPluginRegistry, @@ -41,6 +43,7 @@ type NodeHostManifest = { caps: string[]; commands: string[]; pathEnv: string; + workerRuns?: WorkerAdmissionHandshake; }; export type NodeHostInventory = { @@ -230,7 +233,8 @@ function sameManifest(left: NodeHostManifest, right: NodeHostManifest): boolean return ( left.pathEnv === right.pathEnv && sameStringList(left.caps, right.caps) && - sameStringList(left.commands, right.commands) + sameStringList(left.commands, right.commands) && + JSON.stringify(left.workerRuns) === JSON.stringify(right.workerRuns) ); } @@ -239,6 +243,8 @@ export async function prepareNodeHostRuntime(params?: { env?: NodeJS.ProcessEnv; /** The embedded app worker never advertises native agent runs. */ enableAgentRuns?: boolean; + /** The embedded app worker never advertises full worker session hosting. */ + enableWorkerRuns?: boolean; /** Embedded workers may still host long-lived plugin commands over the app-owned socket. */ enableDuplexPluginCommands?: boolean; installedAppsSharingEnabled?: boolean; @@ -270,6 +276,10 @@ export async function prepareNodeHostRuntime(params?: { params?.enableAgentRuns === true && config.nodeHost?.agentRuns?.claude?.enabled === true ? resolveExecutableTrustPathFromEnv("claude", pathEnv) : null; + const workerRuns = + params?.enableWorkerRuns === true && config.nodeHost?.workerRuns?.enabled === true + ? await resolveNodeWorkerBuild() + : undefined; const skills = config.nodeHost?.skills?.enabled === false ? null : scanNodeHostedSkills(); const buildManifest = (pluginManifest: typeof pluginNodeHost): NodeHostManifest => ({ caps: [ @@ -294,6 +304,7 @@ export async function prepareNodeHostRuntime(params?: { ]), ].toSorted(), pathEnv, + ...(workerRuns ? { workerRuns } : {}), }); const manifest = buildManifest(pluginNodeHost); const initialInventory = createInventory({ diff --git a/src/shared/node-list-types.ts b/src/shared/node-list-types.ts index 0768b6ca61b7..6fb2930275f7 100644 --- a/src/shared/node-list-types.ts +++ b/src/shared/node-list-types.ts @@ -18,6 +18,8 @@ export type NodeListNode = { pathEnv?: string; caps?: string[]; commands?: string[]; + /** Connected node currently advertises full worker session hosting. */ + sessionHost?: boolean; nodePluginTools?: NodePluginToolDescriptor[]; permissions?: Record; approvalState?: "approved" | "pending-approval" | "pending-reapproval" | "unapproved"; diff --git a/src/shared/worker-bundle-hash.ts b/src/shared/worker-bundle-hash.ts new file mode 100644 index 000000000000..d33f4af6f38d --- /dev/null +++ b/src/shared/worker-bundle-hash.ts @@ -0,0 +1,20 @@ +import { createHash } from "node:crypto"; + +export const WORKER_BUNDLE_MANIFEST_VERSION = "openclaw-worker-bundle-v1"; + +type WorkerBundleHashEntry = { + path: string; + mode: number; + size: number; + sha256: string; +}; + +/** Hashes the canonical worker manifest shared by Gateway bundles and node-local installs. */ +export function hashWorkerBundleManifest(entries: readonly WorkerBundleHashEntry[]): string { + const hash = createHash("sha256"); + hash.update(`${WORKER_BUNDLE_MANIFEST_VERSION}\0`); + for (const entry of entries) { + hash.update(`${entry.path}\0${entry.mode.toString(8)}\0${entry.size}\0${entry.sha256}\0`); + } + return hash.digest("hex"); +} diff --git a/src/state/openclaw-database-maintenance.test.ts b/src/state/openclaw-database-maintenance.test.ts index 869865edc2c4..994f6273c2ab 100644 --- a/src/state/openclaw-database-maintenance.test.ts +++ b/src/state/openclaw-database-maintenance.test.ts @@ -160,6 +160,7 @@ describe("OpenClaw database maintenance schema validation", () => { "claw_installs.bootstrap_content_digest TEXT", "claw_installs.bootstrap_source_path TEXT", "worker_environments.desktop_json TEXT", + "worker_environments.bootstrap_install_kind TEXT", "claw_package_refs.extension_adapter_identity TEXT", "claw_package_refs.extension_detected_format TEXT", "claw_package_refs.extension_format TEXT", diff --git a/src/state/openclaw-state-db-additive-columns.ts b/src/state/openclaw-state-db-additive-columns.ts index fa7560080174..3166b7c506d8 100644 --- a/src/state/openclaw-state-db-additive-columns.ts +++ b/src/state/openclaw-state-db-additive-columns.ts @@ -11,6 +11,7 @@ export const CLAW_LAZY_ADDITIVE_STATE_COLUMN_DEFINITIONS = [ { columnName: "bootstrap_content_digest", dataType: "TEXT", tableName: "claw_installs" }, { columnName: "bootstrap_source_path", dataType: "TEXT", tableName: "claw_installs" }, { columnName: "desktop_json", dataType: "TEXT", tableName: "worker_environments" }, + { columnName: "bootstrap_install_kind", dataType: "TEXT", tableName: "worker_environments" }, { columnName: "extension_adapter_identity", dataType: "TEXT", tableName: "claw_package_refs" }, { columnName: "extension_detected_format", dataType: "TEXT", tableName: "claw_package_refs" }, { columnName: "extension_format", dataType: "TEXT", tableName: "claw_package_refs" }, diff --git a/src/state/openclaw-state-db-schema-additive.ts b/src/state/openclaw-state-db-schema-additive.ts index 6d27381b68a3..77ad6bdbf18e 100644 --- a/src/state/openclaw-state-db-schema-additive.ts +++ b/src/state/openclaw-state-db-schema-additive.ts @@ -424,6 +424,7 @@ export function ensureAdditiveStateColumns(db: DatabaseSync): void { ensureColumn(db, "worker_environments", "bootstrap_bundle_hash TEXT"); ensureColumn(db, "worker_environments", "bootstrap_openclaw_version TEXT"); ensureColumn(db, "worker_environments", "bootstrap_protocol_features_json TEXT"); + ensureColumn(db, "worker_environments", "bootstrap_install_kind TEXT"); ensureColumn( db, "worker_environments", diff --git a/src/state/openclaw-state-db.generated.d.ts b/src/state/openclaw-state-db.generated.d.ts index 3e7fd2d9e236..802cb177775a 100644 --- a/src/state/openclaw-state-db.generated.d.ts +++ b/src/state/openclaw-state-db.generated.d.ts @@ -1484,6 +1484,7 @@ export interface WorkerEnvironmentSshFallbackPorts { export interface WorkerEnvironments { attached_session_ids_json: Generated; bootstrap_bundle_hash: string | null; + bootstrap_install_kind: string | null; bootstrap_openclaw_version: string | null; bootstrap_protocol_features_json: string | null; created_at_ms: number; diff --git a/src/state/openclaw-state-db.test.ts b/src/state/openclaw-state-db.test.ts index b3ef401050d4..10d2baf2c53b 100644 --- a/src/state/openclaw-state-db.test.ts +++ b/src/state/openclaw-state-db.test.ts @@ -1202,6 +1202,7 @@ function runConcurrentSchemaProbe(params: { ALTER TABLE worker_environments DROP COLUMN bootstrap_bundle_hash; ALTER TABLE worker_environments DROP COLUMN bootstrap_openclaw_version; ALTER TABLE worker_environments DROP COLUMN bootstrap_protocol_features_json; + ALTER TABLE worker_environments DROP COLUMN bootstrap_install_kind; ALTER TABLE worker_environments DROP COLUMN owner_epoch; ALTER TABLE worker_environments DROP COLUMN teardown_terminal_state; ALTER TABLE worker_environments DROP COLUMN ssh_host_key; @@ -4092,6 +4093,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're ALTER TABLE worker_environments DROP COLUMN bootstrap_bundle_hash; ALTER TABLE worker_environments DROP COLUMN bootstrap_openclaw_version; ALTER TABLE worker_environments DROP COLUMN bootstrap_protocol_features_json; + ALTER TABLE worker_environments DROP COLUMN bootstrap_install_kind; ALTER TABLE worker_environments DROP COLUMN owner_epoch; ALTER TABLE worker_environments DROP COLUMN teardown_terminal_state; ALTER TABLE worker_environments DROP COLUMN ssh_host_key; @@ -4111,6 +4113,7 @@ INSERT INTO macos_port_guardian_records VALUES (4242, 18789, '/usr/bin/ssh', 're "bootstrap_bundle_hash", "bootstrap_openclaw_version", "bootstrap_protocol_features_json", + "bootstrap_install_kind", "owner_epoch", "teardown_terminal_state", "ssh_host_key", diff --git a/src/state/openclaw-state-schema-compatibility.ts b/src/state/openclaw-state-schema-compatibility.ts index 02c383b406da..b042341b2108 100644 --- a/src/state/openclaw-state-schema-compatibility.ts +++ b/src/state/openclaw-state-schema-compatibility.ts @@ -17,6 +17,7 @@ const CLAW_LAZY_ADDITIVE_STATE_COLUMNS = [ "claw_installs.bootstrap_content_digest", "claw_installs.bootstrap_source_path", "worker_environments.desktop_json", + "worker_environments.bootstrap_install_kind", "claw_package_refs.extension_adapter_identity", "claw_package_refs.extension_detected_format", "claw_package_refs.extension_format", @@ -101,6 +102,7 @@ export const STATE_PERSISTENT_SCHEMA_COMPATIBILITY: SqliteSchemaCompatibility = ], "operator_approvals.resolution_ref": ["resolution_ref TEXT"], "worker_environments.desktop_json": ["desktop_json TEXT"], + "worker_environments.bootstrap_install_kind": ["bootstrap_install_kind TEXT"], "worker_environments.shared_host": ["shared_host INTEGER CHECK (shared_host IN (0, 1))"], "worker_session_placements.terminal_reason": ["terminal_reason TEXT"], "worker_session_placements.terminal_at_ms": ["terminal_at_ms INTEGER"], diff --git a/src/state/openclaw-state-schema.sql b/src/state/openclaw-state-schema.sql index 8e9dcd7c6438..342f61b97ccd 100644 --- a/src/state/openclaw-state-schema.sql +++ b/src/state/openclaw-state-schema.sql @@ -1993,6 +1993,7 @@ CREATE TABLE IF NOT EXISTS worker_environments ( bootstrap_bundle_hash TEXT, bootstrap_openclaw_version TEXT, bootstrap_protocol_features_json TEXT, + bootstrap_install_kind TEXT, owner_epoch INTEGER NOT NULL DEFAULT 0 CHECK (owner_epoch >= 0), teardown_terminal_state TEXT CHECK (teardown_terminal_state IN ('destroyed', 'failed')), attached_session_ids_json TEXT NOT NULL DEFAULT '[]',