From c7e7ac2728917f085aff0ad50755fd39aa57f2b3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 19 Jul 2026 11:04:48 -0700 Subject: [PATCH] refactor: remove expired plugin compatibility surfaces (#111451) * docs(secrets): remove retired web credential paths * refactor(web): remove retired provider compatibility paths * refactor(providers): delete retired compatibility routes * refactor(secrets): remove retired credential aliases * refactor(plugin-sdk): delete retired compatibility surfaces * docs(plugin-sdk): remove retired migration guidance * chore(plugin-sdk): refresh rebased surface budgets * chore(plugin-sdk): refresh API removal baseline * refactor(compat): migrate retired internal callers * chore(plugin-sdk): refresh current-main baselines * test(config): migrate plugin-owned secret assertions * test(gateway): narrow plugin secret refs * fix(plugin-sdk): preserve private boundary type identity * chore(compat): remove stale sweep references * chore(lint): lower max-lines budget * refactor(secrets): remove unused web helper * build(plugin-sdk): drop removed compat entries * chore(plugin-sdk): refresh rebased API baseline * chore(plugin-sdk): use Linux API baseline hash * fix(plugin-sdk): preserve private bundled build entries * fix(plugin-sdk): package private runtime facades * fix(plugins): preserve external credential contracts --- config/max-lines-baseline.txt | 2 - .../.generated/plugin-sdk-api-baseline.sha256 | 229 +---- docs/cli/plugins.md | 6 +- docs/concepts/agent-loop.md | 1 - docs/concepts/message-lifecycle-refactor.md | 2 +- docs/docs_map.md | 7 +- docs/gateway/configuration-reference.md | 4 +- docs/plugins/architecture-internals.md | 42 +- docs/plugins/architecture.md | 26 +- docs/plugins/building-plugins.md | 6 - docs/plugins/compatibility.md | 68 +- docs/plugins/copilot.md | 3 +- docs/plugins/hooks.md | 9 +- docs/plugins/manifest.md | 9 +- docs/plugins/sdk-channel-message.md | 8 +- docs/plugins/sdk-channel-outbound.md | 2 +- docs/plugins/sdk-channel-plugins.md | 11 +- docs/plugins/sdk-migration.md | 287 +----- docs/plugins/sdk-overview.md | 15 +- docs/plugins/sdk-runtime.md | 12 +- docs/plugins/sdk-setup.md | 13 +- docs/plugins/sdk-subpaths.md | 323 +++--- docs/plugins/sdk-testing.md | 26 +- .../reference/secretref-credential-surface.md | 6 +- ...tref-user-supplied-credentials-matrix.json | 28 +- docs/tools/brave-search.md | 2 +- docs/tools/kimi-search.md | 4 +- docs/tools/minimax-search.md | 2 +- docs/tools/web.md | 3 +- extensions/brave/openclaw.plugin.json | 3 - .../src/brave-web-search-provider.runtime.ts | 6 +- .../src/brave-web-search-provider.test.ts | 285 ++---- extensions/brave/web-search-shared.ts | 24 +- extensions/clickclack/openclaw.plugin.json | 3 - extensions/clickclack/package.json | 7 + .../src/app-server/run-attempt-prompt.ts | 3 - extensions/copilot/src/attempt.test.ts | 19 - extensions/copilot/src/attempt.ts | 3 - extensions/deepinfra/openclaw.plugin.json | 3 - extensions/discord/openclaw.plugin.json | 5 - extensions/discord/package.json | 14 +- .../native-command.think-autocomplete.test.ts | 61 +- extensions/exa/openclaw.plugin.json | 3 - .../src/exa-web-search-provider.runtime.ts | 4 +- extensions/feishu/openclaw.plugin.json | 8 - extensions/feishu/package.json | 10 + extensions/firecrawl/openclaw.plugin.json | 3 - extensions/firecrawl/src/config.ts | 57 +- .../src/firecrawl-fetch-provider-shared.ts | 5 +- extensions/google/openclaw.plugin.json | 3 - .../src/gemini-web-search-provider.runtime.ts | 2 +- extensions/googlechat/openclaw.plugin.json | 3 - extensions/googlechat/package.json | 8 + extensions/imessage/src/shared.ts | 2 +- extensions/irc/openclaw.plugin.json | 14 - extensions/irc/package.json | 18 +- extensions/line/openclaw.plugin.json | 3 - extensions/line/package.json | 8 + extensions/matrix/openclaw.plugin.json | 14 - extensions/matrix/package.json | 16 + extensions/matrix/src/plugin-entry.runtime.js | 2 +- .../matrix/src/plugin-entry.runtime.test.ts | 4 +- extensions/mattermost/openclaw.plugin.json | 3 - extensions/mattermost/package.json | 8 + extensions/memory-lancedb/index.test.ts | 5 - extensions/minimax/openclaw.plugin.json | 3 - .../minimax-web-search-provider.runtime.ts | 6 +- extensions/moonshot/openclaw.plugin.json | 3 - .../src/kimi-web-search-provider.runtime.ts | 4 +- extensions/msteams/openclaw.plugin.json | 3 - extensions/msteams/package.json | 9 + .../nextcloud-talk/openclaw.plugin.json | 3 - extensions/nextcloud-talk/package.json | 8 + extensions/nostr/openclaw.plugin.json | 3 - extensions/nostr/package.json | 7 + extensions/ollama/src/discovery-shared.ts | 5 +- extensions/ollama/src/provider-base-url.ts | 5 +- extensions/openrouter/index.test.ts | 6 - extensions/openrouter/index.ts | 6 +- extensions/openrouter/thinking-policy.ts | 4 - extensions/parallel/openclaw.plugin.json | 3 - .../parallel-web-search-provider.runtime.ts | 6 +- extensions/perplexity/openclaw.plugin.json | 3 - .../perplexity-web-search-provider.runtime.ts | 4 +- .../perplexity-web-search-provider.test.ts | 2 +- extensions/pixverse/openclaw.plugin.json | 10 +- extensions/qqbot/openclaw.plugin.json | 3 - extensions/qqbot/package.json | 8 + extensions/qqbot/src/bridge/runtime.ts | 2 +- extensions/raft/openclaw.plugin.json | 3 - extensions/raft/package.json | 7 + extensions/slack/openclaw.plugin.json | 3 - extensions/slack/package.json | 18 +- extensions/sms/openclaw.plugin.json | 12 - extensions/sms/package.json | 14 + extensions/synology-chat/openclaw.plugin.json | 10 - extensions/synology-chat/package.json | 12 + extensions/tavily/openclaw.plugin.json | 3 - extensions/telegram/openclaw.plugin.json | 3 - extensions/telegram/package.json | 14 +- .../tsconfig.package-boundary.paths.json | 478 ++++++++- extensions/twitch/openclaw.plugin.json | 3 - extensions/twitch/package.json | 7 + extensions/voice-call/openclaw.plugin.json | 14 - extensions/whatsapp/src/shared.ts | 2 +- extensions/xai/code-execution.test.ts | 23 - extensions/xai/openclaw.plugin.json | 11 - extensions/xai/setup-api.ts | 6 +- extensions/xai/src/tool-auth-shared.test.ts | 28 +- extensions/xai/src/tool-auth-shared.ts | 35 +- .../xai/src/web-search-provider.runtime.ts | 4 +- extensions/xai/src/x-search-config.ts | 19 - extensions/xai/tsconfig.json | 481 ++++++++- extensions/xai/web-search.test.ts | 19 - extensions/xai/x-search.test.ts | 82 -- extensions/xiaomi/openclaw.plugin.json | 4 - extensions/zalo/openclaw.plugin.json | 3 - extensions/zalo/package.json | 8 + extensions/zalouser/openclaw.plugin.json | 3 - extensions/zalouser/package.json | 8 + package.json | 931 +++-------------- packages/plugin-sdk/package.json | 12 - scripts/build-all.mjs | 1 - scripts/check-deprecated-api-usage.mjs | 2 - ...-no-monolithic-plugin-sdk-entry-imports.ts | 56 +- scripts/check-plugin-sdk-exports.mjs | 64 +- scripts/copy-plugin-sdk-root-alias.mjs | 20 - scripts/e2e/Dockerfile | 2 +- scripts/e2e/lib/clawhub-fixture-server.cjs | 2 +- .../write-clickclack-plugin.mjs | 9 +- scripts/e2e/lib/upgrade-survivor/run.sh | 2 +- scripts/e2e/status-corrupt-plugin-deps.sh | 11 +- scripts/e2e/upgrade-survivor-docker.sh | 2 +- .../fixtures/packed-plugin-sdk-type-smoke.ts | 4 +- ...enerate-bundled-channel-config-metadata.ts | 19 +- scripts/lib/deprecated-plugin-sdk-usage.mjs | 18 +- scripts/lib/extension-package-boundary.ts | 51 +- scripts/lib/plugin-contract-test-plan.mjs | 3 - ...plugin-sdk-deprecated-barrel-subpaths.json | 3 - ...plugin-sdk-deprecated-public-subpaths.json | 40 - scripts/lib/plugin-sdk-doc-metadata.ts | 66 -- scripts/lib/plugin-sdk-entries.d.mts | 4 +- scripts/lib/plugin-sdk-entries.mjs | 77 +- scripts/lib/plugin-sdk-entrypoints.json | 41 - ...lugin-sdk-private-local-only-subpaths.json | 155 ++- .../lib/session-accessor-debt-baseline.json | 2 +- scripts/lib/ts-topology/scope.ts | 3 +- scripts/plugin-boundary-report.ts | 2 +- scripts/plugin-sdk-surface-report.mjs | 254 +---- scripts/postinstall-bundled-plugins.mjs | 13 - ...e-extension-package-boundary-artifacts.mjs | 15 +- scripts/release-check.ts | 22 +- scripts/run-node.mjs | 9 - scripts/runtime-postbuild.mjs | 11 - scripts/stage-bundled-plugin-runtime.mjs | 26 +- scripts/test-built-plugin-singleton.mjs | 3 +- scripts/test-projects.test-support.mjs | 1 - scripts/write-plugin-sdk-entry-dts.ts | 47 +- src/acp/persistent-bindings.test.ts | 6 +- src/agents/cli-runner.reliability.test.ts | 1 - src/agents/cli-runner/prepare.test.ts | 57 +- .../compact.hooks.test.ts | 7 +- .../run.overflow-compaction.harness.ts | 9 - .../run.overflow-compaction.test.ts | 27 - .../run/attempt-dispatch-preparation.ts | 2 - .../run/attempt-prompt-assembly.ts | 1 - .../run/attempt.prompt-helpers.test.ts | 1 - .../run/attempt.prompt-helpers.ts | 46 +- ...mpt.spawn-workspace.context-engine.test.ts | 16 +- .../embedded-agent-runner/run/attempt.test.ts | 76 +- .../embedded-agent-runner/run/model-setup.ts | 1 - .../run/run-attempt-dispatch.ts | 2 - .../run/runtime-preparation.ts | 2 - .../embedded-agent-runner/run/setup.test.ts | 6 - src/agents/embedded-agent-runner/run/setup.ts | 34 +- src/agents/embedded-agent-runner/run/types.ts | 2 - .../system-prompt.test.ts | 4 +- .../harness/context-engine-lifecycle.test.ts | 20 +- .../prompt-compaction-hook-helpers.test.ts | 55 +- .../harness/prompt-compaction-hook-helpers.ts | 38 +- .../harness/tool-result-middleware.test.ts | 4 +- src/agents/harness/tool-result-middleware.ts | 3 +- .../model-auth-env.provider-aliases.test.ts | 4 +- src/agents/model-auth.test.ts | 4 +- src/agents/model-provider-auth.worker.test.ts | 1 - ...dels-config.providers.auth-aliases.test.ts | 18 +- .../models-config.providers.implicit.ts | 6 +- .../openclaw-tools.media-factory-plan.test.ts | 15 +- src/agents/sessions/extensions/loader.test.ts | 11 +- src/agents/subagent-announce.live.test.ts | 3 - src/agents/system-prompt.memory.test.ts | 6 +- .../tools/web-search-provider-config.ts | 33 +- src/agents/tools/web-search.late-bind.test.ts | 2 +- src/agents/tools/web-search.test.ts | 43 +- src/agents/xai.live.test.ts | 10 +- ...irective.directive-behavior.e2e-harness.ts | 13 +- src/auto-reply/thinking.test.ts | 89 +- src/auto-reply/thinking.ts | 48 +- src/channels/mention-gating.test.ts | 104 +- src/channels/mention-gating.ts | 78 +- src/channels/plugins/approval-native.types.ts | 6 +- src/channels/plugins/configured-state.test.ts | 16 + .../threading-directory-contract-suites.ts | 10 +- src/channels/plugins/exposure.ts | 18 +- src/channels/plugins/outbound.types.ts | 4 +- src/channels/plugins/read-only.test.ts | 23 +- src/channels/plugins/types.adapters.ts | 34 +- src/channels/plugins/types.core.ts | 14 +- src/channels/plugins/types.public.ts | 68 -- src/cli/command-secret-gateway.test.ts | 61 -- src/cli/command-secret-gateway.ts | 85 +- src/cli/command-secret-targets.test.ts | 107 -- src/cli/command-secret-targets.ts | 115 +-- src/cli/config-cli.test.ts | 85 +- src/cli/plugins-cli.update.test.ts | 194 +--- src/cli/plugins-inspect-command.ts | 19 +- src/cli/plugins-update-command.ts | 26 +- src/commands/auth-choice-legacy.test.ts | 1 - src/commands/channel-setup/discovery.ts | 4 +- .../channels.status.external-env.test.ts | 7 +- src/commands/config-validation.test.ts | 4 +- .../doctor-legacy-config.migrations.test.ts | 84 +- src/commands/doctor-memory-search.test.ts | 2 +- src/commands/doctor-memory-search.ts | 2 +- src/commands/doctor-plugin-registry.test.ts | 17 - src/commands/doctor-plugin-registry.ts | 11 - src/commands/doctor-state-integrity.test.ts | 12 +- src/commands/doctor-state-integrity.ts | 11 +- src/commands/doctor-workspace-status.test.ts | 16 +- .../shared/channel-plugin-blockers.test.ts | 109 +- .../doctor/shared/channel-plugin-blockers.ts | 69 +- .../shared/context-engine-host-compat.test.ts | 12 +- .../shared/legacy-web-search-migrate.test.ts | 22 +- .../shared/plugin-registry-migration.test.ts | 42 +- .../shared/plugin-registry-migration.ts | 39 +- src/commands/onboard-channels.e2e.test.ts | 2 +- src/commands/status-json-payload.test.ts | 4 +- src/commands/status-json-runtime.test.ts | 4 +- src/commands/status.scan-result.test.ts | 4 +- src/commands/status.test.ts | 6 +- ...ndled-channel-config-metadata.generated.ts | 24 +- src/config/channel-configured.test.ts | 15 + src/config/commands.test.ts | 1 - src/config/config.env-vars.test.ts | 28 +- src/config/config.web-search-provider.test.ts | 7 - src/config/io.context.ts | 183 +--- src/config/io.load.ts | 12 +- src/config/io.recovery.ts | 9 +- src/config/io.snapshot.ts | 30 +- src/config/io.types.ts | 9 - src/config/io.write-config.test.ts | 493 +-------- src/config/io.write.ts | 12 +- src/config/plugin-auto-enable.shared.ts | 5 +- src/config/schema.help.agents.ts | 2 +- src/config/schema.help.quality.test.ts | 2 - src/config/schema.help.runtime.ts | 2 - src/config/schema.labels.ts | 1 - src/config/schema.test.ts | 37 +- src/config/sessions.cache.test.ts | 18 +- src/config/sessions.test.ts | 215 ---- src/config/sessions/sessions.test.ts | 4 +- src/config/sessions/store.ts | 36 - src/config/types.plugins.ts | 2 +- src/config/types.tools.ts | 17 - src/config/web-search-codex-config.test.ts | 3 +- src/config/zod-schema.agent-defaults.test.ts | 6 - src/config/zod-schema.agent-runtime.ts | 32 +- src/context-engine/context-engine.test.ts | 125 +-- src/context-engine/registry.ts | 15 - src/context-engine/types.ts | 16 - ...gent.direct-delivery-core-channels.test.ts | 2 +- src/extensionAPI.ts | 34 - src/flows/channel-setup.status.test.ts | 4 +- src/flows/channel-setup.status.ts | 2 - src/flows/search-setup.ts | 3 - src/gateway/gateway-codex-bind.live.test.ts | 2 +- src/gateway/server-channels.test.ts | 7 +- src/gateway/server-plugins.test.ts | 10 +- src/gateway/server-plugins.ts | 3 - src/gateway/server-reload-handlers.test.ts | 12 +- ...server-startup-inactive-web-secret.test.ts | 1 - ...ver-startup-secret-owner-isolation.test.ts | 1 - .../worker-environments/bootstrap.test.ts | 3 - src/gateway/worker-environments/bootstrap.ts | 6 +- src/infra/dotenv.test.ts | 4 +- ...y-queue-media-directive-durability.test.ts | 2 +- src/infra/run-node.test.ts | 57 +- src/infra/tsdown-config.test.ts | 2 - src/memory-host-sdk/event-types.ts | 10 +- src/memory-host-sdk/events.ts | 12 +- src/plugin-activation-boundary.test.ts | 8 +- src/plugin-sdk/agent-dir-compat.test.ts | 24 - src/plugin-sdk/agent-dir-compat.ts | 12 - src/plugin-sdk/agent-harness-runtime.ts | 2 - src/plugin-sdk/agent-harness-tool-runtime.ts | 31 +- src/plugin-sdk/api-baseline.ts | 3 +- .../approval-delivery-helpers.test.ts | 21 - src/plugin-sdk/approval-delivery-helpers.ts | 12 +- src/plugin-sdk/channel-actions.ts | 40 - .../channel-config-schema-legacy.ts | 10 - src/plugin-sdk/channel-entry-contract.test.ts | 6 - src/plugin-sdk/channel-envelope.ts | 6 - src/plugin-sdk/channel-inbound-roots.ts | 2 - src/plugin-sdk/channel-inbound.ts | 8 - src/plugin-sdk/channel-location.ts | 3 - src/plugin-sdk/channel-mention-gating.ts | 8 - src/plugin-sdk/channel-message-runtime.ts | 6 - src/plugin-sdk/channel-message.test.ts | 14 +- src/plugin-sdk/channel-pairing-paths.ts | 2 - .../channel-reply-options-runtime.ts | 2 - src/plugin-sdk/channel-route.test.ts | 13 - src/plugin-sdk/channel-route.ts | 10 - src/plugin-sdk/channel-runtime.ts | 23 - src/plugin-sdk/command-auth.test.ts | 6 +- src/plugin-sdk/command-auth.ts | 26 - src/plugin-sdk/command-gating.ts | 12 - src/plugin-sdk/compat.ts | 159 --- src/plugin-sdk/config-runtime.ts | 5 - src/plugin-sdk/config-schema.ts | 8 - src/plugin-sdk/config-types.ts | 11 - src/plugin-sdk/core.ts | 1 - src/plugin-sdk/direct-dm-access.ts | 2 - src/plugin-sdk/direct-dm.test.ts | 2 +- src/plugin-sdk/direct-dm.ts | 12 - src/plugin-sdk/discord.ts | 4 +- src/plugin-sdk/entrypoints.ts | 54 +- src/plugin-sdk/extension-shared.ts | 2 +- src/plugin-sdk/index.ts | 51 - src/plugin-sdk/lmstudio-runtime.test.ts | 45 - src/plugin-sdk/lmstudio-runtime.ts | 204 ---- src/plugin-sdk/lmstudio.ts | 111 -- src/plugin-sdk/mattermost.ts | 14 - .../media-generation-runtime-shared.ts | 10 - .../memory-core-bundled-runtime.test.ts | 20 + src/plugin-sdk/memory-core-bundled-runtime.ts | 97 +- .../memory-core-engine-runtime.test.ts | 63 -- src/plugin-sdk/memory-core-engine-runtime.ts | 221 ---- src/plugin-sdk/memory-core-host-events.ts | 21 - src/plugin-sdk/memory-core-host-multimodal.ts | 5 - src/plugin-sdk/memory-core-host-query.ts | 5 - src/plugin-sdk/memory-core.ts | 5 - src/plugin-sdk/memory-host-core.test.ts | 10 +- src/plugin-sdk/memory-host-files.ts | 6 - src/plugin-sdk/memory-host-status.ts | 6 - src/plugin-sdk/migration-runtime.ts | 12 +- src/plugin-sdk/migration.ts | 2 +- src/plugin-sdk/music-generation-core.ts | 32 - src/plugin-sdk/outbound-runtime.ts | 24 - src/plugin-sdk/outbound-send-deps.ts | 7 - src/plugin-sdk/plugin-entry.ts | 2 - src/plugin-sdk/plugin-test-api.ts | 3 - src/plugin-sdk/provider-auth-login.ts | 24 - src/plugin-sdk/provider-auth.ts | 1 - src/plugin-sdk/provider-entry.test.ts | 6 +- src/plugin-sdk/provider-setup.ts | 1 - src/plugin-sdk/provider-web-search.ts | 18 - src/plugin-sdk/provider-zai-endpoint.ts | 40 - src/plugin-sdk/reply-dedupe.ts | 3 - src/plugin-sdk/root-alias.cjs | 750 -------------- src/plugin-sdk/runtime-logger.internal.ts | 41 + src/plugin-sdk/runtime-logger.ts | 87 -- src/plugin-sdk/runtime-secret-resolution.ts | 14 - src/plugin-sdk/runtime.ts | 7 +- src/plugin-sdk/secret-provider-integration.ts | 9 - src/plugin-sdk/self-hosted-provider-setup.ts | 23 - src/plugin-sdk/session-store-runtime.test.ts | 2 +- src/plugin-sdk/setup-adapter-runtime.ts | 7 - src/plugin-sdk/skills-runtime.ts | 10 - src/plugin-sdk/telegram-account.ts | 2 +- .../telegram-command-config.test.ts | 50 - src/plugin-sdk/telegram-command-config.ts | 100 -- src/plugin-sdk/test-helpers/directory-ids.ts | 2 +- .../test-helpers/plugin-runtime-mock.ts | 21 - src/plugin-sdk/test-utils.ts | 2 - src/plugin-sdk/webhook-ingress.ts | 3 +- src/plugin-sdk/webhook-path.ts | 49 - src/plugin-sdk/webhook-targets.ts | 31 +- src/plugin-sdk/zalouser.ts | 13 - .../agent-tool-result-middleware-types.ts | 8 - .../agent-tool-result-middleware.test.ts | 22 +- src/plugins/agent-tool-result-middleware.ts | 13 +- src/plugins/api-builder.ts | 10 - src/plugins/bundled-capability-runtime.ts | 9 +- src/plugins/bundled-load-path-aliases.ts | 29 - src/plugins/channel-plugin-ids.test.ts | 207 +--- src/plugins/channel-presence-policy.ts | 16 +- src/plugins/commands.test.ts | 1 - src/plugins/compat/registry.test.ts | 37 +- src/plugins/compat/registry.ts | 378 ++++--- src/plugins/config-contracts.test.ts | 2 - .../contracts/boundary-invariants.test.ts | 6 - .../config-footprint-guardrails.test.ts | 16 +- ...tension-package-project-boundaries.test.ts | 7 - .../contracts/plugin-sdk-index.test.ts | 131 --- ...in-sdk-package-contract-guardrails.test.ts | 4 +- .../contracts/plugin-sdk-root-alias.test.ts | 965 ------------------ .../contracts/plugin-sdk-subpaths.test.ts | 98 +- src/plugins/contracts/registry.ts | 4 - src/plugins/contracts/shape.contract.test.ts | 13 +- src/plugins/discovery.test.ts | 67 -- src/plugins/discovery.ts | 17 +- src/plugins/hook-before-agent-start.types.ts | 48 - src/plugins/hook-lifecycle-gates.test.ts | 76 -- src/plugins/hook-types.ts | 13 - src/plugins/hooks.before-agent-reply.test.ts | 1 - src/plugins/hooks.before-agent-start.test.ts | 222 ---- src/plugins/hooks.correlation.test.ts | 18 - .../hooks.model-override-wiring.test.ts | 90 +- src/plugins/hooks.ts | 33 - src/plugins/inspect-shape.ts | 4 - src/plugins/install-record-commit.ts | 3 +- .../installed-plugin-index-record-builder.ts | 6 - src/plugins/installed-plugin-index.test.ts | 8 - src/plugins/loader-module-runtime.ts | 5 +- .../loader-sdk-import-guardrails.test.ts | 3 - ...oader.discovery-and-security.test-utils.ts | 100 -- .../loader.hooks-and-runtime.test-utils.ts | 37 +- src/plugins/loader.registration.test-utils.ts | 68 +- src/plugins/manifest-registry-installed.ts | 2 - src/plugins/manifest-registry.test.ts | 121 +-- src/plugins/manifest-registry.ts | 38 - src/plugins/manifest-tool-availability.ts | 6 +- src/plugins/manifest.ts | 16 - src/plugins/memory-state.test-fixtures.ts | 9 +- src/plugins/memory-state.test.ts | 12 +- src/plugins/memory-state.ts | 29 - src/plugins/native-module-require.test.ts | 2 +- src/plugins/plugin-api.types.ts | 17 - src/plugins/plugin-definition.types.ts | 1 - .../plugin-metadata-snapshot.memo.test.ts | 1 - src/plugins/plugin-metadata-snapshot.ts | 10 +- src/plugins/plugin-metadata-snapshot.types.ts | 1 - .../plugin-module-loader-cache.test.ts | 3 +- src/plugins/plugin-registry-contributions.ts | 24 - src/plugins/plugin-registry-snapshot.ts | 26 +- src/plugins/plugin-registry.test.ts | 31 +- .../plugin-sdk-native-resolver.test.ts | 4 - src/plugins/plugin-sdk-native-resolver.ts | 4 +- src/plugins/provider-catalog.types.ts | 15 - .../provider-discovery.runtime.test.ts | 6 +- src/plugins/provider-discovery.runtime.ts | 9 +- src/plugins/provider-discovery.test.ts | 86 +- src/plugins/provider-discovery.ts | 10 +- src/plugins/provider-external-auth.types.ts | 7 - src/plugins/provider-plugin.types.ts | 59 -- src/plugins/provider-replay.types.ts | 10 - ...r-runtime.synthetic-auth-discovery.test.ts | 1 - src/plugins/provider-runtime.test.ts | 97 -- src/plugins/provider-runtime.ts | 70 +- src/plugins/provider-self-hosted-setup.ts | 4 +- src/plugins/provider-thinking-active.ts | 26 - src/plugins/provider-thinking.ts | 33 +- src/plugins/provider-validation.test.ts | 93 +- src/plugins/provider-validation.ts | 27 - src/plugins/providers.test.ts | 34 - src/plugins/providers.ts | 28 - src/plugins/registry-api.ts | 7 - src/plugins/registry-registrars-memory.ts | 33 - .../registry-registrars-tools-hooks.ts | 32 +- src/plugins/registry-runtime.ts | 2 - .../registry.dual-kind-memory-gate.test.ts | 8 +- src/plugins/registry.runtime-config.test.ts | 5 - .../runtime-plugin-boundary.whatsapp.test.ts | 5 +- src/plugins/runtime/index.test.ts | 15 +- src/plugins/runtime/index.ts | 9 +- src/plugins/runtime/runtime-channel.ts | 8 +- src/plugins/runtime/runtime-config.test.ts | 117 +-- src/plugins/runtime/runtime-config.ts | 58 -- src/plugins/runtime/runtime-tasks.test.ts | 8 +- src/plugins/runtime/runtime-tasks.ts | 5 +- src/plugins/runtime/runtime-tasks.types.ts | 2 - src/plugins/runtime/types-channel.ts | 2 - src/plugins/runtime/types-core.ts | 24 - src/plugins/runtime/types.ts | 4 - src/plugins/sdk-alias.test.ts | 421 ++------ src/plugins/sdk-alias.ts | 179 +--- .../stage-bundled-plugin-runtime.test.ts | 12 +- src/plugins/status.registry-snapshot.test.ts | 2 - src/plugins/status.test-fixtures.ts | 10 - src/plugins/status.test.ts | 49 +- src/plugins/status.ts | 17 +- .../test-helpers/cold-plugin-fixtures.ts | 4 +- src/plugins/tools.optional.test.ts | 8 +- src/plugins/types.ts | 3 +- .../web-provider-public-artifacts.test.ts | 49 - src/plugins/web-search-credential-presence.ts | 5 +- src/secrets/channel-env-vars.dynamic.test.ts | 14 +- src/secrets/channel-env-vars.ts | 14 +- src/secrets/credential-matrix.ts | 12 +- src/secrets/exec-secret-ref-id-parity.test.ts | 42 +- src/secrets/provider-env-vars.dynamic.test.ts | 55 +- src/secrets/provider-env-vars.ts | 7 - src/secrets/provider-integrations.ts | 2 +- src/secrets/runtime-command-secrets.ts | 165 +-- src/secrets/runtime-fast-path.ts | 2 +- src/secrets/runtime-legacy-x-search.test.ts | 117 --- src/secrets/runtime-owner-contract.test.ts | 17 +- .../runtime-web-tools-manifest.runtime.ts | 1 - src/secrets/runtime-web-tools.shared.ts | 16 - src/secrets/runtime-web-tools.test.ts | 292 +++--- src/secrets/runtime-web-tools.ts | 70 +- src/secrets/runtime.coverage.test.ts | 34 +- src/secrets/runtime.test-support.ts | 4 - src/secrets/target-registry-data.ts | 34 - src/secrets/target-registry-test-helpers.ts | 13 - src/skills/runtime/refresh-state.ts | 2 +- src/skills/runtime/refresh.test.ts | 3 +- src/status/status-plugin-health.test.ts | 8 +- src/version.test.ts | 2 +- src/web-fetch/runtime.ts | 7 +- src/web-search/runtime.ts | 2 - src/wizard/setup.finalize.test.ts | 7 - src/wizard/setup.migration-import.test.ts | 7 - src/wizard/setup.test.ts | 8 +- test/release-check.test.ts | 67 +- test/scripts/build-all.test.ts | 1 - .../check-deprecated-api-usage.test.ts | 28 +- .../scripts/ci-changed-node-test-plan.test.ts | 2 +- test/scripts/docker-build-helper.test.ts | 4 +- .../postinstall-bundled-plugins.test.ts | 71 -- ...tension-package-boundary-artifacts.test.ts | 29 +- test/scripts/runtime-postbuild.test.ts | 18 - test/scripts/test-projects.test.ts | 4 - test/vitest/vitest.shared.config.ts | 8 - tsconfig.json | 3 - tsdown.config.ts | 11 +- 526 files changed, 4009 insertions(+), 13992 deletions(-) delete mode 100644 scripts/copy-plugin-sdk-root-alias.mjs delete mode 100644 src/extensionAPI.ts delete mode 100644 src/plugin-sdk/agent-dir-compat.test.ts delete mode 100644 src/plugin-sdk/agent-dir-compat.ts delete mode 100644 src/plugin-sdk/channel-config-schema-legacy.ts delete mode 100644 src/plugin-sdk/channel-envelope.ts delete mode 100644 src/plugin-sdk/channel-inbound-roots.ts delete mode 100644 src/plugin-sdk/channel-location.ts delete mode 100644 src/plugin-sdk/channel-message-runtime.ts delete mode 100644 src/plugin-sdk/channel-pairing-paths.ts delete mode 100644 src/plugin-sdk/channel-reply-options-runtime.ts delete mode 100644 src/plugin-sdk/channel-runtime.ts delete mode 100644 src/plugin-sdk/command-gating.ts delete mode 100644 src/plugin-sdk/compat.ts delete mode 100644 src/plugin-sdk/config-schema.ts delete mode 100644 src/plugin-sdk/config-types.ts delete mode 100644 src/plugin-sdk/direct-dm-access.ts delete mode 100644 src/plugin-sdk/direct-dm.ts delete mode 100644 src/plugin-sdk/index.ts delete mode 100644 src/plugin-sdk/lmstudio-runtime.test.ts delete mode 100644 src/plugin-sdk/lmstudio-runtime.ts delete mode 100644 src/plugin-sdk/lmstudio.ts delete mode 100644 src/plugin-sdk/mattermost.ts delete mode 100644 src/plugin-sdk/media-generation-runtime-shared.ts delete mode 100644 src/plugin-sdk/memory-core-engine-runtime.test.ts delete mode 100644 src/plugin-sdk/memory-core-engine-runtime.ts delete mode 100644 src/plugin-sdk/memory-core-host-events.ts delete mode 100644 src/plugin-sdk/memory-core-host-multimodal.ts delete mode 100644 src/plugin-sdk/memory-core-host-query.ts delete mode 100644 src/plugin-sdk/memory-core.ts delete mode 100644 src/plugin-sdk/memory-host-files.ts delete mode 100644 src/plugin-sdk/memory-host-status.ts delete mode 100644 src/plugin-sdk/music-generation-core.ts delete mode 100644 src/plugin-sdk/outbound-runtime.ts delete mode 100644 src/plugin-sdk/outbound-send-deps.ts delete mode 100644 src/plugin-sdk/provider-auth-login.ts delete mode 100644 src/plugin-sdk/provider-zai-endpoint.ts delete mode 100644 src/plugin-sdk/reply-dedupe.ts delete mode 100644 src/plugin-sdk/root-alias.cjs create mode 100644 src/plugin-sdk/runtime-logger.internal.ts delete mode 100644 src/plugin-sdk/runtime-logger.ts delete mode 100644 src/plugin-sdk/runtime-secret-resolution.ts delete mode 100644 src/plugin-sdk/secret-provider-integration.ts delete mode 100644 src/plugin-sdk/self-hosted-provider-setup.ts delete mode 100644 src/plugin-sdk/setup-adapter-runtime.ts delete mode 100644 src/plugin-sdk/skills-runtime.ts delete mode 100644 src/plugin-sdk/telegram-command-config.test.ts delete mode 100644 src/plugin-sdk/telegram-command-config.ts delete mode 100644 src/plugin-sdk/test-utils.ts delete mode 100644 src/plugin-sdk/webhook-path.ts delete mode 100644 src/plugin-sdk/zalouser.ts delete mode 100644 src/plugins/contracts/plugin-sdk-index.test.ts delete mode 100644 src/plugins/contracts/plugin-sdk-root-alias.test.ts delete mode 100644 src/plugins/hooks.before-agent-start.test.ts delete mode 100644 src/secrets/runtime-legacy-x-search.test.ts delete mode 100644 src/secrets/target-registry-test-helpers.ts diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt index 1a1b431c99d2..aa519001ad84 100644 --- a/config/max-lines-baseline.txt +++ b/config/max-lines-baseline.txt @@ -631,7 +631,6 @@ src/channels/turn/kernel.test.ts src/cli/capability-cli.test.ts src/cli/command-secret-gateway.test.ts src/cli/command-secret-gateway.ts -src/cli/command-secret-targets.test.ts src/cli/command-secret-targets.ts src/cli/config-cli.test.ts src/cli/config-cli.ts @@ -1007,7 +1006,6 @@ src/plugins/marketplace.test.ts src/plugins/marketplace.ts src/plugins/official-external-plugin-catalog.test.ts src/plugins/official-external-plugin-catalog.ts -src/plugins/plugin-metadata-snapshot.ts src/plugins/plugin-registry-snapshot.test.ts src/plugins/provider-runtime.test.ts src/plugins/provider-runtime.ts diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index d56436fc07c5..9028ab675473 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,333 +1,140 @@ -4f222069c2ba0c355b72e48a27da2294484dcce0a9b4b3fd3a7ea90a134130c7 module/index -f8baddc86ac0e924c7806957f90a6ebdd5916a362ad24b665824503ee77fc6a9 module/access-groups 7a8753532854ff6f6d2a0c8e6386653adce285f9f6a9b5e56e88c9ea49e79cc2 module/account-core e7519fd1b7ff7003a6c8b9b640ffedd443a3754a10efc82877d48cd98cc6ba28 module/account-helpers cbf4e2c3088f8886a7c9ea91325a66e0f0846cea21f0b2891f36399b4811306c module/account-id 6b674e7aa4006240227c16eb5c74360f1d67639e8f5580ad72499d6ed28283c4 module/account-resolution -820af87fab6f24d5aafe5f264fff8f3c4324edab10761022c28b6926f359e6e0 module/account-resolution-runtime -822ddd0df21c641ba0b198da30d60d8cddd38c0d740b005232287ffcb0bf5171 module/acp-binding-resolve-runtime -35395a46410738203c1b9621e4263e8214955af77a3011d45668f777c55cd4a1 module/acp-binding-runtime -2a7a3321dbad3e78e6d6ce2898666674f9cab6adeae9190e47c277079d32ae36 module/acp-runtime -ed5dd7c56969decc47a2528cf1115b781ee7b368f72b9f47e12c3bacd65d95d2 module/acp-runtime-backend e5e67ddf3cab38fcbf9220bc3160715897e2709d9a9ff6ff36f1ecc9453c2367 module/agent-config-primitives -30452bae2a689fb75dcb6dba9ef11e5b95f9cbe0c62eb190a0fcb82d0a19ae52 module/agent-core 74daa746deb548379d3f0d6eac3c4d082df1034c4360cc03bf51fee0f10a2e4d module/agent-harness -e09226afd443cee02ed648f032f53bfeb60585617bb523a649995764d3c38da9 module/agent-harness-exec-review-runtime -ffacec9f51060b81c597a9d169e7ee0fa463fcbd18daffdce129d144897b2c5b module/agent-harness-runtime -ec22d7a039fb58d0b8343ad149322960d3d8ca58b3f4c70f2fa8a099f8186d0c module/agent-harness-task-runtime -5f63bf587bf3547d59d0dc5d0dc2fee54745aa6edaab4aa3ae700dba03443edb module/agent-harness-tool-runtime +c67e52e5bc6d6917f9a13f4133e7b18e111dbd33b66b6a5eae79623d9ff6e918 module/agent-harness-runtime 5168648cd946abad8a92822889f13ceacc87ed502314a66190d0b1eb8ebe76ea module/agent-media-payload 2c60564ae141dc249234e409994498f4143943d36c7860fa5759a63e4375e377 module/agent-runtime -d5191546cfd0b2dde39f6d9f5077ab1539aa359a90cf9a2ab13c5c19b7c948b9 module/agent-sessions dd9282f1eeadf44db2887599b52d80db7f5fb99c6d9eac720dbf1b77065f2145 module/allow-from 55cea5390d68839ca7768b4a0cc570b17b65fa0fa3bc4d76130ef0f16cb79ede module/allowlist-config-edit 7ddd81bd5f55de9adf64bf4d92d012f24b37b6da0a72805a3a220d8feff24ca3 module/approval-auth-runtime a973b03d88f2aa71d3b6c39241527c18d9b1b75f5639bf39cc9b080efbe7b6c6 module/approval-client-runtime -49cd817c2a01f9ba581f24d932dc04a65d79399746df862b3c3947cab659dda4 module/approval-delivery-runtime -2fd995aaf62ce912b97ac01f60260707c5e91b4e40d62167e6704acd5b533527 module/approval-gateway-runtime +37346f8621e2a12ca819f44075882aec1e4a7ebf12dbcd9ba772aeb8413404db module/approval-delivery-runtime +1ad8a27933d4092fb26c7ca1be389a0e463023004735d9e992656f81a4e2956f module/approval-gateway-runtime aceab327f18983cfa074384b4510648be2156dfdd30415a2d7a48fd1f38da1c6 module/approval-handler-adapter-runtime a453261fc6c0d45ad652992bfa3139a1295b6e6296878ee5fde2cf12b995516e module/approval-handler-runtime 1cb17e85297fa4419f7beb7fe7e03747bb6841277ec49fd0fb8794695b0a31f8 module/approval-native-runtime -f7564f1ac34e77d31a38fbd913089799ff459ca8d4500850f4bf35c22cabaa27 module/approval-reaction-runtime -0796ab79f6edbe0c5d6f8393224cc0069358e0a3b4d35d89364ec3dff6ddcc49 module/approval-reference-runtime 0426247e235b793bf13307439a2499ccc9359dab45c5fe05f9a2541240700651 module/approval-reply-runtime -a5e9215460bc99fb6e6cdd4bc67eb6466a5e99f28279e13d1769599c18dfe0b3 module/approval-runtime -6c81b9122ab0a5c3190700c0a234047274a8d48edddcc8f26d3859b886696068 module/async-lock-runtime +b8cfc79901397a7b6ec8b3df9ecccbc36b125ebda23be794deb1e9109acff628 module/approval-runtime ad60ccc4fe9084d47f0477e02d9296bacad32f26d7456e2a84be8d25a53a25c2 module/boolean-param -333a906d4ad9d3e89102ab7eb9059a7f9c7277328041223ee3b0713442f56a29 module/browser-config -c117222faacb1c68bf6df6594fbf47c3ea0240fd1403f913ecb72e08730d32cf module/bundled-channel-config-schema -b6e53fb9c69840d71785325cd9a244809ffd6d3f0dd08ed0cccf816dd993f210 module/channel-actions -fcfd76dbfe818e8e8574a425f340fee935acf53d4484e961f54c3ec760fb15fe module/channel-activity-runtime -24c53c9cefacd8c1bec2aa91ba2b01e606f8b8477bc5cb2f99567225a4dcf67c module/channel-config-helpers +6e3b8ccee738f8eddde5d813b6e204d6f7c8541fda4cb279a3be3ead27f47e7d module/channel-actions +00d326fdff396dfcc657d1fd16a4015b81d60ae8bf7dad09ac1010b376814de6 module/channel-config-helpers 16dc9d32e8ca3ef78fc63e0fc4b20e6b943633e9572de1a49d24a880b6ffc66c module/channel-config-primitives 5da2caccf30780a4cf86ac710e2ca42cc9576cb1c49291c0929889fec0cc2257 module/channel-config-schema -e6c20ca52397a90297c49928e9b8d98a48059ed107d21f8cec6a7ec9c9e2d1ab module/channel-config-schema-legacy -8740f7cc786a380043e41aaf710ac47931b3587c122bb152d5f0231f98fd5351 module/channel-config-writes 2dd98659d9600e755f09ef00dd91c36692b8562ed3700461937e94f6cd1e640a module/channel-contract -3db1a968e5b8aa97623a48a9ee76393d578d4bb90634fa59f499a2fd87b9da72 module/channel-core +a6bc51efd3ce23e65432c9a6790670e2a0acb96ae7263fedbaed52ea353228be module/channel-core fbf353eb38ae68d8ded3f2a60b432c7bb2c245d2ec7e7c9f53c6da19a0db0938 module/channel-entry-contract -264a002a036aa66f87008971130c0264b15e62b00a35f473149aa846e3bcddad module/channel-envelope 982f29a18e07228e3da82cae67d06ff38249592a29c2fd28f01f0d2016ff80d9 module/channel-feedback -19d42de92c4f27a6d785638d6118a2e1302f0cb64cc71a12d6c1875e1e9e1e9a module/channel-inbound +31b33966c8b55f5055066182cf05b3fc68390c26026ce09fe9fb9e176f794925 module/channel-inbound 2940039d5ebdb16c1d745914390db01c0f5f1e9cff33f95ea36c9e77d97c2bae module/channel-inbound-debounce -c84239139c3ce21cc8ca7c01ae9f2a653197afdc7be95a7598616e112e743f4f module/channel-inbound-roots 91425ad75ddb58a3796f7bc88570cb2c81b3db0dd6521a7ce0e55f9148880a47 module/channel-ingress-runtime c97dd36cdf8f83c2893c33e9430a93cd131a03d855725783ca5b545de0cf84f8 module/channel-lifecycle -25230ecd4db8a49f94e29fffce985d40017227a778b1dd59b9c426b9ca627194 module/channel-location 159d034b431d113f3a6dc41ec0bcadba2d6664051f158330b0e3dd3da8b5d42f module/channel-logging -a693996a92b387ec26146daa7ba73b6b026cd8fc5e6d72874a6856d9c38b3f53 module/channel-mention-gating 5eccd03e85c032753e0d8931dace4cd952b83e4975d0acae272849fdabfbfe90 module/channel-message -1eeb53fdc4f526945c2cad5783e3b3184491d14c5286a5cca8145b5c97345e1d module/channel-message-runtime e2b4d1923a19b927e912622576d43d20a6e188c124a7bc588fe567cd3c1c924e module/channel-outbound 50c61de5d522abadcae79841335aeac25e3791e6dbb0cfc3d64b2720a8c514ce module/channel-pairing -e3f8f0d0aaa3ea3a773ce2011d7af30f18b40c27cbbad0df2d53cd582a56b631 module/channel-pairing-paths ee4292b069d4d48cce4fc2dc26df5b5c87eb1fa4769f1f6be9a10c3e1221e1a9 module/channel-plugin-common bb82ca1819308189c4ba7b1e83e0c39fb0fb8b5a7081ee874480d64426778454 module/channel-policy -9c8c877ab0c28ac2d9a84ad33a60e09eecfd68ce0080989ac033df14a8e9bff5 module/channel-reply-options-runtime cc0a77137b304b27a313791aa30e43efb4acc254da7590bd47493d81e7104fba module/channel-reply-pipeline -7521b7b935944c2ca5fdd8171ba921f134b68e6f29e2c9409e4e6290ce667de1 module/channel-route -a676d58af7bc732dbee41157d4e5c9efba4d429ae6502224de0d8ee2d4c23220 module/channel-runtime bba5540be7cf9613a163663decdb2affe2af9bbd3ad7914989ab186f9c2abec1 module/channel-runtime-context 17cec26bc71fc43a066049ef63f95bf29737113c26ab13689ceff602b9aa11d6 module/channel-secret-basic-runtime 0ceb4378709eb2d92a62a275f87fa04e18f77df8f942a9a0acef81019ebc1e24 module/channel-secret-runtime -96e38ca99edb7d39839a7675939d5b8c199f8625502cae15e66b4bc7885922a7 module/channel-secret-tts-runtime 7c90157a95bc0523fc66b1f78ce140f7ec7dbf809dfa3a480244efc01f972754 module/channel-send-result e705b97daac4fbd7f6aeb2e515b1cdaa1db342684644eaef8197767b2595826e module/channel-setup 47719805c7a1d2623dcecafad50abb6cc674262b250e1f0d7020a79ad41cfd42 module/channel-status a4918b82b795f086ae71acb43e2b725fda01f6c8bf2333c155a52581bd4cf8aa module/channel-streaming -dce2e08fdc6794298b93d8ace5dbcbf9dc9981ed43e41b9e894c6c9f656501c6 module/channel-targets -a8afdbd6b36b7e94b64ada56a579a5be72179e9b5e6d2b67542f084f5f7a29c4 module/chat-channel-ids -f687dbadcf1c63d09c03c09d1999bb37e8d49593a311b1c0fa42bcc035477146 module/cli-backend -1ed3823c895332a492eccac1bbf7214d63faa57e64de77f1e33004da7e394e8b module/cli-runtime -5f4c67188a022b1c9ad57799df82d2cef6e5744f3ddc29698de4e8e70a30f143 module/codex-mcp-projection c89ec1b194b76f67a6f4dd108dccf460da6065646cba31374c8aa748f23a39e4 module/collection-runtime -ff4173b17c0140965b08ddf93d4b32c47cd50b65a52ff9ac43db2921658d3a30 module/command-auth +391e6f0c77da2e17a058fc5aea87fd193830b2a14e12fae77c7f63b0226f0bb5 module/command-auth 7dede491d22f7226d955210befca09b32e955c59436c27500f0c7d1971ba07d5 module/command-auth-native fa2df02bede6ed8843e5c2bd605c6ea5cd20313c305593fbd371dba6f1b931c3 module/command-detection -a224ce0381a32ed7a9d2261669c444f7f46701c15bf2f6121e9b057d9910da78 module/command-gating 28a0cae8dee664aef14b1a64c86a46d0fb5637cf9f671790d4fc9ba205e42820 module/command-primitives-runtime eb4c757fe0086c1dbfa4c3f3caf3dcff0d3cab3924c608237f08f740a6ee5f59 module/command-status -10f073c8c7c7384843cb89548e01cf66241e23ebabea01ccc1f9ac7e774f563e module/command-status-runtime -d340686cf814326b5a554a32cc5ca324a9e1254a933c4d9d8d1ada5ac7109a10 module/command-surface -e26e0f75b43c5bbadd34401b21d8c76406ca0b87cc997be5abd100462d318f1a module/compat -2cf2f7732af5491a14466fc984f833ddce2b0f6a20b1c82796c3ee856e2966ce module/concurrency-runtime 235a9e4d983042c3db156efcab0e333984cbbf13ebdfcc44a3a5ab40cf3edb4f module/config-contracts 20f3f8042de53e4eee61b64de9102c8c202b9299e6a29235647a4729f70145f2 module/config-mutation -316949815affe623ac63951a5db580527f02663576dffa084f02768f612c0c1c module/config-runtime -a8ce8d7a8df9d427956a3d6f2daf00b0234be473e8cec2d01b84f34c6d7c78bb module/config-schema -b317e9810e57d07ddd720d68c1ce93172e5bc331d9c095a332edc52f565d5ce4 module/config-types -42d15153981cfe3adc1d5f91621434c56f07a9bd48c15ce34742f72dd040c142 module/context-visibility-runtime -03636897fb99cb73e4d8620c8a0e0d72b4d52fc32bf94f525af2aa88c489c6c2 module/conversation-binding-runtime +0c0bb80f81d0c769f343e3be95f01b3d734077c12e831367440952052aafbeb7 module/config-runtime c1ea9510dfda047609a99d5d2cd1f1560f5d469a36e6b695766213d695c25b0f module/conversation-runtime -d4ade9c9530585638e57602fcbe3ba1caa21a8d1ce88d87019d2d7048e3988b4 module/core -93b61bc7893ed37047c97c10360588c0acf5df72cf49c54678ce68ed8d07f65c module/cron-store-runtime -e6fc74e18435cd4639801d9bd1f224154d1cd9405cdb3ba91a24b67c0792e9d2 module/dangerous-name-runtime +d9267aacc65aeebf0046d4eef691e5a510764063e6c7942b1d6bc7a282ca67a1 module/core 4af19d59c2f18674e7d7f7dc1b358b644dc707e6bd601dc47168bd9e4a669940 module/dedupe-runtime -feb743a5f25297abc7bb47d22ef060e0ed54b0cff69ac50885681dfade970f8e module/delivery-queue-runtime f70c93d28053ca2e8353e45e6515ce7acef188097c6117d1545965d0699c8004 module/device-bootstrap 6215d3af5923bf5a616d73062534968b69f448e3e30adc64ae9caebdd1a46d71 module/diagnostic-runtime -9885951a437128dd3ebea5a54dcd59e6e9f4a1ba78b166c2c4aa0a286f108fe5 module/direct-dm -9cdf7fbc6551e0373d6ab007128a266b200b5c8916fc4f73ffa77192cb0c3e9b module/direct-dm-access -371ee1fd78810526745c93c24c4b85562c981676adcc33aaa0c627f5d2d45810 module/direct-dm-guard-policy -91278c800e0f87d7111f226e1cfcb6f29552936fec7215b3b69be4da7e2ee8fb module/directory-config-runtime ea81ef06956c1bc0853fa00afbbc2b5a4019116aaf8a436e1b27d06f7a2c9e88 module/directory-runtime -7e953d227d71e4e07c412b21f6db0604279cb6360eb6e025aceb79576212afd0 module/discord -f41f9b34ab771c894293453bcdf072860c7cd509dd4e0172156634a816b8d727 module/document-extractor -3ac20ebba52de5a2f18807c0c8ade6e61f59e260a35fc1d077c9dedb9960dabd module/embedding-providers +c7c9119cd43e2ed2eed4fe8bd57a6b75d2ee9a1f7926c432634c49b54ff7f9a6 module/discord 46c05a90b66032d1d7ad08445840a4bf81aa2bd325348f87710ad4538daf38f6 module/error-runtime -64060c2042b1cb587c6180b5fc09b3be29f0defee9770e1ae46592d5666e8362 module/exec-approvals-runtime -8a60bf4ab024c45a6ebb45a88ddd346c75325a579b8e3abea0efd0a2d46bc88f module/expect-runtime b013053a61e7d9be3d0c683c02baf57fa7e4393ec54e0df6a46ab0f2fe2348fd module/extension-shared -733faa5d260108da26d39aa0ada4105085719b26ff19f84b89b7cb9980e948f7 module/fetch-runtime -e2d4a336857b4bdc98137162d16a683d8aa39b46028b74deb731b9f939efc62b module/file-access-runtime -d13708bc224297df10a7f91e244f87e822fef9d71e2ca799442a49e42b6868c5 module/file-lock ceacad83db01c66e7be6aa21a291597020f13f737b697690eae7d47098e6499a module/gateway-method-runtime 1b8eea4bad785031864822f0f07dc03cbe8670e76e0c3ee8fa51c7a2ee5c5e15 module/gateway-runtime -4611de9835515711d641f0f3e48d3c4e528afc343d5e5934fe3ed62072955511 module/global-singleton 6062869202e3d7fdaa1f6801d6c1df7bf7406b8118cc4c7167ed409f5257b05c module/group-access -5e672df8c679d90e2e61b30d08cc9de8f1a1cac439e7020869b088ee9ab4e5ce module/group-activation 412aa303344407c046ee26c83306db0f8a6cccbdd6e7835437711d4dd8d26522 module/health -e1b72e4c2d926d39d216df80775e0a5f44705d04f5040c01fe1043ad7a2c2fe4 module/heartbeat-runtime 182dc685f2103ff66c1a4839a48f4f40d2eeb0070cd74e449b47aacc4e6f1c22 module/hook-runtime -580b0f80fe6d6fe1700d993a8a07e5f7a718fe9128e7767dcacf6ad14bbc3f8f module/host-runtime -1e1dd2480c0fb57a46c6f7a88b51071b44e411b02cbd9051943e00513526e7be module/html-entity-runtime -319df2c4a8637db38c9f115f9fe3d9b8ecbac6520c5e9c44254f2c00d5c9842e module/image-generation -284bda4aca2e435885ec10f1847bce57695168d1416b418e0326251125a656c8 module/image-generation-core -21a233e9008f0701df1efbefe521a9ced45c61da6e297e788bdd86895d793128 module/image-generation-runtime f6e3c44e7d1090a97aca554a3c247219b8de78b3cb4399cac5efde8a0a6c1156 module/inbound-envelope 67142e8e63ea860a03b0cbb5660f63405a83ec575f2ef7a96f9b87f1be362808 module/inbound-reply-dispatch -c1826ee55763f9c1f86e873cda221bdf32122fce5f5a5a46c384521a8ec8bf6c module/infra-runtime +841de925321fbc69c8dcc1beed2468dfcd5f28cc38a8aad6fcde20d85158a985 module/infra-runtime 2e717cccb3db127aed0287d4ea14c41a8e64e46d60c728638153e31e2fb0d296 module/ingress-effect-once -3d0431e2575abf67c7fd9e9763b08b7a46018e01c290ffea8d4af9874d3c3fc4 module/inline-image-data-url-runtime 74fee62a94618d830a5282a7816a73b85570b2e4ce17b90078078734510511d7 module/interactive-runtime -9353103e334009222e5ae4fe4a2992a1d00552735013a69f93f17f74f14eb9ee module/json-schema-runtime 9dd66baf2def46386ad4706380e57f9068fa9bb3878be2d9ba961ec2f46d3d87 module/json-store -ee34dd840075bd687624ead228d3aa906fc52965e1708a5a335bb3ad3b671949 module/json-unsafe-integers -8f37bca66178f4d77303fdd3ded0d445c9358b4f094c07a2f8e32b1721e953e4 module/keyed-async-queue d8ca27a737f235e09f1a9f8cd4b82ac0cde96f10c662347b6ac82e27b70b4a40 module/lazy-runtime -bedaf7b615a4acc66ca4897f2d99da883df638cc427ed3963e974b69bf8b0a32 module/llm -6ae9b0f1f55ec50fa3e0ebc02052bfd04eca5f34c4a936692634b36a4cfa2035 module/lmstudio -dd166527916dc5b9694512e3526e774da0b2427ba7fc8316d2fd799c06238e80 module/lmstudio-runtime fd00374a91ab5b393152bbbaf90010f9140cf15827bde360d0c6c8724ed34e2e module/logging-core -d0d01050ec5c0401fca65e482a183643b40a53ebc95b2a0fa4982e5bba6d93da module/markdown-table-runtime 7670808d8c6227a52b2cddb72121aed9767313dd674f294a1f426dc185c13046 module/matrix -08c4cb46f218c303356c203dac30722cc6c609b6b34604a8846259f684bed2bf module/mattermost -149b66a9e1ce771567495164a7c1680759a4d589f0af87caa72c58cc90bbf163 module/media-generation-runtime -28f843eb592e3f376713f2743acf26087885cd30eba666af98eaea63fd15ecae module/media-generation-runtime-shared a5f59c9acbcaa3f82247bf806eb5ba08032373fb853719f0ec9457690f16fc70 module/media-mime 7b30827ec2f616ae9de0c1d81348a2112a41e9b4a8aeb8f072cfb1480d2eb864 module/media-runtime 7a5a1de743fff9d5a86515b501ffd88e7fa49970c9f46a5153816e2ebaef7bb0 module/media-store c5e3eb1a584f4b8126d9d6c177a840ec9103671e8d1242634ee67db9b5b9e573 module/media-understanding c0ffaed532578cf33493992e1ff806b2268b8e3774a92edbaede5cf5bda162a6 module/media-understanding-runtime 075b7a2783cf22c3210cfce630b815c2e3b7e85c1f5606f0dde4730d47242365 module/meeting-runtime -bdcdb7db74523c557b6bcaa98a47210ad90f16bc97f9143227d03108c7fd8c23 module/memory-core -6ae3c62fd51b36439cd3f22286f2e52c6bb27bc85de648650b805c3b7603a487 module/memory-core-engine-runtime -2bc363010e81c6990c30fd6743e3ffa37320ec39b91abfc222302dc812d8bd33 module/memory-core-host-embedding-registry -ae67c256150b14f1a58738dce0a703071035b86ec26fc344ab03e942253aa72c module/memory-core-host-engine-embeddings 4205ab767b79e740c7c7137079b19b315c85f7f8c748592840f00bbf70a71709 module/memory-core-host-engine-foundation -cb8dcef652580ca82242d6e9506eda094006c85848f9b16b8b1c1aadff1706a8 module/memory-core-host-engine-qmd -bbc06f2bb23d9e4a3445744711f691de9f902270e61c9740fd746f222d10fccb module/memory-core-host-engine-storage -39e86819441281365dedb3d7d59979672bb90196f49206edb50b879bd747c9f0 module/memory-core-host-events -fe87e7d794bbbdaed3c834b218a787e98c0e69b26b0fac582b0668b8effab9b4 module/memory-core-host-multimodal -746e155bb06b398749a6a1bfdc1b435b1ec1941dca3854554a912577731218dd module/memory-core-host-query -e2377479799d52d8f67dcccf8f499f28a63948b0c03fa6043fc67e476a22ec0d module/memory-core-host-runtime-cli -813b7f8b864e3e0497970f03a543801ff3f69cda2f80aecb26e947ef0e22534e module/memory-core-host-runtime-core -f8eb835f0769eba8324033141ecddb9f45bdb3d3241d2a5959fe8426ba6a2280 module/memory-core-host-runtime-files -ebbf937b2258487f773b8a6fbd6c648feea96399cae5fbea18ec7d29e027523d module/memory-core-host-secret -16bdd82751898ffa98157fd27c86fa0eb9ea415fb8ed703eb4e9b170f0a9501b module/memory-core-host-status 646773d8282a2ac6a89101685c406200935ddb3459a2830e820da132fa433b3c module/memory-host-core -f7bde3a0231ac75c128d6ae367d5020de886715fe9edbb2faaf673c6bd168398 module/memory-host-events -059856bc3671ccba7a2191f47c0295e0a91a374f7048cc2bc22784b389d15182 module/memory-host-files -ba07f497aa5469915ebb5b6787148ff1f0f267d7e48c48f0c95d32b7dda8d91a module/memory-host-markdown -896d315fb78af09effd0af467e2b6e84331272b0123d4132e5b1f2254e80108e module/memory-host-search -fa72e6a00014824db9a40c25b404b548feb125c500005ca3b9670c79ffb96fb1 module/memory-host-status -2343c1092d2d6dbfe215052711d273c8d39cfc8a67056d80831ca157958d51d1 module/message-tool-delivery-hints 87b7a3206346c0d4b294fb3a2395cbaabc3e73ff8b1b9ea925bc3aade3e52687 module/messaging-targets -fd79467013bf189fc6cdd5698a3b970fd370542e2f4aaed70a1f3cac9a8dad4a module/migration -90c0578b615d0938a9c1d4bf5cc2e169813176620b57a1ebc3949b8aaeeeb8ff module/migration-runtime 09f842a2787b87117d88ba6545aa0441ff8afdb85e2e66ae4d1838ebecb16148 module/model-session-runtime 44655a08ce111a036c837b8d2e796cf0036941379ab317e5764c6c01e73b39f3 module/models-provider-runtime -4096b2c64b074c651f9e5b0d0e2310082f1002dd5793c6b8a81c268467cef438 module/music-generation -f1167c6fc033405b98c88e55f20e80061dc932acf627b42526e2bc345544145d module/music-generation-core 55a7e1e57345726b2b54a4c71c66744d5559dd40a164ca552808b689306f53f8 module/native-command-config-runtime 1892da4151de02907448430a4661b8578295bb91638fa6c8d940d858d902876f module/native-command-registry -0532bafd9f77262d6e41b95d42e6b841383a790ea1710ba9e65eccf270fad41c module/node-host -ebd0c2fd5ac9a42d9922deed139ec47184d71e32d7ab5141d86490a3c9d3b114 module/number-runtime -ac2114469f321477a1e11133ea629fa672afe1aa9118fd165054009987aec2bb module/outbound-media -1d3fb77912c0fba61487b0ae0c1c6e6e513130808d808335582b3f48136e12d5 module/outbound-runtime -4a7fb40ea1a45e5727ac2adc8ba64204fa1beb4caeec4de1f9ebd5b56bedff5e module/outbound-send-deps -50c4a8ebc6777f422e39a708d01a34b15f42264709da8bd44e1802246d3bbbcd module/pair-loop-guard-runtime 9470d8e08f7e1d2fe144a81da1b4a331b31a0c74d0c9a5f7cfaa15814e953ea2 module/param-readers 8d8c0c4ebfc6e0c6125df3ae64ec66c10cd797a325de5ea7869fcd84068ab290 module/persistent-dedupe 987648ebe317cc4d6c0505a52d344b12ce9c3a8261a5bd969cee3423c0c53529 module/plugin-config-runtime -4f40506eda3a3a54156ec63e43c946cfc11197cf5ef7639c4c1ee9b549ea821d module/plugin-entry +a5e4304b8b5878d5b7b7732939e1c91d009b4d3e9ee1c41d2f6ccde81c10eb99 module/plugin-entry b41a69c03c3a671a120963bebec65f379261220b72a4636242223d8bb15b830c module/plugin-runtime -109f2b3b0984552e620adfb33c2d5dea6f70b4b440677e77d4fd89679e286683 module/plugin-state-runtime -1a5be86c0c661469b3ba07bb1c3c0db4d893cbe3042040c298a412bfffc10b5b module/poll-runtime -c305e43d9cc43cd699759f36212527a1fc1f1c8d26cfa96425b0dae4a98e91b2 module/process-runtime -f9717249bb66c5fff4fce145c260c5b8ac1d6063a03de6025afe19f90efd1ab6 module/provider-auth -a5d3fbf94ef6b3186861c9db8510e749e6caf95a16f87a439c395fd8466ef45d module/provider-auth-api-key -fbe62044a65a728bc6a89fabd80a2ecbc4f8199b4ca6ab15282035db0f76b0b3 module/provider-auth-login -b20eeddd6af4ad509dc0802591180843873a9c6b337edab142a63869d270ebc1 module/provider-auth-login-flow-runtime -b32f041136217c510e559e5fcf7ed4361cf51ebfad0fa15799d69fe539780feb module/provider-auth-result -3381bf9416f0fad6509b43cdf24cf32547f9fa18632ddb5755e3dd7212bd65a3 module/provider-auth-runtime -7a0adc27e12f90f5ab95f36d75fb208029ffc83c29af167ea3fa992273561023 module/provider-catalog-live-runtime +83df49f1fcb2fc4ac1bd2406a87a10be71bbcdb0967f61e9a6ccaa23c534149f module/provider-auth 71bebeac51e701cd7c8e63d22754b9bcbd82b024aced303aa055d229781129d7 module/provider-catalog-runtime -ee5184ab251bcbe49a4cb9c845eee31975733bce1dc2300854cff14e99cf6db7 module/provider-catalog-shared -ca5d408937bc0ff92021673320aeba6ae6fce705711b251f15797887b8f34d3a module/provider-entry -52085bfaa917f4fd262508a93af923e5e834901b5f0f017c93b02a0798073344 module/provider-env-vars -3b311719eae8dbb061ac2e79f0a58f8540f7a416ec281f3ee0c1cc3cfa6b401f module/provider-http -47564e55d5ae9174fc402d6a1ef7120a799a92bb3625106327b642d4d9eb3bd9 module/provider-model-shared -966830793fccc5e5a7e6ae2f7df84f5f39120ac9a6af8c10be1e070eaf257f32 module/provider-model-types -f1452aec01b1bc1d8764b666131b6237bce5b78477ce1137d622c4aa0b691a8e module/provider-oauth-runtime -c543747f32aebde42508e31606db325739f0c54fcc7cf683f68ac941c835f737 module/provider-onboard -3e4187704f3c12b3a058864ef474036f63285dabe687c70461713f869aafbd9d module/provider-selection-runtime -096f53f25cde2c3428b6ff042f1687c5a7c1b161a375ba97aefcdf2f1c38887f module/provider-setup -413af0d4597c1bab45e2f1a260b520445a25b9d11abd46a98fdfb74c60ddd601 module/provider-stream -ea8bd63655e983dacfd306fe77d0793097166228a2dabd9b7fc8e33252eb359f module/provider-stream-family -2285e2c23d64af94ff6b9bd6108e854343f1ec55eca6212fc4fd22138253363b module/provider-stream-shared -5a907292055c941ec7420163ea50e8bc90ead816b3c5867d94a23024d3b579e8 module/provider-tools -8236935f56423a26aeb7219173c0c8c8c7aacb4a13d0d851f05bdf2d35790cd4 module/provider-transport-runtime -009a594b19aa9f0d0c0c28f54968f807cad4b3ee40ec13c685367116d63aafdc module/provider-usage -b6c43bdb895139e6bbdb30f861b7e0e66bd2e8960e4129cc2bdb562c575c5f2f module/provider-web-fetch -8ef7d8c1cef0b23b73b3ec1e8dc8974a4b98ea622a3ad2659ce38e9aec03d499 module/provider-web-fetch-contract -fdcde43943f9bbd4dba4e4b45b396e380708dd3a7314bef141d13a6c8a1fc19e module/provider-web-search -fffda485ad0492168b0b58671a5fe232de028c64cd1c0e36ab710ba7d58bf9d3 module/provider-web-search-config-contract -17c2bf31435cc29125e2115142c780447cefc967fd5ca26f363668f01008dc66 module/provider-web-search-contract -69e205f87efbee176778ef5810c104dc0528e2545cafcf31b6cc8ea360e06020 module/provider-zai-endpoint 56151035047a69e6163d5578023d00f51a2413b777f3784af88e06261c039345 module/proxy-capture -08f9f6abbe7f01fab7f96d611058f0de55996026a0e90cfd014fd2fdbe62ba0a module/qa-runner-runtime aa2a56b4448c8ebdec9d06aac95d809995f533093d42fa32cd75e1d852967245 module/question-gateway-runtime -5e6ac90d11f2f6ed764f5c53ef98110551ff4beaa0d5dcc343a3e7d97e41844d module/realtime-bootstrap-context -6f551c1da49e6652f4af775798f45d3f3d5949c884c2e1967fc6254d34e9867e module/realtime-transcription -4c42bf622e25f5896194512aee7698ae8fbf8a2844657aea7a54bef767602368 module/realtime-voice 2e09c3181e79e157ed5366b144d116ef8cc06023256ace3fa59b35c43cab513a module/reply-chunking -b673d50862d0c5fd00723252a541ab3baeac196b75f689f56439fdb63a68fda0 module/reply-dedupe bd2355e94248d21c252148085e5afa5db9a2f3f48f665a8b3e0fcf77326d9b6c module/reply-dispatch-runtime b215c812a6be8565a25439f52b528fb951f922d2e37900c89a32decddc643ee8 module/reply-history f394fe4d5a7ed9e4d574063ae44e8d6af85c9a0e7d8b329f750ca16b0664325f module/reply-payload -82fcb700b918f0776008e198c72c087ce8c387a9bc982e1e255e67fc5b43cc00 module/reply-reference 4e886f327f912688c338a1eccb208298987613f81325d4f999852b6925487eb2 module/reply-runtime -132c734e81f821ba2dfeac1266d4c946d1d89aa834231eb3312087f458360294 module/request-url -04c04da4ee8dd042787076c759e650941c458d16e725f0d3f9997ff1bfccb213 module/response-limit-runtime -dba5987236821b24a3cfe2767841b99cb4ecfd9c567abc6f9cc2cd675ce503ca module/retry-runtime f7cf5c4b7be10345ac34e5f88990985f418ab855d4c0f25042b7b8cc933176f2 module/routing ff6cca86f54f94f238205f5b122af36666314e0a380f3ec7f0ccb9ed9208df31 module/run-command -af338ca9fbd0dc5701ad085e17937471953241369a0c961f8894ca7df467b72d module/runtime +53b0295cec105696a1664c5c7f5576a7b55d197eb95dcd9185486f010bd53750 module/runtime fcf103d18c1d64ffa9719f1d5a1ca349df3a6125d00c9d54a1b76c195ba8691b module/runtime-config-snapshot -1392e45b8e8ef50b72a9d012a6abe5a73d00b74b6eccc53c817096dfe7a8f591 module/runtime-doctor b6b8edc50ecab8386c9acd8f374a207212b5a99c8f518538bbcf0c458dda3881 module/runtime-env -841bee5465fd523116f8739eb36f79fd9efc8139dff9d98d32e35002c1614fe7 module/runtime-fetch 1b223b31f6f78d9eca4a07b92802e59684f24d7a1d040b83856ad165d0dd70fd module/runtime-group-policy -99ca5432af104f82cea78a632d3fd14c924a859f268063b48bfdf3f080bb1c0c module/runtime-logger -edaa91de9edc92d684c5b5410a86c9a6c072877a43ea699965807c565e2d6a1f module/runtime-secret-resolution 0d8f2c5f3a3325d7d190d2c395835f58ac57f1bea9b6fa516c6942b96d9bf605 module/runtime-store -e9c4398b04d04fead0e46ec618589d8f597a0b6087805ab132138e941b76e190 module/sandbox -79bba48c5fcb578a86149c99c26745ac00cb1b5c2be7d48e982e826b37e2ea26 module/secret-file-runtime 9e34f73592f9df1c01f2df10b6aecbbb2494938e8cc1a6198ef4d56eacedc7c1 module/secret-input 57dcb1462d4c4f9a98d934c4ca975b163d704758af9821a64001ff3ac05637c3 module/secret-input-runtime -3718c15f255d6f315f863a100ae28a578277f07f45034d8403e254e7cb90be49 module/secret-provider-integration 17a6a199714ba8308e62928c0491bcf9fd214c923c3879aa99a047a30138253d module/secret-ref-runtime -7af94d4b67b9d39a80b154ad84987bf4d63b008a63c3cc807baea5b36fbd4138 module/secure-random-runtime 596a315d426121c9620b314e3a9a7f523840b46e007d94d0d5e83cdedf789d15 module/security-runtime -50beebb77e461deaccdbff038f6a461dff1d5773426322dc6d7991f6e05a7c37 module/self-hosted-provider-setup -250476d121ffa4ed67d497c59d9c7bb1886973e92ebed39325a02609217bade0 module/session-binding-runtime -ed35a448c8f7650acb34739a591334267371f1ffe352505fc2808330d78580bd module/session-catalog 31b785e74f1f8f56241b7756ef6a5d86199c5ce177cbb1c234a261866972f270 module/session-discussion -f07839f5b8929a179857a0b4b89f8448864078cd5972dd53f9a30e50bf55408d module/session-key-runtime f59099aa2d536246d4b1297f01bcea66796351a9259debdd45909afeb7a42d86 module/session-store-runtime -b1d0a76337122cb9dbb0c89fbb8bfacc1a88ce689270d3d684019c9a03ce669a module/session-transcript-hit -04c19558a5f27380ea13cadee1e3238729788c18a919fb078b24d2d67d006ecf module/session-transcript-runtime -a8fb87c7c39ec6e874926a311d2306b49843767ee5659e006024c59af822466e module/session-visibility 695971d31b3e16f0bf9b643acc30df312fe94b3c0dfb27a2e6156c8a00b5e261 module/setup -575fe05081bf9edd1c6d886143e4835c218fb8d0641ce148a4a69d3cefcea2ef module/setup-adapter-runtime 9129c17df1523903f34beffd348ce177fcaa2983e155a8c854a50cf1bd7e99c4 module/setup-runtime cd431f6ba8327b81438b7a63b1963120f200f5abd145fb6aa7c5c561339cb0b1 module/setup-tools -f2095165f836537aa6c145aecfff32543c9cc7b3935d33d107869b5f975475ee module/simple-completion-runtime 18e384ec43d9eaee52c8e286e127bda2048370e2337964a754d94b236724ca9e module/skill-commands-runtime -7eaea43619ee15e8b0d2d17c81c63b057576b251fecebcb5034d4e06e5940aea module/skills-runtime -6639d26ccc022ee5c55e23aa52071e30dbbddb17e7c4ecf62b152358380e087f module/speech -5c228af0457bf14b2833c2d0994c98212f6a7451abec7d5fe5cad0632115bb84 module/speech-core ae469f32799380e6b045abaefefee6eb3f00d714ffbf36b6eeef5025dc529472 module/speech-settings -59f8e58aea2ba7870b0bb758d597360241901b8c5165b695df1f4a62912e41d9 module/sqlite-runtime -e1379adab6398cdeda7671a4e6cc268508d21270815b53fef793249698a8d601 module/ssrf-dispatcher 9e521fe9073dfd1a6a6855f909fa6befe8613e18403f0a65faaba973a8b630c1 module/ssrf-policy c564f2f5d1e6cf7a9fb1d3f1a7fa3ccb42c61306a8497524fcab5d4d785d108d module/ssrf-runtime ff35f9f74d35d37a2eb6126b57f3dc5a4d580b6222fe3a34c9368779ad32eab7 module/state-paths c5ff317bb7957d0870cb806e7989c1d48877e3bed583c173ce2f8e9cfe57ee36 module/status-helpers 537047854c21ad20ea0572f8019503cbda3bbafece8194a28c30b0639bde2fde module/string-coerce-runtime -eb9a25321eaa2bbea1721f7d4b8b218bed398379494e09a21a621f264435b39a module/string-normalization-runtime -32f031eb75c887b24b8eaa693cd0aa1a4648dca85eab7bfdfb3d08136861c274 module/system-event-runtime -65361dc9a23578787c1ccf98f7df99e443574614e7ce95889f9a295725e12fae module/talk-config-runtime -9efd666b8c2cc8a9abf816751fd9420f3c048d158e48570cae8b7a4cbc52a83f module/target-resolver-runtime -ec6df323a2dbd1f837df25c0290289031bf60e0b0250f47172f3eaa316667c90 module/telegram-account -7729f9f201c08f114925da75ee86a5a8deb6677e5d1b5ee86bc60aacaa01f63b module/telegram-command-config +1b5b9a4532db991fce807ba736b550de27aa5f9de2e1f508a8f4f09cae76ea29 module/telegram-account 110944726884fca94f38c9c329b5950629438b9a719f4782c4beeade8bd67746 module/temp-path -a65f17db3d04c2ca1b34f9a4ebe8748952bbcb00318b741adbe3f227d2e2de20 module/text-autolink-runtime e24c49c1c7b35e8b4403d45eb76bb0f33ec2f23f714bda322e1363251737d6cd module/text-chunking 1df5a33be5dbf611e301c01c6b8bcf502c3101aae10cd14a79f1bbb58b116497 module/text-runtime -4bdc79e3b42814a30c3c84401f1f0e4620706092787a1f5d2b6c4148b202fe02 module/text-utility-runtime -dac9afb3833e237e3edcc9d687240caceb8288abd0c6b757dcc3c16a819eb029 module/thread-bindings-runtime -65c9b0b77c0c19140765ec282f9a6424f6ce8a55d0e318b30485d52fd41968e9 module/thread-bindings-session-runtime -360458567c29e4b577b7cd3ded81ed3fff6977221f4bd607662690f502a7f620 module/time-runtime -de34bdf5b05937a1dc6b7bd6a06a0372a8017702d5f5fb998b1f065fa46a5570 module/tool-payload a94c9ff59cc361b04f8731a47d722c4bc88cfabf942221369a8cb67bdcda7249 module/tool-plugin -c3c90ba2b012732413c5275d65a1c0ff07a937cd67d04d1acbd6207243544d43 module/tool-results 9d6ab352913a573b226e054e1dc8c6d088493aea9954950c65585923b5b6895a module/tool-send -5d02a59fe9a44a4aaaf027427238874d5ff883688b22b213e05c9f171edfbff5 module/transcripts -14ec8a05b813027a5afb9b92eab79995e0d61768545783081ff1515a84383b2c module/transport-ready-runtime -eb92e9bf85a08cf263e61b5b3497010b1de73f19bdcc5e826b0b9416ad053b90 module/tts-runtime -bf0ffa969b46486f52201900956ed266107f062b22e9be73e5b81356a32185f1 module/types -20946e039f15f52023735836f5d48c72a51c4ace80c019edcb0301571cc79101 module/video-generation -37c7c8e392583c905c4ad6546dfe4f76e52771ef0272b41a57fd4418d70f638e module/video-generation-core -70965c7827aabf85fccedfbba9b1050af476b6772cad2f8ed65e1bee470371a5 module/video-generation-runtime -c1688367f51f96507230782f9eb68b9ba2a4eb2b952a074b8f28e05ac57b36cb module/web-content-extractor 541df9dea799f25e83ea483d481ecebc5b91c016effab593c54d3efe3ee6517b module/web-media -2e59dcb46af00a4a1a1c9c240013c9445d1d938e29e7689db0c450561d49581d module/webhook-ingress -30a1d4dd8c540a1320b7775dc7dbe4464df8c33d0dbfe03f3fc9610b3221a322 module/webhook-path +c1fe90c8d833e82aa6a94885b4ec5cb54d0463ce5a3bee25ad24124ff8cc2d20 module/webhook-ingress 9f2739dfe035d9a053fcd678aa76be79980832495041b770c326ed973a979bee module/webhook-request-guards -8ea24940688b1bf82f0f46e3b9ead88afcd0bc39d2871722cd71a9b9311bd61a module/webhook-targets 1cc469eacda2818a116ab7b63847b9ddf86225e8d02e62fac48e618ba41a9852 module/widget-html -1a6449dbd48d2b9a60d5377a8c3b119df5f6aa2606ca5d61de1d04828125ac93 module/windows-spawn -76e3a7b6eb10921d5c18eeaa2eb22654b5412162eb85810109b333ba5b3e9d18 module/zalouser 9161b36ec0ab062ea41b363c894fcd672a7727f21cb726739f99f9c184fce69d module/zod diff --git a/docs/cli/plugins.md b/docs/cli/plugins.md index 97b0ae74df6b..60f3d18df1c2 100644 --- a/docs/cli/plugins.md +++ b/docs/cli/plugins.md @@ -405,7 +405,7 @@ For runtime hook debugging: Plugin install metadata is machine-managed state, not user config. Installs and updates write it to the shared SQLite state database under the active OpenClaw state directory. The `installed_plugin_index` row stores durable `installRecords` metadata, including records for broken or missing plugin manifests, plus a manifest-derived cold registry cache used by `openclaw plugins update`, uninstall, diagnostics, and the cold plugin registry. -When OpenClaw sees shipped legacy `plugins.installs` records in config, runtime reads treat them as compatibility input without rewriting `openclaw.json`. Explicit plugin writes and `openclaw doctor --fix` move those records into the plugin index and remove the config key when config writes are allowed; if either write fails, the config records are kept so the install metadata is not lost. +`plugins.installs` is a retired authored-config surface. Runtime and update commands read only the SQLite installed-plugin index. Run `openclaw doctor --fix` to import legacy config records into the index and remove the retired key before normal runtime use. ## Uninstall @@ -525,10 +525,6 @@ Use `plugins registry` to inspect whether the persisted registry is present, cur `openclaw doctor --fix` also repairs registry-adjacent managed npm drift: if an orphaned or recovered `@openclaw/*` package under a managed plugin npm project or the legacy flat managed npm root shadows a bundled plugin, doctor removes that stale package and rebuilds the registry so startup validates against the bundled manifest. Doctor also relinks the host `openclaw` package into managed npm plugins that declare `peerDependencies.openclaw`, so package-local runtime imports such as `openclaw/plugin-sdk/*` resolve after updates or npm repairs. - -`OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY=1` is a deprecated break-glass compatibility switch for registry read failures. Prefer `plugins registry --refresh` or `openclaw doctor --fix`; the env fallback is only for emergency startup recovery while the migration rolls out. - - ## Marketplace ```bash diff --git a/docs/concepts/agent-loop.md b/docs/concepts/agent-loop.md index ba67fb7ff88c..8dd552c4d683 100644 --- a/docs/concepts/agent-loop.md +++ b/docs/concepts/agent-loop.md @@ -64,7 +64,6 @@ These run inside the agent loop or gateway pipeline: | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `before_model_resolve` | Pre-session (no `messages`), to deterministically override provider/model before resolution. | | `before_prompt_build` | After session load (with `messages`), to inject `prependContext`, `systemPrompt`, `prependSystemContext`, or `appendSystemContext` before submission. Use `prependContext` for per-turn dynamic text and the system-context fields for stable guidance that belongs in system prompt space. | -| `before_agent_start` | Legacy compatibility hook that may run in either phase; prefer the explicit hooks above. | | `before_agent_reply` | After inline actions, before the LLM call. Lets a plugin claim the turn and return a synthetic reply or silence it entirely. | | `agent_end` | After completion, with the final message list and run metadata. | | `before_compaction` / `after_compaction` | Observe or annotate compaction cycles. | diff --git a/docs/concepts/message-lifecycle-refactor.md b/docs/concepts/message-lifecycle-refactor.md index 65065e30710b..a429acefb606 100644 --- a/docs/concepts/message-lifecycle-refactor.md +++ b/docs/concepts/message-lifecycle-refactor.md @@ -151,7 +151,7 @@ a restart. The refactor absorbed or deprecated: `reply-runtime`, `reply-dispatch-runtime`, `reply-reference`, `reply-chunking`, `reply-payload` helpers exposed as public API, `inbound-reply-dispatch`, `channel-reply-pipeline`, and most public uses -of `outbound-runtime`. `src/plugin-sdk/channel-message.ts` is now a +of the old outbound facade. `src/plugin-sdk/channel-message.ts` is now a `@deprecated` re-export barrel pointing at `channel-outbound` / `channel-inbound`; `channel.turn` runtime aliases were removed and the old `/plugins/sdk-channel-turn` doc page redirects to diff --git a/docs/docs_map.md b/docs/docs_map.md index c796c9092728..9a45f70212ea 100644 --- a/docs/docs_map.md +++ b/docs/docs_map.md @@ -5599,7 +5599,6 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - H2: Public capability model - H3: External compatibility stance - H3: Plugin shapes - - H3: Legacy hooks - H3: Compatibility signals - H2: Architecture overview - H3: Plugin metadata snapshot and lookup table @@ -7502,7 +7501,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - H2: Removed compatibility surfaces - H3: Process-global API-provider publication - H3: Private testing barrel - - H2: Active deprecations + - H2: Migration reference - H2: Talk and realtime voice migration - H2: Removal timeline - H2: Suppressing the warnings temporarily @@ -7579,8 +7578,8 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - Route: /plugins/sdk-subpaths - Headings: - H2: Plugin entry - - H3: Deprecated compatibility and test helpers - - H3: Reserved bundled plugin helper subpaths + - H3: Compatibility and private-local helpers + - H3: Bundled plugin helper subpaths - H2: Related ## plugins/sdk-testing.md diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index c0c63165cf04..154c7fb7b16c 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -251,7 +251,7 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and - `allow`: optional allowlist (only listed plugins load). `deny` wins. - `plugins.entries..apiKey`: plugin-level API key convenience field (when supported by the plugin). - `plugins.entries..env`: plugin-scoped env var map. -- `plugins.entries..hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. Applies to native plugin hooks and supported bundle-provided hook directories. +- `plugins.entries..hooks.allowPromptInjection`: when `false`, core blocks prompt-mutating hooks such as `before_prompt_build`. Applies to native plugin hooks and supported bundle-provided hook directories. - `plugins.entries..hooks.allowConversationAccess`: when `true`, trusted non-bundled plugins may read raw conversation content from typed hooks such as `llm_input`, `llm_output`, `before_model_resolve`, `before_agent_reply`, `before_agent_run`, `before_agent_finalize`, and `agent_end`. - `plugins.entries..subagent.allowModelOverride`: explicitly trust this plugin to request per-run `provider` and `model` overrides for background subagent runs. - `plugins.entries..subagent.allowedModels`: optional allowlist of canonical `provider/model` targets for trusted subagent overrides. Use `"*"` only when you intentionally want to allow any model. @@ -360,7 +360,7 @@ precedence when the same app is present in both paths. If `app/list` cannot be read, account-wide exposure fails closed. - `plugins.entries.firecrawl.config.webFetch`: Firecrawl web-fetch provider settings. - - `apiKey`: Optional Firecrawl API key for higher limits (accepts SecretRef). Falls back to `plugins.entries.firecrawl.config.webSearch.apiKey`, legacy `tools.web.fetch.firecrawl.apiKey`, or `FIRECRAWL_API_KEY` env var. + - `apiKey`: Optional Firecrawl API key for higher limits (accepts SecretRef). Falls back to `plugins.entries.firecrawl.config.webSearch.apiKey` or `FIRECRAWL_API_KEY` env var. - `baseUrl`: Firecrawl API base URL (default: `https://api.firecrawl.dev`; self-hosted overrides must target private/internal endpoints). - `onlyMainContent`: extract only the main content from pages (default: `true`). - `maxAgeMs`: maximum cache age in milliseconds (default: `172800000` / 2 days). diff --git a/docs/plugins/architecture-internals.md b/docs/plugins/architecture-internals.md index 02e4df32c585..1a91384ef49e 100644 --- a/docs/plugins/architecture-internals.md +++ b/docs/plugins/architecture-internals.md @@ -27,10 +27,6 @@ At startup, OpenClaw does roughly this: 7. call native `register(api)` hooks and collect registrations into the plugin registry 8. expose the registry to commands/runtime surfaces - -`activate` is a legacy alias for `register` — the loader resolves whichever is present (`def.register ?? def.activate`) and calls it at the same point. All bundled plugins use `register`; prefer `register` for new plugins. - - Safety gates run **before** runtime execution. Discovery blocks a candidate when: @@ -228,10 +224,9 @@ conversation, and it runs after core approval handling finishes. Provider plugins have three layers: - **Manifest metadata** for cheap pre-runtime lookup: - `setup.providers[].envVars`, deprecated compatibility `providerAuthEnvVars`, - `providerAuthAliases`, `providerAuthChoices`, and `channelEnvVars`. -- **Config-time hooks**: `catalog` (legacy `discovery`) plus - `applyConfigDefaults`. + `setup.providers[].envVars`, `providerAuthAliases`, `providerAuthChoices`, + and `channelConfigs`. +- **Config-time hooks**: `catalog` plus `applyConfigDefaults`. - **Runtime hooks**: 40+ optional hooks covering auth, model resolution, stream wrapping, thinking levels, replay policy, and usage endpoints. See [Hook order and usage](#hook-order-and-usage). @@ -242,9 +237,7 @@ behavior without needing a whole custom inference transport. Use manifest `setup.providers[].envVars` when the provider has env-based credentials that generic auth/status/model-picker paths should see without -loading plugin runtime. Deprecated `providerAuthEnvVars` is still read by the -compatibility adapter during the deprecation window, and non-bundled plugins -that use it receive a manifest diagnostic. Use manifest `providerAuthAliases` +loading plugin runtime. Use manifest `providerAuthAliases` when one provider id should reuse another provider id's env vars, auth profiles, config-backed auth, and API-key onboarding choice. Use manifest `providerAuthChoices` when onboarding/auth-choice CLI surfaces should know the @@ -253,9 +246,8 @@ loading provider runtime. Keep provider runtime `envVars` for operator-facing hints such as onboarding labels or OAuth client-id/client-secret setup vars. -Use manifest `channelEnvVars` when a channel has env-driven auth or setup that -generic shell-env fallback, config/status checks, or setup prompts should see -without loading channel runtime. +Describe env-driven channel setup and auth through the owning +`channelConfigs..schema` and setup descriptors. ### Hook order and usage @@ -571,7 +563,6 @@ Notes: schemas while OpenClaw owns the provider/runtime boundary. - Uses core media-understanding audio configuration (`tools.media.audio`) and provider fallback order. - Returns `{ text: undefined }` when no transcription output is produced (for example skipped/unsupported input). -- `api.runtime.stt.transcribeAudioFile(...)` remains as a compatibility alias. Plugins can also launch background subagent runs through `api.runtime.subagent`: @@ -686,12 +677,11 @@ Notes: Use narrow SDK subpaths instead of the monolithic `openclaw/plugin-sdk` root barrel when authoring new plugins. Core subpaths: -| Subpath | Purpose | -| ----------------------------------- | -------------------------------------------------- | -| `openclaw/plugin-sdk/plugin-entry` | Plugin registration primitives | -| `openclaw/plugin-sdk/channel-core` | Channel entry/build helpers | -| `openclaw/plugin-sdk/core` | Generic shared helpers and umbrella contract | -| `openclaw/plugin-sdk/config-schema` | Root `openclaw.json` Zod schema (`OpenClawSchema`) | +| Subpath | Purpose | +| ---------------------------------- | -------------------------------------------- | +| `openclaw/plugin-sdk/plugin-entry` | Plugin registration primitives | +| `openclaw/plugin-sdk/channel-core` | Channel entry/build helpers | +| `openclaw/plugin-sdk/core` | Generic shared helpers and umbrella contract | Channel plugins pick from a family of narrow seams — `channel-setup`, `setup-runtime`, `setup-tools`, `channel-pairing`, @@ -709,11 +699,10 @@ Runtime and config helpers live under matching focused `*-runtime` subpaths instead of the broad `config-runtime` compatibility barrel. -`openclaw/plugin-sdk/channel-runtime`, `openclaw/plugin-sdk/channel-lifecycle`, -small channel helper facades, `openclaw/plugin-sdk/outbound-runtime`, -`openclaw/plugin-sdk/outbound-send-deps`, `openclaw/plugin-sdk/config-runtime`, -and `openclaw/plugin-sdk/infra-runtime` are deprecated compatibility shims for -older plugins. New code should import narrower generic primitives instead. +`openclaw/plugin-sdk/channel-lifecycle`, small channel helper facades, +`openclaw/plugin-sdk/config-runtime`, and `openclaw/plugin-sdk/infra-runtime` +are deprecated compatibility shims for older plugins. New code should import +narrower generic primitives instead. Repo-internal entry points (per bundled plugin package root): @@ -1014,7 +1003,6 @@ Useful `openclaw.channel` fields beyond the minimal example: - `exposure.configured`: hide the channel from configured-channel listing surfaces when set to `false` - `exposure.setup`: hide the channel from interactive setup/configure pickers when set to `false` - `exposure.docs`: mark the channel as internal/private for docs navigation surfaces -- `showConfigured` / `showInSetup`: legacy aliases still accepted for compatibility; prefer `exposure` - `quickstartAllowFrom`: opt the channel into the standard quickstart `allowFrom` flow - `forceAccountBinding`: require explicit account binding even when only one account exists - `preferSessionLookupForAnnounceTarget`: prefer session lookup when resolving announce targets diff --git a/docs/plugins/architecture.md b/docs/plugins/architecture.md index a8edfa4c4b43..cea1a7473787 100644 --- a/docs/plugins/architecture.md +++ b/docs/plugins/architecture.md @@ -88,18 +88,6 @@ OpenClaw classifies every loaded plugin into a shape based on its actual registr Use `openclaw plugins inspect ` to see a plugin's shape and capability breakdown. See [CLI reference](/cli/plugins#inspect) for details. -### Legacy hooks - -The `before_agent_start` hook remains supported as a compatibility path for hook-only plugins. Legacy real-world plugins still depend on it. - -Direction: - -- keep it working -- document it as legacy -- prefer `before_model_resolve` for model/provider override work -- prefer `before_prompt_build` for prompt mutation work -- remove only after real usage drops and fixture coverage proves migration safety - ### Compatibility signals `openclaw doctor`, `openclaw plugins inspect `, `openclaw status --all`, and `openclaw plugins doctor` surface these compatibility notices: @@ -108,7 +96,6 @@ Direction: | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | | **config valid** | Config parses fine and plugins resolve | | **hook-only** (info) | Plugin registers only hooks; a supported path, but not migrated to capability registration yet | -| **legacy `before_agent_start`** (warn) | Plugin uses the deprecated `before_agent_start` hook instead of `before_model_resolve`/`before_prompt_build` | | **deprecated memory-embedding API** (warn) | Non-bundled plugin uses the old memory-specific embedding provider API instead of `registerEmbeddingProvider` | | **hard error** | Config is invalid or plugin failed to load | @@ -322,7 +309,6 @@ import { describeImageWithModel, transcribeOpenAiCompatibleAudio, } from "openclaw/plugin-sdk/media-understanding"; -import { createPluginBackedWebSearchProvider } from "openclaw/plugin-sdk/provider-web-search"; const plugin: OpenClawPluginDefinition = { id: "exampleai", @@ -355,12 +341,12 @@ const plugin: OpenClawPluginDefinition = { }, }); - api.registerWebSearchProvider( - createPluginBackedWebSearchProvider({ - id: "exampleai-search", - // credential + fetch logic - }), - ); + api.registerWebSearchProvider({ + id: "exampleai-search", + createTool() { + // Return the vendor-owned web search tool. + }, + }); }, }; diff --git a/docs/plugins/building-plugins.md b/docs/plugins/building-plugins.md index 4e88cb38f149..ecd6d4030403 100644 --- a/docs/plugins/building-plugins.md +++ b/docs/plugins/building-plugins.md @@ -333,12 +333,6 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store"; ``` -Do not import from the deprecated root barrel: - -```typescript -import { definePluginEntry } from "openclaw/plugin-sdk"; -``` - Within your plugin package, use local barrel files such as `api.ts` and `runtime-api.ts` for internal imports. Do not import your own plugin through an SDK path. Provider-specific helpers should stay in the provider package unless diff --git a/docs/plugins/compatibility.md b/docs/plugins/compatibility.md index bbaf47f3da6b..587ecb30ead5 100644 --- a/docs/plugins/compatibility.md +++ b/docs/plugins/compatibility.md @@ -62,58 +62,24 @@ compatibility and mark it `active` instead. ## Current compatibility areas -The registry currently tracks around 70 compatibility codes across these -areas. New plugin code should use the replacement in each area and in the -specific migration guide; existing plugins can keep using a compatibility -path until docs, diagnostics, and release notes announce a removal window. +The July 2026 sweep removed the expired root SDK, manifest, provider, runtime, +registry-flag, and plugin-owned web-config aliases. Doctor migrations remain +separately tracked so supported upgrade paths can still repair old config. -- legacy broad SDK imports such as `openclaw/plugin-sdk/compat` -- legacy hook-only plugin shapes and `before_agent_start` -- legacy `api.on("deactivate", ...)` cleanup hook names while plugins - migrate to `gateway_stop` -- legacy `activate(api)` plugin entrypoints while plugins migrate to - `register(api)` -- legacy SDK aliases such as `openclaw/extension-api`, - `openclaw/plugin-sdk/channel-runtime`, `openclaw/plugin-sdk/command-auth` - status builders, `openclaw/plugin-sdk/test-utils` (replaced by focused - `openclaw/plugin-sdk/*` test subpaths), and the `ClawdbotConfig` / - `OpenClawSchemaType` type aliases -- bundled plugin allowlist and enablement behavior -- legacy provider/channel env-var manifest metadata -- legacy provider plugin hooks and type aliases while providers move to - explicit catalog, auth, thinking, replay, and transport hooks -- legacy runtime aliases such as `api.runtime.taskFlow`, - `api.runtime.subagent.getSession`, `api.runtime.stt`, and deprecated - `api.runtime.config.loadConfig()` / `api.runtime.config.writeConfigFile(...)` -- WhatsApp `WebInboundMessage` flat callback fields (see below) -- WhatsApp `WebInboundMessage` top-level admission fields (see below) -- legacy memory-plugin split registration while memory plugins move to - `registerMemoryCapability` -- legacy memory-specific embedding provider registration while embedding - providers move to `api.registerEmbeddingProvider(...)` and - `contracts.embeddingProviders` -- legacy channel SDK helpers for native message schemas, mention gating, - inbound envelope formatting, and approval capability nesting -- legacy channel route key and comparable-target helper aliases while - plugins move to `openclaw/plugin-sdk/channel-route` -- activation hints being replaced by manifest contribution ownership -- `setup-api` runtime fallback while setup descriptors move to cold - `setup.requiresRuntime: false` metadata -- provider `discovery` hooks while provider catalog hooks move to - `catalog.run(...)` -- channel `showConfigured` / `showInSetup` metadata while channel packages - move to `openclaw.channel.exposure` -- legacy runtime-policy config keys while doctor migrates operators to - `agentRuntime` -- generated bundled channel config metadata fallback while registry-first - `channelConfigs` metadata lands -- persisted plugin registry disable and install-migration env flags while - repair flows migrate operators to `openclaw plugins registry --refresh` - and `openclaw doctor --fix` -- legacy plugin-owned web search, web fetch, and x_search config paths - while doctor migrates them to `plugins.entries..config` -- legacy `plugins.installs` authored config and bundled plugin load-path - aliases while install metadata moves into the state-managed plugin ledger +The remaining dated compatibility areas are: + +- the August and September SDK subpath windows listed in the migration guide +- `api.on("deactivate", ...)` and `api.on("subagent_spawning", ...)` hook aliases +- memory-specific embedding registration and the beta.5 session-store bridge +- WhatsApp inbound callback aliases described below +- explicit channel target parsing and `openclaw/plugin-sdk/messaging-targets` +- embedded Pi agent aliases +- the shipped agent-harness SDK aliases, whose removal is pending a new + externally documented migration decision + +Active, undated registry records cover supported behavior rather than removal +debt, including activation hints, plugin capture, bundled plugin enablement, +and the generated channel-config fallback. ### WhatsApp inbound callback flat aliases diff --git a/docs/plugins/copilot.md b/docs/plugins/copilot.md index 9087a43ee0d6..12a59fdee0fa 100755 --- a/docs/plugins/copilot.md +++ b/docs/plugins/copilot.md @@ -209,8 +209,7 @@ plus a small set of env defaults inside `extensions/copilot/src/`: | `enableSessionTelemetry` | Optional SDK session telemetry flag. | OpenClaw plugin hooks need no Copilot-specific attempt configuration. The -harness runs `before_prompt_build` (and the legacy `before_agent_start` -compatibility hook), `llm_input`, `llm_output`, and `agent_end` through the +harness runs `before_prompt_build`, `llm_input`, `llm_output`, and `agent_end` through the standard harness helpers. Successful SDK compactions also run `before_compaction` and `after_compaction`. Bridged OpenClaw tools run `before_tool_call` and report `after_tool_call`; `hooksConfig` remains for diff --git a/docs/plugins/hooks.md b/docs/plugins/hooks.md index 06234dd93550..6ae030375450 100644 --- a/docs/plugins/hooks.md +++ b/docs/plugins/hooks.md @@ -121,7 +121,6 @@ observation-only. | `before_model_resolve` | Override provider or model before session messages load | | `agent_turn_prepare` | Consume queued plugin turn injections and add same-turn context before prompt hooks | | `before_prompt_build` | Add dynamic context or system-prompt text before the model call | -| `before_agent_start` | Compatibility-only combined phase; prefer the two hooks above | | **`before_agent_run`** | Inspect the final prompt and session messages before model submission; can block the run | | **`before_agent_reply`** | Short-circuit the model turn with a synthetic reply or silence | | **`before_agent_finalize`** | Inspect the natural final answer and request one more model pass | @@ -478,9 +477,6 @@ Use the phase-specific hooks for new plugins: `prependContext` or `appendContext`. Intended for background monitors that need to summarize current state without changing user-initiated turns. -`before_agent_start` remains for compatibility. Prefer the explicit hooks -above so the plugin does not depend on a legacy combined phase. - `before_agent_run` runs after prompt construction and before any model input, including prompt-local image loading and `llm_input` observation. It receives the current user input as `prompt`, plus loaded session history in `messages` @@ -497,7 +493,7 @@ excluded from transcript, history, broadcast, log, and diagnostics payloads. Observability should use sanitized fields such as blocker id, outcome, timestamp, or a safe category. -`before_agent_start` and `agent_end` include `event.runId` when OpenClaw can +Agent-turn hooks including `agent_end` include `event.runId` when OpenClaw can identify the active run; the same value is also on `ctx.runId`. Cron-driven runs also expose `ctx.jobId` (the originating cron job id) on the agent-turn context so hooks can scope metrics, side effects, or state to a specific @@ -927,9 +923,6 @@ before the next major release: handlers. Read `BodyForAgent` and the structured user-context blocks instead of parsing flat envelope text. See [Plaintext channel envelopes → BodyForAgent](/plugins/sdk-migration#active-deprecations). -- **`before_agent_start`** remains for compatibility. New plugins should use - `before_model_resolve` and `before_prompt_build` instead of the combined - phase. - **`subagent_spawning`** remains for compatibility with older plugins, but new plugins should not return thread routing from it. Core prepares `thread: true` subagent bindings through channel session-binding adapters diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index 79070185f9bb..f4b650e91e80 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -94,9 +94,6 @@ See [Plugins](/tools/plugin) for the full plugin system guide, and [Capability m "providerAuthAliases": { "openrouter-coding": "openrouter" }, - "channelEnvVars": { - "openrouter-chatops": ["OPENROUTER_CHATOPS_TOKEN"] - }, "providerAuthChoices": [ { "provider": "openrouter", @@ -157,10 +154,8 @@ See [Plugins](/tools/plugin) for the full plugin system guide, and [Capability m | `syntheticAuthRefs` | No | `string[]` | Provider or CLI backend refs whose plugin-owned synthetic auth hook should be probed during cold model discovery before runtime loads. | | `nonSecretAuthMarkers` | No | `string[]` | Bundled-plugin-owned placeholder API key values that represent non-secret local, OAuth, or ambient credential state. | | `commandAliases` | No | `object[]` | Command names owned by this plugin that should produce plugin-aware config and CLI diagnostics before runtime loads. | -| `providerAuthEnvVars` | No | `Record` | Deprecated compatibility env metadata for provider auth/status lookup. Prefer `setup.providers[].envVars` for new plugins; OpenClaw still reads this during the deprecation window. | | `providerUsageAuthEnvVars` | No | `Record` | Usage/billing-only provider credentials. OpenClaw uses these names for usage discovery and secret scrubbing but never for inference auth. | | `providerAuthAliases` | No | `Record` | Provider ids that should reuse another provider id for auth lookup, for example a coding provider that shares the base provider API key and auth profiles. | -| `channelEnvVars` | No | `Record` | Cheap channel env metadata that OpenClaw can inspect without loading plugin code. Use this for env-driven channel setup or auth surfaces that generic startup/config helpers should see. | | `providerAuthChoices` | No | `object[]` | Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, and simple CLI flag wiring. | | `activation` | No | `object` | Cheap activation planner metadata for startup, provider, command, channel, route, and capability-triggered loading. Metadata only; plugin runtime still owns actual behavior. | | `setup` | No | `object` | Cheap setup/onboarding descriptors that discovery and setup surfaces can inspect without loading plugin runtime. | @@ -495,7 +490,7 @@ Top-level `cliBackends` stays valid and continues to describe CLI inference back When present, `setup.providers` and `setup.cliBackends` are the preferred descriptor-first lookup surface for setup discovery. If the descriptor only narrows the candidate plugin and setup still needs richer setup-time runtime hooks, set `requiresRuntime: true` and keep `setup-api` in place as the fallback execution path. -OpenClaw also includes `setup.providers[].envVars` in generic provider auth and env-var lookups. `providerAuthEnvVars` remains supported through a compatibility adapter during the deprecation window, but non-bundled plugins that still use it receive a manifest diagnostic. New plugins should put setup/status env metadata on `setup.providers[].envVars`. +OpenClaw includes `setup.providers[].envVars` in generic provider auth and env-var lookups. Put setup and status env metadata there. Use `providerUsageAuthEnvVars` when a billing or organization-level credential must activate `resolveUsageAuth` without becoming an inference credential. These names join workspace dotenv blocking, ACP child-process stripping, sandbox secret filtering, and broad secret scrubbing. The provider runtime still reads and classifies the value inside `resolveUsageAuth`. @@ -1291,7 +1286,7 @@ See [Configuration reference](/gateway/configuration) for the full `plugins.*` s - `providerCatalogEntry` must stay lightweight and should not import broad runtime code; use it for static provider catalog metadata or narrow discovery descriptors, not request-time execution. - Exclusive plugin kinds are selected through `plugins.slots.*`: `kind: "memory"` via `plugins.slots.memory` (default `memory-core`), `kind: "context-engine"` via `plugins.slots.contextEngine` (default `legacy`). - Declare exclusive plugin kind in this manifest. Runtime-entry `OpenClawPluginDefinition.kind` is deprecated and remains only as a compatibility fallback for older plugins. -- Env-var metadata (`setup.providers[].envVars`, deprecated `providerAuthEnvVars`, and `channelEnvVars`) is declarative only. Status, audit, cron delivery validation, and other read-only surfaces still apply plugin trust and effective activation policy before treating an env var as configured. +- Env-var metadata in `setup.providers[].envVars` is declarative only. Status, audit, cron delivery validation, and other read-only surfaces still apply plugin trust and effective activation policy before treating an env var as configured. - For runtime wizard metadata that requires provider code, see [Provider runtime hooks](/plugins/architecture-internals#provider-runtime-hooks). - If your plugin depends on native modules, document the build steps and any package-manager allowlist requirements (for example, pnpm `allow-build-scripts` + `pnpm rebuild `). diff --git a/docs/plugins/sdk-channel-message.md b/docs/plugins/sdk-channel-message.md index edd350a734f6..bd703ce9356b 100644 --- a/docs/plugins/sdk-channel-message.md +++ b/docs/plugins/sdk-channel-message.md @@ -5,13 +5,11 @@ title: "Channel message API" This page moved to [Channel outbound API](/plugins/sdk-channel-outbound). -`openclaw/plugin-sdk/channel-message` and -`openclaw/plugin-sdk/channel-message-runtime` remain deprecated compatibility -subpaths for older plugins; both are thin aliases over the shared channel -message core. New channel plugins should use +`openclaw/plugin-sdk/channel-message` remains a deprecated compatibility +subpath for older plugins. New channel plugins should use `openclaw/plugin-sdk/channel-outbound` for message lifecycle, receipt, durable send, and live preview helpers instead of adding new helpers to the -deprecated subpaths. +deprecated subpath. Removal plan: keep these aliases through the external plugin migration window, then remove them in the next major SDK cleanup after callers have diff --git a/docs/plugins/sdk-channel-outbound.md b/docs/plugins/sdk-channel-outbound.md index 668b835e5c23..d24589a1e32b 100644 --- a/docs/plugins/sdk-channel-outbound.md +++ b/docs/plugins/sdk-channel-outbound.md @@ -4,7 +4,7 @@ title: "Channel outbound API" read_when: - You are building or refactoring a messaging channel plugin send path - You need durable final reply delivery, receipts, live preview finalization, or receive acknowledgement policy - - You are migrating from channel-message, channel-message-runtime, or legacy reply dispatch helpers + - You are migrating from channel-message or legacy reply dispatch helpers --- Channel plugins expose outbound message behavior from diff --git a/docs/plugins/sdk-channel-plugins.md b/docs/plugins/sdk-channel-plugins.md index b891f5508e76..6406bd247e73 100644 --- a/docs/plugins/sdk-channel-plugins.md +++ b/docs/plugins/sdk-channel-plugins.md @@ -480,10 +480,9 @@ adapter/wizard fail closed on config writes and finalization, and they reuse the same install-required message across validation, finalize, and docs-link copy. -If your channel supports env-driven setup or auth and generic startup/config -flows should know those env names before runtime loads, declare them in the -plugin manifest with `channelEnvVars`. Keep channel runtime `envVars` or local -constants for operator-facing copy only. +If your channel supports env-driven setup or auth, expose it through the +channel config schema and setup descriptors. Keep channel runtime `envVars` or +local constants for operator-facing copy only. If your channel can appear in `status`, `channels list`, `channels status`, or SecretRef scans before the plugin runtime starts, add `openclaw.setupEntry` in @@ -527,10 +526,6 @@ surfaces: and adapter registration - `openclaw/plugin-sdk/agent-media-payload` only when a legacy agent/media payload field layout is still required -- `openclaw/plugin-sdk/telegram-command-config` (deprecated: no bundled - plugin uses it in production) for Telegram custom-command normalization, - duplicate/conflict validation, and a fallback-stable command config - contract; prefer plugin-local command config handling for new plugin code Auth-only channels can usually stop at the default path: core handles approvals and the plugin just exposes outbound/auth capabilities. Native diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index f1d8865f2236..63c3d0595d65 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -16,31 +16,32 @@ change, this guide gets it onto the current contracts. ## What changed -Two wide-open import surfaces used to let plugins reach almost anything from a -single entry point: +Several wide-open import surfaces used to let plugins reach almost anything +from a single entry point: -- **`openclaw/plugin-sdk/compat`** - re-exported dozens of helpers to keep - older hook-based plugins working while the new architecture was built. +- **`openclaw/plugin-sdk`** and **`openclaw/plugin-sdk/compat`** - re-exported + dozens of helpers while the focused SDK was being built. Both roots are now + removed; import a documented subpath instead. - **`openclaw/plugin-sdk/infra-runtime`** - a broad barrel mixing system events, heartbeat state, delivery queues, fetch/proxy helpers, file helpers, approval types, and unrelated utilities. -- **`openclaw/plugin-sdk/config-runtime`** - a broad config barrel still - carrying deprecated direct load/write helpers during the migration window. -- **`openclaw/extension-api`** - a bridge giving plugins direct access to - host-side helpers like the embedded agent runner. +- **`openclaw/plugin-sdk/config-runtime`** - a broad config barrel retained + only for its later compatibility window; direct runtime load/write helpers + have been removed. +- **`openclaw/extension-api`** - a removed bridge that gave plugins direct + access to host-side helpers like the embedded agent runner. - **`api.registerEmbeddedExtensionFactory(...)`** - a removed embedded-runner-only hook that observed embedded-runner events such as `tool_result`. Use agent tool-result middleware instead (see [Migrate embedded tool-result extensions to middleware](#how-to-migrate)). -These surfaces are **deprecated**: they still work, but new plugins must not -use them, and existing plugins should migrate before the next major release -removes them. `registerEmbeddedExtensionFactory` has already been removed; -legacy registrations no longer load. +The root SDK, compat barrel, extension bridge, and embedded extension factory +have been removed. `infra-runtime` and `config-runtime` remain only for their +separately recorded later windows; new plugins should use focused subpaths. - The backwards-compatibility layer will be removed in a future major release. - Plugins still importing from these surfaces will break when that happens. + Plugins importing the removed root, compat, or extension surfaces no longer + load. Follow the mappings below before upgrading. OpenClaw does not remove or reinterpret documented plugin behavior in the same @@ -137,10 +138,9 @@ SDK. tests and logging; the gateway remains responsible for applying or scheduling the restart. - `loadConfig` and `writeConfigFile` remain as deprecated compatibility - helpers for external plugins and warn once with the - `runtime-config-load-write` compatibility code. Bundled plugins and repo - runtime code are guarded by `pnpm check:deprecated-api-usage` and + `loadConfig` and `writeConfigFile` have been removed from the plugin + runtime. Bundled plugins and repo runtime code are guarded by + `pnpm check:deprecated-api-usage` and `pnpm check:no-runtime-action-load-config`: new production plugin usage fails outright, direct config writes fail, gateway server methods must use the request runtime snapshot, runtime channel send/action/client helpers @@ -372,181 +372,11 @@ SDK. ## Import path reference - - | Import path | Purpose | Key exports | - | --- | --- | --- | - | `plugin-sdk/plugin-entry` | Canonical plugin entry helper | `definePluginEntry` | - | `plugin-sdk/core` | Legacy umbrella re-export for channel entry definitions/builders | `defineChannelPluginEntry`, `createChatChannelPlugin` | - | `plugin-sdk/config-schema` | Root config schema export | `OpenClawSchema` | - | `plugin-sdk/provider-entry` | Single-provider entry helper | `defineSingleProviderPluginEntry` | - | `plugin-sdk/channel-core` | Focused channel entry definitions and builders | `defineChannelPluginEntry`, `defineSetupPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `createChannelConfigUiHints` | - | `plugin-sdk/setup` | Shared setup wizard helpers | Setup translator, allowlist prompts, setup status builders | - | `plugin-sdk/setup-runtime` | Setup-time runtime helpers | `createSetupTranslator`, import-safe setup patch adapters, lookup-note helpers, `promptResolvedAllowFrom`, `splitSetupEntries`, delegated setup proxies | - | `plugin-sdk/setup-adapter-runtime` | Deprecated setup adapter alias | Use `plugin-sdk/setup-runtime` | - | `plugin-sdk/setup-tools` | Setup tooling helpers | `formatCliCommand`, `detectBinary`, `extractArchive`, `resolveBrewExecutable`, `formatDocsLink`, `CONFIG_DIR` | - | `plugin-sdk/account-core` | Multi-account helpers | Account list/config/action-gate helpers | - | `plugin-sdk/account-id` | Account-id helpers | `DEFAULT_ACCOUNT_ID`, account-id normalization | - | `plugin-sdk/account-resolution` | Account lookup helpers | Account lookup + default-fallback helpers | - | `plugin-sdk/account-helpers` | Narrow account helpers | Account list/account-action helpers | - | `plugin-sdk/channel-setup` | Setup wizard adapters | `createOptionalChannelSetupSurface`, `createOptionalChannelSetupAdapter`, `createOptionalChannelSetupWizard`, plus `DEFAULT_ACCOUNT_ID`, `createTopLevelChannelDmPolicy`, `setSetupChannelEnabled`, `splitSetupEntries` | - | `plugin-sdk/channel-pairing` | DM pairing primitives | `createChannelPairingController` | - | `plugin-sdk/channel-reply-pipeline` | Reply prefix, typing, and source-delivery wiring | `createChannelReplyPipeline`, `resolveChannelSourceReplyDeliveryMode` | - | `plugin-sdk/channel-config-helpers` | Config adapter factories and DM access helpers | `createHybridChannelConfigAdapter`, `resolveChannelDmAccess`, `resolveChannelDmAllowFrom`, `resolveChannelDmPolicy`, `normalizeChannelDmPolicy`, `normalizeLegacyDmAliases` | - | `plugin-sdk/channel-config-schema` | Config schema builders | Shared channel config schema primitives and the generic builder only | - | `plugin-sdk/bundled-channel-config-schema` | Bundled config schemas | OpenClaw-maintained bundled plugins only; new plugins must define plugin-local schemas | - | `plugin-sdk/channel-config-schema-legacy` | Deprecated bundled config schemas | Compatibility alias only; use `plugin-sdk/bundled-channel-config-schema` for maintained bundled plugins | - | `plugin-sdk/telegram-command-config` | Telegram command config helpers | Command-name normalization, description trimming, duplicate/conflict validation | - | `plugin-sdk/channel-policy` | Group/DM policy resolution | `resolveChannelGroupRequireMention` | - | `plugin-sdk/channel-lifecycle` | Deprecated compatibility facade | Use `plugin-sdk/channel-outbound` | - | `plugin-sdk/inbound-envelope` | Inbound envelope helpers | Shared route + envelope builder helpers | - | `plugin-sdk/channel-inbound` | Inbound receive helpers | Context building, formatting, roots, runners, prepared reply dispatch, and dispatch predicates | - | `plugin-sdk/messaging-targets` | Deprecated target parsing import path | Use `plugin-sdk/channel-targets` for generic target parsing helpers, `plugin-sdk/channel-route` for route comparison, and plugin-owned `messaging.targetResolver` / `messaging.resolveOutboundSessionRoute` for provider-specific target resolution | - | `plugin-sdk/outbound-media` | Outbound media helpers | Shared outbound media loading | - | `plugin-sdk/outbound-send-deps` | Deprecated compatibility facade | Use `plugin-sdk/channel-outbound` | - | `plugin-sdk/channel-outbound` | Outbound message lifecycle helpers | Message adapters, receipts, durable send helpers, live preview/streaming helpers, reply options, lifecycle helpers, outbound identity, and payload planning | - | `plugin-sdk/channel-streaming` | Deprecated compatibility facade | Use `plugin-sdk/channel-outbound` | - | `plugin-sdk/outbound-runtime` | Deprecated compatibility facade | Use `plugin-sdk/channel-outbound` | - | `plugin-sdk/thread-bindings-runtime` | Thread-binding helpers | Thread-binding lifecycle and adapter helpers | - | `plugin-sdk/agent-media-payload` | Legacy media payload helpers | Agent media payload builder for legacy field layouts | - | `plugin-sdk/channel-runtime` | Deprecated compatibility shim | Legacy channel runtime utilities only | - | `plugin-sdk/channel-send-result` | Send result types | Reply result types | - | `plugin-sdk/runtime-store` | Persistent plugin storage | `createPluginRuntimeStore` | - | `plugin-sdk/runtime` | Broad runtime helpers | Runtime/logging/backup/plugin-install helpers | - | `plugin-sdk/runtime-env` | Narrow runtime env helpers | Logger/runtime env, timeout, retry, and backoff helpers | - | `plugin-sdk/plugin-runtime` | Shared plugin runtime helpers | Plugin commands/hooks/http/interactive helpers | - | `plugin-sdk/hook-runtime` | Hook pipeline helpers | Shared webhook/internal hook pipeline helpers | - | `plugin-sdk/lazy-runtime` | Lazy runtime helpers | `createLazyRuntimeModule`, `createLazyRuntimeMethod`, `createLazyRuntimeMethodBinder`, `createLazyRuntimeNamedExport`, `createLazyRuntimeSurface` | - | `plugin-sdk/process-runtime` | Process helpers | Shared exec helpers | - | `plugin-sdk/cli-runtime` | CLI runtime helpers | Command formatting, waits, version helpers | - | `plugin-sdk/gateway-runtime` | Gateway helpers | Gateway client, event-loop-ready start helper, advertised LAN host resolution, and channel-status patch helpers | - | `plugin-sdk/config-runtime` | Deprecated config compatibility shim | Prefer `config-contracts`, `plugin-config-runtime`, `runtime-config-snapshot`, and `config-mutation` | - | `plugin-sdk/telegram-command-config` | Telegram command helpers | Fallback-stable Telegram command validation helpers when the bundled Telegram contract surface is unavailable | - | `plugin-sdk/approval-runtime` | Approval prompt helpers | Exec/plugin approval payload, approval capability/profile helpers, native approval routing/runtime helpers, and structured approval display path formatting | - | `plugin-sdk/approval-auth-runtime` | Approval auth helpers | Approver resolution, same-chat action auth | - | `plugin-sdk/approval-client-runtime` | Approval client helpers | Native exec approval profile/filter helpers | - | `plugin-sdk/approval-delivery-runtime` | Approval delivery helpers | Native approval capability/delivery adapters | - | `plugin-sdk/approval-gateway-runtime` | Approval gateway helpers | Shared approval gateway resolver | - | `plugin-sdk/approval-reference-runtime` | Approval transport references | Deterministic durable-locator helper for transport-limited callbacks | - | `plugin-sdk/approval-handler-adapter-runtime` | Approval adapter helpers | Lightweight native approval adapter loading helpers for hot channel entrypoints | - | `plugin-sdk/approval-handler-runtime` | Approval handler helpers | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough | - | `plugin-sdk/approval-native-runtime` | Approval target helpers | Native approval target/account binding helpers | - | `plugin-sdk/approval-reply-runtime` | Approval reply helpers | Exec/plugin approval reply payload helpers | - | `plugin-sdk/channel-runtime-context` | Channel runtime-context helpers | Generic channel runtime-context register/get/watch helpers | - | `plugin-sdk/security-runtime` | Security helpers | Shared trust, DM gating, root-bounded file/path helpers, external-content, and secret-collection helpers | - | `plugin-sdk/ssrf-policy` | SSRF policy helpers | Host allowlist and private-network policy helpers | - | `plugin-sdk/ssrf-runtime` | SSRF runtime helpers | Pinned-dispatcher, guarded fetch, SSRF policy helpers | - | `plugin-sdk/system-event-runtime` | System event helpers | `enqueueSystemEvent` (including keyed replacement), `peekSystemEventEntries` | - | `plugin-sdk/heartbeat-runtime` | Heartbeat helpers | Heartbeat wake, event, and visibility helpers | - | `plugin-sdk/delivery-queue-runtime` | Delivery queue helpers | `drainPendingDeliveries` | - | `plugin-sdk/channel-activity-runtime` | Channel activity helpers | `recordChannelActivity` | - | `plugin-sdk/dedupe-runtime` | Dedupe helpers | In-memory and persistent-backed dedupe caches | - | `plugin-sdk/file-access-runtime` | File access helpers | Safe local-file/media path helpers | - | `plugin-sdk/transport-ready-runtime` | Transport readiness helpers | `waitForTransportReady` | - | `plugin-sdk/exec-approvals-runtime` | Exec approval policy helpers | `loadExecApprovals`, `resolveExecApprovalsFromFile`, `ExecApprovalsFile` | - | `plugin-sdk/collection-runtime` | Bounded cache helpers | `pruneMapToMaxSize` | - | `plugin-sdk/diagnostic-runtime` | Diagnostic gating helpers | `isDiagnosticFlagEnabled`, `isDiagnosticsEnabled` | - | `plugin-sdk/error-runtime` | Error helpers | `formatUncaughtError`, `isApprovalNotFoundError`, error graph helpers, `PlatformMessageNotDispatchedError` | - | `plugin-sdk/fetch-runtime` | Wrapped fetch/proxy helpers | `resolveFetch`, proxy helpers, EnvHttpProxyAgent option helpers | - | `plugin-sdk/host-runtime` | Host normalization helpers | `normalizeHostname`, `normalizeScpRemoteHost` | - | `plugin-sdk/retry-runtime` | Retry helpers | `RetryConfig`, `retryAsync`, policy runners | - | `plugin-sdk/allow-from` | Allowlist formatting and input mapping | `formatAllowFromLowercase`, `mapAllowlistResolutionInputs` | - | `plugin-sdk/command-auth` | Command gating and command-surface helpers | `resolveControlCommandGate`, sender-authorization helpers, command registry helpers including dynamic argument menu formatting | - | `plugin-sdk/command-status` | Command status/help renderers | `buildCommandsMessage`, `buildCommandsMessagePaginated`, `buildHelpMessage` | - | `plugin-sdk/secret-input` | Secret input parsing | Secret input helpers | - | `plugin-sdk/webhook-ingress` | Webhook request helpers | Webhook target utilities | - | `plugin-sdk/webhook-request-guards` | Webhook body guard helpers | Request body read/limit helpers | - | `plugin-sdk/reply-runtime` | Shared reply runtime | Inbound dispatch, heartbeat, reply planner, chunking | - | `plugin-sdk/reply-dispatch-runtime` | Narrow reply dispatch helpers | Finalize, provider dispatch, and conversation-label helpers | - | `plugin-sdk/reply-history` | Reply-history helpers | `createChannelHistoryWindow`; deprecated map-helper compatibility exports such as `buildPendingHistoryContextFromMap`, `recordPendingHistoryEntry`, and `clearHistoryEntriesIfEnabled` | - | `plugin-sdk/reply-reference` | Reply reference planning | `createReplyReferencePlanner` | - | `plugin-sdk/reply-chunking` | Reply chunk helpers | Text/markdown chunking helpers | - | `plugin-sdk/session-store-runtime` | Session store helpers | Scoped session row helpers, store path helpers, and updated-at reads | - | `plugin-sdk/state-paths` | State path helpers | State and OAuth dir helpers | - | `plugin-sdk/routing` | Routing/session-key helpers | `resolveAgentRoute`, `buildAgentSessionKey`, `resolveDefaultAgentBoundAccountId`, session-key normalization helpers | - | `plugin-sdk/status-helpers` | Channel status helpers | Channel/account status summary builders, runtime-state defaults, issue metadata helpers | - | `plugin-sdk/target-resolver-runtime` | Target resolver helpers | Shared target resolver helpers | - | `plugin-sdk/string-normalization-runtime` | String normalization helpers | Slug/string normalization helpers | - | `plugin-sdk/request-url` | Request URL helpers | Extract string URLs from request-like inputs | - | `plugin-sdk/run-command` | Timed command helpers | Timed command runner with normalized stdout/stderr | - | `plugin-sdk/param-readers` | Param readers | Common tool/CLI param readers | - | `plugin-sdk/tool-payload` | Tool payload extraction | Extract normalized payloads from tool result objects | - | `plugin-sdk/tool-send` | Tool send extraction | Extract canonical send target fields from tool args | - | `plugin-sdk/temp-path` | Temp path helpers | Shared temp-download path helpers | - | `plugin-sdk/logging-core` | Logging helpers | Subsystem logger and redaction helpers | - | `plugin-sdk/markdown-table-runtime` | Markdown-table helpers | Markdown table mode helpers | - | `plugin-sdk/reply-payload` | Message reply types | Reply payload types | - | `plugin-sdk/provider-setup` | Curated local/self-hosted provider setup helpers | Self-hosted provider discovery/config helpers | - | `plugin-sdk/self-hosted-provider-setup` | Focused OpenAI-compatible self-hosted provider setup helpers | Same self-hosted provider discovery/config helpers | - | `plugin-sdk/provider-auth-runtime` | Provider runtime auth helpers | Runtime API-key resolution helpers | - | `plugin-sdk/provider-auth-api-key` | Provider API-key setup helpers | API-key onboarding/profile-write helpers | - | `plugin-sdk/provider-auth-result` | Provider auth-result helpers | Standard OAuth auth-result builder | - | `plugin-sdk/provider-selection-runtime` | Provider selection helpers | Configured-or-auto provider selection and raw provider config merging | - | `plugin-sdk/provider-env-vars` | Provider env-var helpers | Provider auth env-var lookup helpers | - | `plugin-sdk/provider-model-shared` | Shared provider model/replay helpers | `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and model-id normalization helpers | - | `plugin-sdk/provider-catalog-shared` | Shared provider catalog helpers | `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `buildManifestModelProviderConfig`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` | - | `plugin-sdk/provider-onboard` | Provider onboarding patches | Onboarding config helpers | - | `plugin-sdk/provider-http` | Provider HTTP helpers | Generic provider HTTP/endpoint capability helpers, including audio transcription multipart form helpers | - | `plugin-sdk/provider-web-fetch` | Provider web-fetch helpers | Web-fetch provider registration/cache helpers | - | `plugin-sdk/provider-web-search-config-contract` | Provider web-search config helpers | Narrow web-search config/credential helpers for providers that do not need plugin-enable wiring | - | `plugin-sdk/provider-web-search-contract` | Provider web-search contract helpers | Narrow web-search config/credential contract helpers such as `createWebSearchProviderContractFields`, `enablePluginInConfig`, `resolveProviderWebSearchPluginConfig`, and scoped credential setters/getters | - | `plugin-sdk/provider-web-search` | Provider web-search helpers | Web-search provider registration/cache/runtime helpers | - | `plugin-sdk/provider-tools` | Provider tool/schema compat helpers | `ProviderToolCompatFamily`, `buildProviderToolCompatFamilyHooks`, and DeepSeek/Gemini/OpenAI schema cleanup + diagnostics | - | `plugin-sdk/provider-usage` | Provider usage helpers | `fetchClaudeUsage`, `fetchGeminiUsage`, `fetchGithubCopilotUsage`, and other provider usage helpers | - | `plugin-sdk/provider-stream` | Provider stream wrapper helpers | `ProviderStreamFamily`, `buildProviderStreamFamilyHooks`, `composeProviderStreamWrappers`, stream wrapper types, and shared Anthropic/Bedrock/DeepSeek V4/Google/Kilocode/Moonshot/OpenAI/OpenRouter/Z.A.I/MiniMax/Copilot wrapper helpers | - | `plugin-sdk/provider-transport-runtime` | Provider transport helpers | Native provider transport helpers such as guarded fetch, tool-result text extraction, transport message transforms, and writable transport event streams | - | `plugin-sdk/keyed-async-queue` | Ordered async queue | `KeyedAsyncQueue` | - | `plugin-sdk/media-runtime` | Shared media helpers | Media fetch/transform/store helpers, ffprobe-backed video dimension probing, and media payload builders | - | `plugin-sdk/media-generation-runtime` | Shared media-generation helpers | Shared failover helpers, candidate selection, and missing-model messaging for image/video/music generation | - | `plugin-sdk/media-understanding` | Media-understanding helpers | Media understanding provider types plus provider-facing image/audio helper exports | - | `plugin-sdk/text-runtime` | Deprecated broad text compatibility export | Use `string-coerce-runtime`, `text-chunking`, `text-utility-runtime`, and `logging-core` | - | `plugin-sdk/text-chunking` | Text chunking helpers | Outbound text and offset-preserving range chunking helpers | - | `plugin-sdk/speech` | Speech helpers | Speech provider types plus provider-facing directive, registry, validation helpers, and OpenAI-compatible TTS builder | - | `plugin-sdk/speech-core` | Shared speech core | Speech provider types, registry, directives, normalization | - | `plugin-sdk/speech-settings` | Speech settings | Lightweight TTS config resolution and normalization primitives without provider registries or synthesis runtime | - | `plugin-sdk/realtime-transcription` | Realtime transcription helpers | Provider types, registry helpers, and shared WebSocket session helper | - | `plugin-sdk/realtime-voice` | Realtime voice helpers | Provider types, registry/resolution helpers, bridge session helpers, the transport-independent session harness, audio-energy/speech-onset gates, shared agent talk-back queues, active-run voice control, transcript/event health, echo suppression, consult question matching, forced-consult coordination, turn-context tracking, output activity tracking, and fast context consult helpers | - | `plugin-sdk/image-generation` | Image-generation helpers | Image generation provider types plus image asset/data URL helpers and the OpenAI-compatible image provider builder | - | `plugin-sdk/image-generation-core` | Shared image-generation core | Image-generation types, failover, auth, and registry helpers | - | `plugin-sdk/music-generation` | Music-generation helpers | Music-generation provider/request/result types | - | `plugin-sdk/music-generation-core` | Shared music-generation core | Music-generation types, failover helpers, provider lookup, and model-ref parsing | - | `plugin-sdk/video-generation` | Video-generation helpers | Video-generation provider/request/result types | - | `plugin-sdk/video-generation-core` | Shared video-generation core | Video-generation types, failover helpers, provider lookup, and model-ref parsing | - | `plugin-sdk/interactive-runtime` | Interactive reply helpers | Interactive reply payload normalization/reduction | - | `plugin-sdk/channel-config-primitives` | Channel config primitives | Narrow channel config-schema primitives | - | `plugin-sdk/channel-config-writes` | Channel config-write helpers | Channel config-write authorization helpers | - | `plugin-sdk/channel-plugin-common` | Shared channel prelude | Shared channel plugin prelude exports | - | `plugin-sdk/channel-status` | Channel status helpers | Shared channel status snapshot/summary helpers | - | `plugin-sdk/allowlist-config-edit` | Allowlist config helpers | Allowlist config edit/read helpers | - | `plugin-sdk/group-access` | Group access helpers | Shared group-access decision helpers | - | `plugin-sdk/direct-dm`, `plugin-sdk/direct-dm-access` | Deprecated compatibility facades | Use `plugin-sdk/channel-inbound` | - | `plugin-sdk/direct-dm-guard-policy` | Direct-DM guard helpers | Narrow pre-crypto guard policy helpers | - | `plugin-sdk/extension-shared` | Shared extension helpers | Passive-channel/status and ambient proxy helper primitives | - | `plugin-sdk/webhook-targets` | Webhook target helpers | Webhook target registry and route-install helpers | - | `plugin-sdk/webhook-path` | Deprecated webhook path alias | Use `plugin-sdk/webhook-ingress` | - | `plugin-sdk/web-media` | Shared web media helpers | Remote/local media loading helpers | - | `plugin-sdk/zod` | Deprecated Zod compatibility re-export | Import `zod` from `zod` directly | - | `plugin-sdk/memory-core` | Bundled memory-core helpers | Memory manager/config/file/CLI helper surface | - | `plugin-sdk/memory-core-engine-runtime` | Memory engine runtime facade | Memory index/search runtime facade | - | `plugin-sdk/memory-core-host-embedding-registry` | Memory embedding registry | Lightweight memory embedding provider registry helpers | - | `plugin-sdk/memory-core-host-engine-foundation` | Memory host foundation engine | Memory host foundation engine exports | - | `plugin-sdk/memory-core-host-engine-embeddings` | Memory host embedding engine | Memory embedding contracts, registry access, local provider, and generic batch/remote helpers; concrete remote providers live in their owning plugins | - | `plugin-sdk/memory-core-host-engine-qmd` | Memory host QMD engine | Memory host QMD engine exports | - | `plugin-sdk/memory-core-host-engine-storage` | Memory host storage engine | Memory host storage engine exports | - | `plugin-sdk/memory-core-host-multimodal` | Memory host multimodal helpers | Memory host multimodal helpers | - | `plugin-sdk/memory-core-host-query` | Memory host query helpers | Memory host query helpers | - | `plugin-sdk/memory-core-host-secret` | Memory host secret helpers | Memory host secret helpers | - | `plugin-sdk/memory-core-host-events` | Deprecated memory event alias | Use `plugin-sdk/memory-host-events` | - | `plugin-sdk/memory-core-host-status` | Memory host status helpers | Memory host status helpers | - | `plugin-sdk/memory-core-host-runtime-cli` | Memory host CLI runtime | Memory host CLI runtime helpers | - | `plugin-sdk/memory-core-host-runtime-core` | Memory host core runtime | Memory host core runtime helpers | - | `plugin-sdk/memory-core-host-runtime-files` | Memory host file/runtime helpers | Memory host file/runtime helpers | - | `plugin-sdk/memory-host-core` | Memory host core runtime alias | Vendor-neutral alias for memory host core runtime helpers | - | `plugin-sdk/memory-host-events` | Memory host event journal alias | Vendor-neutral alias for memory host event journal helpers | - | `plugin-sdk/memory-host-files` | Deprecated memory file/runtime alias | Use `plugin-sdk/memory-core-host-runtime-files` | - | `plugin-sdk/memory-host-markdown` | Managed markdown helpers | Shared managed-markdown helpers for memory-adjacent plugins | - | `plugin-sdk/memory-host-search` | Active memory search facade | Lazy active-memory search-manager runtime facade | - | `plugin-sdk/memory-host-status` | Deprecated memory host status alias | Use `plugin-sdk/memory-core-host-status` | - +The public package export map is the source of truth for importable SDK +subpaths. Use the topical SDK guides linked from [SDK overview](/plugins/sdk-overview) +and prefer the narrowest documented public subpath. The compiler inventory in +`scripts/lib/plugin-sdk-entrypoints.json` also contains private-local entries used +to build bundled plugins; their presence there does not make them public package exports. This table is the common migration subset, not the full SDK surface. The compiler entrypoint inventory lives in `scripts/lib/plugin-sdk-entrypoints.json`; @@ -566,6 +396,12 @@ contract should own it. ## Removed compatibility surfaces +The July 2026 sweep removed the root SDK and compat barrels, the extension API +bridge, the expired SDK subpath aliases, unused SDK subpaths, and the public +exports for bundled-only SDK modules. Bundled-only modules remain available to +their repository owners through private-local build mappings; they are not +importable from the published package. + ### Process-global API-provider publication `registerApiProvider(...)` and `unregisterApiProviders(...)` were removed from @@ -586,20 +422,21 @@ tests use focused subpaths such as `plugin-sdk/plugin-test-runtime`, `plugin-sdk/channel-test-helpers`, `plugin-sdk/channel-target-testing`, `plugin-sdk/test-env`, and `plugin-sdk/test-fixtures`. -## Active deprecations +## Migration reference -Narrower deprecations across the plugin SDK, provider contract, runtime -surface, and manifest. Each still works today but will be removed in a future -major release. Every entry maps the old API to its canonical replacement. +These mappings cover both removed July 2026 surfaces and later-window active +deprecations. A mapping is migration guidance, not evidence that the old +surface remains available; consult the compatibility registry and removal +timeline for current status. **Old (`openclaw/plugin-sdk/command-auth`)**: `buildCommandsMessage`, `buildCommandsMessagePaginated`, `buildHelpMessage`. - **New (`openclaw/plugin-sdk/command-status`)**: same signatures, same - exports - just imported from the narrower subpath. `command-auth` - re-exports them as compat stubs. + **New (`openclaw/plugin-sdk/command-status`)**: same signatures, imported + from the narrower subpath. The `command-auth` compatibility re-exports + have been removed. ```typescript // Before @@ -627,13 +464,12 @@ major release. Every entry maps the old API to its canonical replacement. - `openclaw/plugin-sdk/channel-runtime` is a compatibility shim for older - channel plugins. Do not import it from new code; use + `openclaw/plugin-sdk/channel-runtime` has been removed. Use `openclaw/plugin-sdk/channel-runtime-context` for registering runtime objects. - `channelActions*` helpers in `openclaw/plugin-sdk/channel-actions` are - deprecated alongside raw "actions" channel exports. Expose capabilities + The native message schema helpers in `openclaw/plugin-sdk/channel-actions` + were removed alongside raw "actions" channel exports. Expose capabilities through the semantic `presentation` surface instead - channel plugins declare what they render (cards, buttons, selects) rather than which raw action names they accept. @@ -729,7 +565,8 @@ major release. Every entry maps the old API to its canonical replacement. | `ProviderDiscoveryResult` | `ProviderCatalogResult` | | `ProviderPluginDiscovery` | `ProviderPluginCatalog` | - Plus the legacy `ProviderCapabilities` static bag - provider plugins + The aliases and legacy `ProviderCapabilities` static bag have been + removed. Provider plugins should use explicit provider hooks such as `buildReplayPolicy`, `normalizeToolSchemas`, and `wrapStreamFn` rather than a static object. @@ -750,8 +587,7 @@ major release. Every entry maps the old API to its canonical replacement. catalog facts to expose a model-specific profile only when the configured request contract supports it. - Implement one hook instead of three. The legacy hooks keep working during - the deprecation window but are not composed with the profile result. + Implement one hook instead of three. The legacy hooks have been removed. @@ -779,8 +615,7 @@ major release. Every entry maps the old API to its canonical replacement. on the manifest. This consolidates setup/status env metadata in one place and avoids booting the plugin runtime just to answer env-var lookups. - `providerAuthEnvVars` remains supported through a compatibility adapter - until the deprecation window closes. + `providerAuthEnvVars` is no longer accepted. @@ -890,7 +725,7 @@ major release. Every entry maps the old API to its canonical replacement. const flow = api.runtime.tasks.managedFlows.fromToolContext(ctx); ``` - Removed after 2026-07-26. + The legacy aliases were removed in July 2026. @@ -903,14 +738,14 @@ major release. Every entry maps the old API to its canonical replacement. - `OpenClawSchemaType` re-exported from `openclaw/plugin-sdk` is now a - one-line alias for `OpenClawConfig`. Prefer the canonical name. + The `OpenClawSchemaType` root-SDK alias was removed. Use the canonical + `OpenClawConfig` name. ```typescript // Before import type { OpenClawSchemaType } from "openclaw/plugin-sdk"; // After - import type { OpenClawConfig } from "openclaw/plugin-sdk/config-schema"; + import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; ``` @@ -1055,25 +890,15 @@ apps own device capture/playback UX. | **Each compat record's `removeAfter` date** | That specific surface is eligible for removal; `pnpm plugins:boundary-report --fail-on-eligible-compat` fails CI once the date passes. | | **Next major release** | Any surfaces still not migrated are removed; plugins still using them will fail. | -The public SDK subpaths below have registry-backed removal or demotion windows. -They do not currently emit a runtime warning when an external plugin imports -them. The repository deprecated-usage guard applies only to the fully unused -θ1 tier and the earlier compatibility tier; θ2 remains available to bundled -plugins during the window. +The remaining public SDK subpaths below have registry-backed removal windows. +The July 30 rows were removed after their early maintainer-authorized sweep: +unused subpaths were deleted, earlier compatibility aliases were deleted, and +bundled-only modules were demoted to private-local build mappings. -For the window introduced on 2026-07-15, θ1 has no known external or bundled -consumers and will be deleted after the window. θ2 has bundled consumers but no -known external consumers; only its public package export will be retired. Its -module will remain available to bundled plugins as a private-local-only -subpath. - -| `removeAfter` | Tier | SDK subpaths | -| ------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `2026-07-30` | Earlier compatibility deprecations | `agent-dir-compat`, `channel-envelope`, `channel-inbound-roots`, `channel-location`, `channel-message-runtime`, `channel-pairing-paths`, `channel-reply-options-runtime`, `config-schema`, `config-types`, `direct-dm`, `direct-dm-access`, `mattermost`, `media-generation-runtime-shared`, `memory-core`, `memory-core-engine-runtime`, `memory-core-host-events`, `memory-core-host-multimodal`, `memory-core-host-query`, `memory-host-files`, `memory-host-status`, `music-generation-core`, `outbound-runtime`, `outbound-send-deps`, `provider-auth-login`, `provider-zai-endpoint`, `reply-dedupe`, `runtime-logger`, `runtime-secret-resolution`, `self-hosted-provider-setup`, `setup-adapter-runtime`, `telegram-command-config`, `webhook-path`, `zalouser` | -| `2026-07-30` | θ1: fully unused; remove subpath | `command-gating`, `lmstudio`, `lmstudio-runtime`, `secret-provider-integration`, `skills-runtime` | -| `2026-07-30` | θ2: bundled-only; retire public export | `access-groups`, `account-resolution-runtime`, `acp-binding-resolve-runtime`, `acp-binding-runtime`, `acp-runtime`, `acp-runtime-backend`, `agent-core`, `agent-harness-exec-review-runtime`, `agent-harness-task-runtime`, `agent-harness-tool-runtime`, `agent-media-payload`, `agent-sessions`, `approval-reaction-runtime`, `approval-reference-runtime`, `async-lock-runtime`, `browser-config`, `bundled-channel-config-schema`, `channel-activity-runtime`, `channel-config-writes`, `channel-mention-gating`, `channel-route`, `channel-secret-tts-runtime`, `channel-targets`, `chat-channel-ids`, `cli-backend`, `cli-runtime`, `codex-mcp-projection`, `command-status-runtime`, `command-surface`, `concurrency-runtime`, `context-visibility-runtime`, `conversation-binding-runtime`, `cron-store-runtime`, `dangerous-name-runtime`, `delivery-queue-runtime`, `direct-dm-guard-policy`, `directory-config-runtime`, `document-extractor`, `embedding-providers`, `exec-approvals-runtime`, `expect-runtime`, `fetch-runtime`, `file-access-runtime`, `file-lock`, `global-singleton`, `group-activation`, `heartbeat-runtime`, `host-runtime`, `html-entity-runtime`, `image-generation`, `image-generation-core`, `image-generation-runtime`, `inline-image-data-url-runtime`, `json-schema-runtime`, `json-unsafe-integers`, `keyed-async-queue`, `llm`, `markdown-table-runtime`, `media-generation-runtime`, `media-understanding`, `memory-core-host-embedding-registry`, `memory-core-host-engine-embeddings`, `memory-core-host-engine-qmd`, `memory-core-host-engine-storage`, `memory-core-host-runtime-cli`, `memory-core-host-runtime-core`, `memory-core-host-runtime-files`, `memory-core-host-secret`, `memory-core-host-status`, `memory-host-core`, `memory-host-events`, `memory-host-markdown`, `memory-host-search`, `message-tool-delivery-hints`, `migration`, `migration-runtime`, `music-generation`, `node-host`, `number-runtime`, `outbound-media`, `pair-loop-guard-runtime`, `plugin-config-runtime`, `plugin-state-runtime`, `poll-runtime`, `process-runtime`, `provider-auth-api-key`, `provider-auth-login-flow-runtime`, `provider-auth-result`, `provider-auth-runtime`, `provider-catalog-live-runtime`, `provider-catalog-shared`, `provider-entry`, `provider-env-vars`, `provider-http`, `provider-model-shared`, `provider-model-types`, `provider-oauth-runtime`, `provider-onboard`, `provider-selection-runtime`, `provider-setup`, `provider-stream`, `provider-stream-family`, `provider-stream-shared`, `provider-tools`, `provider-transport-runtime`, `provider-usage`, `provider-web-fetch`, `provider-web-fetch-contract`, `provider-web-search`, `provider-web-search-config-contract`, `provider-web-search-contract`, `qa-runner-runtime`, `realtime-bootstrap-context`, `realtime-transcription`, `realtime-voice`, `reply-reference`, `request-url`, `response-limit-runtime`, `retry-runtime`, `runtime-doctor`, `runtime-fetch`, `sandbox`, `secret-file-runtime`, `secure-random-runtime`, `session-binding-runtime`, `session-catalog`, `session-key-runtime`, `session-transcript-hit`, `session-transcript-runtime`, `session-visibility`, `simple-completion-runtime`, `speech`, `speech-core`, `sqlite-runtime`, `ssrf-dispatcher`, `string-normalization-runtime`, `system-event-runtime`, `talk-config-runtime`, `target-resolver-runtime`, `text-autolink-runtime`, `text-utility-runtime`, `thread-bindings-runtime`, `thread-bindings-session-runtime`, `time-runtime`, `tool-payload`, `tool-plugin`, `tool-results`, `transcripts`, `transport-ready-runtime`, `tts-runtime`, `types`, `video-generation`, `video-generation-core`, `video-generation-runtime`, `web-content-extractor`, `webhook-targets`, `windows-spawn` | -| `2026-08-15` | Earlier compatibility deprecations | `agent-config-primitives`, `channel-logging`, `channel-secret-runtime`, `channel-streaming`, `group-access`, `inbound-reply-dispatch`, `matrix`, `text-runtime`, `zod` | -| `2026-09-01` | Earlier compatibility deprecations | `channel-lifecycle`, `channel-message`, `channel-reply-pipeline`, `config-runtime`, `infra-runtime` | +| `removeAfter` | Tier | SDK subpaths | +| ------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `2026-08-15` | Earlier compatibility deprecations | `agent-config-primitives`, `channel-logging`, `channel-secret-runtime`, `channel-streaming`, `group-access`, `inbound-reply-dispatch`, `matrix`, `text-runtime`, `zod` | +| `2026-09-01` | Earlier compatibility deprecations | `channel-lifecycle`, `channel-message`, `channel-reply-pipeline`, `config-runtime`, `infra-runtime` | All core plugins have already migrated. External plugins should migrate before the next major release. Run `pnpm plugins:boundary-report` to see which diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index 81a5b47a4a48..7797ad350ec7 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -41,9 +41,8 @@ For channel config, publish the channel-owned JSON Schema through `openclaw.plugin.json#channelConfigs`. The `plugin-sdk/channel-config-schema` subpath is for shared schema primitives and the generic builder. OpenClaw's bundled plugins use `plugin-sdk/bundled-channel-config-schema` for retained -bundled-channel schemas. Deprecated compatibility exports remain on -`plugin-sdk/channel-config-schema-legacy`; neither bundled schema subpath is a -pattern for new plugins. +bundled-channel schemas. That bundled schema subpath is not a pattern for new +plugins. Do not import provider- or channel-branded convenience seams (for example @@ -445,8 +444,7 @@ The contracts intentionally split authority: own command names or aliases. - `allowPromptInjection=false` disables prompt-mutating hooks including `agent_turn_prepare`, `before_prompt_build`, `heartbeat_prompt_contribution`, - prompt fields from legacy `before_agent_start`, and - `enqueueNextTurnInjection`. + and `enqueueNextTurnInjection`. Examples of non-Plan consumers: @@ -603,9 +601,6 @@ For an end-to-end authoring guide, see | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api.registerContextEngine(id, factory)` | Context engine (one active at a time). Lifecycle callbacks receive `runtimeSettings` when the host can provide model/provider/mode diagnostics; older strict engines are retried without that key. | | `api.registerMemoryCapability(capability)` | Unified memory capability | -| `api.registerMemoryPromptSection(builder)` | Memory prompt section builder | -| `api.registerMemoryFlushPlan(resolver)` | Memory flush plan resolver | -| `api.registerMemoryRuntime(runtime)` | Memory runtime adapter | ### Deprecated memory embedding adapters @@ -613,13 +608,11 @@ For an end-to-end authoring guide, see | ---------------------------------------------- | ---------------------------------------------- | | `api.registerMemoryEmbeddingProvider(adapter)` | Memory embedding adapter for the active plugin | -- `registerMemoryCapability` is the preferred exclusive memory-plugin API. +- `registerMemoryCapability` is the exclusive memory-plugin API. - `registerMemoryCapability` may also expose `publicArtifacts.listArtifacts(...)` so companion plugins can consume exported memory artifacts through `openclaw/plugin-sdk/memory-host-core` instead of reaching into a specific memory plugin's private layout. -- `registerMemoryPromptSection`, `registerMemoryFlushPlan`, and - `registerMemoryRuntime` are legacy-compatible exclusive memory-plugin APIs. - `MemoryFlushPlan.model` can pin the flush turn to an exact `provider/model` reference, such as `ollama/qwen3:8b`, without inheriting the active fallback chain. diff --git a/docs/plugins/sdk-runtime.md b/docs/plugins/sdk-runtime.md index 4daa5857bc12..42051fc31e03 100644 --- a/docs/plugins/sdk-runtime.md +++ b/docs/plugins/sdk-runtime.md @@ -43,9 +43,8 @@ Persist changes with `api.runtime.config.mutateConfigFile(...)` or `api.runtime. The mutation helpers return `afterWrite` plus a typed `followUp` summary so callers can log or test whether they requested a restart. The gateway still owns when that restart actually happens. - -`api.runtime.config.loadConfig()` and `api.runtime.config.writeConfigFile(...)` are deprecated. They warn once per plugin at runtime and remain available only for old external plugins during the migration window. Bundled plugins must not use them: an internal config boundary guard fails the build if plugin code calls them or imports those helpers from plugin SDK subpaths. Use `current()`, a passed-in `cfg`, `mutateConfigFile(...)`, or `replaceConfigFile(...)` instead. - +Use `current()`, a passed-in `cfg`, `mutateConfigFile(...)`, or +`replaceConfigFile(...)` for runtime config access and writes. For direct SDK imports, prefer the focused config subpaths over the broad `openclaw/plugin-sdk/config-runtime` compatibility barrel: `config-contracts` for types, `plugin-config-runtime` for already-loaded config assertions, plugin entry lookup, and canonical config merging, `runtime-config-snapshot` for current process snapshots, and `config-mutation` for writes. Bundled plugin tests should mock these focused subpaths directly instead of mocking the broad compatibility barrel. @@ -409,7 +408,6 @@ two-party event loops that do not go through the shared inbound reply runner. - `api.runtime.tasks.managedFlows` is mutation-capable: create, advance, and cancel Task Flows. - `api.runtime.tasks.flows` and `api.runtime.tasks.runs` are read-only DTO views for listing and status lookups; both expose `bindSession(...)` / `fromToolContext(...)` plus `get`, `list`, `findLatest`, and `resolve`. - - `api.runtime.tasks.flow` is a deprecated alias for `managedFlows`. Task Flow tracks durable multi-step workflow state. It is not a scheduler: use Cron or `api.session.workflow.scheduleSessionTurn(...)` for future @@ -533,10 +531,6 @@ two-party event loops that do not go through the shared inbound reply runner. `describeImageFileWithModel(...)` describes an already-known image through a specific provider/model, bypassing the default active-model resolution that `describeImageFile(...)` uses. - - `api.runtime.stt.transcribeAudioFile(...)` remains as a compatibility alias for `api.runtime.mediaUnderstanding.transcribeAudioFile(...)`. - - Image generation. @@ -836,7 +830,7 @@ two-party event loops that do not go through the shared inbound reply runner. - `implicitMentionKindWhen` - `resolveInboundMentionDecision` - `api.runtime.channel.mentions` intentionally does not expose the older `resolveMentionGating*` compatibility helpers. Prefer the normalized `{ facts, policy }` path. + Use the normalized `{ facts, policy }` path for mention decisions. Several fields under `reply`, `session`, and `inbound` carry per-field `@deprecated` notes pointing at the current channel-turn kernel or channel-outbound adapters; check the inline JSDoc on the specific helper before building new code on it. diff --git a/docs/plugins/sdk-setup.md b/docs/plugins/sdk-setup.md index 99862f4e4cd0..ba4d66dfb645 100644 --- a/docs/plugins/sdk-setup.md +++ b/docs/plugins/sdk-setup.md @@ -165,10 +165,6 @@ Example: - `setup`: include the channel in interactive setup/configure pickers - `docs`: mark the channel as public-facing in docs/navigation surfaces - -`showConfigured` and `showInSetup` remain supported as legacy aliases. Prefer `exposure`. - - ### `openclaw.install` `openclaw.install` is package metadata, not manifest metadata. @@ -343,11 +339,10 @@ Bundled workspace channels that keep setup-safe exports in sidecar modules can u For hot setup-only paths, prefer the narrow setup helper seams over the broader `plugin-sdk/setup` umbrella when you only need part of the setup surface: -| Import path | Use it for | Key exports | -| ---------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `plugin-sdk/setup-runtime` | setup-time runtime helpers that stay available in `setupEntry` / deferred channel startup | `createSetupTranslator`, `createPatchedAccountSetupAdapter`, `createEnvPatchedAccountSetupAdapter`, `createSetupInputPresenceValidator`, `noteChannelLookupFailure`, `noteChannelLookupSummary`, `promptResolvedAllowFrom`, `splitSetupEntries`, `createAllowlistSetupWizardProxy`, `createDelegatedSetupWizardProxy` | -| `plugin-sdk/setup-adapter-runtime` | deprecated compatibility alias; use `plugin-sdk/setup-runtime` | `createEnvPatchedAccountSetupAdapter` | -| `plugin-sdk/setup-tools` | setup/install CLI/archive/docs helpers | `formatCliCommand`, `detectBinary`, `extractArchive`, `resolveBrewExecutable`, `formatDocsLink`, `CONFIG_DIR` | +| Import path | Use it for | Key exports | +| -------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `plugin-sdk/setup-runtime` | setup-time runtime helpers that stay available in `setupEntry` / deferred channel startup | `createSetupTranslator`, `createPatchedAccountSetupAdapter`, `createEnvPatchedAccountSetupAdapter`, `createSetupInputPresenceValidator`, `noteChannelLookupFailure`, `noteChannelLookupSummary`, `promptResolvedAllowFrom`, `splitSetupEntries`, `createAllowlistSetupWizardProxy`, `createDelegatedSetupWizardProxy` | +| `plugin-sdk/setup-tools` | setup/install CLI/archive/docs helpers | `formatCliCommand`, `detectBinary`, `extractArchive`, `resolveBrewExecutable`, `formatDocsLink`, `CONFIG_DIR` | Use the broader `plugin-sdk/setup` seam when you want the full shared setup toolbox, including config-patch helpers such as `moveSingleAccountChannelSectionToDefaultAccount(...)`. diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 4287b548de63..18349cc4a92f 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -6,9 +6,9 @@ read_when: title: "Plugin SDK subpaths" --- -The plugin SDK is exposed as a set of narrow public subpaths under -`openclaw/plugin-sdk/`. This page catalogs the commonly used subpaths grouped by -purpose. Three files define the surface: +The plugin SDK contains narrow public subpaths and repository-only bundled +helpers under `openclaw/plugin-sdk/`. This page catalogs both and labels +private-local entries explicitly. Three files define the boundary: - `scripts/lib/plugin-sdk-entrypoints.json`: the maintained entrypoint inventory the build compiles. @@ -31,23 +31,20 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plugin-sdk/plugin-entry` | `definePluginEntry` | | `plugin-sdk/core` | `defineChannelPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `defineSetupPluginEntry`, `buildChannelConfigSchema`, `buildJsonChannelConfigSchema`, `resolveTailscalePublishedHost` | -| `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` | -| `plugin-sdk/migration` | Migration provider item helpers such as `createMigrationItem`, reason constants, item status markers, redaction helpers, and `summarizeMigrationItems` | -| `plugin-sdk/migration-runtime` | Runtime migration helpers such as `copyMigrationFileItem`, `resolvePlannedMigrationTargets`, `withCachedMigrationConfigRuntime`, and `writeMigrationReport` | +| `plugin-sdk/provider-entry` | Private-local after July 2026; `defineSingleProviderPluginEntry` | +| `plugin-sdk/migration` | Private-local after July 2026; Migration provider item helpers such as `createMigrationItem`, reason constants, item status markers, redaction helpers, and `summarizeMigrationItems` | +| `plugin-sdk/migration-runtime` | Private-local after July 2026; Runtime migration helpers such as `copyMigrationFileItem`, `resolvePlannedMigrationTargets`, `withCachedMigrationConfigRuntime`, and `writeMigrationReport` | | `plugin-sdk/health` | Doctor health-check registration, detection, repair, selection, severity, and finding types for bundled health consumers | -| `plugin-sdk/config-schema` | Deprecated. Root `openclaw.json` Zod schema (`OpenClawSchema`); define plugin-local schemas instead and validate with `plugin-sdk/json-schema-runtime` | -### Deprecated compatibility and test helpers +### Compatibility and private-local helpers -Deprecated subpaths stay exported for older plugins, but new code should use the -focused SDK subpaths below. The maintained list is +Only the later-window deprecated subpaths remain exported. July 2026 aliases and +unused subpaths were deleted, while bundled-only helpers were removed from the +public package and are labeled private-local below. The maintained list is `scripts/lib/plugin-sdk-deprecated-public-subpaths.json`; CI rejects bundled -production imports from it. Broad barrels such as `plugin-sdk/compat`, -`plugin-sdk/config-types`, `plugin-sdk/infra-runtime`, and `plugin-sdk/text-runtime` are compatibility only, and `plugin-sdk/zod` is a compatibility re-export: import `zod` directly from `zod`. The broad domain barrels `plugin-sdk/agent-runtime`, `plugin-sdk/channel-lifecycle`, -`plugin-sdk/channel-runtime`, `plugin-sdk/cli-runtime`, `plugin-sdk/conversation-runtime`, `plugin-sdk/hook-runtime`, `plugin-sdk/media-runtime`, `plugin-sdk/plugin-runtime`, and `plugin-sdk/security-runtime` are likewise deprecated in favor of focused @@ -64,22 +61,11 @@ longer package exports: `agent-runtime-test-contracts`, `ssrf-runtime-internal` and `codex-native-task-runtime` are also repo-local only. -### Reserved bundled plugin helper subpaths +### Bundled plugin helper subpaths -`plugin-sdk/codex-mcp-projection` is the only reserved subpath: a plugin-owned -compatibility surface for the bundled Codex plugin, not a general SDK API. -Cross-owner plugin imports are blocked by package contract guardrails, and -CI fails when a reserved subpath stops being imported. -`plugin-sdk/codex-native-task-runtime` is repo-local only and is not a package -export. - -`src/plugin-sdk/entrypoints.ts` also tracks supported bundled facades, SDK +Bundled-only helper modules are private-local after the July 2026 sweep. Cross-owner imports are blocked by package contract guardrails. `src/plugin-sdk/entrypoints.ts` separately tracks the supported bundled facades that remain public, SDK entrypoints backed by their bundled plugin until generic contracts replace -them: `plugin-sdk/discord`, `plugin-sdk/lmstudio`, `plugin-sdk/lmstudio-runtime`, -`plugin-sdk/matrix`, `plugin-sdk/mattermost`, -`plugin-sdk/memory-core-engine-runtime`, `plugin-sdk/provider-zai-endpoint`, `plugin-sdk/qa-runner-runtime`, `plugin-sdk/telegram-account`, -`plugin-sdk/tts-runtime`, and `plugin-sdk/zalouser`. Several of these are also deprecated for new code; see the per-row notes below. @@ -87,113 +73,95 @@ deprecated for new code; see the per-row notes below. | Subpath | Key exports | | --- | --- | | `plugin-sdk/channel-core` | `defineChannelPluginEntry`, `defineSetupPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `createChannelConfigUiHints` | - | `plugin-sdk/json-schema-runtime` | Cached JSON Schema validation helper for plugin-owned schemas | + | `plugin-sdk/json-schema-runtime` | Private-local after July 2026; Cached JSON Schema validation helper for plugin-owned schemas | | `plugin-sdk/channel-setup` | `createOptionalChannelSetupSurface`, `createOptionalChannelSetupAdapter`, `createOptionalChannelSetupWizard`, plus `DEFAULT_ACCOUNT_ID`, `createTopLevelChannelDmPolicy`, `setSetupChannelEnabled`, `splitSetupEntries` | | `plugin-sdk/setup` | Shared setup wizard helpers, setup translator, allowlist prompts, setup status builders | | `plugin-sdk/setup-runtime` | `createSetupTranslator`, `createPatchedAccountSetupAdapter`, `createEnvPatchedAccountSetupAdapter`, `createSetupInputPresenceValidator`, `noteChannelLookupFailure`, `noteChannelLookupSummary`, `promptResolvedAllowFrom`, `splitSetupEntries`, `createAllowlistSetupWizardProxy`, `createDelegatedSetupWizardProxy` | - | `plugin-sdk/setup-adapter-runtime` | Deprecated compatibility alias; use `plugin-sdk/setup-runtime` | | `plugin-sdk/setup-tools` | `formatCliCommand`, `detectBinary`, `extractArchive`, `resolveBrewExecutable`, `formatDocsLink`, `CONFIG_DIR` | | `plugin-sdk/account-core` | Multi-account config/action-gate helpers, default-account fallback helpers | | `plugin-sdk/account-id` | `DEFAULT_ACCOUNT_ID`, account-id normalization helpers | | `plugin-sdk/account-resolution` | Account lookup + default-fallback helpers | | `plugin-sdk/account-helpers` | Narrow account-list/account-action helpers | - | `plugin-sdk/access-groups` | Access-group allowlist parsing and redacted group diagnostics helpers | + | `plugin-sdk/access-groups` | Private-local after July 2026; Access-group allowlist parsing and redacted group diagnostics helpers | | `plugin-sdk/channel-pairing` | `createChannelPairingController` | | `plugin-sdk/channel-reply-pipeline` | Deprecated compatibility facade. Use `plugin-sdk/channel-outbound`. | | `plugin-sdk/channel-config-helpers` | `createHybridChannelConfigAdapter`, `resolveChannelDmAccess`, `resolveChannelDmAllowFrom`, `resolveChannelDmPolicy`, `normalizeChannelDmPolicy`, `normalizeLegacyDmAliases` | | `plugin-sdk/channel-config-schema` | Shared channel config schema primitives plus Zod and direct JSON/TypeBox builders | - | `plugin-sdk/bundled-channel-config-schema` | Bundled OpenClaw channel config schemas for maintained bundled plugins only | - | `plugin-sdk/chat-channel-ids` | `BUNDLED_CHAT_CHANNEL_IDS`, `BUNDLED_CHAT_CHANNEL_ENVELOPE_PREFIXES`, `ChatChannelId`. Canonical bundled/official chat channel ids plus formatter labels/aliases for plugins that need to recognize envelope-prefixed text without hardcoding their own table. | - | `plugin-sdk/channel-config-schema-legacy` | Deprecated compatibility alias for bundled-channel config schemas | - | `plugin-sdk/telegram-command-config` | Deprecated Telegram command-name/description normalization and duplicate/conflict checks; use plugin-local command config handling in new plugin code | - | `plugin-sdk/command-gating` | Narrow command authorization gate helpers | + | `plugin-sdk/bundled-channel-config-schema` | Private-local after July 2026; Bundled OpenClaw channel config schemas for maintained bundled plugins only | + | `plugin-sdk/chat-channel-ids` | Private-local after July 2026; `BUNDLED_CHAT_CHANNEL_IDS`, `BUNDLED_CHAT_CHANNEL_ENVELOPE_PREFIXES`, `ChatChannelId`. Canonical bundled/official chat channel ids plus formatter labels/aliases for plugins that need to recognize envelope-prefixed text without hardcoding their own table. | | `plugin-sdk/channel-policy` | `resolveChannelGroupRequireMention` | | `plugin-sdk/channel-ingress-runtime` | Experimental high-level channel ingress runtime resolver, implicit-mention policy resolver, and route fact builders for migrated channel receive paths. Prefer this over assembling effective allowlists, command allowlists, and legacy projections in each plugin. See [Channel ingress API](/plugins/sdk-channel-ingress). | | `plugin-sdk/channel-lifecycle` | Deprecated compatibility facade. Use `plugin-sdk/channel-outbound`. | | `plugin-sdk/channel-outbound` | Message lifecycle contracts plus reply pipeline options, receipts, live preview/streaming, lifecycle helpers, outbound identity, payload planning, durable sends, and message-send context helpers. See [Channel outbound API](/plugins/sdk-channel-outbound). | | `plugin-sdk/channel-message` | Deprecated compatibility alias for `plugin-sdk/channel-outbound`. | - | `plugin-sdk/channel-message-runtime` | Deprecated compatibility alias for `plugin-sdk/channel-outbound`. | | `plugin-sdk/inbound-envelope` | Shared inbound route + envelope builder helpers | | `plugin-sdk/inbound-reply-dispatch` | Deprecated compatibility facade. Use `plugin-sdk/channel-inbound` for inbound runners and dispatch predicates, and `plugin-sdk/channel-outbound` for message delivery helpers. | | `plugin-sdk/messaging-targets` | Deprecated target parsing alias; use `plugin-sdk/channel-targets` | - | `plugin-sdk/outbound-media` | Shared outbound media loading and hosted-media state helpers | - | `plugin-sdk/outbound-send-deps` | Deprecated compatibility facade. Use `plugin-sdk/channel-outbound`. | - | `plugin-sdk/outbound-runtime` | Deprecated compatibility facade. Use `plugin-sdk/channel-outbound`. | - | `plugin-sdk/poll-runtime` | Narrow poll normalization helpers | - | `plugin-sdk/thread-bindings-runtime` | Thread-binding lifecycle and adapter helpers | + | `plugin-sdk/outbound-media` | Private-local after July 2026; Shared outbound media loading and hosted-media state helpers | + | `plugin-sdk/poll-runtime` | Private-local after July 2026; Narrow poll normalization helpers | + | `plugin-sdk/thread-bindings-runtime` | Private-local after July 2026; Thread-binding lifecycle and adapter helpers | | `plugin-sdk/agent-media-payload` | Agent media payload roots and loaders | | `plugin-sdk/conversation-runtime` | Deprecated broad barrel for conversation/thread binding, pairing, and configured-binding helpers; prefer focused binding subpaths such as `plugin-sdk/thread-bindings-runtime` and `plugin-sdk/session-binding-runtime` | | `plugin-sdk/runtime-group-policy` | Runtime group-policy resolution helpers | | `plugin-sdk/channel-status` | Shared channel status snapshot/summary helpers | | `plugin-sdk/channel-config-primitives` | Narrow channel config-schema primitives | - | `plugin-sdk/channel-config-writes` | Channel config-write authorization helpers | + | `plugin-sdk/channel-config-writes` | Private-local after July 2026; Channel config-write authorization helpers | | `plugin-sdk/channel-plugin-common` | Shared channel plugin prelude exports | | `plugin-sdk/allowlist-config-edit` | Allowlist config edit/read helpers | | `plugin-sdk/group-access` | Deprecated group-access decision helpers; use `resolveChannelMessageIngress` from `plugin-sdk/channel-ingress-runtime` | - | `plugin-sdk/direct-dm`, `plugin-sdk/direct-dm-access` | Deprecated compatibility facades. Use `plugin-sdk/channel-inbound`. | - | `plugin-sdk/direct-dm-guard-policy` | Narrow direct-DM pre-crypto guard policy helpers | + | `plugin-sdk/direct-dm-guard-policy` | Private-local after July 2026; Narrow direct-DM pre-crypto guard policy helpers | | `plugin-sdk/discord` | Deprecated Discord compatibility facade for published `@openclaw/discord@2026.3.13` and tracked owner compatibility; new plugins should use generic channel SDK subpaths | | `plugin-sdk/telegram-account` | Deprecated Telegram account-resolution compatibility facade for tracked owner compatibility; new plugins should use injected runtime helpers or generic channel SDK subpaths | - | `plugin-sdk/zalouser` | Deprecated Zalo Personal compatibility facade for published Lark/Zalo packages that still import sender command authorization; new plugins should use generic channel SDK subpaths | | `plugin-sdk/interactive-runtime` | Semantic message presentation, delivery, and legacy interactive reply helpers. See [Message Presentation](/plugins/message-presentation) | | `plugin-sdk/question-gateway-runtime` | Resolve runtime-authored `ask_user` choices through the Gateway from channel interaction handlers | | `plugin-sdk/channel-inbound` | Shared inbound helpers for event classification, context building, formatting, roots, debounce, mention matching, mention-policy, and inbound logging | | `plugin-sdk/channel-inbound-debounce` | Narrow inbound debounce helpers | - | `plugin-sdk/channel-mention-gating` | Narrow mention-policy, mention marker, and mention text helpers without the broader inbound runtime surface | - | `plugin-sdk/channel-envelope`, `plugin-sdk/channel-inbound-roots`, `plugin-sdk/channel-location`, `plugin-sdk/channel-logging` | Deprecated compatibility facades. Use `plugin-sdk/channel-inbound` or `plugin-sdk/channel-outbound`. | - | `plugin-sdk/channel-pairing-paths` | Deprecated compatibility facade. Use `plugin-sdk/channel-pairing`. | - | `plugin-sdk/channel-reply-options-runtime` | Deprecated compatibility facade. Use `plugin-sdk/channel-outbound`. | + | `plugin-sdk/channel-mention-gating` | Private-local after July 2026; Narrow mention-policy, mention marker, and mention text helpers without the broader inbound runtime surface | | `plugin-sdk/channel-streaming` | Deprecated compatibility facade. Use `plugin-sdk/channel-outbound`. | | `plugin-sdk/channel-send-result` | Reply result types | | `plugin-sdk/channel-actions` | Channel message-action helpers, plus deprecated native schema helpers kept for plugin compatibility | - | `plugin-sdk/channel-route` | Shared route normalization, parser-driven target resolution, thread-id stringification, dedupe/compact route keys, parsed-target types, and route/target comparison helpers | - | `plugin-sdk/channel-targets` | Target parsing helpers; route comparison callers should use `plugin-sdk/channel-route` | + | `plugin-sdk/channel-route` | Private-local after July 2026; Shared route normalization, parser-driven target resolution, thread-id stringification, dedupe/compact route keys, parsed-target types, and route/target comparison helpers | + | `plugin-sdk/channel-targets` | Private-local after July 2026; Target parsing helpers; route comparison callers should use `plugin-sdk/channel-route` | | `plugin-sdk/channel-contract` | Channel contract types | | `plugin-sdk/channel-feedback` | Feedback/reaction wiring | -Deprecated channel helper families stay available only for published-plugin -compatibility. The removal plan is: keep them through the external plugin -migration window, keep repo/bundled plugins on `channel-inbound` and -`channel-outbound`, then remove the compatibility subpaths in the next major -SDK cleanup. This applies to the old channel message/runtime, channel -streaming, direct-DM access, inbound helper splinter, reply-options, -and pairing-path families. +Later-window channel compatibility subpaths remain public only through their +registry dates. July aliases such as direct-DM access, reply-options, pairing +paths, and channel runtime splinters have been removed; bundled-only helpers +are private-local. | Subpath | Key exports | | --- | --- | - | `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` | - | `plugin-sdk/lmstudio` | Supported LM Studio provider facade for setup, catalog discovery, and runtime model preparation | - | `plugin-sdk/lmstudio-runtime` | Supported LM Studio runtime facade for local server defaults, model discovery, request headers, and loaded-model helpers | - | `plugin-sdk/provider-setup` | Curated local/self-hosted provider setup helpers | - | `plugin-sdk/self-hosted-provider-setup` | Deprecated OpenAI-compatible self-hosted setup helpers; use `plugin-sdk/provider-setup` or plugin-owned setup helpers | - | `plugin-sdk/cli-backend` | CLI backend defaults + watchdog constants | - | `plugin-sdk/provider-auth-runtime` | Provider auth runtime helpers: OAuth loopback flow, token exchange, auth persistence, and API-key resolution | - | `plugin-sdk/provider-oauth-runtime` | Generic provider OAuth callback types, callback-page rendering, PKCE/state helpers, authorization-input parsing, token-expiry helpers, and abort helpers | - | `plugin-sdk/provider-auth-api-key` | API-key onboarding/profile-write helpers such as `upsertApiKeyProfile` | - | `plugin-sdk/provider-auth-result` | Standard OAuth auth-result builder | - | `plugin-sdk/provider-env-vars` | Provider auth env-var lookup helpers | + | `plugin-sdk/provider-entry` | Private-local after July 2026; `defineSingleProviderPluginEntry` | + | `plugin-sdk/provider-setup` | Private-local after July 2026; Curated local/self-hosted provider setup helpers | + | `plugin-sdk/cli-backend` | Private-local after July 2026; CLI backend defaults + watchdog constants | + | `plugin-sdk/provider-auth-runtime` | Private-local after July 2026; Provider auth runtime helpers: OAuth loopback flow, token exchange, auth persistence, and API-key resolution | + | `plugin-sdk/provider-oauth-runtime` | Private-local after July 2026; Generic provider OAuth callback types, callback-page rendering, PKCE/state helpers, authorization-input parsing, token-expiry helpers, and abort helpers | + | `plugin-sdk/provider-auth-api-key` | Private-local after July 2026; API-key onboarding/profile-write helpers such as `upsertApiKeyProfile` | + | `plugin-sdk/provider-auth-result` | Private-local after July 2026; Standard OAuth auth-result builder | + | `plugin-sdk/provider-env-vars` | Private-local after July 2026; Provider auth env-var lookup helpers | | `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials`, OpenAI Codex auth-import helpers, deprecated `resolveOpenClawAgentDir` compatibility export | - | `plugin-sdk/provider-model-shared` | `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `selectPreferredLocalModelId`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and shared model-id normalization helpers | - | `plugin-sdk/provider-catalog-live-runtime` | Live provider model catalog helpers for guarded `/models`-style discovery: `buildLiveModelProviderConfig`, `fetchLiveProviderModelRows`, `getCachedLiveProviderModelRows`, `fetchLiveProviderModelIds`, `LiveModelCatalogHttpError`, `clearLiveCatalogCacheForTests`, model-id filtering, TTL cache, and static fallback | + | `plugin-sdk/provider-model-shared` | Private-local after July 2026; `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `selectPreferredLocalModelId`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and shared model-id normalization helpers | + | `plugin-sdk/provider-catalog-live-runtime` | Private-local after July 2026; Live provider model catalog helpers for guarded `/models`-style discovery: `buildLiveModelProviderConfig`, `fetchLiveProviderModelRows`, `getCachedLiveProviderModelRows`, `fetchLiveProviderModelIds`, `LiveModelCatalogHttpError`, `clearLiveCatalogCacheForTests`, model-id filtering, TTL cache, and static fallback | | `plugin-sdk/provider-catalog-runtime` | Provider catalog augmentation runtime hook and plugin-provider registry seams for contract tests | - | `plugin-sdk/provider-catalog-shared` | `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `buildManifestModelProviderConfig`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` | - | `plugin-sdk/provider-http` | Generic provider HTTP/endpoint capability helpers, provider HTTP errors, and audio transcription multipart form helpers | - | `plugin-sdk/provider-web-fetch-contract` | Narrow web-fetch config/selection contract helpers such as `enablePluginInConfig` and `WebFetchProviderPlugin` | - | `plugin-sdk/provider-web-fetch` | Web-fetch provider registration/cache helpers | - | `plugin-sdk/provider-web-search-config-contract` | Narrow web-search config/credential helpers for providers that do not need plugin-enable wiring | - | `plugin-sdk/provider-web-search-contract` | Narrow web-search config/credential contract helpers such as `createWebSearchProviderContractFields`, `enablePluginInConfig`, `resolveProviderWebSearchPluginConfig`, and scoped credential setters/getters | - | `plugin-sdk/provider-web-search` | Web-search provider registration/cache/runtime helpers | - | `plugin-sdk/embedding-providers` | General embedding provider types and read helpers, including `EmbeddingProviderAdapter`, `getEmbeddingProvider(...)`, and `listEmbeddingProviders(...)`; plugins register providers through `api.registerEmbeddingProvider(...)` so manifest ownership is enforced | - | `plugin-sdk/provider-tools` | `ProviderToolCompatFamily`, `buildProviderToolCompatFamilyHooks`, and DeepSeek/Gemini/OpenAI schema cleanup + diagnostics | - | `plugin-sdk/provider-usage` | Provider usage snapshot types, shared usage fetch helpers, and provider fetchers such as `fetchClaudeUsage` | - | `plugin-sdk/provider-stream` | `ProviderStreamFamily`, `buildProviderStreamFamilyHooks`, `composeProviderStreamWrappers`, stream wrapper types, plain-text tool-call compat, and shared Anthropic/Google/Kilocode/MiniMax/Moonshot/OpenAI/OpenRouter/Z.AI wrapper helpers | - | `plugin-sdk/provider-stream-shared` | Public shared provider stream wrapper helpers including `composeProviderStreamWrappers`, `createOpenAICompatibleCompletionsThinkingOffWrapper`, `createPlainTextToolCallCompatWrapper`, `createPayloadPatchStreamWrapper`, `createToolStreamWrapper`, `normalizeOpenAICompatibleReasoningPayload`, `setQwenChatTemplateThinking`, and Anthropic/DeepSeek/OpenAI-compatible stream utilities | - | `plugin-sdk/provider-transport-runtime` | Native provider transport helpers such as guarded fetch, tool-result text extraction, transport message transforms, and writable transport event streams | - | `plugin-sdk/provider-onboard` | Onboarding config patch helpers | - | `plugin-sdk/global-singleton` | Process-local singleton/map/cache helpers | - | `plugin-sdk/group-activation` | Narrow group activation mode and command parsing helpers | + | `plugin-sdk/provider-catalog-shared` | Private-local after July 2026; `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `buildManifestModelProviderConfig`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` | + | `plugin-sdk/provider-http` | Private-local after July 2026; Generic provider HTTP/endpoint capability helpers, provider HTTP errors, and audio transcription multipart form helpers | + | `plugin-sdk/provider-web-fetch-contract` | Private-local after July 2026; Narrow web-fetch config/selection contract helpers such as `enablePluginInConfig` and `WebFetchProviderPlugin` | + | `plugin-sdk/provider-web-fetch` | Private-local after July 2026; Web-fetch provider registration/cache helpers | + | `plugin-sdk/provider-web-search-config-contract` | Private-local after July 2026; Narrow web-search config/credential helpers for providers that do not need plugin-enable wiring | + | `plugin-sdk/provider-web-search-contract` | Private-local after July 2026; Narrow web-search config/credential contract helpers such as `createWebSearchProviderContractFields`, `enablePluginInConfig`, `resolveProviderWebSearchPluginConfig`, and scoped credential setters/getters | + | `plugin-sdk/provider-web-search` | Private-local after July 2026; Web-search provider registration/cache/runtime helpers | + | `plugin-sdk/embedding-providers` | Private-local after July 2026; General embedding provider types and read helpers, including `EmbeddingProviderAdapter`, `getEmbeddingProvider(...)`, and `listEmbeddingProviders(...)`; plugins register providers through `api.registerEmbeddingProvider(...)` so manifest ownership is enforced | + | `plugin-sdk/provider-tools` | Private-local after July 2026; `ProviderToolCompatFamily`, `buildProviderToolCompatFamilyHooks`, and DeepSeek/Gemini/OpenAI schema cleanup + diagnostics | + | `plugin-sdk/provider-usage` | Private-local after July 2026; Provider usage snapshot types, shared usage fetch helpers, and provider fetchers such as `fetchClaudeUsage` | + | `plugin-sdk/provider-stream` | Private-local after July 2026; `ProviderStreamFamily`, `buildProviderStreamFamilyHooks`, `composeProviderStreamWrappers`, stream wrapper types, plain-text tool-call compat, and shared Anthropic/Google/Kilocode/MiniMax/Moonshot/OpenAI/OpenRouter/Z.AI wrapper helpers | + | `plugin-sdk/provider-stream-shared` | Private-local after July 2026; Public shared provider stream wrapper helpers including `composeProviderStreamWrappers`, `createOpenAICompatibleCompletionsThinkingOffWrapper`, `createPlainTextToolCallCompatWrapper`, `createPayloadPatchStreamWrapper`, `createToolStreamWrapper`, `normalizeOpenAICompatibleReasoningPayload`, `setQwenChatTemplateThinking`, and Anthropic/DeepSeek/OpenAI-compatible stream utilities | + | `plugin-sdk/provider-transport-runtime` | Private-local after July 2026; Native provider transport helpers such as guarded fetch, tool-result text extraction, transport message transforms, and writable transport event streams | + | `plugin-sdk/provider-onboard` | Private-local after July 2026; Onboarding config patch helpers | + | `plugin-sdk/global-singleton` | Private-local after July 2026; Process-local singleton/map/cache helpers | + | `plugin-sdk/group-activation` | Private-local after July 2026; Narrow group activation mode and command parsing helpers | Provider usage snapshots normally report one or more quota `windows`, each with @@ -212,28 +180,26 @@ usage endpoint failed or returned no usable usage data. | `plugin-sdk/approval-client-runtime` | Native exec approval profile/filter helpers | | `plugin-sdk/approval-delivery-runtime` | Native approval capability/delivery adapters | | `plugin-sdk/approval-gateway-runtime` | Shared approval gateway resolver | - | `plugin-sdk/approval-reference-runtime` | Deterministic durable-locator helper for transport-limited approval callbacks | + | `plugin-sdk/approval-reference-runtime` | Private-local after July 2026; Deterministic durable-locator helper for transport-limited approval callbacks | | `plugin-sdk/approval-handler-adapter-runtime` | Lightweight native approval adapter loading helpers for hot channel entrypoints | | `plugin-sdk/approval-handler-runtime` | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough | | `plugin-sdk/approval-native-runtime` | Native approval target, account-binding, route-gate, forwarding fallback, and local native exec prompt suppression helpers | - | `plugin-sdk/approval-reaction-runtime` | Hardcoded approval reaction bindings, reaction prompt payloads, reaction target stores, reaction hint text helpers, and compatibility export for local native exec prompt suppression | + | `plugin-sdk/approval-reaction-runtime` | Private-local after July 2026; Hardcoded approval reaction bindings, reaction prompt payloads, reaction target stores, reaction hint text helpers, and compatibility export for local native exec prompt suppression | | `plugin-sdk/approval-reply-runtime` | Exec/plugin approval reply payload helpers | | `plugin-sdk/approval-runtime` | Exec/plugin approval payload helpers, approval-capability builders, approval auth/profile helpers, native approval routing/runtime helpers, and structured approval display helpers such as `formatApprovalDisplayPath` | - | `plugin-sdk/reply-dedupe` | Deprecated narrow inbound reply dedupe reset helpers | | `plugin-sdk/command-auth-native` | Native command auth, dynamic argument menu formatting, and native session-target helpers | | `plugin-sdk/command-detection` | Shared command detection helpers | | `plugin-sdk/command-primitives-runtime` | Lightweight command text predicates for hot channel paths | - | `plugin-sdk/command-surface` | Command-body normalization and command-surface helpers | + | `plugin-sdk/command-surface` | Private-local after July 2026; Command-body normalization and command-surface helpers | | `plugin-sdk/allow-from` | `formatAllowFromLowercase` | - | `plugin-sdk/provider-auth-login-flow-runtime` | Lazy provider auth login flow helpers for private channel and Web UI device-code pairing | + | `plugin-sdk/provider-auth-login-flow-runtime` | Private-local after July 2026; Lazy provider auth login flow helpers for private channel and Web UI device-code pairing | | `plugin-sdk/channel-secret-runtime` | Deprecated broad secret-contract surface (`collectSimpleChannelFieldAssignments`, `getChannelSurface`, `pushAssignment`, secret target types); prefer the focused subpaths below | | `plugin-sdk/channel-secret-basic-runtime` | Narrow secret-contract exports and target-registry builders for non-TTS channel/plugin secret surfaces | - | `plugin-sdk/channel-secret-tts-runtime` | Narrow nested channel TTS secret assignment helpers | + | `plugin-sdk/channel-secret-tts-runtime` | Private-local after July 2026; Narrow nested channel TTS secret assignment helpers | | `plugin-sdk/secret-ref-runtime` | Narrow SecretRef typing, resolution, and plan-target path lookup for secret-contract/config parsing | - | `plugin-sdk/secret-provider-integration` | Type-only SecretRef provider integration manifest and preset contracts for plugins that publish external secret provider presets | | `plugin-sdk/security-runtime` | Deprecated broad barrel for trust, DM gating, root-bounded file/path helpers including create-only writes, sync/async atomic file replacement, sibling temp writes, cross-device move fallback, private file-store helpers, symlink-parent guards, external-content, sensitive text redaction, constant-time secret comparison, and secret-collection helpers; prefer focused security/SSRF/secret subpaths | | `plugin-sdk/ssrf-policy` | Host allowlist and private-network SSRF policy helpers | - | `plugin-sdk/ssrf-dispatcher` | Narrow pinned-dispatcher helpers without the broad infra runtime surface | + | `plugin-sdk/ssrf-dispatcher` | Private-local after July 2026; Narrow pinned-dispatcher helpers without the broad infra runtime surface | | `plugin-sdk/ssrf-runtime` | Pinned-dispatcher, SSRF-guarded fetch, SSRF error, and SSRF policy helpers | | `plugin-sdk/secret-input` | Secret input parsing helpers | | `plugin-sdk/webhook-ingress` | Webhook request/target helpers and raw websocket/body coercion | @@ -245,107 +211,105 @@ usage endpoint failed or returned no usable usage data. | --- | --- | | `plugin-sdk/runtime` | Runtime/logging/backup helpers, plugin install-path warnings, and process helpers | | `plugin-sdk/runtime-env` | Narrow runtime env, logger, timeout, retry, and backoff helpers | - | `plugin-sdk/browser-config` | Supported browser config facade for normalized profile/defaults, CDP URL parsing, and browser-control auth helpers | - | `plugin-sdk/agent-harness-task-runtime` | Generic task lifecycle and completion delivery helpers for harness-backed agents using a host-issued task scope | - | `plugin-sdk/codex-mcp-projection` | Reserved bundled Codex helper for projecting user MCP server config into Codex thread config; not for third-party plugins | + | `plugin-sdk/browser-config` | Private-local after July 2026; Supported browser config facade for normalized profile/defaults, CDP URL parsing, and browser-control auth helpers | + | `plugin-sdk/agent-harness-task-runtime` | Private-local after July 2026; Generic task lifecycle and completion delivery helpers for harness-backed agents using a host-issued task scope | + | `plugin-sdk/codex-mcp-projection` | Private-local after July 2026; Reserved bundled Codex helper for projecting user MCP server config into Codex thread config; not for third-party plugins | | `plugin-sdk/codex-native-task-runtime` | Repo-local bundled Codex helper for native task mirror/runtime wiring; not a package export | | `plugin-sdk/channel-runtime-context` | Generic channel runtime-context registration and lookup helpers | | `plugin-sdk/matrix` | Deprecated Matrix compatibility facade for older third-party channel packages; new plugins should import `plugin-sdk/run-command` directly | - | `plugin-sdk/mattermost` | Deprecated Mattermost compatibility facade for older third-party channel packages; new plugins should import generic SDK subpaths directly | | `plugin-sdk/runtime-store` | `createPluginRuntimeStore` | | `plugin-sdk/plugin-runtime` | Deprecated broad barrel for plugin command/hook/http/interactive helpers; prefer focused plugin runtime subpaths | | `plugin-sdk/hook-runtime` | Deprecated broad barrel for webhook/internal hook pipeline helpers; prefer focused hook/plugin runtime subpaths | | `plugin-sdk/lazy-runtime` | Lazy runtime import/binding helpers such as `createLazyRuntimeModule`, `createLazyRuntimeMethod`, and `createLazyRuntimeSurface` | - | `plugin-sdk/process-runtime` | Process exec helpers | - | `plugin-sdk/node-host` | Node-host executable resolution and PTY resume helpers | - | `plugin-sdk/cli-runtime` | Deprecated broad barrel for CLI formatting, wait, version, argument-invocation, and lazy command-group helpers; prefer focused CLI/runtime subpaths | - | `plugin-sdk/qa-runner-runtime` | Supported facade exposing plugin QA scenarios through the CLI command surface | - | `plugin-sdk/tts-runtime` | Supported facade for text-to-speech config schemas and runtime helpers | + | `plugin-sdk/process-runtime` | Private-local after July 2026; Process exec helpers | + | `plugin-sdk/node-host` | Private-local after July 2026; Node-host executable resolution and PTY resume helpers | + | `plugin-sdk/cli-runtime` | Private-local after July 2026; Deprecated broad barrel for CLI formatting, wait, version, argument-invocation, and lazy command-group helpers; prefer focused CLI/runtime subpaths | + | `plugin-sdk/qa-runner-runtime` | Private-local after July 2026; Supported facade exposing plugin QA scenarios through the CLI command surface | + | `plugin-sdk/tts-runtime` | Private-local after July 2026; Supported facade for text-to-speech config schemas and runtime helpers | | `plugin-sdk/gateway-method-runtime` | Reserved Gateway method dispatch helper for plugin HTTP routes that declare `contracts.gatewayMethodDispatch: ["authenticated-request"]` | | `plugin-sdk/gateway-runtime` | Gateway client, event-loop-ready client start helper, gateway CLI RPC, gateway protocol errors, advertised LAN host resolution, and channel-status patch helpers | | `plugin-sdk/config-contracts` | Focused type-only config surface for plugin config shapes such as `OpenClawConfig` and channel/provider config types | | `plugin-sdk/plugin-config-runtime` | Runtime plugin-config helpers such as `mergeDeep`, `requireRuntimeConfig`, `resolvePluginConfigObject`, and `resolveLivePluginConfigObject` | | `plugin-sdk/config-mutation` | Transactional config mutation helpers such as `mutateConfigFile`, `replaceConfigFile`, and `logConfigUpdated` | - | `plugin-sdk/message-tool-delivery-hints` | Shared message-tool delivery metadata hint strings | + | `plugin-sdk/message-tool-delivery-hints` | Private-local after July 2026; Shared message-tool delivery metadata hint strings | | `plugin-sdk/runtime-config-snapshot` | Current process config snapshot helpers such as `getRuntimeConfig`, `getRuntimeConfigSnapshot`, and test snapshot setters | - | `plugin-sdk/text-autolink-runtime` | File-reference autolink detection without the broad text barrel | + | `plugin-sdk/text-autolink-runtime` | Private-local after July 2026; File-reference autolink detection without the broad text barrel | | `plugin-sdk/reply-runtime` | Shared inbound/reply runtime helpers, chunking, dispatch, heartbeat, reply planner | | `plugin-sdk/reply-dispatch-runtime` | Narrow reply dispatch/finalize and conversation-label helpers | | `plugin-sdk/reply-history` | Shared short-window reply-history helpers. New message-turn code should use `createChannelHistoryWindow`; lower-level map helpers remain deprecated compatibility exports only | - | `plugin-sdk/reply-reference` | `createReplyReferencePlanner` | + | `plugin-sdk/reply-reference` | Private-local after July 2026; `createReplyReferencePlanner` | | `plugin-sdk/reply-chunking` | Narrow text/markdown chunking helpers | | `plugin-sdk/session-store-runtime` | Session workflow helpers (`getSessionEntry`, `listSessionEntries`, `patchSessionEntry`, `upsertSessionEntry`), repair/lifecycle helpers (`deleteSessionEntry`, `cleanupSessionLifecycleArtifacts`, `resolveSessionStoreBackupPaths`), marker helpers for transitional `sessionFile` values, bounded recent user/assistant transcript text reads by session identity, session store path/session-key helpers, and updated-at reads, without broad config writes/maintenance imports | - | `plugin-sdk/session-transcript-runtime` | Transcript identity, bounded raw and visible cursors, scoped target/read/write helpers, visible message-entry projection, update publishing, write locks, and transcript memory hit keys | - | `plugin-sdk/sqlite-runtime` | Focused SQLite agent-schema, path, and transaction helpers for first-party runtime, without database lifecycle controls | - | `plugin-sdk/cron-store-runtime` | Cron store path/load/save helpers | + | `plugin-sdk/session-transcript-runtime` | Private-local after July 2026; Transcript identity, bounded raw and visible cursors, scoped target/read/write helpers, visible message-entry projection, update publishing, write locks, and transcript memory hit keys | + | `plugin-sdk/sqlite-runtime` | Private-local after July 2026; Focused SQLite agent-schema, path, and transaction helpers for first-party runtime, without database lifecycle controls | + | `plugin-sdk/cron-store-runtime` | Private-local after July 2026; Cron store path/load/save helpers | | `plugin-sdk/state-paths` | State/OAuth dir path helpers | - | `plugin-sdk/plugin-state-runtime` | Plugin-scoped keyed-state, BLOB, and cooperative SQLite lease contracts plus connection pragma, verified WAL maintenance, and atomic STRICT-schema migration helpers. Lease callbacks receive an abort signal and typed errors distinguish timeout, cancellation, lost ownership, invalid input, and storage failure | + | `plugin-sdk/plugin-state-runtime` | Private-local after July 2026; Plugin-scoped keyed-state, BLOB, and cooperative SQLite lease contracts plus connection pragma, verified WAL maintenance, and atomic STRICT-schema migration helpers. Lease callbacks receive an abort signal and typed errors distinguish timeout, cancellation, lost ownership, invalid input, and storage failure | | `plugin-sdk/routing` | Route/session-key/account binding helpers such as `resolveAgentRoute`, `buildAgentSessionKey`, and `resolveDefaultAgentBoundAccountId` | | `plugin-sdk/status-helpers` | Shared channel/account status summary helpers, runtime-state defaults, and issue metadata helpers | - | `plugin-sdk/target-resolver-runtime` | Shared target resolver helpers | - | `plugin-sdk/string-normalization-runtime` | Slug/string normalization helpers | - | `plugin-sdk/request-url` | Extract string URLs from fetch/request-like inputs | + | `plugin-sdk/target-resolver-runtime` | Private-local after July 2026; Shared target resolver helpers | + | `plugin-sdk/string-normalization-runtime` | Private-local after July 2026; Slug/string normalization helpers | + | `plugin-sdk/request-url` | Private-local after July 2026; Extract string URLs from fetch/request-like inputs | | `plugin-sdk/run-command` | Timed command runner with normalized stdout/stderr results | | `plugin-sdk/param-readers` | Common tool/CLI param readers | | `plugin-sdk/tool-plugin` | Define a simple typed agent-tool plugin and expose static metadata for manifest generation | - | `plugin-sdk/tool-payload` | Extract normalized payloads from tool result objects | + | `plugin-sdk/tool-payload` | Private-local after July 2026; Extract normalized payloads from tool result objects | | `plugin-sdk/tool-send` | Extract canonical send target fields from tool args | - | `plugin-sdk/sandbox` | Sandbox backend types and SSH/OpenShell command helpers, including fail-fast exec command preflight | + | `plugin-sdk/sandbox` | Private-local after July 2026; Sandbox backend types and SSH/OpenShell command helpers, including fail-fast exec command preflight | | `plugin-sdk/temp-path` | Shared temp-download path helpers and private secure temp workspaces | | `plugin-sdk/logging-core` | Subsystem logger and redaction helpers | - | `plugin-sdk/markdown-table-runtime` | Markdown table mode and conversion helpers | + | `plugin-sdk/markdown-table-runtime` | Private-local after July 2026; Markdown table mode and conversion helpers | | `plugin-sdk/model-session-runtime` | Model/session override helpers such as `applyModelOverrideToSessionEntry` and `resolveAgentMaxConcurrent` | - | `plugin-sdk/talk-config-runtime` | Talk provider config resolution helpers | + | `plugin-sdk/talk-config-runtime` | Private-local after July 2026; Talk provider config resolution helpers | | `plugin-sdk/json-store` | Small JSON state read/write helpers | - | `plugin-sdk/json-unsafe-integers` | JSON parsing helpers that preserve unsafe integer literals as strings | - | `plugin-sdk/file-lock` | Re-entrant file-lock helpers plus Doctor-safe reclaim of definitely stale, unchanged retired lock sidecars | + | `plugin-sdk/json-unsafe-integers` | Private-local after July 2026; JSON parsing helpers that preserve unsafe integer literals as strings | + | `plugin-sdk/file-lock` | Private-local after July 2026; Re-entrant file-lock helpers plus Doctor-safe reclaim of definitely stale, unchanged retired lock sidecars | | `plugin-sdk/persistent-dedupe` | Disk-backed dedupe cache helpers | | `plugin-sdk/ingress-effect-once` | Durable claim/commit guard for non-idempotent ingress side effects | - | `plugin-sdk/acp-runtime` | ACP runtime/session and reply-dispatch helpers | - | `plugin-sdk/acp-runtime-backend` | Lightweight ACP backend registration and reply-dispatch helpers for startup-loaded plugins | - | `plugin-sdk/acp-binding-resolve-runtime` | Read-only ACP binding resolution without lifecycle startup imports | + | `plugin-sdk/acp-runtime` | Private-local after July 2026; ACP runtime/session and reply-dispatch helpers | + | `plugin-sdk/acp-runtime-backend` | Private-local after July 2026; Lightweight ACP backend registration and reply-dispatch helpers for startup-loaded plugins | + | `plugin-sdk/acp-binding-resolve-runtime` | Private-local after July 2026; Read-only ACP binding resolution without lifecycle startup imports | | `plugin-sdk/agent-config-primitives` | Deprecated agent runtime config-schema primitives; import schema primitives from a maintained plugin-owned surface | | `plugin-sdk/boolean-param` | Loose boolean param reader | - | `plugin-sdk/dangerous-name-runtime` | Dangerous-name matching resolution helpers | + | `plugin-sdk/dangerous-name-runtime` | Private-local after July 2026; Dangerous-name matching resolution helpers | | `plugin-sdk/device-bootstrap` | Device bootstrap and pairing token helpers, including `BOOTSTRAP_HANDOFF_OPERATOR_SCOPES` | | `plugin-sdk/extension-shared` | Shared passive-channel, status, and ambient proxy helper primitives | | `plugin-sdk/models-provider-runtime` | `/models` command/provider reply helpers | | `plugin-sdk/skill-commands-runtime` | Skill command listing helpers | | `plugin-sdk/native-command-registry` | Native command registry/build/serialize helpers | | `plugin-sdk/agent-harness` | Experimental trusted-plugin surface for low-level agent harnesses: harness types, active-run steer/abort helpers, OpenClaw tool bridge helpers, runtime-plan tool policy helpers, terminal outcome classification, tool progress formatting/detail helpers, and attempt result utilities | - | `plugin-sdk/provider-zai-endpoint` | Deprecated Z.AI provider-owned endpoint detection facade; use the Z.AI plugin public API | - | `plugin-sdk/async-lock-runtime` | Process-local async lock helper for small runtime state files | - | `plugin-sdk/channel-activity-runtime` | Channel activity telemetry helper | - | `plugin-sdk/concurrency-runtime` | Bounded async task concurrency helper | + | `plugin-sdk/async-lock-runtime` | Private-local after July 2026; Process-local async lock helper for small runtime state files | + | `plugin-sdk/channel-activity-runtime` | Private-local after July 2026; Channel activity telemetry helper | + | `plugin-sdk/concurrency-runtime` | Private-local after July 2026; Bounded async task concurrency helper | | `plugin-sdk/dedupe-runtime` | In-memory and persistent-backed dedupe cache helpers | - | `plugin-sdk/delivery-queue-runtime` | Outbound pending-delivery drain helper | - | `plugin-sdk/file-access-runtime` | Safe local-file and media-source path helpers | - | `plugin-sdk/heartbeat-runtime` | Heartbeat wake, event, and visibility helpers | - | `plugin-sdk/expect-runtime` | Required-value assertion helper for provable runtime invariants | - | `plugin-sdk/number-runtime` | Numeric coercion helper | - | `plugin-sdk/secure-random-runtime` | Secure token/UUID helpers | - | `plugin-sdk/system-event-runtime` | System event queue helpers | - | `plugin-sdk/transport-ready-runtime` | Transport readiness wait helper | - | `plugin-sdk/exec-approvals-runtime` | Exec approval policy file helpers without the broad infra-runtime barrel | + | `plugin-sdk/delivery-queue-runtime` | Private-local after July 2026; Outbound pending-delivery drain helper | + | `plugin-sdk/file-access-runtime` | Private-local after July 2026; Safe local-file and media-source path helpers | + | `plugin-sdk/heartbeat-runtime` | Private-local after July 2026; Heartbeat wake, event, and visibility helpers | + | `plugin-sdk/expect-runtime` | Private-local after July 2026; Required-value assertion helper for provable runtime invariants | + | `plugin-sdk/number-runtime` | Private-local after July 2026; Numeric coercion helper | + | `plugin-sdk/secure-random-runtime` | Private-local after July 2026; Secure token/UUID helpers | + | `plugin-sdk/system-event-runtime` | Private-local after July 2026; System event queue helpers | + | `plugin-sdk/transport-ready-runtime` | Private-local after July 2026; Transport readiness wait helper | + | `plugin-sdk/exec-approvals-runtime` | Private-local after July 2026; Exec approval policy file helpers without the broad infra-runtime barrel | | `plugin-sdk/infra-runtime` | Deprecated compatibility shim; use the focused runtime subpaths above | | `plugin-sdk/collection-runtime` | Small bounded cache helpers | | `plugin-sdk/diagnostic-runtime` | Diagnostic flag, event, and trace-context helpers | | `plugin-sdk/error-runtime` | Error graph, formatting, shared error classification helpers, `PlatformMessageNotDispatchedError`, `isApprovalNotFoundError` | - | `plugin-sdk/fetch-runtime` | Wrapped fetch, proxy, EnvHttpProxyAgent option, and pinned lookup helpers | - | `plugin-sdk/runtime-fetch` | Dispatcher-aware runtime fetch without proxy/guarded-fetch imports | - | `plugin-sdk/inline-image-data-url-runtime` | Inline image data URL sanitizer and signature sniffing helpers without the broad media runtime surface | - | `plugin-sdk/response-limit-runtime` | Byte-, idle-, and deadline-bounded response-body readers without the broad media runtime surface | - | `plugin-sdk/session-binding-runtime` | Current conversation binding state without configured binding routing or pairing stores | - | `plugin-sdk/context-visibility-runtime` | Context visibility resolution and supplemental context filtering without broad config/security imports | + | `plugin-sdk/fetch-runtime` | Private-local after July 2026; Wrapped fetch, proxy, EnvHttpProxyAgent option, and pinned lookup helpers | + | `plugin-sdk/runtime-fetch` | Private-local after July 2026; Dispatcher-aware runtime fetch without proxy/guarded-fetch imports | + | `plugin-sdk/inline-image-data-url-runtime` | Private-local after July 2026; Inline image data URL sanitizer and signature sniffing helpers without the broad media runtime surface | + | `plugin-sdk/response-limit-runtime` | Private-local after July 2026; Byte-, idle-, and deadline-bounded response-body readers without the broad media runtime surface | + | `plugin-sdk/session-binding-runtime` | Private-local after July 2026; Current conversation binding state without configured binding routing or pairing stores | + | `plugin-sdk/context-visibility-runtime` | Private-local after July 2026; Context visibility resolution and supplemental context filtering without broad config/security imports | | `plugin-sdk/string-coerce-runtime` | Narrow primitive record/string coercion and normalization helpers without markdown/logging imports | - | `plugin-sdk/html-entity-runtime` | Single-pass semicolon-terminated HTML5 entity decoding without broad text utilities | - | `plugin-sdk/text-utility-runtime` | Low-level text and path helpers, including five-entity HTML escaping | + | `plugin-sdk/html-entity-runtime` | Private-local after July 2026; Single-pass semicolon-terminated HTML5 entity decoding without broad text utilities | + | `plugin-sdk/text-utility-runtime` | Private-local after July 2026; Low-level text and path helpers, including five-entity HTML escaping | | `plugin-sdk/widget-html` | Complete-document detection, size validation, and tool input errors for self-contained HTML widgets | - | `plugin-sdk/host-runtime` | Hostname and SCP host normalization helpers | - | `plugin-sdk/retry-runtime` | Retry config and retry runner helpers | + | `plugin-sdk/host-runtime` | Private-local after July 2026; Hostname and SCP host normalization helpers | + | `plugin-sdk/retry-runtime` | Private-local after July 2026; Retry config and retry runner helpers | | `plugin-sdk/agent-runtime` | Deprecated broad barrel for agent dir/identity/workspace helpers, including `resolveAgentDir`, `resolveDefaultAgentDir`, and the deprecated `resolveOpenClawAgentDir` compatibility export; prefer focused agent/runtime subpaths | | `plugin-sdk/directory-runtime` | Config-backed directory query/dedup | - | `plugin-sdk/keyed-async-queue` | `KeyedAsyncQueue` | + | `plugin-sdk/keyed-async-queue` | Private-local after July 2026; `KeyedAsyncQueue` | @@ -354,25 +318,23 @@ usage endpoint failed or returned no usable usage data. | `plugin-sdk/media-runtime` | Deprecated broad media barrel including `saveRemoteMedia`, `saveResponseMedia`, `readRemoteMediaBuffer`, and deprecated `fetchRemoteMedia`; prefer `plugin-sdk/media-store`, `plugin-sdk/media-mime`, `plugin-sdk/outbound-media`, and capability runtime subpaths, and prefer store helpers before buffer reads when a URL should become OpenClaw media | | `plugin-sdk/media-mime` | Narrow MIME normalization, file-extension mapping, MIME detection, and media-kind helpers | | `plugin-sdk/media-store` | Narrow media store helpers such as `saveMediaBuffer` and `saveMediaStream` | - | `plugin-sdk/media-generation-runtime` | Shared media-generation failover helpers, candidate selection, and missing-model messaging | + | `plugin-sdk/media-generation-runtime` | Private-local after July 2026; Shared media-generation failover helpers, candidate selection, and missing-model messaging | | `plugin-sdk/media-understanding` | Media understanding provider types plus provider-facing image/audio/structured-extraction helper exports | | `plugin-sdk/text-chunking` | Outbound text and offset-preserving range chunking, markdown chunking/render helpers, quote-aware HTML tag tokenization, markdown table conversion, directive-tag stripping, and safe-text utilities | - | `plugin-sdk/speech` | Speech provider types plus provider-facing directive, registry, validation, OpenAI-compatible TTS builder, and speech helper exports | - | `plugin-sdk/speech-core` | Shared speech provider types, registry, directive, normalization, and speech helper exports | + | `plugin-sdk/speech` | Private-local after July 2026; Speech provider types plus provider-facing directive, registry, validation, OpenAI-compatible TTS builder, and speech helper exports | + | `plugin-sdk/speech-core` | Private-local after July 2026; Shared speech provider types, registry, directive, normalization, and speech helper exports | | `plugin-sdk/speech-settings` | Lightweight TTS config resolution and normalization primitives without provider registries or synthesis runtime | - | `plugin-sdk/realtime-transcription` | Realtime transcription provider types, registry helpers, and shared WebSocket session helper | - | `plugin-sdk/realtime-bootstrap-context` | Realtime profile bootstrap helper for bounded `IDENTITY.md`, `USER.md`, and `SOUL.md` context injection | - | `plugin-sdk/realtime-voice` | Realtime voice provider types, registry helpers, shared audio-energy/speech-onset gates, and realtime voice behavior helpers, including the transport-independent session harness and output activity tracking | + | `plugin-sdk/realtime-transcription` | Private-local after July 2026; Realtime transcription provider types, registry helpers, and shared WebSocket session helper | + | `plugin-sdk/realtime-bootstrap-context` | Private-local after July 2026; Realtime profile bootstrap helper for bounded `IDENTITY.md`, `USER.md`, and `SOUL.md` context injection | + | `plugin-sdk/realtime-voice` | Private-local after July 2026; Realtime voice provider types, registry helpers, shared audio-energy/speech-onset gates, and realtime voice behavior helpers, including the transport-independent session harness and output activity tracking | | `plugin-sdk/meeting-runtime` | Browser-meeting session runtime, realtime audio engines/transports, `MeetingPlatformAdapter`, browser/node control, agent-consult, voice-call delegation, setup checks, and SoX command helpers | - | `plugin-sdk/image-generation` | Image generation provider types plus image asset/data URL helpers and the OpenAI-compatible image provider builder | - | `plugin-sdk/image-generation-core` | Shared image-generation types, failover, auth, and registry helpers | - | `plugin-sdk/music-generation` | Music generation provider/request/result types | - | `plugin-sdk/music-generation-core` | Deprecated shared music-generation types, failover helpers, provider lookup, and model-ref parsing; prefer plugin-owned music provider surfaces | - | `plugin-sdk/video-generation` | Video generation provider/request/result types | - | `plugin-sdk/video-generation-core` | Shared video-generation types, failover helpers, provider lookup, and model-ref parsing | - | `plugin-sdk/transcripts` | Shared transcripts source provider types, registry helpers, session descriptors, and utterance metadata | - | `plugin-sdk/webhook-targets` | Webhook target registry and route-install helpers | - | `plugin-sdk/webhook-path` | Deprecated compatibility alias; use `plugin-sdk/webhook-ingress` | + | `plugin-sdk/image-generation` | Private-local after July 2026; Image generation provider types plus image asset/data URL helpers and the OpenAI-compatible image provider builder | + | `plugin-sdk/image-generation-core` | Private-local after July 2026; Shared image-generation types, failover, auth, and registry helpers | + | `plugin-sdk/music-generation` | Private-local after July 2026; Music generation provider/request/result types | + | `plugin-sdk/video-generation` | Private-local after July 2026; Video generation provider/request/result types | + | `plugin-sdk/video-generation-core` | Private-local after July 2026; Shared video-generation types, failover helpers, provider lookup, and model-ref parsing | + | `plugin-sdk/transcripts` | Private-local after July 2026; Shared transcripts source provider types, registry helpers, session descriptors, and utterance metadata | + | `plugin-sdk/webhook-targets` | Private-local after July 2026; Webhook target registry and route-install helpers | | `plugin-sdk/web-media` | Shared remote/local media loading helpers | | `plugin-sdk/zod` | Deprecated compatibility re-export; import `zod` from `zod` directly | | `plugin-sdk/plugin-test-api` | Repo-local minimal `createTestPluginApi` helper for direct plugin registration unit tests without importing repo test helper bridges | @@ -383,7 +345,7 @@ usage endpoint failed or returned no usable usage data. | `plugin-sdk/plugin-test-contracts` | Repo-local plugin package, registration, public artifact, direct import, runtime API, and import side-effect contract helpers | | `plugin-sdk/plugin-state-test-runtime` | Repo-local plugin state store, ingress queue, and state DB test helpers | | `plugin-sdk/provider-test-contracts` | Repo-local provider runtime, auth, discovery, onboard, catalog, wizard, media capability, replay policy, realtime STT live-audio, web-search/fetch, and stream contract helpers | - | `plugin-sdk/provider-http-test-mocks` | Repo-local opt-in Vitest HTTP/auth mocks for provider tests that exercise `plugin-sdk/provider-http` | + | `plugin-sdk/provider-http-test-mocks` | Private-local after July 2026; Repo-local opt-in Vitest HTTP/auth mocks for provider tests that exercise `plugin-sdk/provider-http` | | `plugin-sdk/reply-payload-testing` | Repo-local helpers for attaching metadata to reply payload fixtures | | `plugin-sdk/sqlite-runtime-testing` | Repo-local SQLite lifecycle helpers for first-party tests | | `plugin-sdk/test-fixtures` | Repo-local generic CLI runtime capture, sandbox context, skill writer, agent-message, system-event, module reload, bundled plugin path, terminal-text, chunking, auth-token, and typed-case fixtures | @@ -393,27 +355,20 @@ usage endpoint failed or returned no usable usage data. | Subpath | Key exports | | --- | --- | - | `plugin-sdk/memory-core` | Deprecated compatibility alias; use `plugin-sdk/memory-host-core` | - | `plugin-sdk/memory-core-engine-runtime` | Deprecated memory index/search runtime facade; prefer vendor-neutral memory-host subpaths | - | `plugin-sdk/memory-core-host-embedding-registry` | Lightweight memory embedding provider registry helpers | + | `plugin-sdk/memory-core-host-embedding-registry` | Private-local after July 2026; Lightweight memory embedding provider registry helpers | | `plugin-sdk/memory-core-host-engine-foundation` | Memory host foundation engine exports | - | `plugin-sdk/memory-core-host-engine-embeddings` | Memory host embedding contracts, registry access, local provider, and generic batch/remote helpers. `registerMemoryEmbeddingProvider` on this surface is deprecated; use the generic embedding provider API for new providers. | - | `plugin-sdk/memory-core-host-engine-qmd` | Memory host QMD engine exports | - | `plugin-sdk/memory-core-host-engine-storage` | Memory host storage engine exports | - | `plugin-sdk/memory-core-host-multimodal` | Deprecated memory host multimodal helpers; prefer vendor-neutral memory-host subpaths | - | `plugin-sdk/memory-core-host-query` | Deprecated memory host query helpers; prefer vendor-neutral memory-host subpaths | - | `plugin-sdk/memory-core-host-secret` | Memory host secret helpers | - | `plugin-sdk/memory-core-host-events` | Deprecated compatibility alias; use `plugin-sdk/memory-host-events` | - | `plugin-sdk/memory-core-host-status` | Memory host status helpers | - | `plugin-sdk/memory-core-host-runtime-cli` | Memory host CLI runtime helpers | - | `plugin-sdk/memory-core-host-runtime-core` | Memory host core runtime helpers | - | `plugin-sdk/memory-core-host-runtime-files` | Memory host file/runtime helpers | - | `plugin-sdk/memory-host-core` | Vendor-neutral alias for memory host core runtime helpers | - | `plugin-sdk/memory-host-events` | Vendor-neutral alias for memory host event journal helpers | - | `plugin-sdk/memory-host-files` | Deprecated compatibility alias; use `plugin-sdk/memory-core-host-runtime-files` | - | `plugin-sdk/memory-host-markdown` | Shared managed-markdown helpers for memory-adjacent plugins | - | `plugin-sdk/memory-host-search` | Active memory runtime facade for search-manager access | - | `plugin-sdk/memory-host-status` | Deprecated compatibility alias; use `plugin-sdk/memory-core-host-status` | + | `plugin-sdk/memory-core-host-engine-embeddings` | Private-local after July 2026; Memory host embedding contracts, registry access, local provider, and generic batch/remote helpers. `registerMemoryEmbeddingProvider` on this surface is deprecated; use the generic embedding provider API for new providers. | + | `plugin-sdk/memory-core-host-engine-qmd` | Private-local after July 2026; Memory host QMD engine exports | + | `plugin-sdk/memory-core-host-engine-storage` | Private-local after July 2026; Memory host storage engine exports | + | `plugin-sdk/memory-core-host-secret` | Private-local after July 2026; Memory host secret helpers | + | `plugin-sdk/memory-core-host-status` | Private-local after July 2026; Memory host status helpers | + | `plugin-sdk/memory-core-host-runtime-cli` | Private-local after July 2026; Memory host CLI runtime helpers | + | `plugin-sdk/memory-core-host-runtime-core` | Private-local after July 2026; Memory host core runtime helpers | + | `plugin-sdk/memory-core-host-runtime-files` | Private-local after July 2026; Memory host file/runtime helpers | + | `plugin-sdk/memory-host-core` | Vendor-neutral memory host core runtime helpers | + | `plugin-sdk/memory-host-events` | Private-local after July 2026; Vendor-neutral alias for memory host event journal helpers | + | `plugin-sdk/memory-host-markdown` | Private-local after July 2026; Shared managed-markdown helpers for memory-adjacent plugins | + | `plugin-sdk/memory-host-search` | Private-local after July 2026; Active memory runtime facade for search-manager access | @@ -426,7 +381,7 @@ usage endpoint failed or returned no usable usage data. | Subpath | Owner and purpose | | --- | --- | - | `plugin-sdk/codex-mcp-projection` | Bundled Codex plugin helper for projecting user MCP server config into Codex app-server thread config (reserved package export) | + | `plugin-sdk/codex-mcp-projection` | Private-local after July 2026; Bundled Codex plugin helper for projecting user MCP server config into Codex app-server thread config (reserved package export) | | `plugin-sdk/codex-native-task-runtime` | Bundled Codex plugin helper for mirroring Codex app-server native subagents into OpenClaw task state (repo-local only, not a package export) | diff --git a/docs/plugins/sdk-testing.md b/docs/plugins/sdk-testing.md index bb5e54b3b3a5..5134e0635a02 100644 --- a/docs/plugins/sdk-testing.md +++ b/docs/plugins/sdk-testing.md @@ -50,10 +50,10 @@ import { mockNodeBuiltinModule } from "openclaw/plugin-sdk/test-node-mocks"; Use these focused subpaths for bundled plugin tests. The former `openclaw/plugin-sdk/testing` barrel was repo-local, excluded from shipped -packages, and has been removed. The legacy `openclaw/plugin-sdk/test-utils` -alias remains repo-local; `pnpm run lint:plugins:no-extension-test-core-imports` -(`scripts/check-no-extension-test-core-imports.ts`) rejects new extension-test -imports of that alias. +packages, and has been removed. The former `openclaw/plugin-sdk/test-utils` +alias was removed with it. `pnpm run lint:plugins:no-extension-test-core-imports` +(`scripts/check-no-extension-test-core-imports.ts`) keeps extension tests on +the focused test subpaths above. ### Available exports @@ -188,11 +188,9 @@ entry to declare `kind: "memory"`. ### Testing runtime config access -Prefer the shared plugin runtime mock from `openclaw/plugin-sdk/plugin-test-runtime`. -Its `runtime.config.loadConfig()` and `runtime.config.writeConfigFile(...)` -mocks throw by default so tests catch new usage of deprecated compatibility -APIs. Override those mocks only when the test is explicitly covering legacy -compatibility behavior. +Prefer the shared plugin runtime mock from +`openclaw/plugin-sdk/plugin-test-runtime`. Its runtime config helpers model the +current snapshot and mutation APIs. ### Unit testing a channel plugin @@ -340,11 +338,11 @@ pnpm test src/plugins/contracts/runtime-seams.contract.test.ts `scripts/run-additional-boundary-checks.mjs` runs a set of `lint:plugins:*` import-boundary checks in CI; each can also be run standalone locally: -| Command | Enforces | -| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| `pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports` | Bundled plugins cannot import the monolithic `openclaw/plugin-sdk` root barrel. | -| `pnpm run lint:plugins:no-extension-src-imports` | Production extension files cannot import the repo `src/**` tree directly (`../../src/...`). | -| `pnpm run lint:plugins:no-extension-test-core-imports` | Extension test files cannot import `plugin-sdk/test-utils` or other core-only test helpers. | +| Command | Enforces | +| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports` | Bundled plugins cannot import the monolithic `openclaw/plugin-sdk` root barrel. | +| `pnpm run lint:plugins:no-extension-src-imports` | Production extension files cannot import the repo `src/**` tree directly (`../../src/...`). | +| `pnpm run lint:plugins:no-extension-test-core-imports` | Extension test files cannot import removed SDK test aliases or other core-only test helpers. | External plugins are not subject to these lint rules, but following the same patterns is recommended. diff --git a/docs/reference/secretref-credential-surface.md b/docs/reference/secretref-credential-surface.md index a3341c4670f4..02ed63c358be 100644 --- a/docs/reference/secretref-credential-surface.md +++ b/docs/reference/secretref-credential-surface.md @@ -42,12 +42,12 @@ The lists below are generated from the source target registry and checked agains - `talk.providers.*.apiKey` - `talk.realtime.providers.*.apiKey` - `messages.tts.providers.*.apiKey` -- `tools.web.fetch.firecrawl.apiKey` - `plugins.entries.acpx.config.mcpServers.*.env.*` - `plugins.entries.brave.config.webSearch.apiKey` - `plugins.entries.codex.config.appServer.authToken` - `plugins.entries.codex.config.appServer.headers.*` - `plugins.entries.exa.config.webSearch.apiKey` +- `plugins.entries.firecrawl.config.webFetch.apiKey` - `plugins.entries.google-meet.config.realtime.providers.*.apiKey` - `plugins.entries.google.config.webSearch.apiKey` - `plugins.entries.xai.config.webSearch.apiKey` @@ -62,8 +62,6 @@ The lists below are generated from the source target registry and checked agains - `plugins.entries.voice-call.config.tts.providers.*.apiKey` - `plugins.entries.voice-call.config.twilio.authToken` - `plugins.entries.webhooks.config.routes.*.secret` -- `tools.web.search.*.apiKey` -- `tools.web.search.apiKey` - `gateway.auth.password` - `gateway.auth.token` - `gateway.remote.token` @@ -137,7 +135,7 @@ Notes: - OAuth policy guard: `auth.profiles..mode = "oauth"` cannot be combined with SecretRef inputs for that profile. Startup/reload and auth-profile resolution fail fast when this policy is violated. - For SecretRef-managed model providers, generated `agents/*/agent/models.json` entries persist non-secret markers (not resolved secret values) for `apiKey`/header surfaces. Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values. - Cold Gateway startup can isolate retryable resolution failures for mapped, non-Gateway owners. Current mapped classes include model providers and skills, media/TTS/cron providers, eligible auth profiles, per-agent memory, sandbox SSH, channel accounts, and manifest-declared plugin routes. Startup keeps each failed owner's explicit refs in the runtime snapshot, reports the owner through status and doctor, and rejects requests for that owner without trying lower-precedence credentials. Reload and config-write preflight use the same owner-aware policy: healthy owners refresh; an eligible failed owner stays stale only when its ref identities, provider definitions, and complete non-secret owner contract are unchanged; a new or changed failure becomes cold. Gateway ingress auth, structurally invalid refs or values, fail-closed owners, and currently unmapped owners remain strict. -- For web search: in explicit provider mode (`tools.web.search.provider` set), only the selected provider key is active. In auto mode (`tools.web.search.provider` unset), only the first provider key that resolves by precedence is active, and non-selected provider refs are treated as inactive until selected. Legacy `tools.web.search.*` provider paths still resolve during the compatibility window, but the canonical SecretRef surface is `plugins.entries..config.webSearch.*`. +- For web search: in explicit provider mode (`tools.web.search.provider` set), only the selected provider key is active. In auto mode (`tools.web.search.provider` unset), only the first provider key that resolves by precedence is active, and non-selected provider refs are treated as inactive until selected. Provider credentials use `plugins.entries..config.webSearch.*`. - Slack `identity: "user"` uses `channels.slack.userToken` with `channels.slack.appToken` for Socket Mode or `channels.slack.signingSecret` for HTTP mode. The same pairing applies under `channels.slack.accounts.*`; no bot token is required for this identity. ## Unsupported credentials diff --git a/docs/reference/secretref-user-supplied-credentials-matrix.json b/docs/reference/secretref-user-supplied-credentials-matrix.json index 5eed92e3734b..f79d9c832ca1 100644 --- a/docs/reference/secretref-user-supplied-credentials-matrix.json +++ b/docs/reference/secretref-user-supplied-credentials-matrix.json @@ -603,6 +603,13 @@ "secretShape": "secret_input", "optIn": true }, + { + "id": "plugins.entries.firecrawl.config.webFetch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.firecrawl.config.webFetch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, { "id": "plugins.entries.firecrawl.config.webSearch.apiKey", "configFile": "openclaw.json", @@ -721,27 +728,6 @@ "path": "talk.realtime.providers.*.apiKey", "secretShape": "secret_input", "optIn": true - }, - { - "id": "tools.web.fetch.firecrawl.apiKey", - "configFile": "openclaw.json", - "path": "tools.web.fetch.firecrawl.apiKey", - "secretShape": "secret_input", - "optIn": true - }, - { - "id": "tools.web.search.*.apiKey", - "configFile": "openclaw.json", - "path": "tools.web.search.*.apiKey", - "secretShape": "secret_input", - "optIn": true - }, - { - "id": "tools.web.search.apiKey", - "configFile": "openclaw.json", - "path": "tools.web.search.apiKey", - "secretShape": "secret_input", - "optIn": true } ] } diff --git a/docs/tools/brave-search.md b/docs/tools/brave-search.md index 53fb3491f493..a0ad1f6ccdeb 100644 --- a/docs/tools/brave-search.md +++ b/docs/tools/brave-search.md @@ -43,7 +43,7 @@ OpenClaw supports Brave Search API as a `web_search` provider. } ``` -Provider-specific Brave search settings live under `plugins.entries.brave.config.webSearch.*`; this is the canonical config path. A shared top-level `tools.web.search.apiKey` and a scoped `tools.web.search.brave.*` still load through a compatibility merge, but new config should use the plugin-scoped path above. +Provider-specific Brave search settings live under `plugins.entries.brave.config.webSearch.*`; this is the canonical config path. `webSearch.mode` controls the Brave transport: diff --git a/docs/tools/kimi-search.md b/docs/tools/kimi-search.md index 7a8b0e242f5a..b1d1fe172f2c 100644 --- a/docs/tools/kimi-search.md +++ b/docs/tools/kimi-search.md @@ -63,8 +63,8 @@ also prompts for: `tools.web.search.provider` is auto-detected from available API keys when omitted; set it to `kimi` explicitly if multiple search credentials are configured. -Equivalent scoped form under `tools.web.search.kimi` (`apiKey`, `baseUrl`, `model`) -also works; both shapes merge into the same resolved config. +Configure Kimi-specific `apiKey`, `baseUrl`, and `model` values under +`plugins.entries.moonshot.config.webSearch`. Defaults: `baseUrl` defaults to `https://api.moonshot.ai/v1` when omitted, `model` defaults to `kimi-k2.6`. diff --git a/docs/tools/minimax-search.md b/docs/tools/minimax-search.md index db8e64af5033..65d1d910a1d4 100644 --- a/docs/tools/minimax-search.md +++ b/docs/tools/minimax-search.md @@ -75,7 +75,7 @@ MiniMax Search uses these endpoints: If `plugins.entries.minimax.config.webSearch.region` is unset, OpenClaw resolves the region in this order: -1. `tools.web.search.minimax.region` / plugin-owned `webSearch.region` +1. Plugin-owned `webSearch.region` 2. `MINIMAX_API_HOST` 3. `models.providers.minimax.baseUrl` 4. `models.providers.minimax-portal.baseUrl` diff --git a/docs/tools/web.md b/docs/tools/web.md index ed67c98bf748..7720920e4a1b 100644 --- a/docs/tools/web.md +++ b/docs/tools/web.md @@ -545,8 +545,7 @@ every provider. xAI credentials are always required. `x_search` posts to `/responses` when `plugins.entries.xai.config.xSearch.baseUrl` is set. If that field is omitted, it falls back to `plugins.entries.xai.config.webSearch.baseUrl`, then the -legacy `tools.web.search.grok.baseUrl`, and finally the public xAI endpoint -(`https://api.x.ai/v1`). +public xAI endpoint (`https://api.x.ai/v1`). ### x_search parameters diff --git a/extensions/brave/openclaw.plugin.json b/extensions/brave/openclaw.plugin.json index a47856781ea6..c8bd8a9bd213 100644 --- a/extensions/brave/openclaw.plugin.json +++ b/extensions/brave/openclaw.plugin.json @@ -34,9 +34,6 @@ "contracts": { "webSearchProviders": ["brave"] }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/brave/src/brave-web-search-provider.runtime.ts b/extensions/brave/src/brave-web-search-provider.runtime.ts index 1613152fe47e..9248dbfd296d 100644 --- a/extensions/brave/src/brave-web-search-provider.runtime.ts +++ b/extensions/brave/src/brave-web-search-provider.runtime.ts @@ -92,8 +92,10 @@ function describeBraveRequestUrl(url: URL): { function resolveBraveApiKey(searchConfig?: SearchConfigRecord): string | undefined { return ( - readConfiguredSecretString(searchConfig?.apiKey, "tools.web.search.apiKey") ?? - readProviderEnvValue(["BRAVE_API_KEY"]) + readConfiguredSecretString( + searchConfig?.apiKey, + "plugins.entries.brave.config.webSearch.apiKey", + ) ?? readProviderEnvValue(["BRAVE_API_KEY"]) ); } diff --git a/extensions/brave/src/brave-web-search-provider.test.ts b/extensions/brave/src/brave-web-search-provider.test.ts index 3a9d6500754a..e1920546706b 100644 --- a/extensions/brave/src/brave-web-search-provider.test.ts +++ b/extensions/brave/src/brave-web-search-provider.test.ts @@ -122,6 +122,34 @@ function createBodyOnlyErrorResponse(params: { body: string; status: number }): } as Response; } +function createBraveTool( + params: { + webSearch?: Record; + searchConfig?: Record; + config?: Record; + } = {}, +) { + const tool = createBraveWebSearchProvider().createTool({ + config: { + ...params.config, + plugins: { + entries: { + brave: { + config: { + webSearch: params.webSearch ?? {}, + }, + }, + }, + }, + }, + searchConfig: params.searchConfig ?? {}, + } as never); + if (!tool) { + throw new Error("Expected tool definition"); + } + return tool; +} + describe("brave web search provider", () => { const priorFetch = global.fetch; @@ -140,41 +168,9 @@ describe("brave web search provider", () => { ); }); - it("exposes legacy top-level apiKey as a Brave-owned compatibility fallback", () => { - const apiKey = { source: "env", provider: "default", id: "BRAVE_API_KEY" } as const; - const config = { - tools: { - web: { - search: { - apiKey, - }, - }, - }, - }; - - expect(createBraveWebSearchProvider().getConfiguredCredentialValue?.(config)).toEqual(apiKey); - expect(createBraveWebSearchContractProvider().getConfiguredCredentialValue?.(config)).toEqual( - apiKey, - ); - expect(createBraveWebSearchProvider().getConfiguredCredentialFallback?.(config)).toEqual({ - path: "tools.web.search.apiKey", - value: apiKey, - }); - expect( - createBraveWebSearchContractProvider().getConfiguredCredentialFallback?.(config), - ).toEqual({ - path: "tools.web.search.apiKey", - value: apiKey, - }); - }); - it("points missing-key users to fetch/browser alternatives", async () => { vi.stubEnv("BRAVE_API_KEY", ""); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ config: {}, searchConfig: {} }); - if (!tool) { - throw new Error("Expected tool definition"); - } + const tool = createBraveTool(); const result = await tool.execute({ query: "OpenClaw docs" }); @@ -274,20 +270,13 @@ describe("brave web search provider", () => { }); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { + const tool = createBraveTool({ + webSearch: { apiKey: "brave-test-key", - brave: { - baseUrl: "https://api.search.brave.com/proxy/", - mode: "web", - }, + baseUrl: "https://api.search.brave.com/proxy/", + mode: "web", }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await tool.execute({ query: "latest ai news" }); @@ -299,20 +288,13 @@ describe("brave web search provider", () => { it("uses configured Brave baseUrl for llm-context requests", async () => { vi.stubEnv("BRAVE_API_KEY", ""); const mockFetch = installBraveLlmContextFetch(); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { + const tool = createBraveTool({ + webSearch: { apiKey: "brave-test-key", - brave: { - baseUrl: "https://api.search.brave.com/proxy", - mode: "llm-context", - }, + baseUrl: "https://api.search.brave.com/proxy", + mode: "llm-context", }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await tool.execute({ query: "latest ai news" }); @@ -327,17 +309,7 @@ describe("brave web search provider", () => { }); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "brave-test-key", - brave: { mode: "web" }, - }, - }); - if (!tool) { - throw new Error("Expected tool definition"); - } + const tool = createBraveTool({ webSearch: { apiKey: "brave-test-key", mode: "web" } }); await expect(tool.execute({ query: "latest ai news" })).rejects.toThrow( "Brave Search API error: malformed JSON response", @@ -351,17 +323,9 @@ describe("brave web search provider", () => { }); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "brave-test-key", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "brave-test-key", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await expect(tool.execute({ query: "latest ai news" })).rejects.toThrow( "Brave LLM Context API error: malformed JSON response", @@ -378,17 +342,7 @@ describe("brave web search provider", () => { ); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "brave-test-key", - brave: { mode: "web" }, - }, - }); - if (!tool) { - throw new Error("Expected tool definition"); - } + const tool = createBraveTool({ webSearch: { apiKey: "brave-test-key", mode: "web" } }); const error = await tool.execute({ query: "latest ai news" }).catch((value: unknown) => value); expect(error).toBeInstanceOf(Error); @@ -408,17 +362,9 @@ describe("brave web search provider", () => { ); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "brave-test-key", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "brave-test-key", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } const error = await tool.execute({ query: "latest ai news" }).catch((value: unknown) => value); expect(error).toBeInstanceOf(Error); @@ -435,30 +381,20 @@ describe("brave web search provider", () => { }); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const firstTool = provider.createTool({ - config: {}, - searchConfig: { + const firstTool = createBraveTool({ + webSearch: { apiKey: "brave-test-key", - brave: { - baseUrl: "https://api.search.brave.com/proxy-one", - mode: "web", - }, + baseUrl: "https://api.search.brave.com/proxy-one", + mode: "web", }, }); - const secondTool = provider.createTool({ - config: {}, - searchConfig: { + const secondTool = createBraveTool({ + webSearch: { apiKey: "brave-test-key", - brave: { - baseUrl: "https://api.search.brave.com/proxy-two", - mode: "web", - }, + baseUrl: "https://api.search.brave.com/proxy-two", + mode: "web", }, }); - if (!firstTool || !secondTool) { - throw new Error("Expected tool definitions"); - } await firstTool.execute({ query: "base url cache identity" }); await secondTool.execute({ query: "base url cache identity" }); @@ -523,17 +459,7 @@ describe("brave web search provider", () => { it("returns validation errors for invalid date ranges", async () => { vi.stubEnv("BRAVE_API_KEY", ""); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "BSA...", - brave: { apiKey: "BSA..." }, - }, - }); - if (!tool) { - throw new Error("Expected tool definition"); - } + const tool = createBraveTool({ webSearch: { apiKey: "BSA..." } }); const result = await tool.execute({ query: "latest gpu news", @@ -551,17 +477,9 @@ describe("brave web search provider", () => { it("passes freshness to Brave llm-context endpoint", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); const mockFetch = installBraveLlmContextFetch(); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "BSA...", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "BSA...", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await tool.execute({ query: "latest ai news", freshness: "week" }); @@ -577,17 +495,7 @@ describe("brave web search provider", () => { }); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "brave-test-key", - brave: { mode: "web" }, - }, - }); - if (!tool) { - throw new Error("Expected tool definition"); - } + const tool = createBraveTool({ webSearch: { apiKey: "brave-test-key", mode: "web" } }); await tool.execute({ query: "latest ai news" }); @@ -600,17 +508,9 @@ describe("brave web search provider", () => { it("sends Brave llm-context auth in the X-Subscription-Token header", async () => { vi.stubEnv("BRAVE_API_KEY", ""); const mockFetch = installBraveLlmContextFetch(); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "brave-test-key", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "brave-test-key", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await tool.execute({ query: "latest ai news" }); @@ -623,17 +523,9 @@ describe("brave web search provider", () => { it("passes bounded date ranges to Brave llm-context endpoint", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); const mockFetch = installBraveLlmContextFetch(); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "BSA...", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "BSA...", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await tool.execute({ query: "latest ai news", @@ -649,17 +541,9 @@ describe("brave web search provider", () => { it("uses today as the end date for Brave llm-context date_after-only ranges", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); const mockFetch = installBraveLlmContextFetch(); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "BSA...", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "BSA...", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await tool.execute({ query: "latest ai news", date_after: "2025-01-01" }); @@ -672,17 +556,9 @@ describe("brave web search provider", () => { it("rejects future Brave llm-context date_after-only ranges before fetch", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); const mockFetch = installBraveLlmContextFetch(); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "BSA...", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "BSA...", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } const result = await tool.execute({ query: "latest ai news", @@ -700,17 +576,9 @@ describe("brave web search provider", () => { it("rejects Brave llm-context date_before-only ranges before fetch", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); const mockFetch = installBraveLlmContextFetch(); - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "BSA...", - brave: { mode: "llm-context" }, - }, + const tool = createBraveTool({ + webSearch: { apiKey: "BSA...", mode: "llm-context" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } const result = await tool.execute({ query: "latest ai news", @@ -733,17 +601,7 @@ describe("brave web search provider", () => { }); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ - config: {}, - searchConfig: { - apiKey: "BSA...", - brave: { apiKey: "BSA..." }, - }, - }); - if (!tool) { - throw new Error("Expected tool definition"); - } + const tool = createBraveTool({ webSearch: { apiKey: "BSA..." } }); await tool.execute({ query: "latest Vietnam news", @@ -771,17 +629,10 @@ describe("brave web search provider", () => { }); global.fetch = mockFetch as typeof global.fetch; - const provider = createBraveWebSearchProvider(); - const tool = provider.createTool({ + const tool = createBraveTool({ config: { diagnostics: { flags: ["brave.http"] } }, - searchConfig: { - apiKey: "brave-test-key", - brave: { mode: "web" }, - }, + webSearch: { apiKey: "brave-test-key", mode: "web" }, }); - if (!tool) { - throw new Error("Expected tool definition"); - } await tool.execute({ query: "unique brave diagnostics query", count: 1 }); await tool.execute({ query: "unique brave diagnostics query", count: 1 }); diff --git a/extensions/brave/web-search-shared.ts b/extensions/brave/web-search-shared.ts index cca5be7f8baa..85e82c2eb2d9 100644 --- a/extensions/brave/web-search-shared.ts +++ b/extensions/brave/web-search-shared.ts @@ -11,22 +11,6 @@ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime"; /** Canonical config path for the Brave Search API key. */ const BRAVE_CREDENTIAL_PATH = "plugins.entries.brave.config.webSearch.apiKey"; -/** Resolve legacy top-level Brave credentials from old web-search config. */ -function resolveLegacyTopLevelBraveCredential( - config: unknown, -): { path: string; value: unknown } | undefined { - if (!isRecord(config)) { - return undefined; - } - const tools = isRecord(config.tools) ? config.tools : undefined; - const web = isRecord(tools?.web) ? tools.web : undefined; - const search = isRecord(web?.search) ? web.search : undefined; - if (!search || !("apiKey" in search)) { - return undefined; - } - return { path: "tools.web.search.apiKey", value: search.apiKey }; -} - function resolveBraveWebSearchPluginConfig(config: unknown): Record | undefined { if (!isRecord(config)) { return undefined; @@ -38,12 +22,9 @@ function resolveBraveWebSearchPluginConfig(config: unknown): Record { ); }); - it("reuses the precomputed legacy before_agent_start result", async () => { - const beforeAgentStart = vi.fn(); - initializeGlobalHookRunner( - createMockPluginRegistry([{ hookName: "before_agent_start", handler: beforeAgentStart }]), - ); - const sdk = makeFakeSdk(); - - await runCopilotAttempt( - makeParams({ - beforeAgentStartResult: { prependContext: "Use the cached result." }, - } as never), - { pool: makeFakePool(sdk) }, - ); - - expect(beforeAgentStart).not.toHaveBeenCalled(); - const messageOptions = sdk.sessions[0]?.sendAndWait.mock.calls[0]?.[0] as { prompt?: string }; - expect(messageOptions.prompt).toBe("Use the cached result.\n\nhello"); - }); - it("preserves native Copilot SDK hooks alongside generic lifecycle hooks", async () => { const sdk = makeFakeSdk(); const onPreToolUse = vi.fn(); diff --git a/extensions/copilot/src/attempt.ts b/extensions/copilot/src/attempt.ts index 0f8583bd1ef7..73682505c1f2 100644 --- a/extensions/copilot/src/attempt.ts +++ b/extensions/copilot/src/attempt.ts @@ -736,9 +736,6 @@ export async function runCopilotAttempt( messages, ctx: hookContext, bootstrapContextRunKind: input.bootstrapContextRunKind, - ...("beforeAgentStartResult" in input - ? { beforeAgentStartResult: input.beforeAgentStartResult } - : {}), }); const attemptInput = promptBuild.prompt === input.prompt ? input : { ...input, prompt: promptBuild.prompt }; diff --git a/extensions/deepinfra/openclaw.plugin.json b/extensions/deepinfra/openclaw.plugin.json index b9e150901ae0..9f2a25170447 100644 --- a/extensions/deepinfra/openclaw.plugin.json +++ b/extensions/deepinfra/openclaw.plugin.json @@ -6,9 +6,6 @@ "enabledByDefault": true, "providers": ["deepinfra"], "providerCatalogEntry": "./provider-discovery.ts", - "providerAuthEnvVars": { - "deepinfra": ["DEEPINFRA_API_KEY"] - }, "providerEndpoints": [ { "endpointClass": "deepinfra-native", diff --git a/extensions/discord/openclaw.plugin.json b/extensions/discord/openclaw.plugin.json index ecf960d46143..5af6d37869f2 100644 --- a/extensions/discord/openclaw.plugin.json +++ b/extensions/discord/openclaw.plugin.json @@ -21,11 +21,6 @@ "discord-voice" ] }, - "channelEnvVars": { - "discord": [ - "DISCORD_BOT_TOKEN" - ] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/discord/package.json b/extensions/discord/package.json index a695582d2946..a079cc8ece0c 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -39,6 +39,13 @@ }, "channel": { "id": "discord", + "configuredState": { + "env": { + "anyOf": [ + "DISCORD_BOT_TOKEN" + ] + } + }, "approvalFlags": ["native"], "label": "Discord", "selectionLabel": "Discord (Bot API)", @@ -52,13 +59,6 @@ "commands": { "nativeCommandsAutoEnabled": true, "nativeSkillsAutoEnabled": true - }, - "configuredState": { - "env": { - "allOf": [ - "DISCORD_BOT_TOKEN" - ] - } } }, "install": { diff --git a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts index 218cc8543ba9..83c1319f5d30 100644 --- a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts +++ b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts @@ -42,10 +42,7 @@ const resolveConfiguredBindingRouteMock = vi.hoisted(() => vi.fn(() => createUnboundConfiguredRouteResult()), ); const providerThinkingMocks = vi.hoisted(() => ({ - resolveProviderBinaryThinking: vi.fn(), - resolveProviderDefaultThinkingLevel: vi.fn(), resolveProviderThinkingProfile: vi.fn(), - resolveProviderXHighThinking: vi.fn(), })); const buildModelsProviderDataMock = vi.hoisted(() => vi.fn()); @@ -166,26 +163,11 @@ function installProviderThinkingRegistryForTest(): void { label: "Discord Test Thinking", aliases: ["anthropic", "openai"], auth: [], - isBinaryThinking: (context) => - providerThinkingMocks.resolveProviderBinaryThinking({ - provider: context.provider, - context, - }), - supportsXHighThinking: (context) => - providerThinkingMocks.resolveProviderXHighThinking({ - provider: context.provider, - context, - }), resolveThinkingProfile: (context) => providerThinkingMocks.resolveProviderThinkingProfile({ provider: context.provider, context, }), - resolveDefaultThinkingLevel: (context) => - providerThinkingMocks.resolveProviderDefaultThinkingLevel({ - provider: context.provider, - context, - }), }, }); setActivePluginRegistry(registry); @@ -204,13 +186,19 @@ async function loadDiscordThinkAutocompleteModulesForTest() { describe("discord native /think autocomplete", () => { beforeAll(async () => { - providerThinkingMocks.resolveProviderBinaryThinking.mockReturnValue(undefined); - providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined); - providerThinkingMocks.resolveProviderThinkingProfile.mockReturnValue(undefined); - providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) => - provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) - ? true - : undefined, + providerThinkingMocks.resolveProviderThinkingProfile.mockImplementation( + ({ provider, context }) => + provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) + ? { + levels: [ + { id: "off" }, + { id: "low" }, + { id: "medium" }, + { id: "high" }, + { id: "xhigh" }, + ], + } + : undefined, ); buildModelsProviderDataMock.mockResolvedValue({ byProvider: new Map>(), @@ -235,17 +223,20 @@ describe("discord native /think autocomplete", () => { ensureConfiguredBindingRouteReadyMock.mockResolvedValue({ ok: true }); resolveConfiguredBindingRouteMock.mockReset(); resolveConfiguredBindingRouteMock.mockReturnValue(createUnboundConfiguredRouteResult()); - providerThinkingMocks.resolveProviderBinaryThinking.mockReset(); - providerThinkingMocks.resolveProviderBinaryThinking.mockReturnValue(undefined); - providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReset(); - providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined); providerThinkingMocks.resolveProviderThinkingProfile.mockReset(); - providerThinkingMocks.resolveProviderThinkingProfile.mockReturnValue(undefined); - providerThinkingMocks.resolveProviderXHighThinking.mockReset(); - providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) => - provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) - ? true - : undefined, + providerThinkingMocks.resolveProviderThinkingProfile.mockImplementation( + ({ provider, context }) => + provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) + ? { + levels: [ + { id: "off" }, + { id: "low" }, + { id: "medium" }, + { id: "high" }, + { id: "xhigh" }, + ], + } + : undefined, ); installProviderThinkingRegistryForTest(); await saveSessionOverride({ diff --git a/extensions/exa/openclaw.plugin.json b/extensions/exa/openclaw.plugin.json index 05e66f3c68b1..19dbdbb33712 100644 --- a/extensions/exa/openclaw.plugin.json +++ b/extensions/exa/openclaw.plugin.json @@ -26,9 +26,6 @@ "contracts": { "webSearchProviders": ["exa"] }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/exa/src/exa-web-search-provider.runtime.ts b/extensions/exa/src/exa-web-search-provider.runtime.ts index b177110a602a..56628d7593ad 100644 --- a/extensions/exa/src/exa-web-search-provider.runtime.ts +++ b/extensions/exa/src/exa-web-search-provider.runtime.ts @@ -112,7 +112,7 @@ function resolveExaConfig(searchConfig?: SearchConfigRecord): ExaConfig { function resolveExaApiKey(exa?: ExaConfig): string | undefined { return ( - readConfiguredSecretString(exa?.apiKey, "tools.web.search.exa.apiKey") ?? + readConfiguredSecretString(exa?.apiKey, "plugins.entries.exa.config.webSearch.apiKey") ?? readProviderEnvValue(["EXA_API_KEY"]) ); } @@ -438,7 +438,7 @@ function missingExaKeyPayload() { return { error: "missing_exa_api_key", message: - "web_search (exa) needs an Exa API key. Set EXA_API_KEY in the Gateway environment, or configure tools.web.search.exa.apiKey.", + "web_search (exa) needs an Exa API key. Set EXA_API_KEY in the Gateway environment, or configure plugins.entries.exa.config.webSearch.apiKey.", docs: "https://docs.openclaw.ai/tools/web", }; } diff --git a/extensions/feishu/openclaw.plugin.json b/extensions/feishu/openclaw.plugin.json index 8163e4b57f61..3546a00c792d 100644 --- a/extensions/feishu/openclaw.plugin.json +++ b/extensions/feishu/openclaw.plugin.json @@ -110,14 +110,6 @@ ] } }, - "channelEnvVars": { - "feishu": [ - "FEISHU_APP_ID", - "FEISHU_APP_SECRET", - "FEISHU_VERIFICATION_TOKEN", - "FEISHU_ENCRYPT_KEY" - ] - }, "skills": ["./skills"], "configSchema": { "type": "object", diff --git a/extensions/feishu/package.json b/extensions/feishu/package.json index a138ecbcf388..ccdb241fc9aa 100644 --- a/extensions/feishu/package.json +++ b/extensions/feishu/package.json @@ -34,6 +34,16 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "feishu", + "configuredState": { + "env": { + "anyOf": [ + "FEISHU_APP_ID", + "FEISHU_APP_SECRET", + "FEISHU_VERIFICATION_TOKEN", + "FEISHU_ENCRYPT_KEY" + ] + } + }, "label": "Feishu", "selectionLabel": "Feishu/Lark (飞书)", "docsPath": "/channels/feishu", diff --git a/extensions/firecrawl/openclaw.plugin.json b/extensions/firecrawl/openclaw.plugin.json index df580c67afc9..1cc3282b9345 100644 --- a/extensions/firecrawl/openclaw.plugin.json +++ b/extensions/firecrawl/openclaw.plugin.json @@ -38,9 +38,6 @@ "webSearchProviders": ["firecrawl", "firecrawl-free"], "tools": ["firecrawl_search", "firecrawl_scrape"] }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/firecrawl/src/config.ts b/extensions/firecrawl/src/config.ts index 48b7ba03a98f..392f796cd9fb 100644 --- a/extensions/firecrawl/src/config.ts +++ b/extensions/firecrawl/src/config.ts @@ -10,18 +10,6 @@ const DEFAULT_FIRECRAWL_SCRAPE_TIMEOUT_SECONDS = 60; const DEFAULT_FIRECRAWL_MAX_AGE_MS = 172_800_000; const FIRECRAWL_API_KEY_ENV_VAR = "FIRECRAWL_API_KEY"; -type WebSearchConfig = NonNullable["web"] extends infer Web - ? Web extends { search?: infer Search } - ? Search - : undefined - : undefined; - -type WebFetchConfig = NonNullable["web"] extends infer Web - ? Web extends { fetch?: infer Fetch } - ? Fetch - : undefined - : undefined; - type FirecrawlSearchConfig = | { apiKey?: unknown; @@ -55,37 +43,13 @@ type FirecrawlFetchConfig = } | undefined; -function resolveSearchConfig(cfg?: OpenClawConfig): WebSearchConfig { - const search = cfg?.tools?.web?.search; - if (!search || typeof search !== "object") { - return undefined; - } - return search; -} - -function resolveFetchConfig(cfg?: OpenClawConfig): WebFetchConfig { - const fetch = cfg?.tools?.web?.fetch; - if (!fetch || typeof fetch !== "object") { - return undefined; - } - return fetch; -} - function resolveFirecrawlSearchConfig(cfg?: OpenClawConfig): FirecrawlSearchConfig { const pluginConfig = cfg?.plugins?.entries?.firecrawl?.config as PluginEntryConfig; const pluginWebSearch = pluginConfig?.webSearch; if (pluginWebSearch && typeof pluginWebSearch === "object" && !Array.isArray(pluginWebSearch)) { return pluginWebSearch; } - const search = resolveSearchConfig(cfg); - if (!search || typeof search !== "object") { - return undefined; - } - const firecrawl = "firecrawl" in search ? search.firecrawl : undefined; - if (!firecrawl || typeof firecrawl !== "object") { - return undefined; - } - return firecrawl as FirecrawlSearchConfig; + return undefined; } function resolveFirecrawlFetchConfig(cfg?: OpenClawConfig): FirecrawlFetchConfig { @@ -94,15 +58,7 @@ function resolveFirecrawlFetchConfig(cfg?: OpenClawConfig): FirecrawlFetchConfig if (pluginWebFetch && typeof pluginWebFetch === "object" && !Array.isArray(pluginWebFetch)) { return pluginWebFetch; } - const fetch = resolveFetchConfig(cfg); - if (!fetch || typeof fetch !== "object") { - return undefined; - } - const firecrawl = "firecrawl" in fetch ? fetch.firecrawl : undefined; - if (!firecrawl || typeof firecrawl !== "object") { - return undefined; - } - return firecrawl as FirecrawlFetchConfig; + return undefined; } type ConfiguredSecretResolution = @@ -151,7 +107,6 @@ function resolveConfiguredSecret( export function resolveFirecrawlApiKey(cfg?: OpenClawConfig): string | undefined { const pluginConfig = cfg?.plugins?.entries?.firecrawl?.config as PluginEntryConfig; const search = resolveFirecrawlSearchConfig(cfg); - const fetch = resolveFirecrawlFetchConfig(cfg); const configuredCandidates: Array<{ value: unknown; path: string }> = [ { value: pluginConfig?.webFetch?.apiKey, @@ -161,14 +116,6 @@ export function resolveFirecrawlApiKey(cfg?: OpenClawConfig): string | undefined value: search?.apiKey, path: "plugins.entries.firecrawl.config.webSearch.apiKey", }, - { - value: search?.apiKey, - path: "tools.web.search.firecrawl.apiKey", - }, - { - value: fetch?.apiKey, - path: "tools.web.fetch.firecrawl.apiKey", - }, ]; let blockedConfiguredSecret = false; for (const candidate of configuredCandidates) { diff --git a/extensions/firecrawl/src/firecrawl-fetch-provider-shared.ts b/extensions/firecrawl/src/firecrawl-fetch-provider-shared.ts index fd9694d509c5..f1fdc1cd2a0c 100644 --- a/extensions/firecrawl/src/firecrawl-fetch-provider-shared.ts +++ b/extensions/firecrawl/src/firecrawl-fetch-provider-shared.ts @@ -23,10 +23,7 @@ export const FIRECRAWL_WEB_FETCH_PROVIDER_SHARED = { docsUrl: "https://docs.firecrawl.dev", autoDetectOrder: 50, credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey", - inactiveSecretPaths: [ - "plugins.entries.firecrawl.config.webFetch.apiKey", - "tools.web.fetch.firecrawl.apiKey", - ], + inactiveSecretPaths: ["plugins.entries.firecrawl.config.webFetch.apiKey"], getCredentialValue: (fetchConfig) => { if (!fetchConfig || typeof fetchConfig !== "object") { return undefined; diff --git a/extensions/google/openclaw.plugin.json b/extensions/google/openclaw.plugin.json index 4507342f5063..8cc8831cf49e 100644 --- a/extensions/google/openclaw.plugin.json +++ b/extensions/google/openclaw.plugin.json @@ -756,9 +756,6 @@ "nativeDocumentInputs": ["pdf"] } }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/google/src/gemini-web-search-provider.runtime.ts b/extensions/google/src/gemini-web-search-provider.runtime.ts index 372e3d6eb650..973c1fe97fae 100644 --- a/extensions/google/src/gemini-web-search-provider.runtime.ts +++ b/extensions/google/src/gemini-web-search-provider.runtime.ts @@ -172,7 +172,7 @@ function resolveGeminiTimeRangeFilter( function resolveGeminiRuntimeApiKey(gemini?: GeminiConfig): string | undefined { return ( - readConfiguredSecretString(gemini?.apiKey, "tools.web.search.gemini.apiKey") ?? + readConfiguredSecretString(gemini?.apiKey, "plugins.entries.google.config.webSearch.apiKey") ?? readProviderEnvValue(["GEMINI_API_KEY"]) ?? readConfiguredSecretString(gemini?.providerApiKey, "models.providers.google.apiKey") ); diff --git a/extensions/googlechat/openclaw.plugin.json b/extensions/googlechat/openclaw.plugin.json index 7517acedd991..6bbdc8d8038f 100644 --- a/extensions/googlechat/openclaw.plugin.json +++ b/extensions/googlechat/openclaw.plugin.json @@ -7,9 +7,6 @@ "onStartup": false }, "channels": ["googlechat"], - "channelEnvVars": { - "googlechat": ["GOOGLE_CHAT_SERVICE_ACCOUNT", "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 8d26da79c499..3535e5e8de69 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -30,6 +30,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "googlechat", + "configuredState": { + "env": { + "anyOf": [ + "GOOGLE_CHAT_SERVICE_ACCOUNT", + "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE" + ] + } + }, "approvalFlags": ["native"], "label": "Google Chat", "selectionLabel": "Google Chat (Chat API)", diff --git a/extensions/imessage/src/shared.ts b/extensions/imessage/src/shared.ts index 3538e0278c5b..773674a88a52 100644 --- a/extensions/imessage/src/shared.ts +++ b/extensions/imessage/src/shared.ts @@ -77,7 +77,7 @@ export function createIMessagePluginBase(params: { meta: { ...getChatChannelMeta(IMESSAGE_CHANNEL), aliases: ["imsg"], - showConfigured: false, + exposure: { configured: false }, }, setupWizard: params.setupWizard, capabilities: { diff --git a/extensions/irc/openclaw.plugin.json b/extensions/irc/openclaw.plugin.json index 3d13633c75f5..8d1911c6c5b0 100644 --- a/extensions/irc/openclaw.plugin.json +++ b/extensions/irc/openclaw.plugin.json @@ -4,20 +4,6 @@ "onStartup": false }, "channels": ["irc"], - "channelEnvVars": { - "irc": [ - "IRC_HOST", - "IRC_PORT", - "IRC_TLS", - "IRC_NICK", - "IRC_USERNAME", - "IRC_REALNAME", - "IRC_PASSWORD", - "IRC_CHANNELS", - "IRC_NICKSERV_PASSWORD", - "IRC_NICKSERV_REGISTER_EMAIL" - ] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/irc/package.json b/extensions/irc/package.json index 7f337f0b75c9..127d44a79721 100644 --- a/extensions/irc/package.json +++ b/extensions/irc/package.json @@ -20,6 +20,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "irc", + "configuredState": { + "env": { + "allOf": [ + "IRC_HOST", + "IRC_NICK" + ] + } + }, "label": "IRC", "selectionLabel": "IRC (Server + Nick)", "detailLabel": "IRC", @@ -29,15 +37,7 @@ "aliases": [ "internet-relay-chat" ], - "systemImage": "network", - "configuredState": { - "env": { - "allOf": [ - "IRC_HOST", - "IRC_NICK" - ] - } - } + "systemImage": "network" }, "compat": { "pluginApi": ">=2026.7.2" diff --git a/extensions/line/openclaw.plugin.json b/extensions/line/openclaw.plugin.json index 9ce2df678b05..be058bd6488f 100644 --- a/extensions/line/openclaw.plugin.json +++ b/extensions/line/openclaw.plugin.json @@ -7,9 +7,6 @@ "onStartup": false }, "channels": ["line"], - "channelEnvVars": { - "line": ["LINE_CHANNEL_ACCESS_TOKEN", "LINE_CHANNEL_SECRET"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/line/package.json b/extensions/line/package.json index e2bd29a7e318..dd1ce8c46d0e 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -30,6 +30,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "line", + "configuredState": { + "env": { + "anyOf": [ + "LINE_CHANNEL_ACCESS_TOKEN", + "LINE_CHANNEL_SECRET" + ] + } + }, "label": "LINE", "selectionLabel": "LINE (Messaging API)", "detailLabel": "LINE Bot", diff --git a/extensions/matrix/openclaw.plugin.json b/extensions/matrix/openclaw.plugin.json index 1ba2ef7d3744..c268c0972e53 100644 --- a/extensions/matrix/openclaw.plugin.json +++ b/extensions/matrix/openclaw.plugin.json @@ -9,20 +9,6 @@ "onCommands": ["matrix"] }, "channels": ["matrix"], - "channelEnvVars": { - "matrix": [ - "MATRIX_HOMESERVER", - "MATRIX_USER_ID", - "MATRIX_ACCESS_TOKEN", - "MATRIX_PASSWORD", - "MATRIX_DEVICE_ID", - "MATRIX_DEVICE_NAME", - "MATRIX_OPS_HOMESERVER", - "MATRIX_OPS_ACCESS_TOKEN", - "MATRIX_OPS_DEVICE_ID", - "MATRIX_OPS_DEVICE_NAME" - ] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index 05e3d479348f..a9a2cd987ec1 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -39,6 +39,22 @@ }, "channel": { "id": "matrix", + "configuredState": { + "env": { + "anyOf": [ + "MATRIX_HOMESERVER", + "MATRIX_USER_ID", + "MATRIX_ACCESS_TOKEN", + "MATRIX_PASSWORD", + "MATRIX_DEVICE_ID", + "MATRIX_DEVICE_NAME", + "MATRIX_OPS_HOMESERVER", + "MATRIX_OPS_ACCESS_TOKEN", + "MATRIX_OPS_DEVICE_ID", + "MATRIX_OPS_DEVICE_NAME" + ] + } + }, "approvalFlags": ["native"], "label": "Matrix", "selectionLabel": "Matrix (plugin)", diff --git a/extensions/matrix/src/plugin-entry.runtime.js b/extensions/matrix/src/plugin-entry.runtime.js index e2d2fdb17026..76892e6b86eb 100644 --- a/extensions/matrix/src/plugin-entry.runtime.js +++ b/extensions/matrix/src/plugin-entry.runtime.js @@ -23,7 +23,7 @@ function normalizeLowercaseStringOrEmpty(value) { function hasTrustedOpenClawRootIndicator(packageRoot, packageJson) { const packageExports = packageJson?.exports ?? {}; - if (!Object.hasOwn(packageExports, "./plugin-sdk")) { + if (!Object.hasOwn(packageExports, "./plugin-sdk/core")) { return false; } const hasCliEntryExport = Object.hasOwn(packageExports, "./cli-entry"); diff --git a/extensions/matrix/src/plugin-entry.runtime.test.ts b/extensions/matrix/src/plugin-entry.runtime.test.ts index 979a4f29f4f1..b90b0421d0be 100644 --- a/extensions/matrix/src/plugin-entry.runtime.test.ts +++ b/extensions/matrix/src/plugin-entry.runtime.test.ts @@ -40,7 +40,7 @@ function writeOpenClawPackageFixture(fixtureRoot: string) { name: "openclaw", type: "module", exports: { - "./plugin-sdk": "./dist/plugin-sdk/index.js", + "./plugin-sdk/core": "./dist/plugin-sdk/core.js", }, }, null, @@ -48,7 +48,7 @@ function writeOpenClawPackageFixture(fixtureRoot: string) { ) + "\n", ); writeFixtureFile(fixtureRoot, "openclaw.mjs", "export {};\n"); - writeFixtureFile(fixtureRoot, "dist/plugin-sdk/index.js", "export {};\n"); + writeFixtureFile(fixtureRoot, "dist/plugin-sdk/core.js", "export {};\n"); } function writeSourceRuntimeWrapperFixture( diff --git a/extensions/mattermost/openclaw.plugin.json b/extensions/mattermost/openclaw.plugin.json index fbba4775282b..19d31e680457 100644 --- a/extensions/mattermost/openclaw.plugin.json +++ b/extensions/mattermost/openclaw.plugin.json @@ -5,9 +5,6 @@ "onStartup": false }, "channels": ["mattermost"], - "channelEnvVars": { - "mattermost": ["MATTERMOST_BOT_TOKEN", "MATTERMOST_URL"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 22f4c860d300..59a42e97a64a 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -30,6 +30,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "mattermost", + "configuredState": { + "env": { + "allOf": [ + "MATTERMOST_BOT_TOKEN", + "MATTERMOST_URL" + ] + } + }, "label": "Mattermost", "selectionLabel": "Mattermost (plugin)", "docsPath": "/channels/mattermost", diff --git a/extensions/memory-lancedb/index.test.ts b/extensions/memory-lancedb/index.test.ts index 6b12487bff32..190fb3573fe1 100644 --- a/extensions/memory-lancedb/index.test.ts +++ b/extensions/memory-lancedb/index.test.ts @@ -147,10 +147,6 @@ function expectHookRegistered(on: ReturnType, hookName: string) { expect(hookHandler(on, hookName)).toBeTypeOf("function"); } -function expectHookNotRegistered(on: ReturnType, hookName: string) { - expect(on.mock.calls.map(([name]) => name)).not.toContain(hookName); -} - function expectToolExecute(tool: unknown, name?: string) { const record = tool as { execute?: unknown; name?: unknown }; if (name) { @@ -409,7 +405,6 @@ describe("memory plugin e2e", () => { registerTestPlugin(memoryPlugin, mockApi); expectHookRegistered(on, "before_prompt_build"); - expectHookNotRegistered(on, "before_agent_start"); }); test("registers memory public artifact provider for memory-wiki bridge parity", async () => { diff --git a/extensions/minimax/openclaw.plugin.json b/extensions/minimax/openclaw.plugin.json index caf6a75a1797..2893bb4cf93e 100644 --- a/extensions/minimax/openclaw.plugin.json +++ b/extensions/minimax/openclaw.plugin.json @@ -97,9 +97,6 @@ "webSearchProviders": ["minimax"], "usageProviders": ["minimax"] }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "mediaUnderstandingProviderMetadata": { "minimax": { "capabilities": ["image"], diff --git a/extensions/minimax/src/minimax-web-search-provider.runtime.ts b/extensions/minimax/src/minimax-web-search-provider.runtime.ts index 250e98357520..bf3d70f2d9a7 100644 --- a/extensions/minimax/src/minimax-web-search-provider.runtime.ts +++ b/extensions/minimax/src/minimax-web-search-provider.runtime.ts @@ -57,8 +57,10 @@ type MiniMaxSearchResponse = { function resolveMiniMaxApiKey(searchConfig?: SearchConfigRecord): string | undefined { return ( - readConfiguredSecretString(searchConfig?.apiKey, "tools.web.search.apiKey") ?? - readProviderEnvValue([...MINIMAX_TOKEN_PLAN_ENV_VARS, "MINIMAX_API_KEY"]) + readConfiguredSecretString( + searchConfig?.apiKey, + "plugins.entries.minimax.config.webSearch.apiKey", + ) ?? readProviderEnvValue([...MINIMAX_TOKEN_PLAN_ENV_VARS, "MINIMAX_API_KEY"]) ); } diff --git a/extensions/moonshot/openclaw.plugin.json b/extensions/moonshot/openclaw.plugin.json index 1281123cdd62..b432ba04e86d 100644 --- a/extensions/moonshot/openclaw.plugin.json +++ b/extensions/moonshot/openclaw.plugin.json @@ -207,9 +207,6 @@ } } }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/moonshot/src/kimi-web-search-provider.runtime.ts b/extensions/moonshot/src/kimi-web-search-provider.runtime.ts index e6b071163636..9273576bb70b 100644 --- a/extensions/moonshot/src/kimi-web-search-provider.runtime.ts +++ b/extensions/moonshot/src/kimi-web-search-provider.runtime.ts @@ -98,7 +98,7 @@ function resolveKimiConfig(searchConfig?: SearchConfigRecord): KimiConfig { function resolveKimiApiKey(kimi?: KimiConfig): string | undefined { return ( - readConfiguredSecretString(kimi?.apiKey, "tools.web.search.kimi.apiKey") ?? + readConfiguredSecretString(kimi?.apiKey, "plugins.entries.moonshot.config.webSearch.apiKey") ?? readProviderEnvValue(["KIMI_API_KEY", "MOONSHOT_API_KEY"]) ); } @@ -358,7 +358,7 @@ export async function executeKimiWebSearchProviderTool( return { error: "missing_kimi_api_key", message: - "web_search (kimi) needs a Moonshot API key. Set KIMI_API_KEY or MOONSHOT_API_KEY in the Gateway environment, or configure tools.web.search.kimi.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.", + "web_search (kimi) needs a Moonshot API key. Set KIMI_API_KEY or MOONSHOT_API_KEY in the Gateway environment, or configure plugins.entries.moonshot.config.webSearch.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.", docs: "https://docs.openclaw.ai/tools/web", }; } diff --git a/extensions/msteams/openclaw.plugin.json b/extensions/msteams/openclaw.plugin.json index c0564132a23e..a947f962914e 100644 --- a/extensions/msteams/openclaw.plugin.json +++ b/extensions/msteams/openclaw.plugin.json @@ -6,9 +6,6 @@ "onStartup": false }, "channels": ["msteams"], - "channelEnvVars": { - "msteams": ["MSTEAMS_APP_ID", "MSTEAMS_APP_PASSWORD", "MSTEAMS_TENANT_ID"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index f0ac452811c7..69901d6866a5 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -36,6 +36,15 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "msteams", + "configuredState": { + "env": { + "anyOf": [ + "MSTEAMS_APP_ID", + "MSTEAMS_APP_PASSWORD", + "MSTEAMS_TENANT_ID" + ] + } + }, "label": "Microsoft Teams", "selectionLabel": "Microsoft Teams (Teams SDK)", "docsPath": "/channels/msteams", diff --git a/extensions/nextcloud-talk/openclaw.plugin.json b/extensions/nextcloud-talk/openclaw.plugin.json index f6f2fc3acbc8..9d0b1e19281d 100644 --- a/extensions/nextcloud-talk/openclaw.plugin.json +++ b/extensions/nextcloud-talk/openclaw.plugin.json @@ -7,9 +7,6 @@ "onStartup": false }, "channels": ["nextcloud-talk"], - "channelEnvVars": { - "nextcloud-talk": ["NEXTCLOUD_TALK_BOT_SECRET", "NEXTCLOUD_TALK_API_PASSWORD"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index 0192925e538e..0d162a56083f 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -26,6 +26,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "nextcloud-talk", + "configuredState": { + "env": { + "anyOf": [ + "NEXTCLOUD_TALK_BOT_SECRET", + "NEXTCLOUD_TALK_API_PASSWORD" + ] + } + }, "label": "Nextcloud Talk", "selectionLabel": "Nextcloud Talk (self-hosted)", "docsPath": "/channels/nextcloud-talk", diff --git a/extensions/nostr/openclaw.plugin.json b/extensions/nostr/openclaw.plugin.json index 1f310cbe29bc..d10df99181e4 100644 --- a/extensions/nostr/openclaw.plugin.json +++ b/extensions/nostr/openclaw.plugin.json @@ -6,9 +6,6 @@ "onStartup": false }, "channels": ["nostr"], - "channelEnvVars": { - "nostr": ["NOSTR_PRIVATE_KEY"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index 8adbe4099def..7b6fc5f80ef1 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -30,6 +30,13 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "nostr", + "configuredState": { + "env": { + "anyOf": [ + "NOSTR_PRIVATE_KEY" + ] + } + }, "label": "Nostr", "selectionLabel": "Nostr (NIP-04 DMs)", "docsPath": "/channels/nostr", diff --git a/extensions/ollama/src/discovery-shared.ts b/extensions/ollama/src/discovery-shared.ts index 5127ff1b285b..77625cadf92d 100644 --- a/extensions/ollama/src/discovery-shared.ts +++ b/extensions/ollama/src/discovery-shared.ts @@ -5,10 +5,7 @@ import type { ModelDefinitionConfig, } from "openclaw/plugin-sdk/provider-model-shared"; -/** - * Provider config input type — partial config without required `models`. - * Replaces the deprecated `openclaw/plugin-sdk/config-types` import. - */ +/** Provider config input type — partial config without required `models`. */ type OllamaProviderConfigInput = Omit, "models"> & { models?: ModelDefinitionConfig[]; }; diff --git a/extensions/ollama/src/provider-base-url.ts b/extensions/ollama/src/provider-base-url.ts index b80336ff0cac..3789b4d75d91 100644 --- a/extensions/ollama/src/provider-base-url.ts +++ b/extensions/ollama/src/provider-base-url.ts @@ -4,10 +4,7 @@ import type { ModelDefinitionConfig, } from "openclaw/plugin-sdk/provider-model-shared"; -/** - * Provider config input type — partial config without required `models`. - * Replaces the deprecated `openclaw/plugin-sdk/config-types` import. - */ +/** Provider config input type — partial config without required `models`. */ type OllamaProviderConfigInput = Omit, "models"> & { models?: ModelDefinitionConfig[]; }; diff --git a/extensions/openrouter/index.test.ts b/extensions/openrouter/index.test.ts index 170047cffb25..d6b80ea1d064 100644 --- a/extensions/openrouter/index.test.ts +++ b/extensions/openrouter/index.test.ts @@ -558,12 +558,6 @@ describe("openrouter provider hooks", () => { modelId: "openrouter/deepseek/deepseek-v4-flash", } as never)?.defaultLevel, ).toBe("high"); - expect( - provider.supportsXHighThinking?.({ - provider: "openrouter", - modelId: "openrouter/deepseek/deepseek-v4-pro", - } as never), - ).toBe(true); expect( provider.resolveThinkingProfile?.({ provider: "openrouter", diff --git a/extensions/openrouter/index.ts b/extensions/openrouter/index.ts index 7a49eb7f73cc..b35598b44185 100644 --- a/extensions/openrouter/index.ts +++ b/extensions/openrouter/index.ts @@ -31,10 +31,7 @@ import { import { resolveOpenRouterExtraParamsForTransport } from "./provider-routing.js"; import { buildOpenRouterSpeechProvider } from "./speech-provider.js"; import { wrapOpenRouterProviderStream } from "./stream.js"; -import { - resolveOpenRouterThinkingProfile, - supportsOpenRouterXHighThinking, -} from "./thinking-policy.js"; +import { resolveOpenRouterThinkingProfile } from "./thinking-policy.js"; import { fetchOpenRouterUsage } from "./usage.js"; import { buildOpenRouterVideoGenerationProvider, @@ -358,7 +355,6 @@ export default definePluginEntry({ ...passthroughGeminiReplayHooks, buildReplayPolicy: buildOpenRouterReplayPolicy, resolveReasoningOutputMode: () => "native", - supportsXHighThinking: ({ modelId }) => supportsOpenRouterXHighThinking(modelId), resolveThinkingProfile: ({ modelId }) => resolveOpenRouterThinkingProfile(modelId), isModernModelRef: () => true, resolveSystemPromptContribution: resolveOpenRouterFusionPromptContribution, diff --git a/extensions/openrouter/thinking-policy.ts b/extensions/openrouter/thinking-policy.ts index 4bc40599533e..ad507950a592 100644 --- a/extensions/openrouter/thinking-policy.ts +++ b/extensions/openrouter/thinking-policy.ts @@ -22,10 +22,6 @@ const OPENROUTER_DEEPSEEK_V4_THINKING_PROFILE = { defaultLevel: "high", } satisfies ProviderThinkingProfile; -export function supportsOpenRouterXHighThinking(modelId: string): boolean { - return isOpenRouterDeepSeekV4ModelId(modelId); -} - export function resolveOpenRouterThinkingProfile( modelId: string, ): ProviderThinkingProfile | undefined { diff --git a/extensions/parallel/openclaw.plugin.json b/extensions/parallel/openclaw.plugin.json index aea9d5cd0f08..d1457cbb48f9 100644 --- a/extensions/parallel/openclaw.plugin.json +++ b/extensions/parallel/openclaw.plugin.json @@ -26,9 +26,6 @@ "contracts": { "webSearchProviders": ["parallel", "parallel-free"] }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/parallel/src/parallel-web-search-provider.runtime.ts b/extensions/parallel/src/parallel-web-search-provider.runtime.ts index afceae49b0e6..466a00a9e075 100644 --- a/extensions/parallel/src/parallel-web-search-provider.runtime.ts +++ b/extensions/parallel/src/parallel-web-search-provider.runtime.ts @@ -62,8 +62,10 @@ function resolveParallelConfig(searchConfig?: SearchConfigRecord): ParallelConfi function resolveParallelApiKey(parallel?: ParallelConfig): string | undefined { return ( - readConfiguredSecretString(parallel?.apiKey, "tools.web.search.parallel.apiKey") ?? - readProviderEnvValue(["PARALLEL_API_KEY"]) + readConfiguredSecretString( + parallel?.apiKey, + "plugins.entries.parallel.config.webSearch.apiKey", + ) ?? readProviderEnvValue(["PARALLEL_API_KEY"]) ); } diff --git a/extensions/perplexity/openclaw.plugin.json b/extensions/perplexity/openclaw.plugin.json index 05f7fd4cc630..db7f737d8ba4 100644 --- a/extensions/perplexity/openclaw.plugin.json +++ b/extensions/perplexity/openclaw.plugin.json @@ -31,9 +31,6 @@ "contracts": { "webSearchProviders": ["perplexity"] }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/perplexity/src/perplexity-web-search-provider.runtime.ts b/extensions/perplexity/src/perplexity-web-search-provider.runtime.ts index 0dfd19c40a75..b6a6c171698d 100644 --- a/extensions/perplexity/src/perplexity-web-search-provider.runtime.ts +++ b/extensions/perplexity/src/perplexity-web-search-provider.runtime.ts @@ -81,7 +81,7 @@ function resolvePerplexityApiKey(perplexity?: PerplexityConfig): { } { const fromConfig = readConfiguredSecretString( perplexity?.apiKey, - "tools.web.search.perplexity.apiKey", + "plugins.entries.perplexity.config.webSearch.apiKey", ); if (fromConfig) { return { apiKey: fromConfig, source: "config" }; @@ -318,7 +318,7 @@ export async function executePerplexitySearch( return { error: "missing_perplexity_api_key", message: - "web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.", + "web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure plugins.entries.perplexity.config.webSearch.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.", docs: "https://docs.openclaw.ai/tools/web", }; } diff --git a/extensions/perplexity/src/perplexity-web-search-provider.test.ts b/extensions/perplexity/src/perplexity-web-search-provider.test.ts index 043ee7641819..ef5956a14c7e 100644 --- a/extensions/perplexity/src/perplexity-web-search-provider.test.ts +++ b/extensions/perplexity/src/perplexity-web-search-provider.test.ts @@ -36,7 +36,7 @@ describe("perplexity web search provider", () => { await expect(tool.execute({ query: "OpenClaw docs" })).resolves.toEqual({ error: "missing_perplexity_api_key", message: - "web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.", + "web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure plugins.entries.perplexity.config.webSearch.apiKey. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.", docs: "https://docs.openclaw.ai/tools/web", }); }, diff --git a/extensions/pixverse/openclaw.plugin.json b/extensions/pixverse/openclaw.plugin.json index 5ce7928fdec0..dc5fe35f8a43 100644 --- a/extensions/pixverse/openclaw.plugin.json +++ b/extensions/pixverse/openclaw.plugin.json @@ -6,8 +6,14 @@ "onStartup": false }, "enabledByDefault": true, - "providerAuthEnvVars": { - "pixverse": ["PIXVERSE_API_KEY"] + "setup": { + "providers": [ + { + "id": "pixverse", + "authMethods": ["api-key"], + "envVars": ["PIXVERSE_API_KEY"] + } + ] }, "providerAuthChoices": [ { diff --git a/extensions/qqbot/openclaw.plugin.json b/extensions/qqbot/openclaw.plugin.json index e3d77a3ad207..d6eed753a7c1 100644 --- a/extensions/qqbot/openclaw.plugin.json +++ b/extensions/qqbot/openclaw.plugin.json @@ -10,9 +10,6 @@ "contracts": { "tools": ["qqbot_channel_api", "qqbot_remind"] }, - "channelEnvVars": { - "qqbot": ["QQBOT_APP_ID", "QQBOT_CLIENT_SECRET"] - }, "enabledByDefault": true, "skills": ["./skills"], "configSchema": { diff --git a/extensions/qqbot/package.json b/extensions/qqbot/package.json index c4ae137ea500..b3ce89515605 100644 --- a/extensions/qqbot/package.json +++ b/extensions/qqbot/package.json @@ -37,6 +37,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "qqbot", + "configuredState": { + "env": { + "anyOf": [ + "QQBOT_APP_ID", + "QQBOT_CLIENT_SECRET" + ] + } + }, "approvalFlags": ["native"], "label": "QQ Bot", "selectionLabel": "QQ Bot (Official API)", diff --git a/extensions/qqbot/src/bridge/runtime.ts b/extensions/qqbot/src/bridge/runtime.ts index 0324c1569859..aa3a82670590 100644 --- a/extensions/qqbot/src/bridge/runtime.ts +++ b/extensions/qqbot/src/bridge/runtime.ts @@ -22,5 +22,5 @@ export { getQQBotRuntime, setQQBotRuntime }; /** Type-narrowed getter for engine/ modules that need GatewayPluginRuntime. */ export function getQQBotRuntimeForEngine(): GatewayPluginRuntime { - return getQQBotRuntime() as GatewayPluginRuntime; + return getQQBotRuntime() as unknown as GatewayPluginRuntime; } diff --git a/extensions/raft/openclaw.plugin.json b/extensions/raft/openclaw.plugin.json index 5d143f1f5d2e..ff9c24910396 100644 --- a/extensions/raft/openclaw.plugin.json +++ b/extensions/raft/openclaw.plugin.json @@ -34,9 +34,6 @@ } } }, - "channelEnvVars": { - "raft": ["RAFT_PROFILE"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/raft/package.json b/extensions/raft/package.json index e92d86331080..10d81ffd0936 100644 --- a/extensions/raft/package.json +++ b/extensions/raft/package.json @@ -40,6 +40,13 @@ }, "channel": { "id": "raft", + "configuredState": { + "env": { + "anyOf": [ + "RAFT_PROFILE" + ] + } + }, "label": "Raft", "selectionLabel": "Raft (CLI wake bridge)", "docsPath": "/channels/raft", diff --git a/extensions/slack/openclaw.plugin.json b/extensions/slack/openclaw.plugin.json index 0417c26f0daf..6744b176aa71 100644 --- a/extensions/slack/openclaw.plugin.json +++ b/extensions/slack/openclaw.plugin.json @@ -17,9 +17,6 @@ } } }, - "channelEnvVars": { - "slack": ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN", "SLACK_USER_TOKEN"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/slack/package.json b/extensions/slack/package.json index dcd126803897..a4282b74d24f 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -35,6 +35,15 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "slack", + "configuredState": { + "env": { + "anyOf": [ + "SLACK_BOT_TOKEN", + "SLACK_APP_TOKEN", + "SLACK_USER_TOKEN" + ] + } + }, "approvalFlags": ["native"], "label": "Slack", "selectionLabel": "Slack (Socket Mode)", @@ -47,15 +56,6 @@ "commands": { "nativeCommandsAutoEnabled": false, "nativeSkillsAutoEnabled": false - }, - "configuredState": { - "env": { - "anyOf": [ - "SLACK_APP_TOKEN", - "SLACK_BOT_TOKEN", - "SLACK_USER_TOKEN" - ] - } } }, "install": { diff --git a/extensions/sms/openclaw.plugin.json b/extensions/sms/openclaw.plugin.json index cb9f33a7e6c2..bad80c18ebe8 100644 --- a/extensions/sms/openclaw.plugin.json +++ b/extensions/sms/openclaw.plugin.json @@ -6,18 +6,6 @@ "onStartup": false }, "channels": ["sms"], - "channelEnvVars": { - "sms": [ - "TWILIO_ACCOUNT_SID", - "TWILIO_AUTH_TOKEN", - "TWILIO_PHONE_NUMBER", - "TWILIO_SMS_FROM", - "TWILIO_MESSAGING_SERVICE_SID", - "SMS_PUBLIC_WEBHOOK_URL", - "SMS_WEBHOOK_PATH", - "SMS_ALLOWED_USERS" - ] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/sms/package.json b/extensions/sms/package.json index b7198c43ce10..89e4fbd45305 100644 --- a/extensions/sms/package.json +++ b/extensions/sms/package.json @@ -19,6 +19,20 @@ ], "channel": { "id": "sms", + "configuredState": { + "env": { + "anyOf": [ + "TWILIO_ACCOUNT_SID", + "TWILIO_AUTH_TOKEN", + "TWILIO_PHONE_NUMBER", + "TWILIO_SMS_FROM", + "TWILIO_MESSAGING_SERVICE_SID", + "SMS_PUBLIC_WEBHOOK_URL", + "SMS_WEBHOOK_PATH", + "SMS_ALLOWED_USERS" + ] + } + }, "label": "SMS", "selectionLabel": "SMS (Twilio)", "detailLabel": "Twilio SMS", diff --git a/extensions/synology-chat/openclaw.plugin.json b/extensions/synology-chat/openclaw.plugin.json index 4448b5147f51..f6ada0ee80b9 100644 --- a/extensions/synology-chat/openclaw.plugin.json +++ b/extensions/synology-chat/openclaw.plugin.json @@ -7,16 +7,6 @@ "onStartup": false }, "channels": ["synology-chat"], - "channelEnvVars": { - "synology-chat": [ - "SYNOLOGY_CHAT_TOKEN", - "SYNOLOGY_CHAT_INCOMING_URL", - "SYNOLOGY_NAS_HOST", - "SYNOLOGY_ALLOWED_USER_IDS", - "SYNOLOGY_RATE_LIMIT", - "OPENCLAW_BOT_NAME" - ] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/synology-chat/package.json b/extensions/synology-chat/package.json index 9705857818d6..0c297b150bff 100644 --- a/extensions/synology-chat/package.json +++ b/extensions/synology-chat/package.json @@ -17,6 +17,18 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "synology-chat", + "configuredState": { + "env": { + "anyOf": [ + "SYNOLOGY_CHAT_TOKEN", + "SYNOLOGY_CHAT_INCOMING_URL", + "SYNOLOGY_NAS_HOST", + "SYNOLOGY_ALLOWED_USER_IDS", + "SYNOLOGY_RATE_LIMIT", + "OPENCLAW_BOT_NAME" + ] + } + }, "label": "Synology Chat", "selectionLabel": "Synology Chat (Webhook)", "docsPath": "/channels/synology-chat", diff --git a/extensions/tavily/openclaw.plugin.json b/extensions/tavily/openclaw.plugin.json index 0822c3e094f2..9645714d5185 100644 --- a/extensions/tavily/openclaw.plugin.json +++ b/extensions/tavily/openclaw.plugin.json @@ -28,9 +28,6 @@ "webSearchProviders": ["tavily"], "tools": ["tavily_search", "tavily_extract"] }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/telegram/openclaw.plugin.json b/extensions/telegram/openclaw.plugin.json index ad89ed8cbb5b..c9d164cb61db 100644 --- a/extensions/telegram/openclaw.plugin.json +++ b/extensions/telegram/openclaw.plugin.json @@ -5,9 +5,6 @@ "onStartup": false }, "channels": ["telegram"], - "channelEnvVars": { - "telegram": ["TELEGRAM_BOT_TOKEN"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index 6c27262a608d..29eec8448212 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -25,6 +25,13 @@ }, "channel": { "id": "telegram", + "configuredState": { + "env": { + "anyOf": [ + "TELEGRAM_BOT_TOKEN" + ] + } + }, "approvalFlags": ["native"], "label": "Telegram", "selectionLabel": "Telegram (Bot API)", @@ -42,13 +49,6 @@ "commands": { "nativeCommandsAutoEnabled": true, "nativeSkillsAutoEnabled": true - }, - "configuredState": { - "env": { - "allOf": [ - "TELEGRAM_BOT_TOKEN" - ] - } } } } diff --git a/extensions/tsconfig.package-boundary.paths.json b/extensions/tsconfig.package-boundary.paths.json index 94ef0d8da9ec..129cc5a3a41f 100644 --- a/extensions/tsconfig.package-boundary.paths.json +++ b/extensions/tsconfig.package-boundary.paths.json @@ -2,27 +2,168 @@ "extends": "../tsconfig.json", "compilerOptions": { "paths": { - "openclaw/extension-api": [ - "../src/extensionAPI.ts" - ], - "openclaw/plugin-sdk": [ - "../dist/plugin-sdk/index.d.ts" - ], "openclaw/plugin-sdk/*": [ "../dist/plugin-sdk/*.d.ts" ], + "openclaw/plugin-sdk/provider-setup": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-setup.d.ts" + ], + "openclaw/plugin-sdk/sandbox": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/sandbox.d.ts" + ], + "openclaw/plugin-sdk/runtime-doctor": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/runtime-doctor.d.ts" + ], + "openclaw/plugin-sdk/approval-reference-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/approval-reference-runtime.d.ts" + ], + "openclaw/plugin-sdk/approval-reaction-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/approval-reaction-runtime.d.ts" + ], + "openclaw/plugin-sdk/cron-store-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/cron-store-runtime.d.ts" + ], + "openclaw/plugin-sdk/json-schema-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/json-schema-runtime.d.ts" + ], + "openclaw/plugin-sdk/json-unsafe-integers": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/json-unsafe-integers.d.ts" + ], + "openclaw/plugin-sdk/reply-reference": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/reply-reference.d.ts" + ], "openclaw/plugin-sdk/reply-payload-testing": [ "../packages/plugin-sdk/dist/src/plugin-sdk/reply-payload-testing.d.ts" ], - "openclaw/plugin-sdk/sqlite-runtime-testing": [ - "../packages/plugin-sdk/dist/src/plugin-sdk/sqlite-runtime-testing.d.ts" + "openclaw/plugin-sdk/outbound-media": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/outbound-media.d.ts" + ], + "openclaw/plugin-sdk/pair-loop-guard-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/pair-loop-guard-runtime.d.ts" + ], + "openclaw/plugin-sdk/poll-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/poll-runtime.d.ts" + ], + "openclaw/plugin-sdk/async-lock-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/async-lock-runtime.d.ts" + ], + "openclaw/plugin-sdk/channel-activity-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/channel-activity-runtime.d.ts" + ], + "openclaw/plugin-sdk/concurrency-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/concurrency-runtime.d.ts" + ], + "openclaw/plugin-sdk/delivery-queue-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/delivery-queue-runtime.d.ts" + ], + "openclaw/plugin-sdk/file-access-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/file-access-runtime.d.ts" + ], + "openclaw/plugin-sdk/heartbeat-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/heartbeat-runtime.d.ts" + ], + "openclaw/plugin-sdk/expect-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/expect-runtime.d.ts" + ], + "openclaw/plugin-sdk/number-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/number-runtime.d.ts" + ], + "openclaw/plugin-sdk/secure-random-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/secure-random-runtime.d.ts" + ], + "openclaw/plugin-sdk/system-event-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/system-event-runtime.d.ts" + ], + "openclaw/plugin-sdk/transport-ready-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/transport-ready-runtime.d.ts" + ], + "openclaw/plugin-sdk/exec-approvals-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/exec-approvals-runtime.d.ts" + ], + "openclaw/plugin-sdk/talk-config-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/talk-config-runtime.d.ts" ], "openclaw/plugin-sdk/ssrf-runtime-internal": [ "../packages/plugin-sdk/dist/src/plugin-sdk/ssrf-runtime-internal.d.ts" ], + "openclaw/plugin-sdk/embedding-providers": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/embedding-providers.d.ts" + ], + "openclaw/plugin-sdk/media-generation-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/media-generation-runtime.d.ts" + ], + "openclaw/plugin-sdk/conversation-binding-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/conversation-binding-runtime.d.ts" + ], + "openclaw/plugin-sdk/thread-bindings-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/thread-bindings-runtime.d.ts" + ], + "openclaw/plugin-sdk/thread-bindings-session-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/thread-bindings-session-runtime.d.ts" + ], + "openclaw/plugin-sdk/simple-completion-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/simple-completion-runtime.d.ts" + ], + "openclaw/plugin-sdk/speech-core": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/speech-core.d.ts" + ], + "openclaw/plugin-sdk/tts-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/tts-runtime.d.ts" + ], + "openclaw/plugin-sdk/channel-secret-tts-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/channel-secret-tts-runtime.d.ts" + ], + "openclaw/plugin-sdk/secret-file-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/secret-file-runtime.d.ts" + ], + "openclaw/plugin-sdk/cli-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/cli-runtime.d.ts" + ], + "openclaw/plugin-sdk/cli-backend": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/cli-backend.d.ts" + ], + "openclaw/plugin-sdk/codex-mcp-projection": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/codex-mcp-projection.d.ts" + ], "openclaw/plugin-sdk/codex-native-task-runtime": [ "../packages/plugin-sdk/dist/src/plugin-sdk/codex-native-task-runtime.d.ts" ], + "openclaw/plugin-sdk/agent-harness-task-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/agent-harness-task-runtime.d.ts" + ], + "openclaw/plugin-sdk/agent-harness-exec-review-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/agent-harness-exec-review-runtime.d.ts" + ], + "openclaw/plugin-sdk/agent-harness-tool-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/agent-harness-tool-runtime.d.ts" + ], + "openclaw/plugin-sdk/html-entity-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/html-entity-runtime.d.ts" + ], + "openclaw/plugin-sdk/host-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/host-runtime.d.ts" + ], + "openclaw/plugin-sdk/types": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/types.d.ts" + ], + "openclaw/plugin-sdk/process-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/process-runtime.d.ts" + ], + "openclaw/plugin-sdk/windows-spawn": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/windows-spawn.d.ts" + ], + "openclaw/plugin-sdk/acp-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/acp-runtime.d.ts" + ], + "openclaw/plugin-sdk/acp-runtime-backend": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/acp-runtime-backend.d.ts" + ], + "openclaw/plugin-sdk/acp-binding-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/acp-binding-runtime.d.ts" + ], + "openclaw/plugin-sdk/acp-binding-resolve-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/acp-binding-resolve-runtime.d.ts" + ], "openclaw/plugin-sdk/agent-runtime-test-contracts": [ "../packages/plugin-sdk/dist/src/plugin-sdk/agent-runtime-test-contracts.d.ts" ], @@ -68,12 +209,321 @@ "openclaw/plugin-sdk/test-node-mocks": [ "../packages/plugin-sdk/dist/src/plugin-sdk/test-node-mocks.d.ts" ], + "openclaw/plugin-sdk/time-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/time-runtime.d.ts" + ], + "openclaw/plugin-sdk/migration": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/migration.d.ts" + ], + "openclaw/plugin-sdk/migration-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/migration-runtime.d.ts" + ], + "openclaw/plugin-sdk/plugin-state-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/plugin-state-runtime.d.ts" + ], "openclaw/plugin-sdk/plugin-state-test-runtime": [ "../packages/plugin-sdk/dist/src/plugin-sdk/plugin-state-test-runtime.d.ts" ], + "openclaw/plugin-sdk/markdown-table-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/markdown-table-runtime.d.ts" + ], + "openclaw/plugin-sdk/account-resolution-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/account-resolution-runtime.d.ts" + ], + "openclaw/plugin-sdk/access-groups": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/access-groups.d.ts" + ], + "openclaw/plugin-sdk/browser-config": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/browser-config.d.ts" + ], "openclaw/plugin-sdk/channel-contract-testing": [ "../packages/plugin-sdk/dist/src/plugin-sdk/channel-contract-testing.d.ts" ], + "openclaw/plugin-sdk/dangerous-name-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/dangerous-name-runtime.d.ts" + ], + "openclaw/plugin-sdk/command-status-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/command-status-runtime.d.ts" + ], + "openclaw/plugin-sdk/command-surface": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/command-surface.d.ts" + ], + "openclaw/plugin-sdk/direct-dm-guard-policy": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/direct-dm-guard-policy.d.ts" + ], + "openclaw/plugin-sdk/channel-config-writes": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/channel-config-writes.d.ts" + ], + "openclaw/plugin-sdk/bundled-channel-config-schema": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/bundled-channel-config-schema.d.ts" + ], + "openclaw/plugin-sdk/chat-channel-ids": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/chat-channel-ids.d.ts" + ], + "openclaw/plugin-sdk/channel-mention-gating": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/channel-mention-gating.d.ts" + ], + "openclaw/plugin-sdk/channel-route": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/channel-route.d.ts" + ], + "openclaw/plugin-sdk/channel-targets": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/channel-targets.d.ts" + ], + "openclaw/plugin-sdk/context-visibility-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/context-visibility-runtime.d.ts" + ], + "openclaw/plugin-sdk/file-lock": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/file-lock.d.ts" + ], + "openclaw/plugin-sdk/fetch-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/fetch-runtime.d.ts" + ], + "openclaw/plugin-sdk/runtime-fetch": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/runtime-fetch.d.ts" + ], + "openclaw/plugin-sdk/inline-image-data-url-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/inline-image-data-url-runtime.d.ts" + ], + "openclaw/plugin-sdk/node-host": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/node-host.d.ts" + ], + "openclaw/plugin-sdk/response-limit-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/response-limit-runtime.d.ts" + ], + "openclaw/plugin-sdk/session-binding-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/session-binding-runtime.d.ts" + ], + "openclaw/plugin-sdk/session-catalog": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/session-catalog.d.ts" + ], + "openclaw/plugin-sdk/session-key-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/session-key-runtime.d.ts" + ], + "openclaw/plugin-sdk/session-transcript-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/session-transcript-runtime.d.ts" + ], + "openclaw/plugin-sdk/sqlite-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/sqlite-runtime.d.ts" + ], + "openclaw/plugin-sdk/sqlite-runtime-testing": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/sqlite-runtime-testing.d.ts" + ], + "openclaw/plugin-sdk/session-transcript-hit": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/session-transcript-hit.d.ts" + ], + "openclaw/plugin-sdk/session-visibility": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/session-visibility.d.ts" + ], + "openclaw/plugin-sdk/ssrf-dispatcher": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/ssrf-dispatcher.d.ts" + ], + "openclaw/plugin-sdk/group-activation": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/group-activation.d.ts" + ], + "openclaw/plugin-sdk/global-singleton": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/global-singleton.d.ts" + ], + "openclaw/plugin-sdk/directory-config-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/directory-config-runtime.d.ts" + ], + "openclaw/plugin-sdk/image-generation": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/image-generation.d.ts" + ], + "openclaw/plugin-sdk/image-generation-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/image-generation-runtime.d.ts" + ], + "openclaw/plugin-sdk/image-generation-core": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/image-generation-core.d.ts" + ], + "openclaw/plugin-sdk/music-generation": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/music-generation.d.ts" + ], + "openclaw/plugin-sdk/video-generation": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/video-generation.d.ts" + ], + "openclaw/plugin-sdk/video-generation-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/video-generation-runtime.d.ts" + ], + "openclaw/plugin-sdk/video-generation-core": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/video-generation-core.d.ts" + ], + "openclaw/plugin-sdk/realtime-transcription": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/realtime-transcription.d.ts" + ], + "openclaw/plugin-sdk/realtime-bootstrap-context": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/realtime-bootstrap-context.d.ts" + ], + "openclaw/plugin-sdk/realtime-voice": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/realtime-voice.d.ts" + ], + "openclaw/plugin-sdk/transcripts": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/transcripts.d.ts" + ], + "openclaw/plugin-sdk/request-url": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/request-url.d.ts" + ], + "openclaw/plugin-sdk/keyed-async-queue": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/keyed-async-queue.d.ts" + ], + "openclaw/plugin-sdk/qa-runner-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/qa-runner-runtime.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-embedding-registry": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-embedding-registry.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-engine-embeddings": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-engine-embeddings.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-engine-qmd": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-engine-qmd.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-engine-storage": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-engine-storage.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-secret": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-secret.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-status": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-status.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-runtime-cli": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-runtime-cli.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-runtime-core": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-runtime-core.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-runtime-files": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-runtime-files.d.ts" + ], + "openclaw/plugin-sdk/memory-host-events": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-host-events.d.ts" + ], + "openclaw/plugin-sdk/memory-host-markdown": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-host-markdown.d.ts" + ], + "openclaw/plugin-sdk/memory-host-search": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/memory-host-search.d.ts" + ], + "openclaw/plugin-sdk/message-tool-delivery-hints": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/message-tool-delivery-hints.d.ts" + ], + "openclaw/plugin-sdk/provider-oauth-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-oauth-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-login-flow-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-login-flow-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-api-key": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-api-key.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-result": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-result.d.ts" + ], + "openclaw/plugin-sdk/provider-selection-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-selection-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-catalog-live-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-catalog-live-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-catalog-shared": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-catalog-shared.d.ts" + ], + "openclaw/plugin-sdk/provider-entry": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-entry.d.ts" + ], + "openclaw/plugin-sdk/provider-env-vars": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-env-vars.d.ts" + ], + "openclaw/plugin-sdk/provider-http": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-http.d.ts" + ], + "openclaw/plugin-sdk/provider-model-types": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-model-types.d.ts" + ], + "openclaw/plugin-sdk/provider-model-shared": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-model-shared.d.ts" + ], + "openclaw/plugin-sdk/provider-onboard": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-onboard.d.ts" + ], + "openclaw/plugin-sdk/provider-stream-family": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-stream-family.d.ts" + ], + "openclaw/plugin-sdk/provider-stream-shared": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-stream-shared.d.ts" + ], + "openclaw/plugin-sdk/provider-transport-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-transport-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-stream": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-stream.d.ts" + ], + "openclaw/plugin-sdk/provider-tools": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-tools.d.ts" + ], + "openclaw/plugin-sdk/provider-usage": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-usage.d.ts" + ], + "openclaw/plugin-sdk/document-extractor": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/document-extractor.d.ts" + ], + "openclaw/plugin-sdk/web-content-extractor": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/web-content-extractor.d.ts" + ], + "openclaw/plugin-sdk/provider-web-fetch-contract": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-fetch-contract.d.ts" + ], + "openclaw/plugin-sdk/provider-web-fetch": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-fetch.d.ts" + ], + "openclaw/plugin-sdk/provider-web-search-config-contract": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-search-config-contract.d.ts" + ], + "openclaw/plugin-sdk/provider-web-search-contract": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-search-contract.d.ts" + ], + "openclaw/plugin-sdk/provider-web-search": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-search.d.ts" + ], + "openclaw/plugin-sdk/retry-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/retry-runtime.d.ts" + ], + "openclaw/plugin-sdk/speech": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/speech.d.ts" + ], + "openclaw/plugin-sdk/string-normalization-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/string-normalization-runtime.d.ts" + ], + "openclaw/plugin-sdk/target-resolver-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/target-resolver-runtime.d.ts" + ], + "openclaw/plugin-sdk/text-autolink-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/text-autolink-runtime.d.ts" + ], + "openclaw/plugin-sdk/text-utility-runtime": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/text-utility-runtime.d.ts" + ], + "openclaw/plugin-sdk/tool-payload": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/tool-payload.d.ts" + ], + "openclaw/plugin-sdk/tool-results": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/tool-results.d.ts" + ], + "openclaw/plugin-sdk/webhook-targets": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/webhook-targets.d.ts" + ], + "openclaw/plugin-sdk/agent-core": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/agent-core.d.ts" + ], + "openclaw/plugin-sdk/agent-sessions": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/agent-sessions.d.ts" + ], + "openclaw/plugin-sdk/llm": [ + "../packages/plugin-sdk/dist/src/plugin-sdk/llm.d.ts" + ], "openclaw/plugin-sdk/account-id": [ "../dist/plugin-sdk/account-id.d.ts" ], @@ -89,24 +539,12 @@ "openclaw/plugin-sdk/channel-secret-runtime": [ "../dist/plugin-sdk/channel-secret-runtime.d.ts" ], - "openclaw/plugin-sdk/channel-secret-tts-runtime": [ - "../dist/plugin-sdk/channel-secret-tts-runtime.d.ts" - ], "openclaw/plugin-sdk/channel-streaming": [ "../dist/plugin-sdk/channel-streaming.d.ts" ], "openclaw/plugin-sdk/error-runtime": [ "../dist/plugin-sdk/error-runtime.d.ts" ], - "openclaw/plugin-sdk/provider-catalog-live-runtime": [ - "../dist/plugin-sdk/provider-catalog-live-runtime.d.ts" - ], - "openclaw/plugin-sdk/provider-catalog-shared": [ - "../dist/plugin-sdk/provider-catalog-shared.d.ts" - ], - "openclaw/plugin-sdk/provider-entry": [ - "../dist/plugin-sdk/provider-entry.d.ts" - ], "openclaw/plugin-sdk/secret-ref-runtime": [ "../dist/plugin-sdk/secret-ref-runtime.d.ts" ], diff --git a/extensions/twitch/openclaw.plugin.json b/extensions/twitch/openclaw.plugin.json index bd4b152bb7c2..952933252e4e 100644 --- a/extensions/twitch/openclaw.plugin.json +++ b/extensions/twitch/openclaw.plugin.json @@ -7,9 +7,6 @@ "onStartup": false }, "channels": ["twitch"], - "channelEnvVars": { - "twitch": ["OPENCLAW_TWITCH_ACCESS_TOKEN"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index dd9c347bf910..2fea52f35daa 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -34,6 +34,13 @@ }, "channel": { "id": "twitch", + "configuredState": { + "env": { + "anyOf": [ + "OPENCLAW_TWITCH_ACCESS_TOKEN" + ] + } + }, "label": "Twitch", "selectionLabel": "Twitch (Chat)", "docsPath": "/channels/twitch", diff --git a/extensions/voice-call/openclaw.plugin.json b/extensions/voice-call/openclaw.plugin.json index 8738282834ed..a29890999d5c 100644 --- a/extensions/voice-call/openclaw.plugin.json +++ b/extensions/voice-call/openclaw.plugin.json @@ -11,20 +11,6 @@ "contracts": { "tools": ["voice_call"] }, - "channelEnvVars": { - "voice-call": [ - "TELNYX_API_KEY", - "TELNYX_CONNECTION_ID", - "TELNYX_PUBLIC_KEY", - "TWILIO_ACCOUNT_SID", - "TWILIO_AUTH_TOKEN", - "TWILIO_FROM_NUMBER", - "PLIVO_AUTH_ID", - "PLIVO_AUTH_TOKEN", - "NGROK_AUTHTOKEN", - "NGROK_DOMAIN" - ] - }, "uiHints": { "provider": { "label": "Provider", diff --git a/extensions/whatsapp/src/shared.ts b/extensions/whatsapp/src/shared.ts index eabe7f6fa65c..995b73e6dee2 100644 --- a/extensions/whatsapp/src/shared.ts +++ b/extensions/whatsapp/src/shared.ts @@ -154,7 +154,7 @@ export function createWhatsAppPluginBase(params: { docsLabel: "whatsapp", blurb: "works with your own number; recommend a separate phone + eSIM.", systemImage: "message", - showConfigured: false, + exposure: { configured: false }, quickstartAllowFrom: true, forceAccountBinding: true, preferSessionLookupForAnnounceTarget: true, diff --git a/extensions/xai/code-execution.test.ts b/extensions/xai/code-execution.test.ts index 7e9d786b9cef..dc91cf27308c 100644 --- a/extensions/xai/code-execution.test.ts +++ b/extensions/xai/code-execution.test.ts @@ -245,27 +245,4 @@ describe("xai code_execution tool", () => { }), ).rejects.toThrow("xAI code execution failed: malformed JSON response"); }); - - it("reuses the legacy grok web search key for code_execution requests", async () => { - const mockFetch = installCodeExecutionFetch(); - const tool = createCodeExecutionTool({ - config: { - tools: { - web: { - search: { - grok: { - apiKey: "xai-legacy-key", // pragma: allowlist secret - }, - }, - }, - }, - }, - }); - - await tool?.execute?.("code-execution:legacy-key", { - task: "Count rows in a two-column table", - }); - - expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer xai-legacy-key"); - }); }); diff --git a/extensions/xai/openclaw.plugin.json b/extensions/xai/openclaw.plugin.json index 082f5713164d..eff10cc0913d 100644 --- a/extensions/xai/openclaw.plugin.json +++ b/extensions/xai/openclaw.plugin.json @@ -215,10 +215,6 @@ "rootPath": "plugins.entries.xai.config", "overlayPath": "webSearch", "required": ["apiKey"] - }, - { - "rootPath": "tools.web.search.grok", - "required": ["apiKey"] } ] }, @@ -234,17 +230,10 @@ "rootPath": "plugins.entries.xai.config", "overlayPath": "webSearch", "required": ["apiKey"] - }, - { - "rootPath": "tools.web.search.grok", - "required": ["apiKey"] } ] } }, - "configContracts": { - "compatibilityRuntimePaths": ["tools.web.search.apiKey"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/xai/setup-api.ts b/extensions/xai/setup-api.ts index 629b69edda54..697f03a21071 100644 --- a/extensions/xai/setup-api.ts +++ b/extensions/xai/setup-api.ts @@ -9,11 +9,9 @@ export default definePluginEntry({ register(api) { api.registerAutoEnableProbe(({ config }) => { const pluginConfig = config.plugins?.entries?.xai?.config; - const web = config.tools?.web as Record | undefined; if ( - isRecord(web?.x_search) || - (isRecord(pluginConfig) && - (isRecord(pluginConfig.xSearch) || isRecord(pluginConfig.codeExecution))) + isRecord(pluginConfig) && + (isRecord(pluginConfig.xSearch) || isRecord(pluginConfig.codeExecution)) ) { return "xai tool configured"; } diff --git a/extensions/xai/src/tool-auth-shared.test.ts b/extensions/xai/src/tool-auth-shared.test.ts index 1e20c3686a21..2c6dd13f736a 100644 --- a/extensions/xai/src/tool-auth-shared.test.ts +++ b/extensions/xai/src/tool-auth-shared.test.ts @@ -12,7 +12,7 @@ describe("xai tool auth helpers", () => { vi.unstubAllEnvs(); }); - it("prefers plugin web search keys over legacy grok keys", () => { + it("uses plugin web search keys", () => { expect( resolveFallbackXaiAuth({ plugins: { @@ -26,15 +26,6 @@ describe("xai tool auth helpers", () => { }, }, }, - tools: { - web: { - search: { - grok: { - apiKey: "legacy-key", // pragma: allowlist secret - }, - }, - }, - }, }), ).toEqual({ apiKey: "plugin-key", @@ -61,23 +52,6 @@ describe("xai tool auth helpers", () => { apiKey: NON_ENV_SECRETREF_MARKER, source: "plugins.entries.xai.config.webSearch.apiKey", }); - - expect( - resolveFallbackXaiAuth({ - tools: { - web: { - search: { - grok: { - apiKey: "legacy-key", // pragma: allowlist secret - }, - }, - }, - }, - }), - ).toEqual({ - apiKey: "legacy-key", - source: "tools.web.search.grok.apiKey", - }); }); it("falls back to runtime, then source config, then env for tool auth", async () => { diff --git a/extensions/xai/src/tool-auth-shared.ts b/extensions/xai/src/tool-auth-shared.ts index 4921a23d23e3..7bdcde28f355 100644 --- a/extensions/xai/src/tool-auth-shared.ts +++ b/extensions/xai/src/tool-auth-shared.ts @@ -40,18 +40,6 @@ function readConfiguredOrManagedApiKey(value: unknown): string | undefined { return ref ? resolveNonEnvSecretRefApiKeyMarker(ref.source) : undefined; } -function readLegacyGrokFallbackAuth(cfg?: OpenClawConfig): XaiFallbackAuth | undefined { - const search = cfg?.tools?.web?.search; - if (!search || typeof search !== "object") { - return undefined; - } - const grok = (search as Record).grok; - const apiKey = readConfiguredOrManagedApiKey( - grok && typeof grok === "object" ? (grok as Record).apiKey : undefined, - ); - return apiKey ? { apiKey, source: "tools.web.search.grok.apiKey" } : undefined; -} - function readConfiguredRuntimeApiKey( value: unknown, path: string, @@ -89,19 +77,6 @@ function readConfiguredRuntimeApiKey( return envValue ? { status: "available", value: envValue } : { status: "missing" }; } -function readLegacyGrokApiKeyResult(cfg?: OpenClawConfig): ConfiguredRuntimeApiKeyResolution { - const search = cfg?.tools?.web?.search; - if (!search || typeof search !== "object") { - return { status: "missing" }; - } - const grok = (search as Record).grok; - return readConfiguredRuntimeApiKey( - grok && typeof grok === "object" ? (grok as Record).apiKey : undefined, - "tools.web.search.grok.apiKey", - cfg, - ); -} - function readPluginXaiWebSearchApiKeyResult( cfg?: OpenClawConfig, ): ConfiguredRuntimeApiKeyResolution { @@ -120,18 +95,10 @@ function resolveConfiguredXaiToolApiKeyResult(params: { if (runtimePlugin.status === "available" || runtimePlugin.status === "blocked") { return runtimePlugin; } - const runtimeLegacy = readLegacyGrokApiKeyResult(params.runtimeConfig); - if (runtimeLegacy.status === "available" || runtimeLegacy.status === "blocked") { - return runtimeLegacy; - } const sourcePlugin = readPluginXaiWebSearchApiKeyResult(params.sourceConfig); if (sourcePlugin.status === "available" || sourcePlugin.status === "blocked") { return sourcePlugin; } - const sourceLegacy = readLegacyGrokApiKeyResult(params.sourceConfig); - if (sourceLegacy.status === "available" || sourceLegacy.status === "blocked") { - return sourceLegacy; - } return { status: "missing" }; } @@ -154,7 +121,7 @@ export function resolveFallbackXaiAuth(cfg?: OpenClawConfig): XaiFallbackAuth | source: "plugins.entries.xai.config.webSearch.apiKey", }; } - return readLegacyGrokFallbackAuth(cfg); + return undefined; } export async function resolveXaiToolApiKeyWithAuth(params: { diff --git a/extensions/xai/src/web-search-provider.runtime.ts b/extensions/xai/src/web-search-provider.runtime.ts index 5eeb5788c166..15da9348aec3 100644 --- a/extensions/xai/src/web-search-provider.runtime.ts +++ b/extensions/xai/src/web-search-provider.runtime.ts @@ -179,7 +179,7 @@ function resolveXaiToolSearchConfig(ctx: { function resolveXaiWebSearchCredential(searchConfig?: Record): string | undefined { return resolveWebSearchProviderCredential({ credentialValue: getScopedCredentialValue(searchConfig, "grok"), - path: "tools.web.search.grok.apiKey", + path: "plugins.entries.xai.config.webSearch.apiKey", envVars: ["XAI_API_KEY"], }); } @@ -189,7 +189,7 @@ function resolveConfiguredXaiWebSearchCredential( ): string | undefined { return resolveWebSearchProviderCredential({ credentialValue: getScopedCredentialValue(searchConfig, "grok"), - path: "tools.web.search.grok.apiKey", + path: "plugins.entries.xai.config.webSearch.apiKey", envVars: [], }); } diff --git a/extensions/xai/src/x-search-config.ts b/extensions/xai/src/x-search-config.ts index bfe5810cd050..62e3c318ceb6 100644 --- a/extensions/xai/src/x-search-config.ts +++ b/extensions/xai/src/x-search-config.ts @@ -11,12 +11,6 @@ function cloneRecord(value: T): T { return { ...value } as T; } -function resolveLegacyXSearchConfig(config?: OpenClawConfig): JsonRecord | undefined { - const web = config?.tools?.web as Record | undefined; - const xSearch = web?.x_search; - return isRecord(xSearch) ? cloneRecord(xSearch) : undefined; -} - function resolvePluginXSearchConfig(config?: OpenClawConfig): JsonRecord | undefined { const pluginConfig = config?.plugins?.entries?.xai?.config; if (!isRecord(pluginConfig?.xSearch)) { @@ -25,15 +19,6 @@ function resolvePluginXSearchConfig(config?: OpenClawConfig): JsonRecord | undef return cloneRecord(pluginConfig.xSearch); } -function resolveLegacyGrokWebSearchConfig(config?: OpenClawConfig): JsonRecord | undefined { - const web = config?.tools?.web as Record | undefined; - const search = web?.search; - if (!isRecord(search) || !isRecord(search.grok)) { - return undefined; - } - return cloneRecord(search.grok); -} - function resolvePluginWebSearchConfig(config?: OpenClawConfig): JsonRecord | undefined { const pluginConfig = config?.plugins?.entries?.xai?.config; if (!isRecord(pluginConfig?.webSearch)) { @@ -49,14 +34,10 @@ function baseUrlFallback(config?: JsonRecord): JsonRecord | undefined { } export function resolveEffectiveXSearchConfig(config?: OpenClawConfig): JsonRecord | undefined { - const legacyGrokBaseUrl = baseUrlFallback(resolveLegacyGrokWebSearchConfig(config)); const pluginWebSearchBaseUrl = baseUrlFallback(resolvePluginWebSearchConfig(config)); - const legacy = resolveLegacyXSearchConfig(config); const pluginOwned = resolvePluginXSearchConfig(config); const merged = { - ...legacyGrokBaseUrl, ...pluginWebSearchBaseUrl, - ...legacy, ...pluginOwned, }; if (Object.keys(merged).length === 0) { diff --git a/extensions/xai/tsconfig.json b/extensions/xai/tsconfig.json index a28730d3b50c..43a732671fef 100644 --- a/extensions/xai/tsconfig.json +++ b/extensions/xai/tsconfig.json @@ -3,27 +3,165 @@ "compilerOptions": { "rootDir": ".", "paths": { - "openclaw/extension-api": [ - "../../src/extensionAPI.ts" - ], - "openclaw/plugin-sdk": [ - "../../dist/plugin-sdk/index.d.ts" - ], "openclaw/plugin-sdk/*": [ "../../dist/plugin-sdk/*.d.ts" ], + "openclaw/plugin-sdk/provider-setup": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-setup.d.ts" + ], + "openclaw/plugin-sdk/sandbox": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/sandbox.d.ts" + ], + "openclaw/plugin-sdk/runtime-doctor": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/runtime-doctor.d.ts" + ], + "openclaw/plugin-sdk/approval-reference-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/approval-reference-runtime.d.ts" + ], + "openclaw/plugin-sdk/approval-reaction-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/approval-reaction-runtime.d.ts" + ], + "openclaw/plugin-sdk/cron-store-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/cron-store-runtime.d.ts" + ], + "openclaw/plugin-sdk/json-schema-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/json-schema-runtime.d.ts" + ], + "openclaw/plugin-sdk/json-unsafe-integers": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/json-unsafe-integers.d.ts" + ], + "openclaw/plugin-sdk/reply-reference": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/reply-reference.d.ts" + ], "openclaw/plugin-sdk/reply-payload-testing": [ "../../packages/plugin-sdk/dist/src/plugin-sdk/reply-payload-testing.d.ts" ], - "openclaw/plugin-sdk/sqlite-runtime-testing": [ - "../../packages/plugin-sdk/dist/src/plugin-sdk/sqlite-runtime-testing.d.ts" + "openclaw/plugin-sdk/outbound-media": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/outbound-media.d.ts" + ], + "openclaw/plugin-sdk/pair-loop-guard-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/pair-loop-guard-runtime.d.ts" + ], + "openclaw/plugin-sdk/poll-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/poll-runtime.d.ts" + ], + "openclaw/plugin-sdk/async-lock-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/async-lock-runtime.d.ts" + ], + "openclaw/plugin-sdk/channel-activity-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/channel-activity-runtime.d.ts" + ], + "openclaw/plugin-sdk/concurrency-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/concurrency-runtime.d.ts" + ], + "openclaw/plugin-sdk/delivery-queue-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/delivery-queue-runtime.d.ts" + ], + "openclaw/plugin-sdk/file-access-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/file-access-runtime.d.ts" + ], + "openclaw/plugin-sdk/heartbeat-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/heartbeat-runtime.d.ts" + ], + "openclaw/plugin-sdk/expect-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/expect-runtime.d.ts" + ], + "openclaw/plugin-sdk/number-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/number-runtime.d.ts" + ], + "openclaw/plugin-sdk/secure-random-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/secure-random-runtime.d.ts" + ], + "openclaw/plugin-sdk/system-event-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/system-event-runtime.d.ts" + ], + "openclaw/plugin-sdk/transport-ready-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/transport-ready-runtime.d.ts" + ], + "openclaw/plugin-sdk/exec-approvals-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/exec-approvals-runtime.d.ts" + ], + "openclaw/plugin-sdk/talk-config-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/talk-config-runtime.d.ts" ], "openclaw/plugin-sdk/ssrf-runtime-internal": [ "../../packages/plugin-sdk/dist/src/plugin-sdk/ssrf-runtime-internal.d.ts" ], + "openclaw/plugin-sdk/embedding-providers": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/embedding-providers.d.ts" + ], + "openclaw/plugin-sdk/media-generation-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/media-generation-runtime.d.ts" + ], + "openclaw/plugin-sdk/conversation-binding-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/conversation-binding-runtime.d.ts" + ], + "openclaw/plugin-sdk/thread-bindings-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/thread-bindings-runtime.d.ts" + ], + "openclaw/plugin-sdk/thread-bindings-session-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/thread-bindings-session-runtime.d.ts" + ], + "openclaw/plugin-sdk/simple-completion-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/simple-completion-runtime.d.ts" + ], + "openclaw/plugin-sdk/speech-core": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/speech-core.d.ts" + ], + "openclaw/plugin-sdk/tts-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/tts-runtime.d.ts" + ], + "openclaw/plugin-sdk/secret-file-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/secret-file-runtime.d.ts" + ], + "openclaw/plugin-sdk/cli-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/cli-runtime.d.ts" + ], + "openclaw/plugin-sdk/cli-backend": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/cli-backend.d.ts" + ], + "openclaw/plugin-sdk/codex-mcp-projection": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/codex-mcp-projection.d.ts" + ], "openclaw/plugin-sdk/codex-native-task-runtime": [ "../../packages/plugin-sdk/dist/src/plugin-sdk/codex-native-task-runtime.d.ts" ], + "openclaw/plugin-sdk/agent-harness-task-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/agent-harness-task-runtime.d.ts" + ], + "openclaw/plugin-sdk/agent-harness-exec-review-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/agent-harness-exec-review-runtime.d.ts" + ], + "openclaw/plugin-sdk/agent-harness-tool-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/agent-harness-tool-runtime.d.ts" + ], + "openclaw/plugin-sdk/html-entity-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/html-entity-runtime.d.ts" + ], + "openclaw/plugin-sdk/host-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/host-runtime.d.ts" + ], + "openclaw/plugin-sdk/types": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/types.d.ts" + ], + "openclaw/plugin-sdk/process-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/process-runtime.d.ts" + ], + "openclaw/plugin-sdk/windows-spawn": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/windows-spawn.d.ts" + ], + "openclaw/plugin-sdk/acp-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/acp-runtime.d.ts" + ], + "openclaw/plugin-sdk/acp-runtime-backend": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/acp-runtime-backend.d.ts" + ], + "openclaw/plugin-sdk/acp-binding-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/acp-binding-runtime.d.ts" + ], + "openclaw/plugin-sdk/acp-binding-resolve-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/acp-binding-resolve-runtime.d.ts" + ], "openclaw/plugin-sdk/agent-runtime-test-contracts": [ "../../packages/plugin-sdk/dist/src/plugin-sdk/agent-runtime-test-contracts.d.ts" ], @@ -69,12 +207,321 @@ "openclaw/plugin-sdk/test-node-mocks": [ "../../packages/plugin-sdk/dist/src/plugin-sdk/test-node-mocks.d.ts" ], + "openclaw/plugin-sdk/time-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/time-runtime.d.ts" + ], + "openclaw/plugin-sdk/migration": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/migration.d.ts" + ], + "openclaw/plugin-sdk/migration-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/migration-runtime.d.ts" + ], + "openclaw/plugin-sdk/plugin-state-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/plugin-state-runtime.d.ts" + ], "openclaw/plugin-sdk/plugin-state-test-runtime": [ "../../packages/plugin-sdk/dist/src/plugin-sdk/plugin-state-test-runtime.d.ts" ], + "openclaw/plugin-sdk/markdown-table-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/markdown-table-runtime.d.ts" + ], + "openclaw/plugin-sdk/account-resolution-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/account-resolution-runtime.d.ts" + ], + "openclaw/plugin-sdk/access-groups": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/access-groups.d.ts" + ], + "openclaw/plugin-sdk/browser-config": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/browser-config.d.ts" + ], "openclaw/plugin-sdk/channel-contract-testing": [ "../../packages/plugin-sdk/dist/src/plugin-sdk/channel-contract-testing.d.ts" ], + "openclaw/plugin-sdk/dangerous-name-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/dangerous-name-runtime.d.ts" + ], + "openclaw/plugin-sdk/command-status-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/command-status-runtime.d.ts" + ], + "openclaw/plugin-sdk/command-surface": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/command-surface.d.ts" + ], + "openclaw/plugin-sdk/direct-dm-guard-policy": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/direct-dm-guard-policy.d.ts" + ], + "openclaw/plugin-sdk/channel-config-writes": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/channel-config-writes.d.ts" + ], + "openclaw/plugin-sdk/bundled-channel-config-schema": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/bundled-channel-config-schema.d.ts" + ], + "openclaw/plugin-sdk/chat-channel-ids": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/chat-channel-ids.d.ts" + ], + "openclaw/plugin-sdk/channel-mention-gating": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/channel-mention-gating.d.ts" + ], + "openclaw/plugin-sdk/channel-route": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/channel-route.d.ts" + ], + "openclaw/plugin-sdk/channel-targets": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/channel-targets.d.ts" + ], + "openclaw/plugin-sdk/context-visibility-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/context-visibility-runtime.d.ts" + ], + "openclaw/plugin-sdk/file-lock": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/file-lock.d.ts" + ], + "openclaw/plugin-sdk/fetch-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/fetch-runtime.d.ts" + ], + "openclaw/plugin-sdk/runtime-fetch": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/runtime-fetch.d.ts" + ], + "openclaw/plugin-sdk/inline-image-data-url-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/inline-image-data-url-runtime.d.ts" + ], + "openclaw/plugin-sdk/node-host": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/node-host.d.ts" + ], + "openclaw/plugin-sdk/response-limit-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/response-limit-runtime.d.ts" + ], + "openclaw/plugin-sdk/session-binding-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/session-binding-runtime.d.ts" + ], + "openclaw/plugin-sdk/session-catalog": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/session-catalog.d.ts" + ], + "openclaw/plugin-sdk/session-key-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/session-key-runtime.d.ts" + ], + "openclaw/plugin-sdk/session-transcript-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/session-transcript-runtime.d.ts" + ], + "openclaw/plugin-sdk/sqlite-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/sqlite-runtime.d.ts" + ], + "openclaw/plugin-sdk/sqlite-runtime-testing": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/sqlite-runtime-testing.d.ts" + ], + "openclaw/plugin-sdk/session-transcript-hit": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/session-transcript-hit.d.ts" + ], + "openclaw/plugin-sdk/session-visibility": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/session-visibility.d.ts" + ], + "openclaw/plugin-sdk/ssrf-dispatcher": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/ssrf-dispatcher.d.ts" + ], + "openclaw/plugin-sdk/group-activation": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/group-activation.d.ts" + ], + "openclaw/plugin-sdk/global-singleton": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/global-singleton.d.ts" + ], + "openclaw/plugin-sdk/directory-config-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/directory-config-runtime.d.ts" + ], + "openclaw/plugin-sdk/image-generation": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/image-generation.d.ts" + ], + "openclaw/plugin-sdk/image-generation-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/image-generation-runtime.d.ts" + ], + "openclaw/plugin-sdk/image-generation-core": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/image-generation-core.d.ts" + ], + "openclaw/plugin-sdk/music-generation": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/music-generation.d.ts" + ], + "openclaw/plugin-sdk/video-generation": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/video-generation.d.ts" + ], + "openclaw/plugin-sdk/video-generation-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/video-generation-runtime.d.ts" + ], + "openclaw/plugin-sdk/video-generation-core": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/video-generation-core.d.ts" + ], + "openclaw/plugin-sdk/realtime-transcription": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/realtime-transcription.d.ts" + ], + "openclaw/plugin-sdk/realtime-bootstrap-context": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/realtime-bootstrap-context.d.ts" + ], + "openclaw/plugin-sdk/realtime-voice": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/realtime-voice.d.ts" + ], + "openclaw/plugin-sdk/transcripts": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/transcripts.d.ts" + ], + "openclaw/plugin-sdk/request-url": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/request-url.d.ts" + ], + "openclaw/plugin-sdk/keyed-async-queue": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/keyed-async-queue.d.ts" + ], + "openclaw/plugin-sdk/qa-runner-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/qa-runner-runtime.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-embedding-registry": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-embedding-registry.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-engine-embeddings": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-engine-embeddings.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-engine-qmd": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-engine-qmd.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-engine-storage": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-engine-storage.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-secret": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-secret.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-status": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-status.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-runtime-cli": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-runtime-cli.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-runtime-core": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-runtime-core.d.ts" + ], + "openclaw/plugin-sdk/memory-core-host-runtime-files": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-core-host-runtime-files.d.ts" + ], + "openclaw/plugin-sdk/memory-host-events": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-host-events.d.ts" + ], + "openclaw/plugin-sdk/memory-host-markdown": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-host-markdown.d.ts" + ], + "openclaw/plugin-sdk/memory-host-search": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/memory-host-search.d.ts" + ], + "openclaw/plugin-sdk/message-tool-delivery-hints": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/message-tool-delivery-hints.d.ts" + ], + "openclaw/plugin-sdk/provider-oauth-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-oauth-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-login-flow-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-login-flow-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-api-key": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-api-key.d.ts" + ], + "openclaw/plugin-sdk/provider-auth-result": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-auth-result.d.ts" + ], + "openclaw/plugin-sdk/provider-selection-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-selection-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-catalog-live-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-catalog-live-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-catalog-shared": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-catalog-shared.d.ts" + ], + "openclaw/plugin-sdk/provider-entry": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-entry.d.ts" + ], + "openclaw/plugin-sdk/provider-env-vars": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-env-vars.d.ts" + ], + "openclaw/plugin-sdk/provider-http": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-http.d.ts" + ], + "openclaw/plugin-sdk/provider-model-types": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-model-types.d.ts" + ], + "openclaw/plugin-sdk/provider-model-shared": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-model-shared.d.ts" + ], + "openclaw/plugin-sdk/provider-onboard": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-onboard.d.ts" + ], + "openclaw/plugin-sdk/provider-stream-family": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-stream-family.d.ts" + ], + "openclaw/plugin-sdk/provider-stream-shared": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-stream-shared.d.ts" + ], + "openclaw/plugin-sdk/provider-transport-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-transport-runtime.d.ts" + ], + "openclaw/plugin-sdk/provider-stream": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-stream.d.ts" + ], + "openclaw/plugin-sdk/provider-tools": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-tools.d.ts" + ], + "openclaw/plugin-sdk/provider-usage": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-usage.d.ts" + ], + "openclaw/plugin-sdk/document-extractor": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/document-extractor.d.ts" + ], + "openclaw/plugin-sdk/web-content-extractor": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/web-content-extractor.d.ts" + ], + "openclaw/plugin-sdk/provider-web-fetch-contract": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-fetch-contract.d.ts" + ], + "openclaw/plugin-sdk/provider-web-fetch": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-fetch.d.ts" + ], + "openclaw/plugin-sdk/provider-web-search-config-contract": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-search-config-contract.d.ts" + ], + "openclaw/plugin-sdk/provider-web-search-contract": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-search-contract.d.ts" + ], + "openclaw/plugin-sdk/provider-web-search": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/provider-web-search.d.ts" + ], + "openclaw/plugin-sdk/retry-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/retry-runtime.d.ts" + ], + "openclaw/plugin-sdk/speech": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/speech.d.ts" + ], + "openclaw/plugin-sdk/string-normalization-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/string-normalization-runtime.d.ts" + ], + "openclaw/plugin-sdk/target-resolver-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/target-resolver-runtime.d.ts" + ], + "openclaw/plugin-sdk/text-autolink-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/text-autolink-runtime.d.ts" + ], + "openclaw/plugin-sdk/text-utility-runtime": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/text-utility-runtime.d.ts" + ], + "openclaw/plugin-sdk/tool-payload": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/tool-payload.d.ts" + ], + "openclaw/plugin-sdk/tool-results": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/tool-results.d.ts" + ], + "openclaw/plugin-sdk/webhook-targets": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/webhook-targets.d.ts" + ], + "openclaw/plugin-sdk/agent-core": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/agent-core.d.ts" + ], + "openclaw/plugin-sdk/agent-sessions": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/agent-sessions.d.ts" + ], + "openclaw/plugin-sdk/llm": [ + "../../packages/plugin-sdk/dist/src/plugin-sdk/llm.d.ts" + ], "openclaw/plugin-sdk/account-id": [ "../../dist/plugin-sdk/account-id.d.ts" ], @@ -93,15 +540,6 @@ "openclaw/plugin-sdk/error-runtime": [ "../../dist/plugin-sdk/error-runtime.d.ts" ], - "openclaw/plugin-sdk/provider-catalog-live-runtime": [ - "../../dist/plugin-sdk/provider-catalog-live-runtime.d.ts" - ], - "openclaw/plugin-sdk/provider-catalog-shared": [ - "../../dist/plugin-sdk/provider-catalog-shared.d.ts" - ], - "openclaw/plugin-sdk/provider-entry": [ - "../../dist/plugin-sdk/provider-entry.d.ts" - ], "openclaw/plugin-sdk/secret-ref-runtime": [ "../../dist/plugin-sdk/secret-ref-runtime.d.ts" ], @@ -394,15 +832,6 @@ "@openclaw/plugin-sdk/*": [ "../../dist/plugin-sdk/*.d.ts" ], - "openclaw/plugin-sdk/cli-runtime": [ - "../../dist/plugin-sdk/cli-runtime.d.ts" - ], - "openclaw/plugin-sdk/provider-env-vars": [ - "../../dist/plugin-sdk/provider-env-vars.d.ts" - ], - "openclaw/plugin-sdk/provider-web-search-contract": [ - "../../dist/plugin-sdk/provider-web-search-contract.d.ts" - ], "@openclaw/anthropic-vertex/api.js": [ "./.boundary-stubs/anthropic-vertex-api.d.ts" ], diff --git a/extensions/xai/web-search.test.ts b/extensions/xai/web-search.test.ts index a9fb8e9c9e71..ff9c8ec854aa 100644 --- a/extensions/xai/web-search.test.ts +++ b/extensions/xai/web-search.test.ts @@ -749,25 +749,6 @@ describe("xai web search config resolution", () => { }); }); - it("reuses the legacy grok web search api key for provider auth fallback", () => { - expect( - resolveFallbackXaiAuth({ - tools: { - web: { - search: { - grok: { - apiKey: "xai-legacy-fallback", // pragma: allowlist secret - }, - }, - }, - }, - } as never), - ).toEqual({ - apiKey: "xai-legacy-fallback", - source: "tools.web.search.grok.apiKey", - }); - }); - it("returns a managed marker for SecretRef-backed plugin auth fallback", () => { expect( resolveFallbackXaiAuth({ diff --git a/extensions/xai/x-search.test.ts b/extensions/xai/x-search.test.ts index 89a2eb57b240..acca84a21f97 100644 --- a/extensions/xai/x-search.test.ts +++ b/extensions/xai/x-search.test.ts @@ -338,30 +338,6 @@ describe("xai x_search tool", () => { expect(firstFetchUrl(mockFetch)).toBe("https://api.x.ai/xai-search/v1/responses"); }); - it("falls back to Grok web search baseUrl for x_search", async () => { - const mockFetch = installXSearchFetch(); - const tool = createXSearchTool({ - config: { - tools: { - web: { - search: { - grok: { - apiKey: "xai-legacy-key", // pragma: allowlist secret - baseUrl: "https://api.x.ai/legacy/v1/", - }, - }, - }, - }, - }, - }); - - await tool?.execute?.("x-search:legacy-grok-base-url", { - query: "legacy base url route", - }); - - expect(firstFetchUrl(mockFetch)).toBe("https://api.x.ai/legacy/v1/responses"); - }); - it("shares plugin webSearch.baseUrl with x_search when xSearch.baseUrl is unset", async () => { const mockFetch = installXSearchFetch(); const tool = createXSearchTool({ @@ -521,64 +497,6 @@ describe("xai x_search tool", () => { expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer x-search-runtime-key"); }); - it("reuses the legacy grok web search key for x_search requests", async () => { - const mockFetch = installXSearchFetch(); - const tool = createXSearchTool({ - config: { - tools: { - web: { - search: { - grok: { - apiKey: "xai-legacy-key", // pragma: allowlist secret - }, - }, - }, - }, - }, - }); - - await tool?.execute?.("x-search:legacy-key", { - query: "latest legacy-key post from huntharo", - }); - - expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer xai-legacy-key"); - }); - - it("uses migrated runtime auth when the source config still carries legacy x_search apiKey", async () => { - const mockFetch = installXSearchFetch(); - const tool = createXSearchTool({ - config: { - tools: { - web: { - x_search: { - apiKey: "legacy-x-search-key", // pragma: allowlist secret - enabled: true, - } as Record, - }, - }, - }, - runtimeConfig: { - plugins: { - entries: { - xai: { - config: { - webSearch: { - apiKey: "migrated-runtime-key", // pragma: allowlist secret - }, - }, - }, - }, - }, - }, - }); - - await tool?.execute?.("x-search:migrated-runtime-key", { - query: "migrated runtime auth", - }); - - expect(firstAuthorizationHeader(mockFetch)).toBe("Bearer migrated-runtime-key"); - }); - it("rejects invalid date ordering before calling xAI", async () => { const mockFetch = installXSearchFetch(); const tool = createXSearchTool({ diff --git a/extensions/xiaomi/openclaw.plugin.json b/extensions/xiaomi/openclaw.plugin.json index 645f69ea16d6..1044b0b3c095 100644 --- a/extensions/xiaomi/openclaw.plugin.json +++ b/extensions/xiaomi/openclaw.plugin.json @@ -118,10 +118,6 @@ "speechProviders": ["xiaomi"], "usageProviders": ["xiaomi", "xiaomi-token-plan"] }, - "providerAuthEnvVars": { - "xiaomi": ["XIAOMI_API_KEY"], - "xiaomi-token-plan": ["XIAOMI_TOKEN_PLAN_API_KEY"] - }, "providerAuthChoices": [ { "provider": "xiaomi", diff --git a/extensions/zalo/openclaw.plugin.json b/extensions/zalo/openclaw.plugin.json index 2ce468c297cd..f29baab21eb4 100644 --- a/extensions/zalo/openclaw.plugin.json +++ b/extensions/zalo/openclaw.plugin.json @@ -7,9 +7,6 @@ "onStartup": false }, "channels": ["zalo"], - "channelEnvVars": { - "zalo": ["ZALO_BOT_TOKEN", "ZALO_WEBHOOK_SECRET"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index dcc007b8ab25..44fe2c906351 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -26,6 +26,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "zalo", + "configuredState": { + "env": { + "anyOf": [ + "ZALO_BOT_TOKEN", + "ZALO_WEBHOOK_SECRET" + ] + } + }, "label": "Zalo", "selectionLabel": "Zalo (Bot API)", "docsPath": "/channels/zalo", diff --git a/extensions/zalouser/openclaw.plugin.json b/extensions/zalouser/openclaw.plugin.json index 2cb842c0f1cc..db07c154c064 100644 --- a/extensions/zalouser/openclaw.plugin.json +++ b/extensions/zalouser/openclaw.plugin.json @@ -10,9 +10,6 @@ "contracts": { "tools": ["zalouser"] }, - "channelEnvVars": { - "zalouser": ["ZALOUSER_PROFILE", "ZCA_PROFILE"] - }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index 3786fc47ff4c..dbe0de3c3f84 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -31,6 +31,14 @@ "setupEntry": "./setup-entry.ts", "channel": { "id": "zalouser", + "configuredState": { + "env": { + "anyOf": [ + "ZALOUSER_PROFILE", + "ZCA_PROFILE" + ] + } + }, "label": "Zalo Personal", "selectionLabel": "Zalo (Personal Account)", "docsPath": "/channels/zalouser", diff --git a/package.json b/package.json index 3ee0e92d9a5e..1d1eb601c279 100644 --- a/package.json +++ b/package.json @@ -40,34 +40,169 @@ "!dist/OpenClaw.app/**", "!dist/**/*.map", "!dist/plugin-sdk/.tsbuildinfo", + "!dist/plugin-sdk/access-groups.d.ts", + "!dist/plugin-sdk/account-resolution-runtime.d.ts", + "!dist/plugin-sdk/acp-binding-resolve-runtime.d.ts", + "!dist/plugin-sdk/acp-binding-runtime.d.ts", + "!dist/plugin-sdk/acp-runtime.d.ts", + "!dist/plugin-sdk/acp-runtime-backend.d.ts", + "!dist/plugin-sdk/agent-core.d.ts", + "!dist/plugin-sdk/agent-harness-exec-review-runtime.d.ts", + "!dist/plugin-sdk/agent-harness-task-runtime.d.ts", + "!dist/plugin-sdk/agent-harness-tool-runtime.d.ts", "!dist/plugin-sdk/agent-runtime-test-contracts.js", "!dist/plugin-sdk/agent-runtime-test-contracts.d.ts", + "!dist/plugin-sdk/agent-sessions.d.ts", + "!dist/plugin-sdk/approval-reaction-runtime.d.ts", + "!dist/plugin-sdk/approval-reference-runtime.d.ts", + "!dist/plugin-sdk/async-lock-runtime.d.ts", + "!dist/plugin-sdk/browser-config.d.ts", + "!dist/plugin-sdk/bundled-channel-config-schema.d.ts", + "!dist/plugin-sdk/channel-activity-runtime.d.ts", + "!dist/plugin-sdk/channel-config-writes.d.ts", "!dist/plugin-sdk/channel-contract-testing.js", "!dist/plugin-sdk/channel-contract-testing.d.ts", + "!dist/plugin-sdk/channel-mention-gating.d.ts", + "!dist/plugin-sdk/channel-route.d.ts", + "!dist/plugin-sdk/channel-secret-tts-runtime.d.ts", "!dist/plugin-sdk/channel-target-testing.js", "!dist/plugin-sdk/channel-target-testing.d.ts", + "!dist/plugin-sdk/channel-targets.d.ts", "!dist/plugin-sdk/channel-test-helpers.js", "!dist/plugin-sdk/channel-test-helpers.d.ts", + "!dist/plugin-sdk/chat-channel-ids.d.ts", + "!dist/plugin-sdk/cli-backend.d.ts", + "!dist/plugin-sdk/cli-runtime.d.ts", + "!dist/plugin-sdk/codex-mcp-projection.d.ts", "!dist/plugin-sdk/codex-native-task-runtime.js", "!dist/plugin-sdk/codex-native-task-runtime.d.ts", + "!dist/plugin-sdk/command-status-runtime.d.ts", + "!dist/plugin-sdk/command-surface.d.ts", + "!dist/plugin-sdk/concurrency-runtime.d.ts", + "!dist/plugin-sdk/context-visibility-runtime.d.ts", + "!dist/plugin-sdk/conversation-binding-runtime.d.ts", + "!dist/plugin-sdk/cron-store-runtime.d.ts", + "!dist/plugin-sdk/dangerous-name-runtime.d.ts", + "!dist/plugin-sdk/delivery-queue-runtime.d.ts", + "!dist/plugin-sdk/direct-dm-guard-policy.d.ts", + "!dist/plugin-sdk/directory-config-runtime.d.ts", + "!dist/plugin-sdk/document-extractor.d.ts", + "!dist/plugin-sdk/embedding-providers.d.ts", + "!dist/plugin-sdk/exec-approvals-runtime.d.ts", + "!dist/plugin-sdk/expect-runtime.d.ts", + "!dist/plugin-sdk/fetch-runtime.d.ts", + "!dist/plugin-sdk/file-access-runtime.d.ts", + "!dist/plugin-sdk/file-lock.d.ts", + "!dist/plugin-sdk/global-singleton.d.ts", + "!dist/plugin-sdk/group-activation.d.ts", + "!dist/plugin-sdk/heartbeat-runtime.d.ts", + "!dist/plugin-sdk/host-runtime.d.ts", + "!dist/plugin-sdk/html-entity-runtime.d.ts", + "!dist/plugin-sdk/image-generation.d.ts", + "!dist/plugin-sdk/image-generation-core.d.ts", + "!dist/plugin-sdk/image-generation-runtime.d.ts", + "!dist/plugin-sdk/inline-image-data-url-runtime.d.ts", + "!dist/plugin-sdk/json-schema-runtime.d.ts", + "!dist/plugin-sdk/json-unsafe-integers.d.ts", + "!dist/plugin-sdk/keyed-async-queue.d.ts", + "!dist/plugin-sdk/llm.d.ts", + "!dist/plugin-sdk/markdown-table-runtime.d.ts", + "!dist/plugin-sdk/media-generation-runtime.d.ts", + "!dist/plugin-sdk/memory-core-host-embedding-registry.d.ts", + "!dist/plugin-sdk/memory-core-host-engine-embeddings.d.ts", + "!dist/plugin-sdk/memory-core-host-engine-qmd.d.ts", + "!dist/plugin-sdk/memory-core-host-engine-storage.d.ts", + "!dist/plugin-sdk/memory-core-host-runtime-cli.d.ts", + "!dist/plugin-sdk/memory-core-host-runtime-core.d.ts", + "!dist/plugin-sdk/memory-core-host-runtime-files.d.ts", + "!dist/plugin-sdk/memory-core-host-secret.d.ts", + "!dist/plugin-sdk/memory-core-host-status.d.ts", + "!dist/plugin-sdk/memory-host-events.d.ts", + "!dist/plugin-sdk/memory-host-markdown.d.ts", + "!dist/plugin-sdk/memory-host-search.d.ts", + "!dist/plugin-sdk/message-tool-delivery-hints.d.ts", + "!dist/plugin-sdk/migration.d.ts", + "!dist/plugin-sdk/migration-runtime.d.ts", + "!dist/plugin-sdk/music-generation.d.ts", + "!dist/plugin-sdk/node-host.d.ts", + "!dist/plugin-sdk/number-runtime.d.ts", + "!dist/plugin-sdk/outbound-media.d.ts", + "!dist/plugin-sdk/pair-loop-guard-runtime.d.ts", + "!dist/plugin-sdk/plugin-state-runtime.d.ts", + "!dist/plugin-sdk/plugin-state-test-runtime.js", + "!dist/plugin-sdk/plugin-state-test-runtime.d.ts", "!dist/plugin-sdk/plugin-test-api.js", "!dist/plugin-sdk/plugin-test-api.d.ts", "!dist/plugin-sdk/plugin-test-contracts.js", "!dist/plugin-sdk/plugin-test-contracts.d.ts", - "!dist/plugin-sdk/plugin-state-test-runtime.js", - "!dist/plugin-sdk/plugin-state-test-runtime.d.ts", "!dist/plugin-sdk/plugin-test-runtime.js", "!dist/plugin-sdk/plugin-test-runtime.d.ts", + "!dist/plugin-sdk/poll-runtime.d.ts", + "!dist/plugin-sdk/process-runtime.d.ts", + "!dist/plugin-sdk/provider-auth-api-key.d.ts", + "!dist/plugin-sdk/provider-auth-login-flow-runtime.d.ts", + "!dist/plugin-sdk/provider-auth-result.d.ts", + "!dist/plugin-sdk/provider-auth-runtime.d.ts", + "!dist/plugin-sdk/provider-catalog-live-runtime.d.ts", + "!dist/plugin-sdk/provider-catalog-shared.d.ts", + "!dist/plugin-sdk/provider-entry.d.ts", + "!dist/plugin-sdk/provider-env-vars.d.ts", + "!dist/plugin-sdk/provider-http.d.ts", "!dist/plugin-sdk/provider-http-test-mocks.js", "!dist/plugin-sdk/provider-http-test-mocks.d.ts", + "!dist/plugin-sdk/provider-model-shared.d.ts", + "!dist/plugin-sdk/provider-model-types.d.ts", + "!dist/plugin-sdk/provider-oauth-runtime.d.ts", + "!dist/plugin-sdk/provider-onboard.d.ts", + "!dist/plugin-sdk/provider-selection-runtime.d.ts", + "!dist/plugin-sdk/provider-setup.d.ts", + "!dist/plugin-sdk/provider-stream.d.ts", + "!dist/plugin-sdk/provider-stream-family.d.ts", + "!dist/plugin-sdk/provider-stream-shared.d.ts", "!dist/plugin-sdk/provider-test-contracts.js", "!dist/plugin-sdk/provider-test-contracts.d.ts", + "!dist/plugin-sdk/provider-tools.d.ts", + "!dist/plugin-sdk/provider-transport-runtime.d.ts", + "!dist/plugin-sdk/provider-usage.d.ts", + "!dist/plugin-sdk/provider-web-fetch.d.ts", + "!dist/plugin-sdk/provider-web-fetch-contract.d.ts", + "!dist/plugin-sdk/provider-web-search.d.ts", + "!dist/plugin-sdk/provider-web-search-config-contract.d.ts", + "!dist/plugin-sdk/provider-web-search-contract.d.ts", + "!dist/plugin-sdk/qa-runner-runtime.d.ts", + "!dist/plugin-sdk/realtime-bootstrap-context.d.ts", + "!dist/plugin-sdk/realtime-transcription.d.ts", + "!dist/plugin-sdk/realtime-voice.d.ts", "!dist/plugin-sdk/reply-payload-testing.js", "!dist/plugin-sdk/reply-payload-testing.d.ts", + "!dist/plugin-sdk/reply-reference.d.ts", + "!dist/plugin-sdk/request-url.d.ts", + "!dist/plugin-sdk/response-limit-runtime.d.ts", + "!dist/plugin-sdk/retry-runtime.d.ts", + "!dist/plugin-sdk/runtime-doctor.d.ts", + "!dist/plugin-sdk/runtime-fetch.d.ts", + "!dist/plugin-sdk/sandbox.d.ts", + "!dist/plugin-sdk/secret-file-runtime.d.ts", + "!dist/plugin-sdk/secure-random-runtime.d.ts", + "!dist/plugin-sdk/session-binding-runtime.d.ts", + "!dist/plugin-sdk/session-catalog.d.ts", + "!dist/plugin-sdk/session-key-runtime.d.ts", + "!dist/plugin-sdk/session-transcript-hit.d.ts", + "!dist/plugin-sdk/session-transcript-runtime.d.ts", + "!dist/plugin-sdk/session-visibility.d.ts", + "!dist/plugin-sdk/simple-completion-runtime.d.ts", + "!dist/plugin-sdk/speech.d.ts", + "!dist/plugin-sdk/speech-core.d.ts", + "!dist/plugin-sdk/sqlite-runtime.d.ts", "!dist/plugin-sdk/sqlite-runtime-testing.js", "!dist/plugin-sdk/sqlite-runtime-testing.d.ts", + "!dist/plugin-sdk/ssrf-dispatcher.d.ts", "!dist/plugin-sdk/ssrf-runtime-internal.js", "!dist/plugin-sdk/ssrf-runtime-internal.d.ts", + "!dist/plugin-sdk/string-normalization-runtime.d.ts", + "!dist/plugin-sdk/system-event-runtime.d.ts", + "!dist/plugin-sdk/talk-config-runtime.d.ts", + "!dist/plugin-sdk/target-resolver-runtime.d.ts", "!dist/plugin-sdk/test-env.js", "!dist/plugin-sdk/test-env.d.ts", "!dist/plugin-sdk/test-fixtures.js", @@ -82,8 +217,23 @@ "!dist/plugin-sdk/test-media-understanding.d.ts", "!dist/plugin-sdk/test-node-mocks.js", "!dist/plugin-sdk/test-node-mocks.d.ts", - "!dist/plugin-sdk/test-utils.js", - "!dist/plugin-sdk/test-utils.d.ts", + "!dist/plugin-sdk/text-autolink-runtime.d.ts", + "!dist/plugin-sdk/text-utility-runtime.d.ts", + "!dist/plugin-sdk/thread-bindings-runtime.d.ts", + "!dist/plugin-sdk/thread-bindings-session-runtime.d.ts", + "!dist/plugin-sdk/time-runtime.d.ts", + "!dist/plugin-sdk/tool-payload.d.ts", + "!dist/plugin-sdk/tool-results.d.ts", + "!dist/plugin-sdk/transcripts.d.ts", + "!dist/plugin-sdk/transport-ready-runtime.d.ts", + "!dist/plugin-sdk/tts-runtime.d.ts", + "!dist/plugin-sdk/types.d.ts", + "!dist/plugin-sdk/video-generation.d.ts", + "!dist/plugin-sdk/video-generation-core.d.ts", + "!dist/plugin-sdk/video-generation-runtime.d.ts", + "!dist/plugin-sdk/web-content-extractor.d.ts", + "!dist/plugin-sdk/webhook-targets.d.ts", + "!dist/plugin-sdk/windows-spawn.d.ts", "!dist/plugin-sdk/src/agents/test-helpers/**", "!dist/plugin-sdk/src/test-helpers/**", "!dist/plugin-sdk/src/test-utils/**", @@ -204,34 +354,10 @@ "main": "dist/index.js", "exports": { ".": "./dist/index.js", - "./plugin-sdk": { - "types": "./dist/plugin-sdk/index.d.ts", - "default": "./dist/plugin-sdk/index.js" - }, "./plugin-sdk/core": { "types": "./dist/plugin-sdk/core.d.ts", "default": "./dist/plugin-sdk/core.js" }, - "./plugin-sdk/lmstudio": { - "types": "./dist/plugin-sdk/lmstudio.d.ts", - "default": "./dist/plugin-sdk/lmstudio.js" - }, - "./plugin-sdk/lmstudio-runtime": { - "types": "./dist/plugin-sdk/lmstudio-runtime.d.ts", - "default": "./dist/plugin-sdk/lmstudio-runtime.js" - }, - "./plugin-sdk/provider-setup": { - "types": "./dist/plugin-sdk/provider-setup.d.ts", - "default": "./dist/plugin-sdk/provider-setup.js" - }, - "./plugin-sdk/sandbox": { - "types": "./dist/plugin-sdk/sandbox.d.ts", - "default": "./dist/plugin-sdk/sandbox.js" - }, - "./plugin-sdk/self-hosted-provider-setup": { - "types": "./dist/plugin-sdk/self-hosted-provider-setup.d.ts", - "default": "./dist/plugin-sdk/self-hosted-provider-setup.js" - }, "./plugin-sdk/routing": { "types": "./dist/plugin-sdk/routing.d.ts", "default": "./dist/plugin-sdk/routing.js" @@ -244,34 +370,18 @@ "types": "./dist/plugin-sdk/health.d.ts", "default": "./dist/plugin-sdk/health.js" }, - "./plugin-sdk/runtime-doctor": { - "types": "./dist/plugin-sdk/runtime-doctor.d.ts", - "default": "./dist/plugin-sdk/runtime-doctor.js" - }, "./plugin-sdk/runtime-env": { "types": "./dist/plugin-sdk/runtime-env.d.ts", "default": "./dist/plugin-sdk/runtime-env.js" }, - "./plugin-sdk/runtime-logger": { - "types": "./dist/plugin-sdk/runtime-logger.d.ts", - "default": "./dist/plugin-sdk/runtime-logger.js" - }, "./plugin-sdk/proxy-capture": { "types": "./dist/plugin-sdk/proxy-capture.d.ts", "default": "./dist/plugin-sdk/proxy-capture.js" }, - "./plugin-sdk/runtime-secret-resolution": { - "types": "./dist/plugin-sdk/runtime-secret-resolution.d.ts", - "default": "./dist/plugin-sdk/runtime-secret-resolution.js" - }, "./plugin-sdk/setup": { "types": "./dist/plugin-sdk/setup.d.ts", "default": "./dist/plugin-sdk/setup.js" }, - "./plugin-sdk/setup-adapter-runtime": { - "types": "./dist/plugin-sdk/setup-adapter-runtime.d.ts", - "default": "./dist/plugin-sdk/setup-adapter-runtime.js" - }, "./plugin-sdk/setup-runtime": { "types": "./dist/plugin-sdk/setup-runtime.d.ts", "default": "./dist/plugin-sdk/setup-runtime.js" @@ -304,10 +414,6 @@ "types": "./dist/plugin-sdk/approval-gateway-runtime.d.ts", "default": "./dist/plugin-sdk/approval-gateway-runtime.js" }, - "./plugin-sdk/approval-reference-runtime": { - "types": "./dist/plugin-sdk/approval-reference-runtime.d.ts", - "default": "./dist/plugin-sdk/approval-reference-runtime.js" - }, "./plugin-sdk/approval-handler-adapter-runtime": { "types": "./dist/plugin-sdk/approval-handler-adapter-runtime.d.ts", "default": "./dist/plugin-sdk/approval-handler-adapter-runtime.js" @@ -324,10 +430,6 @@ "types": "./dist/plugin-sdk/approval-native-runtime.d.ts", "default": "./dist/plugin-sdk/approval-native-runtime.js" }, - "./plugin-sdk/approval-reaction-runtime": { - "types": "./dist/plugin-sdk/approval-reaction-runtime.d.ts", - "default": "./dist/plugin-sdk/approval-reaction-runtime.js" - }, "./plugin-sdk/approval-reply-runtime": { "types": "./dist/plugin-sdk/approval-reply-runtime.d.ts", "default": "./dist/plugin-sdk/approval-reply-runtime.js" @@ -348,10 +450,6 @@ "types": "./dist/plugin-sdk/config-contracts.d.ts", "default": "./dist/plugin-sdk/config-contracts.js" }, - "./plugin-sdk/config-types": { - "types": "./dist/plugin-sdk/config-types.d.ts", - "default": "./dist/plugin-sdk/config-types.js" - }, "./plugin-sdk/plugin-config-runtime": { "types": "./dist/plugin-sdk/plugin-config-runtime.d.ts", "default": "./dist/plugin-sdk/plugin-config-runtime.js" @@ -360,38 +458,14 @@ "types": "./dist/plugin-sdk/config-mutation.d.ts", "default": "./dist/plugin-sdk/config-mutation.js" }, - "./plugin-sdk/cron-store-runtime": { - "types": "./dist/plugin-sdk/cron-store-runtime.d.ts", - "default": "./dist/plugin-sdk/cron-store-runtime.js" - }, - "./plugin-sdk/config-schema": { - "types": "./dist/plugin-sdk/config-schema.d.ts", - "default": "./dist/plugin-sdk/config-schema.js" - }, - "./plugin-sdk/json-schema-runtime": { - "types": "./dist/plugin-sdk/json-schema-runtime.d.ts", - "default": "./dist/plugin-sdk/json-schema-runtime.js" - }, - "./plugin-sdk/json-unsafe-integers": { - "types": "./dist/plugin-sdk/json-unsafe-integers.d.ts", - "default": "./dist/plugin-sdk/json-unsafe-integers.js" - }, "./plugin-sdk/reply-runtime": { "types": "./dist/plugin-sdk/reply-runtime.d.ts", "default": "./dist/plugin-sdk/reply-runtime.js" }, - "./plugin-sdk/reply-dedupe": { - "types": "./dist/plugin-sdk/reply-dedupe.d.ts", - "default": "./dist/plugin-sdk/reply-dedupe.js" - }, "./plugin-sdk/reply-dispatch-runtime": { "types": "./dist/plugin-sdk/reply-dispatch-runtime.d.ts", "default": "./dist/plugin-sdk/reply-dispatch-runtime.js" }, - "./plugin-sdk/reply-reference": { - "types": "./dist/plugin-sdk/reply-reference.d.ts", - "default": "./dist/plugin-sdk/reply-reference.js" - }, "./plugin-sdk/reply-chunking": { "types": "./dist/plugin-sdk/reply-chunking.d.ts", "default": "./dist/plugin-sdk/reply-chunking.js" @@ -416,90 +490,14 @@ "types": "./dist/plugin-sdk/channel-reply-pipeline.d.ts", "default": "./dist/plugin-sdk/channel-reply-pipeline.js" }, - "./plugin-sdk/channel-reply-options-runtime": { - "types": "./dist/plugin-sdk/channel-reply-options-runtime.d.ts", - "default": "./dist/plugin-sdk/channel-reply-options-runtime.js" - }, - "./plugin-sdk/channel-runtime": { - "types": "./dist/plugin-sdk/channel-runtime.d.ts", - "default": "./dist/plugin-sdk/channel-runtime.js" - }, "./plugin-sdk/interactive-runtime": { "types": "./dist/plugin-sdk/interactive-runtime.d.ts", "default": "./dist/plugin-sdk/interactive-runtime.js" }, - "./plugin-sdk/outbound-media": { - "types": "./dist/plugin-sdk/outbound-media.d.ts", - "default": "./dist/plugin-sdk/outbound-media.js" - }, - "./plugin-sdk/outbound-send-deps": { - "types": "./dist/plugin-sdk/outbound-send-deps.d.ts", - "default": "./dist/plugin-sdk/outbound-send-deps.js" - }, - "./plugin-sdk/outbound-runtime": { - "types": "./dist/plugin-sdk/outbound-runtime.d.ts", - "default": "./dist/plugin-sdk/outbound-runtime.js" - }, - "./plugin-sdk/pair-loop-guard-runtime": { - "types": "./dist/plugin-sdk/pair-loop-guard-runtime.d.ts", - "default": "./dist/plugin-sdk/pair-loop-guard-runtime.js" - }, - "./plugin-sdk/poll-runtime": { - "types": "./dist/plugin-sdk/poll-runtime.d.ts", - "default": "./dist/plugin-sdk/poll-runtime.js" - }, - "./plugin-sdk/async-lock-runtime": { - "types": "./dist/plugin-sdk/async-lock-runtime.d.ts", - "default": "./dist/plugin-sdk/async-lock-runtime.js" - }, - "./plugin-sdk/channel-activity-runtime": { - "types": "./dist/plugin-sdk/channel-activity-runtime.d.ts", - "default": "./dist/plugin-sdk/channel-activity-runtime.js" - }, - "./plugin-sdk/concurrency-runtime": { - "types": "./dist/plugin-sdk/concurrency-runtime.d.ts", - "default": "./dist/plugin-sdk/concurrency-runtime.js" - }, "./plugin-sdk/dedupe-runtime": { "types": "./dist/plugin-sdk/dedupe-runtime.d.ts", "default": "./dist/plugin-sdk/dedupe-runtime.js" }, - "./plugin-sdk/delivery-queue-runtime": { - "types": "./dist/plugin-sdk/delivery-queue-runtime.d.ts", - "default": "./dist/plugin-sdk/delivery-queue-runtime.js" - }, - "./plugin-sdk/file-access-runtime": { - "types": "./dist/plugin-sdk/file-access-runtime.d.ts", - "default": "./dist/plugin-sdk/file-access-runtime.js" - }, - "./plugin-sdk/heartbeat-runtime": { - "types": "./dist/plugin-sdk/heartbeat-runtime.d.ts", - "default": "./dist/plugin-sdk/heartbeat-runtime.js" - }, - "./plugin-sdk/expect-runtime": { - "types": "./dist/plugin-sdk/expect-runtime.d.ts", - "default": "./dist/plugin-sdk/expect-runtime.js" - }, - "./plugin-sdk/number-runtime": { - "types": "./dist/plugin-sdk/number-runtime.d.ts", - "default": "./dist/plugin-sdk/number-runtime.js" - }, - "./plugin-sdk/secure-random-runtime": { - "types": "./dist/plugin-sdk/secure-random-runtime.d.ts", - "default": "./dist/plugin-sdk/secure-random-runtime.js" - }, - "./plugin-sdk/system-event-runtime": { - "types": "./dist/plugin-sdk/system-event-runtime.d.ts", - "default": "./dist/plugin-sdk/system-event-runtime.js" - }, - "./plugin-sdk/transport-ready-runtime": { - "types": "./dist/plugin-sdk/transport-ready-runtime.d.ts", - "default": "./dist/plugin-sdk/transport-ready-runtime.js" - }, - "./plugin-sdk/exec-approvals-runtime": { - "types": "./dist/plugin-sdk/exec-approvals-runtime.d.ts", - "default": "./dist/plugin-sdk/exec-approvals-runtime.js" - }, "./plugin-sdk/infra-runtime": { "types": "./dist/plugin-sdk/infra-runtime.d.ts", "default": "./dist/plugin-sdk/infra-runtime.js" @@ -516,10 +514,6 @@ "types": "./dist/plugin-sdk/model-session-runtime.d.ts", "default": "./dist/plugin-sdk/model-session-runtime.js" }, - "./plugin-sdk/talk-config-runtime": { - "types": "./dist/plugin-sdk/talk-config-runtime.d.ts", - "default": "./dist/plugin-sdk/talk-config-runtime.js" - }, "./plugin-sdk/ssrf-policy": { "types": "./dist/plugin-sdk/ssrf-policy.d.ts", "default": "./dist/plugin-sdk/ssrf-policy.js" @@ -540,30 +534,10 @@ "types": "./dist/plugin-sdk/media-mime.d.ts", "default": "./dist/plugin-sdk/media-mime.js" }, - "./plugin-sdk/embedding-providers": { - "types": "./dist/plugin-sdk/embedding-providers.d.ts", - "default": "./dist/plugin-sdk/embedding-providers.js" - }, - "./plugin-sdk/media-generation-runtime": { - "types": "./dist/plugin-sdk/media-generation-runtime.d.ts", - "default": "./dist/plugin-sdk/media-generation-runtime.js" - }, - "./plugin-sdk/conversation-binding-runtime": { - "types": "./dist/plugin-sdk/conversation-binding-runtime.d.ts", - "default": "./dist/plugin-sdk/conversation-binding-runtime.js" - }, "./plugin-sdk/conversation-runtime": { "types": "./dist/plugin-sdk/conversation-runtime.d.ts", "default": "./dist/plugin-sdk/conversation-runtime.js" }, - "./plugin-sdk/thread-bindings-runtime": { - "types": "./dist/plugin-sdk/thread-bindings-runtime.d.ts", - "default": "./dist/plugin-sdk/thread-bindings-runtime.js" - }, - "./plugin-sdk/thread-bindings-session-runtime": { - "types": "./dist/plugin-sdk/thread-bindings-session-runtime.d.ts", - "default": "./dist/plugin-sdk/thread-bindings-session-runtime.js" - }, "./plugin-sdk/text-runtime": { "types": "./dist/plugin-sdk/text-runtime.d.ts", "default": "./dist/plugin-sdk/text-runtime.js" @@ -576,30 +550,14 @@ "types": "./dist/plugin-sdk/agent-runtime.d.ts", "default": "./dist/plugin-sdk/agent-runtime.js" }, - "./plugin-sdk/simple-completion-runtime": { - "types": "./dist/plugin-sdk/simple-completion-runtime.d.ts", - "default": "./dist/plugin-sdk/simple-completion-runtime.js" - }, - "./plugin-sdk/speech-core": { - "types": "./dist/plugin-sdk/speech-core.d.ts", - "default": "./dist/plugin-sdk/speech-core.js" - }, "./plugin-sdk/speech-settings": { "types": "./dist/plugin-sdk/speech-settings.d.ts", "default": "./dist/plugin-sdk/speech-settings.js" }, - "./plugin-sdk/tts-runtime": { - "types": "./dist/plugin-sdk/tts-runtime.d.ts", - "default": "./dist/plugin-sdk/tts-runtime.js" - }, "./plugin-sdk/plugin-runtime": { "types": "./dist/plugin-sdk/plugin-runtime.d.ts", "default": "./dist/plugin-sdk/plugin-runtime.js" }, - "./plugin-sdk/skills-runtime": { - "types": "./dist/plugin-sdk/skills-runtime.d.ts", - "default": "./dist/plugin-sdk/skills-runtime.js" - }, "./plugin-sdk/channel-secret-basic-runtime": { "types": "./dist/plugin-sdk/channel-secret-basic-runtime.d.ts", "default": "./dist/plugin-sdk/channel-secret-basic-runtime.js" @@ -608,22 +566,10 @@ "types": "./dist/plugin-sdk/channel-secret-runtime.d.ts", "default": "./dist/plugin-sdk/channel-secret-runtime.js" }, - "./plugin-sdk/channel-secret-tts-runtime": { - "types": "./dist/plugin-sdk/channel-secret-tts-runtime.d.ts", - "default": "./dist/plugin-sdk/channel-secret-tts-runtime.js" - }, "./plugin-sdk/secret-ref-runtime": { "types": "./dist/plugin-sdk/secret-ref-runtime.d.ts", "default": "./dist/plugin-sdk/secret-ref-runtime.js" }, - "./plugin-sdk/secret-provider-integration": { - "types": "./dist/plugin-sdk/secret-provider-integration.d.ts", - "default": "./dist/plugin-sdk/secret-provider-integration.js" - }, - "./plugin-sdk/secret-file-runtime": { - "types": "./dist/plugin-sdk/secret-file-runtime.d.ts", - "default": "./dist/plugin-sdk/secret-file-runtime.js" - }, "./plugin-sdk/security-runtime": { "types": "./dist/plugin-sdk/security-runtime.d.ts", "default": "./dist/plugin-sdk/security-runtime.js" @@ -636,78 +582,18 @@ "types": "./dist/plugin-sdk/gateway-runtime.d.ts", "default": "./dist/plugin-sdk/gateway-runtime.js" }, - "./plugin-sdk/cli-runtime": { - "types": "./dist/plugin-sdk/cli-runtime.d.ts", - "default": "./dist/plugin-sdk/cli-runtime.js" - }, - "./plugin-sdk/cli-backend": { - "types": "./dist/plugin-sdk/cli-backend.d.ts", - "default": "./dist/plugin-sdk/cli-backend.js" - }, - "./plugin-sdk/codex-mcp-projection": { - "types": "./dist/plugin-sdk/codex-mcp-projection.d.ts", - "default": "./dist/plugin-sdk/codex-mcp-projection.js" - }, - "./plugin-sdk/agent-harness-task-runtime": { - "types": "./dist/plugin-sdk/agent-harness-task-runtime.d.ts", - "default": "./dist/plugin-sdk/agent-harness-task-runtime.js" - }, "./plugin-sdk/agent-harness": { "types": "./dist/plugin-sdk/agent-harness.d.ts", "default": "./dist/plugin-sdk/agent-harness.js" }, - "./plugin-sdk/agent-harness-exec-review-runtime": { - "types": "./dist/plugin-sdk/agent-harness-exec-review-runtime.d.ts", - "default": "./dist/plugin-sdk/agent-harness-exec-review-runtime.js" - }, "./plugin-sdk/agent-harness-runtime": { "types": "./dist/plugin-sdk/agent-harness-runtime.d.ts", "default": "./dist/plugin-sdk/agent-harness-runtime.js" }, - "./plugin-sdk/agent-harness-tool-runtime": { - "types": "./dist/plugin-sdk/agent-harness-tool-runtime.d.ts", - "default": "./dist/plugin-sdk/agent-harness-tool-runtime.js" - }, "./plugin-sdk/hook-runtime": { "types": "./dist/plugin-sdk/hook-runtime.d.ts", "default": "./dist/plugin-sdk/hook-runtime.js" }, - "./plugin-sdk/html-entity-runtime": { - "types": "./dist/plugin-sdk/html-entity-runtime.d.ts", - "default": "./dist/plugin-sdk/html-entity-runtime.js" - }, - "./plugin-sdk/host-runtime": { - "types": "./dist/plugin-sdk/host-runtime.d.ts", - "default": "./dist/plugin-sdk/host-runtime.js" - }, - "./plugin-sdk/types": { - "types": "./dist/plugin-sdk/types.d.ts", - "default": "./dist/plugin-sdk/types.js" - }, - "./plugin-sdk/process-runtime": { - "types": "./dist/plugin-sdk/process-runtime.d.ts", - "default": "./dist/plugin-sdk/process-runtime.js" - }, - "./plugin-sdk/windows-spawn": { - "types": "./dist/plugin-sdk/windows-spawn.d.ts", - "default": "./dist/plugin-sdk/windows-spawn.js" - }, - "./plugin-sdk/acp-runtime": { - "types": "./dist/plugin-sdk/acp-runtime.d.ts", - "default": "./dist/plugin-sdk/acp-runtime.js" - }, - "./plugin-sdk/acp-runtime-backend": { - "types": "./dist/plugin-sdk/acp-runtime-backend.d.ts", - "default": "./dist/plugin-sdk/acp-runtime-backend.js" - }, - "./plugin-sdk/acp-binding-runtime": { - "types": "./dist/plugin-sdk/acp-binding-runtime.d.ts", - "default": "./dist/plugin-sdk/acp-binding-runtime.js" - }, - "./plugin-sdk/acp-binding-resolve-runtime": { - "types": "./dist/plugin-sdk/acp-binding-resolve-runtime.d.ts", - "default": "./dist/plugin-sdk/acp-binding-resolve-runtime.js" - }, "./plugin-sdk/lazy-runtime": { "types": "./dist/plugin-sdk/lazy-runtime.d.ts", "default": "./dist/plugin-sdk/lazy-runtime.js" @@ -716,30 +602,10 @@ "types": "./dist/plugin-sdk/temp-path.d.ts", "default": "./dist/plugin-sdk/temp-path.js" }, - "./plugin-sdk/time-runtime": { - "types": "./dist/plugin-sdk/time-runtime.d.ts", - "default": "./dist/plugin-sdk/time-runtime.js" - }, "./plugin-sdk/logging-core": { "types": "./dist/plugin-sdk/logging-core.d.ts", "default": "./dist/plugin-sdk/logging-core.js" }, - "./plugin-sdk/migration": { - "types": "./dist/plugin-sdk/migration.d.ts", - "default": "./dist/plugin-sdk/migration.js" - }, - "./plugin-sdk/migration-runtime": { - "types": "./dist/plugin-sdk/migration-runtime.d.ts", - "default": "./dist/plugin-sdk/migration-runtime.js" - }, - "./plugin-sdk/plugin-state-runtime": { - "types": "./dist/plugin-sdk/plugin-state-runtime.d.ts", - "default": "./dist/plugin-sdk/plugin-state-runtime.js" - }, - "./plugin-sdk/markdown-table-runtime": { - "types": "./dist/plugin-sdk/markdown-table-runtime.d.ts", - "default": "./dist/plugin-sdk/markdown-table-runtime.js" - }, "./plugin-sdk/account-helpers": { "types": "./dist/plugin-sdk/account-helpers.d.ts", "default": "./dist/plugin-sdk/account-helpers.js" @@ -756,18 +622,10 @@ "types": "./dist/plugin-sdk/account-resolution.d.ts", "default": "./dist/plugin-sdk/account-resolution.js" }, - "./plugin-sdk/account-resolution-runtime": { - "types": "./dist/plugin-sdk/account-resolution-runtime.d.ts", - "default": "./dist/plugin-sdk/account-resolution-runtime.js" - }, "./plugin-sdk/agent-config-primitives": { "types": "./dist/plugin-sdk/agent-config-primitives.d.ts", "default": "./dist/plugin-sdk/agent-config-primitives.js" }, - "./plugin-sdk/access-groups": { - "types": "./dist/plugin-sdk/access-groups.d.ts", - "default": "./dist/plugin-sdk/access-groups.js" - }, "./plugin-sdk/allow-from": { "types": "./dist/plugin-sdk/allow-from.d.ts", "default": "./dist/plugin-sdk/allow-from.js" @@ -776,18 +634,10 @@ "types": "./dist/plugin-sdk/allowlist-config-edit.d.ts", "default": "./dist/plugin-sdk/allowlist-config-edit.js" }, - "./plugin-sdk/browser-config": { - "types": "./dist/plugin-sdk/browser-config.d.ts", - "default": "./dist/plugin-sdk/browser-config.js" - }, "./plugin-sdk/boolean-param": { "types": "./dist/plugin-sdk/boolean-param.d.ts", "default": "./dist/plugin-sdk/boolean-param.js" }, - "./plugin-sdk/dangerous-name-runtime": { - "types": "./dist/plugin-sdk/dangerous-name-runtime.d.ts", - "default": "./dist/plugin-sdk/dangerous-name-runtime.js" - }, "./plugin-sdk/command-auth": { "types": "./dist/plugin-sdk/command-auth.d.ts", "default": "./dist/plugin-sdk/command-auth.js" @@ -796,10 +646,6 @@ "types": "./dist/plugin-sdk/command-auth-native.d.ts", "default": "./dist/plugin-sdk/command-auth-native.js" }, - "./plugin-sdk/command-gating": { - "types": "./dist/plugin-sdk/command-gating.d.ts", - "default": "./dist/plugin-sdk/command-gating.js" - }, "./plugin-sdk/command-primitives-runtime": { "types": "./dist/plugin-sdk/command-primitives-runtime.d.ts", "default": "./dist/plugin-sdk/command-primitives-runtime.js" @@ -808,46 +654,18 @@ "types": "./dist/plugin-sdk/command-status.d.ts", "default": "./dist/plugin-sdk/command-status.js" }, - "./plugin-sdk/command-status-runtime": { - "types": "./dist/plugin-sdk/command-status-runtime.d.ts", - "default": "./dist/plugin-sdk/command-status-runtime.js" - }, "./plugin-sdk/command-detection": { "types": "./dist/plugin-sdk/command-detection.d.ts", "default": "./dist/plugin-sdk/command-detection.js" }, - "./plugin-sdk/command-surface": { - "types": "./dist/plugin-sdk/command-surface.d.ts", - "default": "./dist/plugin-sdk/command-surface.js" - }, "./plugin-sdk/collection-runtime": { "types": "./dist/plugin-sdk/collection-runtime.d.ts", "default": "./dist/plugin-sdk/collection-runtime.js" }, - "./plugin-sdk/compat": { - "types": "./dist/plugin-sdk/compat.d.ts", - "default": "./dist/plugin-sdk/compat.js" - }, - "./plugin-sdk/direct-dm": { - "types": "./dist/plugin-sdk/direct-dm.d.ts", - "default": "./dist/plugin-sdk/direct-dm.js" - }, - "./plugin-sdk/direct-dm-access": { - "types": "./dist/plugin-sdk/direct-dm-access.d.ts", - "default": "./dist/plugin-sdk/direct-dm-access.js" - }, - "./plugin-sdk/direct-dm-guard-policy": { - "types": "./dist/plugin-sdk/direct-dm-guard-policy.d.ts", - "default": "./dist/plugin-sdk/direct-dm-guard-policy.js" - }, "./plugin-sdk/discord": { "types": "./dist/plugin-sdk/discord.d.ts", "default": "./dist/plugin-sdk/discord.js" }, - "./plugin-sdk/mattermost": { - "types": "./dist/plugin-sdk/mattermost.d.ts", - "default": "./dist/plugin-sdk/mattermost.js" - }, "./plugin-sdk/matrix": { "types": "./dist/plugin-sdk/matrix.d.ts", "default": "./dist/plugin-sdk/matrix.js" @@ -872,10 +690,6 @@ "types": "./dist/plugin-sdk/channel-config-helpers.d.ts", "default": "./dist/plugin-sdk/channel-config-helpers.js" }, - "./plugin-sdk/channel-config-writes": { - "types": "./dist/plugin-sdk/channel-config-writes.d.ts", - "default": "./dist/plugin-sdk/channel-config-writes.js" - }, "./plugin-sdk/channel-config-primitives": { "types": "./dist/plugin-sdk/channel-config-primitives.d.ts", "default": "./dist/plugin-sdk/channel-config-primitives.js" @@ -884,18 +698,6 @@ "types": "./dist/plugin-sdk/channel-config-schema.d.ts", "default": "./dist/plugin-sdk/channel-config-schema.js" }, - "./plugin-sdk/bundled-channel-config-schema": { - "types": "./dist/plugin-sdk/bundled-channel-config-schema.d.ts", - "default": "./dist/plugin-sdk/bundled-channel-config-schema.js" - }, - "./plugin-sdk/chat-channel-ids": { - "types": "./dist/plugin-sdk/chat-channel-ids.d.ts", - "default": "./dist/plugin-sdk/chat-channel-ids.js" - }, - "./plugin-sdk/channel-config-schema-legacy": { - "types": "./dist/plugin-sdk/channel-config-schema-legacy.d.ts", - "default": "./dist/plugin-sdk/channel-config-schema-legacy.js" - }, "./plugin-sdk/channel-actions": { "types": "./dist/plugin-sdk/channel-actions.d.ts", "default": "./dist/plugin-sdk/channel-actions.js" @@ -916,10 +718,6 @@ "types": "./dist/plugin-sdk/channel-contract.d.ts", "default": "./dist/plugin-sdk/channel-contract.js" }, - "./plugin-sdk/channel-envelope": { - "types": "./dist/plugin-sdk/channel-envelope.d.ts", - "default": "./dist/plugin-sdk/channel-envelope.js" - }, "./plugin-sdk/channel-feedback": { "types": "./dist/plugin-sdk/channel-feedback.d.ts", "default": "./dist/plugin-sdk/channel-feedback.js" @@ -932,22 +730,10 @@ "types": "./dist/plugin-sdk/channel-inbound-debounce.d.ts", "default": "./dist/plugin-sdk/channel-inbound-debounce.js" }, - "./plugin-sdk/channel-inbound-roots": { - "types": "./dist/plugin-sdk/channel-inbound-roots.d.ts", - "default": "./dist/plugin-sdk/channel-inbound-roots.js" - }, "./plugin-sdk/channel-logging": { "types": "./dist/plugin-sdk/channel-logging.d.ts", "default": "./dist/plugin-sdk/channel-logging.js" }, - "./plugin-sdk/channel-location": { - "types": "./dist/plugin-sdk/channel-location.d.ts", - "default": "./dist/plugin-sdk/channel-location.js" - }, - "./plugin-sdk/channel-mention-gating": { - "types": "./dist/plugin-sdk/channel-mention-gating.d.ts", - "default": "./dist/plugin-sdk/channel-mention-gating.js" - }, "./plugin-sdk/channel-lifecycle": { "types": "./dist/plugin-sdk/channel-lifecycle.d.ts", "default": "./dist/plugin-sdk/channel-lifecycle.js" @@ -960,10 +746,6 @@ "types": "./dist/plugin-sdk/channel-message.d.ts", "default": "./dist/plugin-sdk/channel-message.js" }, - "./plugin-sdk/channel-message-runtime": { - "types": "./dist/plugin-sdk/channel-message-runtime.d.ts", - "default": "./dist/plugin-sdk/channel-message-runtime.js" - }, "./plugin-sdk/channel-outbound": { "types": "./dist/plugin-sdk/channel-outbound.d.ts", "default": "./dist/plugin-sdk/channel-outbound.js" @@ -972,10 +754,6 @@ "types": "./dist/plugin-sdk/channel-pairing.d.ts", "default": "./dist/plugin-sdk/channel-pairing.js" }, - "./plugin-sdk/channel-pairing-paths": { - "types": "./dist/plugin-sdk/channel-pairing-paths.d.ts", - "default": "./dist/plugin-sdk/channel-pairing-paths.js" - }, "./plugin-sdk/channel-policy": { "types": "./dist/plugin-sdk/channel-policy.d.ts", "default": "./dist/plugin-sdk/channel-policy.js" @@ -984,166 +762,34 @@ "types": "./dist/plugin-sdk/channel-send-result.d.ts", "default": "./dist/plugin-sdk/channel-send-result.js" }, - "./plugin-sdk/channel-route": { - "types": "./dist/plugin-sdk/channel-route.d.ts", - "default": "./dist/plugin-sdk/channel-route.js" - }, - "./plugin-sdk/channel-targets": { - "types": "./dist/plugin-sdk/channel-targets.d.ts", - "default": "./dist/plugin-sdk/channel-targets.js" - }, - "./plugin-sdk/context-visibility-runtime": { - "types": "./dist/plugin-sdk/context-visibility-runtime.d.ts", - "default": "./dist/plugin-sdk/context-visibility-runtime.js" - }, - "./plugin-sdk/file-lock": { - "types": "./dist/plugin-sdk/file-lock.d.ts", - "default": "./dist/plugin-sdk/file-lock.js" - }, - "./plugin-sdk/fetch-runtime": { - "types": "./dist/plugin-sdk/fetch-runtime.d.ts", - "default": "./dist/plugin-sdk/fetch-runtime.js" - }, - "./plugin-sdk/runtime-fetch": { - "types": "./dist/plugin-sdk/runtime-fetch.d.ts", - "default": "./dist/plugin-sdk/runtime-fetch.js" - }, - "./plugin-sdk/inline-image-data-url-runtime": { - "types": "./dist/plugin-sdk/inline-image-data-url-runtime.d.ts", - "default": "./dist/plugin-sdk/inline-image-data-url-runtime.js" - }, - "./plugin-sdk/node-host": { - "types": "./dist/plugin-sdk/node-host.d.ts", - "default": "./dist/plugin-sdk/node-host.js" - }, - "./plugin-sdk/response-limit-runtime": { - "types": "./dist/plugin-sdk/response-limit-runtime.d.ts", - "default": "./dist/plugin-sdk/response-limit-runtime.js" - }, - "./plugin-sdk/session-binding-runtime": { - "types": "./dist/plugin-sdk/session-binding-runtime.d.ts", - "default": "./dist/plugin-sdk/session-binding-runtime.js" - }, - "./plugin-sdk/session-catalog": { - "types": "./dist/plugin-sdk/session-catalog.d.ts", - "default": "./dist/plugin-sdk/session-catalog.js" - }, "./plugin-sdk/session-discussion": { "types": "./dist/plugin-sdk/session-discussion.d.ts", "default": "./dist/plugin-sdk/session-discussion.js" }, - "./plugin-sdk/session-key-runtime": { - "types": "./dist/plugin-sdk/session-key-runtime.d.ts", - "default": "./dist/plugin-sdk/session-key-runtime.js" - }, "./plugin-sdk/session-store-runtime": { "types": "./dist/plugin-sdk/session-store-runtime.d.ts", "default": "./dist/plugin-sdk/session-store-runtime.js" }, - "./plugin-sdk/session-transcript-runtime": { - "types": "./dist/plugin-sdk/session-transcript-runtime.d.ts", - "default": "./dist/plugin-sdk/session-transcript-runtime.js" - }, - "./plugin-sdk/sqlite-runtime": { - "types": "./dist/plugin-sdk/sqlite-runtime.d.ts", - "default": "./dist/plugin-sdk/sqlite-runtime.js" - }, - "./plugin-sdk/session-transcript-hit": { - "types": "./dist/plugin-sdk/session-transcript-hit.d.ts", - "default": "./dist/plugin-sdk/session-transcript-hit.js" - }, - "./plugin-sdk/session-visibility": { - "types": "./dist/plugin-sdk/session-visibility.d.ts", - "default": "./dist/plugin-sdk/session-visibility.js" - }, - "./plugin-sdk/ssrf-dispatcher": { - "types": "./dist/plugin-sdk/ssrf-dispatcher.d.ts", - "default": "./dist/plugin-sdk/ssrf-dispatcher.js" - }, "./plugin-sdk/string-coerce-runtime": { "types": "./dist/plugin-sdk/string-coerce-runtime.d.ts", "default": "./dist/plugin-sdk/string-coerce-runtime.js" }, - "./plugin-sdk/group-activation": { - "types": "./dist/plugin-sdk/group-activation.d.ts", - "default": "./dist/plugin-sdk/group-activation.js" - }, "./plugin-sdk/group-access": { "types": "./dist/plugin-sdk/group-access.d.ts", "default": "./dist/plugin-sdk/group-access.js" }, - "./plugin-sdk/global-singleton": { - "types": "./dist/plugin-sdk/global-singleton.d.ts", - "default": "./dist/plugin-sdk/global-singleton.js" - }, - "./plugin-sdk/directory-config-runtime": { - "types": "./dist/plugin-sdk/directory-config-runtime.d.ts", - "default": "./dist/plugin-sdk/directory-config-runtime.js" - }, "./plugin-sdk/directory-runtime": { "types": "./dist/plugin-sdk/directory-runtime.d.ts", "default": "./dist/plugin-sdk/directory-runtime.js" }, - "./plugin-sdk/media-generation-runtime-shared": { - "types": "./dist/plugin-sdk/media-generation-runtime-shared.d.ts", - "default": "./dist/plugin-sdk/media-generation-runtime-shared.js" - }, - "./plugin-sdk/image-generation": { - "types": "./dist/plugin-sdk/image-generation.d.ts", - "default": "./dist/plugin-sdk/image-generation.js" - }, - "./plugin-sdk/image-generation-runtime": { - "types": "./dist/plugin-sdk/image-generation-runtime.d.ts", - "default": "./dist/plugin-sdk/image-generation-runtime.js" - }, - "./plugin-sdk/image-generation-core": { - "types": "./dist/plugin-sdk/image-generation-core.d.ts", - "default": "./dist/plugin-sdk/image-generation-core.js" - }, - "./plugin-sdk/music-generation": { - "types": "./dist/plugin-sdk/music-generation.d.ts", - "default": "./dist/plugin-sdk/music-generation.js" - }, - "./plugin-sdk/music-generation-core": { - "types": "./dist/plugin-sdk/music-generation-core.d.ts", - "default": "./dist/plugin-sdk/music-generation-core.js" - }, - "./plugin-sdk/video-generation": { - "types": "./dist/plugin-sdk/video-generation.d.ts", - "default": "./dist/plugin-sdk/video-generation.js" - }, - "./plugin-sdk/video-generation-runtime": { - "types": "./dist/plugin-sdk/video-generation-runtime.d.ts", - "default": "./dist/plugin-sdk/video-generation-runtime.js" - }, - "./plugin-sdk/video-generation-core": { - "types": "./dist/plugin-sdk/video-generation-core.d.ts", - "default": "./dist/plugin-sdk/video-generation-core.js" - }, "./plugin-sdk/reply-history": { "types": "./dist/plugin-sdk/reply-history.d.ts", "default": "./dist/plugin-sdk/reply-history.js" }, - "./plugin-sdk/realtime-transcription": { - "types": "./dist/plugin-sdk/realtime-transcription.d.ts", - "default": "./dist/plugin-sdk/realtime-transcription.js" - }, - "./plugin-sdk/realtime-bootstrap-context": { - "types": "./dist/plugin-sdk/realtime-bootstrap-context.d.ts", - "default": "./dist/plugin-sdk/realtime-bootstrap-context.js" - }, - "./plugin-sdk/realtime-voice": { - "types": "./dist/plugin-sdk/realtime-voice.d.ts", - "default": "./dist/plugin-sdk/realtime-voice.js" - }, "./plugin-sdk/meeting-runtime": { "types": "./dist/plugin-sdk/meeting-runtime.d.ts", "default": "./dist/plugin-sdk/meeting-runtime.js" }, - "./plugin-sdk/transcripts": { - "types": "./dist/plugin-sdk/transcripts.d.ts", - "default": "./dist/plugin-sdk/transcripts.js" - }, "./plugin-sdk/media-understanding": { "types": "./dist/plugin-sdk/media-understanding.d.ts", "default": "./dist/plugin-sdk/media-understanding.js" @@ -1156,10 +802,6 @@ "types": "./dist/plugin-sdk/messaging-targets.d.ts", "default": "./dist/plugin-sdk/messaging-targets.js" }, - "./plugin-sdk/request-url": { - "types": "./dist/plugin-sdk/request-url.d.ts", - "default": "./dist/plugin-sdk/request-url.js" - }, "./plugin-sdk/runtime-store": { "types": "./dist/plugin-sdk/runtime-store.d.ts", "default": "./dist/plugin-sdk/runtime-store.js" @@ -1176,102 +818,14 @@ "types": "./dist/plugin-sdk/ingress-effect-once.d.ts", "default": "./dist/plugin-sdk/ingress-effect-once.js" }, - "./plugin-sdk/keyed-async-queue": { - "types": "./dist/plugin-sdk/keyed-async-queue.d.ts", - "default": "./dist/plugin-sdk/keyed-async-queue.js" - }, - "./plugin-sdk/qa-runner-runtime": { - "types": "./dist/plugin-sdk/qa-runner-runtime.d.ts", - "default": "./dist/plugin-sdk/qa-runner-runtime.js" - }, - "./plugin-sdk/memory-core": { - "types": "./dist/plugin-sdk/memory-core.d.ts", - "default": "./dist/plugin-sdk/memory-core.js" - }, - "./plugin-sdk/memory-core-engine-runtime": { - "types": "./dist/plugin-sdk/memory-core-engine-runtime.d.ts", - "default": "./dist/plugin-sdk/memory-core-engine-runtime.js" - }, - "./plugin-sdk/memory-core-host-embedding-registry": { - "types": "./dist/plugin-sdk/memory-core-host-embedding-registry.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-embedding-registry.js" - }, - "./plugin-sdk/memory-core-host-engine-embeddings": { - "types": "./dist/plugin-sdk/memory-core-host-engine-embeddings.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-engine-embeddings.js" - }, "./plugin-sdk/memory-core-host-engine-foundation": { "types": "./dist/plugin-sdk/memory-core-host-engine-foundation.d.ts", "default": "./dist/plugin-sdk/memory-core-host-engine-foundation.js" }, - "./plugin-sdk/memory-core-host-engine-qmd": { - "types": "./dist/plugin-sdk/memory-core-host-engine-qmd.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-engine-qmd.js" - }, - "./plugin-sdk/memory-core-host-engine-storage": { - "types": "./dist/plugin-sdk/memory-core-host-engine-storage.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-engine-storage.js" - }, - "./plugin-sdk/memory-core-host-multimodal": { - "types": "./dist/plugin-sdk/memory-core-host-multimodal.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-multimodal.js" - }, - "./plugin-sdk/memory-core-host-query": { - "types": "./dist/plugin-sdk/memory-core-host-query.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-query.js" - }, - "./plugin-sdk/memory-core-host-secret": { - "types": "./dist/plugin-sdk/memory-core-host-secret.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-secret.js" - }, - "./plugin-sdk/memory-core-host-events": { - "types": "./dist/plugin-sdk/memory-core-host-events.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-events.js" - }, - "./plugin-sdk/memory-core-host-status": { - "types": "./dist/plugin-sdk/memory-core-host-status.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-status.js" - }, - "./plugin-sdk/memory-core-host-runtime-cli": { - "types": "./dist/plugin-sdk/memory-core-host-runtime-cli.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-runtime-cli.js" - }, - "./plugin-sdk/memory-core-host-runtime-core": { - "types": "./dist/plugin-sdk/memory-core-host-runtime-core.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-runtime-core.js" - }, - "./plugin-sdk/memory-core-host-runtime-files": { - "types": "./dist/plugin-sdk/memory-core-host-runtime-files.d.ts", - "default": "./dist/plugin-sdk/memory-core-host-runtime-files.js" - }, "./plugin-sdk/memory-host-core": { "types": "./dist/plugin-sdk/memory-host-core.d.ts", "default": "./dist/plugin-sdk/memory-host-core.js" }, - "./plugin-sdk/memory-host-events": { - "types": "./dist/plugin-sdk/memory-host-events.d.ts", - "default": "./dist/plugin-sdk/memory-host-events.js" - }, - "./plugin-sdk/memory-host-files": { - "types": "./dist/plugin-sdk/memory-host-files.d.ts", - "default": "./dist/plugin-sdk/memory-host-files.js" - }, - "./plugin-sdk/memory-host-markdown": { - "types": "./dist/plugin-sdk/memory-host-markdown.d.ts", - "default": "./dist/plugin-sdk/memory-host-markdown.js" - }, - "./plugin-sdk/memory-host-search": { - "types": "./dist/plugin-sdk/memory-host-search.d.ts", - "default": "./dist/plugin-sdk/memory-host-search.js" - }, - "./plugin-sdk/memory-host-status": { - "types": "./dist/plugin-sdk/memory-host-status.d.ts", - "default": "./dist/plugin-sdk/memory-host-status.js" - }, - "./plugin-sdk/message-tool-delivery-hints": { - "types": "./dist/plugin-sdk/message-tool-delivery-hints.d.ts", - "default": "./dist/plugin-sdk/message-tool-delivery-hints.js" - }, "./plugin-sdk/models-provider-runtime": { "types": "./dist/plugin-sdk/models-provider-runtime.d.ts", "default": "./dist/plugin-sdk/models-provider-runtime.js" @@ -1292,130 +846,14 @@ "types": "./dist/plugin-sdk/provider-auth.d.ts", "default": "./dist/plugin-sdk/provider-auth.js" }, - "./plugin-sdk/provider-oauth-runtime": { - "types": "./dist/plugin-sdk/provider-oauth-runtime.d.ts", - "default": "./dist/plugin-sdk/provider-oauth-runtime.js" - }, - "./plugin-sdk/provider-auth-runtime": { - "types": "./dist/plugin-sdk/provider-auth-runtime.d.ts", - "default": "./dist/plugin-sdk/provider-auth-runtime.js" - }, - "./plugin-sdk/provider-auth-login-flow-runtime": { - "types": "./dist/plugin-sdk/provider-auth-login-flow-runtime.d.ts", - "default": "./dist/plugin-sdk/provider-auth-login-flow-runtime.js" - }, - "./plugin-sdk/provider-auth-api-key": { - "types": "./dist/plugin-sdk/provider-auth-api-key.d.ts", - "default": "./dist/plugin-sdk/provider-auth-api-key.js" - }, - "./plugin-sdk/provider-auth-result": { - "types": "./dist/plugin-sdk/provider-auth-result.d.ts", - "default": "./dist/plugin-sdk/provider-auth-result.js" - }, - "./plugin-sdk/provider-auth-login": { - "types": "./dist/plugin-sdk/provider-auth-login.d.ts", - "default": "./dist/plugin-sdk/provider-auth-login.js" - }, - "./plugin-sdk/provider-selection-runtime": { - "types": "./dist/plugin-sdk/provider-selection-runtime.d.ts", - "default": "./dist/plugin-sdk/provider-selection-runtime.js" - }, "./plugin-sdk/plugin-entry": { "types": "./dist/plugin-sdk/plugin-entry.d.ts", "default": "./dist/plugin-sdk/plugin-entry.js" }, - "./plugin-sdk/provider-catalog-live-runtime": { - "types": "./dist/plugin-sdk/provider-catalog-live-runtime.d.ts", - "default": "./dist/plugin-sdk/provider-catalog-live-runtime.js" - }, "./plugin-sdk/provider-catalog-runtime": { "types": "./dist/plugin-sdk/provider-catalog-runtime.d.ts", "default": "./dist/plugin-sdk/provider-catalog-runtime.js" }, - "./plugin-sdk/provider-catalog-shared": { - "types": "./dist/plugin-sdk/provider-catalog-shared.d.ts", - "default": "./dist/plugin-sdk/provider-catalog-shared.js" - }, - "./plugin-sdk/provider-entry": { - "types": "./dist/plugin-sdk/provider-entry.d.ts", - "default": "./dist/plugin-sdk/provider-entry.js" - }, - "./plugin-sdk/provider-env-vars": { - "types": "./dist/plugin-sdk/provider-env-vars.d.ts", - "default": "./dist/plugin-sdk/provider-env-vars.js" - }, - "./plugin-sdk/provider-http": { - "types": "./dist/plugin-sdk/provider-http.d.ts", - "default": "./dist/plugin-sdk/provider-http.js" - }, - "./plugin-sdk/provider-model-types": { - "types": "./dist/plugin-sdk/provider-model-types.d.ts", - "default": "./dist/plugin-sdk/provider-model-types.js" - }, - "./plugin-sdk/provider-model-shared": { - "types": "./dist/plugin-sdk/provider-model-shared.d.ts", - "default": "./dist/plugin-sdk/provider-model-shared.js" - }, - "./plugin-sdk/provider-onboard": { - "types": "./dist/plugin-sdk/provider-onboard.d.ts", - "default": "./dist/plugin-sdk/provider-onboard.js" - }, - "./plugin-sdk/provider-stream-family": { - "types": "./dist/plugin-sdk/provider-stream-family.d.ts", - "default": "./dist/plugin-sdk/provider-stream-family.js" - }, - "./plugin-sdk/provider-stream-shared": { - "types": "./dist/plugin-sdk/provider-stream-shared.d.ts", - "default": "./dist/plugin-sdk/provider-stream-shared.js" - }, - "./plugin-sdk/provider-transport-runtime": { - "types": "./dist/plugin-sdk/provider-transport-runtime.d.ts", - "default": "./dist/plugin-sdk/provider-transport-runtime.js" - }, - "./plugin-sdk/provider-stream": { - "types": "./dist/plugin-sdk/provider-stream.d.ts", - "default": "./dist/plugin-sdk/provider-stream.js" - }, - "./plugin-sdk/provider-tools": { - "types": "./dist/plugin-sdk/provider-tools.d.ts", - "default": "./dist/plugin-sdk/provider-tools.js" - }, - "./plugin-sdk/provider-usage": { - "types": "./dist/plugin-sdk/provider-usage.d.ts", - "default": "./dist/plugin-sdk/provider-usage.js" - }, - "./plugin-sdk/document-extractor": { - "types": "./dist/plugin-sdk/document-extractor.d.ts", - "default": "./dist/plugin-sdk/document-extractor.js" - }, - "./plugin-sdk/web-content-extractor": { - "types": "./dist/plugin-sdk/web-content-extractor.d.ts", - "default": "./dist/plugin-sdk/web-content-extractor.js" - }, - "./plugin-sdk/provider-web-fetch-contract": { - "types": "./dist/plugin-sdk/provider-web-fetch-contract.d.ts", - "default": "./dist/plugin-sdk/provider-web-fetch-contract.js" - }, - "./plugin-sdk/provider-web-fetch": { - "types": "./dist/plugin-sdk/provider-web-fetch.d.ts", - "default": "./dist/plugin-sdk/provider-web-fetch.js" - }, - "./plugin-sdk/provider-web-search-config-contract": { - "types": "./dist/plugin-sdk/provider-web-search-config-contract.d.ts", - "default": "./dist/plugin-sdk/provider-web-search-config-contract.js" - }, - "./plugin-sdk/provider-web-search-contract": { - "types": "./dist/plugin-sdk/provider-web-search-contract.d.ts", - "default": "./dist/plugin-sdk/provider-web-search-contract.js" - }, - "./plugin-sdk/provider-web-search": { - "types": "./dist/plugin-sdk/provider-web-search.d.ts", - "default": "./dist/plugin-sdk/provider-web-search.js" - }, - "./plugin-sdk/retry-runtime": { - "types": "./dist/plugin-sdk/retry-runtime.d.ts", - "default": "./dist/plugin-sdk/retry-runtime.js" - }, "./plugin-sdk/run-command": { "types": "./dist/plugin-sdk/run-command.d.ts", "default": "./dist/plugin-sdk/run-command.js" @@ -1424,10 +862,6 @@ "types": "./dist/plugin-sdk/param-readers.d.ts", "default": "./dist/plugin-sdk/param-readers.js" }, - "./plugin-sdk/provider-zai-endpoint": { - "types": "./dist/plugin-sdk/provider-zai-endpoint.d.ts", - "default": "./dist/plugin-sdk/provider-zai-endpoint.js" - }, "./plugin-sdk/secret-input": { "types": "./dist/plugin-sdk/secret-input.d.ts", "default": "./dist/plugin-sdk/secret-input.js" @@ -1444,38 +878,14 @@ "types": "./dist/plugin-sdk/status-helpers.d.ts", "default": "./dist/plugin-sdk/status-helpers.js" }, - "./plugin-sdk/speech": { - "types": "./dist/plugin-sdk/speech.d.ts", - "default": "./dist/plugin-sdk/speech.js" - }, - "./plugin-sdk/string-normalization-runtime": { - "types": "./dist/plugin-sdk/string-normalization-runtime.d.ts", - "default": "./dist/plugin-sdk/string-normalization-runtime.js" - }, "./plugin-sdk/state-paths": { "types": "./dist/plugin-sdk/state-paths.d.ts", "default": "./dist/plugin-sdk/state-paths.js" }, - "./plugin-sdk/target-resolver-runtime": { - "types": "./dist/plugin-sdk/target-resolver-runtime.d.ts", - "default": "./dist/plugin-sdk/target-resolver-runtime.js" - }, "./plugin-sdk/telegram-account": { "types": "./dist/plugin-sdk/telegram-account.d.ts", "default": "./dist/plugin-sdk/telegram-account.js" }, - "./plugin-sdk/telegram-command-config": { - "types": "./dist/plugin-sdk/telegram-command-config.d.ts", - "default": "./dist/plugin-sdk/telegram-command-config.js" - }, - "./plugin-sdk/text-autolink-runtime": { - "types": "./dist/plugin-sdk/text-autolink-runtime.d.ts", - "default": "./dist/plugin-sdk/text-autolink-runtime.js" - }, - "./plugin-sdk/text-utility-runtime": { - "types": "./dist/plugin-sdk/text-utility-runtime.d.ts", - "default": "./dist/plugin-sdk/text-utility-runtime.js" - }, "./plugin-sdk/widget-html": { "types": "./dist/plugin-sdk/widget-html.d.ts", "default": "./dist/plugin-sdk/widget-html.js" @@ -1484,14 +894,6 @@ "types": "./dist/plugin-sdk/tool-plugin.d.ts", "default": "./dist/plugin-sdk/tool-plugin.js" }, - "./plugin-sdk/tool-payload": { - "types": "./dist/plugin-sdk/tool-payload.d.ts", - "default": "./dist/plugin-sdk/tool-payload.js" - }, - "./plugin-sdk/tool-results": { - "types": "./dist/plugin-sdk/tool-results.d.ts", - "default": "./dist/plugin-sdk/tool-results.js" - }, "./plugin-sdk/tool-send": { "types": "./dist/plugin-sdk/tool-send.d.ts", "default": "./dist/plugin-sdk/tool-send.js" @@ -1500,43 +902,18 @@ "types": "./dist/plugin-sdk/webhook-ingress.d.ts", "default": "./dist/plugin-sdk/webhook-ingress.js" }, - "./plugin-sdk/webhook-targets": { - "types": "./dist/plugin-sdk/webhook-targets.d.ts", - "default": "./dist/plugin-sdk/webhook-targets.js" - }, "./plugin-sdk/webhook-request-guards": { "types": "./dist/plugin-sdk/webhook-request-guards.d.ts", "default": "./dist/plugin-sdk/webhook-request-guards.js" }, - "./plugin-sdk/webhook-path": { - "types": "./dist/plugin-sdk/webhook-path.d.ts", - "default": "./dist/plugin-sdk/webhook-path.js" - }, "./plugin-sdk/web-media": { "types": "./dist/plugin-sdk/web-media.d.ts", "default": "./dist/plugin-sdk/web-media.js" }, - "./plugin-sdk/zalouser": { - "types": "./dist/plugin-sdk/zalouser.d.ts", - "default": "./dist/plugin-sdk/zalouser.js" - }, "./plugin-sdk/zod": { "types": "./dist/plugin-sdk/zod.d.ts", "default": "./dist/plugin-sdk/zod.js" }, - "./plugin-sdk/agent-core": { - "types": "./dist/plugin-sdk/agent-core.d.ts", - "default": "./dist/plugin-sdk/agent-core.js" - }, - "./plugin-sdk/agent-sessions": { - "types": "./dist/plugin-sdk/agent-sessions.d.ts", - "default": "./dist/plugin-sdk/agent-sessions.js" - }, - "./plugin-sdk/llm": { - "types": "./dist/plugin-sdk/llm.d.ts", - "default": "./dist/plugin-sdk/llm.js" - }, - "./extension-api": "./dist/extensionAPI.js", "./cli-entry": "./openclaw.mjs" }, "scripts": { diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index c0fe05940aaf..bf67b680afef 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -84,10 +84,6 @@ "types": "./dist/src/plugin-sdk/config-runtime.d.ts", "default": "./src/config-runtime.ts" }, - "./config-types": { - "types": "./dist/src/plugin-sdk/config-types.d.ts", - "default": "./src/config-types.ts" - }, "./plugin-config-runtime": { "types": "./dist/src/plugin-sdk/plugin-config-runtime.d.ts", "default": "./src/plugin-config-runtime.ts" @@ -108,10 +104,6 @@ "types": "./dist/src/plugin-sdk/channel-streaming.d.ts", "default": "./src/channel-streaming.ts" }, - "./channel-runtime": { - "types": "./dist/src/plugin-sdk/channel-runtime.d.ts", - "default": "./src/channel-runtime.ts" - }, "./cli-runtime": { "types": "./dist/src/plugin-sdk/cli-runtime.d.ts", "default": "./src/cli-runtime.ts" @@ -136,10 +128,6 @@ "types": "./dist/src/plugin-sdk/provider-auth.d.ts", "default": "./src/provider-auth.ts" }, - "./compat": { - "types": "./dist/src/plugin-sdk/compat.d.ts", - "default": "./src/compat.ts" - }, "./provider-auth-runtime": { "types": "./dist/src/plugin-sdk/provider-auth-runtime.d.ts", "default": "./src/provider-auth-runtime.ts" diff --git a/scripts/build-all.mjs b/scripts/build-all.mjs index c3050defbcb6..1b5bc855f50a 100644 --- a/scripts/build-all.mjs +++ b/scripts/build-all.mjs @@ -112,7 +112,6 @@ const PLUGIN_SDK_SELF_BUILT_ENTRY_DTS_CACHE_INPUTS = [ }, ]; const PLUGIN_SDK_ENTRY_DTS_CACHE_OUTPUTS = [ - "dist/plugin-sdk/webhook-path.js", "dist/plugin-sdk/.boundary-entry-shims.stamp", ...pluginSdkEntrypoints.map((entry) => `packages/plugin-sdk/dist/src/plugin-sdk/${entry}.d.ts`), ]; diff --git a/scripts/check-deprecated-api-usage.mjs b/scripts/check-deprecated-api-usage.mjs index 3a92e074b637..60e55388cd06 100644 --- a/scripts/check-deprecated-api-usage.mjs +++ b/scripts/check-deprecated-api-usage.mjs @@ -211,10 +211,8 @@ const rules = [ "src/channels/message/inbound-reply-dispatch.ts", "src/infra/outbound/deliver-runtime.ts", "src/infra/outbound/deliver.ts", - "src/plugin-sdk/channel-message-runtime.ts", "src/plugin-sdk/channel-message.ts", "src/plugin-sdk/inbound-reply-dispatch.ts", - "src/plugin-sdk/outbound-runtime.ts", ], message: "use sendDurableMessageBatch or deliverInboundReplyWithMessageSendContext", }, diff --git a/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts b/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts index 554074fd96ae..2a1d7ddeec1f 100644 --- a/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts +++ b/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts @@ -4,15 +4,7 @@ import path from "node:path"; import { discoverOpenClawPlugins } from "../src/plugins/discovery.js"; import { collectFilesSync, isCodeFile, relativeToCwd } from "./check-file-utils.js"; -// Match exact monolithic-root specifier in any code path: -// imports/exports, require/dynamic import, and test mocks (vi.mock/jest.mock). -const ROOT_IMPORT_PATTERN = /["']openclaw\/plugin-sdk["']/; -const LEGACY_COMPAT_IMPORT_PATTERN = /["']openclaw\/plugin-sdk\/compat["']/; const LEGACY_BROAD_SUBPATH_PATTERNS = [ - { - pattern: /["']openclaw\/plugin-sdk\/channel-runtime["']/, - label: "openclaw/plugin-sdk/channel-runtime", - }, { pattern: /["']openclaw\/plugin-sdk\/config-runtime["']/, label: "openclaw/plugin-sdk/config-runtime", @@ -23,14 +15,6 @@ const LEGACY_BROAD_SUBPATH_PATTERNS = [ }, ] as const; -function hasMonolithicRootImport(content: string): boolean { - return ROOT_IMPORT_PATTERN.test(content); -} - -function hasLegacyCompatImport(content: string): boolean { - return LEGACY_COMPAT_IMPORT_PATTERN.test(content); -} - function findLegacyBroadSubpathImports(content: string): string[] { return LEGACY_BROAD_SUBPATH_PATTERNS.filter(({ pattern }) => pattern.test(content)).map( ({ label }) => label, @@ -90,8 +74,6 @@ function main() { filesToCheck.add(extensionFile); } - const monolithicOffenders: string[] = []; - const legacyCompatOffenders: string[] = []; const legacyBroadSubpathOffenders = new Map(); for (const entryFile of filesToCheck) { let content; @@ -100,37 +82,13 @@ function main() { } catch { continue; } - if (hasMonolithicRootImport(content)) { - monolithicOffenders.push(entryFile); - } - if (hasLegacyCompatImport(content)) { - legacyCompatOffenders.push(entryFile); - } const legacyBroadSubpaths = findLegacyBroadSubpathImports(content); if (legacyBroadSubpaths.length > 0) { legacyBroadSubpathOffenders.set(entryFile, legacyBroadSubpaths); } } - if ( - monolithicOffenders.length > 0 || - legacyCompatOffenders.length > 0 || - legacyBroadSubpathOffenders.size > 0 - ) { - if (monolithicOffenders.length > 0) { - console.error("Bundled plugin source files must not import monolithic openclaw/plugin-sdk."); - for (const file of monolithicOffenders.toSorted()) { - console.error(`- ${relativeToCwd(file)}`); - } - } - if (legacyCompatOffenders.length > 0) { - console.error( - "Bundled plugin source files must not import legacy openclaw/plugin-sdk/compat.", - ); - for (const file of legacyCompatOffenders.toSorted()) { - console.error(`- ${relativeToCwd(file)}`); - } - } + if (legacyBroadSubpathOffenders.size > 0) { if (legacyBroadSubpathOffenders.size > 0) { console.error( "Bundled plugin source files must not import deprecated broad plugin-sdk subpaths.", @@ -141,20 +99,12 @@ function main() { console.error(`- ${relativeToCwd(file)} (${labels.join(", ")})`); } } - if ( - monolithicOffenders.length > 0 || - legacyCompatOffenders.length > 0 || - legacyBroadSubpathOffenders.size > 0 - ) { - console.error( - "Use focused openclaw/plugin-sdk/ subpaths for bundled plugins; root, compat, and broad runtime barrels are legacy surfaces only.", - ); - } + console.error("Use focused openclaw/plugin-sdk/ subpaths for bundled plugins."); process.exit(1); } console.log( - `OK: bundled plugin source files use scoped plugin-sdk subpaths (${filesToCheck.size} checked).`, + `OK: bundled plugin source files avoid deprecated broad plugin-sdk subpaths (${filesToCheck.size} checked).`, ); } diff --git a/scripts/check-plugin-sdk-exports.mjs b/scripts/check-plugin-sdk-exports.mjs index 31cda1dd1c17..5b3ed26a8797 100755 --- a/scripts/check-plugin-sdk-exports.mjs +++ b/scripts/check-plugin-sdk-exports.mjs @@ -1,11 +1,10 @@ #!/usr/bin/env node /** - * Verifies that the root plugin-sdk runtime surface is present in the compiled - * dist output. + * Verifies that public plugin-sdk subpaths are present in the compiled dist output. * - * Run after `pnpm build` to catch missing root exports or leaked repo-only type - * aliases before release. + * Run after `pnpm build` to catch missing exports or leaked repo-only type aliases + * before release. */ import { readFileSync, existsSync, statSync } from "node:fs"; @@ -21,34 +20,6 @@ import { import { publicPluginSdkEntrypoints, publicPluginSdkSubpaths } from "./lib/plugin-sdk-entries.mjs"; const scriptDir = dirname(fileURLToPath(import.meta.url)); -const distFile = resolve(scriptDir, "..", "dist", "plugin-sdk", "index.js"); -if (!existsSync(distFile)) { - console.error("ERROR: dist/plugin-sdk/index.js not found. Run `pnpm build` first."); - process.exit(1); -} - -const content = readFileSync(distFile, "utf-8"); - -// Extract the final export statement from the compiled output. -// tsdown/rolldown emits a single `export { ... }` at the end of the file. -const exportMatch = content.match(/export\s*\{([^}]+)\}\s*;?\s*$/); -if (!exportMatch) { - console.error("ERROR: Could not find export statement in dist/plugin-sdk/index.js"); - process.exit(1); -} - -const exportedNames = exportMatch[1] - .split(",") - .map((s) => { - // Handle `foo as bar` aliases — the exported name is the `bar` part - const parts = s.trim().split(/\s+as\s+/); - return (parts[parts.length - 1] || "").trim(); - }) - .filter(Boolean); - -const exportSet = new Set(exportedNames); - -const requiredRuntimeShimEntries = ["compat.js", "root-alias.cjs"]; const forbiddenPublicDeclarationSpecifiers = ["@openclaw/llm-core"]; const FORBIDDEN_PUBLIC_PROTOCOL_REGISTRY_RE = /\bdeclare\s+const\s+ProtocolSchemas(?:\$\d+)?\b/u; const RELATIVE_DECLARATION_SPECIFIER_RE = /\b(?:from|import)\s*(?:\(\s*)?["']([^"']+)["']/gu; @@ -63,22 +34,7 @@ const requiredSubpathExports = { ], }; -// The root plugin-sdk entry intentionally stays tiny. Keep this list aligned -// with src/plugin-sdk/index.ts runtime exports. -const requiredExports = [ - "emptyPluginConfigSchema", - "onDiagnosticEvent", - "registerContextEngine", - "delegateCompactionToRuntime", -]; - let missing = 0; -for (const name of requiredExports) { - if (!exportSet.has(name)) { - console.error(`MISSING EXPORT: ${name}`); - missing += 1; - } -} for (const entry of publicPluginSdkSubpaths) { const jsPath = resolve(scriptDir, "..", "dist", "plugin-sdk", `${entry}.js`); @@ -93,14 +49,6 @@ for (const entry of publicPluginSdkSubpaths) { } } -for (const entry of requiredRuntimeShimEntries) { - const shimPath = resolve(scriptDir, "..", "dist", "plugin-sdk", entry); - if (!existsSync(shimPath)) { - console.error(`MISSING RUNTIME SHIM: dist/plugin-sdk/${entry}`); - missing += 1; - } -} - for (const [entry, names] of Object.entries(requiredSubpathExports)) { const jsPath = resolve(scriptDir, "..", "dist", "plugin-sdk", `${entry}.js`); if (!existsSync(jsPath)) { @@ -207,10 +155,8 @@ if (missing > 0) { `\nERROR: ${missing} required plugin-sdk artifact(s) missing (named exports or subpath files).`, ); console.error("This will break published plugin-sdk artifacts."); - console.error( - "Check src/plugin-sdk/index.ts, generated d.ts rewrites, subpath entries, and rebuild.", - ); + console.error("Check generated d.ts rewrites, subpath entries, and rebuild."); process.exit(1); } -console.log(`OK: All ${requiredExports.length} required plugin-sdk exports verified.`); +console.log(`OK: All ${publicPluginSdkSubpaths.length} public plugin-sdk subpaths verified.`); diff --git a/scripts/copy-plugin-sdk-root-alias.mjs b/scripts/copy-plugin-sdk-root-alias.mjs deleted file mode 100644 index 3312dd26380a..000000000000 --- a/scripts/copy-plugin-sdk-root-alias.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Copies the CommonJS plugin SDK root alias into dist output. -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { pathToFileURL } from "node:url"; -import { writeTextFileIfChanged } from "./runtime-postbuild-shared.mjs"; - -/** - * Copies the plugin SDK root alias source into the configured output path. - */ -export function copyPluginSdkRootAlias(params = {}) { - const cwd = params.cwd ?? process.cwd(); - const source = resolve(cwd, "src/plugin-sdk/root-alias.cjs"); - const target = resolve(cwd, "dist/plugin-sdk/root-alias.cjs"); - - writeTextFileIfChanged(target, readFileSync(source, "utf8")); -} - -if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { - copyPluginSdkRootAlias(); -} diff --git a/scripts/e2e/Dockerfile b/scripts/e2e/Dockerfile index a132f8cb7b8f..ae5d4951a874 100644 --- a/scripts/e2e/Dockerfile +++ b/scripts/e2e/Dockerfile @@ -99,7 +99,7 @@ COPY --from=functional-deps --chown=appuser:appuser /tmp/openclaw-deps/node_modu # cycle through the link. RUN tar -xzf /tmp/openclaw-current.tgz -C /app --strip-components=1 \ && chmod +x /app/openclaw.mjs \ - && OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION=1 node /app/scripts/postinstall-bundled-plugins.mjs \ + && node /app/scripts/postinstall-bundled-plugins.mjs \ && ln -sfn /app /app/node_modules/openclaw \ && mkdir -p "$HOME/.local/bin" \ && ln -sf /app/openclaw.mjs "$HOME/.local/bin/openclaw" \ diff --git a/scripts/e2e/lib/clawhub-fixture-server.cjs b/scripts/e2e/lib/clawhub-fixture-server.cjs index bb2d08791660..1233b78abee0 100644 --- a/scripts/e2e/lib/clawhub-fixture-server.cjs +++ b/scripts/e2e/lib/clawhub-fixture-server.cjs @@ -297,7 +297,7 @@ export default definePluginEntry({ name: "OpenClaw Kitchen Sink", description: "Docker E2E kitchen-sink plugin fixture", register(api) { - api.on("before_agent_start", async (event, context) => ({ + api.on("before_prompt_build", async (event, context) => ({ kitchenSink: true, observedEventKeys: Object.keys(event || {}), observedContextKeys: Object.keys(context || {}), diff --git a/scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs b/scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs index d8c739a829b6..baf19012b39f 100644 --- a/scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs +++ b/scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs @@ -17,7 +17,13 @@ fs.writeFileSync( name: "clickclack", version: "0.0.1", type: "module", - openclaw: { extensions: ["./index.mjs"] }, + openclaw: { + extensions: ["./index.mjs"], + channel: { + id: "clickclack", + configuredState: { env: { anyOf: ["CLICKCLACK_BOT_TOKEN"] } }, + }, + }, }, null, 2, @@ -30,7 +36,6 @@ fs.writeFileSync( id: "clickclack", activation: { onStartup: false }, channels: ["clickclack"], - channelEnvVars: { clickclack: ["CLICKCLACK_BOT_TOKEN"] }, channelConfigs: { clickclack: { schema: { diff --git a/scripts/e2e/lib/upgrade-survivor/run.sh b/scripts/e2e/lib/upgrade-survivor/run.sh index 000a38e072f9..102f3aefdb8f 100644 --- a/scripts/e2e/lib/upgrade-survivor/run.sh +++ b/scripts/e2e/lib/upgrade-survivor/run.sh @@ -397,7 +397,7 @@ fs.writeFileSync( { id: "brave", activation: { onStartup: false }, - providerAuthEnvVars: { brave: ["BRAVE_API_KEY"] }, + setup: { providers: [{ id: "brave", envVars: ["BRAVE_API_KEY"] }] }, contracts: { webSearchProviders: ["brave"] }, configSchema: { type: "object", diff --git a/scripts/e2e/status-corrupt-plugin-deps.sh b/scripts/e2e/status-corrupt-plugin-deps.sh index 6c276805b40c..0a39730d5ad8 100644 --- a/scripts/e2e/status-corrupt-plugin-deps.sh +++ b/scripts/e2e/status-corrupt-plugin-deps.sh @@ -24,7 +24,11 @@ cat > "$PLUGIN_DIR/package.json" <<'JSON' "version": "1.0.0", "openclaw": { "extensions": ["./index.cjs"], - "setupEntry": "./setup-entry.cjs" + "setupEntry": "./setup-entry.cjs", + "channel": { + "id": "e2e-corrupt-chat", + "configuredState": { "env": { "anyOf": ["E2E_CORRUPT_CHAT_TOKEN"] } } + } } } JSON @@ -51,10 +55,7 @@ cat > "$PLUGIN_DIR/openclaw.plugin.json" <<'JSON' } } }, - "channels": ["e2e-corrupt-chat"], - "channelEnvVars": { - "e2e-corrupt-chat": ["E2E_CORRUPT_CHAT_TOKEN"] - } + "channels": ["e2e-corrupt-chat"] } JSON diff --git a/scripts/e2e/upgrade-survivor-docker.sh b/scripts/e2e/upgrade-survivor-docker.sh index 4b0d11ee1bce..616d74866a69 100755 --- a/scripts/e2e/upgrade-survivor-docker.sh +++ b/scripts/e2e/upgrade-survivor-docker.sh @@ -286,7 +286,7 @@ fs.writeFileSync( { id: "brave", activation: { onStartup: false }, - providerAuthEnvVars: { brave: ["BRAVE_API_KEY"] }, + setup: { providers: [{ id: "brave", envVars: ["BRAVE_API_KEY"] }] }, contracts: { webSearchProviders: ["brave"] }, configSchema: { type: "object", diff --git a/scripts/fixtures/packed-plugin-sdk-type-smoke.ts b/scripts/fixtures/packed-plugin-sdk-type-smoke.ts index 943570383708..f7cb6a4f678e 100644 --- a/scripts/fixtures/packed-plugin-sdk-type-smoke.ts +++ b/scripts/fixtures/packed-plugin-sdk-type-smoke.ts @@ -1,9 +1,9 @@ // Packed Plugin Sdk Type Smoke script supports OpenClaw repository automation. type PublicPluginSdkModules = [ - typeof import("openclaw/plugin-sdk"), + typeof import("openclaw/plugin-sdk/core"), typeof import("openclaw/plugin-sdk/channel-entry-contract"), typeof import("openclaw/plugin-sdk/config-contracts"), - typeof import("openclaw/plugin-sdk/provider-entry"), + typeof import("openclaw/plugin-sdk/plugin-entry"), typeof import("openclaw/plugin-sdk/runtime-env"), ]; diff --git a/scripts/generate-bundled-channel-config-metadata.ts b/scripts/generate-bundled-channel-config-metadata.ts index 62c95f2a5f12..6b25d87d9ab4 100644 --- a/scripts/generate-bundled-channel-config-metadata.ts +++ b/scripts/generate-bundled-channel-config-metadata.ts @@ -16,7 +16,6 @@ type BundledPluginSource = { manifest: { id: string; channels?: unknown; - channelEnvVars?: unknown; name?: string; description?: string; } & Record; @@ -173,17 +172,25 @@ function resolveRootConfigurable(source: BundledPluginSource, channelId: string) } function resolveRootChannelEnvVars(source: BundledPluginSource, channelId: string): string[] { - const raw = source.manifest.channelEnvVars; - if (!raw || typeof raw !== "object" || Array.isArray(raw)) { + const channelMeta = resolvePackageChannelMeta(source); + if (channelMeta?.id !== channelId) { return []; } - const value = (raw as Record)[channelId]; - if (!Array.isArray(value)) { + const configuredState = channelMeta.configuredState; + if (!configuredState || typeof configuredState !== "object" || Array.isArray(configuredState)) { return []; } + const env = (configuredState as Record).env; + if (!env || typeof env !== "object" || Array.isArray(env)) { + return []; + } + const envRecord = env as Record; + const values = [envRecord.allOf, envRecord.anyOf].flatMap((value) => + Array.isArray(value) ? value : [], + ); return [ ...new Set( - value + values .map((entry) => (typeof entry === "string" ? entry.trim() : "")) .filter((entry) => entry.length > 0), ), diff --git a/scripts/lib/deprecated-plugin-sdk-usage.mjs b/scripts/lib/deprecated-plugin-sdk-usage.mjs index 814d07f2289f..336dd0c22dfc 100644 --- a/scripts/lib/deprecated-plugin-sdk-usage.mjs +++ b/scripts/lib/deprecated-plugin-sdk-usage.mjs @@ -1,11 +1,7 @@ // Builds the list of deprecated public plugin SDK specifiers guarded by scripts. import deprecatedPublicPluginSdkSubpaths from "./plugin-sdk-deprecated-public-subpaths.json" with { type: "json" }; -const DEPRECATED_PLUGIN_SDK_EXTRA_SPECIFIERS = [ - "openclaw/plugin-sdk", - "openclaw/plugin-sdk/agent-dir-compat", - "openclaw/plugin-sdk/test-utils", -]; +const DEPRECATED_PLUGIN_SDK_EXTRA_SPECIFIERS = []; /** Build fully qualified deprecated plugin SDK module specifiers from subpath metadata. */ export function buildDeprecatedPluginSdkModuleSpecifiers( @@ -17,7 +13,9 @@ export function buildDeprecatedPluginSdkModuleSpecifiers( ]; // tsconfig aliases the scoped @openclaw/plugin-sdk package to the same // src/plugin-sdk modules, so ban both spellings of every deprecated specifier. - return [...new Set(unscoped.flatMap((specifier) => [specifier, `@${specifier}`]))].toSorted(); + return [...new Set(unscoped.flatMap((specifier) => [specifier, `@${specifier}`]))].toSorted( + (a, b) => a.localeCompare(b), + ); } /** @@ -31,18 +29,10 @@ export function buildDeprecatedPluginSdkModuleSpecifiers( export const BANNED_INTERNAL_PLUGIN_SDK_FACADE_MODULES = [ // Reply facades: canonical seams are openclaw/plugin-sdk/channel-inbound and // openclaw/plugin-sdk/channel-outbound (defineChannelMessageAdapter family). - { - modulePath: "src/plugin-sdk/channel-envelope", - canonical: "openclaw/plugin-sdk/channel-inbound", - }, { modulePath: "src/plugin-sdk/channel-message", canonical: "openclaw/plugin-sdk/channel-outbound", }, - { - modulePath: "src/plugin-sdk/channel-message-runtime", - canonical: "openclaw/plugin-sdk/channel-outbound", - }, { modulePath: "src/plugin-sdk/channel-reply-pipeline", canonical: "openclaw/plugin-sdk/channel-outbound", diff --git a/scripts/lib/extension-package-boundary.ts b/scripts/lib/extension-package-boundary.ts index 77823d3d1dc0..24407df7fc4e 100644 --- a/scripts/lib/extension-package-boundary.ts +++ b/scripts/lib/extension-package-boundary.ts @@ -54,8 +54,6 @@ function buildPackageBoundaryDtsPaths(params: { } export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = { - "openclaw/extension-api": ["../src/extensionAPI.ts"], - "openclaw/plugin-sdk": ["../dist/plugin-sdk/index.d.ts"], "openclaw/plugin-sdk/*": ["../dist/plugin-sdk/*.d.ts"], ...privateLocalOnlyPluginSdkPackageDtsPaths, "openclaw/plugin-sdk/account-id": ["../dist/plugin-sdk/account-id.d.ts"], @@ -67,18 +65,8 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = { "../dist/plugin-sdk/channel-secret-basic-runtime.d.ts", ], "openclaw/plugin-sdk/channel-secret-runtime": ["../dist/plugin-sdk/channel-secret-runtime.d.ts"], - "openclaw/plugin-sdk/channel-secret-tts-runtime": [ - "../dist/plugin-sdk/channel-secret-tts-runtime.d.ts", - ], "openclaw/plugin-sdk/channel-streaming": ["../dist/plugin-sdk/channel-streaming.d.ts"], "openclaw/plugin-sdk/error-runtime": ["../dist/plugin-sdk/error-runtime.d.ts"], - "openclaw/plugin-sdk/provider-catalog-live-runtime": [ - "../dist/plugin-sdk/provider-catalog-live-runtime.d.ts", - ], - "openclaw/plugin-sdk/provider-catalog-shared": [ - "../dist/plugin-sdk/provider-catalog-shared.d.ts", - ], - "openclaw/plugin-sdk/provider-entry": ["../dist/plugin-sdk/provider-entry.d.ts"], "openclaw/plugin-sdk/secret-ref-runtime": ["../dist/plugin-sdk/secret-ref-runtime.d.ts"], "openclaw/plugin-sdk/ssrf-runtime": ["../dist/plugin-sdk/ssrf-runtime.d.ts"], "@openclaw/qa-channel/api.js": ["../dist/plugin-sdk/extensions/qa-channel/api.d.ts"], @@ -257,18 +245,25 @@ function prefixExtensionPackageBoundaryPaths( ); } +function omitExtensionPackageBoundaryPaths( + paths: Record, + omittedKeys: readonly string[], +): Record { + const omitted = new Set(omittedKeys); + return Object.fromEntries(Object.entries(paths).filter(([key]) => !omitted.has(key))); +} + export const EXTENSION_PACKAGE_BOUNDARY_XAI_PATHS = { ...prefixExtensionPackageBoundaryPaths( - (({ - "openclaw/plugin-sdk/channel-secret-basic-runtime": _omitBasic, - "openclaw/plugin-sdk/channel-secret-tts-runtime": _omitTts, - "@openclaw/matrix/test-api.js": _omitMatrix, - "@openclaw/discord/api.js": _omitDiscord, - "@openclaw/slack/api.js": _omitSlack, - "@openclaw/telegram/api.js": _omitTelegram, - "@openclaw/whatsapp/api.js": _omitWhatsApp, - ...rest - }) => rest)(EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS), + omitExtensionPackageBoundaryPaths(EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS, [ + "openclaw/plugin-sdk/channel-secret-basic-runtime", + "openclaw/plugin-sdk/channel-secret-tts-runtime", + "@openclaw/matrix/test-api.js", + "@openclaw/discord/api.js", + "@openclaw/slack/api.js", + "@openclaw/telegram/api.js", + "@openclaw/whatsapp/api.js", + ]), "../", ), "openclaw/plugin-sdk/channel-entry-contract": [ @@ -277,18 +272,6 @@ export const EXTENSION_PACKAGE_BOUNDARY_XAI_PATHS = { "openclaw/plugin-sdk/browser-maintenance": [ "../../dist/plugin-sdk/src/plugin-sdk/browser-maintenance.d.ts", ], - "openclaw/plugin-sdk/cli-runtime": ["../../dist/plugin-sdk/cli-runtime.d.ts"], - "openclaw/plugin-sdk/provider-catalog-live-runtime": [ - "../../dist/plugin-sdk/provider-catalog-live-runtime.d.ts", - ], - "openclaw/plugin-sdk/provider-catalog-shared": [ - "../../dist/plugin-sdk/provider-catalog-shared.d.ts", - ], - "openclaw/plugin-sdk/provider-env-vars": ["../../dist/plugin-sdk/provider-env-vars.d.ts"], - "openclaw/plugin-sdk/provider-entry": ["../../dist/plugin-sdk/provider-entry.d.ts"], - "openclaw/plugin-sdk/provider-web-search-contract": [ - "../../dist/plugin-sdk/provider-web-search-contract.d.ts", - ], "@openclaw/qa-channel/api.js": ["../../dist/plugin-sdk/extensions/qa-channel/api.d.ts"], "@openclaw/*.js": ["../../packages/plugin-sdk/dist/extensions/*.d.ts", "../*"], "@openclaw/*": ["../*"], diff --git a/scripts/lib/plugin-contract-test-plan.mjs b/scripts/lib/plugin-contract-test-plan.mjs index 55247240987f..ffb162cb5f51 100644 --- a/scripts/lib/plugin-contract-test-plan.mjs +++ b/scripts/lib/plugin-contract-test-plan.mjs @@ -7,12 +7,9 @@ function listContractTestFiles(rootDir = "src/plugins/contracts") { const CONTRACT_FILE_WEIGHTS = new Map([ ["plugin-sdk-subpaths.test.ts", 80], - ["plugin-sdk-root-alias.test.ts", 90], ["tts.contract.test.ts", 70], ["boundary-invariants.test.ts", 36], ["extension-package-project-boundaries.test.ts", 34], - ["plugin-sdk-index.test.ts", 32], - ["plugin-sdk-index.bundle.test.ts", 32], ["plugin-sdk-package-contract-guardrails.test.ts", 46], ["providers.contract.test.ts", 30], ["registry.contract.test.ts", 30], diff --git a/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json b/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json index 552a06ff4769..1f8f37412524 100644 --- a/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json +++ b/scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json @@ -1,8 +1,5 @@ [ "channel-lifecycle", - "channel-runtime", - "compat", - "config-types", "infra-runtime", "text-runtime" ] diff --git a/scripts/lib/plugin-sdk-deprecated-public-subpaths.json b/scripts/lib/plugin-sdk-deprecated-public-subpaths.json index 9fb5f898a4c5..d543e5b8330f 100644 --- a/scripts/lib/plugin-sdk-deprecated-public-subpaths.json +++ b/scripts/lib/plugin-sdk-deprecated-public-subpaths.json @@ -1,60 +1,20 @@ [ "agent-config-primitives", - "channel-config-schema-legacy", - "channel-envelope", - "channel-inbound-roots", "channel-lifecycle", - "channel-location", "channel-logging", "channel-message", - "channel-message-runtime", - "channel-pairing-paths", - "channel-reply-options-runtime", "channel-reply-pipeline", - "channel-runtime", "channel-secret-runtime", "channel-streaming", "command-auth", - "command-gating", - "compat", "config-runtime", - "config-schema", - "config-types", - "direct-dm", - "direct-dm-access", "discord", "group-access", "inbound-reply-dispatch", "infra-runtime", - "lmstudio", - "lmstudio-runtime", "matrix", - "mattermost", - "media-generation-runtime-shared", "messaging-targets", - "memory-core", - "memory-core-engine-runtime", - "memory-core-host-events", - "memory-core-host-multimodal", - "memory-core-host-query", - "memory-host-files", - "memory-host-status", - "music-generation-core", - "outbound-runtime", - "outbound-send-deps", - "provider-auth-login", - "provider-zai-endpoint", - "reply-dedupe", - "runtime-logger", - "runtime-secret-resolution", - "secret-provider-integration", - "self-hosted-provider-setup", - "setup-adapter-runtime", - "skills-runtime", "telegram-account", - "telegram-command-config", "text-runtime", - "webhook-path", - "zalouser", "zod" ] diff --git a/scripts/lib/plugin-sdk-doc-metadata.ts b/scripts/lib/plugin-sdk-doc-metadata.ts index 74c9bb20a3e6..3178a81aa145 100644 --- a/scripts/lib/plugin-sdk-doc-metadata.ts +++ b/scripts/lib/plugin-sdk-doc-metadata.ts @@ -12,18 +12,12 @@ type PluginSdkDocMetadata = { }; export const pluginSdkDocMetadata = { - index: { - category: "legacy", - }, core: { category: "core", }, health: { category: "core", }, - sandbox: { - category: "runtime", - }, "approval-runtime": { category: "runtime", }, @@ -39,36 +33,21 @@ export const pluginSdkDocMetadata = { "approval-gateway-runtime": { category: "runtime", }, - "approval-reference-runtime": { - category: "runtime", - }, "approval-native-runtime": { category: "runtime", }, - "approval-reaction-runtime": { - category: "runtime", - }, "approval-reply-runtime": { category: "runtime", }, "plugin-entry": { category: "core", }, - "access-groups": { - category: "channel", - }, "channel-actions": { category: "channel", }, "channel-config-schema": { category: "channel", }, - "channel-config-schema-legacy": { - category: "channel", - }, - "chat-channel-ids": { - category: "channel", - }, "channel-contract": { category: "channel", }, @@ -87,78 +66,33 @@ export const pluginSdkDocMetadata = { "command-auth": { category: "channel", }, - zalouser: { - category: "channel", - }, "command-status": { category: "channel", }, - "command-status-runtime": { - category: "runtime", - }, "secret-input": { category: "channel", }, "webhook-ingress": { category: "channel", }, - "provider-onboard": { - category: "provider", - }, - "provider-oauth-runtime": { - category: "provider", - }, - "message-tool-delivery-hints": { - category: "runtime", - }, - "tool-results": { - category: "utilities", - }, "widget-html": { category: "utilities", }, - "provider-selection-runtime": { - category: "provider", - }, - "provider-catalog-live-runtime": { - category: "provider", - }, - "provider-model-types": { - category: "provider", - }, "runtime-store": { category: "runtime", }, "session-store-runtime": { category: "runtime", }, - "session-transcript-runtime": { - category: "runtime", - }, - "sqlite-runtime": { - category: "runtime", - }, "agent-runtime": { category: "runtime", }, "agent-harness-runtime": { category: "runtime", }, - "speech-core": { - category: "provider", - }, "speech-settings": { category: "provider", }, - "realtime-voice": { - category: "provider", - }, - "tts-runtime": { - category: "runtime", - }, - "inline-image-data-url-runtime": { - category: "runtime", - }, "allow-from": { category: "utilities", }, diff --git a/scripts/lib/plugin-sdk-entries.d.mts b/scripts/lib/plugin-sdk-entries.d.mts index 1042fbbd3bd9..46937a2e5063 100644 --- a/scripts/lib/plugin-sdk-entries.d.mts +++ b/scripts/lib/plugin-sdk-entries.d.mts @@ -1,6 +1,7 @@ export const pluginSdkEntrypoints: string[]; export const pluginSdkSubpaths: string[]; export const privateLocalOnlyPluginSdkEntrypoints: string[]; +export const productionPluginSdkEntrypoints: string[]; export const publicPluginSdkEntrypoints: string[]; export const publicPluginSdkSubpaths: string[]; export const deprecatedPublicPluginSdkEntrypoints: string[]; @@ -15,4 +16,5 @@ export function buildPluginSdkPackageExports(): Record< } >; export function listPluginSdkDistArtifacts(): string[]; -export function listPrivateLocalOnlyPluginSdkDistArtifacts(): string[]; +export function listPackagedPrivatePluginSdkRuntimeArtifacts(): string[]; +export function listUnpackagedPrivatePluginSdkDistArtifacts(): string[]; diff --git a/scripts/lib/plugin-sdk-entries.mjs b/scripts/lib/plugin-sdk-entries.mjs index 6fb7e7772d13..56ec3b0f6aad 100644 --- a/scripts/lib/plugin-sdk-entries.mjs +++ b/scripts/lib/plugin-sdk-entries.mjs @@ -5,16 +5,16 @@ import pluginSdkEntryList from "./plugin-sdk-entrypoints.json" with { type: "jso import privateLocalOnlyPluginSdkSubpathList from "./plugin-sdk-private-local-only-subpaths.json" with { type: "json" }; /** - * All plugin SDK entrypoints, including the package root index. + * All plugin SDK subpath entrypoints. The package root barrel has been removed. * @internal Shared repository-script contract. */ export const pluginSdkEntrypoints = [...pluginSdkEntryList]; /** - * Plugin SDK subpath entrypoints, excluding the package root index. + * Plugin SDK subpath entrypoints. * @internal Shared test-configuration contract. */ -export const pluginSdkSubpaths = pluginSdkEntrypoints.filter((entry) => entry !== "index"); +export const pluginSdkSubpaths = pluginSdkEntrypoints; const privateLocalOnlyPluginSdkSubpathSet = new Set( privateLocalOnlyPluginSdkSubpathList.filter( @@ -32,15 +32,60 @@ export const privateLocalOnlyPluginSdkEntrypoints = pluginSdkSubpaths.filter((en /** Public plugin SDK entrypoints that appear in package exports. */ export const publicPluginSdkEntrypoints = pluginSdkEntrypoints.filter( - (entry) => entry === "index" || !privateLocalOnlyPluginSdkSubpathSet.has(entry), + (entry) => !privateLocalOnlyPluginSdkSubpathSet.has(entry), ); /** - * Public plugin SDK subpaths, excluding the package root index. + * Public plugin SDK subpaths. * @internal Shared repository-script contract. */ -export const publicPluginSdkSubpaths = publicPluginSdkEntrypoints.filter( - (entry) => entry !== "index", +export const publicPluginSdkSubpaths = publicPluginSdkEntrypoints; + +// These local-only entries were already omitted from ordinary packaged builds +// before bundled runtime facades moved behind the same private-local boundary. +const nonProductionPluginSdkSubpathSet = new Set([ + "agent-runtime-test-contracts", + "channel-contract-testing", + "channel-target-testing", + "channel-test-helpers", + "codex-native-task-runtime", + "plugin-test-api", + "plugin-test-contracts", + "plugin-state-test-runtime", + "plugin-test-runtime", + "provider-http-test-mocks", + "provider-test-contracts", + "qa-channel", + "qa-channel-protocol", + "qa-lab", + "qa-runtime", + "reply-payload-testing", + "sqlite-runtime-testing", + "ssrf-runtime-internal", + "test-env", + "test-fixtures", + "test-live", + "test-live-auth", + "test-media-generation", + "test-media-understanding", + "test-node-mocks", +]); + +/** Plugin SDK entrypoints built in ordinary source and packaged runtime builds. */ +export const productionPluginSdkEntrypoints = pluginSdkEntrypoints.filter( + (entry) => !nonProductionPluginSdkSubpathSet.has(entry), +); + +const productionPluginSdkEntrypointSet = new Set(productionPluginSdkEntrypoints); + +/** Private runtime facades required by core or bundled plugins in packaged builds. */ +const packagedPrivatePluginSdkRuntimeEntrypoints = privateLocalOnlyPluginSdkEntrypoints.filter( + (entry) => productionPluginSdkEntrypointSet.has(entry), +); + +/** Private entrypoints reserved for local tests and QA builds. */ +const nonProductionPrivatePluginSdkEntrypoints = privateLocalOnlyPluginSdkEntrypoints.filter( + (entry) => !productionPluginSdkEntrypointSet.has(entry), ); /** @@ -74,7 +119,7 @@ export function buildPluginSdkEntrySources(entries = pluginSdkEntrypoints) { export function buildPluginSdkPackageExports() { return Object.fromEntries( publicPluginSdkEntrypoints.map((entry) => [ - entry === "index" ? "./plugin-sdk" : `./plugin-sdk/${entry}`, + `./plugin-sdk/${entry}`, { types: `./dist/plugin-sdk/${entry}.d.ts`, default: `./dist/plugin-sdk/${entry}.js`, @@ -98,9 +143,15 @@ export function listPluginSdkDistArtifacts() { * List private local-only plugin SDK dist artifacts expected after local builds. * @internal Shared repository-script contract. */ -export function listPrivateLocalOnlyPluginSdkDistArtifacts() { - return privateLocalOnlyPluginSdkEntrypoints.flatMap((entry) => [ - `dist/plugin-sdk/${entry}.js`, - `dist/plugin-sdk/${entry}.d.ts`, - ]); +/** List private runtime facade artifacts required inside package output. */ +export function listPackagedPrivatePluginSdkRuntimeArtifacts() { + return packagedPrivatePluginSdkRuntimeEntrypoints.map((entry) => `dist/plugin-sdk/${entry}.js`); +} + +/** List private artifacts that must stay out of package output. */ +export function listUnpackagedPrivatePluginSdkDistArtifacts() { + return [ + ...privateLocalOnlyPluginSdkEntrypoints.map((entry) => `dist/plugin-sdk/${entry}.d.ts`), + ...nonProductionPrivatePluginSdkEntrypoints.map((entry) => `dist/plugin-sdk/${entry}.js`), + ]; } diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 5b15e8565ae7..13753e7bffb7 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -1,21 +1,14 @@ [ - "index", "core", - "lmstudio", - "lmstudio-runtime", "provider-setup", "sandbox", - "self-hosted-provider-setup", "routing", "runtime", "health", "runtime-doctor", "runtime-env", - "runtime-logger", "proxy-capture", - "runtime-secret-resolution", "setup", - "setup-adapter-runtime", "setup-runtime", "channel-setup", "channel-streaming", @@ -35,15 +28,12 @@ "question-gateway-runtime", "config-runtime", "config-contracts", - "config-types", "plugin-config-runtime", "config-mutation", "cron-store-runtime", - "config-schema", "json-schema-runtime", "json-unsafe-integers", "reply-runtime", - "reply-dedupe", "reply-dispatch-runtime", "reply-reference", "reply-chunking", @@ -53,12 +43,8 @@ "inbound-reply-dispatch", "inbound-envelope", "channel-reply-pipeline", - "channel-reply-options-runtime", - "channel-runtime", "interactive-runtime", "outbound-media", - "outbound-send-deps", - "outbound-runtime", "pair-loop-guard-runtime", "poll-runtime", "async-lock-runtime", @@ -99,12 +85,10 @@ "speech-settings", "tts-runtime", "plugin-runtime", - "skills-runtime", "channel-secret-basic-runtime", "channel-secret-runtime", "channel-secret-tts-runtime", "secret-ref-runtime", - "secret-provider-integration", "secret-file-runtime", "security-runtime", "gateway-method-runtime", @@ -167,19 +151,14 @@ "dangerous-name-runtime", "command-auth", "command-auth-native", - "command-gating", "command-primitives-runtime", "command-status", "command-status-runtime", "command-detection", "command-surface", "collection-runtime", - "compat", - "direct-dm", - "direct-dm-access", "direct-dm-guard-policy", "discord", - "mattermost", "matrix", "device-bootstrap", "diagnostic-runtime", @@ -191,27 +170,21 @@ "channel-config-schema", "bundled-channel-config-schema", "chat-channel-ids", - "channel-config-schema-legacy", "channel-actions", "channel-plugin-common", "channel-core", "channel-entry-contract", "channel-contract", - "channel-envelope", "channel-feedback", "channel-inbound", "channel-inbound-debounce", - "channel-inbound-roots", "channel-logging", - "channel-location", "channel-mention-gating", "channel-lifecycle", "channel-ingress-runtime", "channel-message", - "channel-message-runtime", "channel-outbound", "channel-pairing", - "channel-pairing-paths", "channel-policy", "channel-send-result", "channel-route", @@ -240,12 +213,10 @@ "global-singleton", "directory-config-runtime", "directory-runtime", - "media-generation-runtime-shared", "image-generation", "image-generation-runtime", "image-generation-core", "music-generation", - "music-generation-core", "video-generation", "video-generation-runtime", "video-generation-core", @@ -265,27 +236,20 @@ "ingress-effect-once", "keyed-async-queue", "qa-runner-runtime", - "memory-core", - "memory-core-engine-runtime", "memory-core-host-embedding-registry", "memory-core-host-engine-embeddings", "memory-core-host-engine-foundation", "memory-core-host-engine-qmd", "memory-core-host-engine-storage", - "memory-core-host-multimodal", - "memory-core-host-query", "memory-core-host-secret", - "memory-core-host-events", "memory-core-host-status", "memory-core-host-runtime-cli", "memory-core-host-runtime-core", "memory-core-host-runtime-files", "memory-host-core", "memory-host-events", - "memory-host-files", "memory-host-markdown", "memory-host-search", - "memory-host-status", "message-tool-delivery-hints", "models-provider-runtime", "skill-commands-runtime", @@ -297,7 +261,6 @@ "provider-auth-login-flow-runtime", "provider-auth-api-key", "provider-auth-result", - "provider-auth-login", "provider-selection-runtime", "plugin-entry", "provider-catalog-live-runtime", @@ -325,7 +288,6 @@ "retry-runtime", "run-command", "param-readers", - "provider-zai-endpoint", "secret-input", "secret-input-runtime", "channel-status", @@ -335,7 +297,6 @@ "state-paths", "target-resolver-runtime", "telegram-account", - "telegram-command-config", "text-autolink-runtime", "text-utility-runtime", "widget-html", @@ -346,9 +307,7 @@ "webhook-ingress", "webhook-targets", "webhook-request-guards", - "webhook-path", "web-media", - "zalouser", "zod", "agent-core", "agent-sessions", diff --git a/scripts/lib/plugin-sdk-private-local-only-subpaths.json b/scripts/lib/plugin-sdk-private-local-only-subpaths.json index 91c425c2c133..da7649c17fba 100644 --- a/scripts/lib/plugin-sdk-private-local-only-subpaths.json +++ b/scripts/lib/plugin-sdk-private-local-only-subpaths.json @@ -1,22 +1,157 @@ [ + "access-groups", + "account-resolution-runtime", + "acp-binding-resolve-runtime", + "acp-binding-runtime", + "acp-runtime", + "acp-runtime-backend", + "agent-core", + "agent-harness-exec-review-runtime", + "agent-harness-task-runtime", + "agent-harness-tool-runtime", "agent-runtime-test-contracts", + "agent-sessions", + "approval-reaction-runtime", + "approval-reference-runtime", + "async-lock-runtime", + "browser-config", + "bundled-channel-config-schema", + "channel-activity-runtime", + "channel-config-writes", "channel-contract-testing", + "channel-mention-gating", + "channel-route", + "channel-secret-tts-runtime", "channel-target-testing", + "channel-targets", "channel-test-helpers", + "chat-channel-ids", + "cli-backend", + "cli-runtime", + "codex-mcp-projection", "codex-native-task-runtime", + "command-status-runtime", + "command-surface", + "concurrency-runtime", + "context-visibility-runtime", + "conversation-binding-runtime", + "cron-store-runtime", + "dangerous-name-runtime", + "delivery-queue-runtime", + "direct-dm-guard-policy", + "directory-config-runtime", + "document-extractor", + "embedding-providers", + "exec-approvals-runtime", + "expect-runtime", + "fetch-runtime", + "file-access-runtime", + "file-lock", + "global-singleton", + "group-activation", + "heartbeat-runtime", + "host-runtime", + "html-entity-runtime", + "image-generation", + "image-generation-core", + "image-generation-runtime", + "inline-image-data-url-runtime", + "json-schema-runtime", + "json-unsafe-integers", + "keyed-async-queue", + "llm", + "markdown-table-runtime", + "media-generation-runtime", + "memory-core-host-embedding-registry", + "memory-core-host-engine-embeddings", + "memory-core-host-engine-qmd", + "memory-core-host-engine-storage", + "memory-core-host-runtime-cli", + "memory-core-host-runtime-core", + "memory-core-host-runtime-files", + "memory-core-host-secret", + "memory-core-host-status", + "memory-host-events", + "memory-host-markdown", + "memory-host-search", + "message-tool-delivery-hints", + "migration", + "migration-runtime", + "music-generation", + "node-host", + "number-runtime", + "outbound-media", + "pair-loop-guard-runtime", + "plugin-state-runtime", + "plugin-state-test-runtime", "plugin-test-api", "plugin-test-contracts", - "plugin-state-test-runtime", "plugin-test-runtime", + "poll-runtime", + "process-runtime", + "provider-auth-api-key", + "provider-auth-login-flow-runtime", + "provider-auth-result", + "provider-auth-runtime", + "provider-catalog-live-runtime", + "provider-catalog-shared", + "provider-entry", + "provider-env-vars", + "provider-http", "provider-http-test-mocks", + "provider-model-shared", + "provider-model-types", + "provider-oauth-runtime", + "provider-onboard", + "provider-selection-runtime", + "provider-setup", + "provider-stream", + "provider-stream-family", + "provider-stream-shared", "provider-test-contracts", + "provider-tools", + "provider-transport-runtime", + "provider-usage", + "provider-web-fetch", + "provider-web-fetch-contract", + "provider-web-search", + "provider-web-search-config-contract", + "provider-web-search-contract", "qa-channel", "qa-channel-protocol", "qa-lab", + "qa-runner-runtime", "qa-runtime", + "realtime-bootstrap-context", + "realtime-transcription", + "realtime-voice", "reply-payload-testing", + "reply-reference", + "request-url", + "response-limit-runtime", + "retry-runtime", + "runtime-doctor", + "runtime-fetch", + "sandbox", + "secret-file-runtime", + "secure-random-runtime", + "session-binding-runtime", + "session-catalog", + "session-key-runtime", + "session-transcript-hit", + "session-transcript-runtime", + "session-visibility", + "simple-completion-runtime", + "speech", + "speech-core", + "sqlite-runtime", "sqlite-runtime-testing", + "ssrf-dispatcher", "ssrf-runtime-internal", + "string-normalization-runtime", + "system-event-runtime", + "talk-config-runtime", + "target-resolver-runtime", "test-env", "test-fixtures", "test-live", @@ -24,5 +159,21 @@ "test-media-generation", "test-media-understanding", "test-node-mocks", - "test-utils" + "text-autolink-runtime", + "text-utility-runtime", + "thread-bindings-runtime", + "thread-bindings-session-runtime", + "time-runtime", + "tool-payload", + "tool-results", + "transcripts", + "transport-ready-runtime", + "tts-runtime", + "types", + "video-generation", + "video-generation-core", + "video-generation-runtime", + "web-content-extractor", + "webhook-targets", + "windows-spawn" ] diff --git a/scripts/lib/session-accessor-debt-baseline.json b/scripts/lib/session-accessor-debt-baseline.json index 2077d8fdc86c..72193a169e7a 100644 --- a/scripts/lib/session-accessor-debt-baseline.json +++ b/scripts/lib/session-accessor-debt-baseline.json @@ -12,7 +12,7 @@ "src/config/sessions/legacy-store-readonly.ts": 2, "src/config/sessions/session-accessor.entry.ts": 2, "src/config/sessions/store-load.ts": 3, - "src/config/sessions/store.ts": 10, + "src/config/sessions/store.ts": 9, "src/config/sessions/transcript.ts": 2 }, "sessionAccessorWrite": { diff --git a/scripts/lib/ts-topology/scope.ts b/scripts/lib/ts-topology/scope.ts index 9f8366ee84eb..d44d5e273694 100644 --- a/scripts/lib/ts-topology/scope.ts +++ b/scripts/lib/ts-topology/scope.ts @@ -125,8 +125,7 @@ export function createPluginSdkScope(_repoRoot: string): TopologyScope { const entrypoints = publicPluginSdkEntrypoints.map((entrypoint) => ({ entrypoint, sourcePath: `src/plugin-sdk/${entrypoint}.ts`, - importSpecifier: - entrypoint === "index" ? "openclaw/plugin-sdk" : `openclaw/plugin-sdk/${entrypoint}`, + importSpecifier: `openclaw/plugin-sdk/${entrypoint}`, })); return buildScopeFromEntrypoints("plugin-sdk", "OpenClaw plugin-sdk public surface", entrypoints); } diff --git a/scripts/plugin-boundary-report.ts b/scripts/plugin-boundary-report.ts index 29b2d9b43380..1714ca0ea8b0 100644 --- a/scripts/plugin-boundary-report.ts +++ b/scripts/plugin-boundary-report.ts @@ -539,7 +539,7 @@ function buildReport(options: Partial> = { matchesOwner(options.owner, entry.owner) || matchesOwner(options.owner, entry.consumerOwner), ); const usedReserved = new Set(reservedImports.map((entry) => entry.subpath)); - const unusedReservedSubpaths = reservedBundledPluginSdkEntrypoints + const unusedReservedSubpaths = (reservedBundledPluginSdkEntrypoints as readonly string[]) .filter( (subpath) => !usedReserved.has(subpath) && diff --git a/scripts/plugin-sdk-surface-report.mjs b/scripts/plugin-sdk-surface-report.mjs index 90bc2cd10407..cc477d890b4a 100644 --- a/scripts/plugin-sdk-surface-report.mjs +++ b/scripts/plugin-sdk-surface-report.mjs @@ -92,120 +92,42 @@ function readPluginSdkEntrypointBudgetEnv(name, fallback, env = process.env) { const defaultPublicDeprecatedExportsByEntrypointBudget = Object.freeze({ core: 2, - health: 1, - "command-gating": 5, - lmstudio: 37, - "lmstudio-runtime": 27, - "provider-setup": 1, - "self-hosted-provider-setup": 14, routing: 1, - runtime: 3, - // Deprecated Telegram-named alias retained for plugin SDK compatibility. - "retry-runtime": 1, - "runtime-logger": 3, - "runtime-secret-resolution": 5, - "secret-provider-integration": 4, - "setup-adapter-runtime": 1, - "skills-runtime": 5, - "channel-streaming": 55, + health: 1, + "channel-streaming": 54, "approval-gateway-runtime": 1, "approval-handler-runtime": 1, - "approval-reply-runtime": 3, - "approval-runtime": 1, - "config-runtime": 123, + "approval-reply-runtime": 1, + "config-runtime": 116, "config-contracts": 1, - // +1 each: unified implicit-mention config and AgentThinkingLevel types. - // +1: SwarmConfig mirrors the public tools.swarm config contract. - "config-types": 428, - "config-schema": 3, - "reply-dedupe": 1, "inbound-reply-dispatch": 26, "channel-reply-pipeline": 12, - "channel-reply-options-runtime": 2, - "channel-runtime": 144, "interactive-runtime": 13, - "outbound-send-deps": 4, - "outbound-runtime": 16, - "file-access-runtime": 2, - "infra-runtime": 595, + "infra-runtime": 593, "ssrf-policy": 1, "ssrf-runtime": 1, "media-runtime": 2, "text-runtime": 191, - "agent-core": 1, - "agent-runtime": 7, - "plugin-runtime": 13, + "agent-runtime": 2, "channel-secret-runtime": 23, - "secret-file-runtime": 1, - "security-runtime": 7, - "agent-harness": 7, - "agent-harness-runtime": 11, - types: 6, + "agent-harness-runtime": 5, "agent-config-primitives": 2, - "command-auth": 81, - // +2: group scope encoder/key builder mirrored by deprecated compat. - // +5: shared channel setup, policy, and config schema helpers. - compat: 167, - "direct-dm": 9, - "direct-dm-access": 5, + "command-auth": 78, discord: 48, - mattermost: 7, matrix: 1, - // +3: shared multi-account and group-entry schema builders. - "channel-config-schema-legacy": 25, - "channel-actions": 2, - "channel-envelope": 3, - "channel-inbound": 21, - "channel-inbound-roots": 1, + "channel-inbound": 15, "channel-logging": 4, - "channel-location": 4, - "channel-mention-gating": 7, "channel-lifecycle": 23, - // Registry sweep: 77 packages, zero fetch failures; channel-ingress and dead aliases - // had zero consumers. - // +11 each: durable channel-ingress drain seam (drain/lifecycle/claim/retry) mirrored by compat (#108656). - // +3 each: shared ingress monitor factory and lifecycle/result contracts. - "channel-message": 244, - "channel-message-runtime": 241, - "channel-pairing-paths": 1, - // Deprecated pairing/conversation exports from the SQLite pairing migration - // landed on main (#105802) without entrypoint pins; not touched by this PR. + "channel-message": 129, "channel-pairing": 1, - "conversation-runtime": 4, + "channel-policy": 8, "channel-send-result": 1, - "channel-policy": 15, - "channel-route": 5, "session-store-runtime": 4, - "session-transcript-runtime": 2, "group-access": 13, - "media-generation-runtime-shared": 3, - "music-generation-core": 20, "reply-history": 8, "messaging-targets": 12, - "memory-core": 45, - "memory-core-engine-runtime": 15, - "memory-core-host-multimodal": 3, - "memory-core-host-query": 2, - "memory-core-host-events": 12, - "memory-core-host-status": 1, - "memory-core-host-runtime-core": 1, - "memory-host-core": 1, - "memory-host-files": 7, - "memory-host-status": 72, - "provider-auth": 20, - "provider-oauth-runtime": 2, - "provider-auth-login": 3, - "provider-model-shared": 30, - "provider-stream-family": 40, - "provider-stream-shared": 29, - "provider-stream": 40, - "provider-web-search": 1, - "provider-zai-endpoint": 3, + "provider-auth": 19, "telegram-account": 3, - "telegram-command-config": 7, - "webhook-ingress": 2, - "webhook-path": 2, - zalouser: 5, zod: 282, }); @@ -213,174 +135,32 @@ export function readPluginSdkSurfaceBudgets(env = process.env) { const budgets = { publicEntrypoints: readPluginSdkSurfaceBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", - // Registry sweep: 77 packages, zero fetch failures; retired dead channel-ingress facade. - // +1: speech-settings keeps agent prompt imports off the synthesis/runtime graph. - // +1: meeting-runtime barrel: browser meeting-bot core behind MeetingPlatformAdapter. - // +1: question-gateway-runtime resolves ask_user choices for channel plugins. - // +1: ingress-effect-once gives drained channels a narrow durable side-effect guard. // +1: session-discussion binds one external discussion provider to sessions. - 333, + 140, env, ), - // ScopeTree adds six channel-policy exports, mirrored by compat, including three functions. - // Its flat channel-groups builder adds one function, also mirrored by compat. - // Its case-insensitive scope-key resolver adds one function, also mirrored by compat. - // Its length-prefixed segment encoder and scope-key builder add two functions, also mirrored. - // The focused HTML entity runtime and quote-aware HTML tokenizer add one public function each. - // Plugin service Gateway event scope and emitter types add four facade exports. publicExports: readPluginSdkSurfaceBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", - // +4: registerMcpServerConnectionResolver context/result/resolver/registration types (#106229). - // +2: materializeRequesterScopedMcpToolsForHarnessRun (agent-harness-runtime + compat mirror). - // +1: matchesNoProxy exposes canonical Undici-compatible bypass selection to plugins. - // +4: group scope encoder/key builder (channel-policy + compat mirror). - // +1: runDetachedWebhookWork gives post-ack work an independently tracked admission root. - // +9: app-guided provider setup context/candidate/hook types and their public mirrors. - // +3: atomic SQLite STRICT migration function, options, and result for plugin stores. - // Harvest: channel-ingress -64; dead channel-message dispatch aliases -23. - // Harvest: retired qa-live-transport-scenarios subpath -6. - // +12: typed plan step/status and checklist formatter across channel barrels. - // +8: plan-step ingress union and normalizer across channel barrels. - // Harvest: retired dual-field plan payload builder -1. - // +12: active plan-step consumers pinned through channel-outbound and mirrors. - // +6: app-guided provider setup types retained by plugin-entry and mirrors. - // +3: widget HTML validation helpers and tool input error. - // Used-union narrowing: 31 wildcard barrels drop to explicit used exports; - // proxy stream API and codex marker/scaffold pins retained. - // +2: generic channel retry runner and Retry-After parser. - // +1: shared speech-provider API key resolver. - // +32: shared channel setup, config-schema, policy, and status helpers. - // +2: shared channel replay-guard factory and claim handle. - // +6: lightweight speech settings types, normalizers, and config resolver. - // +4: unified implicit-mention config, schema, resolved policy, and resolver. - // Harvest: retired AudioConfig type -1. - // +4: bounded plugin blob store options, entry, entry info, and store types. - // +6: shared progress receipt tracker + compositor snapshot across channel barrels. - // +1: selectPreferredLocalModelId shares app-guided local model ranking across providers. - // +4: shared audio-energy stats and speech-threshold gate through realtime-voice. - // +2: supplemental sender decision and outbound text chunk sequencer. - // +2: shared realtime voice session harness through realtime-voice. - // +24: narrowed durable channel-ingress drain seam — factory, lifecycle binding, - // tuning constants, and telegram-consumed claim helpers with compat mirrors, - // after harvesting exports orphaned by the split-out WhatsApp adapter (#108656). - // +10: supplemental sender helpers plus host-owned SQLite lease contracts. - // Harvest: retired dual-field plan payload builder -1. - // +23: core channel, envelope, direct-DM, feedback, legacy-payload, and memory contracts. - // +81: meeting-runtime barrel: browser meeting-bot core behind MeetingPlatformAdapter. - // +3: question-gateway-runtime resolver plus request/result types. - // +1: async memory prompt preparation registration. - // +1: canonical memory host event normalization for SQLite storage. - // +1: centralized remember-across-conversations effective-default resolver. - // +4: gateway-backed harness question runner, claim/cancel helpers, and caller type. - // Harvest: internal question runtime exports -2. - // +1: ingress-effect-once factory. - // +1: shared persistent-dedupe claim loop. - // +3: bounded raw transcript cursor request, result, and reader. - // +3: bounded visible transcript cursor request, result, and reader. - // +1: explicit AgentModelPolicyConfig shared with provider setup surfaces. - // +1: AgentHarnessSessionSupersededError lets harness plugins stop stale-owner fallback. - // +1: AgentThinkingLevel shared by default-turn and compaction config. - // +9: shared ingress monitor factory and lifecycle/result contracts across - // channel-outbound and its two deprecated compatibility barrels. - // +1: SwarmConfig exposes the tools.swarm contract through config-types. - // +3: harness sessionFork capability params, result, and failure-code contracts. - // +2: upstream-link registry write/delete for harness-owned session forks. - // Harvest: mention-pattern schemas and helper exports -3. - // +1: config-backed main-session resolver for Gateway-hosted plugin services. - // +9: outbound echo identity type and record/query helpers across - // channel-outbound and its two compatibility barrels. - // Net +1: public session catalog locator types after the protocol cleanup harvest. - // +2: lifecycle-owned prepared model catalog sync and async readers. - // Harvest: retired tuning-knob config types -10. - // Harvest: removed process-global API-provider publication functions -2. // +4: session discussion state, info, provider, and registration contracts. // +2: structured media placeholder formatter and its text-fact contract. - 8186, + 4721, env, ), publicFunctionExports: readPluginSdkSurfaceBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", - // +2: materializeRequesterScopedMcpToolsForHarnessRun (agent-harness-runtime + compat mirror). - // +4: group scope encoder/key builder (channel-policy + compat mirror). - // +1: atomic SQLite STRICT migration for plugin stores. - // +1: runDetachedWebhookWork gives post-ack work an independently tracked admission root. - // Harvest: channel-ingress -19; dead channel-message dispatch aliases -23. - // Harvest: retired qa-live-transport-scenarios subpath -3. - // +4: shared plan checklist formatter across channel barrels. - // +4: plan-step normalizer across channel barrels. - // Harvest: retired dual-field plan payload builder -1. - // +6: active plan-step helpers pinned through channel-outbound and mirrors. - // +2: widget HTML document detection and size assertion. - // Used-union narrowing of the 31 wildcard barrels. - // +2: generic channel retry runner and Retry-After parser. - // +1: shared speech-provider API key resolver. - // +24: shared channel setup, config-schema, policy, and status helpers. - // +1: shared channel replay-guard factory. - // +3: receipt tracker/snapshot callables across channel barrels. - // +3: lightweight speech settings normalizers and config resolver. - // +1: unified implicit-mention policy resolver. - // +1: selectPreferredLocalModelId shares app-guided local model ranking across providers. - // +3: PCM16/mu-law energy readers and speech-threshold gate factory. - // +2: supplemental sender decision and outbound text chunk sequencer. - // +1: shared realtime voice session harness through realtime-voice. - // +9: narrowed drain seam functions and compat mirrors after the - // WhatsApp-split harvest (#108656). - // +3: supplemental sender helpers plus the PluginStateLeaseRunner callback. - // Harvest: retired dual-field plan payload builder -1. - // +13: core channel, envelope, direct-DM, feedback, legacy-payload, and memory operations. - // +32: meeting-runtime barrel: browser meeting-bot core behind MeetingPlatformAdapter. - // +1: question-gateway-runtime resolver. - // +1: async memory prompt preparation registration. - // +1: canonical memory host event normalization for SQLite storage. - // +1: centralized remember-across-conversations effective-default resolver. - // +3: gateway-backed harness question runner and claim/cancel helpers. - // Harvest: internal question runtime callable -1. - // +1: ingress-effect-once factory. - // +1: shared persistent-dedupe claim loop. - // +1: bounded raw transcript cursor reader. - // +1: bounded visible transcript cursor reader. - // +3: shared ingress monitor factory across channel-outbound and compat mirrors. - // +2: upstream-link registry write/delete for harness-owned session forks. - // +1: config-backed main-session resolver for Gateway-hosted plugin services. - // +6: outbound echo record/query helpers across channel-outbound and mirrors. - // +2: lifecycle-owned prepared model catalog sync and async readers. - // Harvest: removed process-global API-provider publication functions -2. // +1: session discussion provider registration. // +1: structured media placeholder formatter for text-only channel carriers. - 4557, + 2879, env, ), publicDeprecatedExports: readPluginSdkSurfaceBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS", - // +2: group scope encoder/key builder mirrored by deprecated compat. - // Harvest: channel-ingress -8; dead channel-message dispatch aliases -23. - // +77: five zero-consumer subpaths enter their removal window. - // +9: typed plan exports and formatter through deprecated channel barrels. - // +6: plan-step ingress union and normalizer through deprecated channel barrels. - // +8: channel-outbound plan pins mirrored through deprecated barrels. - // Used-union narrowing drops inherited deprecated exports. - // +1: Telegram runner alias retained for plugin SDK compatibility. - // +8: shared channel helpers mirrored by deprecated barrels. - // +3: receipt/snapshot exports through deprecated channel barrels. - // +1: unified implicit-mention config type through deprecated config-types. - // +24: narrowed drain seam compat mirrors in the channel-message - // deprecation-window barrels (#108656). - // Harvest: retired dual-field plan payload builder -1; lower-only drift -8. - // +1: AgentModelPolicyConfig mirrored by deprecated config-types. - // +6: ingress monitor lifecycle/result contracts through deprecated channel barrels. - // +1: AgentThinkingLevel mirrored by deprecated config-types. - // +1: SwarmConfig mirrored by deprecated config-types. - // +2: outbound echo helpers inherited by deprecated channel barrels. - // +1: lifecycle-owned prepared model catalog contract mirrored by agent-runtime compat. - 3017, + 1697, env, ), publicWildcardReexports: readPluginSdkSurfaceBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_WILDCARD_REEXPORTS", - // Used-union narrowing removes 103 wildcard re-exports. - // Harvest: freeze the compat config-schema barrel to explicit exports -1; - // retire the Memory Core facade's event-store wildcard -1. - 103, + 83, env, ), }; diff --git a/scripts/postinstall-bundled-plugins.mjs b/scripts/postinstall-bundled-plugins.mjs index 7f72ee8b3fad..e54453911af0 100644 --- a/scripts/postinstall-bundled-plugins.mjs +++ b/scripts/postinstall-bundled-plugins.mjs @@ -28,7 +28,6 @@ import { expandPackageDistImportClosure } from "./lib/package-dist-imports.mjs"; const scriptDir = dirname(fileURLToPath(import.meta.url)); const DEFAULT_PACKAGE_ROOT = join(scriptDir, ".."); const DISABLE_POSTINSTALL_ENV = "OPENCLAW_DISABLE_BUNDLED_PLUGIN_POSTINSTALL"; -const DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV = "OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION"; const DIST_INVENTORY_PATH = "dist/postinstall-inventory.json"; // One budget covers all three prune walks (legacy-deps prepass, file listing, // empty-dir sweep). npm upgrades transiently hold old+new content-hashed dist @@ -116,11 +115,6 @@ const NODE_COMPILE_CACHE_VERSION_DIR_RE = /^v\d+\.\d+\.\d+-/u; class InstalledDistScanLimitError extends Error {} -function hasEnvFlag(env, key) { - const value = env?.[key]?.trim().toLowerCase(); - return Boolean(value && value !== "0" && value !== "false" && value !== "no"); -} - function normalizeRelativePath(filePath) { return filePath.replace(/\\/g, "/"); } @@ -830,10 +824,6 @@ export async function runPluginRegistryPostinstallMigration(params = {}) { const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT; const env = params.env ?? process.env; - if (hasEnvFlag(env, DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV)) { - return { status: "disabled", migrated: false, reason: "disabled-env" }; - } - try { const migrationModule = await importInstalledDistModule( params, @@ -850,9 +840,6 @@ export async function runPluginRegistryPostinstallMigration(params = {}) { env, packageRoot, }); - for (const warning of result.preflight?.deprecationWarnings ?? []) { - log.warn(`[postinstall] ${warning}`); - } if (result.migrated) { log.log( `[postinstall] migrated plugin registry: ${result.current.plugins.length} plugin(s) indexed`, diff --git a/scripts/prepare-extension-package-boundary-artifacts.mjs b/scripts/prepare-extension-package-boundary-artifacts.mjs index de92e44d4557..0986e477c921 100644 --- a/scripts/prepare-extension-package-boundary-artifacts.mjs +++ b/scripts/prepare-extension-package-boundary-artifacts.mjs @@ -11,7 +11,7 @@ import { resolveRepoToolBinPath, } from "./lib/local-heavy-check-runtime.mjs"; import { parsePositiveInt } from "./lib/numeric-options.mjs"; -import { pluginSdkEntrypoints, publicPluginSdkEntrypoints } from "./lib/plugin-sdk-entries.mjs"; +import { pluginSdkEntrypoints, productionPluginSdkEntrypoints } from "./lib/plugin-sdk-entries.mjs"; import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs"; const repoRoot = resolve(import.meta.dirname, ".."); @@ -311,21 +311,18 @@ const ENTRY_SHIMS_INPUTS = [ "scripts/lib/plugin-sdk-entrypoints.json", "scripts/lib/plugin-sdk-entries.mjs", ]; -const ENTRY_SHIM_RUNTIME_OUTPUTS = ["dist/plugin-sdk/webhook-path.js"]; - /** * Lists entry-shim artifacts written by scripts/write-plugin-sdk-entry-dts.ts. */ export function resolveBoundaryEntryShimRequiredOutputs(env = process.env) { const entries = - env.OPENCLAW_BUILD_PRIVATE_QA === "1" ? pluginSdkEntrypoints : publicPluginSdkEntrypoints; - return [ - ...entries.flatMap((entry) => [ + env.OPENCLAW_BUILD_PRIVATE_QA === "1" ? pluginSdkEntrypoints : productionPluginSdkEntrypoints; + return entries + .flatMap((entry) => [ `dist/plugin-sdk/${entry}.d.ts`, `packages/plugin-sdk/dist/src/plugin-sdk/${entry}.d.ts`, - ]), - ...ENTRY_SHIM_RUNTIME_OUTPUTS, - ].toSorted((a, b) => a.localeCompare(b)); + ]) + .toSorted((a, b) => a.localeCompare(b)); } function isRelevantTypeInput(filePath) { diff --git a/scripts/release-check.ts b/scripts/release-check.ts index 5ec9a0f6f8b9..79a90a75618b 100755 --- a/scripts/release-check.ts +++ b/scripts/release-check.ts @@ -41,7 +41,8 @@ import { import { collectBundledPluginPackageDependencySpecs } from "./lib/plugin-package-dependencies.mjs"; import { listPluginSdkDistArtifacts, - listPrivateLocalOnlyPluginSdkDistArtifacts, + listPackagedPrivatePluginSdkRuntimeArtifacts, + listUnpackagedPrivatePluginSdkDistArtifacts, } from "./lib/plugin-sdk-entries.mjs"; import { runInstalledWorkspaceBootstrapSmoke, @@ -87,6 +88,7 @@ const requiredPathGroups = [ ["dist/index.js", "dist/index.mjs"], ["dist/entry.js", "dist/entry.mjs"], ...listPluginSdkDistArtifacts(), + ...listPackagedPrivatePluginSdkRuntimeArtifacts(), ...listBundledPluginPackArtifacts(), ...listStaticExtensionAssetOutputs().filter((relativePath) => { const match = /^dist\/extensions\/([^/]+)\//u.exec(relativePath); @@ -107,8 +109,6 @@ const requiredPathGroups = [ "scripts/lib/recommended-tool-installs.json", "scripts/lib/package-dist-imports.mjs", "scripts/postinstall-bundled-plugins.mjs", - "dist/plugin-sdk/compat.js", - "dist/plugin-sdk/root-alias.cjs", "dist/agents/compaction-planning.worker.js", "dist/agents/model-provider-auth.worker.js", "dist/audit/audit-event-writer.worker.js", @@ -139,7 +139,11 @@ const forbiddenPrefixes = [ "dist/plugin-sdk/src/plugin-sdk/qa-channel-protocol.d.ts", "dist/plugin-sdk/src/plugin-sdk/qa-lab.d.ts", "dist/plugin-sdk/src/plugin-sdk/qa-runtime.d.ts", - ...listPrivateLocalOnlyPluginSdkDistArtifacts(), + "dist/plugin-sdk/index.", + "dist/plugin-sdk/compat.", + "dist/plugin-sdk/root-alias.", + "dist/extensionAPI.", + ...listUnpackagedPrivatePluginSdkDistArtifacts(), "dist/qa-runtime-", "dist/plugin-sdk/.tsbuildinfo", "docs/.generated/", @@ -161,7 +165,6 @@ const forbiddenPrivatePluginSdkDeclarationMarkers = [ "//#region src/test-utils/", ] as const; const forbiddenPrivateQaContentScanPrefixes = ["dist/"] as const; -const forbiddenPluginSdkRootAliasMinifiedExportPattern = /\bmod\.[A-Za-z_$]\b/u; const appcastPath = resolve("appcast.xml"); const laneBuildMin = 1_000_000_000; const laneFloorAdoptionReleaseKey = 20260227; @@ -671,15 +674,6 @@ export function collectPackedInstalledPackageVerificationErrors(params: { `installed openclaw binary version mismatch: expected ${params.expectedVersion}, found ${params.installedBinaryVersion || ""}.`, ); } - const rootAliasPath = join(params.packageRoot, "dist", "plugin-sdk", "root-alias.cjs"); - if (existsSync(rootAliasPath)) { - const rootAliasSource = readFileSync(rootAliasPath, "utf8"); - if (forbiddenPluginSdkRootAliasMinifiedExportPattern.test(rootAliasSource)) { - errors.push( - "installed package dist/plugin-sdk/root-alias.cjs depends on a single-letter bundled export alias.", - ); - } - } return errors; } diff --git a/scripts/run-node.mjs b/scripts/run-node.mjs index b842b6edf122..701ae5c20af5 100644 --- a/scripts/run-node.mjs +++ b/scripts/run-node.mjs @@ -47,7 +47,6 @@ const RUN_NODE_SIGNAL_FORCE_KILL_AFTER_MS = 5_000; const runtimePostBuildWatchedPaths = [ "scripts/copy-bundled-plugin-metadata.mjs", - "scripts/copy-plugin-sdk-root-alias.mjs", "scripts/lib", "scripts/lib/local-build-metadata.mjs", "scripts/lib/local-build-metadata-paths.mjs", @@ -58,7 +57,6 @@ const runtimePostBuildWatchedPaths = [ "scripts/stage-bundled-plugin-runtime.mjs", "scripts/windows-cmd-helpers.mjs", "scripts/write-official-channel-catalog.mjs", - "src/plugin-sdk/root-alias.cjs", BUNDLED_PLUGIN_ROOT_DIR, ]; const runtimePostBuildScriptPaths = new Set( @@ -173,9 +171,6 @@ const hasDirtySourceTree = (deps) => { const isRuntimePostBuildRelevantPath = (repoPath) => { const normalizedPath = normalizePath(repoPath).replace(/^\.\/+/, ""); - if (normalizedPath === "src/plugin-sdk/root-alias.cjs") { - return true; - } if (runtimePostBuildStaticAssetPaths.has(normalizedPath)) { return true; } @@ -434,10 +429,6 @@ const readPackageJsonPluginSdkAliasFileNames = (deps) => { const fileNames = new Set(); for (const exportKey of Object.keys(packageExports)) { - if (exportKey === "./plugin-sdk") { - fileNames.add("index.js"); - continue; - } if (!exportKey.startsWith("./plugin-sdk/")) { continue; } diff --git a/scripts/runtime-postbuild.mjs b/scripts/runtime-postbuild.mjs index 074cf736b8a6..29b1bd87f987 100644 --- a/scripts/runtime-postbuild.mjs +++ b/scripts/runtime-postbuild.mjs @@ -5,7 +5,6 @@ import path from "node:path"; import { performance } from "node:perf_hooks"; import { fileURLToPath, pathToFileURL } from "node:url"; import { copyBundledPluginMetadata } from "./copy-bundled-plugin-metadata.mjs"; -import { copyPluginSdkRootAlias } from "./copy-plugin-sdk-root-alias.mjs"; import { escapeRegExp } from "./lib/regexp.mjs"; import { copyStaticExtensionAssets, @@ -24,7 +23,6 @@ const ROOT_RUNTIME_ALIAS_PATTERN = /^(?.+\.(?:runtime|contract))-[A-Za-z0- const ROOT_STABLE_RUNTIME_ALIAS_PATTERN = /^.+\.(?:runtime|contract)\.js$/u; const ROOT_RUNTIME_IMPORT_SPECIFIER_PATTERN = /(["'])\.\/([^"']+\.(?:runtime|contract)-[A-Za-z0-9_-]+\.js)\1/gu; -const PLUGIN_SDK_ROOT_ALIAS_OUTPUT = "dist/plugin-sdk/root-alias.cjs"; const OFFICIAL_CHANNEL_CATALOG_OUTPUT = "dist/channel-catalog.json"; const LEGACY_ROOT_RUNTIME_COMPAT_ALIASES = [ // v2026.4.29 dispatch lazy chunks. Package updates used to replace the @@ -147,13 +145,6 @@ const LEGACY_CLI_EXIT_COMPAT_CHUNKS = [ }, ]; -/** - * Lists generated plugin SDK root-alias outputs. - */ -function listPluginSdkRootAliasOutputs() { - return [PLUGIN_SDK_ROOT_ALIAS_OUTPUT]; -} - /** * Lists generated official channel catalog outputs. */ @@ -292,7 +283,6 @@ function listLegacyRootRuntimeCompatOutputs(params = {}) { */ export function listCoreRuntimePostBuildOutputs(params = {}) { return [ - ...listPluginSdkRootAliasOutputs(), ...listOfficialChannelCatalogOutputs(), ...listStableRootRuntimeAliasOutputs(params), ...listLegacyRootRuntimeCompatOutputs(params), @@ -584,7 +574,6 @@ export function runRuntimePostBuild(params = {}) { `runtime-postbuild: ${phaseTimings.length} phases completed in ${totalMs}ms (slowest: ${slowest.label} ${slowest.durationMs}ms)`, ); }; - runPhase("plugin SDK root alias", () => copyPluginSdkRootAlias(params)); runPhase("bundled plugin metadata", () => copyBundledPluginMetadata(params)); runPhase("official channel catalog", () => writeOfficialChannelCatalog(params)); runPhase("bundled plugin runtime overlay", () => stageBundledPluginRuntime(params)); diff --git a/scripts/stage-bundled-plugin-runtime.mjs b/scripts/stage-bundled-plugin-runtime.mjs index ae034ea2d378..eeadc6d453a1 100644 --- a/scripts/stage-bundled-plugin-runtime.mjs +++ b/scripts/stage-bundled-plugin-runtime.mjs @@ -83,7 +83,6 @@ const PRIVATE_LOCAL_ONLY_PLUGIN_SDK_DIST_FILE_NAME_FALLBACK = [ "qa-lab.js", "qa-runtime.js", "ssrf-runtime-internal.js", - "test-utils.js", ]; function tryReadJsonFile(targetPath) { @@ -141,10 +140,6 @@ function readPublicPluginSdkDistFileNames(params) { const fileNames = new Set(); for (const exportKey of Object.keys(packageExports)) { - if (exportKey === "./plugin-sdk") { - fileNames.add("index.js"); - continue; - } if (!exportKey.startsWith("./plugin-sdk/")) { continue; } @@ -159,27 +154,16 @@ function readPublicPluginSdkDistFileNames(params) { function buildRuntimePluginSdkPackageExports(publicDistFileNames) { if (!publicDistFileNames) { - return { - "./plugin-sdk": "./plugin-sdk/index.js", - }; + return {}; } - const sortedFileNames = [...publicDistFileNames].toSorted((left, right) => { - if (left === "index.js") { - return -1; - } - if (right === "index.js") { - return 1; - } - return left.localeCompare(right); - }); + const sortedFileNames = [...publicDistFileNames].toSorted((left, right) => + left.localeCompare(right), + ); return Object.fromEntries( sortedFileNames.map((fileName) => { const subpath = fileName.slice(0, -".js".length); - return [ - subpath === "index" ? "./plugin-sdk" : `./plugin-sdk/${subpath}`, - `./plugin-sdk/${fileName}`, - ]; + return [`./plugin-sdk/${subpath}`, `./plugin-sdk/${fileName}`]; }), ); } diff --git a/scripts/test-built-plugin-singleton.mjs b/scripts/test-built-plugin-singleton.mjs index 9ffdcc14b6b2..55f004613378 100644 --- a/scripts/test-built-plugin-singleton.mjs +++ b/scripts/test-built-plugin-singleton.mjs @@ -78,8 +78,7 @@ fs.writeFileSync( fs.writeFileSync( path.join(distPluginDir, "index.js"), [ - "import sdk from 'openclaw/plugin-sdk';", - "const { emptyPluginConfigSchema } = sdk;", + "import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk/plugin-entry';", "", "export default {", ` id: ${JSON.stringify(pluginId)},`, diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index ef94463ccdfd..b1759238dbfe 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -427,7 +427,6 @@ const PRECISE_SOURCE_TEST_TARGETS = new Map([ ]); const PLUGIN_SDK_ENTRY_METADATA_TEST_TARGETS = [ "src/plugins/contracts/plugin-sdk-index.bundle.test.ts", - "src/plugins/contracts/plugin-sdk-index.test.ts", "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/contracts/extension-package-project-boundaries.test.ts", diff --git a/scripts/write-plugin-sdk-entry-dts.ts b/scripts/write-plugin-sdk-entry-dts.ts index b35c11b1d2e8..d80552d16488 100644 --- a/scripts/write-plugin-sdk-entry-dts.ts +++ b/scripts/write-plugin-sdk-entry-dts.ts @@ -6,44 +6,9 @@ import { build } from "tsdown"; import { buildPluginSdkEntrySources, pluginSdkEntrypoints, - publicPluginSdkEntrypoints, + productionPluginSdkEntrypoints, } from "./lib/plugin-sdk-entries.mjs"; -const RUNTIME_SHIMS: Partial> = { - "webhook-path": [ - "/** Normalize webhook paths into the canonical registry form used by route lookup. */", - "export function normalizeWebhookPath(raw) {", - " const trimmed = raw.trim();", - " if (!trimmed) {", - ' return "/";', - " }", - ' const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;', - ' if (withSlash.length > 1 && withSlash.endsWith("/")) {', - " return withSlash.slice(0, -1);", - " }", - " return withSlash;", - "}", - "", - "/** Resolve the effective webhook path from explicit path, URL, or default fallback. */", - "export function resolveWebhookPath(params) {", - " const trimmedPath = params.webhookPath?.trim();", - " if (trimmedPath) {", - " return normalizeWebhookPath(trimmedPath);", - " }", - " if (params.webhookUrl?.trim()) {", - " try {", - " const parsed = new URL(params.webhookUrl);", - ' return normalizeWebhookPath(parsed.pathname || "/");', - " } catch {", - " return null;", - " }", - " }", - " return params.defaultPath ?? null;", - "}", - "", - ].join("\n"), -}; - const USE_CANONICAL_DECLARATIONS = process.env.OPENCLAW_PLUGIN_SDK_CANONICAL_DTS === "1"; function isBareImportSpecifier(id: string): boolean { @@ -87,7 +52,7 @@ const distPluginSdkDir = path.join(process.cwd(), "dist/plugin-sdk"); const shouldBuildPrivateQaEntries = process.env.OPENCLAW_BUILD_PRIVATE_QA === "1"; const flatDeclarationEntrypoints = shouldBuildPrivateQaEntries ? pluginSdkEntrypoints - : publicPluginSdkEntrypoints; + : productionPluginSdkEntrypoints; const flatDeclarationEntrypointSet = new Set(flatDeclarationEntrypoints); if (USE_CANONICAL_DECLARATIONS) { @@ -144,14 +109,6 @@ for (const entry of pluginSdkEntrypoints) { `export * from "../../../../../dist/plugin-sdk/${entry}.js";\n`, "utf8", ); - - const runtimeShim = RUNTIME_SHIMS[entry]; - if (!runtimeShim) { - continue; - } - const runtimeOut = path.join(process.cwd(), `dist/plugin-sdk/${entry}.js`); - fs.mkdirSync(path.dirname(runtimeOut), { recursive: true }); - fs.writeFileSync(runtimeOut, runtimeShim, "utf8"); } const stampPath = path.join(process.cwd(), "dist/plugin-sdk/.boundary-entry-shims.stamp"); diff --git a/src/acp/persistent-bindings.test.ts b/src/acp/persistent-bindings.test.ts index 6ea81fdc21dc..901eebe01a3d 100644 --- a/src/acp/persistent-bindings.test.ts +++ b/src/acp/persistent-bindings.test.ts @@ -2,10 +2,8 @@ import { expectDefined } from "@openclaw/normalization-core"; import { beforeAll, beforeEach, describe, expect, it } from "vitest"; import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; -import type { - ChannelConfiguredBindingProvider, - ChannelPlugin, -} from "../channels/plugins/types.public.js"; +import type { ChannelConfiguredBindingProvider } from "../channels/plugins/types.adapters.js"; +import type { ChannelPlugin } from "../channels/plugins/types.public.js"; import type { OpenClawConfig } from "../config/config.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { createChannelTestPluginBase, createTestRegistry } from "../test-utils/channel-plugins.js"; diff --git a/src/agents/cli-runner.reliability.test.ts b/src/agents/cli-runner.reliability.test.ts index bfa92c13abcf..f9705897b41b 100644 --- a/src/agents/cli-runner.reliability.test.ts +++ b/src/agents/cli-runner.reliability.test.ts @@ -4353,7 +4353,6 @@ describe("runCliAgent reliability", () => { const hookRunner = { hasHooks: vi.fn((hookName: string) => hookName === "before_prompt_build"), runBeforePromptBuild: vi.fn(async () => ({ prependContext: "hook context" })), - runBeforeAgentStart: vi.fn(async () => undefined), }; setHookRunnerForTest(hookRunner); diff --git a/src/agents/cli-runner/prepare.test.ts b/src/agents/cli-runner/prepare.test.ts index d4cd17e299f6..56784cafa010 100644 --- a/src/agents/cli-runner/prepare.test.ts +++ b/src/agents/cli-runner/prepare.test.ts @@ -12,17 +12,14 @@ import { buildGroupChatContext, buildGroupIntro } from "../../auto-reply/reply/g import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { registerLegacyContextEngine } from "../../context-engine/legacy.registration.js"; -import { - registerContextEngine, - registerContextEngineForOwner, -} from "../../context-engine/registry.js"; +import { registerContextEngineForOwner } from "../../context-engine/registry.js"; import type { ContextEngine } from "../../context-engine/types.js"; import type { McpLoopbackRequestContext } from "../../gateway/mcp-grant-store.js"; import type { CliBackendPlugin } from "../../plugins/cli-backend.types.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import { clearMemoryPluginState, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, } from "../../plugins/memory-state.test-fixtures.js"; import { setActivePluginRegistry } from "../../plugins/runtime.js"; import { @@ -52,6 +49,15 @@ type McpLoopbackClientGrant = ReturnType< (typeof import("../../gateway/mcp-grant-store.js"))["mintMcpLoopbackClientGrant"] >; +function registerTestContextEngine( + id: string, + factory: Parameters[1], +) { + return registerContextEngineForOwner(id, factory, `test:${id}`, { + allowSameOwnerRefresh: true, + }); +} + const getRuntimeConfigMock = vi.hoisted(() => vi.fn(() => ({}))); const ensureSandboxWorkspaceForSessionMock = vi.hoisted(() => vi.fn<() => Promise>(async () => null), @@ -1605,7 +1611,6 @@ describe("prepareCliRunContext", () => { prependSystemContext: "prepend system", appendSystemContext: "append system", })), - runBeforeAgentStart: vi.fn(), }; mockGetGlobalHookRunner.mockReturnValue(hookRunner as never); @@ -1700,7 +1705,6 @@ describe("prepareCliRunContext", () => { prependContext: "trusted hook context", appendContext: "trusted hook tail", })), - runBeforeAgentStart: vi.fn(), }; mockGetGlobalHookRunner.mockReturnValue(hookRunner as never); @@ -1799,7 +1803,6 @@ describe("prepareCliRunContext", () => { runBeforePromptBuild: vi.fn(async () => ({ prependContext: "trusted hook context", })), - runBeforeAgentStart: vi.fn(), }; mockGetGlobalHookRunner.mockReturnValue(hookRunner as never); @@ -1844,7 +1847,6 @@ describe("prepareCliRunContext", () => { appendContext: "turn append", })), runBeforePromptBuild: vi.fn(), - runBeforeAgentStart: vi.fn(), }; mockGetGlobalHookRunner.mockReturnValue(hookRunner as never); @@ -1891,13 +1893,12 @@ describe("prepareCliRunContext", () => { expect(turnPrepareContext?.chatId).toBe("chat-1"); expect(turnPrepareContext?.senderId).toBe("user-456"); expect(hookRunner.runBeforePromptBuild).not.toHaveBeenCalled(); - expect(hookRunner.runBeforeAgentStart).not.toHaveBeenCalled(); } finally { fs.rmSync(dir, { recursive: true, force: true }); } }); - it("merges before_prompt_build and legacy before_agent_start hook context for CLI preparation", async () => { + it("applies before_prompt_build hook context for CLI preparation", async () => { const { dir, sessionFile } = createSessionFile(); try { const hookRunner = { @@ -1908,12 +1909,6 @@ describe("prepareCliRunContext", () => { prependSystemContext: "prompt prepend system", appendSystemContext: "prompt append system", })), - runBeforeAgentStart: vi.fn(async () => ({ - prependContext: "legacy prepend", - systemPrompt: "legacy system", - prependSystemContext: "legacy prepend system", - appendSystemContext: "legacy append system", - })), }; mockGetGlobalHookRunner.mockReturnValue(hookRunner as never); @@ -1925,19 +1920,18 @@ describe("prepareCliRunContext", () => { provider: "test-cli", model: "test-model", timeoutMs: 1_000, - runId: "run-test-legacy-merge", + runId: "run-test-prompt-build", messageChannel: "discord", currentChannelId: "channel:room-1", senderId: "user-789", config: createCliBackendConfig(), }); - expect(context.params.prompt).toBe("prompt prepend\n\nlegacy prepend\n\nlatest ask"); + expect(context.params.prompt).toBe("prompt prepend\n\nlatest ask"); expect(context.systemPrompt).toBe( - `${wrappedPluginSystemContext("prompt prepend system")}\n\n${wrappedPluginSystemContext("legacy prepend system")}\n\nprompt system\n\n${wrappedPluginSystemContext("prompt append system")}\n\n${wrappedPluginSystemContext("legacy append system")}${SYSTEM_PROMPT_CACHE_BOUNDARY}\nCurrent model identity: test-cli/test-model. Model question: answer this current-run value.`, + `${wrappedPluginSystemContext("prompt prepend system")}\n\nprompt system\n\n${wrappedPluginSystemContext("prompt append system")}${SYSTEM_PROMPT_CACHE_BOUNDARY}\nCurrent model identity: test-cli/test-model. Model question: answer this current-run value.`, ); expect(hookRunner.runBeforePromptBuild).toHaveBeenCalledOnce(); - expect(hookRunner.runBeforeAgentStart).toHaveBeenCalledOnce(); const beforePromptBuildCalls = hookRunner.runBeforePromptBuild.mock.calls as unknown as Array< [unknown, unknown] >; @@ -1947,15 +1941,6 @@ describe("prepareCliRunContext", () => { expect(promptContext?.channel).toBe("discord"); expect(promptContext?.chatId).toBe("room-1"); expect(promptContext?.senderId).toBe("user-789"); - const beforeAgentStartCalls = hookRunner.runBeforeAgentStart.mock.calls as unknown as Array< - [unknown, unknown] - >; - const legacyContext = beforeAgentStartCalls[0]?.[1] as - | { channel?: string; chatId?: string; senderId?: string } - | undefined; - expect(legacyContext?.channel).toBe("discord"); - expect(legacyContext?.chatId).toBe("room-1"); - expect(legacyContext?.senderId).toBe("user-789"); } finally { fs.rmSync(dir, { recursive: true, force: true }); } @@ -1969,7 +1954,6 @@ describe("prepareCliRunContext", () => { runBeforePromptBuild: vi.fn(async () => { throw new Error("hook exploded"); }), - runBeforeAgentStart: vi.fn(), }; mockGetGlobalHookRunner.mockReturnValue(hookRunner as never); @@ -2010,7 +1994,7 @@ describe("prepareCliRunContext", () => { dispose, }; }); - registerContextEngine(engineId, factory); + registerTestContextEngine(engineId, factory); setCliRunnerPrepareTestDeps({ resolveOpenClawReferencePaths: vi.fn(async () => { throw new Error("reference path lookup failed"); @@ -2101,7 +2085,7 @@ describe("prepareCliRunContext", () => { it("rejects CLI runs for context engines that require pre-prompt assembly", async () => { const { dir, sessionFile } = createSessionFile(); const engineId = `cli-unsupported-engine-${Date.now().toString(36)}`; - registerContextEngine(engineId, (): ContextEngine => { + registerTestContextEngine(engineId, (): ContextEngine => { return { info: { id: engineId, @@ -2161,7 +2145,7 @@ describe("prepareCliRunContext", () => { compact: vi.fn(async () => ({ ok: true, compacted: false })), }; }); - registerContextEngine(engineId, factory); + registerTestContextEngine(engineId, factory); getRuntimeConfigMock.mockReturnValue(runtimeConfig); cliBackendsTesting.setDepsForTest({ resolvePluginSetupCliBackend: () => undefined, @@ -2905,7 +2889,6 @@ describe("prepareCliRunContext", () => { systemPrompt: "hook system", prependSystemContext: "hook prepend system", })), - runBeforeAgentStart: vi.fn(), }; mockGetGlobalHookRunner.mockReturnValue(hookRunner as never); @@ -2980,7 +2963,7 @@ describe("prepareCliRunContext", () => { it("uses loopback-scoped tools when building bundled MCP CLI prompts", async () => { const { dir, sessionFile } = createSessionFile(); try { - registerMemoryPromptSection(({ availableTools }) => + registerTestMemoryPromptBuilder(({ availableTools }) => availableTools.has("memory_search") ? ["## Memory Recall", `tools=${[...availableTools].toSorted().join(",")}`, ""] : [], @@ -3130,7 +3113,7 @@ describe("prepareCliRunContext", () => { it("fails bundled MCP preparation when the loopback runtime is unavailable", async () => { const { dir, sessionFile } = createSessionFile(); try { - registerMemoryPromptSection(({ availableTools }) => + registerTestMemoryPromptBuilder(({ availableTools }) => availableTools.has("memory_search") ? ["## Memory Recall", `tools=${[...availableTools].toSorted().join(",")}`, ""] : [], diff --git a/src/agents/embedded-agent-runner/compact.hooks.test.ts b/src/agents/embedded-agent-runner/compact.hooks.test.ts index 8ce763bcb99d..ce4111c31a4c 100644 --- a/src/agents/embedded-agent-runner/compact.hooks.test.ts +++ b/src/agents/embedded-agent-runner/compact.hooks.test.ts @@ -1412,7 +1412,6 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { }, }, }, - agents: { defaults: { embeddedHarness: { runtime: "codex" } } }, } as never, }); @@ -1511,7 +1510,6 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] }, }, }, - agents: { defaults: { embeddedHarness: { runtime: "openclaw" } } }, } as never, }); @@ -1543,10 +1541,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { }, }, agents: { - defaults: { - embeddedHarness: { runtime: "codex" }, - compaction: { model: "openai/gpt-5.4-mini" }, - }, + defaults: { compaction: { model: "openai/gpt-5.4-mini" } }, }, } as never, }); diff --git a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts index c7ffce6aedaa..c7c075322a12 100644 --- a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts +++ b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts @@ -13,7 +13,6 @@ import type { import type { PluginHookAgentContext, PluginHookBeforeAgentReplyResult, - PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveResult, PluginHookBeforePromptBuildResult, } from "../../plugins/types.js"; @@ -86,12 +85,6 @@ export const mockedGlobalHookRunner = { _ctx: PluginHookAgentContext, ): Promise => undefined, ), - runBeforeAgentStart: vi.fn( - async ( - _eventValue: { prompt: string; messages?: unknown[] }, - _ctx: PluginHookAgentContext, - ): Promise => undefined, - ), runBeforeAgentFinalize: vi.fn( async ( _eventValue: PluginHookBeforeAgentFinalizeEvent, @@ -411,8 +404,6 @@ export function resetRunOverflowCompactionHarnessMocks(): void { mockedGlobalHookRunner.hasHooks.mockReturnValue(false); mockedGlobalHookRunner.runBeforeAgentReply.mockReset(); mockedGlobalHookRunner.runBeforeAgentReply.mockResolvedValue(undefined); - mockedGlobalHookRunner.runBeforeAgentStart.mockReset(); - mockedGlobalHookRunner.runBeforeAgentStart.mockResolvedValue(undefined); mockedGlobalHookRunner.runBeforeAgentFinalize.mockReset(); mockedGlobalHookRunner.runBeforeAgentFinalize.mockResolvedValue(undefined); mockedGlobalHookRunner.runBeforePromptBuild.mockReset(); diff --git a/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts b/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts index 918b9326df3d..58310a0d07a4 100644 --- a/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts +++ b/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts @@ -313,33 +313,6 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { mockedBuildEmbeddedRunPayloads.mockReturnValue([{ text: "ok" }]); }); - it("passes precomputed before_agent_start result into the attempt", async () => { - const beforeAgentStartResult = { - modelOverride: "agent-start-model", - prependContext: "agent start context", - }; - mockedGlobalHookRunner.hasHooks.mockImplementation( - (hookName) => hookName === "before_agent_start", - ); - mockedGlobalHookRunner.runBeforeAgentStart.mockResolvedValueOnce(beforeAgentStartResult); - mockedRunEmbeddedAttempt.mockResolvedValueOnce(makeAttemptResult({ promptError: null })); - - await runEmbeddedAgent({ - sessionId: "test-session", - sessionKey: "test-key", - sessionFile: "/tmp/session.json", - workspaceDir: "/tmp/workspace", - prompt: "hello", - timeoutMs: 30000, - runId: "run-before-agent-start-pass-through", - }); - - expect(mockedGlobalHookRunner.runBeforeAgentStart).toHaveBeenCalledTimes(1); - expectMockCallFields(mockedRunEmbeddedAttempt, { - beforeAgentStartResult, - }); - }); - it("reports hook-selected models as normal selected models, not fallbacks", async () => { useOpenAIPlatformAuthFixture(); mockedGlobalHookRunner.hasHooks.mockImplementation( diff --git a/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts b/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts index 114a7adb3671..44bee8fbd32e 100644 --- a/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts +++ b/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts @@ -79,7 +79,6 @@ export async function prepareAndDispatchEmbeddedRunAttempt(input: { runInput.laneController; const { requestedModelId, - beforeAgentStartResult, expectedHarnessArtifact, nativeModelOwned, authStorage, @@ -201,7 +200,6 @@ export async function prepareAndDispatchEmbeddedRunAttempt(input: { : undefined, modelRegistry, agentId: workspaceResolution.agentId, - beforeAgentStartResult, thinkLevel: runtime.thinkLevel, fastMode: attemptFastMode, fastModeStartedAtMs, diff --git a/src/agents/embedded-agent-runner/run/attempt-prompt-assembly.ts b/src/agents/embedded-agent-runner/run/attempt-prompt-assembly.ts index c9227c65c215..509bf5ed353f 100644 --- a/src/agents/embedded-agent-runner/run/attempt-prompt-assembly.ts +++ b/src/agents/embedded-agent-runner/run/attempt-prompt-assembly.ts @@ -127,7 +127,6 @@ export async function prepareEmbeddedAttemptPromptAssembly(input: { messages: promptBuildMessages, hookCtx, hookRunner: input.hookRunner, - beforeAgentStartResult: attempt.beforeAgentStartResult, bootstrapContextRunKind: attempt.bootstrapContextRunKind, }); const promptBeforePromptBuildHooks = effectivePrompt; diff --git a/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.test.ts b/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.test.ts index 1c45f24453c8..e43341c05678 100644 --- a/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.test.ts @@ -278,7 +278,6 @@ describe("resolvePromptBuildHookResult drain cache", () => { runAgentTurnPrepare, runHeartbeatPromptContribution, runBeforePromptBuild: vi.fn(async () => undefined), - runBeforeAgentStart: vi.fn(async () => undefined), }; const result = await resolvePromptBuildHookResult({ diff --git a/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.ts b/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.ts index 7821d1559384..24a2a87f8d82 100644 --- a/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.ts +++ b/src/agents/embedded-agent-runner/run/attempt.prompt-helpers.ts @@ -14,7 +14,6 @@ import type { PluginAgentTurnPrepareResult, PluginNextTurnInjectionRecord, PluginHookAgentContext, - PluginHookBeforeAgentStartResult, PluginHookBeforePromptBuildResult, } from "../../../plugins/types.js"; import { isCronSessionKey, isSubagentSessionKey } from "../../../routing/session-key.js"; @@ -38,11 +37,7 @@ import type { EmbeddedRunAttemptParams } from "./types.js"; type PromptBuildHookRunner = { hasHooks: ( - hookName: - | "agent_turn_prepare" - | "heartbeat_prompt_contribution" - | "before_prompt_build" - | "before_agent_start", + hookName: "agent_turn_prepare" | "heartbeat_prompt_contribution" | "before_prompt_build", ) => boolean; runAgentTurnPrepare?: ( event: { @@ -60,10 +55,6 @@ type PromptBuildHookRunner = { event: { prompt: string; messages: unknown[] }, ctx: PluginHookAgentContext, ) => Promise; - runBeforeAgentStart: ( - event: { prompt: string; messages: unknown[] }, - ctx: PluginHookAgentContext, - ) => Promise; }; // Cache drained next-turn injections by runId so retry attempts within the @@ -110,7 +101,6 @@ export async function resolvePromptBuildHookResult(params: { messages: unknown[]; hookCtx: PluginHookAgentContext; hookRunner?: PromptBuildHookRunner | null; - beforeAgentStartResult?: PluginHookBeforeAgentStartResult; bootstrapContextRunKind?: EmbeddedRunAttemptParams["bootstrapContextRunKind"]; }): Promise { const runId = params.hookCtx.runId; @@ -136,7 +126,7 @@ export async function resolvePromptBuildHookResult(params: { rememberDrainedInjections(runId, queuedContext.queuedInjections); } // Hook ordering mirrors the prompt assembly boundary: queued injections first, - // then prepare/heartbeat contributions, then prompt-build and legacy start hooks. + // then prepare/heartbeat contributions, then prompt-build hooks. const turnPrepareResult = params.hookRunner?.runAgentTurnPrepare && params.hookRunner.hasHooks("agent_turn_prepare") ? await params.hookRunner @@ -186,48 +176,22 @@ export async function resolvePromptBuildHookResult(params: { return undefined; }) : undefined; - const beforeAgentStartResult = - params.beforeAgentStartResult ?? - (params.hookRunner?.hasHooks("before_agent_start") - ? await params.hookRunner - .runBeforeAgentStart( - { - prompt: params.prompt, - messages: params.messages, - }, - params.hookCtx, - ) - .catch((hookErr: unknown) => { - log.warn( - `deprecated before_agent_start hook failed during prompt build: ${String(hookErr)}`, - ); - return undefined; - }) - : undefined); return { - systemPrompt: promptBuildResult?.systemPrompt ?? beforeAgentStartResult?.systemPrompt, + systemPrompt: promptBuildResult?.systemPrompt, prependContext: joinPresentTextSegments([ queuedContext.prependContext, turnPrepareResult?.prependContext, heartbeatContribution?.prependContext, promptBuildResult?.prependContext, - beforeAgentStartResult?.prependContext, ]), appendContext: joinPresentTextSegments([ queuedContext.appendContext, turnPrepareResult?.appendContext, heartbeatContribution?.appendContext, promptBuildResult?.appendContext, - beforeAgentStartResult?.appendContext, - ]), - prependSystemContext: joinPresentTextSegments([ - wrapPluginSystemContextSection(promptBuildResult?.prependSystemContext), - wrapPluginSystemContextSection(beforeAgentStartResult?.prependSystemContext), - ]), - appendSystemContext: joinPresentTextSegments([ - wrapPluginSystemContextSection(promptBuildResult?.appendSystemContext), - wrapPluginSystemContextSection(beforeAgentStartResult?.appendSystemContext), ]), + prependSystemContext: wrapPluginSystemContextSection(promptBuildResult?.prependSystemContext), + appendSystemContext: wrapPluginSystemContextSection(promptBuildResult?.appendSystemContext), }; } diff --git a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts index 88f5e03261da..73f158d62db0 100644 --- a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts @@ -13,7 +13,7 @@ import type { OpenClawConfig } from "../../../config/types.js"; import { buildMemorySystemPromptAddition } from "../../../context-engine/delegate.js"; import { clearMemoryPluginState, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, } from "../../../plugins/memory-state.test-fixtures.js"; import { createUserTurnTranscriptRecorder } from "../../../sessions/user-turn-transcript.js"; import { @@ -1258,7 +1258,6 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { hoisted.getGlobalHookRunnerMock.mockReturnValue({ hasHooks: vi.fn((name: string) => name === "before_prompt_build"), runBeforePromptBuild, - runBeforeAgentStart: vi.fn(), }); const seen: { modelMessages?: unknown[]; @@ -1325,7 +1324,6 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { hoisted.getGlobalHookRunnerMock.mockReturnValue({ hasHooks: vi.fn((name: string) => name === "before_prompt_build"), runBeforePromptBuild, - runBeforeAgentStart: vi.fn(), }); hoisted.sessionManager.getLeafEntry.mockReturnValueOnce({ id: "orphan-leaf", @@ -1393,7 +1391,6 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { hoisted.getGlobalHookRunnerMock.mockReturnValue({ hasHooks: vi.fn((name: string) => name === "before_prompt_build"), runBeforePromptBuild, - runBeforeAgentStart: vi.fn(), }); hoisted.sessionManager.getLeafEntry.mockReturnValueOnce({ id: "orphan-leaf", @@ -1992,7 +1989,6 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { hoisted.getGlobalHookRunnerMock.mockReturnValue({ hasHooks: vi.fn((name: string) => name === "before_prompt_build"), runBeforePromptBuild, - runBeforeAgentStart: vi.fn(), }); const seen: { modelMessages?: unknown[]; @@ -2073,7 +2069,6 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { hoisted.getGlobalHookRunnerMock.mockReturnValue({ hasHooks: vi.fn((name: string) => name === "before_prompt_build"), runBeforePromptBuild, - runBeforeAgentStart: vi.fn(), }); const result = await createContextEngineAttemptRunner({ @@ -2222,7 +2217,6 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { hoisted.getGlobalHookRunnerMock.mockReturnValue({ hasHooks: vi.fn((name: string) => name === "before_prompt_build"), runBeforePromptBuild, - runBeforeAgentStart: vi.fn(), }); const result = await createContextEngineAttemptRunner({ @@ -2732,12 +2726,8 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { const runBeforePromptBuild = vi.fn(async () => ({ prependContext: "hook context" })); const runLlmInput = vi.fn(async () => {}); hoisted.getGlobalHookRunnerMock.mockReturnValue({ - hasHooks: vi.fn( - (name: string) => - name === "before_prompt_build" || name === "before_agent_start" || name === "llm_input", - ), + hasHooks: vi.fn((name: string) => name === "before_prompt_build" || name === "llm_input"), runBeforePromptBuild, - runBeforeAgentStart: vi.fn(async () => ({ prependContext: "legacy hook context" })), runLlmInput, }); const seen: { prompt?: string; messages?: unknown[]; systemPrompt?: string } = {}; @@ -2968,7 +2958,7 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { }); it("lets non-legacy engines opt into the active memory prompt helper", async () => { - registerMemoryPromptSection(({ availableTools, citationsMode }) => { + registerTestMemoryPromptBuilder(({ availableTools, citationsMode }) => { if (!availableTools.has("memory_search")) { return []; } diff --git a/src/agents/embedded-agent-runner/run/attempt.test.ts b/src/agents/embedded-agent-runner/run/attempt.test.ts index cb4406634fc1..eb3db1b3e91d 100644 --- a/src/agents/embedded-agent-runner/run/attempt.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt.test.ts @@ -139,62 +139,7 @@ describe("buildEmbeddedAttemptToolRunContext", () => { }); describe("resolvePromptBuildHookResult", () => { - function createBeforeAgentStartOnlyHookRunner() { - return { - hasHooks: vi.fn( - ( - hookName: - | "agent_turn_prepare" - | "heartbeat_prompt_contribution" - | "before_prompt_build" - | "before_agent_start", - ) => hookName === "before_agent_start", - ), - runBeforePromptBuild: vi.fn(async () => undefined), - runBeforeAgentStart: vi.fn(async () => ({ prependContext: "from-hook" })), - }; - } - - it("reuses precomputed before_agent_start result without invoking hook again", async () => { - const hookRunner = createBeforeAgentStartOnlyHookRunner(); - const result = await resolvePromptBuildHookResult({ - config: {}, - prompt: "hello", - messages: [], - hookCtx: {}, - hookRunner, - beforeAgentStartResult: { prependContext: "from-cache", systemPrompt: "agent-start-system" }, - }); - - expect(hookRunner.runBeforeAgentStart).not.toHaveBeenCalled(); - expect(result).toEqual({ - prependContext: "from-cache", - appendContext: undefined, - systemPrompt: "agent-start-system", - prependSystemContext: undefined, - appendSystemContext: undefined, - }); - }); - - it("calls before_agent_start hook when precomputed result is absent", async () => { - const hookRunner = createBeforeAgentStartOnlyHookRunner(); - const messages = [{ role: "user", content: "ctx" }]; - const result = await resolvePromptBuildHookResult({ - config: {}, - prompt: "hello", - messages, - hookCtx: {}, - hookRunner, - }); - - expect(hookRunner.runBeforeAgentStart).toHaveBeenCalledTimes(1); - expect(hookRunner.runBeforeAgentStart).toHaveBeenCalledWith({ prompt: "hello", messages }, {}); - expect(result.prependContext).toBe("from-hook"); - }); - - it("merges prompt-build and before_agent_start context fields in deterministic order", async () => { - // Prompt-build hook context comes before before_agent_start context so plugin - // injections are replayed in stable order. + it("preserves prompt-build context fields", async () => { const hookRunner = { hasHooks: vi.fn(() => true), runBeforePromptBuild: vi.fn(async () => ({ @@ -203,12 +148,6 @@ describe("resolvePromptBuildHookResult", () => { prependSystemContext: "prompt prepend", appendSystemContext: "prompt append", })), - runBeforeAgentStart: vi.fn(async () => ({ - prependContext: "agent start context", - appendContext: "agent start append context", - prependSystemContext: "agent start prepend", - appendSystemContext: "agent start append", - })), }; const result = await resolvePromptBuildHookResult({ @@ -219,14 +158,10 @@ describe("resolvePromptBuildHookResult", () => { hookRunner, }); - expect(result.prependContext).toBe("prompt context\n\nagent start context"); - expect(result.appendContext).toBe("prompt append context\n\nagent start append context"); - expect(result.prependSystemContext).toBe( - `${wrappedPluginSystemContext("prompt prepend")}\n\n${wrappedPluginSystemContext("agent start prepend")}`, - ); - expect(result.appendSystemContext).toBe( - `${wrappedPluginSystemContext("prompt append")}\n\n${wrappedPluginSystemContext("agent start append")}`, - ); + expect(result.prependContext).toBe("prompt context"); + expect(result.appendContext).toBe("prompt append context"); + expect(result.prependSystemContext).toBe(wrappedPluginSystemContext("prompt prepend")); + expect(result.appendSystemContext).toBe(wrappedPluginSystemContext("prompt append")); }); it("applies heartbeat prompt contributions only during heartbeat turns", async () => { @@ -237,7 +172,6 @@ describe("resolvePromptBuildHookResult", () => { appendContext: "heartbeat append", })), runBeforePromptBuild: vi.fn(async () => undefined), - runBeforeAgentStart: vi.fn(async () => undefined), }; const heartbeatResult = await resolvePromptBuildHookResult({ diff --git a/src/agents/embedded-agent-runner/run/model-setup.ts b/src/agents/embedded-agent-runner/run/model-setup.ts index e1f5794ee296..a24753529631 100644 --- a/src/agents/embedded-agent-runner/run/model-setup.ts +++ b/src/agents/embedded-agent-runner/run/model-setup.ts @@ -202,7 +202,6 @@ export async function resolveEmbeddedRunModelSetup(params: { modelId, requestedModelId, modelSelectionChangedByHook, - beforeAgentStartResult: hookSelection.beforeAgentStartResult, requestStreamTransportOverrides, expectedHarnessArtifact, agentHarness, diff --git a/src/agents/embedded-agent-runner/run/run-attempt-dispatch.ts b/src/agents/embedded-agent-runner/run/run-attempt-dispatch.ts index 2a4cad490296..9fb893840add 100644 --- a/src/agents/embedded-agent-runner/run/run-attempt-dispatch.ts +++ b/src/agents/embedded-agent-runner/run/run-attempt-dispatch.ts @@ -55,7 +55,6 @@ type AttemptRuntime = { toolAuthProfileStore?: AuthProfileStore; modelRegistry: EmbeddedRunAttemptParams["modelRegistry"]; agentId: string; - beforeAgentStartResult: EmbeddedRunAttemptParams["beforeAgentStartResult"]; thinkLevel: EmbeddedRunAttemptParams["thinkLevel"]; fastMode: EmbeddedRunAttemptParams["fastMode"]; fastModeStartedAtMs?: number; @@ -266,7 +265,6 @@ export async function dispatchEmbeddedRunAttempt(input: { toolAuthProfileStore: runtime.toolAuthProfileStore, modelRegistry: runtime.modelRegistry, agentId: runtime.agentId, - beforeAgentStartResult: runtime.beforeAgentStartResult, thinkLevel: runtime.thinkLevel, onToolOutcome: control.onToolOutcome, allocateToolOutcomeOrdinal: control.allocateToolOutcomeOrdinal, diff --git a/src/agents/embedded-agent-runner/run/runtime-preparation.ts b/src/agents/embedded-agent-runner/run/runtime-preparation.ts index b3a4e2a5e347..455703eb6c0b 100644 --- a/src/agents/embedded-agent-runner/run/runtime-preparation.ts +++ b/src/agents/embedded-agent-runner/run/runtime-preparation.ts @@ -73,7 +73,6 @@ export async function prepareEmbeddedRunRuntime(input: { const { requestedModelId, modelSelectionChangedByHook, - beforeAgentStartResult, requestStreamTransportOverrides, expectedHarnessArtifact, nativeModelOwnedHarnessId, @@ -468,7 +467,6 @@ export async function prepareEmbeddedRunRuntime(input: { provider, modelId, requestedModelId, - beforeAgentStartResult, expectedHarnessArtifact, nativeModelOwned, model, diff --git a/src/agents/embedded-agent-runner/run/setup.test.ts b/src/agents/embedded-agent-runner/run/setup.test.ts index 272d85bf4181..5381448bdeed 100644 --- a/src/agents/embedded-agent-runner/run/setup.test.ts +++ b/src/agents/embedded-agent-runner/run/setup.test.ts @@ -138,7 +138,6 @@ describe("resolveHookModelSelection", () => { const hookRunner = { hasHooks: vi.fn(() => true), runBeforeModelResolve: vi.fn(), - runBeforeAgentStart: vi.fn(), }; await expect( @@ -153,11 +152,9 @@ describe("resolveHookModelSelection", () => { ).resolves.toEqual({ provider: "foreground-provider", modelId: "foreground-model", - beforeAgentStartResult: undefined, }); expect(hookRunner.hasHooks).not.toHaveBeenCalled(); expect(hookRunner.runBeforeModelResolve).not.toHaveBeenCalled(); - expect(hookRunner.runBeforeAgentStart).not.toHaveBeenCalled(); }); it("passes attachment metadata to before_model_resolve hooks", async () => { @@ -168,7 +165,6 @@ describe("resolveHookModelSelection", () => { providerOverride: "vision-provider", modelOverride: "vision-model", })), - runBeforeAgentStart: vi.fn(), }; const result = await resolveHookModelSelection({ @@ -184,7 +180,6 @@ describe("resolveHookModelSelection", () => { { prompt: "describe this image", attachments }, hookContext, ); - expect(hookRunner.runBeforeAgentStart).not.toHaveBeenCalled(); expect(result.provider).toBe("vision-provider"); expect(result.modelId).toBe("vision-model"); }); @@ -193,7 +188,6 @@ describe("resolveHookModelSelection", () => { const hookRunner = { hasHooks: vi.fn((hookName: string) => hookName === "before_model_resolve"), runBeforeModelResolve: vi.fn(async () => undefined), - runBeforeAgentStart: vi.fn(), }; await resolveHookModelSelection({ diff --git a/src/agents/embedded-agent-runner/run/setup.ts b/src/agents/embedded-agent-runner/run/setup.ts index 966261596cd0..588ab69cc5b7 100644 --- a/src/agents/embedded-agent-runner/run/setup.ts +++ b/src/agents/embedded-agent-runner/run/setup.ts @@ -5,7 +5,6 @@ import type { SessionEntry } from "../../../config/sessions/types.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import type { ProviderRuntimeModel } from "../../../plugins/provider-runtime-model.types.js"; import type { - PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveAttachment, PluginHookBeforeModelResolveEvent, } from "../../../plugins/types.js"; @@ -49,10 +48,6 @@ type HookRunnerLike = { input: PluginHookBeforeModelResolveEvent, context: HookContext, ): Promise<{ providerOverride?: string; modelOverride?: string } | undefined>; - runBeforeAgentStart( - input: { prompt: string }, - context: HookContext, - ): Promise; }; /** Durable harness sessions run only with their exact persisted identity and runtime lock. */ @@ -98,9 +93,7 @@ export function resolveAgentHarnessRunAdmissionError(params: { } /** - * Runs model-selection hooks before resolving the runtime model. The dedicated - * `before_model_resolve` hook wins over legacy `before_agent_start` overrides - * when both provide provider/model changes. + * Runs model-selection hooks before resolving the runtime model. */ export async function resolveHookModelSelection(params: { prompt: string; @@ -114,17 +107,13 @@ export async function resolveHookModelSelection(params: { let provider = params.provider; let modelId = params.modelId; if (params.modelSelectionLocked === true) { - return { provider, modelId, beforeAgentStartResult: undefined }; + return { provider, modelId }; } let modelResolveOverride: { providerOverride?: string; modelOverride?: string } | undefined; - let beforeAgentStartResult: PluginHookBeforeAgentStartResult | undefined; const hookRunner = params.hookRunner; // Run before_model_resolve hooks early so plugins can override the // provider/model before resolveModel(). - // - // Legacy compatibility: before_agent_start is also checked for override - // fields if present. New hook takes precedence when both are set. if (hookRunner?.hasHooks("before_model_resolve")) { try { const event: PluginHookBeforeModelResolveEvent = params.attachments @@ -136,24 +125,6 @@ export async function resolveHookModelSelection(params: { } } - if (hookRunner?.hasHooks("before_agent_start")) { - try { - beforeAgentStartResult = await hookRunner.runBeforeAgentStart( - { prompt: params.prompt }, - params.hookContext, - ); - modelResolveOverride = { - providerOverride: - modelResolveOverride?.providerOverride ?? beforeAgentStartResult?.providerOverride, - modelOverride: modelResolveOverride?.modelOverride ?? beforeAgentStartResult?.modelOverride, - }; - } catch (hookErr) { - log.warn( - `deprecated before_agent_start hook failed during model resolve: ${String(hookErr)}`, - ); - } - } - if (modelResolveOverride?.providerOverride) { provider = modelResolveOverride.providerOverride; log.info(`[hooks] provider overridden to ${provider}`); @@ -166,7 +137,6 @@ export async function resolveHookModelSelection(params: { return { provider, modelId, - beforeAgentStartResult, }; } diff --git a/src/agents/embedded-agent-runner/run/types.ts b/src/agents/embedded-agent-runner/run/types.ts index cf73d06960d2..56b6704c0120 100644 --- a/src/agents/embedded-agent-runner/run/types.ts +++ b/src/agents/embedded-agent-runner/run/types.ts @@ -10,7 +10,6 @@ import type { import type { ContextEngine, ContextEnginePromptCacheInfo } from "../../../context-engine/types.js"; import type { DiagnosticTraceContext } from "../../../infra/diagnostic-trace-context.js"; import type { AssistantMessage, Model } from "../../../llm/types.js"; -import type { PluginHookBeforeAgentStartResult } from "../../../plugins/hook-before-agent-start.types.js"; import type { AgentHarnessTaskRuntimeScope } from "../../../tasks/agent-harness-task-runtime-scope.js"; import type { AcceptedSessionSpawn } from "../../accepted-session-spawn.js"; import type { ToolOutcomeObserver } from "../../agent-tools.before-tool-call.js"; @@ -161,7 +160,6 @@ export type EmbeddedRunAttemptParams = EmbeddedRunAttemptBase & { fastMode?: EmbeddedRunFastModeParam; /** True when this attempt is running the auto fast-mode policy. */ fastModeAuto?: boolean; - beforeAgentStartResult?: PluginHookBeforeAgentStartResult; beforeAgentFinalizeRevisionAttempts?: number; maxBeforeAgentFinalizeRevisions?: number; }; diff --git a/src/agents/embedded-agent-runner/system-prompt.test.ts b/src/agents/embedded-agent-runner/system-prompt.test.ts index 202439a8f166..979d42252c98 100644 --- a/src/agents/embedded-agent-runner/system-prompt.test.ts +++ b/src/agents/embedded-agent-runner/system-prompt.test.ts @@ -3,7 +3,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { clearMemoryPluginState, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, } from "../../plugins/memory-state.test-fixtures.js"; import type { AgentSession } from "../sessions/index.js"; import { applySystemPromptToSession, buildEmbeddedSystemPrompt } from "./system-prompt.js"; @@ -241,7 +241,7 @@ describe("buildEmbeddedSystemPrompt", () => { }); it("can omit base memory guidance for non-legacy context engines", () => { - registerMemoryPromptSection(() => ["## Memory Recall", "Use memory carefully.", ""]); + registerTestMemoryPromptBuilder(() => ["## Memory Recall", "Use memory carefully.", ""]); const prompt = buildEmbeddedSystemPrompt({ workspaceDir: "/tmp/openclaw", diff --git a/src/agents/harness/context-engine-lifecycle.test.ts b/src/agents/harness/context-engine-lifecycle.test.ts index b6bd8d41d6cf..79a234ed2c61 100644 --- a/src/agents/harness/context-engine-lifecycle.test.ts +++ b/src/agents/harness/context-engine-lifecycle.test.ts @@ -6,7 +6,10 @@ import { CODEX_APP_SERVER_CONTEXT_ENGINE_HOST, OPENCLAW_EMBEDDED_CONTEXT_ENGINE_HOST, } from "../../context-engine/host-compat.js"; -import { registerContextEngine, resolveContextEngine } from "../../context-engine/registry.js"; +import { + registerContextEngineForOwner, + resolveContextEngine, +} from "../../context-engine/registry.js"; import { buildContextEngineRuntimeSettings } from "../../context-engine/runtime-settings.js"; import type { ContextEngine, @@ -16,7 +19,7 @@ import type { import { clearMemoryPluginState, registerMemoryPromptPreparation, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, } from "../../plugins/memory-state.test-fixtures.js"; import { compactContextEngineWithSafetyTimeout } from "../embedded-agent-runner/compaction-safety-timeout.js"; import { OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE } from "../internal-runtime-context.js"; @@ -26,6 +29,15 @@ import { finalizeHarnessContextEngineTurn, } from "./context-engine-lifecycle.js"; +function registerTestContextEngine( + id: string, + factory: Parameters[1], +) { + return registerContextEngineForOwner(id, factory, `test:${id}`, { + allowSameOwnerRefresh: true, + }); +} + function textMessage(role: "user" | "assistant", text: string, timestamp: number): AgentMessage { return { role, @@ -80,7 +92,7 @@ describe("harness context engine lifecycle", () => { `sandboxed=${sandboxed}`, "", ]); - registerMemoryPromptSection(() => ["## Memory Recall", ""]); + registerTestMemoryPromptBuilder(() => ["## Memory Recall", ""]); registerMemoryPromptPreparation("memory-wiki", prepare); const availableTools = new Set(["wiki_search"]); const assemble = vi.fn(async (params: Parameters[0]) => ({ @@ -240,7 +252,7 @@ describe("harness context engine lifecycle", () => { return { ok: true, compacted: false }; }), }); - registerContextEngine(engineId, () => engine); + registerTestContextEngine(engineId, () => engine); const configuredEngine = await resolveContextEngine({ plugins: { slots: { contextEngine: engineId } }, }); diff --git a/src/agents/harness/prompt-compaction-hook-helpers.test.ts b/src/agents/harness/prompt-compaction-hook-helpers.test.ts index e4e210862a0a..bba17eb6b379 100644 --- a/src/agents/harness/prompt-compaction-hook-helpers.test.ts +++ b/src/agents/harness/prompt-compaction-hook-helpers.test.ts @@ -26,48 +26,6 @@ describe("resolveAgentHarnessBeforePromptBuildResult", () => { }); }); - it("uses precomputed agent-start context without a global hook runner", async () => { - const result = await resolveAgentHarnessBeforePromptBuildResult({ - prompt: "hello", - developerInstructions: "base instructions", - messages: [], - ctx: { - agentId: "agent-1", - sessionKey: "session-1", - workspaceDir: "/workspace", - }, - beforeAgentStartResult: { - prependContext: "cached context", - systemPrompt: "cached instructions", - }, - }); - - expect(result).toEqual({ - prompt: "cached context\n\nhello", - developerInstructions: "cached instructions", - promptInputRange: { start: 16, end: 21 }, - }); - }); - - it("keeps an empty input range between prepended and appended context", async () => { - const result = await resolveAgentHarnessBeforePromptBuildResult({ - prompt: "", - developerInstructions: "base instructions", - messages: [], - ctx: {}, - beforeAgentStartResult: { - appendContext: "appended context", - prependContext: "prepended context", - }, - }); - - expect(result).toEqual({ - prompt: "prepended context\n\nappended context", - developerInstructions: "base instructions", - promptInputRange: { start: 17, end: 17 }, - }); - }); - it("runs heartbeat_prompt_contribution on a heartbeat turn and prepends its contribution", async () => { initializeGlobalHookRunner( createMockPluginRegistry([ @@ -108,13 +66,6 @@ describe("resolveAgentHarnessBeforePromptBuildResult", () => { return { prependContext: "prompt context" }; }, }, - { - hookName: "before_agent_start", - handler: () => { - calls.push("before_agent_start"); - return { prependContext: "agent-start context" }; - }, - }, ]), ); @@ -125,10 +76,8 @@ describe("resolveAgentHarnessBeforePromptBuildResult", () => { ctx: { trigger: "heartbeat", agentId: "agent-1", sessionKey: "session-1" }, }); - expect(calls).toEqual(["heartbeat", "before_prompt_build", "before_agent_start"]); - expect(result.prompt).toBe( - "heartbeat context\n\nprompt context\n\nagent-start context\n\nhello", - ); + expect(calls).toEqual(["heartbeat", "before_prompt_build"]); + expect(result.prompt).toBe("heartbeat context\n\nprompt context\n\nhello"); }); it("skips heartbeat_prompt_contribution off a heartbeat turn", async () => { diff --git a/src/agents/harness/prompt-compaction-hook-helpers.ts b/src/agents/harness/prompt-compaction-hook-helpers.ts index 0364807bcbbc..69951caa3f3a 100644 --- a/src/agents/harness/prompt-compaction-hook-helpers.ts +++ b/src/agents/harness/prompt-compaction-hook-helpers.ts @@ -6,10 +6,7 @@ */ import { createSubsystemLogger } from "../../logging/subsystem.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; -import type { - PluginHookBeforeAgentStartResult, - PluginHookBeforePromptBuildResult, -} from "../../plugins/types.js"; +import type { PluginHookBeforePromptBuildResult } from "../../plugins/types.js"; import { joinPresentTextSegments } from "../../shared/text/join-segments.js"; import type { BootstrapContextRunKind } from "../bootstrap-mode.js"; import { wrapPluginSystemContextSection } from "../hook-system-context-boundary.js"; @@ -32,11 +29,9 @@ export async function resolveAgentHarnessBeforePromptBuildResult(params: { developerInstructions: string; messages: unknown[]; ctx: AgentHarnessHookContext; - beforeAgentStartResult?: PluginHookBeforeAgentStartResult; bootstrapContextRunKind?: BootstrapContextRunKind; }): Promise { const hookRunner = getGlobalHookRunner(); - const hasPrecomputedBeforeAgentStartResult = "beforeAgentStartResult" in params; // heartbeat_prompt_contribution fires only on heartbeat turns. Harness runtimes // (e.g. the Codex app-server) build the prompt through this helper rather than // the embedded runner's resolvePromptBuildHookResult, so the hook must run from @@ -45,12 +40,7 @@ export async function resolveAgentHarnessBeforePromptBuildResult(params: { params.ctx.trigger === "heartbeat" && params.bootstrapContextRunKind !== "commitment-only"; const hasHeartbeatContribution = isHeartbeatTurn && Boolean(hookRunner?.hasHooks("heartbeat_prompt_contribution")); - if ( - !hasPrecomputedBeforeAgentStartResult && - !hasHeartbeatContribution && - !hookRunner?.hasHooks("before_prompt_build") && - !hookRunner?.hasHooks("before_agent_start") - ) { + if (!hasHeartbeatContribution && !hookRunner?.hasHooks("before_prompt_build")) { return { prompt: params.prompt, developerInstructions: params.developerInstructions, @@ -82,41 +72,23 @@ export async function resolveAgentHarnessBeforePromptBuildResult(params: { }) : undefined; - // Support the newer before_prompt_build hook plus the deprecated - // before_agent_start hook during the prompt-build migration window. const promptBuildResult = hookRunner?.hasHooks("before_prompt_build") ? await hookRunner.runBeforePromptBuild(promptEvent, hookCtx).catch((error: unknown) => { log.warn(`before_prompt_build hook failed: ${String(error)}`); return undefined; }) : undefined; - // The runner resolves before_agent_start during model selection. Reuse that - // result so legacy one-shot hooks do not run twice for the same turn. - const beforeAgentStartResult = hasPrecomputedBeforeAgentStartResult - ? params.beforeAgentStartResult - : hookRunner?.hasHooks("before_agent_start") - ? await hookRunner.runBeforeAgentStart(promptEvent, hookCtx).catch((error: unknown) => { - log.warn( - `deprecated before_agent_start hook failed during prompt build: ${String(error)}`, - ); - return undefined; - }) - : undefined; - const systemPrompt = resolvePromptBuildSystemPrompt({ developerInstructions: params.developerInstructions, promptBuildResult, - beforeAgentStartResult, }); const promptPrefix = joinPresentTextSegments([ heartbeatResult?.prependContext, promptBuildResult?.prependContext, - beforeAgentStartResult?.prependContext, ]); const promptSuffix = joinPresentTextSegments([ heartbeatResult?.appendContext, promptBuildResult?.appendContext, - beforeAgentStartResult?.appendContext, ]); const prompt = joinPresentTextSegments([promptPrefix, params.prompt, promptSuffix]) ?? params.prompt; @@ -131,10 +103,8 @@ export async function resolveAgentHarnessBeforePromptBuildResult(params: { developerInstructions: joinPresentTextSegments([ wrapPluginSystemContextSection(promptBuildResult?.prependSystemContext), - wrapPluginSystemContextSection(beforeAgentStartResult?.prependSystemContext), systemPrompt, wrapPluginSystemContextSection(promptBuildResult?.appendSystemContext), - wrapPluginSystemContextSection(beforeAgentStartResult?.appendSystemContext), ]) ?? systemPrompt, promptInputRange: { start: promptInputStart, @@ -146,14 +116,10 @@ export async function resolveAgentHarnessBeforePromptBuildResult(params: { function resolvePromptBuildSystemPrompt(params: { developerInstructions: string; promptBuildResult?: PluginHookBeforePromptBuildResult; - beforeAgentStartResult?: PluginHookBeforeAgentStartResult; }): string { if (typeof params.promptBuildResult?.systemPrompt === "string") { return params.promptBuildResult.systemPrompt; } - if (typeof params.beforeAgentStartResult?.systemPrompt === "string") { - return params.beforeAgentStartResult.systemPrompt; - } return params.developerInstructions; } diff --git a/src/agents/harness/tool-result-middleware.test.ts b/src/agents/harness/tool-result-middleware.test.ts index 617adcbe8031..f8082cbf9456 100644 --- a/src/agents/harness/tool-result-middleware.test.ts +++ b/src/agents/harness/tool-result-middleware.test.ts @@ -641,7 +641,7 @@ describe("createAgentToolResultMiddlewareRunner", () => { (eventValue, ctx) => ({ result: { content: [{ type: "text", text: "compacted" }], - details: { compacted: true, runtime: ctx.runtime, harness: ctx.harness }, + details: { compacted: true, runtime: ctx.runtime }, }, }), ]); @@ -654,6 +654,6 @@ describe("createAgentToolResultMiddlewareRunner", () => { }); expect(result.content).toEqual([{ type: "text", text: "compacted" }]); - expect(result.details).toEqual({ compacted: true, runtime: "codex", harness: "codex" }); + expect(result.details).toEqual({ compacted: true, runtime: "codex" }); }); }); diff --git a/src/agents/harness/tool-result-middleware.ts b/src/agents/harness/tool-result-middleware.ts index d8317e6f767e..e6d9dc69f72d 100644 --- a/src/agents/harness/tool-result-middleware.ts +++ b/src/agents/harness/tool-result-middleware.ts @@ -481,7 +481,6 @@ export function createAgentToolResultMiddlewareRunner( ctx: AgentToolResultMiddlewareContext, handlers?: AgentToolResultMiddleware[], ) { - const middlewareContext = { ...ctx, harness: ctx.harness ?? ctx.runtime }; let resolvedHandlers = handlers; const resolvedHandlersLoader = createLazyPromiseLoader(async () => { const { loadAgentToolResultMiddlewaresForRuntime } = @@ -518,7 +517,7 @@ export function createAgentToolResultMiddlewareRunner( let current = sanitizeToolResultForMiddleware(event.result); for (const handler of handlersForRun) { try { - const next = await handler({ ...event, result: current }, middlewareContext); + const next = await handler({ ...event, result: current }, ctx); // Middleware may mutate event.result in place for legacy runtime parity. // Validate the current object after every handler so in-place writes // cannot bypass the same shape and size bounds as returned results. diff --git a/src/agents/model-auth-env.provider-aliases.test.ts b/src/agents/model-auth-env.provider-aliases.test.ts index f48cf7b327c6..4fb1564fd746 100644 --- a/src/agents/model-auth-env.provider-aliases.test.ts +++ b/src/agents/model-auth-env.provider-aliases.test.ts @@ -25,8 +25,8 @@ const pluginMetadataMocks = vi.hoisted(() => { providerAuthAliases: { "cloud-alias": "external-cloud", }, - providerAuthEnvVars: { - "external-cloud": ["EXTERNAL_CLOUD_API_KEY"], + setup: { + providers: [{ id: "external-cloud", envVars: ["EXTERNAL_CLOUD_API_KEY"] }], }, }, ], diff --git a/src/agents/model-auth.test.ts b/src/agents/model-auth.test.ts index 1a16ffda527e..25c41e18f38f 100644 --- a/src/agents/model-auth.test.ts +++ b/src/agents/model-auth.test.ts @@ -28,8 +28,8 @@ vi.mock("../plugins/plugin-registry.js", () => ({ { origin: "bundled", nonSecretAuthMarkers: ["gcp-vertex-credentials", "ollama-local"], - providerAuthEnvVars: { - ollama: ["OLLAMA_API_KEY"], + setup: { + providers: [{ id: "ollama", envVars: ["OLLAMA_API_KEY"] }], }, }, ], diff --git a/src/agents/model-provider-auth.worker.test.ts b/src/agents/model-provider-auth.worker.test.ts index 6b56d1b78607..b4f8c16eb192 100644 --- a/src/agents/model-provider-auth.worker.test.ts +++ b/src/agents/model-provider-auth.worker.test.ts @@ -49,7 +49,6 @@ describe("provider auth warm worker", () => { await withEnvAsync( { - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", OPENCLAW_STATE_DIR: path.join(root, "state"), }, async () => { diff --git a/src/agents/models-config.providers.auth-aliases.test.ts b/src/agents/models-config.providers.auth-aliases.test.ts index 2ff6d0f3e3c9..128aa0e4cf2c 100644 --- a/src/agents/models-config.providers.auth-aliases.test.ts +++ b/src/agents/models-config.providers.auth-aliases.test.ts @@ -10,8 +10,8 @@ type MockManifestRegistry = { providers: string[]; cliBackends: string[]; rootDir: string; - providerAuthEnvVars?: Record; providerAuthAliases?: Record; + setup?: { providers: Array<{ id: string; envVars: string[] }> }; }>; diagnostics: unknown[]; }; @@ -24,8 +24,8 @@ const createFixtureProviderRegistry = (): MockManifestRegistry => ({ providers: ["fixture-provider"], cliBackends: [], rootDir: "/tmp/openclaw-test/fixture-provider", - providerAuthEnvVars: { - "fixture-provider": ["FIXTURE_PROVIDER_API_KEY"], + setup: { + providers: [{ id: "fixture-provider", envVars: ["FIXTURE_PROVIDER_API_KEY"] }], }, providerAuthAliases: { "fixture-provider-plan": "fixture-provider", @@ -44,8 +44,8 @@ const loadPluginManifestRegistry = vi.hoisted(() => providers: ["fixture-provider"], cliBackends: [], rootDir: "/tmp/openclaw-test/fixture-provider", - providerAuthEnvVars: { - "fixture-provider": ["FIXTURE_PROVIDER_API_KEY"], + setup: { + providers: [{ id: "fixture-provider", envVars: ["FIXTURE_PROVIDER_API_KEY"] }], }, providerAuthAliases: { "fixture-provider-plan": "fixture-provider", @@ -164,8 +164,8 @@ describe("provider auth aliases", () => { providers: ["openai"], cliBackends: [], rootDir: "/tmp/openclaw-test/openai", - providerAuthEnvVars: { - openai: ["OPENAI_API_KEY"], + setup: { + providers: [{ id: "openai", envVars: ["OPENAI_API_KEY"] }], }, providerAuthAliases: {}, }, @@ -222,8 +222,8 @@ describe("provider auth aliases", () => { providers: ["openai"], cliBackends: [], rootDir: "/tmp/openclaw-test/openai", - providerAuthEnvVars: { - openai: ["OPENAI_API_KEY"], + setup: { + providers: [{ id: "openai", envVars: ["OPENAI_API_KEY"] }], }, providerAuthAliases: { "openai-compatible": "openai", diff --git a/src/agents/models-config.providers.implicit.ts b/src/agents/models-config.providers.implicit.ts index 66d59cdac0c1..1c21c26abe4b 100644 --- a/src/agents/models-config.providers.implicit.ts +++ b/src/agents/models-config.providers.implicit.ts @@ -360,15 +360,13 @@ function hasProviderWildcardVisibility(params: { function hasRuntimeProviderCatalog( provider: import("../plugins/types.js").ProviderPlugin, ): boolean { - return ( - typeof provider.catalog?.run === "function" || typeof provider.discovery?.run === "function" - ); + return typeof provider.catalog?.run === "function"; } async function resolvePluginImplicitProviders( ctx: ImplicitProviderContext, providers: import("../plugins/types.js").ProviderPlugin[], - order: import("../plugins/types.js").ProviderDiscoveryOrder, + order: import("../plugins/types.js").ProviderCatalogOrder, ): Promise | undefined> { const byOrder = groupPluginDiscoveryProvidersByOrder(providers); const discovered: Record = {}; diff --git a/src/agents/openclaw-tools.media-factory-plan.test.ts b/src/agents/openclaw-tools.media-factory-plan.test.ts index a2fc500d4b37..cee01ee6256e 100644 --- a/src/agents/openclaw-tools.media-factory-plan.test.ts +++ b/src/agents/openclaw-tools.media-factory-plan.test.ts @@ -53,7 +53,6 @@ function createPlugin(params: { imageGenerationProviderMetadata?: PluginManifestRecord["imageGenerationProviderMetadata"]; videoGenerationProviderMetadata?: PluginManifestRecord["videoGenerationProviderMetadata"]; musicGenerationProviderMetadata?: PluginManifestRecord["musicGenerationProviderMetadata"]; - providerAuthEnvVars?: PluginManifestRecord["providerAuthEnvVars"]; setupProviders?: Array<{ id: string; envVars?: string[] }>; }): PluginManifestRecord { return { @@ -71,7 +70,6 @@ function createPlugin(params: { imageGenerationProviderMetadata: params.imageGenerationProviderMetadata, videoGenerationProviderMetadata: params.videoGenerationProviderMetadata, musicGenerationProviderMetadata: params.musicGenerationProviderMetadata, - providerAuthEnvVars: params.providerAuthEnvVars, setup: params.setupProviders ? { providers: params.setupProviders } : undefined, }; } @@ -521,16 +519,19 @@ describe("optional media tool factory planning", () => { }); }); - it("keeps manifest provider auth env aliases on the music factory path", () => { + it("keeps manifest setup provider env vars on the music factory path", () => { const config: OpenClawConfig = {}; installSnapshot(config, [ createPlugin({ id: "minimax", contracts: { musicGenerationProviders: ["minimax", "minimax-portal"] }, - providerAuthEnvVars: { - minimax: ["MINIMAX_CODE_PLAN_KEY", "MINIMAX_CODING_API_KEY", "MINIMAX_API_KEY"], - "minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"], - }, + setupProviders: [ + { + id: "minimax", + envVars: ["MINIMAX_CODE_PLAN_KEY", "MINIMAX_CODING_API_KEY", "MINIMAX_API_KEY"], + }, + { id: "minimax-portal", envVars: ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"] }, + ], }), ]); vi.stubEnv("MINIMAX_API_KEY", "minimax-key"); diff --git a/src/agents/sessions/extensions/loader.test.ts b/src/agents/sessions/extensions/loader.test.ts index b181c486672b..ab99662a1af1 100644 --- a/src/agents/sessions/extensions/loader.test.ts +++ b/src/agents/sessions/extensions/loader.test.ts @@ -18,22 +18,17 @@ describe("loadExtensionsCached", () => { beforeAll(async () => { clearExtensionCache(); - // Extensions import both public SDK helpers and runtime helper subpaths; the - // loader must route those aliases without package-manager involvement. + // Extensions import public SDK helpers through package subpaths; the loader + // must route those aliases without package-manager involvement. const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-sdk-")); tempDirs.push(dir); const extensionPath = join(dir, "extension.ts"); await writeFile( extensionPath, ` -import { createAssistantMessageEventStream } from "openclaw/plugin-sdk/llm"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime"; export default async function(api) { - const stream = createAssistantMessageEventStream(); - if (!stream || typeof stream.result !== "function") { - throw new Error("generic LLM helper unavailable"); - } if (normalizeLowercaseStringOrEmpty(" MIXED ") !== "mixed") { throw new Error("generic sdk subpath unavailable"); } @@ -48,7 +43,7 @@ export default async function(api) { result = await loadExtensionsCached([extensionPath], dir); }); - it("resolves plugin SDK subpaths in jiti-loaded extensions", () => { + it("resolves a public plugin SDK subpath in jiti-loaded extensions", () => { expect(result.errors).toEqual([]); expect(result.extensions).toHaveLength(1); expect(result.extensions[0]?.commands.has("sdk-subpath-probe")).toBe(true); diff --git a/src/agents/subagent-announce.live.test.ts b/src/agents/subagent-announce.live.test.ts index 40dc566b9984..9484b3d18d8d 100644 --- a/src/agents/subagent-announce.live.test.ts +++ b/src/agents/subagent-announce.live.test.ts @@ -295,7 +295,6 @@ describeLive("subagent announce live", () => { OPENCLAW_SKIP_CANVAS_HOST: "1", OPENCLAW_TEST_MINIMAL_GATEWAY: "1", OPENCLAW_DISABLE_BUNDLED_PLUGINS: undefined, - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"), OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1", OPENCLAW_PLUGIN_CATALOG_PATHS: undefined, @@ -489,7 +488,6 @@ describeLive("subagent announce live", () => { OPENCLAW_SKIP_CANVAS_HOST: "1", OPENCLAW_TEST_MINIMAL_GATEWAY: "1", OPENCLAW_DISABLE_BUNDLED_PLUGINS: undefined, - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"), OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1", OPENCLAW_PLUGIN_CATALOG_PATHS: undefined, @@ -685,7 +683,6 @@ describeLive("subagent announce live", () => { OPENCLAW_SKIP_CANVAS_HOST: "1", OPENCLAW_TEST_MINIMAL_GATEWAY: "1", OPENCLAW_DISABLE_BUNDLED_PLUGINS: undefined, - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"), OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1", OPENCLAW_PLUGIN_CATALOG_PATHS: undefined, diff --git a/src/agents/system-prompt.memory.test.ts b/src/agents/system-prompt.memory.test.ts index 21f2f83d0c91..02c4bd1667e7 100644 --- a/src/agents/system-prompt.memory.test.ts +++ b/src/agents/system-prompt.memory.test.ts @@ -4,7 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { clearMemoryPluginState, registerMemoryPromptPreparation, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, } from "../plugins/memory-state.test-fixtures.js"; import { prepareAgentMemoryPrompt } from "./memory-prompt-prepare.js"; import { buildAgentSystemPrompt } from "./system-prompt.js"; @@ -15,7 +15,7 @@ describe("buildAgentSystemPrompt memory guidance", () => { }); it("can suppress base memory guidance so context engines own memory prompt assembly", () => { - registerMemoryPromptSection(() => ["## Memory Recall", "Use memory carefully.", ""]); + registerTestMemoryPromptBuilder(() => ["## Memory Recall", "Use memory carefully.", ""]); const promptWithMemory = buildAgentSystemPrompt({ workspaceDir: "/tmp/openclaw", @@ -33,7 +33,7 @@ describe("buildAgentSystemPrompt memory guidance", () => { let observedContext: | { agentId?: string; agentSessionKey?: string; sandboxed?: boolean } | undefined; - registerMemoryPromptSection((context) => { + registerTestMemoryPromptBuilder((context) => { observedContext = context; return [ "## Agent Memory", diff --git a/src/agents/tools/web-search-provider-config.ts b/src/agents/tools/web-search-provider-config.ts index e4b4092f58d4..65bd285dc7fd 100644 --- a/src/agents/tools/web-search-provider-config.ts +++ b/src/agents/tools/web-search-provider-config.ts @@ -1,7 +1,7 @@ /** * Provider-scoped web-search config helpers. * - * Bridges legacy top-level credentials with plugin-owned provider configuration. + * Projects plugin-owned provider configuration into the tool-local search shape. */ import { resolvePluginWebSearchConfig } from "../../config/plugin-web-search-config.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -46,37 +46,26 @@ export function setScopedCredentialValue( (scoped as Record).apiKey = value; } -/** Merges plugin web-search config into a provider-scoped legacy-compatible shape. */ +/** Projects plugin web-search config into the provider-scoped tool-local shape. */ export function mergeScopedSearchConfig( searchConfig: Record | undefined, key: string, pluginConfig: Record | undefined, options?: { mirrorApiKeyToTopLevel?: boolean }, ): Record | undefined { + const next: Record = { ...searchConfig }; + delete next.apiKey; + if (isLegacyWebSearchProviderConfigKey(key)) { + delete next[key]; + } if (!pluginConfig) { - return searchConfig; + return Object.keys(next).length > 0 ? next : undefined; } - const currentScoped = - searchConfig?.[key] && - typeof searchConfig[key] === "object" && - !Array.isArray(searchConfig[key]) - ? (searchConfig[key] as Record) - : {}; - const next: Record = { ...searchConfig }; - const existingDescriptor = searchConfig - ? Object.getOwnPropertyDescriptor(searchConfig, key) - : undefined; - const shouldHideRuntimeInjectedLegacyShape = - isLegacyWebSearchProviderConfigKey(key) && existingDescriptor === undefined; - - // Runtime-injected legacy provider keys should be addressable but absent from JSON writes. + // Provider-local projections are runtime-only and must never reserialize into tools.web.search. Object.defineProperty(next, key, { - value: { - ...currentScoped, - ...pluginConfig, - }, - enumerable: !shouldHideRuntimeInjectedLegacyShape, + value: { ...pluginConfig }, + enumerable: false, configurable: true, writable: true, }); diff --git a/src/agents/tools/web-search.late-bind.test.ts b/src/agents/tools/web-search.late-bind.test.ts index 7853c3be45f1..42ac9f8fc7c0 100644 --- a/src/agents/tools/web-search.late-bind.test.ts +++ b/src/agents/tools/web-search.late-bind.test.ts @@ -183,7 +183,7 @@ describe("web_search late-bound runtime fallback", () => { ownerKind: "capability", ownerId: "web-search:brave", state: "unavailable", - paths: ["tools.web.search.brave.apiKey"], + paths: ["plugins.entries.brave.config.webSearch.apiKey"], refKeys: ["env:default:MISSING_BRAVE_KEY"], reason: "missing test ref", }, diff --git a/src/agents/tools/web-search.test.ts b/src/agents/tools/web-search.test.ts index 76bc4218c55a..aadb89716209 100644 --- a/src/agents/tools/web-search.test.ts +++ b/src/agents/tools/web-search.test.ts @@ -980,36 +980,35 @@ describe("web_search unsupported filter response", () => { }); describe("web_search scoped config merge", () => { - it("returns the original config when no plugin config exists", () => { + it("drops retired provider config when no plugin config exists", () => { const searchConfig = { provider: "grok", grok: { model: "grok-4-1-fast" } }; - expect(mergeScopedSearchConfig(searchConfig, "grok", undefined)).toBe(searchConfig); + expect(mergeScopedSearchConfig(searchConfig, "grok", undefined)).toEqual({ provider: "grok" }); }); - it("merges plugin config into the scoped provider object", () => { - expect( - mergeScopedSearchConfig({ provider: "grok", grok: { model: "old-model" } }, "grok", { + it("projects plugin config into a runtime-only provider object", () => { + const merged = mergeScopedSearchConfig( + { provider: "grok", grok: { model: "old-model" } }, + "grok", + { model: "new-model", apiKey: "xai-test-key", - }), - ).toEqual({ - provider: "grok", - grok: { model: "new-model", apiKey: "xai-test-key" }, - }); + }, + ); + + expect(merged?.grok).toEqual({ model: "new-model", apiKey: "xai-test-key" }); + expect(Object.keys(merged ?? {})).toEqual(["provider"]); }); it("can mirror the plugin apiKey to the top level config", () => { - expect( - mergeScopedSearchConfig( - { provider: "brave", brave: { count: 5 } }, - "brave", - { apiKey: "brave-test-key" }, - { mirrorApiKeyToTopLevel: true }, - ), - ).toEqual({ - provider: "brave", - apiKey: "brave-test-key", - brave: { count: 5, apiKey: "brave-test-key" }, - }); + const merged = mergeScopedSearchConfig( + { provider: "brave", brave: { count: 5 } }, + "brave", + { apiKey: "brave-test-key" }, + { mirrorApiKeyToTopLevel: true }, + ); + + expect(merged).toEqual({ provider: "brave", apiKey: "brave-test-key" }); + expect(merged?.brave).toEqual({ apiKey: "brave-test-key" }); }); it("keeps mirrored Brave plugin config runtime-only when newly injected", () => { diff --git a/src/agents/xai.live.test.ts b/src/agents/xai.live.test.ts index fe87e5044931..4f78f56eeb58 100644 --- a/src/agents/xai.live.test.ts +++ b/src/agents/xai.live.test.ts @@ -224,12 +224,20 @@ describeLive("xai live", () => { await runXaiLiveCase("web-search", async () => { const tool = createWebSearchTool({ config: { + plugins: { + entries: { + xai: { + config: { + webSearch: { model: "grok-4.3" }, + }, + }, + }, + }, tools: { web: { search: { provider: "grok", timeoutSeconds: XAI_WEB_SEARCH_LIVE_TIMEOUT_SECONDS, - grok: { model: "grok-4.3" }, }, }, }, diff --git a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts index 2736c5e9160c..ed814788e6ad 100644 --- a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts +++ b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts @@ -67,8 +67,17 @@ function createThinkingPolicyProvider( id: providerId, label: providerId, auth: [], - supportsXHighThinking: ({ modelId }) => - xhighModelIds.includes(normalizeLowercaseStringOrEmpty(modelId)), + resolveThinkingProfile: ({ modelId }) => ({ + levels: [ + { id: "off" }, + { id: "low" }, + { id: "medium" }, + { id: "high" }, + ...(xhighModelIds.includes(normalizeLowercaseStringOrEmpty(modelId)) + ? [{ id: "xhigh" as const }] + : []), + ], + }), }; } diff --git a/src/auto-reply/thinking.test.ts b/src/auto-reply/thinking.test.ts index 101ee17ebaea..5fab458f50d1 100644 --- a/src/auto-reply/thinking.test.ts +++ b/src/auto-reply/thinking.test.ts @@ -2,17 +2,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const providerRuntimeMocks = vi.hoisted(() => ({ - resolveProviderBinaryThinking: vi.fn(), - resolveProviderDefaultThinkingLevel: vi.fn(), resolveProviderThinkingProfile: vi.fn(), - resolveProviderXHighThinking: vi.fn(), })); vi.mock("../plugins/provider-thinking.js", () => ({ - resolveProviderBinaryThinking: providerRuntimeMocks.resolveProviderBinaryThinking, - resolveProviderDefaultThinkingLevel: providerRuntimeMocks.resolveProviderDefaultThinkingLevel, resolveProviderThinkingProfile: providerRuntimeMocks.resolveProviderThinkingProfile, - resolveProviderXHighThinking: providerRuntimeMocks.resolveProviderXHighThinking, })); const { @@ -29,14 +23,8 @@ const { } = await import("./thinking.js"); beforeEach(() => { - providerRuntimeMocks.resolveProviderBinaryThinking.mockReset(); - providerRuntimeMocks.resolveProviderBinaryThinking.mockReturnValue(undefined); - providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockReset(); - providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined); providerRuntimeMocks.resolveProviderThinkingProfile.mockReset(); providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue(undefined); - providerRuntimeMocks.resolveProviderXHighThinking.mockReset(); - providerRuntimeMocks.resolveProviderXHighThinking.mockReturnValue(undefined); }); describe("normalizeThinkLevel", () => { @@ -86,25 +74,30 @@ describe("normalizeThinkLevel", () => { }); describe("listThinkingLevels", () => { - it("uses provider runtime hooks for xhigh support", () => { - providerRuntimeMocks.resolveProviderXHighThinking.mockReturnValue(true); + it("uses provider thinking profiles for xhigh support", () => { + providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({ + levels: [{ id: "off" }, { id: "low" }, { id: "xhigh" }], + }); expect(listThinkingLevels("demo", "demo-model")).toContain("xhigh"); }); - it("uses provider runtime hooks for xhigh labels", () => { - providerRuntimeMocks.resolveProviderXHighThinking.mockReturnValue(true); + it("uses provider thinking profiles for xhigh labels", () => { + providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({ + levels: [{ id: "off" }, { id: "low" }, { id: "xhigh" }], + }); expect(listThinkingLevelLabels("demo", "demo-model")).toContain("xhigh"); }); it("includes xhigh for provider-advertised models", () => { - providerRuntimeMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) => - (provider === "openai" && - ["gpt-5.4", "gpt-5.4-pro", "gpt-5.3-codex-spark"].includes(context.modelId)) || - (provider === "github-copilot" && context.modelId === "gpt-5.4") - ? true - : undefined, + providerRuntimeMocks.resolveProviderThinkingProfile.mockImplementation( + ({ provider, context }) => + (provider === "openai" && + ["gpt-5.4", "gpt-5.4-pro", "gpt-5.3-codex-spark"].includes(context.modelId)) || + (provider === "github-copilot" && context.modelId === "gpt-5.4") + ? { levels: [{ id: "off" }, { id: "low" }, { id: "xhigh" }] } + : undefined, ); for (const [provider, model] of [ @@ -186,13 +179,11 @@ describe("listThinkingLevels", () => { ]); }); - it("uses provider thinking profiles ahead of legacy hooks", () => { + it("uses provider thinking profiles as the canonical policy", () => { providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({ levels: [{ id: "off" }, { id: "low", label: "on" }], defaultLevel: "off", }); - providerRuntimeMocks.resolveProviderXHighThinking.mockReturnValue(true); - expect(listThinkingLevels("demo", "demo-model")).toEqual(["off", "low"]); expect(listThinkingLevelLabels("demo", "demo-model")).toEqual(["off", "on"]); }); @@ -611,7 +602,12 @@ describe("listThinkingLevels", () => { }); it("does not let catalog xhigh compat override binary thinking providers", () => { - providerRuntimeMocks.resolveProviderBinaryThinking.mockReturnValue(true); + providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({ + levels: [ + { id: "off", label: "off" }, + { id: "low", label: "on" }, + ], + }); const catalog = [ { provider: "zai", @@ -702,15 +698,27 @@ describe("listThinkingLevels", () => { }); describe("listThinkingLevelLabels", () => { - it("uses provider runtime hooks for binary thinking providers", () => { - providerRuntimeMocks.resolveProviderBinaryThinking.mockReturnValue(true); + it("uses provider thinking profiles for binary thinking providers", () => { + providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({ + levels: [ + { id: "off", label: "off" }, + { id: "low", label: "on" }, + ], + }); expect(listThinkingLevelLabels("demo", "demo-model")).toEqual(["off", "on"]); }); it("returns on/off for provider-advertised binary thinking", () => { - providerRuntimeMocks.resolveProviderBinaryThinking.mockImplementation(({ provider }) => - provider === "zai" ? true : undefined, + providerRuntimeMocks.resolveProviderThinkingProfile.mockImplementation(({ provider }) => + provider === "zai" + ? { + levels: [ + { id: "off", label: "off" }, + { id: "low", label: "on" }, + ], + } + : undefined, ); expect(listThinkingLevelLabels("zai", "glm-4.7")).toEqual(["off", "on"]); @@ -728,8 +736,11 @@ describe("listThinkingLevelLabels", () => { }); describe("resolveThinkingDefaultForModel", () => { - it("uses provider runtime hooks for default thinking levels", () => { - providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockReturnValue("adaptive"); + it("uses provider thinking profiles for default thinking levels", () => { + providerRuntimeMocks.resolveProviderThinkingProfile.mockReturnValue({ + levels: [{ id: "off" }, { id: "adaptive" }], + defaultLevel: "adaptive", + }); expect(resolveThinkingDefaultForModel({ provider: "demo", model: "demo-model" })).toBe( "adaptive", @@ -737,9 +748,11 @@ describe("resolveThinkingDefaultForModel", () => { }); it("uses provider-advertised adaptive defaults", () => { - providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockImplementation( + providerRuntimeMocks.resolveProviderThinkingProfile.mockImplementation( ({ provider, context }) => - provider === "anthropic" && context.modelId === "claude-opus-4-6" ? "adaptive" : undefined, + provider === "anthropic" && context.modelId === "claude-opus-4-6" + ? { levels: [{ id: "off" }, { id: "adaptive" }], defaultLevel: "adaptive" } + : undefined, ); expect( @@ -748,10 +761,10 @@ describe("resolveThinkingDefaultForModel", () => { }); it("does not apply provider-advertised adaptive defaults across Bedrock id variants", () => { - providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockImplementation( + providerRuntimeMocks.resolveProviderThinkingProfile.mockImplementation( ({ provider, context }) => provider === "amazon-bedrock" && context.modelId === "claude-sonnet-4-6" - ? "adaptive" + ? { levels: [{ id: "off" }, { id: "adaptive" }], defaultLevel: "adaptive" } : undefined, ); @@ -780,8 +793,8 @@ describe("resolveThinkingDefaultForModel", () => { }); it("remaps implicit reasoning defaults to the strongest supported level at or below medium", () => { - providerRuntimeMocks.resolveProviderBinaryThinking.mockImplementation( - ({ provider }) => provider === "demo-binary", + providerRuntimeMocks.resolveProviderThinkingProfile.mockImplementation(({ provider }) => + provider === "demo-binary" ? { levels: [{ id: "off" }, { id: "low" }] } : undefined, ); expect( diff --git a/src/auto-reply/thinking.ts b/src/auto-reply/thinking.ts index e5a88a045792..eebedf843e53 100644 --- a/src/auto-reply/thinking.ts +++ b/src/auto-reply/thinking.ts @@ -33,12 +33,7 @@ import { normalizeOptionalLowercaseString, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; -import { - resolveProviderBinaryThinking, - resolveProviderDefaultThinkingLevel, - resolveProviderThinkingProfile, - resolveProviderXHighThinking, -} from "../plugins/provider-thinking.js"; +import { resolveProviderThinkingProfile } from "../plugins/provider-thinking.js"; import type { ProviderThinkingProfile } from "../plugins/provider-thinking.types.js"; /** UI-facing thinking level option. */ @@ -155,16 +150,6 @@ function buildOffOnlyThinkingProfile(): ResolvedThinkingProfile { }; } -function buildBinaryThinkingProfile(defaultLevel?: ThinkLevel | null): ResolvedThinkingProfile { - return { - levels: [ - { id: "off", label: "off", rank: THINKING_LEVEL_RANKS.off }, - { id: "low", label: "on", rank: THINKING_LEVEL_RANKS.low }, - ], - defaultLevel, - }; -} - function appendProfileLevel(profile: ResolvedThinkingProfile, id: ThinkLevel) { if (profile.levels.some((level) => level.id === id)) { return; @@ -229,35 +214,8 @@ export function resolveThinkingProfile(params: { return buildOffOnlyThinkingProfile(); } - const defaultLevel = resolveProviderDefaultThinkingLevel({ - provider: context.normalizedProvider, - context: providerContext, - }); - const binaryDecision = resolveProviderBinaryThinking({ - provider: context.normalizedProvider, - context: { - provider: context.normalizedProvider, - modelId: context.modelId, - }, - }); - const profile = - binaryDecision === true - ? buildBinaryThinkingProfile(defaultLevel) - : buildBaseThinkingProfile(defaultLevel); - if (binaryDecision !== true && catalogSupportsXHigh(context.compat)) { - appendProfileLevel(profile, "xhigh"); - } - const policyContext = { - provider: context.normalizedProvider, - modelId: context.modelKey || context.modelId, - }; - if ( - binaryDecision !== true && - resolveProviderXHighThinking({ - provider: context.normalizedProvider, - context: policyContext, - }) === true - ) { + const profile = buildBaseThinkingProfile(); + if (catalogSupportsXHigh(context.compat)) { appendProfileLevel(profile, "xhigh"); } return profile; diff --git a/src/channels/mention-gating.test.ts b/src/channels/mention-gating.test.ts index bbef95fdb3e7..0debaed59438 100644 --- a/src/channels/mention-gating.test.ts +++ b/src/channels/mention-gating.test.ts @@ -4,66 +4,8 @@ import { allowedImplicitMentionKindsFromConfig, implicitMentionKindWhen, resolveInboundMentionDecision, - resolveMentionGating, - resolveMentionGatingWithBypass, } from "./mention-gating.js"; -describe("resolveMentionGating", () => { - it("combines explicit, implicit, and bypass mentions", () => { - const res = resolveMentionGating({ - requireMention: true, - canDetectMention: true, - wasMentioned: false, - implicitMention: true, - shouldBypassMention: false, - }); - expect(res.effectiveWasMentioned).toBe(true); - expect(res.shouldSkip).toBe(false); - }); - - it("skips when mention required and none detected", () => { - const res = resolveMentionGating({ - requireMention: true, - canDetectMention: true, - wasMentioned: false, - implicitMention: false, - shouldBypassMention: false, - }); - expect(res.effectiveWasMentioned).toBe(false); - expect(res.shouldSkip).toBe(true); - }); -}); - -describe("resolveMentionGatingWithBypass", () => { - it.each([ - { - name: "enables bypass when control commands are authorized", - commandAuthorized: true, - shouldBypassMention: true, - shouldSkip: false, - }, - { - name: "does not bypass when control commands are not authorized", - commandAuthorized: false, - shouldBypassMention: false, - shouldSkip: true, - }, - ])("$name", ({ commandAuthorized, shouldBypassMention, shouldSkip }) => { - const res = resolveMentionGatingWithBypass({ - isGroup: true, - requireMention: true, - canDetectMention: true, - wasMentioned: false, - hasAnyMention: false, - allowTextCommands: true, - hasControlCommand: true, - commandAuthorized, - }); - expect(res.shouldBypassMention).toBe(shouldBypassMention); - expect(res.shouldSkip).toBe(shouldSkip); - }); -}); - describe("resolveInboundMentionDecision", () => { it("allows matching implicit mention kinds by default", () => { const res = resolveInboundMentionDecision({ @@ -269,36 +211,22 @@ describe("resolveInboundMentionDecision", () => { }); describe("unavailable mention detection", () => { - it.each([ - { - name: "raw gating", - check: () => - resolveMentionGating({ - requireMention: true, - canDetectMention: false, - wasMentioned: false, - }).shouldSkip, - }, - { - name: "inbound decision", - check: () => - resolveInboundMentionDecision({ - facts: { - canDetectMention: false, - wasMentioned: false, - implicitMentionKinds: [], - }, - policy: { - isGroup: true, - requireMention: true, - allowTextCommands: true, - hasControlCommand: false, - commandAuthorized: false, - }, - }).shouldSkip, - }, - ])("does not skip when mention detection is unavailable for $name", ({ check }) => { - expect(check()).toBe(false); + it("does not skip when mention detection is unavailable", () => { + const decision = resolveInboundMentionDecision({ + facts: { + canDetectMention: false, + wasMentioned: false, + implicitMentionKinds: [], + }, + policy: { + isGroup: true, + requireMention: true, + allowTextCommands: true, + hasControlCommand: false, + commandAuthorized: false, + }, + }); + expect(decision.shouldSkip).toBe(false); }); }); diff --git a/src/channels/mention-gating.ts b/src/channels/mention-gating.ts index e2c57fbf392b..3b91aafe897e 100644 --- a/src/channels/mention-gating.ts +++ b/src/channels/mention-gating.ts @@ -1,38 +1,5 @@ import type { ChannelImplicitMentionsConfig } from "../config/types.channels.js"; -/** @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. */ -export type MentionGateParams = { - requireMention: boolean; - canDetectMention: boolean; - wasMentioned: boolean; - implicitMention?: boolean; - shouldBypassMention?: boolean; -}; - -/** @deprecated Prefer `InboundMentionDecision`. */ -export type MentionGateResult = { - effectiveWasMentioned: boolean; - shouldSkip: boolean; -}; - -/** @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. */ -export type MentionGateWithBypassParams = { - isGroup: boolean; - requireMention: boolean; - canDetectMention: boolean; - wasMentioned: boolean; - implicitMention?: boolean; - hasAnyMention?: boolean; - allowTextCommands: boolean; - hasControlCommand: boolean; - commandAuthorized: boolean; -}; - -/** @deprecated Prefer `InboundMentionDecision`. */ -export type MentionGateWithBypassResult = MentionGateResult & { - shouldBypassMention: boolean; -}; - export type InboundImplicitMentionKind = | "reply_to_bot" | "quoted_bot" @@ -68,7 +35,9 @@ export type ResolveInboundMentionDecisionParams = | ResolveInboundMentionDecisionFlatParams | ResolveInboundMentionDecisionNestedParams; -export type InboundMentionDecision = MentionGateResult & { +export type InboundMentionDecision = { + effectiveWasMentioned: boolean; + shouldSkip: boolean; implicitMention: boolean; matchedImplicitMentionKinds: InboundImplicitMentionKind[]; shouldBypassMention: boolean; @@ -211,44 +180,3 @@ export function resolveInboundMentionDecision( shouldBypassMention, }); } - -/** @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. */ -export function resolveMentionGating(params: MentionGateParams): MentionGateResult { - const result = resolveMentionDecisionCore({ - requireMention: params.requireMention, - canDetectMention: params.canDetectMention, - wasMentioned: params.wasMentioned, - implicitMentionKinds: implicitMentionKindWhen("native", params.implicitMention === true), - shouldBypassMention: params.shouldBypassMention === true, - }); - return { - effectiveWasMentioned: result.effectiveWasMentioned, - shouldSkip: result.shouldSkip, - }; -} - -/** @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. */ -export function resolveMentionGatingWithBypass( - params: MentionGateWithBypassParams, -): MentionGateWithBypassResult { - const result = resolveInboundMentionDecision({ - facts: { - canDetectMention: params.canDetectMention, - wasMentioned: params.wasMentioned, - hasAnyMention: params.hasAnyMention, - implicitMentionKinds: implicitMentionKindWhen("native", params.implicitMention === true), - }, - policy: { - isGroup: params.isGroup, - requireMention: params.requireMention, - allowTextCommands: params.allowTextCommands, - hasControlCommand: params.hasControlCommand, - commandAuthorized: params.commandAuthorized, - }, - }); - return { - effectiveWasMentioned: result.effectiveWasMentioned, - shouldSkip: result.shouldSkip, - shouldBypassMention: result.shouldBypassMention, - }; -} diff --git a/src/channels/plugins/approval-native.types.ts b/src/channels/plugins/approval-native.types.ts index 7b867b762346..554f30d4052a 100644 --- a/src/channels/plugins/approval-native.types.ts +++ b/src/channels/plugins/approval-native.types.ts @@ -24,17 +24,17 @@ export type ChannelApprovalNativeTarget = { /** * Preferred native delivery surface for approval prompts. */ -export type ChannelApprovalNativeDeliveryPreference = ChannelApprovalNativeSurface | "both"; +type ChannelApprovalNativeDeliveryPreference = ChannelApprovalNativeSurface | "both"; /** * Approval request shapes supported by native channel approval delivery. */ -export type ChannelApprovalNativeRequest = ExecApprovalRequest | PluginApprovalRequest; +type ChannelApprovalNativeRequest = ExecApprovalRequest | PluginApprovalRequest; /** * Capabilities returned by native channel approval delivery inspection. */ -export type ChannelApprovalNativeDeliveryCapabilities = { +type ChannelApprovalNativeDeliveryCapabilities = { enabled: boolean; preferredSurface: ChannelApprovalNativeDeliveryPreference; supportsOriginSurface: boolean; diff --git a/src/channels/plugins/configured-state.test.ts b/src/channels/plugins/configured-state.test.ts index 88d09bc10e76..1b93cc3f08a0 100644 --- a/src/channels/plugins/configured-state.test.ts +++ b/src/channels/plugins/configured-state.test.ts @@ -11,10 +11,26 @@ const nodeRequire = createRequire(import.meta.url); describe("bundled channel configured-state metadata", () => { it("lists the shipped metadata-first configured-state channels", () => { expect(listBundledChannelIdsWithConfiguredState()).toEqual([ + "clickclack", "discord", + "feishu", + "googlechat", "irc", + "line", + "matrix", + "mattermost", + "msteams", + "nextcloud-talk", + "nostr", + "qqbot", + "raft", "slack", + "sms", + "synology-chat", "telegram", + "twitch", + "zalo", + "zalouser", ]); }); diff --git a/src/channels/plugins/contracts/test-helpers/threading-directory-contract-suites.ts b/src/channels/plugins/contracts/test-helpers/threading-directory-contract-suites.ts index c50548193564..78e714844977 100644 --- a/src/channels/plugins/contracts/test-helpers/threading-directory-contract-suites.ts +++ b/src/channels/plugins/contracts/test-helpers/threading-directory-contract-suites.ts @@ -8,12 +8,18 @@ import type { OpenClawConfig } from "../../../../config/config.js"; import type { RuntimeEnv } from "../../../../runtime.js"; import type { ChannelDirectoryEntry, - ChannelFocusedBindingContext, - ChannelReplyTransport, + ChannelThreadingAdapter, ChannelThreadingToolContext, } from "../../types.core.js"; import type { ChannelPlugin } from "../../types.js"; +type ChannelReplyTransport = NonNullable< + ReturnType> +>; +type ChannelFocusedBindingContext = NonNullable< + ReturnType> +>; + const contractRuntime = new Proxy(Object.create(null), { get(_target, property) { throw new Error(`Directory contract unexpectedly accessed runtime.${String(property)}`); diff --git a/src/channels/plugins/exposure.ts b/src/channels/plugins/exposure.ts index e9aecd1c0b13..81511a3dc599 100644 --- a/src/channels/plugins/exposure.ts +++ b/src/channels/plugins/exposure.ts @@ -8,14 +8,10 @@ import type { ChannelMeta } from "./types.core.js"; /** * Resolves where a channel should appear in configured, setup, and docs views. */ -export function resolveChannelExposure( - meta: Pick, -) { - // `showConfigured` and `showInSetup` are legacy metadata fields; keep them - // as fallback inputs so older bundled manifests keep their visibility. +export function resolveChannelExposure(meta: Pick) { return { - configured: meta.exposure?.configured ?? meta.showConfigured ?? true, - setup: meta.exposure?.setup ?? meta.showInSetup ?? true, + configured: meta.exposure?.configured ?? true, + setup: meta.exposure?.setup ?? true, docs: meta.exposure?.docs ?? true, }; } @@ -23,17 +19,13 @@ export function resolveChannelExposure( /** * Returns whether the channel should be listed for already configured agents. */ -export function isChannelVisibleInConfiguredLists( - meta: Pick, -): boolean { +export function isChannelVisibleInConfiguredLists(meta: Pick): boolean { return resolveChannelExposure(meta).configured; } /** * Returns whether the channel should be offered during setup/onboarding. */ -export function isChannelVisibleInSetup( - meta: Pick, -): boolean { +export function isChannelVisibleInSetup(meta: Pick): boolean { return resolveChannelExposure(meta).setup; } diff --git a/src/channels/plugins/outbound.types.ts b/src/channels/plugins/outbound.types.ts index 290bb70635f3..99b341ca0011 100644 --- a/src/channels/plugins/outbound.types.ts +++ b/src/channels/plugins/outbound.types.ts @@ -146,11 +146,11 @@ export type ChannelOutboundTargetRef = { threadId?: string | number | null; }; -export type ChannelOutboundFormattedContext = ChannelOutboundContext & { +type ChannelOutboundFormattedContext = ChannelOutboundContext & { abortSignal?: AbortSignal; }; -export type ChannelOutboundChunkContext = { +type ChannelOutboundChunkContext = { formatting?: OutboundDeliveryFormattingOptions; }; diff --git a/src/channels/plugins/read-only.test.ts b/src/channels/plugins/read-only.test.ts index 80ceb0780e21..1b038ab1a116 100644 --- a/src/channels/plugins/read-only.test.ts +++ b/src/channels/plugins/read-only.test.ts @@ -206,6 +206,10 @@ function writeExternalSetupChannelPlugin( openclaw: { extensions: ["./index.cjs"], ...(setupEntry ? { setupEntry: "./setup-entry.cjs" } : {}), + channel: { + id: channelId, + configuredState: { env: { anyOf: ["EXTERNAL_CHAT_TOKEN"] } }, + }, }, }, null, @@ -220,9 +224,6 @@ function writeExternalSetupChannelPlugin( id: pluginId, configSchema: EMPTY_PLUGIN_SCHEMA, channels: manifestChannelIds, - channelEnvVars: { - [channelId]: ["EXTERNAL_CHAT_TOKEN"], - }, ...(typeof options.setupRequiresRuntime === "boolean" ? { setup: { requiresRuntime: options.setupRequiresRuntime } } : {}), @@ -385,6 +386,7 @@ function writeBundledSetupChannelPlugin( selectionLabel: "Bundled Chat", docsPath: `/channels/${channelId}`, blurb: "bundled setup entry", + configuredState: { env: { anyOf: [envVar] } }, }, }, }, @@ -400,9 +402,6 @@ function writeBundledSetupChannelPlugin( id: pluginId, configSchema: EMPTY_PLUGIN_SCHEMA, channels: [channelId], - channelEnvVars: { - [channelId]: [envVar], - }, }, null, 2, @@ -505,7 +504,7 @@ describe("listReadOnlyChannelPluginsForConfig", () => { expect(candidates).not.toContain(path.join(packageRoot, "..", "plugins", "loader.js")); }); - it("does not load setup-only channel plugin runtime by default", () => { + it("uses package channel metadata without loading setup or full runtime", () => { const { pluginDir, fullMarker, setupMarker } = writeExternalSetupChannelPlugin(); const plugins = listReadOnlyChannelPluginsForConfig( { @@ -523,7 +522,7 @@ describe("listReadOnlyChannelPluginsForConfig", () => { }, ); - expect(pluginIds(plugins)).not.toContain("external-chat"); + expect(pluginIds(plugins)).toContain("external-chat"); expect(fs.existsSync(setupMarker)).toBe(false); expect(fs.existsSync(fullMarker)).toBe(false); }); @@ -1421,12 +1420,12 @@ describe("listReadOnlyChannelPluginsForConfig", () => { ); expect(pluginIds(plugins)).not.toContain("spoofed-chat"); - expect(pluginIds(plugins)).not.toContain("external-chat"); + expect(pluginIds(plugins)).toContain("external-chat"); expect(fs.existsSync(setupMarker)).toBe(true); expect(fs.existsSync(fullMarker)).toBe(false); }); - it("reports setup-entry load failures for configured channel plugins", () => { + it("falls back to manifest metadata and reports setup-entry load failures", () => { const { pluginDir, fullMarker, setupMarker } = writeExternalSetupChannelPlugin({ pluginId: "external-chat-plugin", channelId: "external-chat", @@ -1453,8 +1452,8 @@ describe("listReadOnlyChannelPluginsForConfig", () => { }, ); - expect(pluginIds(result.plugins)).not.toContain("external-chat"); - expect(result.missingConfiguredChannelIds).toContain("external-chat"); + expect(pluginIds(result.plugins)).toContain("external-chat"); + expect(result.missingConfiguredChannelIds).not.toContain("external-chat"); expect(result.loadFailures).toEqual([ expect.objectContaining({ channelId: "external-chat", diff --git a/src/channels/plugins/types.adapters.ts b/src/channels/plugins/types.adapters.ts index 4fc11e91fd97..ce63a0c90d84 100644 --- a/src/channels/plugins/types.adapters.ts +++ b/src/channels/plugins/types.adapters.ts @@ -23,9 +23,7 @@ import type { ChannelRuntimeSurface } from "./channel-runtime-surface.types.js"; import type { ConfigWriteTarget } from "./config-writes.js"; export type { ChannelOutboundAdapter, - ChannelOutboundChunkContext, ChannelOutboundContext, - ChannelOutboundFormattedContext, ChannelOutboundPayloadContext, ChannelOutboundPayloadHint, ChannelOutboundTargetRef, @@ -47,16 +45,13 @@ import type { export type { ChannelPairingAdapter } from "./pairing.types.js"; type ConfiguredBindingRule = AgentBinding; -export type { ChannelApprovalKind } from "../../infra/approval-types.js"; -export type ChannelActionAvailabilityState = +type ChannelActionAvailabilityState = | { kind: "enabled" } | { kind: "disabled" } | { kind: "unsupported" }; -export type ChannelApprovalInitiatingSurfaceState = ChannelActionAvailabilityState; - -export type ChannelApprovalForwardTarget = { +type ChannelApprovalForwardTarget = { channel: string; to: string; accountId?: string | null; @@ -64,7 +59,7 @@ export type ChannelApprovalForwardTarget = { source?: "session" | "target"; }; -export type ChannelCapabilitiesDisplayTone = "default" | "muted" | "success" | "warn" | "error"; +type ChannelCapabilitiesDisplayTone = "default" | "muted" | "success" | "warn" | "error"; export type ChannelCapabilitiesDisplayLine = { text: string; @@ -319,25 +314,25 @@ export type ChannelGatewayContext = { channelRuntime?: ChannelRuntimeSurface; }; -export type ChannelLogoutResult = { +type ChannelLogoutResult = { cleared: boolean; loggedOut?: boolean; [key: string]: unknown; }; -export type ChannelLoginWithQrStartResult = { +type ChannelLoginWithQrStartResult = { qrDataUrl?: string; message: string; connected?: boolean; }; -export type ChannelLoginWithQrWaitResult = { +type ChannelLoginWithQrWaitResult = { connected: boolean; message: string; qrDataUrl?: string; }; -export type ChannelLogoutContext = { +type ChannelLogoutContext = { cfg: OpenClawConfig; accountId: string; account: ResolvedAccount; @@ -580,7 +575,7 @@ export type ChannelLifecycleAdapter = { }) => ChannelLegacyStateMigrationPlan[] | Promise; }; -export type ChannelApprovalDeliveryAdapter = { +type ChannelApprovalDeliveryAdapter = { hasConfiguredDmRoute?: (params: { cfg: OpenClawConfig }) => boolean; shouldSuppressForwardingFallback?: (params: { cfg: OpenClawConfig; @@ -589,21 +584,14 @@ export type ChannelApprovalDeliveryAdapter = { request: ExecApprovalRequest | PluginApprovalRequest; }) => boolean; }; -export type ChannelApproveCommandBehavior = +type ChannelApproveCommandBehavior = | { kind: "allow" } | { kind: "ignore" } | { kind: "reply"; text: string }; -export type { - ChannelApprovalNativeAdapter, - ChannelApprovalNativeDeliveryCapabilities, - ChannelApprovalNativeDeliveryPreference, - ChannelApprovalNativeRequest, - ChannelApprovalNativeSurface, - ChannelApprovalNativeTarget, -} from "./approval-native.types.js"; +export type { ChannelApprovalNativeAdapter } from "./approval-native.types.js"; -export type ChannelApprovalRenderAdapter = { +type ChannelApprovalRenderAdapter = { exec?: { buildPendingPayload?: (params: { cfg: OpenClawConfig; diff --git a/src/channels/plugins/types.core.ts b/src/channels/plugins/types.core.ts index da0e737dcc5f..26a0c9040113 100644 --- a/src/channels/plugins/types.core.ts +++ b/src/channels/plugins/types.core.ts @@ -26,7 +26,7 @@ import type { ChannelMessageCapability } from "./message-capabilities.js"; export type { ChannelId } from "./channel-id.types.js"; export type { ChannelLegacyStateMigrationPlan } from "./legacy-state-migration.types.js"; -export type ChannelExposure = { +type ChannelExposure = { configured?: boolean; setup?: boolean; docs?: boolean; @@ -187,8 +187,6 @@ export type ChannelMeta = { systemImage?: string; markdownCapable?: boolean; exposure?: ChannelExposure; - showConfigured?: boolean; - showInSetup?: boolean; quickstartAllowFrom?: boolean; forceAccountBinding?: boolean; preferSessionLookupForAnnounceTarget?: boolean; @@ -296,7 +294,7 @@ export type ChannelGroupContext = { * one here. Adding a new entry requires extending the host transcoder * recipe table in lockstep so a typed declaration cannot silently no-op. */ -export type PreferredAudioFileFormat = "caf"; +type PreferredAudioFileFormat = "caf"; export type ChannelTtsVoiceDeliveryCapabilities = { synthesisTarget: "audio-file" | "voice-note"; @@ -377,19 +375,19 @@ export type ChannelStreamingAdapter = { // their side and cast at the boundary. export type ChannelStructuredComponents = unknown[]; -export type ChannelCrossContextPresentationFactory = (params: { +type ChannelCrossContextPresentationFactory = (params: { originLabel: string; message: string; cfg: OpenClawConfig; accountId?: string | null; }) => MessagePresentation; -export type ChannelReplyTransport = { +type ChannelReplyTransport = { replyToId?: string | null; threadId?: string | number | null; }; -export type ChannelFocusedBindingContext = { +type ChannelFocusedBindingContext = { conversationId: string; parentConversationId?: string; placement: "current" | "child"; @@ -765,7 +763,7 @@ export type ChannelToolSend = { threadSuppressed?: boolean; }; -export type ChannelMessagePreparedSendPayloadContext = { +type ChannelMessagePreparedSendPayloadContext = { ctx: ChannelMessageActionContext; to: string; payload: ReplyPayload; diff --git a/src/channels/plugins/types.public.ts b/src/channels/plugins/types.public.ts index 275d27758654..6ce27f153ac1 100644 --- a/src/channels/plugins/types.public.ts +++ b/src/channels/plugins/types.public.ts @@ -10,108 +10,40 @@ export type { BaseProbeResult, BaseTokenResolution, ChannelAccountSnapshot, - ChannelAccountState, - ChannelAgentPromptAdapter, ChannelAgentTool, - ChannelAgentToolFactory, ChannelCapabilities, - ChannelCrossContextPresentationFactory, ChannelDirectoryEntry, ChannelDirectoryEntryKind, - ChannelExposure, - ChannelFocusedBindingContext, ChannelGroupContext, ChannelHeartbeatDeps, ChannelId, - ChannelLegacyStateMigrationPlan, ChannelLogSink, - ChannelMentionAdapter, ChannelMessageActionAdapter, ChannelMessageActionContext, ChannelMessageActionDiscoveryContext, - ChannelMessagePreparedSendPayloadContext, ChannelMessageToolDiscovery, ChannelMessageToolSchemaContribution, ChannelMessagingAdapter, ChannelMeta, - ChannelOutboundSessionRoute, ChannelOutboundTargetMode, - ChannelPollContext, ChannelPollResult, - ChannelReplyTransport, - ChannelSecurityContext, - ChannelSecurityDmPolicy, ChannelSetupInput, ChannelStatusIssue, - ChannelStreamingAdapter, ChannelStructuredComponents, ChannelThreadingAdapter, ChannelThreadingContext, ChannelThreadingToolContext, ChannelToolSend, - ChannelTtsVoiceDeliveryCapabilities, - PreferredAudioFileFormat, } from "./types.core.js"; export type { - ChannelActionAvailabilityState, - ChannelAllowlistAdapter, ChannelApprovalAdapter, ChannelApprovalCapability, - ChannelApprovalDeliveryAdapter, - ChannelApprovalForwardTarget, - ChannelApprovalInitiatingSurfaceState, - ChannelApprovalKind, - ChannelApprovalNativeAdapter, - ChannelApprovalNativeDeliveryCapabilities, - ChannelApprovalNativeDeliveryPreference, - ChannelApprovalNativeRequest, - ChannelApprovalNativeSurface, - ChannelApprovalNativeTarget, - ChannelApprovalRenderAdapter, - ChannelApproveCommandBehavior, - ChannelAuthAdapter, ChannelCapabilitiesDiagnostics, ChannelCapabilitiesDisplayLine, - ChannelCapabilitiesDisplayTone, - ChannelCommandAdapter, ChannelCommandConversationContext, - ChannelConfigAdapter, - ChannelConfiguredBindingConversationRef, - ChannelConfiguredBindingMatch, - ChannelConfiguredBindingProvider, - ChannelConversationBindingSupport, - ChannelDeliveryCapabilities, - ChannelDirectoryAdapter, - ChannelDoctorAdapter, - ChannelDoctorConfigMutation, - ChannelDoctorEmptyAllowlistAccountContext, - ChannelDoctorLegacyConfigRule, - ChannelDoctorSequenceResult, - ChannelElevatedAdapter, - ChannelGatewayAdapter, - ChannelGatewayContext, - ChannelGroupAdapter, - ChannelHeartbeatAdapter, - ChannelLifecycleAdapter, - ChannelLoginWithQrStartResult, - ChannelLoginWithQrWaitResult, - ChannelLogoutContext, - ChannelLogoutResult, ChannelOutboundAdapter, - ChannelOutboundChunkContext, - ChannelOutboundContext, - ChannelOutboundFormattedContext, - ChannelOutboundPayloadContext, - ChannelOutboundPayloadHint, - ChannelOutboundTargetRef, - ChannelPairingAdapter, ChannelResolveKind, ChannelResolveResult, - ChannelResolverAdapter, - ChannelSecretsAdapter, - ChannelSecurityAdapter, - ChannelSetupAdapter, - ChannelStatusAdapter, } from "./types.adapters.js"; export type { ChannelMessageCapability } from "./message-capabilities.js"; export type { ChannelPlugin } from "./types.plugin.js"; diff --git a/src/cli/command-secret-gateway.test.ts b/src/cli/command-secret-gateway.test.ts index 5a088ab68d7e..e7251050d6d5 100644 --- a/src/cli/command-secret-gateway.test.ts +++ b/src/cli/command-secret-gateway.test.ts @@ -843,67 +843,6 @@ describe("resolveCommandSecretRefsViaGateway", () => { }); }); - it("falls back to local resolution for legacy web fetch SecretRefs", async () => { - const envKey = "WEB_FETCH_LEGACY_FIRECRAWL_API_KEY_LOCAL_FALLBACK"; - await withEnvValue(envKey, "firecrawl-legacy-local-fallback-key", async () => { - callGateway.mockRejectedValueOnce(new Error("gateway closed")); - const result = await resolveCommandSecretRefsViaGateway({ - config: { - tools: { - web: { - fetch: { - provider: "firecrawl", - firecrawl: { - apiKey: { source: "env", provider: "default", id: envKey }, - }, - }, - }, - }, - } as unknown as OpenClawConfig, - commandName: "infer web fetch", - targetIds: new Set(["tools.web.fetch.firecrawl.apiKey"]), - }); - - const fetchConfig = result.resolvedConfig.tools?.web?.fetch as - | { firecrawl?: { apiKey?: unknown } } - | undefined; - expect(fetchConfig?.firecrawl?.apiKey).toBe("firecrawl-legacy-local-fallback-key"); - expect(result.targetStatesByPath["tools.web.fetch.firecrawl.apiKey"]).toBe("resolved_local"); - expectGatewayUnavailableLocalFallbackDiagnostics(result); - }); - }); - - it("keeps top-level web search SecretRefs on the direct local fallback path", async () => { - const runtimeWebTools = await import("../secrets/runtime-web-tools.js"); - vi.mocked(runtimeWebTools.resolveRuntimeWebTools).mockClear(); - const envKey = "WEB_SEARCH_BRAVE_TOP_LEVEL_LOCAL_FALLBACK"; - await withEnvValue(envKey, "brave-top-level-local-fallback-key", async () => { - callGateway.mockRejectedValueOnce(new Error("gateway closed")); - const result = await resolveCommandSecretRefsViaGateway({ - config: { - tools: { - web: { - search: { - provider: "exa", - apiKey: { source: "env", provider: "default", id: envKey }, - }, - }, - }, - } as unknown as OpenClawConfig, - commandName: "infer web search", - targetIds: new Set(["tools.web.search.apiKey"]), - forcedActivePaths: new Set(["tools.web.search.apiKey"]), - }); - - expect(result.resolvedConfig.tools?.web?.search?.apiKey).toBe( - "brave-top-level-local-fallback-key", - ); - expect(result.targetStatesByPath["tools.web.search.apiKey"]).toBe("resolved_local"); - expect(runtimeWebTools.resolveRuntimeWebTools).not.toHaveBeenCalled(); - expectGatewayUnavailableLocalFallbackDiagnostics(result); - }); - }); - it("treats command-scoped web fetch fallback SecretRefs as active even when web search is disabled", async () => { const restoreDeps = setFirecrawlWebSearchTargetDeps(); const envKey = "WEB_FETCH_FIRECRAWL_SEARCH_FALLBACK_KEY"; diff --git a/src/cli/command-secret-gateway.ts b/src/cli/command-secret-gateway.ts index d0650c9d04d6..7cb1db4ec993 100644 --- a/src/cli/command-secret-gateway.ts +++ b/src/cli/command-secret-gateway.ts @@ -71,16 +71,8 @@ type GatewaySecretsResolveResult = { inactiveRefPaths?: string[]; }; -const WEB_RUNTIME_SECRET_TARGET_ID_PREFIXES = [ - "tools.web.search", - "tools.web.fetch", - "plugins.entries.", -] as const; -const WEB_RUNTIME_SECRET_PATH_PREFIXES = [ - "tools.web.search.", - "tools.web.fetch.", - "plugins.entries.", -] as const; +const WEB_RUNTIME_SECRET_TARGET_ID_PREFIXES = ["plugins.entries."] as const; +const WEB_RUNTIME_SECRET_PATH_PREFIXES = ["plugins.entries."] as const; type CommandSecretGatewayDeps = { analyzeCommandSecretAssignmentsFromSnapshot: typeof analyzeCommandSecretAssignmentsFromSnapshot; @@ -165,22 +157,6 @@ function classifyRuntimeWebTargetPathState(params: { config: OpenClawConfig; path: string; }): "active" | "inactive" | "unknown" { - if (params.path === "tools.web.search.apiKey") { - return params.config.tools?.web?.search?.enabled !== false ? "active" : "inactive"; - } - const fetchMatch = /^tools\.web\.fetch\.([^.]+)\.apiKey$/.exec(params.path); - if (fetchMatch) { - const fetch = params.config.tools?.web?.fetch; - if (fetch?.enabled === false) { - return "inactive"; - } - const configuredProvider = normalizeLowercaseStringOrEmpty(fetch?.provider); - if (!configuredProvider) { - return "active"; - } - return configuredProvider === fetchMatch[1] ? "active" : "inactive"; - } - const pluginId = pluginIdFromRuntimeWebPath(params.path); if (pluginId) { if (params.path.endsWith(".config.webFetch.apiKey")) { @@ -223,46 +199,13 @@ function classifyRuntimeWebTargetPathState(params: { return configuredPluginId === pluginId ? "active" : "inactive"; } - const match = /^tools\.web\.search\.([^.]+)\.apiKey$/.exec(params.path); - if (!match) { - return "unknown"; - } - - const search = params.config.tools?.web?.search; - if (search?.enabled === false) { - return "inactive"; - } - - const configuredProvider = normalizeLowercaseStringOrEmpty(search?.provider); - if (!configuredProvider) { - return "active"; - } - - return configuredProvider === match[1] ? "active" : "inactive"; + return "unknown"; } function describeInactiveRuntimeWebTargetPath(params: { config: OpenClawConfig; path: string; }): string | undefined { - if (params.path === "tools.web.search.apiKey") { - return params.config.tools?.web?.search?.enabled === false - ? "tools.web.search is disabled." - : undefined; - } - const fetchMatch = /^tools\.web\.fetch\.([^.]+)\.apiKey$/.exec(params.path); - if (fetchMatch) { - const fetch = params.config.tools?.web?.fetch; - if (fetch?.enabled === false) { - return "tools.web.fetch is disabled."; - } - const configuredProvider = normalizeLowercaseStringOrEmpty(fetch?.provider); - if (configuredProvider && configuredProvider !== fetchMatch[1]) { - return `tools.web.fetch.provider is "${configuredProvider}".`; - } - return undefined; - } - const pluginId = pluginIdFromRuntimeWebPath(params.path); if (pluginId) { if (params.path.endsWith(".config.webFetch.apiKey")) { @@ -295,21 +238,6 @@ function describeInactiveRuntimeWebTargetPath(params: { return undefined; } - const match = /^tools\.web\.search\.([^.]+)\.apiKey$/.exec(params.path); - if (!match) { - return undefined; - } - - const search = params.config.tools?.web?.search; - if (search?.enabled === false) { - return "tools.web.search is disabled."; - } - - const configuredProvider = normalizeLowercaseStringOrEmpty(search?.provider); - if (configuredProvider && configuredProvider !== match[1]) { - return `tools.web.search.provider is "${configuredProvider}".`; - } - return undefined; } @@ -606,12 +534,7 @@ async function callGatewaySecretsResolve(params: { } function isDirectRuntimeWebTargetPath(path: string): boolean { - return ( - path === "tools.web.search.apiKey" || - /^plugins\.entries\.[^.]+\.config\.(webSearch|webFetch)\.apiKey$/.test(path) || - /^tools\.web\.search\.[^.]+\.apiKey$/.test(path) || - /^tools\.web\.fetch\.[^.]+\.apiKey$/.test(path) - ); + return /^plugins\.entries\.[^.]+\.config\.(webSearch|webFetch)\.apiKey$/.test(path); } async function resolveCommandSecretRefsLocally(params: { diff --git a/src/cli/command-secret-targets.test.ts b/src/cli/command-secret-targets.test.ts index c4d4ff44d209..490c6cd7862b 100644 --- a/src/cli/command-secret-targets.test.ts +++ b/src/cli/command-secret-targets.test.ts @@ -22,9 +22,6 @@ const REGISTRY_IDS = [ "plugins.entries.other-fetch.config.webFetch.apiKey", "plugins.entries.other-fetch.config.webSearch.apiKey", "skills.entries.demo.apiKey", - "tools.web.fetch.firecrawl.apiKey", - "tools.web.search.apiKey", - "tools.web.search.*.apiKey", ] as const; function readPath(source: unknown, path: string): unknown { @@ -290,8 +287,6 @@ describe("command secret target ids", () => { it("scopes capability web search commands to search credential surfaces only", () => { const ids = getCapabilityWebSearchCommandSecretTargetIds(); - expect(ids.has("tools.web.search.apiKey")).toBe(true); - expect(ids.has("tools.web.search.*.apiKey")).toBe(true); expect(ids.has("plugins.entries.exa.config.webSearch.apiKey")).toBe(true); expect(ids.has("plugins.entries.firecrawl.config.webFetch.apiKey")).toBe(false); expect(ids.has("plugins.entries.voice-call.config.twilio.authToken")).toBe(false); @@ -304,8 +299,6 @@ describe("command secret target ids", () => { it("scopes capability web fetch commands to fetch credential surfaces only", () => { const ids = getCapabilityWebFetchCommandSecretTargetIds(); - expect(ids.has("tools.web.search.apiKey")).toBe(false); - expect(ids.has("tools.web.fetch.firecrawl.apiKey")).toBe(true); expect(ids.has("plugins.entries.exa.config.webSearch.apiKey")).toBe(false); expect(ids.has("plugins.entries.firecrawl.config.webFetch.apiKey")).toBe(true); expect(ids.has("plugins.entries.voice-call.config.twilio.authToken")).toBe(false); @@ -446,80 +439,6 @@ describe("command secret target ids", () => { ); }); - it("keeps selected top-level web search credential refs in command targets", () => { - const scoped = getCapabilityWebSearchCommandSecretTargets({ - tools: { - web: { - search: { - provider: "brave", - enabled: true, - apiKey: { source: "env", provider: "default", id: "BRAVE_API_KEY" }, - }, - }, - }, - } as never); - - expect(scoped.targetIds).toEqual( - new Set(["plugins.entries.brave.config.webSearch.apiKey", "tools.web.search.apiKey"]), - ); - expect(scoped.forcedActivePaths).toBeUndefined(); - }); - - it("maps selected legacy scoped web search refs to registry targets", () => { - const scoped = getCapabilityWebSearchCommandSecretTargets({ - tools: { - web: { - search: { - provider: "exa", - enabled: true, - exa: { - apiKey: { source: "env", provider: "default", id: "EXA_API_KEY" }, - }, - }, - }, - }, - } as never); - - expect(scoped.targetIds).toEqual( - new Set(["plugins.entries.exa.config.webSearch.apiKey", "tools.web.search.*.apiKey"]), - ); - expect(scoped.allowedPaths).toEqual( - new Set(["plugins.entries.exa.config.webSearch.apiKey", "tools.web.search.exa.apiKey"]), - ); - expect(scoped.forcedActivePaths).toBeUndefined(); - }); - - it("skips stale legacy scoped web search refs when plugin credential wins", () => { - const scoped = getCapabilityWebSearchCommandSecretTargets({ - tools: { - web: { - search: { - provider: "exa", - enabled: true, - exa: { - apiKey: { source: "env", provider: "default", id: "STALE_EXA_API_KEY" }, - }, - }, - }, - }, - plugins: { - entries: { - exa: { - config: { - webSearch: { - apiKey: { source: "env", provider: "default", id: "EXA_API_KEY" }, - }, - }, - }, - }, - }, - } as never); - - expect(scoped.targetIds).toEqual(new Set(["plugins.entries.exa.config.webSearch.apiKey"])); - expect(scoped.allowedPaths).toBeUndefined(); - expect(scoped.forcedActivePaths).toBeUndefined(); - }); - it("maps selected fallback credential paths to registry targets", () => { const scoped = getCapabilityWebSearchCommandSecretTargets({ tools: { web: { search: { provider: "gemini", enabled: true } } }, @@ -845,31 +764,6 @@ describe("command secret target ids", () => { expect(fetchConfigured.forcedActivePaths).toBeUndefined(); }); - it("keeps selected legacy Firecrawl web fetch refs in command targets", () => { - const scoped = getCapabilityWebFetchCommandSecretTargets({ - tools: { - web: { - fetch: { - provider: "firecrawl", - enabled: true, - firecrawl: { - apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" }, - }, - }, - }, - }, - } as never); - - expect(scoped.targetIds).toEqual( - new Set([ - "plugins.entries.firecrawl.config.webFetch.apiKey", - "tools.web.fetch.firecrawl.apiKey", - ]), - ); - expect(scoped.allowedPaths).toBeUndefined(); - expect(scoped.forcedActivePaths).toBeUndefined(); - }); - it("does not add fallback credential paths for non-selected fetch providers", () => { const scoped = getCapabilityWebFetchCommandSecretTargets({ tools: { web: { fetch: { provider: "other", enabled: true } } }, @@ -1159,4 +1053,3 @@ describe("command secret target ids", () => { expect(scoped.allowedPaths).toEqual(new Set()); }); }); -/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/cli/command-secret-targets.ts b/src/cli/command-secret-targets.ts index 7d3ea6498d06..55640827d447 100644 --- a/src/cli/command-secret-targets.ts +++ b/src/cli/command-secret-targets.ts @@ -1,6 +1,5 @@ // Command-specific secret target policy. Each exported helper returns the config secret IDs // a command may inspect, with optional concrete-path filters for selected providers/accounts. -import { isRecord } from "@openclaw/normalization-core/record-coerce"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { sortUniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js"; @@ -44,8 +43,6 @@ const STATIC_AGENT_RUNTIME_BASE_TARGET_IDS = [ "agents.list[].tts.providers.*.apiKey", "messages.tts.providers.*.apiKey", "skills.entries.*.apiKey", - "tools.web.search.apiKey", - "tools.web.fetch.firecrawl.apiKey", ] as const; const STATIC_MEMORY_EMBEDDING_TARGET_IDS = [ ...STATIC_MODEL_TARGET_IDS, @@ -98,12 +95,6 @@ type SelectedProviderTargetIds = { fallbackPaths: string[]; }; -const STATIC_CAPABILITY_WEB_SEARCH_TARGET_IDS = [ - "tools.web.search.apiKey", - "tools.web.search.*.apiKey", -] as const; -const STATIC_CAPABILITY_WEB_FETCH_TARGET_IDS = ["tools.web.fetch.firecrawl.apiKey"] as const; - let cachedCommandSecretTargets: CommandSecretTargets | undefined; let cachedAgentRuntimeBaseTargetIds: string[] | undefined; let cachedCapabilityWebFetchTargetIds: string[] | undefined; @@ -141,22 +132,20 @@ function isPluginWebFetchCredentialTargetId(id: string): boolean { } function getCapabilityWebSearchTargetIds(): string[] { - cachedCapabilityWebSearchTargetIds ??= sortUniqueStrings([ - ...STATIC_CAPABILITY_WEB_SEARCH_TARGET_IDS, - ...listSecretTargetRegistryEntries() + cachedCapabilityWebSearchTargetIds ??= sortUniqueStrings( + listSecretTargetRegistryEntries() .map((entry) => entry.id) .filter(isPluginWebSearchCredentialTargetId), - ]); + ); return cachedCapabilityWebSearchTargetIds; } function getCapabilityWebFetchTargetIds(): string[] { - cachedCapabilityWebFetchTargetIds ??= sortUniqueStrings([ - ...STATIC_CAPABILITY_WEB_FETCH_TARGET_IDS, - ...listSecretTargetRegistryEntries() + cachedCapabilityWebFetchTargetIds ??= sortUniqueStrings( + listSecretTargetRegistryEntries() .map((entry) => entry.id) .filter(isPluginWebFetchCredentialTargetId), - ]); + ); return cachedCapabilityWebFetchTargetIds; } @@ -354,83 +343,14 @@ function hasConfiguredFetchCredential(params: { provider: PluginWebFetchProviderEntry; config: OpenClawConfig; }): boolean { - return ( - isConfiguredSecretCandidate(params.provider.getConfiguredCredentialValue?.(params.config)) || - isConfiguredSecretCandidate( - params.provider.getCredentialValue(resolveFetchConfig(params.config)), - ) - ); + return isConfiguredSecretCandidate(params.provider.getConfiguredCredentialValue?.(params.config)); } function hasConfiguredSearchCredential(params: { provider: PluginWebSearchProviderEntry; config: OpenClawConfig; }): boolean { - return ( - isConfiguredSecretCandidate(params.provider.getConfiguredCredentialValue?.(params.config)) || - isConfiguredSecretCandidate( - params.provider.getCredentialValue(resolveSearchConfig(params.config)), - ) - ); -} - -function addConfiguredSearchCredentialTargetIds(params: { - config: OpenClawConfig; - provider: PluginWebSearchProviderEntry; - targetIds: Set; - targetPaths: Set; - allowedPaths: Set; -}): void { - const searchConfig = resolveSearchConfig(params.config); - if (!searchConfig) { - return; - } - const configuredCredential = params.provider.getCredentialValue(searchConfig); - if (!isConfiguredSecretCandidate(configuredCredential)) { - return; - } - const pluginCredential = params.provider.getConfiguredCredentialValue?.(params.config); - if (isConfiguredSecretCandidate(pluginCredential) && configuredCredential !== pluginCredential) { - return; - } - if (configuredCredential === searchConfig.apiKey) { - addConfigPathTargets({ ...params, path: "tools.web.search.apiKey" }); - } - const scopedConfig = searchConfig[params.provider.id]; - if (isRecord(scopedConfig) && configuredCredential === scopedConfig.apiKey) { - addConfigPathTargets({ - ...params, - path: `tools.web.search.${params.provider.id}.apiKey`, - }); - } -} - -function addConfiguredFetchCredentialTargetIds(params: { - config: OpenClawConfig; - provider: PluginWebFetchProviderEntry; - targetIds: Set; - targetPaths: Set; - allowedPaths: Set; -}): void { - const fetchConfig = resolveFetchConfig(params.config); - if (!fetchConfig) { - return; - } - const configuredCredential = params.provider.getCredentialValue(fetchConfig); - if (!isConfiguredSecretCandidate(configuredCredential)) { - return; - } - const pluginCredential = params.provider.getConfiguredCredentialValue?.(params.config); - if (isConfiguredSecretCandidate(pluginCredential) && configuredCredential !== pluginCredential) { - return; - } - const scopedConfig = fetchConfig[params.provider.id]; - if (isRecord(scopedConfig) && configuredCredential === scopedConfig.apiKey) { - addConfigPathTargets({ - ...params, - path: `tools.web.fetch.${params.provider.id}.apiKey`, - }); - } + return isConfiguredSecretCandidate(params.provider.getConfiguredCredentialValue?.(params.config)); } type ConfigPathTargetParams = { @@ -516,17 +436,9 @@ function addSelectedProviderCredentialTargets< config: OpenClawConfig; provider: Provider; state: SelectedProviderTargetState; - addConfiguredCredentialTargetIds: (targetParams: { - config: OpenClawConfig; - provider: Provider; - targetIds: Set; - targetPaths: Set; - allowedPaths: Set; - }) => void; hasConfiguredCredential: (provider: Provider) => boolean; }): boolean { - // A selected provider can own a direct path and a plugin-scoped path; include both so - // secret filtering does not hide the credential a provider actually resolved from config. + // Selected providers own one canonical plugin-scoped credential path. if (params.provider.credentialPath.trim()) { addConfigPathTargets({ path: params.provider.credentialPath, @@ -535,13 +447,6 @@ function addSelectedProviderCredentialTargets< allowedPaths: params.state.allowedPaths, }); } - params.addConfiguredCredentialTargetIds({ - config: params.config, - provider: params.provider, - targetIds: params.state.targetIds, - targetPaths: params.state.targetPaths, - allowedPaths: params.state.allowedPaths, - }); if (params.hasConfiguredCredential(params.provider)) { return true; } @@ -585,7 +490,6 @@ function getCapabilityWebSearchSelectedProviderTargetIds( config, provider, state, - addConfiguredCredentialTargetIds: addConfiguredSearchCredentialTargetIds, hasConfiguredCredential: (entry) => hasConfiguredSearchCredential({ provider: entry, config }), }) @@ -631,7 +535,6 @@ function getCapabilityWebFetchSelectedProviderTargetIds( config, provider, state, - addConfiguredCredentialTargetIds: addConfiguredFetchCredentialTargetIds, hasConfiguredCredential: (entry) => hasConfiguredFetchCredential({ provider: entry, config }), }); } diff --git a/src/cli/config-cli.test.ts b/src/cli/config-cli.test.ts index a44b71df39d7..7f61c7ad7cd0 100644 --- a/src/cli/config-cli.test.ts +++ b/src/cli/config-cli.test.ts @@ -3093,24 +3093,22 @@ describe("config cli", () => { it("fails dry-run when provider updates make existing refs unresolvable", async () => { const resolved: OpenClawConfig = { - gateway: { port: 18789 }, + gateway: { + port: 18789, + auth: { + mode: "token", + token: { + source: "file", + provider: "vaultfile", + id: "/providers/search/apiKey", + }, + }, + }, secrets: { providers: { vaultfile: { source: "file", path: "/tmp/secrets.json", mode: "json" }, }, }, - tools: { - web: { - search: { - enabled: true, - apiKey: { - source: "file", - provider: "vaultfile", - id: "/providers/search/apiKey", - }, - }, - }, - } as never, }; setSnapshot(resolved, resolved); mockResolveSecretRefValue.mockImplementationOnce(async () => { @@ -3134,24 +3132,22 @@ describe("config cli", () => { it("fails dry-run for nested provider edits that make existing refs unresolvable", async () => { const resolved: OpenClawConfig = { - gateway: { port: 18789 }, + gateway: { + port: 18789, + auth: { + mode: "token", + token: { + source: "file", + provider: "vaultfile", + id: "/providers/search/apiKey", + }, + }, + }, secrets: { providers: { vaultfile: { source: "file", path: "/tmp/secrets.json", mode: "json" }, }, }, - tools: { - web: { - search: { - enabled: true, - apiKey: { - source: "file", - provider: "vaultfile", - id: "/providers/search/apiKey", - }, - }, - }, - } as never, }; setSnapshot(resolved, resolved); mockResolveSecretRefValue.mockImplementationOnce(async () => { @@ -3582,24 +3578,22 @@ describe("config cli", () => { it("validates existing refs when unset dry-run removes all secret providers", async () => { const resolved: OpenClawConfig = { - gateway: { port: 18789 }, + gateway: { + port: 18789, + auth: { + mode: "token", + token: { + source: "file", + provider: "vaultfile", + id: "/providers/search/apiKey", + }, + }, + }, secrets: { providers: { vaultfile: { source: "file", path: "/tmp/secrets.json", mode: "json" }, }, }, - tools: { - web: { - search: { - enabled: true, - apiKey: { - source: "file", - provider: "vaultfile", - id: "/providers/search/apiKey", - }, - }, - }, - } as never, }; setSnapshot(resolved, resolved); setSnapshot(resolved, resolved); @@ -3620,7 +3614,10 @@ describe("config cli", () => { it("validates existing refs when unset dry-run removes secret defaults", async () => { const resolved: OpenClawConfig = { - gateway: { port: 18789 }, + gateway: { + port: 18789, + auth: { mode: "token", token: "${WEB_SEARCH_API_KEY}" }, + }, secrets: { defaults: { env: "vaultenv", @@ -3630,14 +3627,6 @@ describe("config cli", () => { vaultenv: { source: "env" }, }, }, - tools: { - web: { - search: { - enabled: true, - apiKey: "${WEB_SEARCH_API_KEY}", - }, - }, - } as never, } as OpenClawConfig; setSnapshot(resolved, resolved); setSnapshot(resolved, resolved); diff --git a/src/cli/plugins-cli.update.test.ts b/src/cli/plugins-cli.update.test.ts index d54d3a50b2ca..6dbc6c9cc26f 100644 --- a/src/cli/plugins-cli.update.test.ts +++ b/src/cli/plugins-cli.update.test.ts @@ -373,11 +373,21 @@ describe("plugins cli update", () => { expect(hookUpdateParams.hookIds).toEqual(["new-hooks"]); }); - it("uses resolved shipped install records instead of raw env placeholders", async () => { - const cfg = createTrackedPluginConfig({ - pluginId: "alpha", - spec: "@openclaw/alpha@1.0.0", - }); + it("uses persisted install records instead of retired config records", async () => { + const cfg = { + plugins: { + entries: { + alpha: { enabled: true }, + }, + }, + } as OpenClawConfig; + const persistedRecords = { + alpha: { + source: "npm", + spec: "@openclaw/alpha@1.0.0", + installPath: "/tmp/alpha", + }, + } as const; primeUpdateConfigSnapshot({ config: cfg, parsed: { @@ -392,8 +402,15 @@ describe("plugins cli update", () => { }, }, }); + setInstalledPluginIndexInstallRecords(persistedRecords); updateNpmInstalledPlugins.mockResolvedValue({ - config: cfg, + config: { + ...cfg, + plugins: { + ...cfg.plugins, + installs: persistedRecords, + }, + } as OpenClawConfig, changed: false, outcomes: [], }); @@ -401,7 +418,13 @@ describe("plugins cli update", () => { await runPluginsCommand(["plugins", "update", "alpha"]); const updateParams = expectSingleCallParams(updateNpmInstalledPlugins); - expect(updateParams.config).toEqual(cfg); + expect(updateParams.config).toEqual({ + ...cfg, + plugins: { + ...cfg.plugins, + installs: persistedRecords, + }, + }); }); it("rejects invalid config snapshots before updater side effects", async () => { @@ -970,7 +993,7 @@ describe("plugins cli update", () => { expect(writeConfigFile).not.toHaveBeenCalled(); }); - it("preflights legacy plugin-record cleanup before hook-only updater side effects", async () => { + it("ignores retired plugin records during hook-only ownership checks", async () => { const cfg = { hooks: { internal: { @@ -995,15 +1018,15 @@ describe("plugins cli update", () => { } as OpenClawConfig; primeBlockedUpdateConfig("plugins", cfg); - await expect(runPluginsCommand(["plugins", "update", "demo-hooks"])).rejects.toThrow( - "__exit__:1", - ); + await runPluginsCommand(["plugins", "update", "demo-hooks"]); - expect(runtimeErrors.at(-1)).toContain( - "Config plugins are stored in an external or unresolved top-level $include", - ); - expect(updateNpmInstalledPlugins).not.toHaveBeenCalled(); - expect(updateNpmInstalledHookPacks).not.toHaveBeenCalled(); + expect(runtimeErrors).toEqual([]); + const pluginUpdateParams = expectSingleCallParams(updateNpmInstalledPlugins); + expect(pluginUpdateParams.config).toEqual({ + ...cfg, + plugins: { installs: {} }, + }); + expect(updateNpmInstalledHookPacks).toHaveBeenCalledOnce(); expect(writeConfigFile).not.toHaveBeenCalled(); }); @@ -1070,129 +1093,6 @@ describe("plugins cli update", () => { expect(writeConfigFile).not.toHaveBeenCalled(); }); - it("preserves an include-owned plugins section during legacy-record cleanup", async () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-update-")); - const configPath = path.join(tempRoot, "openclaw.json5"); - const pluginsPath = path.join(tempRoot, "plugins.json5"); - const cfg = createTrackedPluginConfig({ - pluginId: "alpha", - spec: "@openclaw/alpha@1.0.0", - }); - const pluginsRaw = `${JSON.stringify(cfg.plugins, null, 2)}\n`; - const nextConfig = createTrackedPluginConfig({ - pluginId: "alpha", - spec: "@openclaw/alpha@1.1.0", - }); - fs.writeFileSync(pluginsPath, pluginsRaw); - primeUpdateConfigSnapshot({ - config: cfg, - configPath, - parsed: { plugins: { $include: "./plugins.json5" } }, - includeFileHashesForWrite: { - [pluginsPath]: hashConfigIncludeRaw(pluginsRaw), - }, - includeFileTargetsForWrite: { - [pluginsPath]: fs.realpathSync(pluginsPath), - }, - }); - setInstalledPluginIndexInstallRecords(cfg.plugins?.installs ?? {}); - updateNpmInstalledPlugins.mockResolvedValue({ - config: nextConfig, - changed: true, - outcomes: [{ pluginId: "alpha", status: "updated", message: "Updated alpha." }], - }); - - try { - await runPluginsCommand(["plugins", "update", "alpha"]); - - expect(runtimeErrors).toEqual([]); - expect(updateNpmInstalledPlugins).toHaveBeenCalledOnce(); - expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( - nextConfig.plugins?.installs, - ); - expect(writeConfigFile).toHaveBeenCalledWith({ plugins: {} }); - } finally { - fs.rmSync(tempRoot, { recursive: true, force: true }); - } - }); - - it("migrates included legacy install records while updating another indexed plugin", async () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-update-")); - const configPath = path.join(tempRoot, "openclaw.json5"); - const pluginsPath = path.join(tempRoot, "plugins.json5"); - const legacyRecord = { - source: "npm", - spec: "@openclaw/legacy@1.0.0", - installPath: "/tmp/legacy", - } as const; - const indexedRecord = { - source: "npm", - spec: "@openclaw/alpha@1.0.0", - installPath: "/tmp/alpha", - } as const; - const updatedIndexedRecord = { - ...indexedRecord, - spec: "@openclaw/alpha@1.1.0", - } as const; - const cfg = { - plugins: { - installs: { - legacy: legacyRecord, - }, - }, - } as OpenClawConfig; - const pluginsRaw = `${JSON.stringify(cfg.plugins, null, 2)}\n`; - const nextInstallRecords = { - alpha: updatedIndexedRecord, - legacy: legacyRecord, - }; - fs.writeFileSync(pluginsPath, pluginsRaw); - primeUpdateConfigSnapshot({ - config: cfg, - configPath, - parsed: { plugins: { $include: "./plugins.json5" } }, - includeFileHashesForWrite: { - [pluginsPath]: hashConfigIncludeRaw(pluginsRaw), - }, - includeFileTargetsForWrite: { - [pluginsPath]: fs.realpathSync(pluginsPath), - }, - }); - setInstalledPluginIndexInstallRecords({ - alpha: indexedRecord, - }); - updateNpmInstalledPlugins.mockResolvedValue({ - config: { - plugins: { - installs: nextInstallRecords, - }, - } as OpenClawConfig, - changed: true, - outcomes: [{ pluginId: "alpha", status: "updated", message: "Updated alpha." }], - }); - - try { - await runPluginsCommand(["plugins", "update", "alpha"]); - - expect(runtimeErrors).toEqual([]); - const updateParams = expectSingleCallParams(updateNpmInstalledPlugins); - expect(updateParams.config).toEqual({ - plugins: { - installs: { - alpha: indexedRecord, - legacy: legacyRecord, - }, - }, - }); - expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( - nextInstallRecords, - ); - expect(writeConfigFile).toHaveBeenCalledWith({ plugins: {} }); - } finally { - fs.rmSync(tempRoot, { recursive: true, force: true }); - } - }); - it("blocks combined plugin and hook updates when either config section uses an include", async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-update-")); const configPath = path.join(tempRoot, "openclaw.json5"); @@ -1212,11 +1112,9 @@ describe("plugins cli update", () => { }, }, plugins: { - installs: { - alpha: { - source: "npm", - spec: "@openclaw/alpha@1.0.0", - installPath: "/tmp/alpha", + entries: { + "voice-call": { + enabled: true, }, }, }, @@ -1235,7 +1133,13 @@ describe("plugins cli update", () => { [pluginsPath]: fs.realpathSync(pluginsPath), }, }); - setInstalledPluginIndexInstallRecords(cfg.plugins?.installs ?? {}); + setInstalledPluginIndexInstallRecords({ + "voice-call": { + source: "npm", + spec: "@openclaw/voice-call@1.0.0", + installPath: "/tmp/voice-call", + }, + }); try { await expect(runPluginsCommand(["plugins", "update", "--all"])).rejects.toThrow("__exit__:1"); diff --git a/src/cli/plugins-inspect-command.ts b/src/cli/plugins-inspect-command.ts index 1548fc7b1ba1..f258d4e6e2f3 100644 --- a/src/cli/plugins-inspect-command.ts +++ b/src/cli/plugins-inspect-command.ts @@ -37,19 +37,10 @@ function formatCapabilityKinds( return capabilities.map((entry) => entry.kind).join(", "); } -function formatHookSummary(params: { - usesLegacyBeforeAgentStart: boolean; - typedHookCount: number; - customHookCount: number; -}): string { +function formatHookSummary(params: { typedHookCount: number; customHookCount: number }): string { const parts: string[] = []; - if (params.usesLegacyBeforeAgentStart) { - parts.push("before_agent_start"); - } - const nonLegacyTypedHookCount = - params.typedHookCount - (params.usesLegacyBeforeAgentStart ? 1 : 0); - if (nonLegacyTypedHookCount > 0) { - parts.push(`${nonLegacyTypedHookCount} typed`); + if (params.typedHookCount > 0) { + parts.push(`${params.typedHookCount} typed`); } if (params.customHookCount > 0) { parts.push(`${params.customHookCount} custom`); @@ -196,7 +187,6 @@ export async function runPluginsInspectCommand( : "none", Bundle: inspect.bundleCapabilities.length > 0 ? inspect.bundleCapabilities.join(", ") : "-", Hooks: formatHookSummary({ - usesLegacyBeforeAgentStart: inspect.usesLegacyBeforeAgentStart, typedHookCount: inspect.typedHooks.length, customHookCount: inspect.customHooks.length, }), @@ -318,9 +308,6 @@ export async function runPluginsInspectCommand( } lines.push(`${theme.muted("Shape:")} ${inspect.shape}`); lines.push(`${theme.muted("Capability mode:")} ${inspect.capabilityMode}`); - lines.push( - `${theme.muted("Legacy before_agent_start:")} ${inspect.usesLegacyBeforeAgentStart ? "yes" : "no"}`, - ); if (inspect.bundleCapabilities.length > 0) { lines.push(`${theme.muted("Bundle capabilities:")} ${inspect.bundleCapabilities.join(", ")}`); } diff --git a/src/cli/plugins-update-command.ts b/src/cli/plugins-update-command.ts index e0b0052e87cc..9bb8ebf7468e 100644 --- a/src/cli/plugins-update-command.ts +++ b/src/cli/plugins-update-command.ts @@ -15,7 +15,6 @@ import type { ConfigWriteOptions } from "../config/io.js"; import { createMergePatch } from "../config/io.write-prepare.js"; import { applyMergePatch } from "../config/merge-patch.js"; import { ConfigMutationConflictError } from "../config/mutate.js"; -import { extractShippedPluginInstallConfigRecords } from "../config/plugin-install-config-migration.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { PluginInstallRecord } from "../config/types.plugins.js"; import { updateNpmInstalledHookPacks } from "../hooks/update.js"; @@ -201,18 +200,8 @@ export async function runPluginUpdateCommand(params: { // mutation-start snapshot so concurrent config changes cannot be resurrected. const cfg = mutationSnapshot?.snapshot.runtimeConfig ?? getRuntimeConfig(); const sourceCfg = mutationSnapshot?.snapshot.sourceConfig ?? cfg; - const shippedPluginInstallRecords = mutationSnapshot - ? { - ...extractShippedPluginInstallConfigRecords(mutationSnapshot.snapshot.parsed), - ...extractShippedPluginInstallConfigRecords(mutationSnapshot.snapshot.sourceConfig), - } - : extractShippedPluginInstallConfigRecords(cfg); const persistedPluginInstallRecords = await loadInstalledPluginIndexInstallRecords(); - // Persisted index records win over shipped legacy config during migration. - const pluginInstallRecords = { - ...shippedPluginInstallRecords, - ...persistedPluginInstallRecords, - }; + const pluginInstallRecords = persistedPluginInstallRecords; const cfgWithPluginInstallRecords = withPluginInstallRecords(cfg, pluginInstallRecords); const sourceCfgWithPluginInstallRecords = withPluginInstallRecords( sourceCfg, @@ -272,13 +261,6 @@ export async function runPluginUpdateCommand(params: { snapshotPath: mutationSnapshot.snapshot.path, writeOptions: mutationSnapshot.writeOptions, }); - // Write snapshots retain valid shipped install records in sourceConfig after - // include resolution; parsed also catches root-authored legacy records. - const pluginRecordCleanupMayMutate = - Object.keys(extractShippedPluginInstallConfigRecords(mutationSnapshot.snapshot.sourceConfig)) - .length > 0 || - Object.keys(extractShippedPluginInstallConfigRecords(mutationSnapshot.snapshot.parsed)) - .length > 0; const parsedConfig = mutationSnapshot.snapshot.parsed && typeof mutationSnapshot.snapshot.parsed === "object" && @@ -299,9 +281,9 @@ export async function runPluginUpdateCommand(params: { pluginConfigReferencesId(mutationSnapshot.snapshot.sourceConfig, pluginId)) ); }); - // Manual update records stay in the index unless shipped-record cleanup or - // scoped-package compatibility migrates authored references from a legacy id. - const pluginConfigMayMutate = pluginRecordCleanupMayMutate || pluginIdMigrationMayMutate; + // Manual update records stay in the index unless scoped-package compatibility + // migrates authored references from a legacy id. + const pluginConfigMayMutate = pluginIdMigrationMayMutate; const blockedReasons = new Set(); if (pluginConfigMayMutate && pluginMutation.mode === "blocked") { blockedReasons.add(pluginMutation.reason); diff --git a/src/commands/auth-choice-legacy.test.ts b/src/commands/auth-choice-legacy.test.ts index 21dcddb94122..2c2f8caf7bf7 100644 --- a/src/commands/auth-choice-legacy.test.ts +++ b/src/commands/auth-choice-legacy.test.ts @@ -36,7 +36,6 @@ function authChoiceManifestEnv(): NodeJS.ProcessEnv { return { OPENCLAW_BUNDLED_PLUGINS_DIR: "extensions", OPENCLAW_DISABLE_BUNDLED_PLUGINS: "0", - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", VITEST: "1", } as NodeJS.ProcessEnv; } diff --git a/src/commands/channel-setup/discovery.ts b/src/commands/channel-setup/discovery.ts index 3c0618c65fc2..4c257a61da0c 100644 --- a/src/commands/channel-setup/discovery.ts +++ b/src/commands/channel-setup/discovery.ts @@ -22,9 +22,7 @@ type ChannelCatalogEntry = { }; /** Return true when channel metadata should appear in setup/onboarding choices. */ -export function shouldShowChannelInSetup( - meta: Pick, -): boolean { +export function shouldShowChannelInSetup(meta: Pick): boolean { return isChannelVisibleInSetup(meta); } diff --git a/src/commands/channels.status.external-env.test.ts b/src/commands/channels.status.external-env.test.ts index 5f086249c0c5..039d127327ff 100644 --- a/src/commands/channels.status.external-env.test.ts +++ b/src/commands/channels.status.external-env.test.ts @@ -60,6 +60,10 @@ function writeExternalEnvChannelPlugin() { version: "1.0.0", openclaw: { extensions: ["./index.cjs"], + channel: { + id: "external-env-channel", + configuredState: { env: { anyOf: ["EXTERNAL_ENV_CHANNEL_TOKEN"] } }, + }, }, }, null, @@ -74,9 +78,6 @@ function writeExternalEnvChannelPlugin() { id: "external-env-channel-plugin", configSchema: EMPTY_PLUGIN_SCHEMA, channels: ["external-env-channel"], - channelEnvVars: { - "external-env-channel": ["EXTERNAL_ENV_CHANNEL_TOKEN"], - }, }, null, 2, diff --git a/src/commands/config-validation.test.ts b/src/commands/config-validation.test.ts index 7f7189976f27..a0bedc8508be 100644 --- a/src/commands/config-validation.test.ts +++ b/src/commands/config-validation.test.ts @@ -34,7 +34,7 @@ describe("requireValidConfigSnapshot", () => { issues: [], }); buildPluginCompatibilitySnapshotNotices.mockReturnValue([ - createCompatibilityNotice({ pluginId: "legacy-plugin", code: "legacy-before-agent-start" }), + createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }), ]); } @@ -85,7 +85,7 @@ describe("requireValidConfigSnapshot", () => { expect(requireFirstLog(runtime)).toBe( [ "Plugin compatibility: 1 notice.", - "- legacy-plugin still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.", + "- legacy-plugin is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet.", "Review: openclaw doctor", ].join("\n"), ); diff --git a/src/commands/doctor-legacy-config.migrations.test.ts b/src/commands/doctor-legacy-config.migrations.test.ts index 483c3e5685d5..33a2e642dc41 100644 --- a/src/commands/doctor-legacy-config.migrations.test.ts +++ b/src/commands/doctor-legacy-config.migrations.test.ts @@ -58,6 +58,10 @@ vi.mock("../plugins/manifest-registry.js", () => ({ }, })); +function legacyConfig(value: unknown): OpenClawConfig { + return value as OpenClawConfig; +} + vi.mock("./doctor/shared/channel-legacy-config-migrate.js", () => ({ applyChannelDoctorCompatibilityMigrations: (cfg: OpenClawConfig) => ({ next: cfg, @@ -1543,25 +1547,27 @@ describe("normalizeCompatibilityConfigValues", () => { }); it("migrates legacy web search provider config to plugin-owned config paths", () => { - const res = normalizeCompatibilityConfigValues({ - tools: { - web: { - search: { - provider: "gemini", - maxResults: 5, - apiKey: "brave-key", - gemini: { - apiKey: "gemini-key", - model: "gemini-2.5-flash", - }, - firecrawl: { - apiKey: "firecrawl-key", - baseUrl: "https://api.firecrawl.dev", + const res = normalizeCompatibilityConfigValues( + legacyConfig({ + tools: { + web: { + search: { + provider: "gemini", + maxResults: 5, + apiKey: "brave-key", + gemini: { + apiKey: "gemini-key", + model: "gemini-2.5-flash", + }, + firecrawl: { + apiKey: "firecrawl-key", + baseUrl: "https://api.firecrawl.dev", + }, }, }, }, - }, - }); + }), + ); expect(res.config.tools?.web?.search).toEqual({ provider: "gemini", @@ -1601,32 +1607,34 @@ describe("normalizeCompatibilityConfigValues", () => { }); it("merges legacy web search provider config into explicit plugin config without overriding it", () => { - const res = normalizeCompatibilityConfigValues({ - tools: { - web: { - search: { - provider: "gemini", - gemini: { - apiKey: "legacy-gemini-key", - model: "legacy-model", - }, - }, - }, - }, - plugins: { - entries: { - google: { - enabled: true, - config: { - webSearch: { - model: "explicit-model", - baseUrl: "https://generativelanguage.googleapis.com", + const res = normalizeCompatibilityConfigValues( + legacyConfig({ + tools: { + web: { + search: { + provider: "gemini", + gemini: { + apiKey: "legacy-gemini-key", + model: "legacy-model", }, }, }, }, - }, - }); + plugins: { + entries: { + google: { + enabled: true, + config: { + webSearch: { + model: "explicit-model", + baseUrl: "https://generativelanguage.googleapis.com", + }, + }, + }, + }, + }, + }), + ); expect(res.config.tools?.web?.search).toEqual({ provider: "gemini", diff --git a/src/commands/doctor-memory-search.test.ts b/src/commands/doctor-memory-search.test.ts index 6db2f1da252d..8444a0a56c3a 100644 --- a/src/commands/doctor-memory-search.test.ts +++ b/src/commands/doctor-memory-search.test.ts @@ -61,7 +61,7 @@ vi.mock("../memory-host-sdk/engine-qmd.js", () => ({ resolveQmdBinaryUnavailableReason: (result: { reason?: string }) => result.reason ?? "binary", })); -vi.mock("../plugin-sdk/memory-core-engine-runtime.js", () => ({ +vi.mock("../plugin-sdk/memory-core-bundled-runtime.js", () => ({ auditDreamingArtifacts, auditShortTermPromotionArtifacts, repairDreamingArtifacts, diff --git a/src/commands/doctor-memory-search.ts b/src/commands/doctor-memory-search.ts index 87ec82e6bb38..455229236221 100644 --- a/src/commands/doctor-memory-search.ts +++ b/src/commands/doctor-memory-search.ts @@ -39,7 +39,7 @@ import { repairShortTermPromotionArtifacts, type DreamingArtifactsAuditSummary, type ShortTermAuditSummary, -} from "../plugin-sdk/memory-core-engine-runtime.js"; +} from "../plugin-sdk/memory-core-bundled-runtime.js"; import { normalizePluginsConfig } from "../plugins/config-state.js"; import { getActiveMemorySearchManager, diff --git a/src/commands/doctor-plugin-registry.test.ts b/src/commands/doctor-plugin-registry.test.ts index eebda9f95c75..3d1dc890a0e7 100644 --- a/src/commands/doctor-plugin-registry.test.ts +++ b/src/commands/doctor-plugin-registry.test.ts @@ -20,7 +20,6 @@ import { pluginRegistryIssueToHealthFinding, pluginRegistryIssueToRepairEffect, } from "./doctor-plugin-registry.js"; -import { DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV } from "./doctor/shared/plugin-registry-migration.js"; vi.mock("../../packages/terminal-core/src/note.js", () => ({ note: vi.fn(), @@ -473,22 +472,6 @@ describe("maybeRepairPluginRegistryState", () => { ]); }); - it("does not repair when registry migration is disabled", async () => { - const stateDir = makeTempDir(); - - const nextConfig = await maybeRepairPluginRegistryState({ - stateDir, - env: hermeticEnv({ - [DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV]: "1", - }), - config: {}, - prompter: { shouldRepair: true }, - }); - - expect(nextConfig).toStrictEqual({}); - expect(vi.mocked(note).mock.calls.join("\n")).toContain(DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV); - }); - it("warns about stale managed npm packages that shadow bundled plugins", async () => { const stateDir = makeTempDir(); const bundledDir = path.join(stateDir, "bundled", "google-meet"); diff --git a/src/commands/doctor-plugin-registry.ts b/src/commands/doctor-plugin-registry.ts index 0044863fdb22..bbeb05300c33 100644 --- a/src/commands/doctor-plugin-registry.ts +++ b/src/commands/doctor-plugin-registry.ts @@ -30,7 +30,6 @@ import { import { shortenHomePath } from "../utils.js"; import type { DoctorPrompter } from "./doctor-prompter.js"; import { - DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV, migratePluginRegistryForInstall, preflightPluginRegistryInstallMigration, type PluginRegistryInstallMigrationParams, @@ -647,16 +646,6 @@ export async function maybeRepairPluginRegistryState( params: PluginRegistryDoctorRepairParams, ): Promise { const preflight = preflightPluginRegistryInstallMigration(params); - for (const warning of preflight.deprecationWarnings) { - note(warning, "Plugin registry"); - } - if (preflight.action === "disabled") { - note( - `${DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV} is set; skipping plugin registry repair.`, - "Plugin registry", - ); - return params.config; - } const migrationParams = { ...params, diff --git a/src/commands/doctor-state-integrity.test.ts b/src/commands/doctor-state-integrity.test.ts index 3ca6f6dd290f..f31f3f2ed1fb 100644 --- a/src/commands/doctor-state-integrity.test.ts +++ b/src/commands/doctor-state-integrity.test.ts @@ -366,7 +366,7 @@ describe("doctor state integrity oauth dir checks", () => { expect(text).not.toContain("Examples:"); }); - it("does not warn when the live compatibility main agent dir is missing from agents.list", async () => { + it("reports an unconfigured main agent dir after compatibility removal", async () => { createAgentDir("main"); const text = await runStateIntegrityText({ @@ -375,11 +375,11 @@ describe("doctor state integrity oauth dir checks", () => { }, }); - expect(text).not.toContain("without a matching agents.list entry"); - expect(text).not.toContain("Examples:"); + expect(text).toContain("without a matching agents.list entry"); + expect(text).toContain("Examples: main"); }); - it("does not warn when OPENCLAW_AGENT_DIR points at the live compatibility agent dir", async () => { + it("does not let OPENCLAW_AGENT_DIR hide an unconfigured agent dir", async () => { createAgentDir("legacy"); const legacyAgentDir = path.join( process.env.OPENCLAW_STATE_DIR ?? "", @@ -395,8 +395,8 @@ describe("doctor state integrity oauth dir checks", () => { }, }); - expect(text).not.toContain("without a matching agents.list entry"); - expect(text).not.toContain("Examples:"); + expect(text).toContain("without a matching agents.list entry"); + expect(text).toContain("Examples: legacy"); }); it("warns about tombstoned subagent restart recovery sessions", async () => { diff --git a/src/commands/doctor-state-integrity.ts b/src/commands/doctor-state-integrity.ts index 260a1d0d32e6..c0d1b2e8ca31 100644 --- a/src/commands/doctor-state-integrity.ts +++ b/src/commands/doctor-state-integrity.ts @@ -7,7 +7,11 @@ import { asNullableObjectRecord } from "@openclaw/normalization-core/record-coer import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { note } from "../../packages/terminal-core/src/note.js"; -import { listAgentEntries, resolveDefaultAgentId } from "../agents/agent-scope.js"; +import { + listAgentEntries, + resolveDefaultAgentDir, + resolveDefaultAgentId, +} from "../agents/agent-scope.js"; import { clearWedgedSubagentRecoveryAbort, formatSubagentRecoveryWedgedReason, @@ -32,7 +36,6 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { HealthFinding, HealthRepairEffect } from "../flows/health-checks.js"; import { resolveRequiredHomeDir } from "../infra/home-dir.js"; import { resolveMemoryBackendConfig } from "../memory-host-sdk/engine-storage.js"; -import { resolveOpenClawAgentDir } from "../plugin-sdk/agent-dir-compat.js"; import { listConfiguredChannelIdsForReadOnlyScope } from "../plugins/channel-plugin-ids.js"; import { normalizeAgentId } from "../routing/session-key.js"; import { parseAgentSessionKey } from "../sessions/session-key-utils.js"; @@ -179,7 +182,7 @@ function listOrphanAgentDirs(cfg: OpenClawConfig, stateDir: string): OrphanAgent } const agentsRoot = path.join(stateDir, "agents"); - const liveCompatibilityAgentDir = resolveOpenClawAgentDir(); + const liveDefaultAgentDir = resolveDefaultAgentDir(cfg); try { const entries = fs.readdirSync(agentsRoot, { withFileTypes: true }); return entries @@ -194,7 +197,7 @@ function listOrphanAgentDirs(cfg: OpenClawConfig, stateDir: string): OrphanAgent if (!hasNestedAgentDir) { return false; } - if (areComparablePathsEqual(nestedAgentDir, liveCompatibilityAgentDir)) { + if (areComparablePathsEqual(nestedAgentDir, liveDefaultAgentDir)) { return false; } if (!configuredIds.has(agentId)) { diff --git a/src/commands/doctor-workspace-status.test.ts b/src/commands/doctor-workspace-status.test.ts index 44f94d70be61..571b313f9bbe 100644 --- a/src/commands/doctor-workspace-status.test.ts +++ b/src/commands/doctor-workspace-status.test.ts @@ -85,7 +85,7 @@ describe("noteWorkspaceStatus", () => { }), ], typedHooks: [ - createTypedHook({ pluginId: "legacy-plugin", hookName: "before_agent_start" }), + createTypedHook({ pluginId: "legacy-plugin", hookName: "before_prompt_build" }), ], }), ); @@ -230,9 +230,7 @@ describe("noteWorkspaceStatus", () => { ], }), }); - mocks.buildPluginCompatibilityWarnings.mockReturnValue([ - "legacy-plugin still uses legacy before_agent_start", - ]); + mocks.buildPluginCompatibilityWarnings.mockReturnValue(["legacy-plugin is hook-only"]); mocks.listTaskFlowRecords.mockReturnValue([ { flowId: "flow-123", @@ -258,7 +256,7 @@ describe("noteWorkspaceStatus", () => { severity: "warning", path: "plugins", requirement: "plugin-compatibility", - message: "legacy-plugin still uses legacy before_agent_start", + message: "legacy-plugin is hook-only", }), expect.objectContaining({ checkId: "core/doctor/workspace-status", @@ -437,11 +435,9 @@ describe("noteWorkspaceStatus", () => { hookCount: 1, }), ], - typedHooks: [createTypedHook({ pluginId: "legacy-plugin", hookName: "before_agent_start" })], + typedHooks: [createTypedHook({ pluginId: "legacy-plugin", hookName: "before_prompt_build" })], }); - const noteSpy = await runNoteWorkspaceStatusForTest(loadResult, [ - "legacy-plugin still uses legacy before_agent_start", - ]); + const noteSpy = await runNoteWorkspaceStatusForTest(loadResult, ["legacy-plugin is hook-only"]); try { expect(mocks.buildPluginRegistrySnapshotReport).toHaveBeenCalledWith({ config: {}, @@ -460,7 +456,7 @@ describe("noteWorkspaceStatus", () => { ); expect(compatibilityCalls).toHaveLength(1); const [body] = expectDefined(compatibilityCalls[0], "(compatibilityCalls)[0] test invariant"); - expect(body).toContain("legacy-plugin still uses legacy before_agent_start"); + expect(body).toContain("legacy-plugin is hook-only"); } finally { noteSpy.mockRestore(); } diff --git a/src/commands/doctor/shared/channel-plugin-blockers.test.ts b/src/commands/doctor/shared/channel-plugin-blockers.test.ts index 0d0f66683ed2..1fca197e6bc8 100644 --- a/src/commands/doctor/shared/channel-plugin-blockers.test.ts +++ b/src/commands/doctor/shared/channel-plugin-blockers.test.ts @@ -11,12 +11,19 @@ import { scanConfiguredChannelPluginBlockers, } from "./channel-plugin-blockers.js"; +function createPackageChannelEnv(channelId: string, envVars: string[]) { + return { + id: channelId, + configuredState: { env: { anyOf: envVars } }, + }; +} + describe("channel plugin blockers", () => { beforeEach(() => { vi.restoreAllMocks(); }); - it("returns no blockers when config and manifest env have no channel surfaces", () => { + it("returns no blockers when config and package env have no channel surfaces", () => { const registrySpy = vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [], diagnostics: [], @@ -359,16 +366,14 @@ describe("channel plugin blockers", () => { ]); }); - it("diagnoses an external-only manifest env channel that lacks source trust", () => { + it("diagnoses an external-only package env channel that lacks source trust", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "discord", origin: "global", channels: ["discord"], - channelEnvVars: { - discord: ["DISCORD_BOT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("discord", ["DISCORD_BOT_TOKEN"]), enabledByDefault: false, }, ], @@ -398,7 +403,41 @@ describe("channel plugin blockers", () => { ]); }); - it("keeps manifest env trust diagnostics scoped to the declaring owner", () => { + it("requires every package channel allOf environment variable", () => { + vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ + plugins: [ + { + id: "irc", + origin: "global", + channels: ["irc"], + packageChannel: { + id: "irc", + configuredState: { env: { allOf: ["IRC_HOST", "IRC_NICK"] } }, + }, + enabledByDefault: false, + }, + ], + diagnostics: [], + } as unknown as ReturnType); + + expect( + scanConfiguredChannelPluginBlockers({}, { IRC_HOST: "configured" } as NodeJS.ProcessEnv), + ).toStrictEqual([]); + expect( + scanConfiguredChannelPluginBlockers({}, { + IRC_HOST: "configured", + IRC_NICK: "configured", + } as NodeJS.ProcessEnv), + ).toEqual([ + { + channelId: "irc", + pluginId: "irc", + reason: "missing explicit enablement", + }, + ]); + }); + + it("keeps package env trust diagnostics scoped to the declaring owner", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { @@ -411,9 +450,7 @@ describe("channel plugin blockers", () => { id: "external-chat", origin: "config", channels: ["shared-chat"], - channelEnvVars: { - "shared-chat": ["EXTERNAL_CHAT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("shared-chat", ["EXTERNAL_CHAT_TOKEN"]), enabledByDefault: false, }, ], @@ -466,25 +503,21 @@ describe("channel plugin blockers", () => { ).toBe(true); }); - it("accepts an available co-owner for the same manifest env trigger", () => { + it("accepts an available co-owner for the same package env trigger", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "bundled-chat", origin: "bundled", channels: ["shared-chat"], - channelEnvVars: { - "shared-chat": ["SHARED_CHAT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("shared-chat", ["SHARED_CHAT_TOKEN"]), enabledByDefault: true, }, { id: "external-chat", origin: "config", channels: ["shared-chat"], - channelEnvVars: { - "shared-chat": ["SHARED_CHAT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("shared-chat", ["SHARED_CHAT_TOKEN"]), enabledByDefault: false, }, ], @@ -498,25 +531,21 @@ describe("channel plugin blockers", () => { expect(hits).toStrictEqual([]); }); - it("deduplicates global plugin disablement across manifest env triggers", () => { + it("deduplicates global plugin disablement across package env triggers", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "first-chat", origin: "config", channels: ["shared-chat"], - channelEnvVars: { - "shared-chat": ["FIRST_CHAT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("shared-chat", ["FIRST_CHAT_TOKEN"]), enabledByDefault: false, }, { id: "second-chat", origin: "config", channels: ["shared-chat"], - channelEnvVars: { - "shared-chat": ["SECOND_CHAT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("shared-chat", ["SECOND_CHAT_TOKEN"]), enabledByDefault: false, }, ], @@ -544,16 +573,14 @@ describe("channel plugin blockers", () => { ]); }); - it("does not report unrelated blocked owners for a manifest env trigger", () => { + it("does not report unrelated blocked owners for a package env trigger", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "triggered-chat", origin: "config", channels: ["shared-chat"], - channelEnvVars: { - "shared-chat": ["TRIGGERED_CHAT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("shared-chat", ["TRIGGERED_CHAT_TOKEN"]), enabledByDefault: false, }, { @@ -579,16 +606,14 @@ describe("channel plugin blockers", () => { ]); }); - it("ignores manifest env mappings for channels the plugin does not own", () => { + it("ignores package env mappings for channels the plugin does not own", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "external-chat", origin: "config", channels: ["external-chat"], - channelEnvVars: { - discord: ["EXTERNAL_CHAT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("discord", ["EXTERNAL_CHAT_TOKEN"]), enabledByDefault: false, }, ], @@ -602,16 +627,14 @@ describe("channel plugin blockers", () => { expect(hits).toStrictEqual([]); }); - it("diagnoses a manifest env channel whose bundled owner is opt-in", () => { + it("diagnoses a package env channel whose bundled owner is opt-in", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "twitch", origin: "bundled", channels: ["twitch"], - channelEnvVars: { - twitch: ["OPENCLAW_TWITCH_ACCESS_TOKEN"], - }, + packageChannel: createPackageChannelEnv("twitch", ["OPENCLAW_TWITCH_ACCESS_TOKEN"]), enabledByDefault: false, }, ], @@ -641,9 +664,7 @@ describe("channel plugin blockers", () => { id: "twitch", origin: "bundled", channels: ["twitch"], - channelEnvVars: { - twitch: ["OPENCLAW_TWITCH_ACCESS_TOKEN"], - }, + packageChannel: createPackageChannelEnv("twitch", ["OPENCLAW_TWITCH_ACCESS_TOKEN"]), enabledByDefault: false, }, ], @@ -673,16 +694,14 @@ describe("channel plugin blockers", () => { ]); }); - it("keeps manifest env blockers when another channel is explicitly configured", () => { + it("keeps package env blockers when another channel is explicitly configured", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "discord", origin: "global", channels: ["discord"], - channelEnvVars: { - discord: ["DISCORD_BOT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("discord", ["DISCORD_BOT_TOKEN"]), enabledByDefault: false, }, ], @@ -723,16 +742,14 @@ describe("channel plugin blockers", () => { ]); }); - it("honors explicit channel disablement over manifest env triggers", () => { + it("honors explicit channel disablement over package env triggers", () => { vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({ plugins: [ { id: "discord", origin: "global", channels: ["discord"], - channelEnvVars: { - discord: ["DISCORD_BOT_TOKEN"], - }, + packageChannel: createPackageChannelEnv("discord", ["DISCORD_BOT_TOKEN"]), enabledByDefault: false, }, ], diff --git a/src/commands/doctor/shared/channel-plugin-blockers.ts b/src/commands/doctor/shared/channel-plugin-blockers.ts index 329d0fa0338a..a61790f9be8b 100644 --- a/src/commands/doctor/shared/channel-plugin-blockers.ts +++ b/src/commands/doctor/shared/channel-plugin-blockers.ts @@ -64,7 +64,7 @@ export function scanConfiguredChannelPluginBlockers( env, includeDisabled: true, }).plugins; - const manifestEnvTriggers = listManifestEnvConfiguredChannelTriggers(manifestRecords, env); + const packageEnvTriggers = listPackageEnvConfiguredChannelTriggers(manifestRecords, env); const policyEntries = resolveConfiguredChannelPresencePolicy({ config: cfg, activationSourceConfig, @@ -72,12 +72,12 @@ export function scanConfiguredChannelPluginBlockers( includePersistedAuthState: false, manifestRecords, }); - // A manifest env match identifies one owner. Do not widen the same ambient env signal to + // A package env match identifies one owner. Do not widen the same ambient env signal to // sibling owners that cannot consume that credential. const policyChannelIds = policyEntries .filter( (entry) => - !manifestEnvTriggers.has(entry.channelId) || + !packageEnvTriggers.has(entry.channelId) || entry.sources.some((source) => source !== "env" && source !== "manifest-env"), ) .map((entry) => entry.channelId); @@ -88,9 +88,9 @@ export function scanConfiguredChannelPluginBlockers( for (const channelId of listExplicitlyDisabledChannelIdsForConfig(cfg)) { const normalizedChannelId = normalizeOptionalLowercaseString(channelId) ?? channelId; genericChannelIds.delete(normalizedChannelId); - manifestEnvTriggers.delete(normalizedChannelId); + packageEnvTriggers.delete(normalizedChannelId); } - if (genericChannelIds.size === 0 && manifestEnvTriggers.size === 0) { + if (genericChannelIds.size === 0 && packageEnvTriggers.size === 0) { return []; } const hits: ChannelPluginBlockerHit[] = []; @@ -150,7 +150,7 @@ export function scanConfiguredChannelPluginBlockers( addHits(channelId, ownerStates); } - for (const [channelId, triggers] of manifestEnvTriggers) { + for (const [channelId, triggers] of packageEnvTriggers) { const channelOwnerStates = manifestRecords .filter((plugin) => plugin.channels.some( @@ -180,7 +180,7 @@ export function scanConfiguredChannelPluginBlockers( return hits; } -function listManifestEnvConfiguredChannelTriggers( +function listPackageEnvConfiguredChannelTriggers( plugins: readonly PluginManifestRecord[], env: NodeJS.ProcessEnv, ): Map>> { @@ -191,32 +191,39 @@ function listManifestEnvConfiguredChannelTriggers( .map((channelId) => normalizeOptionalLowercaseString(channelId)) .filter((channelId): channelId is string => Boolean(channelId)), ); - for (const [rawChannelId, envVars] of Object.entries(plugin.channelEnvVars ?? {})) { - const channelId = normalizeOptionalLowercaseString(rawChannelId); - if (!channelId || !ownedChannelIds.has(channelId)) { - continue; + const channelId = normalizeOptionalLowercaseString(plugin.packageChannel?.id); + if (!channelId || !ownedChannelIds.has(channelId)) { + continue; + } + const channelEnv = plugin.packageChannel?.configuredState?.env; + const allOf = channelEnv?.allOf ?? []; + const anyOf = channelEnv?.anyOf ?? []; + if (allOf.length === 0 && anyOf.length === 0) { + continue; + } + let triggers = triggersByChannelId.get(channelId); + if (!triggers) { + triggers = new Map(); + triggersByChannelId.set(channelId, triggers); + } + const hasEnvValue = (envVar: string) => { + if (!isSafeChannelEnvVarTriggerName(envVar)) { + return false; } - for (const envVar of envVars) { - if (!isSafeChannelEnvVarTriggerName(envVar)) { - continue; - } - const value = env[envVar] ?? env[envVar.toUpperCase()]; - if (typeof value !== "string" || value.trim().length === 0) { - continue; - } - let triggers = triggersByChannelId.get(channelId); - if (!triggers) { - triggers = new Map(); - triggersByChannelId.set(channelId, triggers); - } - const trigger = envVar.trim().toUpperCase(); - let ownerIds = triggers.get(trigger); - if (!ownerIds) { - ownerIds = new Set(); - triggers.set(trigger, ownerIds); - } - ownerIds.add(plugin.id); + const value = env[envVar] ?? env[envVar.toUpperCase()]; + return typeof value === "string" && value.trim().length > 0; + }; + if (!allOf.every(hasEnvValue) || (anyOf.length > 0 && !anyOf.some(hasEnvValue))) { + continue; + } + for (const envVar of [...allOf, ...anyOf].filter(hasEnvValue)) { + const trigger = envVar.trim().toUpperCase(); + let ownerIds = triggers.get(trigger); + if (!ownerIds) { + ownerIds = new Set(); + triggers.set(trigger, ownerIds); } + ownerIds.add(plugin.id); } } return triggersByChannelId; diff --git a/src/commands/doctor/shared/context-engine-host-compat.test.ts b/src/commands/doctor/shared/context-engine-host-compat.test.ts index 66cc0fb96d84..035c95bc4b79 100644 --- a/src/commands/doctor/shared/context-engine-host-compat.test.ts +++ b/src/commands/doctor/shared/context-engine-host-compat.test.ts @@ -3,7 +3,6 @@ import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import { getContextEngineRegistration, - registerContextEngine, registerContextEngineForOwner, } from "../../../context-engine/registry.js"; import type { ContextEngine, ContextEngineHostCapability } from "../../../context-engine/types.js"; @@ -49,6 +48,15 @@ function uniqueEngineId(): string { return `doctor-host-compat-${engineCounter}`; } +function registerTestContextEngine( + id: string, + factory: Parameters[1], +) { + return registerContextEngineForOwner(id, factory, `doctor-test-owner-${id}`, { + allowSameOwnerRefresh: true, + }); +} + function registerEngine(requiredCapabilities: ContextEngineHostCapability[]): string { const id = uniqueEngineId(); const engine: ContextEngine = { @@ -75,7 +83,7 @@ function registerEngine(requiredCapabilities: ContextEngineHostCapability[]): st return { ok: true, compacted: false }; }, }; - registerContextEngine(id, () => engine); + registerTestContextEngine(id, () => engine); return id; } diff --git a/src/commands/doctor/shared/legacy-web-search-migrate.test.ts b/src/commands/doctor/shared/legacy-web-search-migrate.test.ts index 21a7807b604b..4b96b271b119 100644 --- a/src/commands/doctor/shared/legacy-web-search-migrate.test.ts +++ b/src/commands/doctor/shared/legacy-web-search-migrate.test.ts @@ -6,9 +6,17 @@ import { migrateLegacyWebSearchConfig, } from "./legacy-web-search-migrate.js"; +type LegacyWebSearchConfig = Omit & { + tools?: { + web?: { + search?: Record; + }; + }; +}; + describe("legacy web search config", () => { it("migrates legacy provider config through bundled web search ownership metadata", () => { - const res = migrateLegacyWebSearchConfig({ + const res = migrateLegacyWebSearchConfig({ tools: { web: { search: { @@ -65,14 +73,14 @@ describe("legacy web search config", () => { }); it("repairs retired Grok code aliases while preserving current aliases", () => { - const retired = migrateLegacyWebSearchConfig({ + const retired = migrateLegacyWebSearchConfig({ tools: { web: { search: { grok: { model: "grok-code-fast-1" } }, }, }, }); - const current = migrateLegacyWebSearchConfig({ + const current = migrateLegacyWebSearchConfig({ tools: { web: { search: { grok: { model: "grok-latest" } }, @@ -106,10 +114,10 @@ describe("legacy web search config", () => { }, }, }, - } satisfies OpenClawConfig; + } satisfies LegacyWebSearchConfig; const original = structuredClone(input); - const res = migrateLegacyWebSearchConfig(input); + const res = migrateLegacyWebSearchConfig(input); expect(res.config.plugins?.entries?.xai?.config?.webSearch).toEqual({ apiKey: "xai-key", @@ -119,7 +127,7 @@ describe("legacy web search config", () => { }); it("preserves unrelated record-valued web search config", () => { - const res = migrateLegacyWebSearchConfig({ + const res = migrateLegacyWebSearchConfig({ tools: { web: { search: { @@ -156,7 +164,7 @@ describe("legacy web search config", () => { }); it("drops dangerous record keys while preserving unrelated web search config", () => { - const res = migrateLegacyWebSearchConfig({ + const res = migrateLegacyWebSearchConfig({ tools: { web: { search: { diff --git a/src/commands/doctor/shared/plugin-registry-migration.test.ts b/src/commands/doctor/shared/plugin-registry-migration.test.ts index 69c47ae1e09c..23bc0bee2cac 100644 --- a/src/commands/doctor/shared/plugin-registry-migration.test.ts +++ b/src/commands/doctor/shared/plugin-registry-migration.test.ts @@ -14,13 +14,7 @@ import { makeTrackedTempDir, } from "../../../plugins/test-helpers/fs-fixtures.js"; import { runOpenClawStateWriteTransaction } from "../../../state/openclaw-state-db.js"; -import { - DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV, - migratePluginRegistryForInstall, - preflightPluginRegistryInstallMigration, -} from "./plugin-registry-migration.js"; - -const FORCE_PLUGIN_REGISTRY_MIGRATION_ENV = "OPENCLAW_FORCE_PLUGIN_REGISTRY_MIGRATION"; +import { migratePluginRegistryForInstall } from "./plugin-registry-migration.js"; const tempDirs: string[] = []; afterEach(() => { @@ -497,38 +491,4 @@ describe("plugin registry install migration", () => { }); expect(persisted?.plugins).toEqual([]); }); - - it("marks force migration env as deprecated break-glass", () => { - const result = preflightPluginRegistryInstallMigration({ - stateDir: makeTempDir(), - env: hermeticEnv({ - [FORCE_PLUGIN_REGISTRY_MIGRATION_ENV]: "1", - }), - }); - expectRecordFields(requireRecord(result, "preflight result"), { - action: "migrate", - force: true, - }); - expect(result.deprecationWarnings).toStrictEqual([ - `${FORCE_PLUGIN_REGISTRY_MIGRATION_ENV} is deprecated and will be removed after the plugin registry migration rollout; use doctor registry repair once available.`, - ]); - }); - - it("treats falsey env flag strings as unset", async () => { - const stateDir = makeTempDir(); - await writePersistedInstalledPluginIndex(createCurrentIndex(), { stateDir }); - - const result = preflightPluginRegistryInstallMigration({ - stateDir, - env: hermeticEnv({ - [DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV]: "0", - [FORCE_PLUGIN_REGISTRY_MIGRATION_ENV]: "false", - }), - }); - expectRecordFields(requireRecord(result, "preflight result"), { - action: "skip-existing", - force: false, - deprecationWarnings: [], - }); - }); }); diff --git a/src/commands/doctor/shared/plugin-registry-migration.ts b/src/commands/doctor/shared/plugin-registry-migration.ts index de7a95d76b0f..2c4ee95cb2e1 100644 --- a/src/commands/doctor/shared/plugin-registry-migration.ts +++ b/src/commands/doctor/shared/plugin-registry-migration.ts @@ -24,28 +24,22 @@ import { import { loadPluginManifestRegistryForInstalledIndex } from "../../../plugins/manifest-registry-installed.js"; import type { PluginManifestRecord } from "../../../plugins/manifest-registry.js"; -export const DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV = "OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION"; -const FORCE_PLUGIN_REGISTRY_MIGRATION_ENV = "OPENCLAW_FORCE_PLUGIN_REGISTRY_MIGRATION"; const DOCTOR_PLUGIN_ID_ALIASES: Readonly> = { openai: ["openai-codex"], }; -type PluginRegistryInstallMigrationPreflightAction = "disabled" | "skip-existing" | "migrate"; +type PluginRegistryInstallMigrationPreflightAction = "skip-existing" | "migrate"; type PluginRegistryInstallMigrationPreflight = { /** Migration action selected before reading or writing registry state. */ action: PluginRegistryInstallMigrationPreflightAction; /** Persisted plugin index path that migration will inspect or write. */ filePath: string; - /** True when deprecated force env requested migration despite existing registry. */ - force: boolean; - /** Deprecation warnings for env toggles that should be shown to users. */ - deprecationWarnings: readonly string[]; }; type PluginRegistryInstallMigrationResult = | { - status: "disabled" | "skip-existing" | "dry-run"; + status: "skip-existing" | "dry-run"; migrated: false; preflight: PluginRegistryInstallMigrationPreflight; } @@ -64,48 +58,24 @@ export type PluginRegistryInstallMigrationParams = LoadInstalledPluginIndexParam readConfig?: () => Promise | OpenClawConfig; }; -function hasEnvFlag(env: NodeJS.ProcessEnv | undefined, key: string): boolean { - const value = env?.[key]?.trim().toLowerCase(); - return Boolean(value && value !== "0" && value !== "false" && value !== "no"); -} - -function forceDeprecationWarning(): string { - return `${FORCE_PLUGIN_REGISTRY_MIGRATION_ENV} is deprecated and will be removed after the plugin registry migration rollout; use doctor registry repair once available.`; -} - /** Decide whether plugin install registry migration should run for this environment. */ export function preflightPluginRegistryInstallMigration( params: PluginRegistryInstallMigrationParams = {}, ): PluginRegistryInstallMigrationPreflight { - const env = params.env ?? process.env; const filePath = resolveInstalledPluginIndexStorePath(params); - const force = hasEnvFlag(env, FORCE_PLUGIN_REGISTRY_MIGRATION_ENV); - const deprecationWarnings = force ? [forceDeprecationWarning()] : []; - if (hasEnvFlag(env, DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV)) { - return { - action: "disabled", - filePath, - force, - deprecationWarnings, - }; - } const pathExists = params.existsSync ?? fs.existsSync; - if (!force && pathExists(filePath)) { + if (pathExists(filePath)) { const currentRegistry = readPersistedInstalledPluginIndexSync(params); if (currentRegistry) { return { action: "skip-existing", filePath, - force, - deprecationWarnings, }; } } return { action: "migrate", filePath, - force, - deprecationWarnings, }; } @@ -292,9 +262,6 @@ export async function migratePluginRegistryForInstall( params: PluginRegistryInstallMigrationParams = {}, ): Promise { const preflight = preflightPluginRegistryInstallMigration(params); - if (preflight.action === "disabled") { - return { status: "disabled", migrated: false, preflight }; - } if (preflight.action === "skip-existing") { return { status: "skip-existing", migrated: false, preflight }; } diff --git a/src/commands/onboard-channels.e2e.test.ts b/src/commands/onboard-channels.e2e.test.ts index b0933211372f..3afcfef50d62 100644 --- a/src/commands/onboard-channels.e2e.test.ts +++ b/src/commands/onboard-channels.e2e.test.ts @@ -887,7 +887,7 @@ describe("setupChannels", () => { ...qaChannelBase, meta: { ...qaChannelBase.meta, - showInSetup: false, + exposure: { setup: false }, }, }, }, diff --git a/src/commands/status-json-payload.test.ts b/src/commands/status-json-payload.test.ts index 6a88dbbec855..cc76ea268b81 100644 --- a/src/commands/status-json-payload.test.ts +++ b/src/commands/status-json-payload.test.ts @@ -86,7 +86,7 @@ describe("status-json-payload", () => { pluginCompatibility: [ { pluginId: "legacy", - code: "legacy-before-agent-start", + code: "deprecated-memory-embedding-provider-api", severity: "warn", message: "warn", }, @@ -129,7 +129,7 @@ describe("status-json-payload", () => { warnings: [ { pluginId: "legacy", - code: "legacy-before-agent-start", + code: "deprecated-memory-embedding-provider-api", severity: "warn", message: "warn", }, diff --git a/src/commands/status-json-runtime.test.ts b/src/commands/status-json-runtime.test.ts index 5cd65da9eaa2..42c9009db352 100644 --- a/src/commands/status-json-runtime.test.ts +++ b/src/commands/status-json-runtime.test.ts @@ -41,7 +41,7 @@ function createScan() { pluginCompatibility: [ { pluginId: "legacy", - code: "legacy-before-agent-start", + code: "deprecated-memory-embedding-provider-api", severity: "warn", message: "warn", }, @@ -105,7 +105,7 @@ describe("status-json-runtime", () => { expect(payloadInput.pluginCompatibility).toStrictEqual([ { pluginId: "legacy", - code: "legacy-before-agent-start", + code: "deprecated-memory-embedding-provider-api", severity: "warn", message: "warn", }, diff --git a/src/commands/status.scan-result.test.ts b/src/commands/status.scan-result.test.ts index 9152080c4d9c..47f64853b735 100644 --- a/src/commands/status.scan-result.test.ts +++ b/src/commands/status.scan-result.test.ts @@ -87,8 +87,8 @@ describe("buildStatusScanResult", () => { const pluginCompatibility = [ { pluginId: "legacy", - code: "legacy-before-agent-start" as const, - compatCode: "legacy-before-agent-start" as const, + code: "deprecated-memory-embedding-provider-api" as const, + compatCode: "deprecated-memory-embedding-provider-api" as const, severity: "warn" as const, message: "warn", }, diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index 7d79cfa7f8c1..a46981581dec 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -1013,7 +1013,7 @@ describe("statusCommand", () => { it("prints JSON and includes security audit only when all is requested", async () => { mocks.buildPluginCompatibilityNotices.mockReturnValue([ - createCompatibilityNotice({ pluginId: "legacy-plugin", code: "legacy-before-agent-start" }), + createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }), ]); await statusCommand({ json: true }, runtime as never); const payload = JSON.parse(getRuntimeLog(0)); @@ -1137,7 +1137,7 @@ describe("statusCommand", () => { it("prints formatted lines with verbose cache details", async () => { mocks.buildPluginCompatibilityNotices.mockReturnValue([ - createCompatibilityNotice({ pluginId: "legacy-plugin", code: "legacy-before-agent-start" }), + createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }), ]); const logs = await runStatusAndGetLogs({ verbose: true }); for (const token of [ @@ -1164,7 +1164,7 @@ describe("statusCommand", () => { ]) { expectLogsInclude(logs, token); } - expectLogsInclude(logs, "legacy-plugin still uses legacy before_agent_start"); + expectLogsInclude(logs, "legacy-plugin is hook-only"); expectLogsMatch(logs, /openclaw (?:--profile isolated )?status --all/); expectLogsInclude(logs, "Cache"); expectLogsInclude(logs, "40% hit"); diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index abc9caa00afa..4f54cc613f45 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -21,18 +21,18 @@ const RAW_BUNDLED_CHANNEL_CONFIG_METADATA = [ 'cord voice channel conversations."},"voice.enabled":{"label":"Discord Voice Enabled","help":"Enable Discord voice channel conversations. Text-only Discord configs leave voice off by default; set true to enable /vc commands and the Guild Voice States intent."},"voice.model":{"label":"Discord Voice Model","help":"Optional LLM model override for Discord voice channel responses and realtime agent consults (for example openai/gpt-5.6-sol). Leave unset to inherit the routed agent model."},"voice.mode":{"label":"Discord Voice Mode","help":"Conversation mode: agent-proxy (default) uses realtime voice as the microphone/speaker for the routed OpenClaw agent, stt-tts uses batch speech-to-text plus TTS, and bidi lets the realtime provider converse directly with the OpenClaw consult tool."},"voice.agentSession":{"label":"Discord Voice Agent Session","help":"Controls which OpenClaw conversation receives voice turns. Leave unset for the voice channel session, or set mode=\\"target\\" with a Discord target such as channel:123 to make voice an extension of an existing text channel session."},"voice.agentSession.target":{"label":"Discord Voice Agent Session Target","help":"Discord target used when voice.agentSession.mode=\\"target\\", for example channel:123."},"voice.followUsersEnabled":{"label":"Discord Voice Follow Users Enabled","help":"Toggle Discord voice follow-users behavior without removing the saved voice.followUsers list. Defaults to true when followUsers is configured."},"voice.followUsers":{"label":"Discord Voice Follow Users","help":"Discord user IDs to follow into voice channels. The bot joins when a followed user joins or moves, and leaves when that user disconnects."},"voice.realtime.provider":{"label":"Discord Realtime Provider","help":"Realtime voice provider for agent-proxy or bidi Discord voice modes, such as openai."},"voice.realtime.model":{"label":"Discord Realtime Model","help":"Provider realtime session model, such as gpt-realtime-2.1. This is separate from voice.model, which remains the OpenClaw agent brain model."},"voice.realtime.speakerVoice":{"label":"Discord Realtime Speaker Voice","help":"Provider realtime output voice name, such as cedar."},"voice.realtime.speakerVoiceId":{"label":"Discord Realtime Speaker Voice ID","help":"Provider realtime output voice id."},"voice.realtime.toolPolicy":{"label":"Discord Realtime Tool Policy","help":"Tool policy for the OpenClaw agent consult tool in realtime voice modes: safe-read-only, owner, or none. Default is owner for agent-proxy and safe-read-only for bidi."},"voice.realtime.consultPolicy":{"label":"Discord Realtime Consult Policy","help":"Use always to strongly prefer the OpenClaw agent brain for substantive realtime turns. agent-proxy defaults to always."},"voice.realtime.requireWakeName":{"label":"Discord Realtime Require Wake Name","help":"Control OpenAI agent-proxy wake-name gating. Unset listens naturally with one human and requires a wake name with two or more; true always requires one and false never does."},"voice.realtime.wakeNames":{"label":"Discord Realtime Wake Names","help":"One- or two-word activation names used whenever OpenAI agent-proxy Discord realtime voice has an active wake-name gate."},"voice.realtime.bootstrapContextFiles":{"label":"Discord Realtime Bootstrap Context Files","help":"Agent profile bootstrap files included in realtime provider instructions for direct voice identity/persona grounding. Defaults to IDENTITY.md, USER.md, and SOUL.md; set [] to disable."},"voice.realtime.bargeIn":{"label":"Discord Realtime Barge-In","help":"Allow Discord speaker-start events to interrupt active realtime playback. Set true to keep manual interruption when provider input-audio interruption is disabled for echo control."},"voice.realtime.minBargeInAudioEndMs":{"label":"Discord Realtime Minimum Barge-In Audio (ms)","help":"Minimum assistant playback duration before a Discord barge-in truncates realtime audio. Default: 250; set 0 for immediate interruption in low-echo rooms."},"voice.realtime.providers":{"label":"Discord Realtime Provider Settings","help":"Provider-specific realtime voice settings keyed by provider id.","advanced":true},"voice.autoJoin":{"label":"Discord Voice Auto-Join","help":"Voice channels to auto-join on startup (list of guildId/channelId entries)."},"voice.allowedChannels":{"label":"Discord Voice Allowed Channels","help":"Optional voice channel residency allowlist. When set, /vc join, auto-join, and bot voice-state moves are restricted to these guildId/channelId entries. Leave unset to allow any voice channel."},"voice.daveEncryption":{"label":"Discord Voice DAVE Encryption","help":"Toggle DAVE end-to-end encryption for Discord voice joins (default: true in @discordjs/voice; Discord may require this)."},"voice.decryptionFailureTolerance":{"label":"Discord Voice Decrypt Failure Tolerance","help":"Consecutive decrypt failures before DAVE attempts session recovery (passed to @discordjs/voice; default: 24)."},"voice.connectTimeoutMs":{"label":"Discord Voice Connect Timeout (ms)","help":"Initial @discordjs/voice Ready wait before a join is treated as failed. Default: 30000."},"voice.reconnectGraceMs":{"label":"Discord Voice Reconnect Grace (ms)","help":"Grace period for a disconnected Discord voice session to enter Signalling or Connecting before OpenClaw destroys it. Default: 15000."},"voice.captureSilenceGraceMs":{"label":"Discord Voice Capture Silence Grace (ms)","help":"Silence window after Discord reports a speaker ended before OpenClaw finalizes the audio segment for transcription. Default: 2000."},"voice.tts":{"label":"Discord Voice Text-to-Speech","help":"Optional TTS overrides for Discord voice playback (merged with messages.tts)."},"pluralkit.enabled":{"label":"Discord PluralKit Enabled","help":"Resolve PluralKit proxied messages and treat system members as distinct senders."},"pluralkit.token":{"label":"Discord PluralKit Token","help":"Optional PluralKit token for resolving private systems or members."},"activity":{"label":"Discord Presence Activity","help":"Discord presence activity text (defaults to custom status)."},"status":{"label":"Discord Presence Status","help":"Discord presence status (online, dnd, idle, invisible)."},"autoPresence.enabled":{"label":"Discord Auto Presence Enabled","help":"Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd."},"autoPresence.intervalMs":{"label":"Discord Auto Presence Check Interval (ms)","help":"How often to evaluate Discord auto-presence state in milliseconds (default: 30000)."},"autoPresence.minUpdateIntervalMs":{"label":"Discord Auto Presence Min Update Interval (ms)","help":"Minimum time between actual Discord presence update calls in milliseconds (default: 15000). Prevents status spam on noisy state changes."},"autoPresence.healthyText":{"label":"Discord Auto Presence Healthy Text","help":"Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set."},"autoPresence.degradedText":{"label":"Discord Auto Presence Degraded Text","help":"Optional custom status text while runtime/model availability is degraded or unknown (idle)."},"autoPresence.exhaustedText":{"label":"Discord Auto Presence Exhausted Text","help":"Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder."},"guilds.*.presenceEvents":{"label":"Discord Online Presence Events","help":"Route selected human offline-to-online transitions into the configured guild channel as agent system events. Requires the Guild Presences privileged intent and an enabled agent heartbeat."},"guilds.*.presenceEvents.enabled":{"label":"Discord Online Presence Events Enabled","help":"Enable online-presence agent wakes for this guild. Defaults to true when presenceEvents is configured."},"guilds.*.presenceEvents.channelId":{"label":"Discord Online Presence Target Channel","help":"Numeric Discord channel ID whose routed agent session receives online-presence events and greeting delivery."},"guilds.*.presenceEvents.users":{"label":"Discord Online Presence User IDs","help":"Optional immutable Discord user ID allowlist. Omit to include all human members in the guild."},"guilds.*.presenceEvents.reconnectSuppressSeconds":{"label":"Discord Online Presence Reconnect Suppression","help":"Suppress online-presence events for this many seconds after a new Gateway session while guild presence state is rebuilt. Resumed sessions are unaffected. 0 disables. Default: 300."},"guilds.*.presenceEvents.burstLimit":{"label":"Discord Online Presence Burst Limit","help":"Maximum successfully queued online-presence events for this guild per burst window; the rest are suppressed and logged once. Default: 8."},"guilds.*.presenceEvents.burstWindowSeconds":{"label":"Discord Online Presence Burst Window","help":"Sliding window in seconds used for burst detection. Default: 60."},"activityType":{"label":"Discord Presence Activity Type","help":"Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing)."},"activityUrl":{"label":"Discord Presence Activity URL","help":"Discord presence streaming URL (required for activityType=1)."},"allowBots":{"label":"Discord Allow Bot Messages","help":"Allow bot-authored messages to trigger Discord replies (default: false). Set \\"mentions\\" to only accept bot messages that mention the bot."},"botLoopProtection":{"label":"Discord Bot Loop Protection","help":"Sliding-window guard for bot-to-bot Discord loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Discord Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Discord Bot Pair Events Per Window","help":"Maximum messages a single Discord bot pair may exchange in the configured window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Discord Bot Loop Window Seconds","help":"Sliding window length in seconds for Discord bot-pair loop budgets. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Discord Bot Loop Cooldown Seconds","help":"Seconds to suppress a Discord bot pair after it exceeds the loop budget. Default: 60."},"mentionAliases":{"label":"Discord Mention Aliases","help":"Map outbound @handle text to stable Discord user IDs before sending. Set per account via channels.discord.accounts..mentionAliases."},"token":{"label":"Discord Bot Token","help":"Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.","sensitive":true},"applicationId":{"label":"Discord Application ID","help":"Optional Discord application/client ID. Set this when hosted environments cannot reach Discord\'s application lookup endpoint during startup."},"activities":{"label":"Discord Activities","help":"Enable Discord Activity widgets for this account. Routes, the agent tool, and the launch handler remain disabled when this block is absent."},"activities.clientSecret":{"label":"Discord Activities Client Secret","help":"OAuth2 client secret for the Discord application. DISCORD_CLIENT_SECRET is used when this field is unset.","sensitive":true},"activities.applicationId":{"label":"Discord Activities Application ID","help":"Optional Activity application ID. Defaults to the bot application ID learned at gateway startup."}},"unsupportedSecretRefSurfacePatterns":["channels.discord.accounts.*.threadBindings.webhookToken","channels.discord.threadBindings.webhookToken"]},{"pluginId":"feishu","channelId":"feishu","aliases":["lark"],"order":35,"channelEnvVars":["FEISHU_APP_ID","FEISHU_APP_SECRET","FEISHU_ENCRYPT_KEY","FEISHU_VERIFICATION_TOKEN"],"label":"Feishu","description":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"default":"feishu","anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"default":"websocket","type":"string","enum":["websocket","webhook"]},"webhookPath":{"default":"/feishu/events","type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"syste', 'mPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"},"bitable":{"type":"boolean"},"base":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"default":"own","type":"string","enum":["off","own","all"]},"typingIndicator":{"default":true,"type":"boolean"},"resolveSenderNames":{"default":true,"type":"boolean"},"allowBots":{"type":"boolean"},"vcAutoJoin":{"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"dynamicAgentCreation":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspaceTemplate":{"type":"string"},"agentDirTemplate":{"type":"string"},"maxAgents":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"type":"string","enum":["websocket","webhook"]},"webhookPath":{"type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","disabled","allowlist"]},{"type":"string","const":"allowall"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"},"bitable":{"type":"boolean"},"base":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"typingIndicator":{"type":"boolean"},"resolveSenderNames":{"type":"boolean"},"allowBots":{"type":"boolean"},"vcAutoJoin":{"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}}},"additionalProperties":false}},{"pluginId":"googlechat","channelId":"googlechat","aliases":["gchat","google-chat"],"order":55,"channelEnvVars":["GOOGLE_CHAT_SERVICE_ACCOUNT","GOOGLE_CHAT_SERVICE_ACCOUNT_FILE"],"label":"Google Chat","description":"Google Workspace Chat app with HTTP webhook.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountRef":{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","propertie', 's":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountRef":{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"imessage","channelId":"imessage","aliases":["imsg"],"label":"iMessage","description":"Local iMessage/SMS through the imsg bridge, including private API message actions when enabled.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"sendTransport":{"type":"string","enum":["auto","bridge","applescript"]},"region":{"type":"string"},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"type":"boolean"},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"},"polls":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"sendTransport":{"type":"string","enum":["auto","bridge","applescript"]},"region":{"type":"string"},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"type":"boolean"},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"iMessage","help":"iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations."},"dmPolicy":{"label":"iMessage DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.imessage.allowFrom=[\\"*\\"].', - '"},"configWrites":{"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true)."},"cliPath":{"label":"iMessage CLI Path","help":"Filesystem path to the iMessage bridge CLI binary used for send/receive operations. Set explicitly when the binary is not on PATH in service runtime environments."},"sendTransport":{"label":"iMessage Send Transport","help":"Preferred imsg RPC send transport for normal outbound replies. \\"auto\\" uses the IMCore bridge when available, \\"bridge\\" requires it, and \\"applescript\\" forces Messages automation."}}},{"pluginId":"irc","channelId":"irc","aliases":["internet-relay-chat"],"channelEnvVars":["IRC_CHANNELS","IRC_HOST","IRC_NICK","IRC_NICKSERV_PASSWORD","IRC_NICKSERV_REGISTER_EMAIL","IRC_PASSWORD","IRC_PORT","IRC_REALNAME","IRC_TLS","IRC_USERNAME"],"label":"IRC","description":"classic IRC networks with DM/channel routing and pairing controls.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"IRC","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw."},"dmPolicy":{"label":"IRC DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.irc.allowFrom=[\\"*\\"]."},"nickserv.enabled":{"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured)."},"nickserv.service":{"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ)."},"nickserv.password":{"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive)."},"nickserv.passwordFile":{"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password."},"nickserv.register":{"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable."},"nickserv.registerEmail":{"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true)."},"configWrites":{"label":"IRC Config Writes","help":"Allow IRC to write config in response to channel events/commands (default: true)."}}},{"pluginId":"line","channelId":"line","order":75,"channelEnvVars":["LINE_CHANNEL_ACCESS_TOKEN","LINE_CHANNEL_SECRET"],"label":"LINE","description":"LINE Messaging API webhook bot.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"matrix","channelId":"matrix","order":70,"channelEnvVars":["MATRIX_ACCESS_TOKEN","MATRIX_DEVICE_ID","MATRIX_DEVICE_NAME","MATRIX_HOMESERVER","MATRIX_OPS_ACCESS_TOKEN","MATRIX_OPS_DEVICE_ID","MATRIX_OPS_DEVICE_NAME","MATRIX_OPS_HOMESERVER","MATRIX_PASSWORD","MATRIX_USER_ID"],"label":"Matrix","description":"open protocol; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"homeserver":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"userId":{"type":"string"},"accessToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"password":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"avatarUrl":{"type":"string"},"initialSyncLimit":{"type":"number"},"encryption":{"type":"boolean"},"allowlistOnly":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["partial","quiet","progress","off"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0', - ',"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"}},"additionalProperties":false},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"textChunkLimit":{"type":"number"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","none","off"]},"reactionNotifications":{"type":"string","enum":["off","own"]},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"startupVerification":{"type":"string","enum":["off","if-unverified"]},"startupVerificationCooldownHours":{"type":"number"},"mediaMaxMb":{"type":"number"},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoJoin":{"type":"string","enum":["always","allowlist","off"]},"autoJoinAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"sessionScope":{"type":"string","enum":["per-user","per-room"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"type":"boolean"},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"rooms":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"profile":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"verification":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"uiHints":{"mentionPatterns":{"label":"Matrix Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Matrix room IDs. Native Matrix mention evidence still triggers even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Matrix Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Matrix Mention Pattern Allowlist","help":"Matrix room IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Matrix Mention Pattern Denylist","help":"Matrix room IDs where configured regex mention patterns are disabled. Native mention evidence still triggers."},"allowBots":{"label":"Matrix Allow Bot Messages","help":"Allow messages from other configured Matrix bot accounts to trigger replies (default: false). Set \\"mentions\\" to require a visible room mention."},"botLoopProtection":{"label":"Matrix Bot Loop Protection","help":"Sliding-window guard for accepted Matrix configured-bot loops. Default is enabled whenever allowBots lets configured bot messages reach dispatch."},"botLoopProtection.enabled":{"label":"Matrix Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when configured bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Matrix Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Matrix Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Matrix Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"dangerouslyAllowNameMatching":{"label":"Matrix Display Name Matching","help":"Compatibility opt-in for resolving Matrix display names and joined room names in allowlists. Prefer full @user:server IDs and room IDs or aliases because names are mutable."},"streaming.progress.label":{"label":"Matrix Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Matrix Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Matrix Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Matrix Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Matrix Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Matrix Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"pluginId":"mattermost","channelId":"mattermost","order":65,"channelEnvVars":["MATTERMOST_BOT_TOKEN","MATTERMOST_URL"],"label":"Mattermost","description":"self-hosted Slack-style chat; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandT', - 'ext":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Mattermost","help":"Mattermost channel provider configuration for bot auth, access policy, slash commands, and preview streaming."},"dmPolicy":{"label":"Mattermost DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.mattermost.allowFrom=[\\"*\\"]."},"implicitMentions":{"label":"Mattermost Implicit Mentions","help":"Control which Mattermost reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Mattermost Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Mattermost Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Mattermost Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"streaming":{"label":"Mattermost Streaming Mode","help":"Unified Mattermost stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". \\"progress\\" keeps a single editable progress draft until final delivery."},"streaming.mode":{"label":"Mattermost Streaming Mode","help":"Canonical Mattermost preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"streaming.progress.label":{"label":"Mattermost Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Mattermost Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Mattermost Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Mattermost Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Mattermost Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Mattermost Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.preview.toolProgress":{"label":"Mattermost Draft Tool Progress","help":"Show tool/progress activity in the live draft preview post (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Mattermost Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.block.enabled":{"label":"Mattermost Block Streaming Enabled","help":"Enable chunked block-style Mattermost preview delivery when channels.mattermost.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Mattermost Block Streaming Coalesce","help":"Merge streamed Mattermost block replies before final delivery."}}},{"pluginId":"msteams","channelId":"msteams","aliases":["teams"],"order":60,"channelEnvVars":["MSTEAMS_APP_ID","MSTEAMS_APP_PASSWORD","MSTEAMS_TENANT_ID"],"label":"Microsoft Teams","description":"Teams SDK; enterprise support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"dangerouslyAllowNameMatching":{"type":"boolean"},"appId":{"type":"string"},"appPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tenantId":{"type":"string"},"cloud":{"type":"string","enum":["Public","USGov","USGovDoD","China"]},"serviceUrl":{"type":"string","format":"uri"},"authType":{"type":"string","enum":["secret","federated"]},"certificatePath":{"type":"string"},"certificateThumbprint":{"type":"string"},"useManagedIdentity":{"type":"boolean"},"managedIdentityClientId":{"type":"string"},"webhook":{"type":"object","properties":{"port":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"path":{"type":"string"}},"additionalProperties":false},"typingIndicator":{"type":"boolean"},"mediaAllowHosts":{"type":"array","items":{"type":"string"}},"mediaAuthAllowHosts":{"type":"array","items":{"type":"string"}},"graphMediaFallback":{"type":"boolean"},"requireMention":{"type":"boolean"},"replyStyle":{"type":"string","enum":["thread","top-level"]},"teams":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]}},"additionalProperties":false}}},"additionalProperties":false}},"sharePointSiteId":{"type":"string"},"welcomeCard":{"type":"boolean"},"promptStarters":{"type":"array","items":{"type":"string"}},"groupWelcomeCard":{"type":"boolean"},"feedbackEnabled":{"type":"boolean"},"feedbackReflection":{"type":"boolean"},"feedbackReflectionCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"delegatedAuth":{"type":"object","properties":{"enabled":{"type":"boolean"},"scopes":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"sso":{"type":"object","properties":{"enabled":{"type":"boolean"},"connectionName":{"type":"string"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"MS Teams","help":"Microsoft Teams channel provider configuration and provider-specific policy toggles. Use this section to isolate Teams behavior from other enterprise chat providers."},"configWrites":{"label":"MS Teams Config Writes","help":"Allow Microsoft Teams to write config in response to channel events/commands (default: true)."},"cloud":{"label":"MS Teams Cloud","help":"Teams SDK cloud environment for auth, token validation, and token services: \\"Public\\", \\"USGov\\", \\"USGovDoD\\", or \\"China\\" (default: Public)."},"serviceUrl":{"label":"MS Teams Service URL","help":"Bot Connector service URL for SDK proactive sends/edits/deletes. Set with cloud for USGov/DoD; set alone for GCC."},"graphMediaFallback":{"label":"MS Teams Graph Media Fallback","help":"Query Microsoft Graph for unresolved channel or group-chat HTML media. Adds one lookup per matching message when enabled (default: false)."},"streaming":{"label":"MS Teams Streaming","help":"Microsoft Teams preview/progress streaming mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Personal chats use Teams native streaminfo progress when available."},"streaming.progress.label":{"label":"MS Teams Progress Label","help":"Initial progress title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"MS Teams Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"MS Teams Progress Max Lines","help":"Maximum number of compact progress lines to keep below the progress title (default: 8)."},"streaming.progress.maxLineChars":{"label":"MS Teams Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"MS Teams Progress Tool Lines","help":"Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery."},"streaming.progress.commandText":{"label":"MS Teams Progress Command Text","help":"Command/exec detail in progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"pluginId":"nextcloud-talk","channelId":"nextcloud-talk","aliases":["nc","nc-talk"],"order":65,"channelEnvVars":["NEXTCLOUD_TALK_API_PASSWORD","NEXTCLOUD_TALK_BOT_SECRET"],"label":"Nextcloud Talk","description":"Self-hosted chat via Nextcloud Talk webhook bots.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":fals', - 'e},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"nostr","channelId":"nostr","order":55,"channelEnvVars":["NOSTR_PRIVATE_KEY"],"label":"Nostr","description":"Decentralized protocol; encrypted DMs via NIP-04.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"defaultAccount":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"privateKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"relays":{"type":"array","items":{"type":"string"}},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"profile":{"type":"object","properties":{"name":{"type":"string","maxLength":256},"displayName":{"type":"string","maxLength":256},"about":{"type":"string","maxLength":2000},"picture":{"type":"string","format":"uri"},"banner":{"type":"string","format":"uri"},"website":{"type":"string","format":"uri"},"nip05":{"type":"string"},"lud16":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false}},{"pluginId":"qa-channel","channelId":"qa-channel","order":999,"configurable":false,"label":"QA Channel","description":"Synthetic Slack-class transport for automated OpenClaw QA scenarios.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"qqbot","channelId":"qqbot","channelEnvVars":["QQBOT_APP_ID","QQBOT_CLIENT_SECRET"],"label":"QQ Bot","description":"connect to QQ via official QQ Bot API with group chat and direct message support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties"', - ':false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}},"stt":{"type":"object","properties":{"enabled":{"type":"boolean"},"provider":{"type":"string"},"baseUrl":{"type":"string"},"apiKey":{"type":"string"},"model":{"type":"string"}},"additionalProperties":false},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":{}}},"defaultAccount":{"type":"string"}},"additionalProperties":{}}},{"pluginId":"raft","channelId":"raft","order":72,"channelEnvVars":["RAFT_PROFILE"],"label":"Raft","description":"Raft CLI wake bridge for human and agent collaboration.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"profile":{"type":"string","minLength":1},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"profile":{"type":"string","minLength":1}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"reef","channelId":"reef","label":"Reef","description":"Guarded end-to-end encrypted claw messaging.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"default":true,"type":"boolean"},"relayUrl":{"default":"https://reefwire.ai","type":"string","format":"uri","pattern":"^[hH][tT][tT][pP][sS]?:\\\\/\\\\/[^\\\\\\\\/?#@]+\\\\/?$"},"handle":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$"},"email":{"type":"string","format":"email","pattern":"^(?!\\\\.)(?!.*\\\\.\\\\.)([A-Za-z0-9_\'+\\\\-\\\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$"},"guard":{"type":"object","properties":{"provider":{"type":"string","enum":["anthropic","openai"]},"pinnedModel":{"type":"string","minLength":1},"apiKeyEnv":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"},"policyVersion":{"type":"string","minLength":1},"timeoutMs":{"type":"integer","minimum":100,"maximum":120000}},"required":["provider","pinnedModel","apiKeyEnv","policyVersion","timeoutMs"],"additionalProperties":false},"stateDir":{"type":"string","minLength":1},"requestPolicy":{"default":"code-only","type":"string","enum":["code-only","friends-of-friends","open"]},"friends":{}},"required":["enabled","relayUrl","requestPolicy"],"additionalProperties":false}},{"pluginId":"signal","channelId":"signal","label":"Signal","description":"signal-cli linked device; more setup (David Reagans: \\"Hop on Discord.\\").","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"configPath":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"apiMode":{"type":"string","enum":["auto","native","container"]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"configPath":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Signal","help":"Signal channel provider configuration including account identity and DM policy behavior. Keep account mapping explicit so routing remains stable across multi-device setups."},"dmPolicy":{"label":"Signal DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.signal.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Signal Config Writes","help":"Allow Signal to write config in response to channel events/commands (default: true)."},"account":{"label":"Signal Account","help":"Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state."},"configPath":{"label":"Signal CLI Config Path","', - 'help":"Optional directory passed to signal-cli via --config when the service needs a non-default signal-cli data path."}}},{"pluginId":"slack","channelId":"slack","channelEnvVars":["SLACK_APP_TOKEN","SLACK_BOT_TOKEN","SLACK_USER_TOKEN"],"label":"Slack","description":"supported (Socket Mode).","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"identity":{"default":"bot","type":"string","enum":["bot","user"]},"mode":{"default":"socket","type":"string","enum":["socket","http","relay"]},"enterpriseOrgInstall":{"type":"boolean"},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"additionalProperties":false},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"default":"/slack/events","type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991', - '},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"identity":{"type":"string","enum":["bot","user"]},"mode":{"type":"string","enum":["socket","http","relay"]},"enterpriseOrgInstall":{"type":"boolean"},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"additionalProperties":false},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"}},"required":["userTokenReadOnly"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy","identity","mode","webhookPath","userTokenReadOnly"],"additionalProperties":false},"uiHints":{"":{"label":"Slack","help":"Slack channel provider configuration for bot/app tokens, streaming behavior, and DM policy controls. Keep token handling and thread behavior explicit to avoid noisy workspace interactions."},"enterpriseOrgInstall":{"label":"Slack Enterprise Grid Org Install","help":"Enable only for an Enterprise Grid org-wide bot installation. OpenClaw verifies the token with Slack auth.test at startup; DMs must be disabled or use dmPolicy=\\"open\\" with allowFrom=[\\"*\\"]."},"identity":{"label":"Slack Identity","help":"Select \\"bot\\" (default) for the classic Slack app/bot identity or \\"user\\" to post as the authorizing human through a user token while the app carries event transport."},"dmPolicy":{"label":"Slack DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.slack.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Slack Config Writes","help":"Allow Slack to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Slack Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Slack channel IDs. Native Slack @mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Slack Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Slack Mention Pattern Allowlist","help":"Slack channel IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Slack Mention Pattern Denylist","help":"Slack channel IDs where configured regex mention patterns are disabled. Native @mentions still trigger."},"commands.native":{"label":"Slack Native Commands","help":"Override native commands for Slack (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Slack Native Skill Commands","help":"Override native skill commands for Slack (bool or \\"auto\\")."},"implicitMentions":{"label":"Slack Implicit Mentions","help":"Control which Slack reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Slack Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Slack Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Slack Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"allowBots":{"label":"Slack Allow Bot Messages","help":"Allow bot-authored messages to trigger Slack replies (default: false)."},"botLoopProtection":{"label":"Slack Bot Loop Protection","help":"Sliding-window guard for Slack bot-to-bot loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Slack Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Slack Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Slack Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Slack Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"socketMode":{"label":"Slack Socket Mode Transport","help":"Slack Socket Mode transport tuning passed to the Slack SDK. Use only when investigating ping/pong timeout or stale websocket behavior."},"socketMode.clientPingTimeout":{"label":"Slack Socket Mode Pong Timeout","help":"Milliseconds the Slack SDK waits for a pong after its client ping before treating the websocket as stale (OpenClaw default: 15000). Increase on hosts with event-loop starvation or slow network scheduling."},"socketMode.serverPingTimeout":{"label":"Slack Socket Mode Server Ping Timeout","help":"Milliseconds the Slack SDK waits for Slack server pings before treating the websocket as stale."},"socketMode.pingPongLoggingEnabled":{"label":"Slack Socket Mode Ping/Pong Logging","help":"Enable Slack SDK ping/pong transport logs while debugging Socket Mode websocket health."},"relay":{"label":"Slack Relay Mode","help":"Relay-delivered Slack events. Use with mode=\\"relay\\" when openclaw-slack-router owns the Slack Socket Mode connection."},"relay.url":{"label":"Slack Relay URL","help":"Full websocket URL for openclaw-slack-router. Include the route path, for example ws://127.0.0.1:8081/gateway/ws."},"relay.authToken":{"label":"Slack Relay Auth Token","help":"Bearer token used by this gateway to authenticate its reverse websocket connection to openclaw-slack-router."},"relay.gatewayId":{"label":"Slack Relay Gateway ID","help":"Destination id that openclaw-slack-router uses when routing user-group mentions to this gateway."},"botToken":{"label":"Slack Bot Token', - '","help":"Slack bot token used for standard chat actions in the configured workspace. Keep this credential scoped and rotate if workspace app permissions change."},"appToken":{"label":"Slack App Token","help":"Slack app-level token used for Socket Mode connections and event transport when enabled. Use least-privilege app scopes and store this token as a secret."},"userToken":{"label":"Slack User Token","help":"Optional Slack user token for workflows requiring user-context API access beyond bot permissions. Use sparingly and audit scopes because this token can carry broader authority."},"userTokenReadOnly":{"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes."},"capabilities.interactiveReplies":{"label":"Slack Interactive Replies","help":"Enable agent-authored Slack interactive reply directives (`[[slack_buttons: ...]]`, `[[slack_select: ...]]`). Default: false."},"execApprovals":{"label":"Slack Exec Approvals","help":"Slack-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for this workspace account."},"presenceEvents":{"label":"Slack Presence Events","help":"Poll observed human participants and wake the routed agent on away-to-active transitions. Default: \\"off\\"."},"presenceEvents.mode":{"label":"Slack Presence Event Mode","help":"\\"off\\" disables polling; \\"auto\\" covers DMs, MPIMs, and recent threads with up to 8 observed people; \\"on\\" also covers larger threads and top-level channels."},"channels.*.presenceEvents.mode":{"label":"Slack Channel Presence Event Mode","help":"Override presence events for one Slack channel. Use \\"on\\" to include large threads or top-level channel sessions."},"execApprovals.enabled":{"label":"Slack Exec Approvals Enabled","help":"Controls Slack native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Slack Exec Approval Approvers","help":"Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Slack Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Slack exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Slack."},"execApprovals.sessionFilter":{"label":"Slack Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Slack approval routing is used. Use narrow patterns so Slack approvals only appear for intended sessions."},"execApprovals.target":{"label":"Slack Exec Approval Target","help":"Controls where Slack approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Slack chat/thread, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted channels."},"streaming":{"label":"Slack Streaming Mode","help":"Unified Slack stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Slack Streaming Mode","help":"Canonical Slack preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"streaming.chunkMode":{"label":"Slack Chunk Mode","help":"Chunking mode for outbound Slack text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Slack Block Streaming Enabled","help":"Enable chunked block-style Slack preview delivery when channels.slack.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Slack Block Streaming Coalesce","help":"Merge streamed Slack block replies before final delivery."},"streaming.nativeTransport":{"label":"Slack Native Streaming","help":"Enable native Slack text streaming (chat.startStream/chat.appendStream/chat.stopStream) when channels.slack.streaming.mode is partial (default: true). Native streaming and Slack assistant thread status require a reply thread target; top-level DMs can still use draft post-and-edit preview streaming."},"streaming.preview.toolProgress":{"label":"Slack Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Slack Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.label":{"label":"Slack Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Slack Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"Slack Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Slack Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.render":{"label":"Slack Progress Renderer","help":"Progress draft renderer: \\"text\\" uses one portable text body; \\"rich\\" renders structured Slack Block Kit fields with the same text fallback."},"streaming.progress.nativeTaskCards":{"label":"Slack Native Progress Task Cards","help":"Opt in to Slack native task-card progress updates when channels.slack.streaming.mode=\\"progress\\" and streaming.nativeTransport is enabled. Default: false."},"streaming.progress.toolProgress":{"label":"Slack Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Slack Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"thread.historyScope":{"label":"Slack Thread History Scope","help":"Scope for Slack thread history context (\\"thread\\" isolates per thread; \\"channel\\" reuses channel history)."},"thread.inheritParent":{"label":"Slack Thread Parent Inheritance","help":"If true, Slack thread sessions inherit the parent channel transcript (default: false)."},"thread.initialHistoryLimit":{"label":"Slack Thread Initial History Limit","help":"Maximum number of existing Slack thread messages to fetch when starting a new thread session (default: 20, set to 0 to disable)."}}},{"pluginId":"sms","channelId":"sms","order":88,"channelEnvVars":["SMS_ALLOWED_USERS","SMS_PUBLIC_WEBHOOK_URL","SMS_WEBHOOK_PATH","TWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN","TWILIO_MESSAGING_SERVICE_SID","TWILIO_PHONE_NUMBER","TWILIO_SMS_FROM"],"label":"SMS","description":"Twilio-backed SMS with inbound webhooks and outbound replies.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["dmPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"SMS","help":"Twilio SMS channel configuration for inbound webhooks and outbound text replies."},"accountSid":{"label":"Twilio Account SID","help":"Twilio Account SID used for SMS outbound API calls."},"authToken":{"label":"Twilio Auth Token","help":"Twilio Auth Token used to sign webhook validation and SMS outbound API calls."},"fromNumber":{"label":"SMS From Number","help":"Twilio SMS-capable phone number in E.164 format, for example +15551234567."},"messagingServiceSid":{"label":"Twilio Messaging Service SID","help":"Twilio Messaging Service SID to use instead of a dedicated fromNumber."},"defaultTo":{"label":"SMS Default To Number","help":"Optional default outbound phone number used when a send flow omits an explicit SMS target."},"publicWebhookUrl":{"label":"SMS Public Webhook URL","help":"Public URL configured in Twilio for incoming messages. Must match Twilio\'s signed URL exactly."},"webhookPath":{"label":"SMS Webhook Path","help":"Gateway HTTP path that receives Twilio incoming-message webhooks. Use a distinct path per account."},"dmPolicy":{"label":"SMS DM Policy","help":"Direct SMS access control (\\"pairing\\" recommended). \\"open\\" requires channels.sms.allowFrom=[\\"*\\"]."},"allowFrom":{"label":"SMS Allow From","help":"Allowed sender phone numbers in E.164 format, or * when dmPolicy is open."},"textChunkLimit":{"label":"SMS Text Chunk Limit","help":"Maximum characters per outbound SMS chunk before OpenClaw splits long replies."}}},{"pluginId":"synology-chat","channelId":"synology-chat","order":90,"channelEnvVars":["OPENCLAW_BOT_NAME","SYNOLOGY_ALLOWED_USER_IDS","SYNOLOGY_CHAT_INCOMING_URL","SYNOLOGY_CHAT_TOKEN","SYNOLOGY_NAS_HOST","SYNOLOGY_RATE_LIMIT"],"label":"Synology Chat","description":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"dangerouslyAllowNameMatching":{"type":"boolean"},"dangerouslyAllowInheritedWebhookPath":{"type":"boolean"}},"additionalProperties":{}}},{"pluginId":"telegram","channelId":"telegram","channelEnvVars":["TELEGRAM_BOT_TOKEN"],"label":"Telegram","description":"simplest way to get started — register a bot with @BotFather and get going.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type"', - ':"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct ingress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","all', - 'owlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct ingress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Telegram","help":"Telegram channel provider configuration including auth tokens, retry behavior, and message rendering controls. Use this section to tune bot behavior for Telegram-specific API semantics."},"customCommands":{"label":"Telegram Custom Commands","help":"Additional Telegram bot menu commands (merged with native; conflicts ignored)."},"botToken":{"label":"Telegram Bot Token","help":"Telegram bot token used to authenticate Bot API requests for this account/provider config. Use secret/env substitution and rotate tokens if exposure is suspected."},"dmPolicy":{"label":"Telegram DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.telegram.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Telegram Config Writes","help":"Allow Telegram to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Telegram Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Telegram group chat IDs or chatId:topic:threadId topic IDs. Native Telegram bot mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Telegram Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Telegram Mention Pattern Allowlist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Telegram Mention Pattern Denylist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are disabled. Native bot mentions still trigger."},"commands.native":{"label":"Telegram Native Commands","help":"Override native commands for Telegram (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Telegram Native Skill Commands","help":"Override native skill commands for Telegram (bool or \\"auto\\")."},"streaming":{"label":"Telegram Streaming Mode","help":"Unified Telegram stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"partial\\"). \\"progress\\" keeps a single editable progress draft until final delivery. Legacy boolean/streamMode keys are detected; run doctor --fix to migrate."},"streaming.mode":{"label":"Telegram Streaming Mode","help":"Canonical Telegram preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"partial\\")."},"streaming.chunkMode":{"label":"Telegram Chunk Mode","help":"Chunking mode for outbound Telegram text delivery: \\"length\\" (default) or \\"newline\\"."},"richMessages":{"label":"Telegram Rich Messages","help":"Opt into Bot API 10.1 rich text sends and edits, including native tables and rich media. Default: false because some current Telegram clients render these messages as unsupported."},"streaming.block.enabled":{"label":"Telegram Block Streaming Enabled","help":"Enable chunked block-style Telegram preview delivery when channels.telegram.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Telegram Block Streaming Coalesce","help":"Merge streamed Telegram block replies before sending final delivery."},"streaming.preview.chunk.minChars":{"label":"Telegram Draft Chunk Min Chars","help":"Minimum chars before emitting a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.maxChars":{"label":"Telegram Draft Chunk Max Chars","help":"Target max size for a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.breakPreference":{"label":"Telegram Draft Chunk Break Preference","help":"Preferred breakpoints for Telegram draft chunks (paragraph | newline | sentence)."},"streaming.preview.toolProgress":{"label":"Telegram Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true when preview streaming is active). Set false to keep tool updates out of the edited Telegram preview."},"streaming.preview.commandText":{"label":"Telegram Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.label":{"label":"Telegram Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Telegram Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Telegram Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Telegram Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Telegram Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Telegram Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.commentary":{"label":"Telegram Progress Commentary","help":"Show assistant commentary/preamble text in the temporary progress draft. Final answer delivery is unchanged."},"network.autoSelectFamily":{"label":"Telegram autoSelectFamily","help":"Override Node autoSelectFamily for Telegram (true=enable, false=disable)."},"network.dangerouslyAllowPrivateNetwork":{"label":"Telegram Dangerously Allow Private Network","help":"Dangerous opt-in for trusted fake-IP or transparent-proxy environments where Telegram media downloads resolve api.telegram.org to private/internal/special-use addresses."},"silentErrorReplies":{"label":"Telegram Silent Error Replies","help":"When true, Telegram bot replies marked as errors are sent silently (no notification sound). Default: false."},"apiRoot":{"label":"Telegram API Root URL","help":"Custom Telegram Bot API root URL. Use the API root only (for example https://api.telegram.org), not a full /bot endpoint. Use for self-hosted Bot API servers (https://github.com/tdlib/telegram-bot-api) or reverse proxies in regions where api.telegram.org is blocked."},"trustedLocalFileRoots":{"label":"Telegram Trusted Local File Roots","help":"Trusted local filesystem roots for self-hosted Telegram Bot API file_path values. Exact in-root paths are read directly; container paths under /var/lib/telegram-bot-api can map into a host volume mount. Other absolute paths are rejected."},"autoTopicLabel":{"label":"Telegram Auto Topic Label","help":"Auto-rename DM forum topics on first message using LLM. Default: true. Set to false to disable, or use object form { enabled: true, prompt: \'...\' } for custom prompt."},"autoTopicLabel.enabled":{"label":"Telegram Auto Topic Label Enabled","help":"Whether auto topic labeling is enabled. Default: true."},"autoTopicLabel.prompt":{"label":"Telegram Auto Topic Label Prompt","help":"Custom prompt for LLM-based topic naming. The user message is appended after the prompt."},"capabilities.inlineButtons":{"label":"Telegram Inline Buttons","help":"Enable Telegram inline button components for supported command and interaction surfaces. Disable if your deployment needs plain-text-only compatibility behavior."},"execApprovals":{"label":"Telegram Exec Approvals","help":"Telegram-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for the selected bot account."},"execApprovals.enabled":{"label":"Telegram Exec Approvals Enabled","help":"Controls Telegram native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Telegram Exec Approval Approvers","help":"Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Telegram Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Telegram exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Telegram."},"execApprovals.sessionFilter":{"label":"Telegram Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Telegram approval routing is used. Use narrow patterns so Telegram approvals only appear for intended sessions."},"execApprovals.target":{"label":"Telegram Exec Approval Target","help":"Controls where Telegram approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Telegram chat/topic, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted groups/topics."},"threadBindings.enabled":{"label":"Telegram Thread Binding Enabled","help":"Enable Telegram conversation binding features (/focus, /unfocus, /agents, and /session idle|max-age). Overrides session.threadBindings.enabled when set."},"threadBindings.idleHours":{"label":"Telegram Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Telegram bound sessions. Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set."},"threadBindings.maxAgeHours":{"label":"Telegram Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Telegram bound sessions. Set 0 to disable hard cap (default: 0', - '). Overrides session.threadBindings.maxAgeHours when set."},"threadBindings.spawnSessions":{"label":"Telegram Thread-Bound Session Spawn","help":"Allow sessions_spawn(thread=true) and ACP thread spawns to auto-bind Telegram current conversations when supported."},"threadBindings.defaultSpawnContext":{"label":"Telegram Thread Spawn Context","help":"Default native subagent context for thread-bound spawns. \\"fork\\" starts from the requester transcript; \\"isolated\\" starts clean. Default: \\"fork\\"."}}},{"pluginId":"tlon","channelId":"tlon","order":90,"label":"Tlon","description":"decentralized messaging on Urbit; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1},"authorization":{"type":"object","properties":{"channelRules":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"mode":{"type":"string","enum":["restricted","open"]},"allowedShips":{"type":"array","items":{"type":"string","minLength":1}}},"additionalProperties":false}}},"additionalProperties":false},"defaultAuthorizedShips":{"type":"array","items":{"type":"string","minLength":1}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1}},"additionalProperties":false}}},"additionalProperties":false},"uiHints":{"implicitMentions":{"label":"Tlon Implicit Mentions","help":"Control which Tlon reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Tlon Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Tlon Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Tlon Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."}}},{"pluginId":"twitch","channelId":"twitch","aliases":["twitch-chat"],"channelEnvVars":["OPENCLAW_TWITCH_ACCESS_TOKEN"],"label":"Twitch","description":"Twitch chat integration","schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false}}},"required":["accounts"],"additionalProperties":false}]}},{"pluginId":"whatsapp","channelId":"whatsapp","label":"WhatsApp","description":"works with your own number; recommend a separate phone + eSIM.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"default":50,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"debounceMs":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"debounceMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"name":{"type":"string"},"authDir":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"polls":{"type":"boolean"},"calls":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy","mediaMaxMb","debounceMs"],"additionalProperties":false},"uiHints":{"":{"label":"WhatsApp","help":"WhatsApp channel provider configuration for access policy and message batching behavior. Use this section to tune responsiveness and direct-message routing safety for WhatsApp chats."},"dmPolicy":{"label":"WhatsApp DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.whatsapp.allowFrom=[\\"*\\"]."},"selfChatMode":{"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number)."},"debounceMs":{"label":"WhatsApp Message Debounce (ms)","help":"Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable)."},"configWrites":{"label":"WhatsApp Config Writes","help":"Allow WhatsApp to write config in response to channel events/commands (default: true)."},"actions.calls":{"label":"WhatsApp Voice Calls","help":"Expose the experimental requester-bound WhatsApp voice-call tool. Default: false. Requires a separately paired MeowCaller CLI."},"mentionPatterns":{"label":"WhatsApp Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected WhatsApp conversation IDs such as 123@g.us."},"mentionPatterns.mode":{"label":"WhatsApp Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"WhatsApp Mention Pattern Allowlist","help', - '":"WhatsApp conversation IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"WhatsApp Mention Pattern Denylist","help":"WhatsApp conversation IDs where configured regex mention patterns are disabled."}},"unsupportedSecretRefSurfacePatterns":["channels.whatsapp.accounts.*.creds.json","channels.whatsapp.creds.json"]},{"pluginId":"zalo","channelId":"zalo","aliases":["zl"],"order":80,"channelEnvVars":["ZALO_BOT_TOKEN","ZALO_WEBHOOK_SECRET"],"label":"Zalo","description":"Vietnam-focused messaging platform with Bot API.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"zalouser","channelId":"zalouser","aliases":["zlu"],"order":85,"channelEnvVars":["ZALOUSER_PROFILE","ZCA_PROFILE"],"label":"Zalo Personal","description":"Zalo personal account via QR code login.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}}]', + '"},"configWrites":{"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true)."},"cliPath":{"label":"iMessage CLI Path","help":"Filesystem path to the iMessage bridge CLI binary used for send/receive operations. Set explicitly when the binary is not on PATH in service runtime environments."},"sendTransport":{"label":"iMessage Send Transport","help":"Preferred imsg RPC send transport for normal outbound replies. \\"auto\\" uses the IMCore bridge when available, \\"bridge\\" requires it, and \\"applescript\\" forces Messages automation."}}},{"pluginId":"irc","channelId":"irc","aliases":["internet-relay-chat"],"channelEnvVars":["IRC_HOST","IRC_NICK"],"label":"IRC","description":"classic IRC networks with DM/channel routing and pairing controls.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"IRC","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw."},"dmPolicy":{"label":"IRC DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.irc.allowFrom=[\\"*\\"]."},"nickserv.enabled":{"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured)."},"nickserv.service":{"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ)."},"nickserv.password":{"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive)."},"nickserv.passwordFile":{"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password."},"nickserv.register":{"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable."},"nickserv.registerEmail":{"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true)."},"configWrites":{"label":"IRC Config Writes","help":"Allow IRC to write config in response to channel events/commands (default: true)."}}},{"pluginId":"line","channelId":"line","order":75,"channelEnvVars":["LINE_CHANNEL_ACCESS_TOKEN","LINE_CHANNEL_SECRET"],"label":"LINE","description":"LINE Messaging API webhook bot.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"matrix","channelId":"matrix","order":70,"channelEnvVars":["MATRIX_ACCESS_TOKEN","MATRIX_DEVICE_ID","MATRIX_DEVICE_NAME","MATRIX_HOMESERVER","MATRIX_OPS_ACCESS_TOKEN","MATRIX_OPS_DEVICE_ID","MATRIX_OPS_DEVICE_NAME","MATRIX_OPS_HOMESERVER","MATRIX_PASSWORD","MATRIX_USER_ID"],"label":"Matrix","description":"open protocol; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"homeserver":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"userId":{"type":"string"},"accessToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"password":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"avatarUrl":{"type":"string"},"initialSyncLimit":{"type":"number"},"encryption":{"type":"boolean"},"allowlistOnly":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["partial","quiet","progress","off"]},"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","', + 'minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"}},"additionalProperties":false},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"textChunkLimit":{"type":"number"},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","none","off"]},"reactionNotifications":{"type":"string","enum":["off","own"]},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"startupVerification":{"type":"string","enum":["off","if-unverified"]},"startupVerificationCooldownHours":{"type":"number"},"mediaMaxMb":{"type":"number"},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoJoin":{"type":"string","enum":["always","allowlist","off"]},"autoJoinAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"sessionScope":{"type":"string","enum":["per-user","per-room"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"type":"boolean"},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"rooms":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"account":{"type":"string"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"autoReply":{"type":"boolean"},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false}},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"profile":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"verification":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"uiHints":{"mentionPatterns":{"label":"Matrix Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Matrix room IDs. Native Matrix mention evidence still triggers even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Matrix Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Matrix Mention Pattern Allowlist","help":"Matrix room IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Matrix Mention Pattern Denylist","help":"Matrix room IDs where configured regex mention patterns are disabled. Native mention evidence still triggers."},"allowBots":{"label":"Matrix Allow Bot Messages","help":"Allow messages from other configured Matrix bot accounts to trigger replies (default: false). Set \\"mentions\\" to require a visible room mention."},"botLoopProtection":{"label":"Matrix Bot Loop Protection","help":"Sliding-window guard for accepted Matrix configured-bot loops. Default is enabled whenever allowBots lets configured bot messages reach dispatch."},"botLoopProtection.enabled":{"label":"Matrix Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when configured bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Matrix Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Matrix Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Matrix Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"dangerouslyAllowNameMatching":{"label":"Matrix Display Name Matching","help":"Compatibility opt-in for resolving Matrix display names and joined room names in allowlists. Prefer full @user:server IDs and room IDs or aliases because names are mutable."},"streaming.progress.label":{"label":"Matrix Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Matrix Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Matrix Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Matrix Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Matrix Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Matrix Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"pluginId":"mattermost","channelId":"mattermost","order":65,"channelEnvVars":["MATTERMOST_BOT_TOKEN","MATTERMOST_URL"],"label":"Mattermost","description":"self-hosted Slack-style chat; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"dangerouslyAllowNameMatching":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"baseUrl":{"type":"string"},"chatmode":{"type":"string","enum":["oncall","onmessage","onchar"]},"oncharPrefixes":{"type":"array","items":{"type":"string"}},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":', + '"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"replyToModeByChatType":{"type":"object","properties":{"direct":{"type":"string","enum":["off","first","all","batched"]},"group":{"type":"string","enum":["off","first","all","batched"]},"channel":{"type":"string","enum":["off","first","all","batched"]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"callbackPath":{"type":"string"},"callbackUrl":{"type":"string"}},"additionalProperties":false},"interactions":{"type":"object","properties":{"callbackBaseUrl":{"type":"string"},"allowedSourceIps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"dmChannelRetry":{"type":"object","properties":{"maxRetries":{"type":"integer","minimum":0,"maximum":10},"initialDelayMs":{"type":"integer","minimum":100,"maximum":60000},"maxDelayMs":{"type":"integer","minimum":1000,"maximum":60000},"timeoutMs":{"type":"integer","minimum":5000,"maximum":120000}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Mattermost","help":"Mattermost channel provider configuration for bot auth, access policy, slash commands, and preview streaming."},"dmPolicy":{"label":"Mattermost DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.mattermost.allowFrom=[\\"*\\"]."},"implicitMentions":{"label":"Mattermost Implicit Mentions","help":"Control which Mattermost reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Mattermost Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Mattermost Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Mattermost Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"streaming":{"label":"Mattermost Streaming Mode","help":"Unified Mattermost stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". \\"progress\\" keeps a single editable progress draft until final delivery."},"streaming.mode":{"label":"Mattermost Streaming Mode","help":"Canonical Mattermost preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"streaming.progress.label":{"label":"Mattermost Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Mattermost Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Mattermost Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Mattermost Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Mattermost Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Mattermost Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.preview.toolProgress":{"label":"Mattermost Draft Tool Progress","help":"Show tool/progress activity in the live draft preview post (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Mattermost Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.block.enabled":{"label":"Mattermost Block Streaming Enabled","help":"Enable chunked block-style Mattermost preview delivery when channels.mattermost.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Mattermost Block Streaming Coalesce","help":"Merge streamed Mattermost block replies before final delivery."}}},{"pluginId":"msteams","channelId":"msteams","aliases":["teams"],"order":60,"channelEnvVars":["MSTEAMS_APP_ID","MSTEAMS_APP_PASSWORD","MSTEAMS_TENANT_ID"],"label":"Microsoft Teams","description":"Teams SDK; enterprise support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"dangerouslyAllowNameMatching":{"type":"boolean"},"appId":{"type":"string"},"appPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tenantId":{"type":"string"},"cloud":{"type":"string","enum":["Public","USGov","USGovDoD","China"]},"serviceUrl":{"type":"string","format":"uri"},"authType":{"type":"string","enum":["secret","federated"]},"certificatePath":{"type":"string"},"certificateThumbprint":{"type":"string"},"useManagedIdentity":{"type":"boolean"},"managedIdentityClientId":{"type":"string"},"webhook":{"type":"object","properties":{"port":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"path":{"type":"string"}},"additionalProperties":false},"typingIndicator":{"type":"boolean"},"mediaAllowHosts":{"type":"array","items":{"type":"string"}},"mediaAuthAllowHosts":{"type":"array","items":{"type":"string"}},"graphMediaFallback":{"type":"boolean"},"requireMention":{"type":"boolean"},"replyStyle":{"type":"string","enum":["thread","top-level"]},"teams":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"replyStyle":{"type":"string","enum":["thread","top-level"]}},"additionalProperties":false}}},"additionalProperties":false}},"sharePointSiteId":{"type":"string"},"welcomeCard":{"type":"boolean"},"promptStarters":{"type":"array","items":{"type":"string"}},"groupWelcomeCard":{"type":"boolean"},"feedbackEnabled":{"type":"boolean"},"feedbackReflection":{"type":"boolean"},"feedbackReflectionCooldownMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"delegatedAuth":{"type":"object","properties":{"enabled":{"type":"boolean"},"scopes":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"sso":{"type":"object","properties":{"enabled":{"type":"boolean"},"connectionName":{"type":"string"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"MS Teams","help":"Microsoft Teams channel provider configuration and provider-specific policy toggles. Use this section to isolate Teams behavior from other enterprise chat providers."},"configWrites":{"label":"MS Teams Config Writes","help":"Allow Microsoft Teams to write config in response to channel events/commands (default: true)."},"cloud":{"label":"MS Teams Cloud","help":"Teams SDK cloud environment for auth, token validation, and token services: \\"Public\\", \\"USGov\\", \\"USGovDoD\\", or \\"China\\" (default: Public)."},"serviceUrl":{"label":"MS Teams Service URL","help":"Bot Connector service URL for SDK proactive sends/edits/deletes. Set with cloud for USGov/DoD; set alone for GCC."},"graphMediaFallback":{"label":"MS Teams Graph Media Fallback","help":"Query Microsoft Graph for unresolved channel or group-chat HTML media. Adds one lookup per matching message when enabled (default: false)."},"streaming":{"label":"MS Teams Streaming","help":"Microsoft Teams preview/progress streaming mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Personal chats use Teams native streaminfo progress when available."},"streaming.progress.label":{"label":"MS Teams Progress Label","help":"Initial progress title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"MS Teams Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"MS Teams Progress Max Lines","help":"Maximum number of compact progress lines to keep below the progress title (default: 8)."},"streaming.progress.maxLineChars":{"label":"MS Teams Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"MS Teams Progress Tool Lines","help":"Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery."},"streaming.progress.commandText":{"label":"MS Teams Progress Command Text","help":"Command/exec detail in progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."}}},{"pluginId":"nextcloud-talk","channelId":"nextcloud-talk","aliases":["nc","nc-talk"],"order":65,"channelEnvVars":["NEXTCLOUD_TALK_API_PASSWORD","NEXTCLOUD_TALK_BOT_SECRET"],"label":"Nextcloud Talk","description":"Self-hosted chat via Nextcloud Talk webhook bots.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,6', + '3}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"baseUrl":{"type":"string"},"botSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"botSecretFile":{"type":"string"},"apiUser":{"type":"string"},"apiPassword":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"apiPasswordFile":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"webhookHost":{"type":"string"},"webhookPath":{"type":"string"},"webhookPublicUrl":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"rooms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"nostr","channelId":"nostr","order":55,"channelEnvVars":["NOSTR_PRIVATE_KEY"],"label":"Nostr","description":"Decentralized protocol; encrypted DMs via NIP-04.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"defaultAccount":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"privateKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"relays":{"type":"array","items":{"type":"string"}},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"profile":{"type":"object","properties":{"name":{"type":"string","maxLength":256},"displayName":{"type":"string","maxLength":256},"about":{"type":"string","maxLength":2000},"picture":{"type":"string","format":"uri"},"banner":{"type":"string","format":"uri"},"website":{"type":"string","format":"uri"},"nip05":{"type":"string"},"lud16":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false}},{"pluginId":"qa-channel","channelId":"qa-channel","order":999,"configurable":false,"label":"QA Channel","description":"Synthetic Slack-class transport for automated OpenClaw QA scenarios.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"botUserId":{"type":"string"},"botDisplayName":{"type":"string"},"pollTimeoutMs":{"type":"integer","minimum":100,"maximum":30000},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}},"defaultTo":{"type":"string"},"actions":{"type":"object","properties":{"messages":{"type":"boolean"},"reactions":{"type":"boolean"},"search":{"type":"boolean"},"threads":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"qqbot","channelId":"qqbot","channelEnvVars":["QQBOT_APP_ID","QQBOT_CLIENT_SECRET"],"label":"QQ Bot","description":"connect to QQ via official QQ Bot API with group chat and direct message support.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow', + '":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}},"stt":{"type":"object","properties":{"enabled":{"type":"boolean"},"provider":{"type":"string"},"baseUrl":{"type":"string"},"apiKey":{"type":"string"},"model":{"type":"string"}},"additionalProperties":false},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"clientSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"clientSecretFile":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"type":"string","enum":["open","allowlist","disabled"]},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"systemPrompt":{"type":"string"},"markdownSupport":{"type":"boolean"},"voiceDirectUploadFormats":{"type":"array","items":{"type":"string"}},"audioFormatPolicy":{"type":"object","properties":{"sttDirectFormats":{"type":"array","items":{"type":"string"}},"uploadDirectFormats":{"type":"array","items":{"type":"string"}},"transcodeEnabled":{"type":"boolean"}},"additionalProperties":false},"urlDirectUpload":{"type":"boolean"},"upgradeUrl":{"type":"string"},"upgradeMode":{"type":"string","enum":["doc","hot-reload"]},"streaming":{"type":"object","properties":{"mode":{"default":"partial","type":"string","enum":["off","partial"]},"nativeTransport":{"type":"boolean"}},"required":["mode"],"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"commandLevel":{"type":"string","enum":["all","safety","strict"]},"ignoreOtherMentions":{"type":"boolean"},"historyLimit":{"type":"number"},"name":{"type":"string"},"prompt":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":{}}},"defaultAccount":{"type":"string"}},"additionalProperties":{}}},{"pluginId":"raft","channelId":"raft","order":72,"channelEnvVars":["RAFT_PROFILE"],"label":"Raft","description":"Raft CLI wake bridge for human and agent collaboration.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"profile":{"type":"string","minLength":1},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"profile":{"type":"string","minLength":1}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"reef","channelId":"reef","label":"Reef","description":"Guarded end-to-end encrypted claw messaging.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"default":true,"type":"boolean"},"relayUrl":{"default":"https://reefwire.ai","type":"string","format":"uri","pattern":"^[hH][tT][tT][pP][sS]?:\\\\/\\\\/[^\\\\\\\\/?#@]+\\\\/?$"},"handle":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,62}$"},"email":{"type":"string","format":"email","pattern":"^(?!\\\\.)(?!.*\\\\.\\\\.)([A-Za-z0-9_\'+\\\\-\\\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$"},"guard":{"type":"object","properties":{"provider":{"type":"string","enum":["anthropic","openai"]},"pinnedModel":{"type":"string","minLength":1},"apiKeyEnv":{"type":"string","pattern":"^[A-Z_][A-Z0-9_]*$"},"policyVersion":{"type":"string","minLength":1},"timeoutMs":{"type":"integer","minimum":100,"maximum":120000}},"required":["provider","pinnedModel","apiKeyEnv","policyVersion","timeoutMs"],"additionalProperties":false},"stateDir":{"type":"string","minLength":1},"requestPolicy":{"default":"code-only","type":"string","enum":["code-only","friends-of-friends","open"]},"friends":{}},"required":["enabled","relayUrl","requestPolicy"],"additionalProperties":false}},{"pluginId":"signal","channelId":"signal","label":"Signal","description":"signal-cli linked device; more setup (David Reagans: \\"Hop on Discord.\\").","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"configPath":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"apiMode":{"type":"string","enum":["auto","native","container"]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"account":{"type":"string"},"accountUuid":{"type":"string"},"configPath":{"type":"string"},"httpUrl":{"type":"string"},"httpHost":{"type":"string"},"httpPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cliPath":{"type":"string"},"autoStart":{"type":"boolean"},"startupTimeoutMs":{"type":"integer","minimum":1000,"maximum":120000},"receiveMode":{"anyOf":[{"type":"string","const":"on-start"},{"type":"string","const":"manual"}]},"ignoreAttachments":{"type":"boolean"},"ignoreStories":{"type":"boolean"},"sendReadReceipts":{"type":"boolean"},"aliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"ingest":{"type":"boolean"}},"additionalProperties":false}},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Signal","help":"Signal channel provider configuration including account identity and DM policy behavior. Keep account mapping explicit so routing remains stable across multi-device setups."},"dmPolicy":{"label":"Signal DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.signal.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Signal Config Writes","help":"Allow Signal to write config in response to channel events/commands (default: true)."},"account":{"label":"Signal Account","help":"Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state."},"configPath":{"label":"Signal CLI Config Path","help":"Optional directory passed to signal-cli via --config when the service needs a non-default signal-cli data path."}}},{"pluginId":', + '"slack","channelId":"slack","channelEnvVars":["SLACK_APP_TOKEN","SLACK_BOT_TOKEN","SLACK_USER_TOKEN"],"label":"Slack","description":"supported (Socket Mode).","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"identity":{"default":"bot","type":"string","enum":["bot","user"]},"mode":{"default":"socket","type":"string","enum":["socket","http","relay"]},"enterpriseOrgInstall":{"type":"boolean"},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"additionalProperties":false},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"default":"/slack/events","type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"interactiveReplies":{"type":"boolean"}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","sta', + 'tus"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"},"nativeTaskCards":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false},"nativeTransport":{"type":"boolean"}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"identity":{"type":"string","enum":["bot","user"]},"mode":{"type":"string","enum":["socket","http","relay"]},"enterpriseOrgInstall":{"type":"boolean"},"socketMode":{"type":"object","properties":{"clientPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"serverPingTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"pingPongLoggingEnabled":{"type":"boolean"}},"additionalProperties":false},"relay":{"type":"object","properties":{"url":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"gatewayId":{"type":"string"}},"additionalProperties":false},"signingSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"appToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"userTokenReadOnly":{"default":true,"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"unfurlLinks":{"type":"boolean"},"unfurlMedia":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"reactionAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"ackReaction":{"type":"string"},"replyToModeByChatType":{"type":"object","properties":{"direct":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"group":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"channel":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]}},"additionalProperties":false},"thread":{"type":"object","properties":{"historyScope":{"type":"string","enum":["thread","channel"]},"inheritParent":{"type":"boolean"},"initialHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"messages":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"permissions":{"type":"boolean"},"memberInfo":{"type":"boolean"},"channelInfo":{"type":"boolean"},"emojiList":{"type":"boolean"}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"sessionPrefix":{"type":"string"},"ephemeral":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"additionalProperties":false},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"},"ignoreOtherMentions":{"type":"boolean"},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"presenceEvents":{"type":"object","properties":{"mode":{"type":"string","enum":["off","auto","on"]}},"additionalProperties":false}},"additionalProperties":false}},"typingReaction":{"type":"string"}},"required":["userTokenReadOnly"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy","identity","mode","webhookPath","userTokenReadOnly"],"additionalProperties":false},"uiHints":{"":{"label":"Slack","help":"Slack channel provider configuration for bot/app tokens, streaming behavior, and DM policy controls. Keep token handling and thread behavior explicit to avoid noisy workspace interactions."},"enterpriseOrgInstall":{"label":"Slack Enterprise Grid Org Install","help":"Enable only for an Enterprise Grid org-wide bot installation. OpenClaw verifies the token with Slack auth.test at startup; DMs must be disabled or use dmPolicy=\\"open\\" with allowFrom=[\\"*\\"]."},"identity":{"label":"Slack Identity","help":"Select \\"bot\\" (default) for the classic Slack app/bot identity or \\"user\\" to post as the authorizing human through a user token while the app carries event transport."},"dmPolicy":{"label":"Slack DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.slack.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Slack Config Writes","help":"Allow Slack to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Slack Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Slack channel IDs. Native Slack @mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Slack Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Slack Mention Pattern Allowlist","help":"Slack channel IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Slack Mention Pattern Denylist","help":"Slack channel IDs where configured regex mention patterns are disabled. Native @mentions still trigger."},"commands.native":{"label":"Slack Native Commands","help":"Override native commands for Slack (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Slack Native Skill Commands","help":"Override native skill commands for Slack (bool or \\"auto\\")."},"implicitMentions":{"label":"Slack Implicit Mentions","help":"Control which Slack reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Slack Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Slack Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Slack Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."},"allowBots":{"label":"Slack Allow Bot Messages","help":"Allow bot-authored messages to trigger Slack replies (default: false)."},"botLoopProtection":{"label":"Slack Bot Loop Protection","help":"Sliding-window guard for Slack bot-to-bot loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Slack Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Slack Bot Loop Events per Window","help":"Maximum accepted bot-pair messages within the sliding window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Slack Bot Loop Window Seconds","help":"Sliding window length for counting bot-pair messages. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Slack Bot Loop Cooldown Seconds","help":"How long to suppress the bot pair after it exceeds the budget. Default: 60."},"socketMode":{"label":"Slack Socket Mode Transport","help":"Slack Socket Mode transport tuning passed to the Slack SDK. Use only when investigating ping/pong timeout or stale websocket behavior."},"socketMode.clientPingTimeout":{"label":"Slack Socket Mode Pong Timeout","help":"Milliseconds the Slack SDK waits for a pong after its client ping before treating the websocket as stale (OpenClaw default: 15000). Increase on hosts with event-loop starvation or slow network scheduling."},"socketMode.serverPingTimeout":{"label":"Slack Socket Mode Server Ping Timeout","help":"Milliseconds the Slack SDK waits for Slack server pings before treating the websocket as stale."},"socketMode.pingPongLoggingEnabled":{"label":"Slack Socket Mode Ping/Pong Logging","help":"Enable Slack SDK ping/pong transport logs while debugging Socket Mode websocket health."},"relay":{"label":"Slack Relay Mode","help":"Relay-delivered Slack events. Use with mode=\\"relay\\" when openclaw-slack-router owns the Slack Socket Mode connection."},"relay.url":{"label":"Slack Relay URL","help":"Full websocket URL for openclaw-slack-router. Include the route path, for example ws://127.0.0.1:8081/gateway/ws."},"relay.authToken":{"label":"Slack Relay Auth Token","help":"Bearer token used by this gateway to authenticate its reverse websocket connection to openclaw-slack-router."},"relay.gatewayId":{"label":"Slack Relay Gateway ID","help":"Destination id that openclaw-slack-router uses when routing user-group mentions to this gateway."},"botToken":{"label":"Slack Bot Token","help":"Slack bot token used for standard chat actions in the configured workspace. Keep this credential scoped and rotate if workspa', + 'ce app permissions change."},"appToken":{"label":"Slack App Token","help":"Slack app-level token used for Socket Mode connections and event transport when enabled. Use least-privilege app scopes and store this token as a secret."},"userToken":{"label":"Slack User Token","help":"Optional Slack user token for workflows requiring user-context API access beyond bot permissions. Use sparingly and audit scopes because this token can carry broader authority."},"userTokenReadOnly":{"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes."},"capabilities.interactiveReplies":{"label":"Slack Interactive Replies","help":"Enable agent-authored Slack interactive reply directives (`[[slack_buttons: ...]]`, `[[slack_select: ...]]`). Default: false."},"execApprovals":{"label":"Slack Exec Approvals","help":"Slack-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for this workspace account."},"presenceEvents":{"label":"Slack Presence Events","help":"Poll observed human participants and wake the routed agent on away-to-active transitions. Default: \\"off\\"."},"presenceEvents.mode":{"label":"Slack Presence Event Mode","help":"\\"off\\" disables polling; \\"auto\\" covers DMs, MPIMs, and recent threads with up to 8 observed people; \\"on\\" also covers larger threads and top-level channels."},"channels.*.presenceEvents.mode":{"label":"Slack Channel Presence Event Mode","help":"Override presence events for one Slack channel. Use \\"on\\" to include large threads or top-level channel sessions."},"execApprovals.enabled":{"label":"Slack Exec Approvals Enabled","help":"Controls Slack native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Slack Exec Approval Approvers","help":"Slack user IDs allowed to approve exec requests for this workspace account. Use Slack user IDs or user targets such as `U123`, `user:U123`, or `<@U123>`. If you leave this unset, OpenClaw falls back to commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Slack Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Slack exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Slack."},"execApprovals.sessionFilter":{"label":"Slack Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Slack approval routing is used. Use narrow patterns so Slack approvals only appear for intended sessions."},"execApprovals.target":{"label":"Slack Exec Approval Target","help":"Controls where Slack approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Slack chat/thread, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted channels."},"streaming":{"label":"Slack Streaming Mode","help":"Unified Slack stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Slack Streaming Mode","help":"Canonical Slack preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"streaming.chunkMode":{"label":"Slack Chunk Mode","help":"Chunking mode for outbound Slack text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Slack Block Streaming Enabled","help":"Enable chunked block-style Slack preview delivery when channels.slack.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Slack Block Streaming Coalesce","help":"Merge streamed Slack block replies before final delivery."},"streaming.nativeTransport":{"label":"Slack Native Streaming","help":"Enable native Slack text streaming (chat.startStream/chat.appendStream/chat.stopStream) when channels.slack.streaming.mode is partial (default: true). Native streaming and Slack assistant thread status require a reply thread target; top-level DMs can still use draft post-and-edit preview streaming."},"streaming.preview.toolProgress":{"label":"Slack Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Slack Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.label":{"label":"Slack Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Slack Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"Slack Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Slack Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.render":{"label":"Slack Progress Renderer","help":"Progress draft renderer: \\"text\\" uses one portable text body; \\"rich\\" renders structured Slack Block Kit fields with the same text fallback."},"streaming.progress.nativeTaskCards":{"label":"Slack Native Progress Task Cards","help":"Opt in to Slack native task-card progress updates when channels.slack.streaming.mode=\\"progress\\" and streaming.nativeTransport is enabled. Default: false."},"streaming.progress.toolProgress":{"label":"Slack Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Slack Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"thread.historyScope":{"label":"Slack Thread History Scope","help":"Scope for Slack thread history context (\\"thread\\" isolates per thread; \\"channel\\" reuses channel history)."},"thread.inheritParent":{"label":"Slack Thread Parent Inheritance","help":"If true, Slack thread sessions inherit the parent channel transcript (default: false)."},"thread.initialHistoryLimit":{"label":"Slack Thread Initial History Limit","help":"Maximum number of existing Slack thread messages to fetch when starting a new thread session (default: 20, set to 0 to disable)."}}},{"pluginId":"sms","channelId":"sms","order":88,"channelEnvVars":["SMS_ALLOWED_USERS","SMS_PUBLIC_WEBHOOK_URL","SMS_WEBHOOK_PATH","TWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN","TWILIO_MESSAGING_SERVICE_SID","TWILIO_PHONE_NUMBER","TWILIO_SMS_FROM"],"label":"SMS","description":"Twilio-backed SMS with inbound webhooks and outbound replies.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"accountSid":{"type":"string"},"authToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"fromNumber":{"type":"string"},"messagingServiceSid":{"type":"string"},"defaultTo":{"type":"string"},"webhookPath":{"type":"string"},"publicWebhookUrl":{"type":"string"},"dangerouslyDisableSignatureValidation":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["dmPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"SMS","help":"Twilio SMS channel configuration for inbound webhooks and outbound text replies."},"accountSid":{"label":"Twilio Account SID","help":"Twilio Account SID used for SMS outbound API calls."},"authToken":{"label":"Twilio Auth Token","help":"Twilio Auth Token used to sign webhook validation and SMS outbound API calls."},"fromNumber":{"label":"SMS From Number","help":"Twilio SMS-capable phone number in E.164 format, for example +15551234567."},"messagingServiceSid":{"label":"Twilio Messaging Service SID","help":"Twilio Messaging Service SID to use instead of a dedicated fromNumber."},"defaultTo":{"label":"SMS Default To Number","help":"Optional default outbound phone number used when a send flow omits an explicit SMS target."},"publicWebhookUrl":{"label":"SMS Public Webhook URL","help":"Public URL configured in Twilio for incoming messages. Must match Twilio\'s signed URL exactly."},"webhookPath":{"label":"SMS Webhook Path","help":"Gateway HTTP path that receives Twilio incoming-message webhooks. Use a distinct path per account."},"dmPolicy":{"label":"SMS DM Policy","help":"Direct SMS access control (\\"pairing\\" recommended). \\"open\\" requires channels.sms.allowFrom=[\\"*\\"]."},"allowFrom":{"label":"SMS Allow From","help":"Allowed sender phone numbers in E.164 format, or * when dmPolicy is open."},"textChunkLimit":{"label":"SMS Text Chunk Limit","help":"Maximum characters per outbound SMS chunk before OpenClaw splits long replies."}}},{"pluginId":"synology-chat","channelId":"synology-chat","order":90,"channelEnvVars":["OPENCLAW_BOT_NAME","SYNOLOGY_ALLOWED_USER_IDS","SYNOLOGY_CHAT_INCOMING_URL","SYNOLOGY_CHAT_TOKEN","SYNOLOGY_NAS_HOST","SYNOLOGY_RATE_LIMIT"],"label":"Synology Chat","description":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"dangerouslyAllowNameMatching":{"type":"boolean"},"dangerouslyAllowInheritedWebhookPath":{"type":"boolean"}},"additionalProperties":{}}},{"pluginId":"telegram","channelId":"telegram","channelEnvVars":["TELEGRAM_BOT_TOKEN"],"label":"Telegram","description":"simplest way to get started — register a bot with @BotFather and get going.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProp', + 'erties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct ingress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"object","properties":{"inlineButtons":{"type":"string","enum":["off","dm","group","all","allowlist"]}},"additionalProperties":false}]},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"anyOf":[{"type":"string"},{"type":"number"}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"},"narration":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"customCommands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"description":{"type":"string"}},"required":["command","description"],"additionalProperties":false}},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf"', + ':[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"topics":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ingest":{"type":"boolean"},"disableAudioPreflight":{"type":"boolean"},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"agentId":{"type":"string"},"errorPolicy":{"type":"string","enum":["always","once","silent"]}},"additionalProperties":false}},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"requireTopic":{"type":"boolean"},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false}},"richMessages":{"type":"boolean"},"network":{"type":"object","properties":{"autoSelectFamily":{"type":"boolean"},"dnsResultOrder":{"type":"string","enum":["ipv4first","verbatim"]},"dangerouslyAllowPrivateNetwork":{"description":"Dangerous opt-in for trusted Telegram fake-IP or transparent-proxy environments where api.telegram.org resolves to private/internal/special-use addresses during media downloads.","type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"webhookUrl":{"description":"Public HTTPS webhook URL registered with Telegram for inbound updates. This must be internet-reachable and requires channels.telegram.webhookSecret.","type":"string"},"webhookSecret":{"description":"Secret token sent to Telegram during webhook registration and verified on inbound webhook requests. Telegram returns this value for verification; this is not the gateway auth token and not the bot token.","anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"description":"Local webhook route path served by the gateway listener. Defaults to /telegram-webhook.","type":"string"},"webhookHost":{"description":"Local bind host for the webhook listener. Defaults to 127.0.0.1; keep loopback unless you intentionally expose direct ingress.","type":"string"},"webhookPort":{"description":"Local bind port for the webhook listener. Defaults to 8787; set to 0 to let the OS assign an ephemeral port.","type":"integer","minimum":0,"maximum":9007199254740991},"webhookCertPath":{"description":"Path to the self-signed certificate (PEM) to upload to Telegram during webhook registration. Required for self-signed certs (direct IP or no domain).","type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"poll":{"type":"boolean"},"deleteMessage":{"type":"boolean"},"editMessage":{"type":"boolean"},"sticker":{"type":"boolean"},"createForumTopic":{"type":"boolean"},"editForumTopic":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]}},"additionalProperties":false},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"string"},"linkPreview":{"type":"boolean"},"silentErrorReplies":{"type":"boolean"},"errorPolicy":{"type":"string","enum":["always","once","silent"]},"apiRoot":{"type":"string","format":"uri"},"trustedLocalFileRoots":{"description":"Trusted local filesystem roots for self-hosted Telegram Bot API absolute file_path values. Only absolute paths under these roots are read directly; all other absolute paths are rejected.","type":"array","items":{"type":"string"}},"autoTopicLabel":{"anyOf":[{"type":"boolean"},{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"type":"string"}},"additionalProperties":false}]}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Telegram","help":"Telegram channel provider configuration including auth tokens, retry behavior, and message rendering controls. Use this section to tune bot behavior for Telegram-specific API semantics."},"customCommands":{"label":"Telegram Custom Commands","help":"Additional Telegram bot menu commands (merged with native; conflicts ignored)."},"botToken":{"label":"Telegram Bot Token","help":"Telegram bot token used to authenticate Bot API requests for this account/provider config. Use secret/env substitution and rotate tokens if exposure is suspected."},"dmPolicy":{"label":"Telegram DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.telegram.allowFrom=[\\"*\\"]."},"configWrites":{"label":"Telegram Config Writes","help":"Allow Telegram to write config in response to channel events/commands (default: true)."},"mentionPatterns":{"label":"Telegram Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected Telegram group chat IDs or chatId:topic:threadId topic IDs. Native Telegram bot mentions still trigger even when regex patterns are denied."},"mentionPatterns.mode":{"label":"Telegram Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"Telegram Mention Pattern Allowlist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label":"Telegram Mention Pattern Denylist","help":"Telegram group chat IDs or chatId:topic:threadId topic IDs where configured regex mention patterns are disabled. Native bot mentions still trigger."},"commands.native":{"label":"Telegram Native Commands","help":"Override native commands for Telegram (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Telegram Native Skill Commands","help":"Override native skill commands for Telegram (bool or \\"auto\\")."},"streaming":{"label":"Telegram Streaming Mode","help":"Unified Telegram stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"partial\\"). \\"progress\\" keeps a single editable progress draft until final delivery. Legacy boolean/streamMode keys are detected; run doctor --fix to migrate."},"streaming.mode":{"label":"Telegram Streaming Mode","help":"Canonical Telegram preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\" (default: \\"partial\\")."},"streaming.chunkMode":{"label":"Telegram Chunk Mode","help":"Chunking mode for outbound Telegram text delivery: \\"length\\" (default) or \\"newline\\"."},"richMessages":{"label":"Telegram Rich Messages","help":"Opt into Bot API 10.1 rich text sends and edits, including native tables and rich media. Default: false because some current Telegram clients render these messages as unsupported."},"streaming.block.enabled":{"label":"Telegram Block Streaming Enabled","help":"Enable chunked block-style Telegram preview delivery when channels.telegram.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Telegram Block Streaming Coalesce","help":"Merge streamed Telegram block replies before sending final delivery."},"streaming.preview.chunk.minChars":{"label":"Telegram Draft Chunk Min Chars","help":"Minimum chars before emitting a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.maxChars":{"label":"Telegram Draft Chunk Max Chars","help":"Target max size for a Telegram block preview chunk when channels.telegram.streaming.mode=\\"block\\"."},"streaming.preview.chunk.breakPreference":{"label":"Telegram Draft Chunk Break Preference","help":"Preferred breakpoints for Telegram draft chunks (paragraph | newline | sentence)."},"streaming.preview.toolProgress":{"label":"Telegram Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true when preview streaming is active). Set false to keep tool updates out of the edited Telegram preview."},"streaming.preview.commandText":{"label":"Telegram Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.label":{"label":"Telegram Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Telegram Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use the built-in \\"Working\\" label."},"streaming.progress.maxLines":{"label":"Telegram Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Telegram Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Telegram Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commandText":{"label":"Telegram Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.commentary":{"label":"Telegram Progress Commentary","help":"Show assistant commentary/preamble text in the temporary progress draft. Final answer delivery is unchanged."},"network.autoSelectFamily":{"label":"Telegram autoSelectFamily","help":"Override Node autoSelectFamily for Telegram (true=enable, false=disable)."},"network.dangerouslyAllowPrivateNetwork":{"label":"Telegram Dangerously Allow Private Network","help":"Dangerous opt-in for trusted fake-IP or transparent-proxy environments where Telegram media downloads resolve api.telegram.org to private/internal/special-use addresses."},"silentErrorReplies":{"label":"Telegram Silent Error Replies","help":"When true, Telegram bot replies marked as errors are sent silently (no notification sound). Default: false."},"apiRoot":{"label":"Telegram API Root URL","help":"Custom Telegram Bot API root URL. Use the API root only (for example https://api.telegram.org), not a full /bot endpoint. Use for self-hosted Bot API servers (https://github.com/tdlib/telegram-bot-api) or reverse proxies in regions where api.telegram.org is blocked."},"trustedLocalFileRoots":{"label":"Telegram Trusted Local File Roots","help":"Trusted local filesystem roots for self-hosted Telegram Bot API file_path values. Exact in-root paths are read directly; container paths under /var/lib/telegram-bot-api can map into a host volume mount. Other absolute paths are rejected."},"autoTopicLabel":{"label":"Telegram Auto Topic Label","help":"Auto-rename DM forum topics on first message using LLM. Default: true. Set to false to disable, or use object form { enabled: true, prompt: \'...\' } for custom prompt."},"autoTopicLabel.enabled":{"label":"Telegram Auto Topic Label Enabled","help":"Whether auto topic labeling is enabled. Default: true."},"autoTopicLabel.prompt":{"label":"Telegram Auto Topic Label Prompt","help":"Custom prompt for LLM-based topic naming. The user message is appended after the prompt."},"capabilities.inlineButtons":{"label":"Telegram Inline Buttons","help":"Enable Telegram inline button components for supported command and interaction surfaces. Disable if your deployment needs plain-text-only compatibility behavior."},"execApprovals":{"label":"Telegram Exec Approvals","help":"Telegram-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for the selected bot account."},"execApprovals.enabled":{"label":"Telegram Exec Approvals Enabled","help":"Controls Telegram native exec approvals for this account: unset or \\"auto\\" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them."},"execApprovals.approvers":{"label":"Telegram Exec Approval Approvers","help":"Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from commands.ownerAllowFrom when possible."},"execApprovals.agentFilter":{"label":"Telegram Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Telegram exec approvals, for example `[\\"main\\", \\"ops-agent\\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Telegram."},"execApprovals.sessionFilter":{"label":"Telegram Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Telegram approval routing is used. Use narrow patterns so Telegram approvals only appear for intended sessions."},"execApprovals.target":{"label":"Telegram Exec Approval Target","help":"Controls where Telegram approval prompts are sent: \\"dm\\" sends to approver DMs (default), \\"channel\\" sends to the originating Telegram chat/topic, and \\"both\\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted groups/topics."},"threadBindings.enabled":{"label":"Telegram Thread Binding Enabled","help":"Enable Telegram conversation binding features (/focus, /unfocus, /agents, and /session idle|max-age). Overrides session.threadBindings.enabled when set."},"threadBindings.idleHours":{"label":"Telegram Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Telegram bound sessions. Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set."},"threadBindings.maxAgeHours":{"label":"Telegram Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Telegram bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set."},"threadBindings.spawnSessions":{"label":"Telegram Thread-Bound Session Spaw', + 'n","help":"Allow sessions_spawn(thread=true) and ACP thread spawns to auto-bind Telegram current conversations when supported."},"threadBindings.defaultSpawnContext":{"label":"Telegram Thread Spawn Context","help":"Default native subagent context for thread-bound spawns. \\"fork\\" starts from the requester transcript; \\"isolated\\" starts clean. Default: \\"fork\\"."}}},{"pluginId":"tlon","channelId":"tlon","order":90,"label":"Tlon","description":"decentralized messaging on Urbit; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1},"authorization":{"type":"object","properties":{"channelRules":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"mode":{"type":"string","enum":["restricted","open"]},"allowedShips":{"type":"array","items":{"type":"string","minLength":1}}},"additionalProperties":false}}},"additionalProperties":false},"defaultAuthorizedShips":{"type":"array","items":{"type":"string","minLength":1}},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"ship":{"type":"string","minLength":1},"url":{"type":"string"},"code":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"groupChannels":{"type":"array","items":{"type":"string","minLength":1}},"dmAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"groupInviteAllowlist":{"type":"array","items":{"type":"string","minLength":1}},"autoDiscoverChannels":{"type":"boolean"},"showModelSignature":{"type":"boolean"},"responsePrefix":{"type":"string"},"implicitMentions":{"type":"object","properties":{"replyToBot":{"type":"boolean"},"quotedBot":{"type":"boolean"},"threadParticipation":{"type":"boolean"}},"additionalProperties":false},"autoAcceptDmInvites":{"type":"boolean"},"autoAcceptGroupInvites":{"type":"boolean"},"ownerShip":{"type":"string","minLength":1}},"additionalProperties":false}}},"additionalProperties":false},"uiHints":{"implicitMentions":{"label":"Tlon Implicit Mentions","help":"Control which Tlon reply, quote, and thread-participation signals count as mentions. Unset flags preserve the channel defaults."},"implicitMentions.replyToBot":{"label":"Tlon Replies to Bot","help":"Treat replies to the bot\'s own messages as implicit mentions when the channel reports that signal."},"implicitMentions.quotedBot":{"label":"Tlon Quoted Bot Messages","help":"Treat messages quoting the bot as implicit mentions when the channel reports that signal."},"implicitMentions.threadParticipation":{"label":"Tlon Thread Participation","help":"Treat follow-ups in threads where the bot participated as implicit mentions when the channel reports that signal."}}},{"pluginId":"twitch","channelId":"twitch","aliases":["twitch-chat"],"channelEnvVars":["OPENCLAW_TWITCH_ACCESS_TOKEN"],"label":"Twitch","description":"Twitch chat integration","schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"username":{"type":"string"},"accessToken":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string","minLength":1},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"type":"string"}},"allowedRoles":{"type":"array","items":{"type":"string","enum":["moderator","owner","vip","subscriber","all"]}},"requireMention":{"type":"boolean"},"responsePrefix":{"type":"string"},"clientSecret":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"obtainmentTimestamp":{"type":"number"}},"required":["username","accessToken","channel"],"additionalProperties":false}}},"required":["accounts"],"additionalProperties":false}]}},{"pluginId":"whatsapp","channelId":"whatsapp","label":"WhatsApp","description":"works with your own number; recommend a separate phone + eSIM.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"default":50,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"debounceMs":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"enabled":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"type":"string"}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"mentionPatterns":{"type":"object","properties":{"mode":{"anyOf":[{"type":"string","const":"allow"},{"type":"string","const":"deny"}]},"allowIn":{"type":"array","items":{"type":"string"}},"denyIn":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"streaming":{"type":"object","properties":{"chunkMode":{"type":"string","enum":["length","newline"]},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"sendReadReceipts":{"type":"boolean"},"messagePrefix":{"type":"string"},"selfChatMode":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"direct":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"systemPrompt":{"type":"string"}},"additionalProperties":false}},"reactionLevel":{"type":"string","enum":["off","ack","minimal","extensive"]},"ackReaction":{"type":"object","properties":{"emoji":{"type":"string"},"direct":{"default":true,"type":"boolean"},"group":{"default":"mentions","type":"string","enum":["always","mentions","never"]}},"required":["direct","group"],"additionalProperties":false},"debounceMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"pluginHooks":{"type":"object","properties":{"messageReceived":{"type":"boolean"}},"additionalProperties":false},"name":{"type":"string"},"authDir":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"sendMessage":{"type":"boolean"},"polls":{"type":"boolean"},"calls":{"type":"boolean"}},"additionalProperties":false}},"required":["dmPolicy","groupPolicy","mediaMaxMb","debounceMs"],"additionalProperties":false},"uiHints":{"":{"label":"WhatsApp","help":"WhatsApp channel provider configuration for access policy and message batching behavior. Use this section to tune responsiveness and direct-message routing safety for WhatsApp chats."},"dmPolicy":{"label":"WhatsApp DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.whatsapp.allowFrom=[\\"*\\"]."},"selfChatMode":{"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number)."},"debounceMs":{"label":"WhatsApp Message Debounce (ms)","help":"Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable)."},"configWrites":{"label":"WhatsApp Config Writes","help":"Allow WhatsApp to write config in response to channel events/commands (default: true)."},"actions.calls":{"label":"WhatsApp Voice Calls","help":"Expose the experimental requester-bound WhatsApp voice-call tool. Default: false. Requires a separately paired MeowCaller CLI."},"mentionPatterns":{"label":"WhatsApp Mention Pattern Policy","help":"Scopes configured groupChat mentionPatterns to selected WhatsApp conversation IDs such as 123@g.us."},"mentionPatterns.mode":{"label":"WhatsApp Mention Pattern Mode","help":"\\"allow\\" enables configured regex mention patterns unless denyIn matches; \\"deny\\" disables them unless allowIn matches."},"mentionPatterns.allowIn":{"label":"WhatsApp Mention Pattern Allowlist","help":"WhatsApp conversation IDs where configured regex mention patterns are enabled when mode is deny."},"mentionPatterns.denyIn":{"label"', + ':"WhatsApp Mention Pattern Denylist","help":"WhatsApp conversation IDs where configured regex mention patterns are disabled."}},"unsupportedSecretRefSurfacePatterns":["channels.whatsapp.accounts.*.creds.json","channels.whatsapp.creds.json"]},{"pluginId":"zalo","channelId":"zalo","aliases":["zl"],"order":80,"channelEnvVars":["ZALO_BOT_TOKEN","ZALO_WEBHOOK_SECRET"],"label":"Zalo","description":"Vietnam-focused messaging platform with Bot API.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"botToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"tokenFile":{"type":"string"},"webhookUrl":{"type":"string"},"webhookSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"webhookPath":{"type":"string"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"mediaMaxMb":{"type":"number"},"proxy":{"type":"string"},"responsePrefix":{"type":"string"}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"zalouser","channelId":"zalouser","aliases":["zlu"],"order":85,"channelEnvVars":["ZALOUSER_PROFILE","ZCA_PROFILE"],"label":"Zalo Personal","description":"Zalo personal account via QR code login.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"},"accounts":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"profile":{"type":"string"},"dangerouslyAllowNameMatching":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"enabled":{"type":"boolean"}},"additionalProperties":false}},"messagePrefix":{"type":"string"},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}}]', ].join(""); export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = JSON.parse( diff --git a/src/config/channel-configured.test.ts b/src/config/channel-configured.test.ts index 55401c30b458..2f432669f9e5 100644 --- a/src/config/channel-configured.test.ts +++ b/src/config/channel-configured.test.ts @@ -29,6 +29,21 @@ describe("isChannelConfigured", () => { ).toBe(true); }); + it("requires both Mattermost URL and token env vars through the package metadata seam", () => { + expect(isChannelConfigured({}, "mattermost", { MATTERMOST_BOT_TOKEN: "token" })).toBe(false); + expect( + isChannelConfigured({}, "mattermost", { + MATTERMOST_URL: "https://mattermost.example.test", + }), + ).toBe(false); + expect( + isChannelConfigured({}, "mattermost", { + MATTERMOST_BOT_TOKEN: "token", + MATTERMOST_URL: "https://mattermost.example.test", + }), + ).toBe(true); + }); + it("still falls back to generic config presence for channels without a custom hook", () => { expect( isChannelConfigured( diff --git a/src/config/commands.test.ts b/src/config/commands.test.ts index f4bc4a3ea03a..9d058aae0e70 100644 --- a/src/config/commands.test.ts +++ b/src/config/commands.test.ts @@ -106,7 +106,6 @@ describe("resolveNativeSkillsEnabled", () => { const env = { ...process.env, OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"), - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", }; expect( diff --git a/src/config/config.env-vars.test.ts b/src/config/config.env-vars.test.ts index 806e817a4ce5..3918d0c2104a 100644 --- a/src/config/config.env-vars.test.ts +++ b/src/config/config.env-vars.test.ts @@ -572,10 +572,14 @@ describe("config env vars", () => { await fs.writeFile(path.join(stateDir, ".env"), "BRAVE_API_KEY=from-dotenv\n", "utf-8"); const config: OpenClawConfig = { - tools: { - web: { - search: { - apiKey: "${BRAVE_API_KEY}", + plugins: { + entries: { + brave: { + config: { + webSearch: { + apiKey: "${BRAVE_API_KEY}", + }, + }, }, }, }, @@ -583,12 +587,24 @@ describe("config env vars", () => { loadDotEnv({ quiet: true }); const first = resolveConfigEnvVars(config, process.env) as OpenClawConfig; - expect(first.tools?.web?.search?.apiKey).toBe("from-dotenv"); + expect( + ( + first.plugins?.entries?.brave?.config as + | { webSearch?: { apiKey?: unknown } } + | undefined + )?.webSearch?.apiKey, + ).toBe("from-dotenv"); delete process.env.BRAVE_API_KEY; loadDotEnv({ quiet: true }); const second = resolveConfigEnvVars(config, process.env) as OpenClawConfig; - expect(second.tools?.web?.search?.apiKey).toBe("from-dotenv"); + expect( + ( + second.plugins?.entries?.brave?.config as + | { webSearch?: { apiKey?: unknown } } + | undefined + )?.webSearch?.apiKey, + ).toBe("from-dotenv"); }); }); }); diff --git a/src/config/config.web-search-provider.test.ts b/src/config/config.web-search-provider.test.ts index 476055beacad..f92bbd768604 100644 --- a/src/config/config.web-search-provider.test.ts +++ b/src/config/config.web-search-provider.test.ts @@ -8,13 +8,6 @@ vi.mock("../runtime.js", () => ({ defaultRuntime: { log: vi.fn(), error: vi.fn() }, })); -vi.mock("../plugin-sdk/telegram-command-config.js", () => ({ - TELEGRAM_COMMAND_NAME_PATTERN: /^[a-z0-9_]+$/, - normalizeTelegramCommandName: (value: string) => value.trim().toLowerCase(), - normalizeTelegramCommandDescription: (value: string) => value.trim(), - resolveTelegramCustomCommands: () => ({ commands: [], issues: [] }), -})); - const mockWebSearchProviders = vi.hoisted(() => { const getScopedWebSearchCredential = (key: string) => (search?: Record) => (search?.[key] as { apiKey?: unknown } | undefined)?.apiKey; diff --git a/src/config/io.context.ts b/src/config/io.context.ts index 5f128fa77dfd..0a7b7b75c664 100644 --- a/src/config/io.context.ts +++ b/src/config/io.context.ts @@ -1,10 +1,7 @@ import crypto from "node:crypto"; -import path from "node:path"; import { collectManifestModelIdNormalizationPolicies } from "@openclaw/model-catalog-core/provider-model-id-normalization"; import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope-config.js"; import { ensureOwnerDisplaySecret } from "../agents/owner-display.js"; -import { formatErrorMessage } from "../infra/errors.js"; -import { replaceFileAtomicSync } from "../infra/replace-file.js"; import { loadShellEnvFallback, resolveShellEnvFallbackTimeoutMs, @@ -12,10 +9,6 @@ import { shouldEnableShellEnvFallback, } from "../infra/shell-env.js"; import { createConfigValidationMetadataPluginIdScope } from "../plugins/gateway-startup-plugin-ids.js"; -import { - loadInstalledPluginIndexInstallRecordsSync, - writePersistedInstalledPluginIndexInstallRecordsSync, -} from "../plugins/installed-plugin-index-records.js"; import { resolvePluginMetadataSnapshot, type PluginMetadataSnapshot, @@ -32,18 +25,8 @@ import { resolveConfigPathForDeps, } from "./io.read-helpers.js"; import { autoOwnerDisplaySecretByPath } from "./io.state.js"; -import type { - ConfigIoFactoryOptions, - NormalizedConfigIoDeps, - ShippedPluginInstallConfigReadMigration, - ShippedPluginInstallConfigWriteMigration, -} from "./io.types.js"; +import type { ConfigIoFactoryOptions, NormalizedConfigIoDeps } from "./io.types.js"; import { materializeRuntimeConfig } from "./materialize.js"; -import { resolveStateDir } from "./paths.js"; -import { - extractShippedPluginInstallConfigRecords, - stripShippedPluginInstallConfigRecords, -} from "./plugin-install-config-migration.js"; import { applyConfigOverrides } from "./runtime-overrides.js"; import { resolveShellEnvExpectedKeys } from "./shell-env-expected-keys.js"; import type { ConfigFileSnapshot, OpenClawConfig } from "./types.js"; @@ -60,25 +43,11 @@ export type ConfigIoContext = { options: ConfigIoFactoryOptions; observeLoadConfigSnapshot: (snapshot: ConfigFileSnapshot) => ConfigFileSnapshot; finalizeLoadedRuntimeConfig: (config: OpenClawConfig) => OpenClawConfig; - migrateAndStripShippedPluginInstallConfigRecords: ( - configRaw: unknown, - options?: { persist?: boolean; rootConfigRaw?: unknown }, - ) => ShippedPluginInstallConfigReadMigration; - retainRuntimeOnlyShippedPluginInstallConfigRecords: ( - config: OpenClawConfig, - sourceRaw: unknown, - ) => OpenClawConfig; createValidationPluginMetadataSnapshotLoader: (params: { effectiveConfigRaw: unknown; env: NodeJS.ProcessEnv; }) => ValidationPluginMetadataSnapshotLoader; resolveRuntimePreflightSourceConfig: (candidate: OpenClawConfig) => OpenClawConfig; - ensureShippedPluginInstallConfigRecordsMigratedForWrite: ( - snapshot: ConfigFileSnapshot, - ) => ShippedPluginInstallConfigWriteMigration; - rollbackShippedPluginInstallConfigWriteMigration: ( - migration: ShippedPluginInstallConfigWriteMigration, - ) => boolean; resolveSuspiciousRecoveryBackupCandidate: (parsed: unknown) => OpenClawConfig | null; }; @@ -124,76 +93,6 @@ export function createConfigIoContext(options: ConfigIoFactoryOptions = {}): Con ); } - function replaceConfigFileSync(raw: string): void { - replaceFileAtomicSync({ - filePath: configPath, - content: raw, - dirMode: 0o700, - mode: 0o600, - tempPrefix: path.basename(configPath), - copyFallbackOnPermissionError: true, - fileSystem: deps.fs, - }); - } - - function migrateAndStripShippedPluginInstallConfigRecords( - configRaw: unknown, - migrationOptions: { persist?: boolean; rootConfigRaw?: unknown } = {}, - ): ShippedPluginInstallConfigReadMigration { - const installRecords = extractShippedPluginInstallConfigRecords(configRaw); - const stripped = stripShippedPluginInstallConfigRecords(configRaw); - if (Object.keys(installRecords).length === 0) { - return { config: stripped }; - } - if (migrationOptions.persist === false) { - return { config: configRaw, validationConfig: stripped }; - } - try { - const stateDir = resolveStateDir(deps.env, deps.homedir); - const existingRecords = loadInstalledPluginIndexInstallRecordsSync({ - env: deps.env, - stateDir, - }); - const nextRecords = { ...installRecords, ...existingRecords }; - if (Object.keys(installRecords).some((pluginId) => !(pluginId in existingRecords))) { - writePersistedInstalledPluginIndexInstallRecordsSync(nextRecords, { - config: coerceConfig(stripped), - env: deps.env, - stateDir, - }); - } - const rootConfigRaw = migrationOptions.rootConfigRaw; - if ( - rootConfigRaw !== undefined && - Object.keys(extractShippedPluginInstallConfigRecords(rootConfigRaw)).length > 0 - ) { - const persistedRootParsed = stripShippedPluginInstallConfigRecords(rootConfigRaw); - const persistedRootRaw = JSON.stringify(persistedRootParsed, null, 2) - .trimEnd() - .concat("\n"); - replaceConfigFileSync(persistedRootRaw); - return { config: stripped, persistedRootParsed, persistedRootRaw }; - } - } catch (error) { - deps.logger.warn( - `Config (${configPath}): could not migrate shipped plugins.installs records into the plugin index: ${formatErrorMessage(error)}`, - ); - return { config: configRaw }; - } - return { config: stripped }; - } - - function retainRuntimeOnlyShippedPluginInstallConfigRecords( - config: OpenClawConfig, - sourceRaw: unknown, - ): OpenClawConfig { - const installRecords = extractShippedPluginInstallConfigRecords(sourceRaw); - if (Object.keys(installRecords).length === 0) { - return config; - } - return { ...config, plugins: { ...config.plugins, installs: installRecords } }; - } - function createValidationPluginMetadataSnapshotLoader(params: { effectiveConfigRaw: unknown; env: NodeJS.ProcessEnv; @@ -204,10 +103,7 @@ export function createConfigIoContext(options: ConfigIoFactoryOptions = {}): Con if (snapshot) { return snapshot; } - const metadataConfig = retainRuntimeOnlyShippedPluginInstallConfigRecords( - config, - params.effectiveConfigRaw, - ); + const metadataConfig = config; const defaultAgentId = resolveDefaultAgentId(metadataConfig); snapshot = resolvePluginMetadataSnapshot({ config: metadataConfig, @@ -229,54 +125,7 @@ export function createConfigIoContext(options: ConfigIoFactoryOptions = {}): Con const env = { ...deps.env } as NodeJS.ProcessEnv; const resolvedIncludes = resolveConfigIncludesForRead(candidate, configPath, { ...deps, env }); const resolution = resolveConfigForRead(resolvedIncludes, env, deps.lowerPrecedenceEnv); - return coerceConfig( - migrateAndStripShippedPluginInstallConfigRecords(resolution.resolvedConfigRaw, { - persist: false, - rootConfigRaw: candidate, - }).config, - ); - } - - function ensureShippedPluginInstallConfigRecordsMigratedForWrite( - snapshot: ConfigFileSnapshot, - ): ShippedPluginInstallConfigWriteMigration { - const installRecords = { - ...extractShippedPluginInstallConfigRecords(snapshot.sourceConfig), - ...extractShippedPluginInstallConfigRecords(snapshot.parsed), - }; - if (Object.keys(installRecords).length === 0) { - return { migrated: false }; - } - const stateDir = resolveStateDir(deps.env, deps.homedir); - const existingRecords = loadInstalledPluginIndexInstallRecordsSync({ env: deps.env, stateDir }); - if (Object.keys(installRecords).every((pluginId) => pluginId in existingRecords)) { - return { migrated: false }; - } - try { - writePersistedInstalledPluginIndexInstallRecordsSync( - { ...installRecords, ...existingRecords }, - { - config: coerceConfig(stripShippedPluginInstallConfigRecords(snapshot.sourceConfig)), - env: deps.env, - stateDir, - }, - ); - return { migrated: true }; - } catch (error) { - throw new Error( - `Config write blocked: shipped plugins.installs records in ${configPath} could not be migrated into the plugin index. Fix state directory permissions or run openclaw plugins registry --refresh, then retry. ${formatErrorMessage(error)}`, - { cause: error }, - ); - } - } - - function rollbackShippedPluginInstallConfigWriteMigration( - migration: ShippedPluginInstallConfigWriteMigration, - ): boolean { - if (!migration.migrated) { - return false; - } - return false; + return coerceConfig(resolution.resolvedConfigRaw); } function resolveSuspiciousRecoveryBackupCandidate(parsed: unknown): OpenClawConfig | null { @@ -287,17 +136,12 @@ export function createConfigIoContext(options: ConfigIoFactoryOptions = {}): Con env: candidateEnv, }); const resolution = resolveConfigForRead(resolved, candidateEnv, deps.lowerPrecedenceEnv); - const migration = migrateAndStripShippedPluginInstallConfigRecords( - resolution.resolvedConfigRaw, - { persist: false, rootConfigRaw: parsed }, - ); - const effectiveConfigRaw = migration.config; - const validationConfigRaw = migration.validationConfig ?? effectiveConfigRaw; + const effectiveConfigRaw = resolution.resolvedConfigRaw; const pluginMetadata = createValidationPluginMetadataSnapshotLoader({ effectiveConfigRaw, env: candidateEnv, }); - const validated = validateConfigObjectWithPlugins(validationConfigRaw, { + const validated = validateConfigObjectWithPlugins(effectiveConfigRaw, { env: candidateEnv, pluginValidation: options.pluginValidation, loadPluginMetadataSnapshot: pluginMetadata.load, @@ -316,12 +160,8 @@ export function createConfigIoContext(options: ConfigIoFactoryOptions = {}): Con options, observeLoadConfigSnapshot, finalizeLoadedRuntimeConfig, - migrateAndStripShippedPluginInstallConfigRecords, - retainRuntimeOnlyShippedPluginInstallConfigRecords, createValidationPluginMetadataSnapshotLoader, resolveRuntimePreflightSourceConfig, - ensureShippedPluginInstallConfigRecordsMigratedForWrite, - rollbackShippedPluginInstallConfigWriteMigration, resolveSuspiciousRecoveryBackupCandidate, }; } @@ -331,15 +171,12 @@ export function resolveModelIdNormalizationPolicies(snapshot: PluginMetadataSnap } export function materializeConfigForLoad( - context: ConfigIoContext, + _context: ConfigIoContext, config: OpenClawConfig, - effectiveConfigRaw: unknown, + _effectiveConfigRaw: unknown, pluginMetadata: PluginMetadataSnapshot | undefined, ): OpenClawConfig { - return context.retainRuntimeOnlyShippedPluginInstallConfigRecords( - materializeRuntimeConfig(config, "load", { - manifestRegistry: pluginMetadata?.manifestRegistry, - }), - effectiveConfigRaw, - ); + return materializeRuntimeConfig(config, "load", { + manifestRegistry: pluginMetadata?.manifestRegistry, + }); } diff --git a/src/config/io.load.ts b/src/config/io.load.ts index 37eedd03bf21..f1811760c031 100644 --- a/src/config/io.load.ts +++ b/src/config/io.load.ts @@ -63,14 +63,10 @@ export function loadConfigFromContext( deps.env, deps.lowerPrecedenceEnv, ); - const migration = context.migrateAndStripShippedPluginInstallConfigRecords( - readResolution.resolvedConfigRaw, - { persist: false, rootConfigRaw: parsed }, - ); - const effectiveConfigRaw = migration.config; - const validationConfigRaw = migration.validationConfig ?? effectiveConfigRaw; - const snapshotRaw = migration.persistedRootRaw ?? raw; - const snapshotParsed = migration.persistedRootParsed ?? parsed; + const effectiveConfigRaw = readResolution.resolvedConfigRaw; + const validationConfigRaw = effectiveConfigRaw; + const snapshotRaw = raw; + const snapshotParsed = parsed; const hash = hashConfigRaw(snapshotRaw); for (const warning of readResolution.envWarnings) { deps.logger.warn( diff --git a/src/config/io.recovery.ts b/src/config/io.recovery.ts index eaa830fe91c3..4da88e6d29b4 100644 --- a/src/config/io.recovery.ts +++ b/src/config/io.recovery.ts @@ -5,7 +5,6 @@ import { resolveConfigForRead, resolveConfigIncludesForRead, } from "./io.read-helpers.js"; -import { stripShippedPluginInstallConfigRecords } from "./plugin-install-config-migration.js"; import type { ConfigFileSnapshot } from "./types.js"; import { validateConfigObjectWithPlugins } from "./validation.js"; @@ -94,10 +93,10 @@ export async function recoverConfigFromJsonRootSuffixWithContext( context.deps.env, context.deps.lowerPrecedenceEnv, ); - const validated = validateConfigObjectWithPlugins( - stripShippedPluginInstallConfigRecords(resolution.resolvedConfigRaw), - { env: context.deps.env, sourceRaw: suffixRecovery.parsed }, - ); + const validated = validateConfigObjectWithPlugins(resolution.resolvedConfigRaw, { + env: context.deps.env, + sourceRaw: suffixRecovery.parsed, + }); if (!validated.ok) { return false; } diff --git a/src/config/io.snapshot.ts b/src/config/io.snapshot.ts index dd8eb1630eb6..4b07de331549 100644 --- a/src/config/io.snapshot.ts +++ b/src/config/io.snapshot.ts @@ -29,7 +29,6 @@ import { warnIfConfigFromFuture } from "./io.warnings.js"; import { resolveManagedUnsetPathsForWrite } from "./io.write-prepare.js"; import { materializeRuntimeConfig } from "./materialize.js"; import { ConfigMutationConflictError } from "./mutation-conflict.js"; -import { stripShippedPluginInstallConfigRecords } from "./plugin-install-config-migration.js"; import type { ConfigFileSnapshot, LegacyConfigIssue, OpenClawConfig } from "./types.js"; import { validateConfigObjectWithPlugins } from "./validation.js"; @@ -151,19 +150,11 @@ export async function readConfigFileSnapshotInternal( path: warning.configPath, message: `Missing env var "${warning.varName}" - feature using this value will be unavailable`, })); - const migration = await deps.measure("config.snapshot.read.plugin-install-migration", () => - context.migrateAndStripShippedPluginInstallConfigRecords(readResolution.resolvedConfigRaw, { - persist: false, - rootConfigRaw: effectiveParsed, - }), - ); - const effectiveConfigRaw = migration.config; - const validationConfigRaw = migration.validationConfig ?? effectiveConfigRaw; - const snapshotRaw = migration.persistedRootRaw ?? raw; - const snapshotParsed = migration.persistedRootParsed ?? effectiveParsed; - const snapshotHash = migration.persistedRootRaw - ? hashConfigRaw(migration.persistedRootRaw) - : rawHash; + const effectiveConfigRaw = readResolution.resolvedConfigRaw; + const validationConfigRaw = effectiveConfigRaw; + const snapshotRaw = raw; + const snapshotParsed = effectiveParsed; + const snapshotHash = rawHash; fallbackSourceConfig = coerceConfig(effectiveConfigRaw); const pluginMetadata = context.createValidationPluginMetadataSnapshotLoader({ effectiveConfigRaw, @@ -255,12 +246,9 @@ export async function readConfigFileSnapshotInternal( } } const snapshotConfig = await deps.measure("config.snapshot.read.materialize", () => - context.retainRuntimeOnlyShippedPluginInstallConfigRecords( - materializeRuntimeConfig(validated.config, "snapshot", { - manifestRegistry: pluginMetadata.getSnapshot()?.manifestRegistry, - }), - effectiveConfigRaw, - ), + materializeRuntimeConfig(validated.config, "snapshot", { + manifestRegistry: pluginMetadata.getSnapshot()?.manifestRegistry, + }), ); return await deps.measure("config.snapshot.read.observe", () => finalizeReadConfigSnapshotInternalResult( @@ -422,7 +410,7 @@ export async function readSourceConfigBestEffortFromContext( return coerceConfig(parsed.parsed); } const resolution = resolveConfigForRead(resolved, deps.env, deps.lowerPrecedenceEnv); - return coerceConfig(stripShippedPluginInstallConfigRecords(resolution.resolvedConfigRaw)); + return coerceConfig(resolution.resolvedConfigRaw); } catch { return {}; } diff --git a/src/config/io.types.ts b/src/config/io.types.ts index 5017e77e5224..acd6d2ee0b02 100644 --- a/src/config/io.types.ts +++ b/src/config/io.types.ts @@ -143,12 +143,3 @@ export type BestEffortConfigSnapshot = { config: OpenClawConfig; sourceConfig: OpenClawConfig; }; - -export type ShippedPluginInstallConfigWriteMigration = { migrated: false } | { migrated: true }; - -export type ShippedPluginInstallConfigReadMigration = { - config: unknown; - validationConfig?: unknown; - persistedRootParsed?: unknown; - persistedRootRaw?: string; -}; diff --git a/src/config/io.write-config.test.ts b/src/config/io.write-config.test.ts index 5ca215d60911..79a7af002467 100644 --- a/src/config/io.write-config.test.ts +++ b/src/config/io.write-config.test.ts @@ -6,7 +6,6 @@ import chokidar from "chokidar"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { startGatewayConfigReloader } from "../gateway/config-reload.js"; import { executeSqliteQueryTakeFirstSync, getNodeSqliteKysely } from "../infra/kysely-sync.js"; -import { readPersistedInstalledPluginIndex } from "../plugins/installed-plugin-index-store.js"; import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; import { clearLoadPluginMetadataSnapshotMemo } from "../plugins/plugin-metadata-snapshot.js"; import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js"; @@ -170,23 +169,6 @@ describe("config io write", () => { return value as Record; }; - const requireArray = (value: unknown, label: string): unknown[] => { - if (!Array.isArray(value)) { - throw new Error(`expected ${label} to be an array`); - } - return value; - }; - - const expectInstallRecord = ( - record: unknown, - expected: { source: string; spec: string; installPath: string }, - ) => { - const actual = requireRecord(record, "plugin install record"); - expect(actual.source).toBe(expected.source); - expect(actual.spec).toBe(expected.spec); - expect(actual.installPath).toBe(expected.installPath); - }; - const expectConfigWriteRejected = async (promise: Promise) => { try { await promise; @@ -217,6 +199,19 @@ describe("config io write", () => { logger: silentLogger, }); + const writeGatewayPortAndReadConfig = async (home: string, configPath: string) => { + const io = createFastConfigIO(home); + + await io.writeConfigFile({ + gateway: { mode: "local", port: 18789 }, + }); + + return JSON.parse(await fs.readFile(configPath, "utf-8")) as { + $schema?: string; + gateway?: { mode?: string; port?: number }; + }; + }; + it("writes health state to SQLite through public config reads", async () => { await withSuiteHome(async (home) => { const configPath = path.join(home, ".openclaw", "openclaw.json"); @@ -277,338 +272,6 @@ describe("config io write", () => { }); }); - it("loads shipped plugin install config records without mutating config or plugin index", async () => { - await withSuiteHome(async (home) => { - const configPath = path.join(home, ".openclaw", "openclaw.json"); - const pluginDir = path.join(home, ".openclaw", "plugins", "demo"); - const manifestPath = path.join(pluginDir, "openclaw.plugin.json"); - const source = path.join(pluginDir, "index.ts"); - await fs.mkdir(pluginDir, { recursive: true }); - await fs.writeFile(source, "export function register() {}\n", "utf-8"); - await fs.writeFile( - manifestPath, - `${JSON.stringify({ id: "demo", configSchema: { type: "object" } }, null, 2)}\n`, - "utf-8", - ); - await fs.mkdir(path.dirname(configPath), { recursive: true }); - await fs.writeFile( - configPath, - `${JSON.stringify( - { - plugins: { - entries: { demo: { enabled: true } }, - installs: { - demo: { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }, - }, - }, - }, - null, - 2, - )}\n`, - "utf-8", - ); - mockLoadPluginManifestRegistry.mockReturnValue({ - diagnostics: [], - plugins: [ - { - id: "demo", - origin: "global", - channels: [], - providers: [], - cliBackends: [], - skills: [], - hooks: [], - rootDir: pluginDir, - source, - manifestPath, - configSchema: { - type: "object", - }, - }, - ], - } satisfies PluginManifestRegistry); - - const io = createFastConfigIO(home); - try { - const initialRaw = await fs.readFile(configPath, "utf-8"); - const cfg = io.loadConfig(); - - expectInstallRecord(cfg.plugins?.installs?.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - const snapshot = await io.readConfigFileSnapshot(); - expectInstallRecord(snapshot.sourceConfig.plugins?.installs?.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - expectInstallRecord(snapshot.runtimeConfig.plugins?.installs?.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - await expect( - readPersistedInstalledPluginIndex({ - stateDir: path.join(home, ".openclaw"), - }), - ).resolves.toBeNull(); - await expect(fs.readFile(configPath, "utf-8")).resolves.toBe(initialRaw); - } finally { - mockLoadPluginManifestRegistry.mockReturnValue({ - diagnostics: [], - plugins: [], - } satisfies PluginManifestRegistry); - } - }); - }); - - it("retains included shipped plugin install records in write snapshots", async () => { - await withSuiteHome(async (home) => { - const configDir = path.join(home, ".openclaw"); - const configPath = path.join(configDir, "openclaw.json"); - const pluginsPath = path.join(configDir, "plugins.json5"); - await fs.mkdir(configDir, { recursive: true }); - await fs.writeFile( - configPath, - `${JSON.stringify({ plugins: { $include: "./plugins.json5" } }, null, 2)}\n`, - "utf-8", - ); - await fs.writeFile( - pluginsPath, - `${JSON.stringify( - { - installs: { - demo: { - source: "npm", - spec: "demo@1.0.0", - installPath: "/tmp/demo", - }, - }, - }, - null, - 2, - )}\n`, - "utf-8", - ); - - const prepared = await createFastConfigIO(home).readConfigFileSnapshotForWrite(); - - expect(prepared.snapshot.valid).toBe(true); - expect(prepared.snapshot.parsed).toEqual({ - plugins: { $include: "./plugins.json5" }, - }); - expectInstallRecord(prepared.snapshot.sourceConfig.plugins?.installs?.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: "/tmp/demo", - }); - }); - }); - - it("migrates shipped plugin install config records into the plugin index during explicit writes", async () => { - await withSuiteHome(async (home) => { - const configPath = path.join(home, ".openclaw", "openclaw.json"); - const pluginDir = path.join(home, ".openclaw", "plugins", "demo"); - const manifestPath = path.join(pluginDir, "openclaw.plugin.json"); - const source = path.join(pluginDir, "index.ts"); - await fs.mkdir(pluginDir, { recursive: true }); - await fs.writeFile(source, "export function register() {}\n", "utf-8"); - await fs.writeFile( - manifestPath, - `${JSON.stringify({ id: "demo", configSchema: { type: "object" } }, null, 2)}\n`, - "utf-8", - ); - await fs.mkdir(path.dirname(configPath), { recursive: true }); - await fs.writeFile( - configPath, - `${JSON.stringify( - { - plugins: { - entries: { demo: { enabled: true } }, - installs: { - demo: { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }, - }, - }, - }, - null, - 2, - )}\n`, - "utf-8", - ); - mockLoadPluginManifestRegistry.mockReturnValue({ - diagnostics: [], - plugins: [ - { - id: "demo", - origin: "global", - channels: [], - providers: [], - cliBackends: [], - skills: [], - hooks: [], - rootDir: pluginDir, - source, - manifestPath, - configSchema: { - type: "object", - }, - }, - ], - } satisfies PluginManifestRegistry); - - const io = createFastConfigIO(home); - try { - await io.writeConfigFile({ - plugins: { - entries: { demo: { enabled: true } }, - }, - }); - - const index = requireRecord( - await readPersistedInstalledPluginIndex({ - stateDir: path.join(home, ".openclaw"), - }), - "persisted plugin index", - ); - expectInstallRecord(requireRecord(index.installRecords, "install records").demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - const plugins = requireArray(index.plugins, "plugin index plugins"); - expect(plugins).toHaveLength(1); - const indexedPlugin = requireRecord(plugins[0], "indexed plugin"); - expect(indexedPlugin.pluginId).toBe("demo"); - expect(indexedPlugin.installRecordHash).toMatch(/^[a-f0-9]{64}$/u); - const persistedConfig = JSON.parse(await fs.readFile(configPath, "utf-8")) as { - plugins?: { installs?: unknown }; - }; - expect(persistedConfig.plugins?.installs).toBeUndefined(); - } finally { - mockLoadPluginManifestRegistry.mockReturnValue({ - diagnostics: [], - plugins: [], - } satisfies PluginManifestRegistry); - } - }); - }); - - it("migrates shipped plugin install config records during explicit writes even when the manifest is missing", async () => { - await withSuiteHome(async (home) => { - const configPath = path.join(home, ".openclaw", "openclaw.json"); - const pluginDir = path.join(home, ".openclaw", "plugins", "missing"); - await fs.mkdir(path.dirname(configPath), { recursive: true }); - await fs.writeFile( - configPath, - `${JSON.stringify( - { - plugins: { - entries: { missing: { enabled: true } }, - installs: { - missing: { - source: "npm", - spec: "missing-plugin@1.0.0", - installPath: pluginDir, - }, - }, - }, - }, - null, - 2, - )}\n`, - "utf-8", - ); - - const io = createFastConfigIO(home); - await io.writeConfigFile({ - plugins: { - entries: { missing: { enabled: true } }, - }, - }); - - const index = requireRecord( - await readPersistedInstalledPluginIndex({ - stateDir: path.join(home, ".openclaw"), - }), - "persisted plugin index", - ); - expectInstallRecord(requireRecord(index.installRecords, "install records").missing, { - source: "npm", - spec: "missing-plugin@1.0.0", - installPath: pluginDir, - }); - expect(index.plugins).toEqual([]); - const persistedConfig = JSON.parse(await fs.readFile(configPath, "utf-8")) as { - plugins?: { installs?: unknown }; - }; - expect(persistedConfig.plugins?.installs).toBeUndefined(); - }); - }); - - it("keeps shipped plugin install config records when index migration fails", async () => { - mockLoadPluginManifestRegistry.mockReturnValue({ - diagnostics: [], - plugins: [], - } satisfies PluginManifestRegistry); - await withSuiteHome(async (home) => { - const configPath = path.join(home, ".openclaw", "openclaw.json"); - const unwritableStatePath = path.join(home, ".openclaw"); - const pluginDir = path.join(unwritableStatePath, "plugins", "demo"); - const original = { - plugins: { - entries: { demo: { enabled: true } }, - installs: { - demo: { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }, - }, - }, - }; - await fs.mkdir(path.dirname(configPath), { recursive: true }); - await fs.writeFile(configPath, `${JSON.stringify(original, null, 2)}\n`, "utf-8"); - const warn = vi.fn(); - const io = createConfigIO({ - env: { OPENCLAW_TEST_FAST: "1" } as NodeJS.ProcessEnv, - homedir: () => home, - logger: { warn, error: vi.fn() }, - }); - await fs.writeFile(path.join(unwritableStatePath, "state"), "not a directory", "utf-8"); - - const loadedConfig = io.loadConfig(); - expectInstallRecord(loadedConfig.plugins?.installs?.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - expect(warn.mock.calls).toContainEqual([ - "Config warnings:\n- plugins.entries.demo: plugin not found: demo (stale config entry ignored; remove it from plugins config)", - ]); - - await expect(io.writeConfigFile({ gateway: { mode: "local" } })).rejects.toThrow( - "Config write blocked: shipped plugins.installs records", - ); - - const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as typeof original; - expectInstallRecord(persisted.plugins.installs.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - }); - }); - it("dedupes validation warnings across writes and reloads until config becomes clean", async () => { await withSuiteHome(async (home) => { const warn = vi.fn(); @@ -649,83 +312,6 @@ describe("config io write", () => { }); }); - it("keeps shipped plugin install index migration when config write fails", async () => { - await withSuiteHome(async (home) => { - const configPath = path.join(home, ".openclaw", "openclaw.json"); - const pluginDir = path.join(home, ".openclaw", "plugins", "demo"); - const original = { - plugins: { - entries: { demo: { enabled: true } }, - installs: { - demo: { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }, - }, - }, - }; - await fs.mkdir(path.dirname(configPath), { recursive: true }); - await fs.writeFile(configPath, `${JSON.stringify(original, null, 2)}\n`, "utf-8"); - mockMaintainConfigBackups.mockRejectedValueOnce(new Error("backup failed")); - - const io = createFastConfigIO(home); - await expect(io.writeConfigFile({ gateway: { mode: "local" } })).rejects.toThrow( - "backup failed", - ); - - const persistedConfig = JSON.parse(await fs.readFile(configPath, "utf-8")) as typeof original; - expectInstallRecord(persistedConfig.plugins.installs.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - const persistedIndex = await readPersistedInstalledPluginIndex({ - stateDir: path.join(home, ".openclaw"), - }); - expectInstallRecord(persistedIndex?.installRecords.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - }); - }); - - const writeGatewayPortAndReadConfig = async (home: string, configPath: string) => { - const io = createFastConfigIO(home); - - await io.writeConfigFile({ - gateway: { mode: "local", port: 18789 }, - }); - - return JSON.parse(await fs.readFile(configPath, "utf-8")) as { - $schema?: string; - gateway?: { mode?: string; port?: number }; - }; - }; - - it.runIf(process.platform !== "win32")( - "tightens world-writable state dir when writing the default config", - async () => { - await withSuiteHome(async (home) => { - const stateDir = path.join(home, ".openclaw"); - await fs.mkdir(stateDir, { recursive: true, mode: 0o777 }); - await fs.chmod(stateDir, 0o777); - - const io = createConfigIO({ - env: {} as NodeJS.ProcessEnv, - homedir: () => home, - logger: silentLogger, - }); - - await io.writeConfigFile({ gateway: { mode: "local" } }); - - const stat = await fs.stat(stateDir); - expect(stat.mode & 0o777).toBe(0o700); - }); - }, - ); - it("keeps writes inside an OPENCLAW_STATE_DIR override even when the real home config exists", async () => { await withSuiteHome(async (home) => { const liveConfigPath = path.join(home, ".openclaw", "openclaw.json"); @@ -3046,59 +2632,6 @@ describe("config io write", () => { }); }); - it("keeps plugin install index migration when runtime refresh fails", async () => { - await withSuiteHome(async (home) => { - const stateDir = path.join(home, ".openclaw"); - const configPath = path.join(stateDir, "openclaw.json"); - const pluginDir = path.join(stateDir, "plugins", "demo"); - await fs.mkdir(path.dirname(configPath), { recursive: true }); - const initialConfig = { - plugins: { - entries: { demo: { enabled: true } }, - installs: { - demo: { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }, - }, - }, - } satisfies OpenClawConfig; - const initialRaw = `${JSON.stringify(initialConfig, null, 2)}\n`; - await fs.writeFile(configPath, initialRaw, "utf-8"); - - try { - await withEnvAsync( - { - OPENCLAW_CONFIG_PATH: configPath, - OPENCLAW_STATE_DIR: stateDir, - }, - async () => { - setRuntimeConfigSnapshotRefreshHandler({ - refresh: () => { - throw new Error("synthetic refresh failure"); - }, - }); - - await expect( - writeConfigFile({ plugins: { entries: { demo: { enabled: true } } } }), - ).rejects.toThrow(/runtime snapshot refresh failed: synthetic refresh failure/); - - await expect(fs.readFile(configPath, "utf-8")).resolves.toBe(initialRaw); - const persistedIndex = await readPersistedInstalledPluginIndex({ stateDir }); - expectInstallRecord(persistedIndex?.installRecords.demo, { - source: "npm", - spec: "demo@1.0.0", - installPath: pluginDir, - }); - }, - ); - } finally { - setRuntimeConfigSnapshotRefreshHandler(null); - } - }); - }); - it("blocks runtime preflight failures before committing root writes", async () => { await withSuiteHome(async (home) => { const configPath = path.join(home, ".openclaw", "openclaw.json"); diff --git a/src/config/io.write.ts b/src/config/io.write.ts index 718a493e7607..7e29745b3f36 100644 --- a/src/config/io.write.ts +++ b/src/config/io.write.ts @@ -363,8 +363,6 @@ export async function writeConfigFileFromContext( const sourceConfigForPreflight = context.resolveRuntimePreflightSourceConfig(stampedOutputConfig); await preCommitRuntimePreflight(sourceConfigForPreflight); - const pluginMigration = context.ensureShippedPluginInstallConfigRecordsMigratedForWrite(snapshot); - let configCommitted = false; try { const result = await replaceFileAtomic({ filePath: configPath, @@ -395,20 +393,16 @@ export async function writeConfigFileFromContext( }); }, }); - configCommitted = true; try { options.assertConfigPathForWrite?.(); } catch (error) { try { - const rolledBack = await rollbackConfigFileWriteIfUnchanged({ + await rollbackConfigFileWriteIfUnchanged({ configPath, previousSnapshot: snapshot, committedHash: nextHash, fsModule: deps.fs, }); - if (rolledBack) { - context.rollbackShippedPluginInstallConfigWriteMigration(pluginMigration); - } } catch (rollbackError) { throw new ConfigRuntimeRefreshError( `${formatErrorMessage(error)} Rollback failed: ${formatErrorMessage(rollbackError)}`, @@ -483,7 +477,6 @@ export async function writeConfigFileFromContext( snapshot: priorSnapshotAuditRecord, expectedSnapshot: writtenSnapshotAuditRecord, }); - context.rollbackShippedPluginInstallConfigWriteMigration(pluginMigration); if (previousWarningFingerprint === undefined) { loggedConfigWarningFingerprints.delete(configPath); } else { @@ -496,9 +489,6 @@ export async function writeConfigFileFromContext( }, }; } catch (error) { - if (!configCommitted) { - context.rollbackShippedPluginInstallConfigWriteMigration(pluginMigration); - } await appendWriteAudit("failed", error); throw error; } diff --git a/src/config/plugin-auto-enable.shared.ts b/src/config/plugin-auto-enable.shared.ts index 06239bae117d..25a91700d137 100644 --- a/src/config/plugin-auto-enable.shared.ts +++ b/src/config/plugin-auto-enable.shared.ts @@ -467,9 +467,8 @@ function hasXaiSetupAutoEnableRelevantConfig(cfg: OpenClawConfig): boolean { } const pluginConfig = cfg.plugins?.entries?.xai?.config; return ( - (isRecord(pluginConfig) && - (isRecord(pluginConfig.xSearch) || isRecord(pluginConfig.codeExecution))) || - (isRecord(cfg.tools?.web) && isRecord((cfg.tools.web as Record).x_search)) + isRecord(pluginConfig) && + (isRecord(pluginConfig.xSearch) || isRecord(pluginConfig.codeExecution)) ); } diff --git a/src/config/schema.help.agents.ts b/src/config/schema.help.agents.ts index 9d70f078940b..55691cef546e 100644 --- a/src/config/schema.help.agents.ts +++ b/src/config/schema.help.agents.ts @@ -37,7 +37,7 @@ export const AGENT_FIELD_HELP: Record = { "plugins.entries.*.hooks": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", "plugins.entries.*.hooks.allowPromptInjection": - "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build`.", "plugins.entries.*.hooks.allowConversationAccess": "Controls whether this plugin may read raw conversation content from typed hooks such as `before_agent_run`, `before_model_resolve`, `before_agent_reply`, `llm_input`, `llm_output`, `before_agent_finalize`, and `agent_end`. Non-bundled plugins must opt in explicitly.", "plugins.entries.*.hooks.timeoutMs": diff --git a/src/config/schema.help.quality.test.ts b/src/config/schema.help.quality.test.ts index af5895352e11..067b2359ff04 100644 --- a/src/config/schema.help.quality.test.ts +++ b/src/config/schema.help.quality.test.ts @@ -340,8 +340,6 @@ describe("config help copy quality", () => { 'FIELD_HELP["plugins.entries.*.hooks.allowPromptInjection"] test invariant', ); expect(pluginPromptPolicy.includes("before_prompt_build")).toBe(true); - expect(pluginPromptPolicy.includes("before_agent_start")).toBe(true); - expect(pluginPromptPolicy.includes("modelOverride")).toBe(true); const pluginConversationPolicy = expectDefined( FIELD_HELP["plugins.entries.*.hooks.allowConversationAccess"], diff --git a/src/config/schema.help.runtime.ts b/src/config/schema.help.runtime.ts index 8fd881324637..a9d62fc6cda7 100644 --- a/src/config/schema.help.runtime.ts +++ b/src/config/schema.help.runtime.ts @@ -572,8 +572,6 @@ export const RUNTIME_FIELD_HELP: Record = { "Approximate city sent to native Codex web search.", "tools.web.search.openaiCodex.userLocation.timezone": "Approximate timezone sent to native Codex web search.", - "tools.web.search.brave.mode": - 'Brave Search mode: "web" (URL results) or "llm-context" (pre-extracted page content for LLM grounding).', "tools.web.fetch.enabled": "Enable the web_fetch tool (lightweight HTTP fetch).", "tools.web.fetch.maxChars": "Max characters returned by web_fetch (truncated).", "tools.web.fetch.maxCharsCap": diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts index d5a2b1a24365..dfef694d1a80 100644 --- a/src/config/schema.labels.ts +++ b/src/config/schema.labels.ts @@ -352,7 +352,6 @@ export const FIELD_LABELS: Record = { "tools.web.search.openaiCodex.userLocation.region": "Codex User Region", "tools.web.search.openaiCodex.userLocation.city": "Codex User City", "tools.web.search.openaiCodex.userLocation.timezone": "Codex User Timezone", - "tools.web.search.brave.mode": "Brave Search Mode", "tools.web.fetch.enabled": "Enable Web Fetch Tool", "tools.web.fetch.maxChars": "Web Fetch Max Chars", "tools.web.fetch.maxCharsCap": "Web Fetch Hard Max Chars", diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts index 82e96407e4ef..2800ec9bfbaf 100644 --- a/src/config/schema.test.ts +++ b/src/config/schema.test.ts @@ -599,8 +599,8 @@ describe("config schema", () => { } }); - it("accepts web fetch readability and firecrawl config in the runtime zod schema", () => { - const parsed = ToolsSchema.parse({ + it("rejects removed Firecrawl config from the core web fetch schema", () => { + const result = ToolsSchema.safeParse({ web: { fetch: { readability: true, @@ -616,15 +616,7 @@ describe("config schema", () => { }, }); - expect(parsed?.web?.fetch?.readability).toBe(true); - expect(parsed?.web?.fetch?.firecrawl).toEqual({ - enabled: true, - apiKey: "firecrawl-test-key", - baseUrl: "https://api.firecrawl.dev", - onlyMainContent: true, - maxAgeMs: 60_000, - timeoutSeconds: 15, - }); + expect(result.success).toBe(false); }); it("keeps top-level subagent tools schema limited to tool policy", () => { @@ -1009,29 +1001,6 @@ describe("config schema", () => { } }); - it("rejects unknown keys inside web fetch firecrawl config", () => { - const result = ToolsSchema.safeParse({ - web: { - fetch: { - firecrawl: { - enabled: true, - nope: true, - }, - }, - }, - }); - - expect(result.success).toBe(false); - if (!result.success) { - const firecrawlIssue = result.error.issues.find( - (issue) => JSON.stringify(issue.path) === JSON.stringify(["web", "fetch", "firecrawl"]), - ); - expect(firecrawlIssue?.path).toEqual(["web", "fetch", "firecrawl"]); - const firecrawlKeys = (firecrawlIssue as { keys?: unknown } | undefined)?.keys; - expect(firecrawlKeys).toEqual(["nope"]); - } - }); - it("looks up a config schema path with immediate child summaries", () => { const lookup = lookupConfigSchema(baseSchema, "gateway.auth"); expect(lookup?.path).toBe("gateway.auth"); diff --git a/src/config/sessions.cache.test.ts b/src/config/sessions.cache.test.ts index d58f2791062e..4d600b8f76ec 100644 --- a/src/config/sessions.cache.test.ts +++ b/src/config/sessions.cache.test.ts @@ -18,7 +18,7 @@ import { loadSessionStore, saveSessionStore, updateSessionStore, - updateSessionStoreEntry, + patchSessionEntryWithKey, } from "./sessions/store.js"; import type { SessionEntry } from "./sessions/types.js"; import type { SessionSkillPromptRef } from "./sessions/types.js"; @@ -477,7 +477,7 @@ describe("Session Store Cache", () => { it("can publish writer-owned entry patches directly into the object cache", async () => { await saveSessionStore(storePath, createSingleSessionStore()); - const persisted = await updateSessionStoreEntry({ + const persisted = await patchSessionEntryWithKey({ storePath, sessionKey: "session:1", takeCacheOwnership: true, @@ -488,7 +488,7 @@ describe("Session Store Cache", () => { }); const cached = loadSessionStore(storePath, { clone: false }); - expect(cached["session:1"]).toBe(persisted); + expect(cached["session:1"]).toBe(persisted?.entry); expect( expectDefined(cached["session:1"], 'cached["session:1"] test invariant').displayName, ).toBe("Entry writer owned"); @@ -502,7 +502,7 @@ describe("Session Store Cache", () => { const before = loadSessionStore(storePath, { clone: false }); const untouched = before["session:2"]; - const persisted = await updateSessionStoreEntry({ + const persisted = await patchSessionEntryWithKey({ storePath, sessionKey: "session:1", update: async () => ({ @@ -513,8 +513,8 @@ describe("Session Store Cache", () => { const cached = loadSessionStore(storePath, { clone: false }); expect(cached["session:2"]).toBe(untouched); - expect(cached["session:1"]).not.toBe(persisted); - persisted!.displayName = "Mutated returned entry"; + expect(cached["session:1"]).not.toBe(persisted?.entry); + persisted!.entry.displayName = "Mutated returned entry"; expect( expectDefined(cached["session:1"], 'cached["session:1"] test invariant').displayName, ).toBe("Entry writer owned by default"); @@ -529,7 +529,7 @@ describe("Session Store Cache", () => { const untouched = before["session:2"]; const deliveryContext = { channel: "telegram", to: "chat-1" }; - await updateSessionStoreEntry({ + await patchSessionEntryWithKey({ storePath, sessionKey: "session:1", update: async () => ({ deliveryContext }), @@ -564,7 +564,7 @@ describe("Session Store Cache", () => { force: true, }); - await updateSessionStoreEntry({ + await patchSessionEntryWithKey({ storePath, sessionKey: "session:1", update: async () => ({ displayName: "After" }), @@ -588,7 +588,7 @@ describe("Session Store Cache", () => { "session:2": createSessionEntry({ sessionId: "id-2", displayName: "Untouched" }), }); - await updateSessionStoreEntry({ + await patchSessionEntryWithKey({ storePath, sessionKey: "session:1", update: async () => ({ diff --git a/src/config/sessions.test.ts b/src/config/sessions.test.ts index c61f52925b76..6da693862905 100644 --- a/src/config/sessions.test.ts +++ b/src/config/sessions.test.ts @@ -4,7 +4,6 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import { createDeferred } from "../test-utils/deferred.js"; import { withEnv } from "../test-utils/env.js"; import { buildGroupDisplayName, @@ -16,7 +15,6 @@ import { resolveSessionTranscriptPath, resolveSessionTranscriptsDir, updateSessionStore, - updateSessionStoreEntry, } from "./sessions.js"; describe("sessions", () => { @@ -97,16 +95,6 @@ describe("sessions", () => { return path.join(canonicalParent, path.basename(filePath)); } - async function expectPathMissing(targetPath: string): Promise { - let error: { code?: unknown } | undefined; - try { - await fs.stat(targetPath); - } catch (err) { - error = err as { code?: unknown }; - } - expect(error?.code).toBe("ENOENT"); - } - const deriveSessionKeyCases = [ { name: "returns normalized per-sender key", @@ -218,94 +206,6 @@ describe("sessions", () => { }); } - it("updateSessionStoreEntry preserves existing fields when patching", async () => { - const sessionKey = "agent:main:main"; - const { storePath } = await createSessionStoreFixture({ - prefix: "updateSessionStoreEntry", - entries: { - [sessionKey]: { - sessionId: "sess-1", - updatedAt: 100, - reasoningLevel: "on", - }, - }, - }); - - await updateSessionStoreEntry({ - storePath, - sessionKey, - update: async () => ({ updatedAt: 200 }), - }); - - const store = loadSessionStore(storePath); - expect(store[sessionKey]?.updatedAt).toBeGreaterThanOrEqual(200); - expect(store[sessionKey]?.reasoningLevel).toBe("on"); - }); - - it("updateSessionStoreEntry returns null when session key does not exist", async () => { - const { storePath } = await createSessionStoreFixture({ - prefix: "updateSessionStoreEntry-missing", - entries: {}, - }); - const update = async () => ({ thinkingLevel: "high" as const }); - const result = await updateSessionStoreEntry({ - storePath, - sessionKey: "agent:main:missing", - update, - }); - expect(result).toBeNull(); - }); - - it("updateSessionStoreEntry keeps existing entry when patch callback returns null", async () => { - const sessionKey = "agent:main:main"; - const { storePath } = await createSessionStoreFixture({ - prefix: "updateSessionStoreEntry-noop", - entries: { - [sessionKey]: { - sessionId: "sess-1", - updatedAt: 123, - thinkingLevel: "low", - }, - }, - }); - - const result = await updateSessionStoreEntry({ - storePath, - sessionKey, - update: async () => null, - }); - expect(result?.sessionId).toBe("sess-1"); - expect(result?.thinkingLevel).toBe("low"); - - const store = loadSessionStore(storePath); - expect(store[sessionKey]?.thinkingLevel).toBe("low"); - }); - - it("updateSessionStoreEntry persists callback mutations returned as patches", async () => { - const sessionKey = "agent:main:main"; - const { storePath } = await createSessionStoreFixture({ - prefix: "updateSessionStoreEntry-mutated-patch", - entries: { - [sessionKey]: { - sessionId: "sess-1", - updatedAt: 123, - displayName: "before", - }, - }, - }); - - await updateSessionStoreEntry({ - storePath, - sessionKey, - update: async (entry) => { - entry.displayName = "after"; - return { displayName: entry.displayName }; - }, - }); - - expect(loadSessionStore(storePath)[sessionKey]?.displayName).toBe("after"); - }); - it("updateSessionStore preserves concurrent additions", async () => { const dir = await createCaseDir("updateSessionStore"); const storePath = path.join(dir, "sessions.json"); @@ -557,121 +457,6 @@ describe("sessions", () => { ); }); - it("updateSessionStoreEntry merges concurrent patches", async () => { - const mainSessionKey = "agent:main:main"; - const { storePath } = await createSessionStoreFixture({ - prefix: "updateSessionStoreEntry", - entries: { - [mainSessionKey]: { - sessionId: "sess-1", - updatedAt: 123, - thinkingLevel: "low", - }, - }, - }); - - const firstStarted = createDeferred(); - const releaseFirst = createDeferred(); - - const p1 = updateSessionStoreEntry({ - storePath, - sessionKey: mainSessionKey, - update: async () => { - firstStarted.resolve(); - await releaseFirst.promise; - return { modelOverride: "anthropic/claude-opus-4-6" }; - }, - }); - const p2 = updateSessionStoreEntry({ - storePath, - sessionKey: mainSessionKey, - update: async () => { - await firstStarted.promise; - return { thinkingLevel: "high" }; - }, - }); - - await firstStarted.promise; - releaseFirst.resolve(); - await Promise.all([p1, p2]); - - const store = loadSessionStore(storePath); - expect(store[mainSessionKey]?.modelOverride).toBe("anthropic/claude-opus-4-6"); - expect(store[mainSessionKey]?.thinkingLevel).toBe("high"); - await expectPathMissing(`${storePath}.lock`); - }); - - it("updateSessionStoreEntry re-reads disk inside the writer slot instead of using stale cache", async () => { - const mainSessionKey = "agent:main:main"; - const { storePath } = await createSessionStoreFixture({ - prefix: "updateSessionStoreEntry-cache-bypass", - entries: { - [mainSessionKey]: { - sessionId: "sess-1", - updatedAt: 123, - thinkingLevel: "low", - }, - }, - }); - - // Prime the in-process cache with the original entry. - expect(loadSessionStore(storePath)[mainSessionKey]?.thinkingLevel).toBe("low"); - const originalStat = await fs.stat(storePath); - - // Simulate an external writer that updates the store but preserves mtime. - const externalStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record< - string, - Record - >; - externalStore[mainSessionKey] = { - ...externalStore[mainSessionKey], - providerOverride: "anthropic", - updatedAt: 124, - }; - await fs.writeFile(storePath, JSON.stringify(externalStore), "utf-8"); - await fs.utimes(storePath, originalStat.atime, originalStat.mtime); - - await updateSessionStoreEntry({ - storePath, - sessionKey: mainSessionKey, - update: async () => ({ thinkingLevel: "high" }), - }); - - const store = loadSessionStore(storePath); - expect(store[mainSessionKey]?.providerOverride).toBe("anthropic"); - expect(store[mainSessionKey]?.thinkingLevel).toBe("high"); - }); - - it("updateSessionStoreEntry can skip maintenance for existing-entry metadata writes", async () => { - const mainSessionKey = "agent:main:main"; - const staleSessionKey = "agent:main:stale"; - const { storePath } = await createSessionStoreFixture({ - prefix: "updateSessionStoreEntry-skip-maintenance", - entries: { - [mainSessionKey]: { - sessionId: "sess-1", - updatedAt: Date.now(), - thinkingLevel: "low", - }, - [staleSessionKey]: { - sessionId: "sess-stale", - updatedAt: 1, - }, - }, - }); - - await updateSessionStoreEntry({ - storePath, - sessionKey: mainSessionKey, - skipMaintenance: true, - update: async () => ({ thinkingLevel: "high" }), - }); - - const store = loadSessionStore(storePath); - expect(store[mainSessionKey]?.thinkingLevel).toBe("high"); - expect(store[staleSessionKey]?.sessionId).toBe("sess-stale"); - }); - it("updateSessionStore uses the writer-owned mutable cache without disk read", async () => { const mainSessionKey = "agent:main:main"; const { storePath } = await createSessionStoreFixture({ diff --git a/src/config/sessions/sessions.test.ts b/src/config/sessions/sessions.test.ts index 7d2dd7bcdc6b..6547d58059f5 100644 --- a/src/config/sessions/sessions.test.ts +++ b/src/config/sessions/sessions.test.ts @@ -27,7 +27,7 @@ import { loadSessionStore, saveSessionStore, updateSessionStore, - updateSessionStoreEntry, + patchSessionEntryWithKey, } from "./store.js"; import { useTempSessionsFixture } from "./test-helpers.js"; import { mergeSessionEntry, type SessionEntry } from "./types.js"; @@ -833,7 +833,7 @@ describe("session store writer queue", () => { ); writeSpy.mockClear(); - await updateSessionStoreEntry({ + await patchSessionEntryWithKey({ storePath, sessionKey: key, update: async (entry) => ({ displayName: entry.displayName, updatedAt: entry.updatedAt }), diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index 016a207b91d8..745530c7b5c0 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -1285,42 +1285,6 @@ async function persistResolvedSessionEntry(params: { return entryUnchanged || params.returnDetached ? cloneSessionEntry(next) : next; } -export async function updateSessionStoreEntry(params: { - storePath: string; - sessionKey: string; - update: ( - entry: SessionEntry, - ) => Promise | null> | Partial | null; - skipMaintenance?: boolean; - takeCacheOwnership?: boolean; - requireWriteSuccess?: boolean; -}): Promise { - const { storePath, sessionKey, update } = params; - return await runExclusiveSessionStoreWrite(storePath, async () => { - const store = loadMutableSessionStoreForWriter(storePath); - const resolved = resolveSessionStoreEntry({ store, sessionKey }); - const existing = resolved.existing; - if (!existing) { - return null; - } - const patch = await update(cloneSessionEntry(existing)); - if (!patch) { - return existing; - } - const next = mergeSessionEntry(existing, patch); - return await persistResolvedSessionEntry({ - storePath, - store, - resolved, - next, - skipMaintenance: params.skipMaintenance, - takeCacheOwnership: params.takeCacheOwnership ?? true, - requireWriteSuccess: params.requireWriteSuccess, - returnDetached: params.takeCacheOwnership !== true, - }); - }); -} - type SessionEntryPatchParams = SessionEntryWorkflowOptions & { sessionKey: string; fallbackEntry?: SessionEntry; diff --git a/src/config/types.plugins.ts b/src/config/types.plugins.ts index 0c9bf5f8bf22..2d8146d5013c 100644 --- a/src/config/types.plugins.ts +++ b/src/config/types.plugins.ts @@ -2,7 +2,7 @@ export type PluginEntryConfig = { enabled?: boolean; hooks?: { - /** Controls prompt mutation via before_prompt_build and prompt fields from legacy before_agent_start. */ + /** Controls prompt mutation via before_prompt_build. */ allowPromptInjection?: boolean; /** * Controls access to raw conversation content from conversation hooks including diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 8b1b8dcdb67d..edd58b04ec78 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -570,8 +570,6 @@ export type ToolsConfig = { enabled?: boolean; /** Search provider id. */ provider?: string; - /** Shared API key slot used by providers that do not need nested config. */ - apiKey?: SecretInput; /** Default search results count (1-10). */ maxResults?: number; /** Timeout in seconds for search requests. */ @@ -596,21 +594,6 @@ export type ToolsConfig = { timezone?: string; }; }; - } & Record; - /** X (formerly Twitter) search tool configuration using xAI Grok. */ - x_search?: { - /** Enable X search tool (default: true when xAI auth is available via plugin config or XAI_API_KEY). */ - enabled?: boolean; - /** Model id to use for X search. */ - model?: string; - /** Keep inline citations in the xAI response payload when available. */ - inlineCitations?: boolean; - /** Optional max search/tool turns for xAI to use internally. */ - maxTurns?: number; - /** Timeout in seconds for X search requests. */ - timeoutSeconds?: number; - /** Cache TTL in minutes for X search results. */ - cacheTtlMinutes?: number; }; fetch?: { /** Enable web fetch tool (default: true). */ diff --git a/src/config/web-search-codex-config.test.ts b/src/config/web-search-codex-config.test.ts index b802e3439101..5a6e8412f442 100644 --- a/src/config/web-search-codex-config.test.ts +++ b/src/config/web-search-codex-config.test.ts @@ -49,7 +49,8 @@ describe("web search Codex native config validation", () => { expect(result.ok).toBe(true); if (result.ok) { - expect(result.config.tools?.web?.search?.customSearch).toEqual({ + const search = result.config.tools?.web?.search as Record | undefined; + expect(search?.customSearch).toEqual({ endpoint: "https://search.example.test", mode: "strict", }); diff --git a/src/config/zod-schema.agent-defaults.test.ts b/src/config/zod-schema.agent-defaults.test.ts index 49dcad445147..27d32597475d 100644 --- a/src/config/zod-schema.agent-defaults.test.ts +++ b/src/config/zod-schema.agent-defaults.test.ts @@ -320,15 +320,9 @@ describe("agent defaults schema", () => { it("rejects legacy whole-agent runtime pins outside doctor migration", () => { expect(AgentDefaultsSchema.safeParse({ agentRuntime: { id: "codex" } }).success).toBe(false); - expect(AgentDefaultsSchema.safeParse({ embeddedHarness: { runtime: "codex" } }).success).toBe( - false, - ); expect( AgentEntrySchema.safeParse({ id: "legacy", agentRuntime: { id: "codex" } }).success, ).toBe(false); - expect( - AgentEntrySchema.safeParse({ id: "legacy", embeddedHarness: { runtime: "codex" } }).success, - ).toBe(false); }); it("accepts embeddedAgent project settings policy", () => { diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 0123f1f2dc55..da5ae4059f46 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -369,7 +369,6 @@ const ToolsWebSearchSchema = z maxResults: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), cacheTtlMinutes: z.number().nonnegative().optional(), - apiKey: SecretInputSchema.optional().register(sensitive), openaiCodex: z .object({ enabled: z.boolean().optional(), @@ -402,7 +401,10 @@ const ToolsWebSearchSchema = z if (key === BLOCKED_WEB_SEARCH_KEYS_ISSUE_FIELD || isBlockedObjectKey(key)) { continue; } - if (LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS.has(key) && isPlainRecord(entry)) { + if ( + key === "apiKey" || + (LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS.has(key) && isPlainRecord(entry)) + ) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: [key], @@ -435,31 +437,6 @@ const ToolsWebFetchSchema = z }) .strict() .optional(), - // Keep the legacy Firecrawl fetch shape loadable so existing installs can - // start and then migrate cleanly through doctor. - firecrawl: z - .object({ - enabled: z.boolean().optional(), - apiKey: SecretInputSchema.optional().register(sensitive), - baseUrl: z.string().optional(), - onlyMainContent: z.boolean().optional(), - maxAgeMs: z.number().int().nonnegative().optional(), - timeoutSeconds: z.number().int().positive().optional(), - }) - .strict() - .optional(), - }) - .strict() - .optional(); - -const ToolsWebXSearchSchema = z - .object({ - enabled: z.boolean().optional(), - model: z.string().optional(), - inlineCitations: z.boolean().optional(), - maxTurns: z.number().int().optional(), - timeoutSeconds: z.number().int().positive().optional(), - cacheTtlMinutes: z.number().nonnegative().optional(), }) .strict() .optional(); @@ -468,7 +445,6 @@ const ToolsWebSchema = z .object({ search: ToolsWebSearchSchema, fetch: ToolsWebFetchSchema, - x_search: ToolsWebXSearchSchema, }) .strict() .optional(); diff --git a/src/context-engine/context-engine.test.ts b/src/context-engine/context-engine.test.ts index eba858bb3b87..261cb97953e7 100644 --- a/src/context-engine/context-engine.test.ts +++ b/src/context-engine/context-engine.test.ts @@ -6,7 +6,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { clearMemoryPluginState, registerMemoryPromptPreparation, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, } from "../plugins/memory-state.test-fixtures.js"; // --------------------------------------------------------------------------- // We dynamically import the registry so we can get a fresh module per test @@ -20,7 +20,6 @@ import { import { LegacyContextEngine } from "./legacy.js"; import { registerLegacyContextEngine } from "./legacy.registration.js"; import { - registerContextEngine, registerContextEngineForOwner, getContextEngineRegistration, listContextEngineQuarantines, @@ -39,9 +38,15 @@ import type { IngestResult, } from "./types.js"; -type ContextEngineFactory = Parameters[1]; +type ContextEngineFactory = Parameters[1]; type ContextEngineFactoryContext = Parameters[0]; +function registerTestContextEngine(id: string, factory: ContextEngineFactory) { + return registerContextEngineForOwner(id, factory, `test:${id}`, { + allowSameOwnerRefresh: true, + }); +} + const { compactEmbeddedAgentSessionDirectMock } = vi.hoisted(() => ({ compactEmbeddedAgentSessionDirectMock: vi.fn(), })); @@ -96,7 +101,7 @@ function uniqueEngineId(prefix: string): string { function registerPromptTrackingEngine(engineId: string) { const calls: Array> = []; - registerContextEngine(engineId, () => ({ + registerTestContextEngine(engineId, () => ({ info: { id: engineId, name: "Prompt Tracker", version: "0.0.0" }, async ingest() { return { ingested: false }; @@ -568,7 +573,7 @@ describe("Engine contract tests", () => { it("a mock engine implementing ContextEngine can be registered and resolved", async () => { const factory = () => new MockContextEngine(); - registerContextEngine("mock", factory); + registerTestContextEngine("mock", factory); const engine = await resolveContextEngine(configWithSlot("mock")); expect(engine).toBeInstanceOf(MockContextEngine); @@ -699,7 +704,7 @@ describe("Engine contract tests", () => { }); it("builds a normalized memory system prompt addition from the active memory prompt path", () => { - registerMemoryPromptSection(({ citationsMode }) => [ + registerTestMemoryPromptBuilder(({ citationsMode }) => [ "## Memory Recall", `citations=${citationsMode ?? "auto"}`, "", @@ -714,7 +719,7 @@ describe("Engine contract tests", () => { }); it("passes agent context through delegated memory prompt assembly", () => { - registerMemoryPromptSection(({ agentId, agentSessionKey, sandboxed }) => [ + registerTestMemoryPromptBuilder(({ agentId, agentSessionKey, sandboxed }) => [ "## Agent Memory", `agent=${agentId} session=${agentSessionKey} sandboxed=${sandboxed}`, "", @@ -760,16 +765,16 @@ describe("Engine contract tests", () => { // ═══════════════════════════════════════════════════════════════════════════ describe("Registry tests", () => { - it("registerContextEngine() stores retrievable factories", () => { + it("registerTestContextEngine() stores retrievable factories", () => { const factory = () => new MockContextEngine(); - registerContextEngine("reg-test-2", factory); + registerTestContextEngine("reg-test-2", factory); expect(getContextEngineRegistration("reg-test-2")?.factory).toBe(factory); }); it("tracks all registered ids", () => { - registerContextEngine("reg-test-a", () => new MockContextEngine()); - registerContextEngine("reg-test-b", () => new MockContextEngine()); + registerTestContextEngine("reg-test-a", () => new MockContextEngine()); + registerTestContextEngine("reg-test-b", () => new MockContextEngine()); expect(getContextEngineRegistration("reg-test-a")).toBeDefined(); expect(getContextEngineRegistration("reg-test-b")).toBeDefined(); @@ -821,44 +826,6 @@ describe("Registry tests", () => { expect(resolveContextEngineOwnerPluginId(engine)).toBe("lossless-claw"); }); - - it("public registerContextEngine cannot spoof owner or refresh existing ids", () => { - const ownedFactory = () => new MockContextEngine(); - expect( - registerContextEngineForOwner("public-owner-guard", ownedFactory, "owner-a", { - allowSameOwnerRefresh: true, - }), - ).toEqual({ ok: true }); - - const spoofAttempt = ( - registerContextEngine as unknown as ( - id: string, - factory: ContextEngineFactory, - opts?: { owner?: string }, - ) => ReturnType - )("public-owner-guard", () => new MockContextEngine(), { owner: "owner-a" }); - - expect(spoofAttempt).toEqual({ - ok: false, - existingOwner: "owner-a", - }); - expect(getContextEngineRegistration("public-owner-guard")?.factory).toBe(ownedFactory); - }); - - it("public registerContextEngine reserves the default legacy id", () => { - const legacyAttempt = ( - registerContextEngine as unknown as ( - id: string, - factory: ContextEngineFactory, - opts?: { owner?: string }, - ) => ReturnType - )("legacy", () => new MockContextEngine(), { owner: "core" }); - - expect(legacyAttempt).toEqual({ - ok: false, - existingOwner: "core", - }); - }); }); // ═══════════════════════════════════════════════════════════════════════════ @@ -879,7 +846,7 @@ describe("Legacy sessionKey compatibility", () => { it("memoizes legacy mode after the first strict compatibility retry", async () => { const engineId = `legacy-sessionkey-${Date.now().toString(36)}`; const strictEngine = new LegacySessionKeyStrictEngine(engineId); - registerContextEngine(engineId, () => strictEngine); + registerTestContextEngine(engineId, () => strictEngine); const engine = await resolveContextEngine(configWithSlot(engineId)); const firstAssembled = await engine.assemble({ @@ -904,7 +871,7 @@ describe("Legacy sessionKey compatibility", () => { it("retries strict ingest once and ingests each message only once", async () => { const engineId = `legacy-sessionkey-ingest-${Date.now().toString(36)}`; const strictEngine = new LegacySessionKeyStrictEngine(engineId); - registerContextEngine(engineId, () => strictEngine); + registerTestContextEngine(engineId, () => strictEngine); const engine = await resolveContextEngine(configWithSlot(engineId)); const firstMessage = makeMockMessage("user", "first"); @@ -931,7 +898,7 @@ describe("Legacy sessionKey compatibility", () => { it("retries strict maintain once and memoizes legacy mode there too", async () => { const engineId = `legacy-sessionkey-maintain-${Date.now().toString(36)}`; const strictEngine = new LegacySessionKeyStrictEngine(engineId); - registerContextEngine(engineId, () => strictEngine); + registerTestContextEngine(engineId, () => strictEngine); const engine = await resolveContextEngine(configWithSlot(engineId)); @@ -949,7 +916,7 @@ describe("Legacy sessionKey compatibility", () => { it("keeps learning rejected legacy keys after runtimeSettings marks an engine legacy", async () => { const engineId = uniqueEngineId("legacy-runtime-then-assemble"); const strictEngine = new LegacyRuntimeThenAssembleStrictEngine(engineId); - registerContextEngine(engineId, () => strictEngine); + registerTestContextEngine(engineId, () => strictEngine); const runtimeSettings = { schemaVersion: 1 } as never; const engine = await resolveContextEngine(configWithSlot(engineId)); @@ -987,7 +954,7 @@ describe("Legacy sessionKey compatibility", () => { it("quarantines and falls back for non-compat runtime errors", async () => { const engineId = `sessionkey-runtime-${Date.now().toString(36)}`; const runtimeErrorEngine = new SessionKeyRuntimeErrorEngine(engineId); - registerContextEngine(engineId, () => runtimeErrorEngine); + registerTestContextEngine(engineId, () => runtimeErrorEngine); const engine = await resolveContextEngine(configWithSlot(engineId)); const message = makeMockMessage(); @@ -1017,7 +984,7 @@ describe("Legacy sessionKey compatibility", () => { engineId, 'Unknown sessionKey "agent:main:missing"', ); - registerContextEngine(engineId, () => runtimeErrorEngine); + registerTestContextEngine(engineId, () => runtimeErrorEngine); const engine = await resolveContextEngine(configWithSlot(engineId)); const message = makeMockMessage(); @@ -1046,7 +1013,7 @@ describe("Default engine selection", () => { // Registration is idempotent (Map.set), so calling again is safe. registerLegacyContextEngine(); // Register a lightweight custom stub so we don't need external resources. - registerContextEngine("test-engine", () => { + registerTestContextEngine("test-engine", () => { const engine: ContextEngine = { info: { id: "test-engine", name: "Custom Test Engine", version: "0.0.0" }, async ingest() { @@ -1103,7 +1070,7 @@ describe("Factory context passing", () => { }, }; }; - registerContextEngine(engineId, factory); + registerTestContextEngine(engineId, factory); const cfg = configWithSlot(engineId); await resolveContextEngine(cfg, { @@ -1136,7 +1103,7 @@ describe("Factory context passing", () => { }, }; }; - registerContextEngine(engineId, factory); + registerTestContextEngine(engineId, factory); const engine = await resolveContextEngine(configWithSlot(engineId), { agentDir: "/tmp/agent", @@ -1291,18 +1258,18 @@ describe("Invalid engine fallback", () => { name: "factory throws", engineId: uniqueEngineId("factory-throw"), register: (engineId: string) => { - registerContextEngine(engineId, () => { + registerTestContextEngine(engineId, () => { throw new Error("plugin version mismatch"); }); }, expectedError: (engineId: string) => - `[context-engine] Context engine "${engineId}" owner=public-sdk failed during factory: plugin version mismatch; quarantining it for this process and falling back to default engine "legacy".`, + `[context-engine] Context engine "${engineId}" owner=test:${engineId} failed during factory: plugin version mismatch; quarantining it for this process and falling back to default engine "legacy".`, }, { name: "missing info metadata", engineId: uniqueEngineId("invalid-info"), register: (engineId: string) => { - registerContextEngine( + registerTestContextEngine( engineId, () => ({ @@ -1319,13 +1286,13 @@ describe("Invalid engine fallback", () => { ); }, expectedError: (engineId: string) => - `[context-engine] Context engine "${engineId}" owner=public-sdk failed during contract-validation: Context engine "${engineId}" factory returned an invalid ContextEngine: missing info.; quarantining it for this process and falling back to default engine "legacy".`, + `[context-engine] Context engine "${engineId}" owner=test:${engineId} failed during contract-validation: Context engine "${engineId}" factory returned an invalid ContextEngine: missing info.; quarantining it for this process and falling back to default engine "legacy".`, }, { name: "missing lifecycle methods", engineId: uniqueEngineId("invalid-methods"), register: (engineId: string) => { - registerContextEngine( + registerTestContextEngine( engineId, () => ({ @@ -1337,16 +1304,16 @@ describe("Invalid engine fallback", () => { ); }, expectedError: (engineId: string) => - `[context-engine] Context engine "${engineId}" owner=public-sdk failed during contract-validation: Context engine "${engineId}" factory returned an invalid ContextEngine: missing assemble(), missing compact().; quarantining it for this process and falling back to default engine "legacy".`, + `[context-engine] Context engine "${engineId}" owner=test:${engineId} failed during contract-validation: Context engine "${engineId}" factory returned an invalid ContextEngine: missing assemble(), missing compact().; quarantining it for this process and falling back to default engine "legacy".`, }, { name: "contract validation throws", engineId: uniqueEngineId("validation-throw"), register: (engineId: string) => { - registerContextEngine(engineId, () => 42n as unknown as ContextEngine); + registerTestContextEngine(engineId, () => 42n as unknown as ContextEngine); }, expectedError: (engineId: string) => - `[context-engine] Context engine "${engineId}" owner=public-sdk failed during contract-validation: Do not know how to serialize a BigInt; quarantining it for this process and falling back to default engine "legacy".`, + `[context-engine] Context engine "${engineId}" owner=test:${engineId} failed during contract-validation: Do not know how to serialize a BigInt; quarantining it for this process and falling back to default engine "legacy".`, }, ] as const; @@ -1372,7 +1339,7 @@ describe("Invalid engine fallback", () => { throw new Error("lcm db is corrupt"); }); let factoryCalls = 0; - registerContextEngine(engineId, () => { + registerTestContextEngine(engineId, () => { factoryCalls += 1; return { info: { id: "lcm", name: "Lossless Context Manager" }, @@ -1401,13 +1368,13 @@ describe("Invalid engine fallback", () => { expect(listContextEngineQuarantines()).toEqual([ expect.objectContaining({ engineId, - owner: "public-sdk", + owner: `test:${engineId}`, operation: "assemble", reason: "lcm db is corrupt", }), ]); expect(console.error).toHaveBeenCalledWith( - `[context-engine] Context engine "${engineId}" owner=public-sdk failed during assemble: lcm db is corrupt; quarantining it for this process and falling back to default engine "legacy".`, + `[context-engine] Context engine "${engineId}" owner=test:${engineId} failed during assemble: lcm db is corrupt; quarantining it for this process and falling back to default engine "legacy".`, ); }); @@ -1506,7 +1473,7 @@ describe("Invalid engine fallback", () => { }), ]); - registerContextEngine(engineId, () => ({ + registerTestContextEngine(engineId, () => ({ info: { id: engineId, name: "Late Registered Engine" }, async ingest() { return { ingested: true }; @@ -1530,7 +1497,7 @@ describe("Invalid engine fallback", () => { const abortError = new Error("compaction aborted"); abortError.name = "AbortError"; const controller = new AbortController(); - registerContextEngine(engineId, () => ({ + registerTestContextEngine(engineId, () => ({ info: { id: engineId, name: "Abort Aware Engine" }, async ingest() { return { ingested: true }; @@ -1562,7 +1529,7 @@ describe("Invalid engine fallback", () => { it("quarantines subagent preparation failures while failing the active spawn closed", async () => { const engineId = uniqueEngineId("prepare-subagent-fail"); - registerContextEngine(engineId, () => ({ + registerTestContextEngine(engineId, () => ({ info: { id: engineId, name: "Spawn Aware Engine" }, async ingest() { return { ingested: true }; @@ -1652,7 +1619,7 @@ describe("Invalid engine fallback", () => { // (e.g. "lcm"). That id is metadata, not the lookup key. const engineId = `plugin-slot-${Date.now().toString(36)}`; const internalInfoId = "lcm"; - registerContextEngine( + registerTestContextEngine( engineId, () => ({ @@ -1774,7 +1741,7 @@ describe("assemble() prompt forwarding", () => { it("retries strict legacy assemble without sessionKey, prompt, and runtimeSettings", async () => { const engineId = `prompt-legacy-${Date.now().toString(36)}`; const strictEngine = new LegacyAssembleStrictEngine(engineId); - registerContextEngine(engineId, () => strictEngine); + registerTestContextEngine(engineId, () => strictEngine); const engine = await resolveContextEngine(configWithSlot(engineId)); const result = await engine.assemble({ @@ -1812,7 +1779,7 @@ describe("assemble() prompt forwarding", () => { const resolveStrictEngine = async () => { const engineId = uniqueEngineId("runtime-settings-legacy"); const strictEngine = new LegacyRuntimeSettingsStrictEngine(engineId); - registerContextEngine(engineId, () => strictEngine); + registerTestContextEngine(engineId, () => strictEngine); const engine = await resolveContextEngine(configWithSlot(engineId)); return { engine, strictEngine }; }; @@ -1920,7 +1887,7 @@ describe("Initialization guard", () => { // // Published builds may split the context-engine registry across multiple // output chunks. The Symbol.for() keyed global ensures that a plugin -// calling registerContextEngine() from chunk A is visible to +// registering an owned engine from chunk A is visible to // resolveContextEngine() imported from chunk B. // // These tests exercise the invariant that failed in 2026.3.7 when @@ -1934,7 +1901,7 @@ describe("Bundle chunk isolation (#40096)", () => { const dynamicChunk = await import(/* @vite-ignore */ `${registryUrl}?chunk=${ts}-dynamic`); const chunks = [ { - registerContextEngine, + registerContextEngineForOwner, getContextEngineRegistration, resolveContextEngine, }, @@ -1954,7 +1921,7 @@ describe("Bundle chunk isolation (#40096)", () => { return { ok: true, compacted: false }; }, }); - chunks[0].registerContextEngine(engineId, factory); + chunks[0].registerContextEngineForOwner(engineId, factory, `test:${engineId}`); expect(chunks[1].getContextEngineRegistration(engineId)?.factory).toBe(factory); const engine = await chunks[1].resolveContextEngine(configWithSlot(engineId)); @@ -1964,7 +1931,7 @@ describe("Bundle chunk isolation (#40096)", () => { const registrationTasks = chunks.map((chunk, i) => Promise.resolve().then(() => { const id = `concurrent-${ts}-${i}`; - chunk.registerContextEngine(id, () => new MockContextEngine()); + chunk.registerContextEngineForOwner(id, () => new MockContextEngine(), `test:${id}`); }), ); await Promise.all(registrationTasks); diff --git a/src/context-engine/registry.ts b/src/context-engine/registry.ts index fdd3602952f1..f7890e589476 100644 --- a/src/context-engine/registry.ts +++ b/src/context-engine/registry.ts @@ -411,7 +411,6 @@ function wrapResolvedContextEngine( const CONTEXT_ENGINE_REGISTRY_STATE = Symbol.for("openclaw.contextEngineRegistryState"); const CORE_CONTEXT_ENGINE_OWNER = "core"; -const PUBLIC_CONTEXT_ENGINE_OWNER = "public-sdk"; type ContextEngineRuntimeQuarantine = { engineId: string; @@ -567,20 +566,6 @@ export function registerContextEngineForOwner( return { ok: true }; } -/** - * Public SDK entry point for third-party registrations. - * - * This path is intentionally unprivileged: it cannot claim core-owned ids and - * it cannot safely refresh an existing registration because the caller's - * identity is not authenticated. - */ -export function registerContextEngine( - id: string, - factory: ContextEngineFactory, -): ContextEngineRegistrationResult { - return registerContextEngineForOwner(id, factory, PUBLIC_CONTEXT_ENGINE_OWNER); -} - /** Returns registration metadata so callers can distinguish discovery snapshots from runtime entries. */ export function getContextEngineRegistration(id: string): ContextEngineRegistration | undefined { return getContextEngineRegistryState().engines.get(id); diff --git a/src/context-engine/types.ts b/src/context-engine/types.ts index f7faf10f5239..5373a2ada95b 100644 --- a/src/context-engine/types.ts +++ b/src/context-engine/types.ts @@ -109,22 +109,6 @@ export type ContextEngineRuntimeSettings = { }; }; -export class ContextEngineRuntimeSettingsUnavailableError extends Error { - readonly code = "context_engine_runtime_settings_unavailable"; - constructor(message?: string) { - super(message); - this.name = "ContextEngineRuntimeSettingsUnavailableError"; - } -} - -export class ContextEngineRuntimeSettingsUnsupportedError extends Error { - readonly code = "context_engine_runtime_settings_unsupported"; - constructor(message?: string) { - super(message); - this.name = "ContextEngineRuntimeSettingsUnsupportedError"; - } -} - export type CompactResult = { ok: boolean; compacted: boolean; diff --git a/src/cron/isolated-agent.direct-delivery-core-channels.test.ts b/src/cron/isolated-agent.direct-delivery-core-channels.test.ts index 20857bb5191c..de9abfde4f41 100644 --- a/src/cron/isolated-agent.direct-delivery-core-channels.test.ts +++ b/src/cron/isolated-agent.direct-delivery-core-channels.test.ts @@ -5,7 +5,7 @@ import { runSubagentAnnounceFlow } from "../agents/subagent-announce.js"; import type { ChannelOutboundAdapter, ChannelOutboundContext, -} from "../channels/plugins/types.public.js"; +} from "../channels/plugins/types.adapters.js"; import type { CliDeps } from "../cli/deps.js"; import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js"; import { resolveOutboundSendDep } from "../infra/outbound/send-deps.js"; diff --git a/src/extensionAPI.ts b/src/extensionAPI.ts deleted file mode 100644 index ea9ab35fa9e5..000000000000 --- a/src/extensionAPI.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @deprecated Legacy compat surface for plugins that still import - * openclaw/extension-api. Use the injected plugin runtime or focused - * openclaw/plugin-sdk subpaths instead. - */ - -const shouldWarnExtensionApiImport = - process.env.VITEST !== "true" && - process.env.NODE_ENV !== "test" && - process.env.OPENCLAW_SUPPRESS_EXTENSION_API_WARNING !== "1"; - -if (shouldWarnExtensionApiImport) { - process.emitWarning( - "openclaw/extension-api is deprecated. Migrate to api.runtime.agent.* or focused openclaw/plugin-sdk/ imports. See https://docs.openclaw.ai/plugins/sdk-migration", - { - code: "OPENCLAW_EXTENSION_API_DEPRECATED", - detail: - "This compatibility bridge is temporary. Bundled plugins should use the injected plugin runtime instead of importing host-side agent helpers directly. Migration guide: https://docs.openclaw.ai/plugins/sdk-migration", - }, - ); -} - -export { resolveAgentDir, resolveAgentWorkspaceDir } from "./agents/agent-scope.js"; -export { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./agents/defaults.js"; -export { resolveAgentIdentity } from "./agents/identity.js"; -export { resolveThinkingDefault } from "./agents/model-selection.js"; -export { - runEmbeddedAgent, - /** @deprecated Use runEmbeddedAgent. */ - runEmbeddedAgent as runEmbeddedPiAgent, -} from "./agents/embedded-agent.js"; -export { resolveAgentTimeoutMs } from "./agents/timeout.js"; -export { ensureAgentWorkspace } from "./agents/workspace.js"; -export { resolveStorePath, updateSessionStoreEntry } from "./config/sessions.js"; diff --git a/src/flows/channel-setup.status.test.ts b/src/flows/channel-setup.status.test.ts index a9037fd1e061..250d18b1953a 100644 --- a/src/flows/channel-setup.status.test.ts +++ b/src/flows/channel-setup.status.test.ts @@ -53,8 +53,8 @@ vi.mock("../channels/registry.js", () => ({ vi.mock("../commands/channel-setup/discovery.js", () => ({ resolveChannelSetupEntries: (params: Parameters[0]) => resolveChannelSetupEntries(params), - shouldShowChannelInSetup: (meta: { exposure?: { setup?: boolean }; showInSetup?: boolean }) => - meta.showInSetup !== false && meta.exposure?.setup !== false, + shouldShowChannelInSetup: (meta: { exposure?: { setup?: boolean } }) => + meta.exposure?.setup !== false, })); vi.mock("../config/channel-configured.js", () => ({ diff --git a/src/flows/channel-setup.status.ts b/src/flows/channel-setup.status.ts index 2273f31065dd..cf4116a9fc24 100644 --- a/src/flows/channel-setup.status.ts +++ b/src/flows/channel-setup.status.ts @@ -51,8 +51,6 @@ type ChannelSetupSelectionEntry = { label: string; selectionLabel?: string; exposure?: { setup?: boolean }; - showConfigured?: boolean; - showInSetup?: boolean; }; }; diff --git a/src/flows/search-setup.ts b/src/flows/search-setup.ts index 0873c2aaa480..4f6ff750d894 100644 --- a/src/flows/search-setup.ts +++ b/src/flows/search-setup.ts @@ -280,9 +280,6 @@ export function applySearchKey( return config; } const search: MutableSearchConfig = { ...config.tools?.web?.search, provider, enabled: true }; - if (!providerEntry.setConfiguredCredentialValue) { - providerEntry.setCredentialValue(search, key); - } const nextBase: OpenClawConfig = { ...config, tools: { diff --git a/src/gateway/gateway-codex-bind.live.test.ts b/src/gateway/gateway-codex-bind.live.test.ts index f42d6e71a871..012bc29d15b0 100644 --- a/src/gateway/gateway-codex-bind.live.test.ts +++ b/src/gateway/gateway-codex-bind.live.test.ts @@ -6,7 +6,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { renderCatFacePngBase64 } from "../../test/helpers/live-image-probe.js"; import { isLiveTestEnabled } from "../agents/live-test-helpers.js"; -import type { ChannelOutboundContext } from "../channels/plugins/types.public.js"; +import type { ChannelOutboundContext } from "../channels/plugins/types.adapters.js"; import { clearConfigCache, clearRuntimeConfigSnapshot } from "../config/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isTruthyEnvValue } from "../infra/env.js"; diff --git a/src/gateway/server-channels.test.ts b/src/gateway/server-channels.test.ts index 57cd9bd037f5..67d38fc04cef 100644 --- a/src/gateway/server-channels.test.ts +++ b/src/gateway/server-channels.test.ts @@ -2,11 +2,8 @@ * Server channel lifecycle tests. */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { - ChannelGatewayContext, - ChannelId, - ChannelPlugin, -} from "../channels/plugins/types.public.js"; +import type { ChannelGatewayContext } from "../channels/plugins/types.adapters.js"; +import type { ChannelId, ChannelPlugin } from "../channels/plugins/types.public.js"; import { createSubsystemLogger, type SubsystemLogger, diff --git a/src/gateway/server-plugins.test.ts b/src/gateway/server-plugins.test.ts index 4e161949d481..c10aca76c7a3 100644 --- a/src/gateway/server-plugins.test.ts +++ b/src/gateway/server-plugins.test.ts @@ -842,7 +842,7 @@ describe("loadGatewayPlugins", () => { }); }); - test("provides subagent runtime with sessions.get method aliases", async () => { + test("provides subagent runtime session messages through sessions.get", async () => { const runtime = await createSubagentRuntime(serverPluginsModule); serverPluginsModule.setFallbackGatewayContext(createTestContext("sessions-get-aliases")); handleGatewayRequest @@ -851,11 +851,6 @@ describe("loadGatewayPlugins", () => { expect(opts.req.params).toEqual({ key: "s-read" }); opts.respond(true, { messages: [{ id: "m-1" }] }); }) - .mockImplementationOnce(async (opts: HandleGatewayRequestOptions) => { - expect(opts.req.method).toBe("sessions.get"); - expect(opts.req.params).toEqual({ key: "s-legacy" }); - opts.respond(true, { messages: [{ id: "m-2" }] }); - }) .mockImplementationOnce(async (opts: HandleGatewayRequestOptions) => { expect(opts.req.method).toBe("sessions.get"); expect(opts.req.params).toEqual({ key: "s-limited", limit: 1_000 }); @@ -865,9 +860,6 @@ describe("loadGatewayPlugins", () => { await expect(runtime.getSessionMessages({ sessionKey: "s-read" })).resolves.toEqual({ messages: [{ id: "m-1" }], }); - await expect(runtime.getSession({ sessionKey: "s-legacy" })).resolves.toEqual({ - messages: [{ id: "m-2" }], - }); await expect( runtime.getSessionMessages({ sessionKey: "s-limited", diff --git a/src/gateway/server-plugins.ts b/src/gateway/server-plugins.ts index e24b2f97e3bc..811a55afcb41 100644 --- a/src/gateway/server-plugins.ts +++ b/src/gateway/server-plugins.ts @@ -473,9 +473,6 @@ export function createGatewaySubagentRuntime(): PluginRuntime["subagent"] { }; }, getSessionMessages, - async getSession(params) { - return getSessionMessages(params); - }, async deleteSession(params) { const scope = getPluginRuntimeGatewayRequestScope(); const pluginId = diff --git a/src/gateway/server-reload-handlers.test.ts b/src/gateway/server-reload-handlers.test.ts index 765b3b088ce5..ccffba8f5640 100644 --- a/src/gateway/server-reload-handlers.test.ts +++ b/src/gateway/server-reload-handlers.test.ts @@ -522,9 +522,11 @@ function createManagedRestartSequenceHarness( } as OpenClawConfig; const invalidNoopConfig = { ...deferredConfig, - tools: { - web: { - search: { apiKey: missingHotSecret }, + plugins: { + entries: { + brave: { + config: { webSearch: { apiKey: missingHotSecret } }, + }, }, }, } as OpenClawConfig; @@ -568,13 +570,15 @@ function createManagedRestartSequenceHarness( const secretInputs = [ config.gateway?.auth?.token, config.models?.providers?.test?.apiKey, - config.tools?.web?.search?.apiKey, + (config.plugins?.entries?.brave?.config as { webSearch?: { apiKey?: unknown } } | undefined) + ?.webSearch?.apiKey, ]; for (const secretInput of secretInputs) { if ( typeof secretInput === "object" && secretInput !== null && "id" in secretInput && + typeof secretInput.id === "string" && unavailableSecretIds.has(secretInput.id) ) { throw new Error(`required SecretRef ${secretInput.id} is unavailable`); diff --git a/src/gateway/server-startup-inactive-web-secret.test.ts b/src/gateway/server-startup-inactive-web-secret.test.ts index c0d1a4e061cd..e7f2fc00e968 100644 --- a/src/gateway/server-startup-inactive-web-secret.test.ts +++ b/src/gateway/server-startup-inactive-web-secret.test.ts @@ -54,7 +54,6 @@ vi.mock("../secrets/runtime-web-tools-manifest.runtime.js", () => ({ contract === "webSearchProviders" ? ["google"] : [], resolveManifestContractOwnerPluginId: ({ value }: { value: string }) => value === "gemini" ? "google" : undefined, - resolveManifestContractPluginIdsByCompatibilityRuntimePath: () => [], })); vi.mock("../plugins/web-provider-public-artifacts.explicit.js", () => ({ diff --git a/src/gateway/server-startup-secret-owner-isolation.test.ts b/src/gateway/server-startup-secret-owner-isolation.test.ts index 48aa6256c08e..64b03f1e609e 100644 --- a/src/gateway/server-startup-secret-owner-isolation.test.ts +++ b/src/gateway/server-startup-secret-owner-isolation.test.ts @@ -58,7 +58,6 @@ vi.mock("../secrets/runtime-web-tools-manifest.runtime.js", () => ({ contract === "webSearchProviders" ? ["google"] : [], resolveManifestContractOwnerPluginId: ({ value }: { value: string }) => value === "gemini" ? "google" : undefined, - resolveManifestContractPluginIdsByCompatibilityRuntimePath: () => [], })); vi.mock("../plugins/web-provider-public-artifacts.explicit.js", () => ({ diff --git a/src/gateway/worker-environments/bootstrap.test.ts b/src/gateway/worker-environments/bootstrap.test.ts index fd503909554f..94c9986d76d4 100644 --- a/src/gateway/worker-environments/bootstrap.test.ts +++ b/src/gateway/worker-environments/bootstrap.test.ts @@ -241,9 +241,6 @@ describe("bootstrapWorker", () => { expect(npmRunner.calls[1]?.options.input).toContain("npm pack"); expect(npmRunner.calls[1]?.options.input).toContain("npm install --global"); expect(npmRunner.calls[1]?.options.input).toContain("--registry=https://registry.npmjs.org/"); - expect(npmRunner.calls[1]?.options.input).toContain( - "OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION=1", - ); expect(npmRunner.calls[1]?.options.input).toContain("postinstall-inventory.json"); expect(npmRunner.calls[1]?.options.input).toContain("lib/node_modules/openclaw"); expect(npmRunner.calls[1]?.options.input).toContain('cp -R "$package_dir/." "$staging/"'); diff --git a/src/gateway/worker-environments/bootstrap.ts b/src/gateway/worker-environments/bootstrap.ts index 533618bcae28..12689d85571b 100644 --- a/src/gateway/worker-environments/bootstrap.ts +++ b/src/gateway/worker-environments/bootstrap.ts @@ -447,14 +447,14 @@ case "$install" in fi npm_prefix=$staging/.npm-prefix npm_pack_json=$staging/npm-pack.json - OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION=1 npm pack "$package_spec" --pack-destination "$staging" --ignore-scripts --json --registry=https://registry.npmjs.org/ > "$npm_pack_json" + npm pack "$package_spec" --pack-destination "$staging" --ignore-scripts --json --registry=https://registry.npmjs.org/ > "$npm_pack_json" package_archive=$(node -e '${READ_NPM_PACK_FILENAME_JS}' "$npm_pack_json") package_archive=$staging/$package_archive if ! node -e '${VERIFY_NPM_PACKAGE_JS}' "$package_archive" "$package_integrity"; then printf '%s\n' 'worker npm package integrity mismatch' >&2 exit 2 fi - OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION=1 npm install --global --prefix "$npm_prefix" --ignore-scripts --omit=dev --no-audit --no-fund "$package_archive" + npm install --global --prefix "$npm_prefix" --ignore-scripts --omit=dev --no-audit --no-fund "$package_archive" package_dir=$npm_prefix/lib/node_modules/openclaw if [ ! -f "$package_dir/openclaw.mjs" ]; then printf '%s\n' 'npm did not install the OpenClaw package root' >&2 @@ -482,7 +482,7 @@ if [ "$install" = bundle ]; then printf '%s\n' '${NPM_MISSING_MARKER}' >&2 exit ${NPM_MISSING_EXIT_CODE} fi - OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION=1 npm install --prefix "$staging" --ignore-scripts --omit=dev --no-audit --no-fund >&2 + npm install --prefix "$staging" --ignore-scripts --omit=dev --no-audit --no-fund >&2 fi printf '%s\n' "$receipt_json" > "$staging/${BOOTSTRAP_RECEIPT}" chmod 600 "$staging/${BOOTSTRAP_RECEIPT}" diff --git a/src/infra/dotenv.test.ts b/src/infra/dotenv.test.ts index 05b57ccebe30..976c85501837 100644 --- a/src/infra/dotenv.test.ts +++ b/src/infra/dotenv.test.ts @@ -894,8 +894,8 @@ describe("workspace .env blocklist completeness", () => { rootDir: "/plugins/runtime-cloud", source: "/plugins/runtime-cloud/index.js", manifestPath: "/plugins/runtime-cloud/openclaw.plugin.json", - providerAuthEnvVars: { - "runtime-cloud": ["RUNTIME_CLOUD_API_KEY"], + setup: { + providers: [{ id: "runtime-cloud", envVars: ["RUNTIME_CLOUD_API_KEY"] }], }, }; await writeEnvFile( diff --git a/src/infra/outbound/delivery-queue-media-directive-durability.test.ts b/src/infra/outbound/delivery-queue-media-directive-durability.test.ts index 8a5da8ad18e5..a7499b8c2964 100644 --- a/src/infra/outbound/delivery-queue-media-directive-durability.test.ts +++ b/src/infra/outbound/delivery-queue-media-directive-durability.test.ts @@ -5,7 +5,7 @@ import type { ReplyPayload } from "../../auto-reply/reply-payload.js"; import type { ChannelOutboundAdapter, ChannelOutboundContext, -} from "../../channels/plugins/types.public.js"; +} from "../../channels/plugins/types.adapters.js"; import type { OpenClawConfig } from "../../config/config.js"; import { createEmptyPluginRegistry } from "../../plugins/registry.js"; import { diff --git a/src/infra/run-node.test.ts b/src/infra/run-node.test.ts index 44ae34d94f67..f1c10f4ee9b9 100644 --- a/src/infra/run-node.test.ts +++ b/src/infra/run-node.test.ts @@ -32,8 +32,7 @@ const GENERATED_PLUGIN_ASSET_BUNDLE_HASH = "extensions/demo/src/host/assets/.bun const DIST_ENTRY = "dist/entry.js"; const BUILD_STAMP = `dist/${BUILD_STAMP_FILE}`; const RUNTIME_POSTBUILD_STAMP = `dist/${RUNTIME_POSTBUILD_STAMP_FILE}`; -const DIST_PLUGIN_SDK_INDEX = "dist/plugin-sdk/index.js"; -const DIST_PLUGIN_SDK_ROOT_ALIAS = "dist/plugin-sdk/root-alias.cjs"; +const DIST_PLUGIN_SDK_CORE = "dist/plugin-sdk/core.js"; const DIST_CHANNEL_CATALOG = "dist/channel-catalog.json"; const DIST_LEGACY_CLI_EXIT_COMPAT = "dist/memory-state-CcqRgDZU.js"; const DIST_LEGACY_CLI_EXIT_COMPAT_ALT = "dist/memory-state-DwGdReW4.js"; @@ -60,8 +59,8 @@ const DIST_RUNTIME_EXTENSION_MANIFEST = "dist-runtime/extensions/demo/openclaw.p const DIST_RUNTIME_EXTENSION_PACKAGE = "dist-runtime/extensions/demo/package.json"; const DIST_RUNTIME_EXTENSION_SKILL = "dist-runtime/extensions/demo/skills/SKILL.md"; const DIST_OPENCLAW_ALIAS_PACKAGE = "dist/extensions/node_modules/openclaw/package.json"; -const DIST_OPENCLAW_ALIAS_PLUGIN_SDK_INDEX = - "dist/extensions/node_modules/openclaw/plugin-sdk/index.js"; +const DIST_OPENCLAW_ALIAS_PLUGIN_SDK_CORE = + "dist/extensions/node_modules/openclaw/plugin-sdk/core.js"; const DIST_OPENCLAW_ALIAS_PLUGIN_SDK_STRING_COERCE = "dist/extensions/node_modules/openclaw/plugin-sdk/string-coerce-runtime.js"; const DIFFS_PACKAGE = "extensions/diffs/package.json"; @@ -143,27 +142,25 @@ function firstMockCall(mock: { mock: { calls: T[] } }): T | } async function writeRuntimePostBuildScaffold(tmp: string): Promise { - const pluginSdkAliasPath = path.join(tmp, "src", "plugin-sdk", "root-alias.cjs"); - await fs.mkdir(path.dirname(pluginSdkAliasPath), { recursive: true }); await fs.mkdir(path.join(tmp, "extensions"), { recursive: true }); - await fs.writeFile(pluginSdkAliasPath, "module.exports = {};\n", "utf-8"); - await fs.utimes(pluginSdkAliasPath, BUILD_TIME, BUILD_TIME); await writeProjectFiles(tmp, { - [DIST_PLUGIN_SDK_ROOT_ALIAS]: "module.exports = {};\n", + [DIST_PLUGIN_SDK_CORE]: "export const core = true;\n", [DIST_CHANNEL_CATALOG]: '{"entries":[]}\n', [DIST_LEGACY_CLI_EXIT_COMPAT]: "export function hasMemoryRuntime() { return false; }\n", [DIST_LEGACY_CLI_EXIT_COMPAT_ALT]: "export function hasMemoryRuntime() { return false; }\n", [DIST_OPENCLAW_ALIAS_PACKAGE]: - '{"name":"openclaw","type":"module","exports":{"./plugin-sdk":"./plugin-sdk/index.js"}}\n', + '{"name":"openclaw","type":"module","exports":{"./plugin-sdk/core":"./plugin-sdk/core.js"}}\n', + [DIST_OPENCLAW_ALIAS_PLUGIN_SDK_CORE]: "export * from '../../../../plugin-sdk/core.js';\n", }); await touchProjectFiles( tmp, [ - DIST_PLUGIN_SDK_ROOT_ALIAS, DIST_CHANNEL_CATALOG, + DIST_PLUGIN_SDK_CORE, DIST_LEGACY_CLI_EXIT_COMPAT, DIST_LEGACY_CLI_EXIT_COMPAT_ALT, DIST_OPENCLAW_ALIAS_PACKAGE, + DIST_OPENCLAW_ALIAS_PLUGIN_SDK_CORE, ], BUILD_TIME, ); @@ -470,9 +467,6 @@ describe("run-node script", () => { statusCommandSpawn(), ]); - await expect( - fs.readFile(resolvePath(tmp, "dist/plugin-sdk/root-alias.cjs"), "utf-8"), - ).resolves.toContain("module.exports = {};"); await expectManifestId(tmp, DIST_EXTENSION_MANIFEST, "demo"); await expect( fs.readFile(resolvePath(tmp, DIST_EXTENSION_PACKAGE), "utf-8"), @@ -701,7 +695,6 @@ describe("run-node script", () => { await setupTrackedProject(tmp, { files: { [ROOT_SRC]: "export const value = 1;\n", - [DIST_PLUGIN_SDK_ROOT_ALIAS]: "module.exports = {};\n", [DIST_CHANNEL_CATALOG]: '{"entries":[]}\n', [DIST_LEGACY_CLI_EXIT_COMPAT]: "export function hasMemoryRuntime() { return false; }\n", [DIST_LEGACY_CLI_EXIT_COMPAT_ALT]: @@ -710,7 +703,6 @@ describe("run-node script", () => { oldPaths: [ROOT_SRC, ROOT_TSCONFIG, ROOT_PACKAGE], buildPaths: [ DIST_ENTRY, - DIST_PLUGIN_SDK_ROOT_ALIAS, DIST_CHANNEL_CATALOG, DIST_LEGACY_CLI_EXIT_COMPAT, DIST_LEGACY_CLI_EXIT_COMPAT_ALT, @@ -1167,7 +1159,6 @@ describe("run-node script", () => { await setupTrackedProject(tmp, { files: { [ROOT_SRC]: "export const value = 1;\n", - [DIST_PLUGIN_SDK_ROOT_ALIAS]: "module.exports = {};\n", [DIST_LEGACY_CLI_EXIT_COMPAT]: "export function hasMemoryRuntime() { return false; }\n", [DIST_LEGACY_CLI_EXIT_COMPAT_ALT]: "export function hasMemoryRuntime() { return false; }\n", @@ -1175,7 +1166,6 @@ describe("run-node script", () => { oldPaths: [ROOT_SRC, ROOT_TSCONFIG, ROOT_PACKAGE], buildPaths: [ DIST_ENTRY, - DIST_PLUGIN_SDK_ROOT_ALIAS, DIST_LEGACY_CLI_EXIT_COMPAT, DIST_LEGACY_CLI_EXIT_COMPAT_ALT, BUILD_STAMP, @@ -2324,8 +2314,7 @@ describe("run-node script", () => { await setupTrackedProject(tmp, { files: { [ROOT_SRC]: "export const value = 1;\n", - [DIST_PLUGIN_SDK_INDEX]: "export * from './core.js';\n", - [DIST_PLUGIN_SDK_ROOT_ALIAS]: "module.exports = {};\n", + [DIST_PLUGIN_SDK_CORE]: "export const core = true;\n", [DIST_CHANNEL_CATALOG]: '{"entries":[]}\n', [DIST_LEGACY_CLI_EXIT_COMPAT]: "export function hasMemoryRuntime() { return false; }\n", [DIST_LEGACY_CLI_EXIT_COMPAT_ALT]: @@ -2335,8 +2324,7 @@ describe("run-node script", () => { buildPaths: [ ROOT_SRC, DIST_ENTRY, - DIST_PLUGIN_SDK_INDEX, - DIST_PLUGIN_SDK_ROOT_ALIAS, + DIST_PLUGIN_SDK_CORE, DIST_CHANNEL_CATALOG, DIST_LEGACY_CLI_EXIT_COMPAT, DIST_LEGACY_CLI_EXIT_COMPAT_ALT, @@ -2365,24 +2353,26 @@ describe("run-node script", () => { await setupTrackedProject(tmp, { files: { [ROOT_SRC]: "export const value = 1;\n", - [DIST_PLUGIN_SDK_INDEX]: "export * from './core.js';\n", + [ROOT_PACKAGE]: + '{"name":"openclaw-test","exports":{"./plugin-sdk/core":"./dist/plugin-sdk/core.js"}}\n', + [DIST_PLUGIN_SDK_CORE]: "export const core = true;\n", [DIST_OPENCLAW_ALIAS_PACKAGE]: - '{"name":"openclaw","type":"module","exports":{"./plugin-sdk":"./plugin-sdk/index.js"}}\n', - [DIST_OPENCLAW_ALIAS_PLUGIN_SDK_INDEX]: - "export * from '../../../../plugin-sdk/index.js';\n", + '{"name":"openclaw","type":"module","exports":{"./plugin-sdk/core":"./plugin-sdk/core.js"}}\n', + [DIST_OPENCLAW_ALIAS_PLUGIN_SDK_CORE]: + "export * from '../../../../plugin-sdk/core.js';\n", [RUNTIME_POSTBUILD_STAMP]: '{"head":"abc123"}\n', }, buildPaths: [ ROOT_SRC, DIST_ENTRY, - DIST_PLUGIN_SDK_INDEX, + DIST_PLUGIN_SDK_CORE, DIST_OPENCLAW_ALIAS_PACKAGE, - DIST_OPENCLAW_ALIAS_PLUGIN_SDK_INDEX, + DIST_OPENCLAW_ALIAS_PLUGIN_SDK_CORE, BUILD_STAMP, RUNTIME_POSTBUILD_STAMP, ], }); - await fs.rm(resolvePath(tmp, DIST_OPENCLAW_ALIAS_PLUGIN_SDK_INDEX)); + await fs.rm(resolvePath(tmp, DIST_OPENCLAW_ALIAS_PLUGIN_SDK_CORE)); const requirement = resolveRuntimePostBuildRequirement( createBuildRequirementDeps(tmp, { @@ -2407,20 +2397,16 @@ describe("run-node script", () => { { name: "openclaw-test", exports: { - "./plugin-sdk": "./dist/plugin-sdk/index.js", "./plugin-sdk/string-coerce-runtime": "./dist/plugin-sdk/string-coerce-runtime.js", }, }, null, 2, ), - [DIST_PLUGIN_SDK_INDEX]: "export * from './core.js';\n", "dist/plugin-sdk/string-coerce-runtime.js": "export const publicRuntime = true;\n", "dist/plugin-sdk/ssrf-runtime-internal.js": "export const internal = true;\n", [DIST_OPENCLAW_ALIAS_PACKAGE]: - '{"name":"openclaw","type":"module","exports":{"./plugin-sdk":"./plugin-sdk/index.js","./plugin-sdk/string-coerce-runtime":"./plugin-sdk/string-coerce-runtime.js"}}\n', - [DIST_OPENCLAW_ALIAS_PLUGIN_SDK_INDEX]: - "export * from '../../../../plugin-sdk/index.js';\n", + '{"name":"openclaw","type":"module","exports":{"./plugin-sdk/string-coerce-runtime":"./plugin-sdk/string-coerce-runtime.js"}}\n', [DIST_OPENCLAW_ALIAS_PLUGIN_SDK_STRING_COERCE]: "export * from '../../../../plugin-sdk/string-coerce-runtime.js';\n", [RUNTIME_POSTBUILD_STAMP]: '{"head":"abc123"}\n', @@ -2429,11 +2415,9 @@ describe("run-node script", () => { ROOT_SRC, ROOT_PACKAGE, DIST_ENTRY, - DIST_PLUGIN_SDK_INDEX, "dist/plugin-sdk/string-coerce-runtime.js", "dist/plugin-sdk/ssrf-runtime-internal.js", DIST_OPENCLAW_ALIAS_PACKAGE, - DIST_OPENCLAW_ALIAS_PLUGIN_SDK_INDEX, DIST_OPENCLAW_ALIAS_PLUGIN_SDK_STRING_COERCE, BUILD_STAMP, RUNTIME_POSTBUILD_STAMP, @@ -2620,7 +2604,6 @@ describe("run-node script", () => { }); for (const missingPath of [ - DIST_PLUGIN_SDK_ROOT_ALIAS, DIST_CHANNEL_CATALOG, DIST_LEGACY_CLI_EXIT_COMPAT, DIST_STABLE_ROOT_RUNTIME_ALIAS, diff --git a/src/infra/tsdown-config.test.ts b/src/infra/tsdown-config.test.ts index 644e995f42f8..fc09613f606e 100644 --- a/src/infra/tsdown-config.test.ts +++ b/src/infra/tsdown-config.test.ts @@ -117,8 +117,6 @@ describe("tsdown config", () => { "web-fetch/runtime", "mcp/openclaw-tools-serve", "mcp/plugin-tools-serve", - "plugin-sdk/compat", - "plugin-sdk/index", bundledEntry("active-memory"), "bundled/boot-md/handler", ]) { diff --git a/src/memory-host-sdk/event-types.ts b/src/memory-host-sdk/event-types.ts index f2bba5380940..3672d18efddd 100644 --- a/src/memory-host-sdk/event-types.ts +++ b/src/memory-host-sdk/event-types.ts @@ -6,7 +6,7 @@ type MemoryHostEventStorageMetadata = { }; /** Event emitted when a recall query records the selected memory snippets. */ -export type MemoryHostRecallRecordedEvent = MemoryHostEventStorageMetadata & { +type MemoryHostRecallRecordedEvent = MemoryHostEventStorageMetadata & { type: "memory.recall.recorded"; timestamp: string; query: string; @@ -20,7 +20,7 @@ export type MemoryHostRecallRecordedEvent = MemoryHostEventStorageMetadata & { }; /** Event emitted when recall hits are visible but excluded from short-term promotion. */ -export type MemoryHostRecallSkippedEvent = MemoryHostEventStorageMetadata & { +type MemoryHostRecallSkippedEvent = MemoryHostEventStorageMetadata & { type: "memory.recall.skipped"; timestamp: string; query: string; @@ -37,7 +37,7 @@ export type MemoryHostRecallSkippedEvent = MemoryHostEventStorageMetadata & { }; /** Event emitted when deep-dream candidates are promoted into durable memory. */ -export type MemoryHostPromotionAppliedEvent = MemoryHostEventStorageMetadata & { +type MemoryHostPromotionAppliedEvent = MemoryHostEventStorageMetadata & { type: "memory.promotion.applied"; timestamp: string; memoryPath: string; @@ -53,10 +53,10 @@ export type MemoryHostPromotionAppliedEvent = MemoryHostEventStorageMetadata & { }; /** Normalized outcome for a dreaming phase run. */ -export type MemoryDreamOutcome = "completed" | "failed"; +type MemoryDreamOutcome = "completed" | "failed"; /** Event emitted after a dreaming phase writes inline memory and/or reports. */ -export type MemoryHostDreamCompletedEvent = MemoryHostEventStorageMetadata & { +type MemoryHostDreamCompletedEvent = MemoryHostEventStorageMetadata & { type: "memory.dream.completed"; timestamp: string; phase: MemoryDreamingPhaseName; diff --git a/src/memory-host-sdk/events.ts b/src/memory-host-sdk/events.ts index 06ae73d4f442..b3e9c09d0321 100644 --- a/src/memory-host-sdk/events.ts +++ b/src/memory-host-sdk/events.ts @@ -7,20 +7,10 @@ import { } from "./event-store.js"; import type { MemoryHostEvent, MemoryHostEventRecord } from "./event-types.js"; -export type { - MemoryDreamOutcome, - MemoryHostDreamCompletedEvent, - MemoryHostEvent, - MemoryHostEventRecord, - MemoryHostPromotionAppliedEvent, - MemoryHostRecallRecordedEvent, - MemoryHostRecallSkippedEvent, -} from "./event-types.js"; - export { normalizeMemoryHostEventRecordForStorage }; /** Legacy workspace JSONL path retained only for doctor migration discovery. */ -export const MEMORY_HOST_EVENT_LOG_RELATIVE_PATH = path.join("memory", ".dreams", "events.jsonl"); +const MEMORY_HOST_EVENT_LOG_RELATIVE_PATH = path.join("memory", ".dreams", "events.jsonl"); /** Resolve the retired JSONL source path without reading it at runtime. */ export function resolveMemoryHostEventLogPath(workspaceDir: string): string { diff --git a/src/plugin-activation-boundary.test.ts b/src/plugin-activation-boundary.test.ts index 55300e08830c..d109ba42b074 100644 --- a/src/plugin-activation-boundary.test.ts +++ b/src/plugin-activation-boundary.test.ts @@ -45,11 +45,9 @@ const loadPluginManifestRegistryForPluginRegistry = vi.hoisted(() => channels: ["discord", "irc", "slack", "telegram"], providers: [], cliBackends: [], - channelEnvVars: { - discord: ["DISCORD_BOT_TOKEN"], - irc: ["IRC_HOST", "IRC_NICK"], - slack: ["SLACK_BOT_TOKEN"], - telegram: ["TELEGRAM_BOT_TOKEN"], + packageChannel: { + id: "discord", + configuredState: { env: { anyOf: ["DISCORD_BOT_TOKEN"] } }, }, modelIdNormalization: testModelIdNormalization, skills: [], diff --git a/src/plugin-sdk/agent-dir-compat.test.ts b/src/plugin-sdk/agent-dir-compat.test.ts deleted file mode 100644 index b0ed8ae28bd8..000000000000 --- a/src/plugin-sdk/agent-dir-compat.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Tests agent directory compatibility helpers. - */ -import { describe, expect, it } from "vitest"; -import { resolveOpenClawAgentDir } from "./agent-dir-compat.js"; - -describe("resolveOpenClawAgentDir", () => { - it("keeps the shipped Pi env alias for deprecated plugin SDK callers", () => { - expect( - resolveOpenClawAgentDir({ - PI_CODING_AGENT_DIR: "/tmp/openclaw-legacy-agent", - }), - ).toBe("/tmp/openclaw-legacy-agent"); - }); - - it("prefers the OpenClaw env override over the deprecated Pi alias", () => { - expect( - resolveOpenClawAgentDir({ - OPENCLAW_AGENT_DIR: "/tmp/openclaw-agent", - PI_CODING_AGENT_DIR: "/tmp/openclaw-legacy-agent", - }), - ).toBe("/tmp/openclaw-agent"); - }); -}); diff --git a/src/plugin-sdk/agent-dir-compat.ts b/src/plugin-sdk/agent-dir-compat.ts deleted file mode 100644 index 354bb2ecc06d..000000000000 --- a/src/plugin-sdk/agent-dir-compat.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Agent directory compatibility helpers resolve legacy and current agent directory locations. -import { resolveDefaultAgentDir } from "../agents/agent-scope-config.js"; -import { resolveUserPath } from "../utils.js"; - -/** - * @deprecated Prefer resolveAgentDir(cfg, agentId) or resolveDefaultAgentDir(cfg). - * Kept for third-party plugin SDK compatibility. - */ -export function resolveOpenClawAgentDir(env: NodeJS.ProcessEnv = process.env): string { - const override = env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim(); - return override ? resolveUserPath(override, env) : resolveDefaultAgentDir({}, env); -} diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index 2b3a8dd4090a..67d4c6aad97f 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -102,7 +102,6 @@ export type { AgentToolResultMiddleware, AgentToolResultMiddlewareContext, AgentToolResultMiddlewareEvent, - AgentToolResultMiddlewareHarness, AgentToolResultMiddlewareOptions, AgentToolResultMiddlewareResult, AgentToolResultMiddlewareRuntime, @@ -168,7 +167,6 @@ export { type ToolResultFailureKind, } from "../agents/tool-result-error.js"; export { normalizeUsage } from "../agents/usage.js"; -export { resolveOpenClawAgentDir } from "./agent-dir-compat.js"; export { resolveAgentDir, resolveDefaultAgentDir, diff --git a/src/plugin-sdk/agent-harness-tool-runtime.ts b/src/plugin-sdk/agent-harness-tool-runtime.ts index 09d3a9d06e82..a94ea315246d 100644 --- a/src/plugin-sdk/agent-harness-tool-runtime.ts +++ b/src/plugin-sdk/agent-harness-tool-runtime.ts @@ -4,7 +4,32 @@ * Keep tool-search and code-mode dependencies out of the lightweight harness * lifecycle facade used during plugin startup. */ -export { - createAgentHarnessToolSurfaceRuntime, - type AgentHarnessToolSurfaceRuntime, +import { + createAgentHarnessToolSurfaceRuntime as createCoreAgentHarnessToolSurfaceRuntime, + type AgentHarnessToolSurfaceRuntime as CoreAgentHarnessToolSurfaceRuntime, } from "../agents/harness/tool-surface-bridge.js"; + +type OpenClawCodingToolsOptions = NonNullable< + Parameters[0] +>; + +export type AgentHarnessToolSurfaceRuntime = Omit< + CoreAgentHarnessToolSurfaceRuntime, + "toolSearchCatalogExecutor" | "toolSearchCatalogRef" +> & { + toolSearchCatalogExecutor: OpenClawCodingToolsOptions["toolSearchCatalogExecutor"]; + toolSearchCatalogRef: OpenClawCodingToolsOptions["toolSearchCatalogRef"]; +}; + +export type AgentHarnessToolSurfaceRuntimeParams = Omit< + Parameters[0], + "executeTool" +> & { + executeTool: NonNullable; +}; + +export function createAgentHarnessToolSurfaceRuntime( + params: AgentHarnessToolSurfaceRuntimeParams, +): AgentHarnessToolSurfaceRuntime { + return createCoreAgentHarnessToolSurfaceRuntime(params); +} diff --git a/src/plugin-sdk/api-baseline.ts b/src/plugin-sdk/api-baseline.ts index c127446ebd35..7a078915ae04 100644 --- a/src/plugin-sdk/api-baseline.ts +++ b/src/plugin-sdk/api-baseline.ts @@ -611,8 +611,7 @@ function buildModuleSurface(params: { const metadata = Object.hasOwn(pluginSdkDocMetadata, entrypoint) ? pluginSdkDocMetadata[entrypoint as PluginSdkDocEntrypoint] : undefined; - const importSpecifier = - entrypoint === "index" ? "openclaw/plugin-sdk" : `openclaw/plugin-sdk/${entrypoint}`; + const importSpecifier = `openclaw/plugin-sdk/${entrypoint}`; const moduleSourcePath = path.join(repoRoot, "src", "plugin-sdk", `${entrypoint}.ts`); const sourceFile = program.getSourceFile(moduleSourcePath); assert(sourceFile, `Missing source file for ${importSpecifier}`); diff --git a/src/plugin-sdk/approval-delivery-helpers.test.ts b/src/plugin-sdk/approval-delivery-helpers.test.ts index 61e42c6b6b5a..7c4a58d6c2ad 100644 --- a/src/plugin-sdk/approval-delivery-helpers.test.ts +++ b/src/plugin-sdk/approval-delivery-helpers.test.ts @@ -477,25 +477,4 @@ describe("createChannelApprovalCapability", () => { native, }); }); - - it("keeps the deprecated approvals alias as a compatibility shim", () => { - const delivery = { hasConfiguredDmRoute: vi.fn() }; - - expect( - createChannelApprovalCapability({ - approvals: { delivery }, - }), - ).toEqual({ - authorizeActorAction: undefined, - getActionAvailabilityState: undefined, - getExecInitiatingSurfaceState: undefined, - resolveApproveCommandBehavior: undefined, - describeExecApprovalSetup: undefined, - describePluginApprovalSetup: undefined, - delivery, - nativeRuntime: undefined, - render: undefined, - native: undefined, - }); - }); }); diff --git a/src/plugin-sdk/approval-delivery-helpers.ts b/src/plugin-sdk/approval-delivery-helpers.ts index d71ec0e37f1e..1bd9f285e967 100644 --- a/src/plugin-sdk/approval-delivery-helpers.ts +++ b/src/plugin-sdk/approval-delivery-helpers.ts @@ -247,16 +247,12 @@ export function createChannelApprovalCapability(params: { render?: ChannelApprovalCapability["render"]; /** Native target/capability discovery hooks. */ native?: ChannelApprovalCapability["native"]; - /** @deprecated Pass delivery/nativeRuntime/render/native directly. */ - approvals?: Partial; }): ChannelApprovalCapability { - // Keep the approvals alias for shipped plugin-sdk callers; registry tests track - // this compatibility marker until the public deprecation window closes. const surfaces: ChannelApprovalCapabilitySurfaces = { - delivery: params.delivery ?? params.approvals?.delivery, - nativeRuntime: params.nativeRuntime ?? params.approvals?.nativeRuntime, - render: params.render ?? params.approvals?.render, - native: params.native ?? params.approvals?.native, + delivery: params.delivery, + nativeRuntime: params.nativeRuntime, + render: params.render, + native: params.native, }; return { authorizeActorAction: params.authorizeActorAction, diff --git a/src/plugin-sdk/channel-actions.ts b/src/plugin-sdk/channel-actions.ts index cfd0b6972f5d..e7d3a3648dc6 100644 --- a/src/plugin-sdk/channel-actions.ts +++ b/src/plugin-sdk/channel-actions.ts @@ -1,7 +1,4 @@ // Channel action schemas describe plugin-declared actions available through channel UIs. -import { Type } from "typebox"; -import type { TSchema } from "typebox"; -import { stringEnum as createStringEnum } from "../agents/schema/typebox.js"; export { createUnionActionGate, @@ -33,40 +30,3 @@ export { optionalStringEnum, stringEnum, } from "../agents/schema/typebox.js"; - -/** - * @deprecated Use semantic `presentation` capabilities instead of exposing - * provider-native button schemas through the shared message tool. - */ -export function createMessageToolButtonsSchema(): TSchema { - return Type.Optional( - Type.Array( - Type.Array( - Type.Object({ - text: Type.String(), - callback_data: Type.String(), - style: Type.Optional(createStringEnum(["danger", "success", "primary"])), - }), - ), - { - description: "Button rows for channels that support button-style actions.", - }, - ), - ); -} - -/** - * @deprecated Use semantic `presentation` capabilities instead of exposing - * provider-native card schemas through the shared message tool. - */ -export function createMessageToolCardSchema(): TSchema { - return Type.Optional( - Type.Object( - {}, - { - additionalProperties: true, - description: "Structured card payload for channels that support card-style messages.", - }, - ), - ); -} diff --git a/src/plugin-sdk/channel-config-schema-legacy.ts b/src/plugin-sdk/channel-config-schema-legacy.ts deleted file mode 100644 index a09fadd3bf65..000000000000 --- a/src/plugin-sdk/channel-config-schema-legacy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @deprecated Compatibility surface for bundled channel schemas. - * - * OpenClaw-maintained bundled plugins should import - * openclaw/plugin-sdk/bundled-channel-config-schema. Third-party plugins should - * define plugin-local schemas and import primitives from - * openclaw/plugin-sdk/channel-config-schema instead of depending on bundled - * channel schemas. - */ -export * from "./bundled-channel-config-schema.js"; diff --git a/src/plugin-sdk/channel-entry-contract.test.ts b/src/plugin-sdk/channel-entry-contract.test.ts index d9be7507f9fb..0997be779f71 100644 --- a/src/plugin-sdk/channel-entry-contract.test.ts +++ b/src/plugin-sdk/channel-entry-contract.test.ts @@ -302,17 +302,11 @@ function runCompiledEsmSidecarFastPathProbe(): SpawnSyncReturns { type: "module", bin: { openclaw: "./openclaw.mjs" }, exports: { - "./plugin-sdk": "./dist/plugin-sdk/root-alias.cjs", "./plugin-sdk/channel-outbound": "./dist/plugin-sdk/channel-outbound.js", }, }); fs.writeFileSync(path.join(tempRoot, "openclaw.mjs"), "#!/usr/bin/env node\n", "utf8"); fs.mkdirSync(path.join(tempRoot, "dist", "plugin-sdk"), { recursive: true }); - fs.writeFileSync( - path.join(tempRoot, "dist", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf8", - ); fs.writeFileSync( path.join(tempRoot, "dist", "plugin-sdk", "channel-outbound.js"), 'export const defineChannelMessageAdapter = () => "adapter";\n', diff --git a/src/plugin-sdk/channel-envelope.ts b/src/plugin-sdk/channel-envelope.ts deleted file mode 100644 index 8d94796484cd..000000000000 --- a/src/plugin-sdk/channel-envelope.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-inbound`. */ -export { - formatInboundEnvelope, - resolveEnvelopeFormatOptions, - type EnvelopeFormatOptions, -} from "./channel-inbound.js"; diff --git a/src/plugin-sdk/channel-inbound-roots.ts b/src/plugin-sdk/channel-inbound-roots.ts deleted file mode 100644 index 806d8d7ad56c..000000000000 --- a/src/plugin-sdk/channel-inbound-roots.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-inbound`. */ -export { mergeInboundPathRoots } from "./channel-inbound.js"; diff --git a/src/plugin-sdk/channel-inbound.ts b/src/plugin-sdk/channel-inbound.ts index 1494c05c9b2c..a5c1e650c30b 100644 --- a/src/plugin-sdk/channel-inbound.ts +++ b/src/plugin-sdk/channel-inbound.ts @@ -65,10 +65,6 @@ export type { InboundMentionPolicy, InboundImplicitMentionKind, InboundMentionDecision, - MentionGateParams, - MentionGateResult, - MentionGateWithBypassParams, - MentionGateWithBypassResult, ResolveInboundMentionDecisionFlatParams, ResolveInboundMentionDecisionNestedParams, ResolveInboundMentionDecisionParams, @@ -76,10 +72,6 @@ export type { export { implicitMentionKindWhen, resolveInboundMentionDecision, - // @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. - resolveMentionGating, - // @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. - resolveMentionGatingWithBypass, } from "../channels/mention-gating.js"; export type { LocationSource, NormalizedLocation, OutboundLocation } from "../channels/location.js"; export { diff --git a/src/plugin-sdk/channel-location.ts b/src/plugin-sdk/channel-location.ts deleted file mode 100644 index 611446c6e430..000000000000 --- a/src/plugin-sdk/channel-location.ts +++ /dev/null @@ -1,3 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-inbound`. */ -export type { LocationSource, NormalizedLocation } from "./channel-inbound.js"; -export { formatLocationText, toLocationContext } from "./channel-inbound.js"; diff --git a/src/plugin-sdk/channel-mention-gating.ts b/src/plugin-sdk/channel-mention-gating.ts index efb5e02b85ab..9047695f0d60 100644 --- a/src/plugin-sdk/channel-mention-gating.ts +++ b/src/plugin-sdk/channel-mention-gating.ts @@ -4,10 +4,6 @@ export type { InboundMentionDecision, InboundMentionFacts, InboundMentionPolicy, - MentionGateParams, - MentionGateResult, - MentionGateWithBypassParams, - MentionGateWithBypassResult, ResolveInboundMentionDecisionFlatParams, ResolveInboundMentionDecisionNestedParams, ResolveInboundMentionDecisionParams, @@ -15,10 +11,6 @@ export type { export { implicitMentionKindWhen, resolveInboundMentionDecision, - // @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. - resolveMentionGating, - // @deprecated Prefer `resolveInboundMentionDecision({ facts, policy })`. - resolveMentionGatingWithBypass, } from "../channels/mention-gating.js"; export { CURRENT_MESSAGE_MARKER, diff --git a/src/plugin-sdk/channel-message-runtime.ts b/src/plugin-sdk/channel-message-runtime.ts deleted file mode 100644 index a8817ec0b02f..000000000000 --- a/src/plugin-sdk/channel-message-runtime.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @deprecated Use `openclaw/plugin-sdk/channel-outbound`. The outbound subpath - * contains message lifecycle contracts plus runtime send helpers. - */ - -export * from "./channel-outbound.js"; diff --git a/src/plugin-sdk/channel-message.test.ts b/src/plugin-sdk/channel-message.test.ts index f65cddaea45a..9d6375aaa729 100644 --- a/src/plugin-sdk/channel-message.test.ts +++ b/src/plugin-sdk/channel-message.test.ts @@ -13,9 +13,7 @@ describe("defineChannelMessageAdapter", () => { await Promise.all([ import("openclaw/plugin-sdk/channel-outbound"), import("openclaw/plugin-sdk/channel-message"), - import("openclaw/plugin-sdk/channel-message-runtime"), import("openclaw/plugin-sdk/channel-reply-pipeline"), - import("openclaw/plugin-sdk/compat"), ] as const); let pluginSdkSubpaths: Awaited>; @@ -23,9 +21,8 @@ describe("defineChannelMessageAdapter", () => { pluginSdkSubpaths = await loadPluginSdkSubpaths(); }); - it("keeps new and legacy channel plugin SDK subpaths importable", async () => { - const [channelOutbound, channelMessage, channelMessageRuntime, channelReplyPipeline, compat] = - pluginSdkSubpaths; + it("keeps channel plugin SDK subpaths aligned", async () => { + const [channelOutbound, channelMessage, channelReplyPipeline] = pluginSdkSubpaths; expect(channelOutbound.createChannelMessageReplyPipeline).toBe( channelReplyPipeline.createChannelReplyPipeline, @@ -37,14 +34,7 @@ describe("defineChannelMessageAdapter", () => { channelReplyPipeline.createReplyPrefixOptions, ); expect(channelMessage.createTypingCallbacks).toBe(channelReplyPipeline.createTypingCallbacks); - expect(channelMessageRuntime.sendDurableMessageBatch).toBe( - channelMessage.sendDurableMessageBatch, - ); - expect(channelMessageRuntime.withDurableMessageSendContext).toBe( - channelMessage.withDurableMessageSendContext, - ); expect(channelOutbound.defineChannelMessageAdapter).toBe(defineCoreChannelMessageAdapter); - expect(compat.createChannelReplyPipeline).toBe(channelReplyPipeline.createChannelReplyPipeline); }); it("defaults new message adapters to plugin-owned receive acknowledgement", () => { diff --git a/src/plugin-sdk/channel-pairing-paths.ts b/src/plugin-sdk/channel-pairing-paths.ts deleted file mode 100644 index f781f10484c9..000000000000 --- a/src/plugin-sdk/channel-pairing-paths.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-pairing`. */ -export { resolveChannelAllowFromPath } from "./channel-pairing.js"; diff --git a/src/plugin-sdk/channel-reply-options-runtime.ts b/src/plugin-sdk/channel-reply-options-runtime.ts deleted file mode 100644 index fcf76eb51472..000000000000 --- a/src/plugin-sdk/channel-reply-options-runtime.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-outbound`. */ -export { createReplyPrefixOptions, createTypingCallbacks } from "./channel-outbound.js"; diff --git a/src/plugin-sdk/channel-route.test.ts b/src/plugin-sdk/channel-route.test.ts index 671de6564f5e..c40a05d161d0 100644 --- a/src/plugin-sdk/channel-route.test.ts +++ b/src/plugin-sdk/channel-route.test.ts @@ -5,8 +5,6 @@ import { describe, expect, it } from "vitest"; import { channelRouteCompactKey, channelRouteDedupeKey, - channelRouteIdentityKey, - channelRouteKey, channelRouteTargetsMatchExact, channelRouteTargetsShareConversation, channelRoutesMatchExact, @@ -48,7 +46,6 @@ describe("plugin-sdk channel-route", () => { expect(stringifyRouteThreadId(route?.thread?.id)).toBe("42"); expect(channelRouteCompactKey(route)).toBe("telegram|-100123||42"); - expect(channelRouteKey(route)).toBe(channelRouteCompactKey(route)); }); it("builds compact route keys from raw route-like input", () => { @@ -80,16 +77,6 @@ describe("plugin-sdk channel-route", () => { ); }); - it("keeps deprecated identity key alias wired to the dedupe key", () => { - const input = { - channel: "telegram", - to: "-100123", - accountId: "work", - threadId: "42", - }; - expect(channelRouteIdentityKey(input)).toBe(channelRouteDedupeKey(input)); - }); - it("matches exact routes when numeric and string thread ids are equivalent", () => { expect( channelRoutesMatchExact({ diff --git a/src/plugin-sdk/channel-route.ts b/src/plugin-sdk/channel-route.ts index bd2eb0581e1c..67967d40d6d5 100644 --- a/src/plugin-sdk/channel-route.ts +++ b/src/plugin-sdk/channel-route.ts @@ -204,11 +204,6 @@ export function channelRouteDedupeKey(input?: ChannelRouteTargetInput | null): s ]); } -/** @deprecated Use `channelRouteDedupeKey`. */ -export function channelRouteIdentityKey(input?: ChannelRouteTargetInput | null): string { - return channelRouteDedupeKey(input); -} - function threadIdsEqual(left?: string | number, right?: string | number): boolean { const normalizedLeft = stringifyRouteThreadId(left); const normalizedRight = stringifyRouteThreadId(right); @@ -323,8 +318,3 @@ export function channelRouteCompactKey(route?: ChannelRouteKeyInput | null): str stringifyRouteThreadId(normalized.thread?.id) ?? "", ].join("|"); } - -/** @deprecated Use `channelRouteCompactKey`. */ -export function channelRouteKey(route?: ChannelRouteRef): string | undefined { - return channelRouteCompactKey(route); -} diff --git a/src/plugin-sdk/channel-runtime.ts b/src/plugin-sdk/channel-runtime.ts deleted file mode 100644 index f6c513769318..000000000000 --- a/src/plugin-sdk/channel-runtime.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @deprecated Compatibility shim only. Keep old plugins working, but do not - * add new imports here and do not use this subpath from repo code. - * Prefer the dedicated generic plugin-sdk subpaths instead. - */ - -export * from "../channels/chat-type.js"; -export * from "../channels/reply-prefix.js"; -export * from "../channels/typing.js"; -export type * from "../channels/plugins/types.public.js"; -export { normalizeChannelId } from "../channels/plugins/registry.js"; -export * from "../channels/plugins/outbound/interactive.js"; -export * from "../polls.js"; -export { enqueueSystemEvent, resetSystemEventsForTest } from "../infra/system-events.js"; -export { recordChannelActivity } from "../infra/channel-activity.js"; -export * from "../infra/heartbeat-events.ts"; -export * from "../infra/heartbeat-visibility.ts"; -export * from "../infra/transport-ready.js"; -export { - createAccountStatusSink, - keepHttpServerTaskAlive, - waitUntilAbort, -} from "./channel-lifecycle.core.js"; diff --git a/src/plugin-sdk/command-auth.test.ts b/src/plugin-sdk/command-auth.test.ts index 5c7683e31169..c7cebb31d4b7 100644 --- a/src/plugin-sdk/command-auth.test.ts +++ b/src/plugin-sdk/command-auth.test.ts @@ -3,12 +3,12 @@ */ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; +import { resolveSenderCommandAuthorization } from "./command-auth.js"; import { buildCommandsMessage, buildCommandsMessagePaginated, buildHelpMessage, - resolveSenderCommandAuthorization, -} from "./command-auth.js"; +} from "./command-status.js"; const baseCfg = { commands: { useAccessGroups: true }, @@ -39,7 +39,7 @@ async function resolveAuthorization(params: { } describe("plugin-sdk/command-auth", () => { - it("keeps deprecated command status builders available for compatibility", () => { + it("keeps command status builders on their focused subpath", () => { const cfg = { commands: { config: false, debug: false } } as unknown as OpenClawConfig; expect(buildHelpMessage(cfg)).toContain("/commands for full list"); diff --git a/src/plugin-sdk/command-auth.ts b/src/plugin-sdk/command-auth.ts index 7ac302c68133..67013a86e64b 100644 --- a/src/plugin-sdk/command-auth.ts +++ b/src/plugin-sdk/command-auth.ts @@ -4,11 +4,6 @@ * instead of this broad compatibility surface. */ -import { - buildCommandsMessage as buildCommandsMessageCompat, - buildCommandsMessagePaginated as buildCommandsMessagePaginatedCompat, - buildHelpMessage as buildHelpMessageCompat, -} from "../auto-reply/command-status-builders.js"; import type { ChannelId } from "../channels/plugins/types.public.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { @@ -294,24 +289,3 @@ export async function resolveSenderCommandAuthorization( commandAuthorized, }; } - -/** @deprecated Use `openclaw/plugin-sdk/command-status` instead. */ -export function buildCommandsMessage( - ...args: Parameters -): ReturnType { - return buildCommandsMessageCompat(...args); -} - -/** @deprecated Use `openclaw/plugin-sdk/command-status` instead. */ -export function buildCommandsMessagePaginated( - ...args: Parameters -): ReturnType { - return buildCommandsMessagePaginatedCompat(...args); -} - -/** @deprecated Use `openclaw/plugin-sdk/command-status` instead. */ -export function buildHelpMessage( - ...args: Parameters -): ReturnType { - return buildHelpMessageCompat(...args); -} diff --git a/src/plugin-sdk/command-gating.ts b/src/plugin-sdk/command-gating.ts deleted file mode 100644 index 8c83a63a770f..000000000000 --- a/src/plugin-sdk/command-gating.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Public SDK subpath for command authorization and control-command gating. - */ -export type { - CommandAuthorizer, - CommandGatingModeWhenAccessGroupsOff, -} from "../channels/command-gating.js"; -export { - resolveCommandAuthorizedFromAuthorizers, - resolveControlCommandGate, - resolveDualTextControlCommandGate, -} from "../channels/command-gating.js"; diff --git a/src/plugin-sdk/compat.ts b/src/plugin-sdk/compat.ts deleted file mode 100644 index 24ff9d75d4b6..000000000000 --- a/src/plugin-sdk/compat.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @deprecated Legacy compat surface for external plugins that still depend on - * older broad plugin-sdk imports. Use focused openclaw/plugin-sdk subpaths - * instead. - */ - -import { - createChannelReplyPipeline as createChannelReplyPipelineCompat, - createReplyPrefixContext as createReplyPrefixContextCompat, - createReplyPrefixOptions as createReplyPrefixOptionsCompat, - createTypingCallbacks as createTypingCallbacksCompat, - resolveChannelSourceReplyDeliveryMode as resolveChannelSourceReplyDeliveryModeCompat, - type ChannelReplyPipeline as ChannelReplyPipelineCompat, - type CreateTypingCallbacksParams as CreateTypingCallbacksParamsCompat, - type ReplyPrefixContext as ReplyPrefixContextCompat, - type ReplyPrefixContextBundle as ReplyPrefixContextBundleCompat, - type ReplyPrefixOptions as ReplyPrefixOptionsCompat, - type SourceReplyDeliveryMode as SourceReplyDeliveryModeCompat, - type TypingCallbacks as TypingCallbacksCompat, -} from "./channel-reply-core.js"; - -const shouldWarnCompatImport = - process.env.VITEST !== "true" && - process.env.NODE_ENV !== "test" && - process.env.OPENCLAW_SUPPRESS_PLUGIN_SDK_COMPAT_WARNING !== "1"; - -if (shouldWarnCompatImport) { - process.emitWarning( - "openclaw/plugin-sdk/compat is deprecated for new plugins. Migrate to focused openclaw/plugin-sdk/ imports. See https://docs.openclaw.ai/plugins/sdk-migration", - { - code: "OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED", - detail: - "Bundled plugins must use scoped plugin-sdk subpaths. External plugins may keep compat temporarily while migrating. Migration guide: https://docs.openclaw.ai/plugins/sdk-migration", - }, - ); -} - -export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; -export type { - MemoryPluginCapability, - MemoryPluginPublicArtifact, - MemoryPluginPublicArtifactsProvider, -} from "../plugins/memory-state.js"; -export { resolveControlCommandGate } from "../channels/command-gating.js"; -export { - buildMemorySystemPromptAddition, - delegateCompactionToRuntime, -} from "../context-engine/delegate.js"; -export { registerContextEngine } from "../context-engine/registry.js"; -export type { DiagnosticEventPayload } from "../infra/diagnostic-events.js"; -export { onDiagnosticEvent } from "../infra/diagnostic-events.js"; -export { optionalStringEnum, stringEnum } from "../agents/schema/typebox.js"; -export { - applyAuthProfileConfig, - buildApiKeyCredential, - upsertApiKeyProfile, - writeOAuthCredentials, - type ApiKeyStorageOptions, - type WriteOAuthCredentialsOptions, -} from "../plugins/provider-auth-helpers.js"; - -export { createAccountStatusSink } from "./channel-lifecycle.core.js"; -export { createPluginRuntimeStore } from "./runtime-store.js"; -export { KeyedAsyncQueue } from "./keyed-async-queue.js"; -export { normalizeAccountId } from "./account-id.js"; -export { resolvePreferredOpenClawTmpDir } from "./temp-path.js"; - -export { - createHybridChannelConfigAdapter, - createHybridChannelConfigBase, - createScopedAccountConfigAccessors, - createScopedChannelConfigAdapter, - createScopedChannelConfigBase, - createScopedDmSecurityResolver, - createTopLevelChannelConfigAdapter, - createTopLevelChannelConfigBase, - mapAllowFromEntries, -} from "./channel-config-helpers.js"; -export { formatAllowFromLowercase, formatNormalizedAllowFromEntries } from "./allow-from.js"; -// Freeze the shipped compat config-schema surface so new focused primitives do -// not silently become deprecated root exports. -export { - AllowFromListSchema, - BlockStreamingCoalesceSchema, - buildCatchallMultiAccountChannelSchema, - buildChannelConfigSchema, - buildGroupEntrySchema, - buildJsonChannelConfigSchema, - buildMultiAccountChannelSchema, - buildNestedDmConfigSchema, - ChannelGroupEntrySchema, - ContextVisibilityModeSchema, - DmConfigSchema, - DmPolicySchema, - GroupPolicySchema, - MarkdownConfigSchema, - MentionPatternsPolicySchema, - ReplyRuntimeConfigSchemaShape, - requireAllowlistAllowFrom, - requireOpenAllowFrom, - ToolPolicySchema, -} from "./channel-config-schema.js"; -export * from "./channel-policy.js"; -export { collectOpenGroupPolicyConfiguredRouteWarnings } from "./channel-policy.js"; -export * from "./reply-history.js"; -// Freeze the shipped compat surface so focused directory-runtime additions do -// not silently become new deprecated root exports. -export { - applyDirectoryQueryAndLimit, - type ChannelDirectoryEntry, - type ChannelDirectoryEntryKind, - collectNormalizedDirectoryIds, - createChannelDirectoryAdapter, - createEmptyChannelDirectoryAdapter, - createInspectedDirectoryEntriesLister, - createResolvedDirectoryEntriesLister, - createRuntimeDirectoryLiveAdapter, - type DirectoryConfigParams, - emptyChannelDirectoryList, - inspectReadOnlyChannelAccount, - listDirectoryEntriesFromSources, - listDirectoryGroupEntriesFromMapKeys, - listDirectoryGroupEntriesFromMapKeysAndAllowFrom, - listDirectoryUserEntriesFromAllowFrom, - listDirectoryUserEntriesFromAllowFromAndMapKeys, - listInspectedDirectoryEntriesFromSources, - listResolvedDirectoryEntriesFromSources, - listResolvedDirectoryGroupEntriesFromMapKeys, - listResolvedDirectoryUserEntriesFromAllowFrom, - nullChannelDirectorySelf, - type ReadOnlyInspectedAccount, - toDirectoryEntries, -} from "./directory-runtime.js"; -export { mapAllowlistResolutionInputs } from "./allow-from.js"; - -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export const createChannelReplyPipeline = createChannelReplyPipelineCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export const createReplyPrefixContext = createReplyPrefixContextCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export const createReplyPrefixOptions = createReplyPrefixOptionsCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export const createTypingCallbacks = createTypingCallbacksCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export const resolveChannelSourceReplyDeliveryMode = resolveChannelSourceReplyDeliveryModeCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export type ChannelReplyPipeline = ChannelReplyPipelineCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export type CreateTypingCallbacksParams = CreateTypingCallbacksParamsCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export type ReplyPrefixContext = ReplyPrefixContextCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export type ReplyPrefixContextBundle = ReplyPrefixContextBundleCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export type ReplyPrefixOptions = ReplyPrefixOptionsCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export type SourceReplyDeliveryMode = SourceReplyDeliveryModeCompat; -/** @deprecated Use `openclaw/plugin-sdk/channel-outbound`. */ -export type TypingCallbacks = TypingCallbacksCompat; diff --git a/src/plugin-sdk/config-runtime.ts b/src/plugin-sdk/config-runtime.ts index 8a7642302d6f..c7183aca3507 100644 --- a/src/plugin-sdk/config-runtime.ts +++ b/src/plugin-sdk/config-runtime.ts @@ -74,11 +74,6 @@ export { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled, } from "../config/commands.js"; -export { - TELEGRAM_COMMAND_NAME_PATTERN, - normalizeTelegramCommandName, - resolveTelegramCustomCommands, -} from "./telegram-command-config.js"; export { resolveActiveTalkProviderConfig } from "../config/talk.js"; export { resolveAgentMaxConcurrent } from "../config/agent-limits.js"; export { loadCronStore, resolveCronStorePath, saveCronStore } from "../cron/store.js"; diff --git a/src/plugin-sdk/config-schema.ts b/src/plugin-sdk/config-schema.ts deleted file mode 100644 index 0b7e61bcb416..000000000000 --- a/src/plugin-sdk/config-schema.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Plugin authors should define plugin-local schemas instead of depending on the - * full root OpenClaw config schema. - */ -export { OpenClawSchema } from "../config/zod-schema.js"; -export { validateJsonSchemaValue } from "../plugins/schema-validator.js"; -export type { JsonSchemaObject } from "../shared/json-schema.types.js"; diff --git a/src/plugin-sdk/config-types.ts b/src/plugin-sdk/config-types.ts deleted file mode 100644 index 022ebb3fbd9f..000000000000 --- a/src/plugin-sdk/config-types.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @deprecated Broad public SDK type barrel. Prefer focused config type - * subpaths or plugin-local config types. - */ - -export type * from "../config/types.js"; -export type { ConfigWriteAfterWrite } from "../config/runtime-snapshot.js"; -export type { ChannelGroupPolicy } from "../config/group-policy.js"; -export type { SessionResetMode } from "../config/sessions/reset.js"; -export type { SessionEntry } from "../config/sessions/types.js"; -export type { SessionScope } from "../config/sessions/types.js"; diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index f21df65991bc..f588cbeded07 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -98,7 +98,6 @@ export type { ProviderCatalogContext, ProviderCatalogResult, ProviderDefaultThinkingPolicyContext, - ProviderDiscoveryContext, ProviderFetchUsageSnapshotContext, ProviderModernModelPolicyContext, ProviderNormalizeResolvedModelContext, diff --git a/src/plugin-sdk/direct-dm-access.ts b/src/plugin-sdk/direct-dm-access.ts deleted file mode 100644 index 4d642f759c1d..000000000000 --- a/src/plugin-sdk/direct-dm-access.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-inbound`. */ -export * from "../channels/direct-dm-access.js"; diff --git a/src/plugin-sdk/direct-dm.test.ts b/src/plugin-sdk/direct-dm.test.ts index 6d7e24e6e2db..dd00bcdf2c50 100644 --- a/src/plugin-sdk/direct-dm.test.ts +++ b/src/plugin-sdk/direct-dm.test.ts @@ -75,7 +75,7 @@ function createDirectDmRuntime() { }; } -describe("plugin-sdk/direct-dm", () => { +describe("channel-inbound direct-message helpers", () => { it("resolves inbound DM access and command auth through one helper", async () => { const result = await resolveInboundDirectDmAccessWithRuntime({ cfg: baseCfg, diff --git a/src/plugin-sdk/direct-dm.ts b/src/plugin-sdk/direct-dm.ts deleted file mode 100644 index ff9a3a439a4a..000000000000 --- a/src/plugin-sdk/direct-dm.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-inbound`. */ -export { - createDirectDmPreCryptoGuardPolicy, - createPreCryptoDirectDmAuthorizer, - dispatchInboundDirectDmWithRuntime, - resolveInboundDirectDmAccessWithRuntime, - type AccessGroupMembershipResolver, - type DirectDmCommandAuthorizationRuntime, - type DirectDmPreCryptoGuardPolicy, - type DirectDmPreCryptoGuardPolicyOverrides, - type ResolvedInboundDirectDmAccess, -} from "../channels/direct-dm.js"; diff --git a/src/plugin-sdk/discord.ts b/src/plugin-sdk/discord.ts index a182cf2fac25..7d6ce0b82e8e 100644 --- a/src/plugin-sdk/discord.ts +++ b/src/plugin-sdk/discord.ts @@ -10,7 +10,7 @@ import type { } from "./channel-contract.js"; import type { ChannelPlugin } from "./channel-core.js"; import type { MessageReceipt } from "./channel-outbound.js"; -import type { OpenClawConfig } from "./config-types.js"; +import type { OpenClawConfig } from "./config-contracts.js"; import { createLazyFacadeObjectValue, loadBundledPluginPublicSurfaceModuleSync, @@ -23,7 +23,7 @@ import { getRuntimeConfig, getRuntimeConfigSnapshot } from "./runtime-config-sna */ export type { ChannelMessageActionAdapter, ChannelMessageActionName } from "./channel-contract.js"; export type { ChannelPlugin } from "./channel-core.js"; -export type { OpenClawConfig } from "./config-types.js"; +export type { OpenClawConfig } from "./config-contracts.js"; export type { OpenClawPluginApi, PluginRuntime } from "./channel-plugin-common.js"; export { diff --git a/src/plugin-sdk/entrypoints.ts b/src/plugin-sdk/entrypoints.ts index 90eb733a80ec..57b201397eb4 100644 --- a/src/plugin-sdk/entrypoints.ts +++ b/src/plugin-sdk/entrypoints.ts @@ -4,11 +4,11 @@ import deprecatedPublicPluginSdkSubpathList from "../../scripts/lib/plugin-sdk-d import pluginSdkEntryList from "../../scripts/lib/plugin-sdk-entrypoints.json" with { type: "json" }; import privateLocalOnlyPluginSdkSubpathList from "../../scripts/lib/plugin-sdk-private-local-only-subpaths.json" with { type: "json" }; -/** All declared SDK entrypoints, including public, deprecated, and local-only subpaths. */ +/** All declared SDK subpath entrypoints, including public and local-only surfaces. */ export const pluginSdkEntrypoints = [...pluginSdkEntryList]; -/** SDK subpaths without the root `openclaw/plugin-sdk` barrel entry. */ -export const pluginSdkSubpaths = pluginSdkEntrypoints.filter((entry) => entry !== "index"); +/** All SDK subpaths; the removed package root is intentionally absent. */ +export const pluginSdkSubpaths = pluginSdkEntrypoints; const privateLocalOnlyPluginSdkSubpathSet = new Set( privateLocalOnlyPluginSdkSubpathList.filter( @@ -23,13 +23,11 @@ export const privateLocalOnlyPluginSdkEntrypoints = pluginSdkSubpaths.filter((en /** Entrypoints exported by the published package for third-party plugin imports. */ export const publicPluginSdkEntrypoints = pluginSdkEntrypoints.filter( - (entry) => entry === "index" || !privateLocalOnlyPluginSdkSubpathSet.has(entry), + (entry) => !privateLocalOnlyPluginSdkSubpathSet.has(entry), ); -/** Published SDK subpaths, excluding the root barrel. */ -export const publicPluginSdkSubpaths = publicPluginSdkEntrypoints.filter( - (entry) => entry !== "index", -); +/** Published SDK subpaths. */ +export const publicPluginSdkSubpaths = publicPluginSdkEntrypoints; /** Public SDK subpaths that remain importable but are marked deprecated in docs/contracts. */ export const deprecatedPublicPluginSdkEntrypoints = publicPluginSdkSubpaths.filter((entry) => @@ -46,53 +44,19 @@ export const deprecatedBarrelPluginSdkEntrypoints = pluginSdkSubpaths.filter((en * * Cross-owner extension imports are blocked by package contract guardrails. */ -export const reservedBundledPluginSdkEntrypoints = ["codex-mcp-projection"] as const; +export const reservedBundledPluginSdkEntrypoints = [] as const; /** * Supported SDK facades backed by bundled plugins until generic contracts replace them. */ export const supportedBundledFacadeSdkEntrypoints = [ "discord", - "lmstudio", - "lmstudio-runtime", "matrix", - "mattermost", - "memory-core-engine-runtime", - "provider-zai-endpoint", - "qa-runner-runtime", "telegram-account", - "tts-runtime", - "zalouser", ] as const; /** Plugin-owned surfaces intentionally public and documented for third-party plugins. */ -export const publicPluginOwnedSdkEntrypoints = [ - "browser-config", - "image-generation-core", - "memory-core", - "memory-core-host-embedding-registry", - "memory-core-host-engine-embeddings", - "memory-core-host-engine-foundation", - "memory-core-host-engine-qmd", - "memory-core-host-engine-storage", - "memory-core-host-events", - "memory-core-host-multimodal", - "memory-core-host-query", - "memory-core-host-runtime-cli", - "memory-core-host-runtime-core", - "memory-core-host-runtime-files", - "memory-core-host-secret", - "memory-core-host-status", - "memory-host-core", - "memory-host-events", - "memory-host-files", - "memory-host-markdown", - "memory-host-search", - "memory-host-status", - "speech-core", - "telegram-command-config", - "video-generation-core", -] as const; +export const publicPluginOwnedSdkEntrypoints = ["memory-core-host-engine-foundation"] as const; /** Map every SDK entrypoint name to its source file path inside the repo. */ export function buildPluginSdkEntrySources(entries: readonly string[] = pluginSdkEntrypoints) { @@ -103,7 +67,7 @@ export function buildPluginSdkEntrySources(entries: readonly string[] = pluginSd export function buildPluginSdkPackageExports() { return Object.fromEntries( publicPluginSdkEntrypoints.map((entry) => [ - entry === "index" ? "./plugin-sdk" : `./plugin-sdk/${entry}`, + `./plugin-sdk/${entry}`, { types: `./dist/plugin-sdk/${entry}.d.ts`, default: `./dist/plugin-sdk/${entry}.js`, diff --git a/src/plugin-sdk/extension-shared.ts b/src/plugin-sdk/extension-shared.ts index bb523e806066..382c7466c528 100644 --- a/src/plugin-sdk/extension-shared.ts +++ b/src/plugin-sdk/extension-shared.ts @@ -7,7 +7,7 @@ import { resolveActiveManagedProxyTlsOptions } from "../infra/net/proxy/managed- import { resolveDefaultSecretProviderAlias } from "../secrets/ref-contract.js"; import { createDeferred as createSharedDeferred } from "../shared/deferred.js"; import { runPassiveAccountLifecycle } from "./channel-lifecycle.core.js"; -import { createLoggerBackedRuntime } from "./runtime-logger.js"; +import { createLoggerBackedRuntime } from "./runtime-logger.internal.js"; export { safeParseJsonWithSchema, safeParseWithSchema } from "../utils/zod-parse.js"; export { buildTimeoutAbortSignal } from "../utils/fetch-timeout.js"; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts deleted file mode 100644 index 39e458e19857..000000000000 --- a/src/plugin-sdk/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Shared root plugin-sdk surface. -// Keep this entry intentionally tiny. Channel/provider helpers belong on -// dedicated subpaths or, for legacy consumers, the compat surface. - -export type { - ChannelAccountSnapshot, - ChannelAgentTool, - ChannelMessageActionAdapter, -} from "../channels/plugins/types.public.js"; -export type { ChannelGatewayContext } from "../channels/plugins/types.adapters.js"; -export type { ChannelConfigSchema } from "../channels/plugins/types.config.js"; -export type { ChannelSetupInput } from "../channels/plugins/types.public.js"; -export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js"; - -export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; - -export type { ChannelSetupWizard } from "../channels/plugins/setup-wizard-types.js"; -export type { - AnyAgentTool, - OpenClawPluginApi, - OpenClawPluginConfigSchema, - PluginLogger, -} from "../plugins/types.js"; - -export type { PluginRuntime } from "../plugins/runtime/types.js"; - -export type { OpenClawConfig } from "../config/config.js"; -/** @deprecated Use OpenClawConfig instead */ -export type { OpenClawConfig as ClawdbotConfig } from "../config/config.js"; - -export type { SecretInput } from "../config/types.secrets.js"; -export type { RuntimeEnv } from "../runtime.js"; - -export type { ReplyPayload } from "./reply-payload.js"; -export type { WizardPrompter } from "../wizard/prompts.js"; - -export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; -export { optionalStringEnum, stringEnum } from "../agents/schema/string-enum.js"; -export { assertContextEngineHostSupport } from "../context-engine/host-compat.js"; -export { - buildMemorySystemPromptAddition, - delegateCompactionToRuntime, -} from "../context-engine/delegate.js"; -export { registerContextEngine } from "../context-engine/registry.js"; -export { - ContextEngineRuntimeSettingsUnavailableError, - ContextEngineRuntimeSettingsUnsupportedError, -} from "../context-engine/types.js"; -export { onDiagnosticEvent } from "../infra/diagnostic-events.js"; -/** @deprecated Use OpenClawConfig instead */ -export type { OpenClawConfig as OpenClawSchemaType } from "../config/config.js"; diff --git a/src/plugin-sdk/lmstudio-runtime.test.ts b/src/plugin-sdk/lmstudio-runtime.test.ts deleted file mode 100644 index 6592e2a92030..000000000000 --- a/src/plugin-sdk/lmstudio-runtime.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -// LM Studio runtime tests cover provider runtime setup, auth status, and model discovery. -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); - -vi.mock("./facade-runtime.js", async () => { - const actual = await vi.importActual("./facade-runtime.js"); - return { - ...actual, - loadBundledPluginPublicSurfaceModuleSync, - }; -}); - -describe("plugin-sdk lmstudio-runtime", () => { - beforeEach(() => { - loadBundledPluginPublicSurfaceModuleSync.mockReset(); - }); - - it("keeps the lmstudio runtime facade cold until a helper is used", async () => { - const module = await import("./lmstudio-runtime.js"); - - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); - expect(module.LMSTUDIO_PROVIDER_ID).toBe("lmstudio"); - expect(module.LMSTUDIO_DEFAULT_EMBEDDING_MODEL).toBe("text-embedding-nomic-embed-text-v1.5"); - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); - }); - - it("delegates lmstudio helpers through the bundled runtime facade", async () => { - const resolveLmstudioInferenceBase = vi.fn().mockReturnValue("http://localhost:1234/v1"); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ - resolveLmstudioInferenceBase, - }); - - const module = await import("./lmstudio-runtime.js"); - - expect(module.resolveLmstudioInferenceBase("http://localhost:1234/api/v1/")).toBe( - "http://localhost:1234/v1", - ); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ - dirName: "lmstudio", - artifactBasename: "runtime-api.js", - }); - expect(resolveLmstudioInferenceBase).toHaveBeenCalledWith("http://localhost:1234/api/v1/"); - }); -}); diff --git a/src/plugin-sdk/lmstudio-runtime.ts b/src/plugin-sdk/lmstudio-runtime.ts deleted file mode 100644 index 84c45eb49dee..000000000000 --- a/src/plugin-sdk/lmstudio-runtime.ts +++ /dev/null @@ -1,204 +0,0 @@ -// Manual facade. Keep loader boundary explicit. -import type { - ModelDefinitionConfig, - ModelProviderConfig, - OpenClawConfig, -} from "../config/types.js"; -import { - createLazyFacadeValue as createLazyFacadeRuntimeValue, - loadBundledPluginPublicSurfaceModuleSync, -} from "./facade-runtime.js"; - -type LmstudioReasoningCapabilityWire = { - allowed_options?: unknown; - default?: unknown; -}; - -/** Raw model entry returned by LM Studio's local model catalog endpoints. */ -export type LmstudioModelWire = { - type?: "llm" | "embedding"; - key?: string; - display_name?: string; - max_context_length?: number; - format?: "gguf" | "mlx" | null; - capabilities?: { - vision?: boolean; - trained_for_tool_use?: boolean; - reasoning?: LmstudioReasoningCapabilityWire; - }; - loaded_instances?: Array<{ - id?: string; - config?: { - context_length?: number; - } | null; - } | null>; -}; - -/** Normalized model metadata used by OpenClaw provider catalogs. */ -export type LmstudioModelBase = { - id: string; - displayName: string; - format: "gguf" | "mlx" | null; - vision: boolean; - trainedForToolUse: boolean; - loaded: boolean; - reasoning: boolean; - input: Array<"text" | "image">; - cost: ModelDefinitionConfig["cost"]; - contextWindow: number; - contextTokens: number; - maxTokens: number; -}; - -/** Result from probing LM Studio model discovery without throwing on unreachable servers. */ -export type FetchLmstudioModelsResult = { - reachable: boolean; - status?: number; - models: LmstudioModelWire[]; - error?: unknown; -}; - -type FacadeModule = { - LMSTUDIO_DEFAULT_BASE_URL: string; - LMSTUDIO_DEFAULT_INFERENCE_BASE_URL: string; - LMSTUDIO_DEFAULT_EMBEDDING_MODEL: string; - LMSTUDIO_PROVIDER_LABEL: string; - LMSTUDIO_DEFAULT_API_KEY_ENV_VAR: string; - LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER: string; - LMSTUDIO_MODEL_PLACEHOLDER: string; - LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH: number; - LMSTUDIO_DEFAULT_MODEL_ID: string; - LMSTUDIO_PROVIDER_ID: string; - resolveLmstudioReasoningCapability: (entry: Pick) => boolean; - resolveLoadedContextWindow: (entry: Pick) => number | null; - resolveLmstudioServerBase: (configuredBaseUrl?: string) => string; - resolveLmstudioInferenceBase: (configuredBaseUrl?: string) => string; - normalizeLmstudioProviderConfig: (provider: ModelProviderConfig) => ModelProviderConfig; - fetchLmstudioModels: (params?: { - baseUrl?: string; - apiKey?: string; - headers?: Record; - ssrfPolicy?: unknown; - timeoutMs?: number; - fetchImpl?: typeof fetch; - }) => Promise; - mapLmstudioWireEntry: (entry: LmstudioModelWire) => LmstudioModelBase | null; - discoverLmstudioModels: (params?: { - config?: OpenClawConfig; - baseUrl?: string; - apiKey?: string; - headers?: Record; - }) => Promise; - ensureLmstudioModelLoaded: (params: Record) => Promise; - buildLmstudioAuthHeaders: (params: { - apiKey?: string; - json?: boolean; - headers?: Record; - }) => Record | undefined; - resolveLmstudioConfiguredApiKey: (params: { - config?: OpenClawConfig; - env?: NodeJS.ProcessEnv; - path?: string; - }) => Promise; - resolveLmstudioProviderHeaders: (params: { - config?: OpenClawConfig; - env?: NodeJS.ProcessEnv; - headers?: unknown; - path?: string; - }) => Promise | undefined>; - resolveLmstudioRequestContext: (params: { - config?: OpenClawConfig; - env?: NodeJS.ProcessEnv; - headers?: unknown; - providerHeaders?: unknown; - path?: string; - }) => Promise<{ - apiKey?: string; - headers?: Record; - }>; - resolveLmstudioRuntimeApiKey: (params: { - config?: OpenClawConfig; - agentDir?: string; - env?: NodeJS.ProcessEnv; - headers?: unknown; - }) => Promise; -}; - -function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ - dirName: "lmstudio", - artifactBasename: "runtime-api.js", - }); -} - -// Keep defaults inline so importing the runtime facade stays cold until a helper -// is actually used. These values are part of the public LM Studio contract. -/** Default local LM Studio server base URL. */ -export const LMSTUDIO_DEFAULT_BASE_URL: FacadeModule["LMSTUDIO_DEFAULT_BASE_URL"] = - "http://localhost:1234"; -/** Default OpenAI-compatible inference base derived from the local LM Studio server URL. */ -export const LMSTUDIO_DEFAULT_INFERENCE_BASE_URL: FacadeModule["LMSTUDIO_DEFAULT_INFERENCE_BASE_URL"] = `${LMSTUDIO_DEFAULT_BASE_URL}/v1`; -/** Default embedding model id advertised by LM Studio setup helpers. */ -export const LMSTUDIO_DEFAULT_EMBEDDING_MODEL: FacadeModule["LMSTUDIO_DEFAULT_EMBEDDING_MODEL"] = - "text-embedding-nomic-embed-text-v1.5"; -/** Human-readable provider label for LM Studio catalogs and setup output. */ -export const LMSTUDIO_PROVIDER_LABEL: FacadeModule["LMSTUDIO_PROVIDER_LABEL"] = "LM Studio"; -/** Environment variable checked for LM Studio API tokens. */ -export const LMSTUDIO_DEFAULT_API_KEY_ENV_VAR: FacadeModule["LMSTUDIO_DEFAULT_API_KEY_ENV_VAR"] = - "LM_API_TOKEN"; -/** Placeholder token used for local LM Studio servers that accept any API key. */ -export const LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER: FacadeModule["LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER"] = - "lmstudio-local"; -/** Placeholder model id shown when setup needs a model from `/api/v1/models`. */ -export const LMSTUDIO_MODEL_PLACEHOLDER: FacadeModule["LMSTUDIO_MODEL_PLACEHOLDER"] = - "model-key-from-api-v1-models"; -/** Default context length requested when loading LM Studio models. */ -export const LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH: FacadeModule["LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH"] = 64000; -/** Default chat model id used when no local LM Studio model has been selected. */ -export const LMSTUDIO_DEFAULT_MODEL_ID: FacadeModule["LMSTUDIO_DEFAULT_MODEL_ID"] = - "qwen/qwen3.5-9b"; -/** Stable provider id used in OpenClaw config and provider catalogs. */ -export const LMSTUDIO_PROVIDER_ID: FacadeModule["LMSTUDIO_PROVIDER_ID"] = "lmstudio"; - -/** Resolve whether an LM Studio wire entry advertises reasoning support. */ -export const resolveLmstudioReasoningCapability: FacadeModule["resolveLmstudioReasoningCapability"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLmstudioReasoningCapability"); -/** Resolve context-window metadata from currently loaded LM Studio instances. */ -export const resolveLoadedContextWindow: FacadeModule["resolveLoadedContextWindow"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLoadedContextWindow"); -/** Normalize a configured LM Studio server base URL. */ -export const resolveLmstudioServerBase: FacadeModule["resolveLmstudioServerBase"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLmstudioServerBase"); -/** Normalize the OpenAI-compatible LM Studio inference base URL. */ -export const resolveLmstudioInferenceBase: FacadeModule["resolveLmstudioInferenceBase"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLmstudioInferenceBase"); -/** Normalize an LM Studio provider config before runtime use. */ -export const normalizeLmstudioProviderConfig: FacadeModule["normalizeLmstudioProviderConfig"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "normalizeLmstudioProviderConfig"); -/** Fetch raw LM Studio model entries with SSRF and timeout handling owned by the facade. */ -export const fetchLmstudioModels: FacadeModule["fetchLmstudioModels"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "fetchLmstudioModels"); -/** Map one raw LM Studio model entry into OpenClaw model metadata. */ -export const mapLmstudioWireEntry: FacadeModule["mapLmstudioWireEntry"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "mapLmstudioWireEntry"); -/** Discover OpenClaw model definitions from an LM Studio server. */ -export const discoverLmstudioModels: FacadeModule["discoverLmstudioModels"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "discoverLmstudioModels"); -/** Ensure a specific LM Studio model is loaded before use. */ -export const ensureLmstudioModelLoaded: FacadeModule["ensureLmstudioModelLoaded"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "ensureLmstudioModelLoaded"); -/** Build request headers for LM Studio calls from optional API key and caller headers. */ -export const buildLmstudioAuthHeaders: FacadeModule["buildLmstudioAuthHeaders"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "buildLmstudioAuthHeaders"); -/** Resolve the configured LM Studio API key from config, env, or profile path. */ -export const resolveLmstudioConfiguredApiKey: FacadeModule["resolveLmstudioConfiguredApiKey"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLmstudioConfiguredApiKey"); -/** Resolve provider headers for LM Studio catalog and runtime requests. */ -export const resolveLmstudioProviderHeaders: FacadeModule["resolveLmstudioProviderHeaders"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLmstudioProviderHeaders"); -/** Resolve the combined API key and headers used for LM Studio requests. */ -export const resolveLmstudioRequestContext: FacadeModule["resolveLmstudioRequestContext"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLmstudioRequestContext"); -/** Resolve the runtime API key for an agent-scoped LM Studio request. */ -export const resolveLmstudioRuntimeApiKey: FacadeModule["resolveLmstudioRuntimeApiKey"] = - createLazyFacadeRuntimeValue(loadFacadeModule, "resolveLmstudioRuntimeApiKey"); diff --git a/src/plugin-sdk/lmstudio.ts b/src/plugin-sdk/lmstudio.ts deleted file mode 100644 index 9d1ba63a532b..000000000000 --- a/src/plugin-sdk/lmstudio.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Public SDK facade for LM Studio provider config, discovery, and auth helpers. - */ -import type { OpenClawConfig } from "../config/types.js"; -import type { - ProviderAuthMethodNonInteractiveContext, - ProviderAuthResult, - ProviderCatalogContext, - ProviderPrepareDynamicModelContext, - ProviderRuntimeModel, -} from "../plugins/types.js"; -import type { WizardPrompter } from "../wizard/prompts.js"; - -export type { - OpenClawPluginApi, - ProviderAuthContext, - ProviderAuthMethodNonInteractiveContext, - ProviderAuthResult, - ProviderCatalogContext, - ProviderDiscoveryContext, - ProviderPrepareDynamicModelContext, - ProviderRuntimeModel, -} from "../plugins/types.js"; -export type { LmstudioModelBase, LmstudioModelWire } from "./lmstudio-runtime.js"; -export { - LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, - LMSTUDIO_DEFAULT_BASE_URL, - LMSTUDIO_DEFAULT_EMBEDDING_MODEL, - LMSTUDIO_DEFAULT_INFERENCE_BASE_URL, - LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH, - LMSTUDIO_DEFAULT_MODEL_ID, - LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER, - LMSTUDIO_MODEL_PLACEHOLDER, - LMSTUDIO_PROVIDER_ID, - LMSTUDIO_PROVIDER_LABEL, - buildLmstudioAuthHeaders, - discoverLmstudioModels, - ensureLmstudioModelLoaded, - fetchLmstudioModels, - mapLmstudioWireEntry, - normalizeLmstudioProviderConfig, - resolveLoadedContextWindow, - resolveLmstudioConfiguredApiKey, - resolveLmstudioInferenceBase, - resolveLmstudioProviderHeaders, - resolveLmstudioReasoningCapability, - resolveLmstudioRuntimeApiKey, - resolveLmstudioServerBase, -} from "./lmstudio-runtime.js"; - -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js"; - -type LmstudioInteractiveParams = { - config: OpenClawConfig; - prompter?: WizardPrompter; - secretInputMode?: unknown; - allowSecretRefPrompt?: boolean; - promptText?: (params: { - message: string; - initialValue?: string; - placeholder?: string; - validate?: (value: string | undefined) => string | undefined; - }) => Promise; - note?: (message: string, title?: string) => Promise | void; -}; - -type FacadeModule = { - promptAndConfigureLmstudioInteractive: ( - params: LmstudioInteractiveParams, - ) => Promise; - configureLmstudioNonInteractive: ( - ctx: ProviderAuthMethodNonInteractiveContext, - ) => Promise; - discoverLmstudioProvider: ( - ctx: ProviderCatalogContext, - ) => Promise<{ provider: import("../config/types.js").ModelProviderConfig } | null>; - prepareLmstudioDynamicModels: ( - ctx: ProviderPrepareDynamicModelContext, - ) => Promise; -}; - -function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ - dirName: "lmstudio", - artifactBasename: "api.js", - }); -} - -/** Prompts for LM Studio configuration through the activated bundled provider facade. */ -export const promptAndConfigureLmstudioInteractive: FacadeModule["promptAndConfigureLmstudioInteractive"] = - ((...args) => - loadFacadeModule().promptAndConfigureLmstudioInteractive( - ...args, - )) as FacadeModule["promptAndConfigureLmstudioInteractive"]; -/** Applies non-interactive LM Studio auth/configuration through the provider facade. */ -export const configureLmstudioNonInteractive: FacadeModule["configureLmstudioNonInteractive"] = (( - ...args -) => - loadFacadeModule().configureLmstudioNonInteractive( - ...args, - )) as FacadeModule["configureLmstudioNonInteractive"]; -/** Discovers LM Studio provider config through the activated provider facade. */ -export const discoverLmstudioProvider: FacadeModule["discoverLmstudioProvider"] = ((...args) => - loadFacadeModule().discoverLmstudioProvider(...args)) as FacadeModule["discoverLmstudioProvider"]; -/** Prepares dynamic LM Studio models through the activated provider facade. */ -export const prepareLmstudioDynamicModels: FacadeModule["prepareLmstudioDynamicModels"] = (( - ...args -) => - loadFacadeModule().prepareLmstudioDynamicModels( - ...args, - )) as FacadeModule["prepareLmstudioDynamicModels"]; diff --git a/src/plugin-sdk/mattermost.ts b/src/plugin-sdk/mattermost.ts deleted file mode 100644 index 6ce697296c05..000000000000 --- a/src/plugin-sdk/mattermost.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @deprecated Compatibility facade for older third-party channel packages that - * imported the previous Mattermost-shaped helper bundle. New plugins should - * import the generic SDK subpaths directly. - */ -export { resolveControlCommandGate } from "./command-auth.js"; -export { formatPairingApproveHint } from "./channel-plugin-common.js"; -export type { HistoryEntry } from "./reply-history.js"; -export { - createChannelHistoryWindow, - buildPendingHistoryContextFromMap, - clearHistoryEntriesIfEnabled, - recordPendingHistoryEntryIfEnabled, -} from "./reply-history.js"; diff --git a/src/plugin-sdk/media-generation-runtime-shared.ts b/src/plugin-sdk/media-generation-runtime-shared.ts deleted file mode 100644 index ad99e2110a52..000000000000 --- a/src/plugin-sdk/media-generation-runtime-shared.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Prefer capability-specific generation core/runtime subpaths. - */ - -export { - buildNoCapabilityModelConfiguredMessage, - resolveCapabilityModelCandidates, - throwCapabilityGenerationFailure, -} from "../media-generation/runtime-shared.js"; diff --git a/src/plugin-sdk/memory-core-bundled-runtime.test.ts b/src/plugin-sdk/memory-core-bundled-runtime.test.ts index 798ed3dbe374..f8eca2aa0607 100644 --- a/src/plugin-sdk/memory-core-bundled-runtime.test.ts +++ b/src/plugin-sdk/memory-core-bundled-runtime.test.ts @@ -8,6 +8,10 @@ const configureMemoryCoreDreamingStateImpl = vi.hoisted(() => vi.fn()); const createEmbeddingProviderImpl = vi.hoisted(() => vi.fn()); const removeGroundedShortTermCandidatesImpl = vi.hoisted(() => vi.fn()); const loadShortTermPromotionDreamingStatsImpl = vi.hoisted(() => vi.fn()); +const auditDreamingArtifactsImpl = vi.hoisted(() => vi.fn()); +const auditShortTermPromotionArtifactsImpl = vi.hoisted(() => vi.fn()); +const repairDreamingArtifactsImpl = vi.hoisted(() => vi.fn()); +const repairShortTermPromotionArtifactsImpl = vi.hoisted(() => vi.fn()); const previewGroundedRemMarkdownImpl = vi.hoisted(() => vi.fn()); const writeBackfillDiaryEntriesImpl = vi.hoisted(() => vi.fn()); const removeBackfillDiaryEntriesImpl = vi.hoisted(() => vi.fn()); @@ -28,6 +32,10 @@ describe("plugin-sdk memory-core bundled runtime", () => { createEmbeddingProviderImpl.mockReset().mockResolvedValue({ provider: { id: "openai" } }); removeGroundedShortTermCandidatesImpl.mockReset().mockResolvedValue({ removed: 1 }); loadShortTermPromotionDreamingStatsImpl.mockReset().mockResolvedValue({ shortTermCount: 0 }); + auditDreamingArtifactsImpl.mockReset().mockResolvedValue({ issues: [] }); + auditShortTermPromotionArtifactsImpl.mockReset().mockResolvedValue({ issues: [] }); + repairDreamingArtifactsImpl.mockReset().mockResolvedValue({ changed: false }); + repairShortTermPromotionArtifactsImpl.mockReset().mockResolvedValue({ changed: false }); previewGroundedRemMarkdownImpl.mockReset().mockResolvedValue({ files: [] }); writeBackfillDiaryEntriesImpl.mockReset().mockResolvedValue({ writtenCount: 1 }); removeBackfillDiaryEntriesImpl.mockReset().mockResolvedValue({ removedCount: 1 }); @@ -42,6 +50,10 @@ describe("plugin-sdk memory-core bundled runtime", () => { createEmbeddingProvider: createEmbeddingProviderImpl, removeGroundedShortTermCandidates: removeGroundedShortTermCandidatesImpl, loadShortTermPromotionDreamingStats: loadShortTermPromotionDreamingStatsImpl, + auditDreamingArtifacts: auditDreamingArtifactsImpl, + auditShortTermPromotionArtifacts: auditShortTermPromotionArtifactsImpl, + repairDreamingArtifacts: repairDreamingArtifactsImpl, + repairShortTermPromotionArtifacts: repairShortTermPromotionArtifactsImpl, }; } if (artifactBasename === "api.js") { @@ -79,10 +91,18 @@ describe("plugin-sdk memory-core bundled runtime", () => { await module.previewGroundedRemMarkdown({} as never); await module.removeGroundedShortTermCandidates({} as never); await module.loadShortTermPromotionDreamingStats({} as never); + await module.auditDreamingArtifacts({} as never); + await module.auditShortTermPromotionArtifacts({} as never); + await module.repairDreamingArtifacts({} as never); + await module.repairShortTermPromotionArtifacts({} as never); expect(previewGroundedRemMarkdownImpl).toHaveBeenCalledWith({} as never); expect(removeGroundedShortTermCandidatesImpl).toHaveBeenCalledWith({} as never); expect(loadShortTermPromotionDreamingStatsImpl).toHaveBeenCalledWith({} as never); + expect(auditDreamingArtifactsImpl).toHaveBeenCalledWith({} as never); + expect(auditShortTermPromotionArtifactsImpl).toHaveBeenCalledWith({} as never); + expect(repairDreamingArtifactsImpl).toHaveBeenCalledWith({} as never); + expect(repairShortTermPromotionArtifactsImpl).toHaveBeenCalledWith({} as never); expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ dirName: "memory-core", artifactBasename: "api.js", diff --git a/src/plugin-sdk/memory-core-bundled-runtime.ts b/src/plugin-sdk/memory-core-bundled-runtime.ts index 8d87c5becde5..eb585cafb42f 100644 --- a/src/plugin-sdk/memory-core-bundled-runtime.ts +++ b/src/plugin-sdk/memory-core-bundled-runtime.ts @@ -20,6 +20,71 @@ type EmbeddingProviderResult = { runtime?: MemoryEmbeddingProviderRuntime; }; +export type DreamingArtifactsAuditIssue = { + severity: "warn" | "error"; + code: + | "dreaming-session-corpus-unreadable" + | "dreaming-session-corpus-self-ingested" + | "dreaming-session-ingestion-unreadable" + | "dreaming-diary-unreadable"; + message: string; + fixable: boolean; +}; + +export type DreamingArtifactsAuditSummary = { + dreamsPath?: string; + sessionCorpusDir: string; + sessionCorpusFileCount: number; + suspiciousSessionCorpusFileCount: number; + suspiciousSessionCorpusLineCount: number; + sessionIngestionPath: string; + sessionIngestionExists: boolean; + issues: DreamingArtifactsAuditIssue[]; +}; + +export type ShortTermAuditIssue = { + severity: "warn" | "error"; + code: + | "recall-store-unreadable" + | "recall-store-empty" + | "recall-store-invalid" + | "recall-store-over-limit" + | "recall-lock-stale" + | "recall-lock-unreadable" + | "qmd-index-missing" + | "qmd-index-empty" + | "qmd-collections-empty"; + message: string; + fixable: boolean; +}; + +export type ShortTermAuditSummary = { + storePath: string; + lockPath: string; + updatedAt?: string; + exists: boolean; + entryCount: number; + promotedCount: number; + spacedEntryCount: number; + conceptTaggedEntryCount: number; + conceptTagScripts?: Record; + invalidEntryCount: number; + issues: ShortTermAuditIssue[]; + qmd?: { + dbPath?: string; + collections?: number; + dbBytes?: number; + }; +}; + +export type RepairShortTermPromotionArtifactsResult = { + changed: boolean; + removedInvalidEntries: number; + removedOverflowEntries?: number; + rewroteStore: boolean; + removedStaleLock: boolean; +}; + type RuntimeFacadeModule = { configureMemoryCoreDreamingState: ( openKeyedStore: (options: OpenKeyedStoreOptions) => PluginStateKeyedStore, @@ -38,11 +103,24 @@ type RuntimeFacadeModule = { nowMs: number; timezone?: string; }) => Promise; + auditDreamingArtifacts: (params: { + workspaceDir: string; + }) => Promise; + auditShortTermPromotionArtifacts: (params: { + workspaceDir: string; + qmd?: { + dbPath?: string; + collections?: number; + }; + }) => Promise; repairDreamingArtifacts: (params: { workspaceDir: string; archiveDiary?: boolean; now?: Date; }) => Promise; + repairShortTermPromotionArtifacts: (params: { + workspaceDir: string; + }) => Promise; }; type GroundedRemPreviewItem = { @@ -205,7 +283,7 @@ type ApiFacadeModule = { }) => Promise; }; -type RepairDreamingArtifactsResult = { +export type RepairDreamingArtifactsResult = { changed: boolean; archiveDir?: string; archivedDreamsDiary: boolean; @@ -262,11 +340,28 @@ export const loadShortTermPromotionDreamingStats: RuntimeFacadeModule["loadShort loadRuntimeFacadeModule().loadShortTermPromotionDreamingStats( ...args, )) as RuntimeFacadeModule["loadShortTermPromotionDreamingStats"]; +/** Audit dreaming diary and session-corpus artifacts through the bundled runtime facade. */ +export const auditDreamingArtifacts: RuntimeFacadeModule["auditDreamingArtifacts"] = ((...args) => + loadRuntimeFacadeModule().auditDreamingArtifacts( + ...args, + )) as RuntimeFacadeModule["auditDreamingArtifacts"]; +/** Audit short-term promotion artifacts through the bundled runtime facade. */ +export const auditShortTermPromotionArtifacts: RuntimeFacadeModule["auditShortTermPromotionArtifacts"] = + ((...args) => + loadRuntimeFacadeModule().auditShortTermPromotionArtifacts( + ...args, + )) as RuntimeFacadeModule["auditShortTermPromotionArtifacts"]; /** Repair or archive problematic dreaming artifacts through the bundled runtime facade. */ export const repairDreamingArtifacts: RuntimeFacadeModule["repairDreamingArtifacts"] = ((...args) => loadRuntimeFacadeModule().repairDreamingArtifacts( ...args, )) as RuntimeFacadeModule["repairDreamingArtifacts"]; +/** Repair short-term promotion artifacts through the bundled runtime facade. */ +export const repairShortTermPromotionArtifacts: RuntimeFacadeModule["repairShortTermPromotionArtifacts"] = + ((...args) => + loadRuntimeFacadeModule().repairShortTermPromotionArtifacts( + ...args, + )) as RuntimeFacadeModule["repairShortTermPromotionArtifacts"]; /** Preview grounded REM markdown facts and candidates for selected input files. */ export const previewGroundedRemMarkdown: ApiFacadeModule["previewGroundedRemMarkdown"] = (( diff --git a/src/plugin-sdk/memory-core-engine-runtime.test.ts b/src/plugin-sdk/memory-core-engine-runtime.test.ts deleted file mode 100644 index 6a103352268e..000000000000 --- a/src/plugin-sdk/memory-core-engine-runtime.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Tests memory core engine runtime facade behavior. - */ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { ShortTermAuditIssue } from "./memory-core-engine-runtime.js"; - -const loadActivatedBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); -const getMemorySearchManagerImpl = vi.hoisted(() => vi.fn(async () => ({ manager: null }))); -const getMemoryIndexManagerImpl = vi.hoisted(() => vi.fn(async () => null)); - -vi.mock("./facade-runtime.js", async () => { - const actual = await vi.importActual("./facade-runtime.js"); - return { - ...actual, - loadActivatedBundledPluginPublicSurfaceModuleSync, - }; -}); - -describe("memory-core engine runtime SDK facade", () => { - beforeEach(() => { - getMemorySearchManagerImpl.mockClear(); - getMemoryIndexManagerImpl.mockClear(); - loadActivatedBundledPluginPublicSurfaceModuleSync.mockReset().mockReturnValue({ - configureMemoryCoreDreamingState: vi.fn(), - getMemorySearchManager: getMemorySearchManagerImpl, - MemoryIndexManager: { - get: getMemoryIndexManagerImpl, - }, - }); - }); - - it("exposes the short-term recall overflow audit code", () => { - const issue = { - severity: "warn", - code: "recall-store-over-limit", - message: "Short-term recall store is over the retention limit.", - fixable: true, - } satisfies ShortTermAuditIssue; - - expect(issue.code).toBe("recall-store-over-limit"); - }); - - it("injects local-service acquisition and SQLite leases into manager facade calls", async () => { - const runtime = await import("./memory-core-engine-runtime.js"); - const params = { cfg: {}, agentId: "main" } as never; - - await runtime.getMemorySearchManager(params); - await runtime.MemoryIndexManager.get(params); - - expect(getMemorySearchManagerImpl).toHaveBeenCalledWith({ - cfg: {}, - agentId: "main", - acquireLocalService: expect.any(Function), - withLease: expect.any(Function), - }); - expect(getMemoryIndexManagerImpl).toHaveBeenCalledWith({ - cfg: {}, - agentId: "main", - acquireLocalService: expect.any(Function), - withLease: expect.any(Function), - }); - }); -}); diff --git a/src/plugin-sdk/memory-core-engine-runtime.ts b/src/plugin-sdk/memory-core-engine-runtime.ts deleted file mode 100644 index 2ca347165af1..000000000000 --- a/src/plugin-sdk/memory-core-engine-runtime.ts +++ /dev/null @@ -1,221 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Prefer vendor-neutral memory-host SDK subpaths for new plugin code. - */ -import { createConfiguredProviderLocalServiceAcquirer } from "../agents/provider-local-service.js"; -import { getRuntimeConfig } from "../config/config.js"; -import type { OpenClawConfig } from "../config/types.js"; -import { withPluginStateLease } from "../plugin-state/plugin-state-lease.js"; -import { createPluginStateKeyedStore } from "../plugin-state/plugin-state-store.js"; -import { loadActivatedBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js"; -import type { MemorySearchManager } from "./memory-core-host-engine-storage.js"; -import type { - OpenKeyedStoreOptions, - PluginStateKeyedStore, - PluginStateLeaseRunner, -} from "./plugin-state-runtime.js"; - -/** Doctor metadata for a built-in memory embedding provider. */ -export type BuiltinMemoryEmbeddingProviderDoctorMetadata = { - providerId: string; - authProviderId: string; - envVars: string[]; - transport: "local" | "remote"; - autoSelectPriority?: number; -}; - -/** One issue found while auditing dreaming/session-corpus artifacts. */ -export type DreamingArtifactsAuditIssue = { - severity: "warn" | "error"; - code: - | "dreaming-session-corpus-unreadable" - | "dreaming-session-corpus-self-ingested" - | "dreaming-session-ingestion-unreadable" - | "dreaming-diary-unreadable"; - message: string; - fixable: boolean; -}; - -/** Summary of dreaming diary and session-corpus artifact health. */ -export type DreamingArtifactsAuditSummary = { - dreamsPath?: string; - sessionCorpusDir: string; - sessionCorpusFileCount: number; - suspiciousSessionCorpusFileCount: number; - suspiciousSessionCorpusLineCount: number; - sessionIngestionPath: string; - sessionIngestionExists: boolean; - issues: DreamingArtifactsAuditIssue[]; -}; - -/** Result from archiving or repairing problematic dreaming artifacts. */ -export type RepairDreamingArtifactsResult = { - changed: boolean; - archiveDir?: string; - archivedDreamsDiary: boolean; - archivedSessionCorpus: boolean; - archivedSessionIngestion: boolean; - archivedPaths: string[]; - warnings: string[]; -}; - -/** One issue found while auditing short-term promotion artifacts. */ -export type ShortTermAuditIssue = { - severity: "warn" | "error"; - code: - | "recall-store-unreadable" - | "recall-store-empty" - | "recall-store-invalid" - | "recall-store-over-limit" - | "recall-lock-stale" - | "recall-lock-unreadable" - | "qmd-index-missing" - | "qmd-index-empty" - | "qmd-collections-empty"; - message: string; - fixable: boolean; -}; - -/** Summary of recall-store and qmd state used by short-term promotion. */ -export type ShortTermAuditSummary = { - storePath: string; - lockPath: string; - updatedAt?: string; - exists: boolean; - entryCount: number; - promotedCount: number; - spacedEntryCount: number; - conceptTaggedEntryCount: number; - conceptTagScripts?: Record; - invalidEntryCount: number; - issues: ShortTermAuditIssue[]; - qmd?: - | { - dbPath?: string; - collections?: number; - dbBytes?: number; - } - | undefined; -}; - -/** Result from repairing invalid recall-store entries or stale short-term locks. */ -export type RepairShortTermPromotionArtifactsResult = { - changed: boolean; - removedInvalidEntries: number; - removedOverflowEntries?: number; - rewroteStore: boolean; - removedStaleLock: boolean; -}; - -type MemoryIndexManagerFacade = { - get(params: { - cfg: OpenClawConfig; - agentId: string; - purpose?: "default" | "status"; - }): Promise; -}; - -type FacadeModule = { - configureMemoryCoreDreamingState: ( - openKeyedStore: (options: OpenKeyedStoreOptions) => PluginStateKeyedStore, - ) => void; - auditShortTermPromotionArtifacts: (params: { - workspaceDir: string; - qmd?: { - dbPath?: string; - collections?: number; - }; - }) => Promise; - auditDreamingArtifacts: (params: { - workspaceDir: string; - }) => Promise; - getBuiltinMemoryEmbeddingProviderDoctorMetadata: ( - providerId: string, - ) => BuiltinMemoryEmbeddingProviderDoctorMetadata | null; - getMemorySearchManager: (params: { - cfg: OpenClawConfig; - agentId: string; - purpose?: "default" | "status" | "cli"; - }) => Promise<{ - manager: MemorySearchManager | null; - error?: string; - }>; - listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata: () => Array; - MemoryIndexManager: MemoryIndexManagerFacade; - repairShortTermPromotionArtifacts: (params: { - workspaceDir: string; - }) => Promise; - repairDreamingArtifacts: (params: { - workspaceDir: string; - archiveDiary?: boolean; - now?: Date; - }) => Promise; -}; - -function loadFacadeModule(): FacadeModule { - const module = loadActivatedBundledPluginPublicSurfaceModuleSync({ - dirName: "memory-core", - artifactBasename: "runtime-api.js", - }); - module.configureMemoryCoreDreamingState((options: OpenKeyedStoreOptions) => - createPluginStateKeyedStore("memory-core", options), - ); - return module; -} - -const acquireLocalService = createConfiguredProviderLocalServiceAcquirer(getRuntimeConfig); -const withLease: PluginStateLeaseRunner = (options, run) => - withPluginStateLease("memory-core", options, run); -/** Audit short-term promotion artifacts in an agent workspace. */ -export const auditShortTermPromotionArtifacts: FacadeModule["auditShortTermPromotionArtifacts"] = (( - ...args -) => - loadFacadeModule()["auditShortTermPromotionArtifacts"]( - ...args, - )) as FacadeModule["auditShortTermPromotionArtifacts"]; -/** Audit dreaming diary and session-corpus artifacts in an agent workspace. */ -export const auditDreamingArtifacts: FacadeModule["auditDreamingArtifacts"] = ((...args) => - loadFacadeModule()["auditDreamingArtifacts"](...args)) as FacadeModule["auditDreamingArtifacts"]; -/** Resolve doctor metadata for one built-in memory embedding provider. */ -export const getBuiltinMemoryEmbeddingProviderDoctorMetadata: FacadeModule["getBuiltinMemoryEmbeddingProviderDoctorMetadata"] = - ((...args) => - loadFacadeModule()["getBuiltinMemoryEmbeddingProviderDoctorMetadata"]( - ...args, - )) as FacadeModule["getBuiltinMemoryEmbeddingProviderDoctorMetadata"]; -/** Resolve the active memory search manager and any runtime availability error. */ -export const getMemorySearchManager: FacadeModule["getMemorySearchManager"] = ((params) => { - const managerParams = { - ...params, - acquireLocalService, - withLease, - }; - return loadFacadeModule()["getMemorySearchManager"](managerParams); -}) as FacadeModule["getMemorySearchManager"]; -/** List built-in memory embedding providers eligible for automatic selection. */ -export const listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata: FacadeModule["listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata"] = - ((...args) => - loadFacadeModule()["listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata"]( - ...args, - )) as FacadeModule["listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata"]; -/** Lazy memory index manager facade used by status and runtime callers. */ -export const MemoryIndexManager: FacadeModule["MemoryIndexManager"] = { - async get(params) { - const managerParams = { - ...params, - acquireLocalService, - withLease, - }; - return await loadFacadeModule()["MemoryIndexManager"].get(managerParams); - }, -}; -/** Repair invalid recall-store entries and stale short-term promotion locks. */ -export const repairShortTermPromotionArtifacts: FacadeModule["repairShortTermPromotionArtifacts"] = - ((...args) => - loadFacadeModule()["repairShortTermPromotionArtifacts"]( - ...args, - )) as FacadeModule["repairShortTermPromotionArtifacts"]; -/** Repair or archive problematic dreaming artifacts. */ -export const repairDreamingArtifacts: FacadeModule["repairDreamingArtifacts"] = ((...args) => - loadFacadeModule()["repairDreamingArtifacts"]( - ...args, - )) as FacadeModule["repairDreamingArtifacts"]; diff --git a/src/plugin-sdk/memory-core-host-events.ts b/src/plugin-sdk/memory-core-host-events.ts deleted file mode 100644 index 8e1c48a6b723..000000000000 --- a/src/plugin-sdk/memory-core-host-events.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @deprecated Compatibility alias. Import memory event helpers from - * `openclaw/plugin-sdk/memory-host-events` instead. - */ - -export { - appendMemoryHostEvent, - MEMORY_HOST_EVENT_LOG_RELATIVE_PATH, - readMemoryHostEventRecords, - readMemoryHostEvents, - resolveMemoryHostEventLogPath, -} from "../memory-host-sdk/events.js"; -export type { - MemoryDreamOutcome, - MemoryHostDreamCompletedEvent, - MemoryHostEvent, - MemoryHostEventRecord, - MemoryHostPromotionAppliedEvent, - MemoryHostRecallRecordedEvent, - MemoryHostRecallSkippedEvent, -} from "../memory-host-sdk/events.js"; diff --git a/src/plugin-sdk/memory-core-host-multimodal.ts b/src/plugin-sdk/memory-core-host-multimodal.ts deleted file mode 100644 index 3c41f0a8cc69..000000000000 --- a/src/plugin-sdk/memory-core-host-multimodal.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Prefer vendor-neutral memory-host SDK subpaths for new plugin code. - */ -export * from "../../packages/memory-host-sdk/src/multimodal.js"; diff --git a/src/plugin-sdk/memory-core-host-query.ts b/src/plugin-sdk/memory-core-host-query.ts deleted file mode 100644 index cbf9ffb15080..000000000000 --- a/src/plugin-sdk/memory-core-host-query.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Prefer vendor-neutral memory-host SDK subpaths for new plugin code. - */ -export * from "../../packages/memory-host-sdk/src/query.js"; diff --git a/src/plugin-sdk/memory-core.ts b/src/plugin-sdk/memory-core.ts deleted file mode 100644 index b018742a58b6..000000000000 --- a/src/plugin-sdk/memory-core.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @deprecated Use `openclaw/plugin-sdk/memory-host-core` for vendor-neutral - * memory host helpers. - */ -export * from "./memory-host-core.js"; diff --git a/src/plugin-sdk/memory-host-core.test.ts b/src/plugin-sdk/memory-host-core.test.ts index 3ed306fbb929..0904530a7635 100644 --- a/src/plugin-sdk/memory-host-core.test.ts +++ b/src/plugin-sdk/memory-host-core.test.ts @@ -13,9 +13,8 @@ import { import { clearMemoryPluginState, registerMemoryCapability, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, } from "../plugins/memory-state.test-fixtures.js"; -import * as memoryCoreAlias from "./memory-core.js"; import { buildActiveMemoryPromptSection, listMemoryHostPublicArtifacts, @@ -32,7 +31,7 @@ describe("memory-host-core helpers", () => { }); it("exposes the active memory prompt guidance builder for context engines", () => { - registerMemoryPromptSection(({ citationsMode }) => [ + registerTestMemoryPromptBuilder(({ citationsMode }) => [ "## Memory Recall", `citations=${citationsMode ?? "default"}`, "", @@ -993,9 +992,4 @@ describe("memory-host-core helpers", () => { await fs.rm(fixtureRoot, { recursive: true, force: true }); } }); - - it("keeps the deprecated memory-core alias wired to memory-host-core", () => { - expect(memoryCoreAlias.buildActiveMemoryPromptSection).toBe(buildActiveMemoryPromptSection); - expect(memoryCoreAlias.listActiveMemoryPublicArtifacts).toBe(listActiveMemoryPublicArtifacts); - }); }); diff --git a/src/plugin-sdk/memory-host-files.ts b/src/plugin-sdk/memory-host-files.ts deleted file mode 100644 index 083044a529f9..000000000000 --- a/src/plugin-sdk/memory-host-files.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @deprecated Compatibility alias. Import from - * `openclaw/plugin-sdk/memory-core-host-runtime-files` instead. - */ - -export * from "./memory-core-host-runtime-files.js"; diff --git a/src/plugin-sdk/memory-host-status.ts b/src/plugin-sdk/memory-host-status.ts deleted file mode 100644 index 2e329aa6b3f0..000000000000 --- a/src/plugin-sdk/memory-host-status.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @deprecated Compatibility alias. Import from - * `openclaw/plugin-sdk/memory-core-host-status` instead. - */ - -export * from "./memory-core-host-status.js"; diff --git a/src/plugin-sdk/migration-runtime.ts b/src/plugin-sdk/migration-runtime.ts index 3cd9ba5bfe91..c0549e4d6206 100644 --- a/src/plugin-sdk/migration-runtime.ts +++ b/src/plugin-sdk/migration-runtime.ts @@ -8,11 +8,6 @@ import { resolveAgentConfig } from "../agents/agent-scope-config.js"; import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { ensureAbsoluteDirectory, pathExists, root as openFsSafeRoot } from "../infra/fs-safe.js"; import { resolveHomeRelativePath } from "../infra/home-dir.js"; -import type { - MigrationApplyResult, - MigrationItem, - MigrationProviderContext, -} from "../plugins/types.js"; import { assertMemoryMigrationSourceRevision, MAX_MEMORY_MIGRATION_FILE_BYTES, @@ -24,8 +19,13 @@ import { markMigrationItemError, redactMigrationPlan, } from "./migration.js"; +import type { + MigrationApplyResult, + MigrationItem, + MigrationProviderContext, +} from "./plugin-entry.js"; -export type { MigrationApplyResult, MigrationItem } from "../plugins/types.js"; +export type { MigrationApplyResult, MigrationItem } from "./plugin-entry.js"; /** Directories a migration provider writes imported agent data into. */ export type PlannedMigrationTargets = { diff --git a/src/plugin-sdk/migration.ts b/src/plugin-sdk/migration.ts index aba062c30efa..b025b175593f 100644 --- a/src/plugin-sdk/migration.ts +++ b/src/plugin-sdk/migration.ts @@ -9,7 +9,7 @@ import type { MigrationProviderContext, MigrationProviderPlugin, MigrationSummary, -} from "../plugins/types.js"; +} from "./plugin-entry.js"; export type { MigrationDetection, diff --git a/src/plugin-sdk/music-generation-core.ts b/src/plugin-sdk/music-generation-core.ts deleted file mode 100644 index 759f0374f2fc..000000000000 --- a/src/plugin-sdk/music-generation-core.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Prefer plugin-owned music provider surfaces until a current shared contract - * is needed by bundled extensions. - */ - -export type { AuthProfileStore } from "../agents/auth-profiles/types.js"; -export type { FallbackAttempt } from "../agents/model-fallback.types.js"; -export type { OpenClawConfig } from "../config/types.openclaw.js"; -export type { MusicGenerationProviderPlugin } from "../plugins/types.js"; -export type { - GeneratedMusicAsset, - MusicGenerationOutputFormat, - MusicGenerationProvider, - MusicGenerationProviderCapabilities, - MusicGenerationRequest, - MusicGenerationResult, - MusicGenerationSourceImage, -} from "../music-generation/types.js"; - -export { describeFailoverError, isFailoverError } from "../agents/failover-error.js"; -export { - resolveAgentModelFallbackValues, - resolveAgentModelPrimaryValue, -} from "../config/model-input.js"; -export { createSubsystemLogger } from "../logging/subsystem.js"; -export { parseMusicGenerationModelRef } from "../music-generation/model-ref.js"; -export { - getMusicGenerationProvider, - listMusicGenerationProviders, -} from "../music-generation/provider-registry.js"; -export { getProviderEnvVars } from "../secrets/provider-env-vars.js"; diff --git a/src/plugin-sdk/outbound-runtime.ts b/src/plugin-sdk/outbound-runtime.ts deleted file mode 100644 index 519d3cd0ca32..000000000000 --- a/src/plugin-sdk/outbound-runtime.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-outbound`. */ -export { - buildOutboundSessionContext, - createOutboundPayloadPlan, - createReplyToFanout, - createRuntimeOutboundDelegates, - projectOutboundPayloadPlanForDelivery, - resolveAgentOutboundIdentity, - resolveOutboundSendDep, - sanitizeForPlainText, -} from "./channel-outbound.js"; -export type { - OutboundDeliveryFormattingOptions, - OutboundIdentity, - OutboundSendDeps, - OutboundSessionContext, - ReplyToResolution, -} from "./channel-outbound.js"; - -/** @deprecated Direct outbound delivery is compatibility/runtime substrate. */ -export { deliverOutboundPayloads } from "../infra/outbound/deliver.js"; -/** @deprecated Direct outbound delivery params are compatibility/runtime substrate. */ -export type { DeliverOutboundPayloadsParams } from "../infra/outbound/deliver.js"; -export { type OutboundDeliveryResult } from "../infra/outbound/deliver.js"; diff --git a/src/plugin-sdk/outbound-send-deps.ts b/src/plugin-sdk/outbound-send-deps.ts deleted file mode 100644 index b158c204bb6c..000000000000 --- a/src/plugin-sdk/outbound-send-deps.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** @deprecated Compatibility subpath. Use `openclaw/plugin-sdk/channel-outbound`. */ -export { - resolveLegacyOutboundSendDepKeys, - resolveOutboundSendDep, - type OutboundSendDeps, - type ResolveOutboundSendDepOptions, -} from "../infra/outbound/send-deps.js"; diff --git a/src/plugin-sdk/plugin-entry.ts b/src/plugin-sdk/plugin-entry.ts index 49e49de8a274..f25ae806ccbc 100644 --- a/src/plugin-sdk/plugin-entry.ts +++ b/src/plugin-sdk/plugin-entry.ts @@ -86,7 +86,6 @@ import type { ProviderCatalogResult as _ProviderCatalogResult, ProviderDefaultThinkingPolicyContext as _ProviderDefaultThinkingPolicyContext, ProviderDeferSyntheticProfileAuthContext as _ProviderDeferSyntheticProfileAuthContext, - ProviderDiscoveryContext as _ProviderDiscoveryContext, ProviderFailoverErrorContext as _ProviderFailoverErrorContext, ProviderFetchUsageSnapshotContext as _ProviderFetchUsageSnapshotContext, ProviderModernModelPolicyContext as _ProviderModernModelPolicyContext, @@ -223,7 +222,6 @@ export type ProviderCatalogContext = _ProviderCatalogContext; export type ProviderCatalogResult = _ProviderCatalogResult; export type ProviderDefaultThinkingPolicyContext = _ProviderDefaultThinkingPolicyContext; export type ProviderDeferSyntheticProfileAuthContext = _ProviderDeferSyntheticProfileAuthContext; -export type ProviderDiscoveryContext = _ProviderDiscoveryContext; export type ProviderFailoverErrorContext = _ProviderFailoverErrorContext; export type ProviderFetchUsageSnapshotContext = _ProviderFetchUsageSnapshotContext; export type ProviderModernModelPolicyContext = _ProviderModernModelPolicyContext; diff --git a/src/plugin-sdk/plugin-test-api.ts b/src/plugin-sdk/plugin-test-api.ts index 5256cce82960..1601cfecabef 100644 --- a/src/plugin-sdk/plugin-test-api.ts +++ b/src/plugin-sdk/plugin-test-api.ts @@ -84,12 +84,9 @@ export function createTestPluginApi(api: TestPluginApiInput = {}): OpenClawPlugi scheduleSessionTurn: async () => undefined, unscheduleSessionTurnsByTag: async () => ({ removed: 0, failed: 0 }), registerMemoryCapability() {}, - registerMemoryPromptSection() {}, registerMemoryPromptSupplement() {}, registerMemoryPromptPreparation() {}, registerMemoryCorpusSupplement() {}, - registerMemoryFlushPlan() {}, - registerMemoryRuntime() {}, registerMemoryEmbeddingProvider() {}, resolvePath(input: string) { return input; diff --git a/src/plugin-sdk/provider-auth-login.ts b/src/plugin-sdk/provider-auth-login.ts deleted file mode 100644 index 5488ff4c2b11..000000000000 --- a/src/plugin-sdk/provider-auth-login.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @deprecated Compatibility subpath for provider-owned login helpers. - * Use provider auth hooks instead of importing bundled provider login commands. - */ - -import { createLazyRuntimeMethodBinder, createLazyRuntimeModule } from "../shared/lazy-runtime.js"; - -type ProviderAuthLoginRuntime = typeof import("./provider-auth-login.runtime.js"); - -const loadProviderAuthLoginRuntime = createLazyRuntimeModule( - () => import("./provider-auth-login.runtime.js"), -); -const bindProviderAuthLoginRuntime = createLazyRuntimeMethodBinder(loadProviderAuthLoginRuntime); - -/** @deprecated GitHub Copilot provider-owned login helper; use provider auth hooks instead. */ -export const githubCopilotLoginCommand: ProviderAuthLoginRuntime["githubCopilotLoginCommand"] = - bindProviderAuthLoginRuntime((runtime) => runtime.githubCopilotLoginCommand); -/** @deprecated Chutes provider-owned login helper; use provider auth hooks instead. */ -export const loginChutes: ProviderAuthLoginRuntime["loginChutes"] = bindProviderAuthLoginRuntime( - (runtime) => runtime.loginChutes, -); -/** @deprecated OpenAI Codex provider-owned login helper; use provider auth hooks instead. */ -export const loginOpenAICodexOAuth: ProviderAuthLoginRuntime["loginOpenAICodexOAuth"] = - bindProviderAuthLoginRuntime((runtime) => runtime.loginOpenAICodexOAuth); diff --git a/src/plugin-sdk/provider-auth.ts b/src/plugin-sdk/provider-auth.ts index 4f01cf901a10..76edb6ca81ba 100644 --- a/src/plugin-sdk/provider-auth.ts +++ b/src/plugin-sdk/provider-auth.ts @@ -100,7 +100,6 @@ export { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth export { coerceSecretRef, hasConfiguredSecretInput } from "../config/types.secrets.js"; export { resolveDefaultSecretProviderAlias } from "../secrets/ref-contract.js"; export { resolveRequiredHomeDir } from "../infra/home-dir.js"; -export { resolveOpenClawAgentDir } from "./agent-dir-compat.js"; export { normalizeOptionalSecretInput, normalizeSecretInput, diff --git a/src/plugin-sdk/provider-entry.test.ts b/src/plugin-sdk/provider-entry.test.ts index 4193a7a39a67..841f770b1619 100644 --- a/src/plugin-sdk/provider-entry.test.ts +++ b/src/plugin-sdk/provider-entry.test.ts @@ -178,9 +178,7 @@ describe("defineSingleProviderPluginEntry", () => { }), allowExplicitBaseUrl: true, }, - capabilities: { - transcriptToolCallIdMode: "strict9", - }, + preserveLiteralProviderPrefix: true, }, register(api) { api.registerWebSearchProvider({ @@ -223,7 +221,7 @@ describe("defineSingleProviderPluginEntry", () => { expect(provider?.label).toBe("Gateway"); expect(provider?.aliases).toEqual(["gw"]); expect(provider?.envVars).toEqual(["GATEWAY_KEY", "SECONDARY_KEY"]); - expect(provider?.capabilities?.transcriptToolCallIdMode).toBe("strict9"); + expect(provider?.preserveLiteralProviderPrefix).toBe(true); expect(provider?.auth[0]?.wizard?.choiceId).toBe("gateway-api-key"); expect(provider?.auth[0]?.wizard?.groupId).toBe("shared-gateway"); expect(provider?.auth[0]?.wizard?.groupLabel).toBe("Shared Gateway"); diff --git a/src/plugin-sdk/provider-setup.ts b/src/plugin-sdk/provider-setup.ts index d1d8f8b03ce0..f67aa0d8bb4a 100644 --- a/src/plugin-sdk/provider-setup.ts +++ b/src/plugin-sdk/provider-setup.ts @@ -5,7 +5,6 @@ export type { ProviderAuthMethodNonInteractiveContext, ProviderAuthResult, ProviderCatalogContext, - ProviderDiscoveryContext, ProviderPrepareDynamicModelContext, ProviderRuntimeModel, } from "../plugins/types.js"; diff --git a/src/plugin-sdk/provider-web-search.ts b/src/plugin-sdk/provider-web-search.ts index 4e5e0b45ea97..f386d012d9c2 100644 --- a/src/plugin-sdk/provider-web-search.ts +++ b/src/plugin-sdk/provider-web-search.ts @@ -77,21 +77,3 @@ export type { WebSearchProviderToolDefinition, WebSearchProviderToolExecutionContext, }; - -/** - * @deprecated Implement provider-owned `createTool(...)` directly on the - * returned WebSearchProviderPlugin instead of routing through core. - */ -export function createPluginBackedWebSearchProvider( - provider: WebSearchProviderPlugin, -): WebSearchProviderPlugin { - return { - ...provider, - createTool: () => { - throw new Error( - `createPluginBackedWebSearchProvider(${provider.id}) is no longer supported. ` + - "Define provider-owned createTool(...) directly in the extension's WebSearchProviderPlugin.", - ); - }, - }; -} diff --git a/src/plugin-sdk/provider-zai-endpoint.ts b/src/plugin-sdk/provider-zai-endpoint.ts deleted file mode 100644 index 00611307ccc8..000000000000 --- a/src/plugin-sdk/provider-zai-endpoint.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @deprecated Z.AI provider-owned endpoint detection helper. Use the bundled - * Z.AI plugin public API instead, or keep endpoint probing local to your - * provider plugin. - */ - -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; - -/** Supported Z.AI endpoint families handled by the deprecated endpoint probe. */ -export type ZaiEndpointId = "global" | "cn" | "coding-global" | "coding-cn"; - -/** Result of probing a Z.AI API key against one endpoint family. */ -export type ZaiDetectedEndpoint = { - endpoint: ZaiEndpointId; - baseUrl: string; - modelId: string; - note: string; -}; - -type DetectZaiEndpoint = (params: { - apiKey: string; - endpoint?: ZaiEndpointId; - timeoutMs?: number; - fetchFn?: typeof fetch; -}) => Promise; - -type FacadeModule = { - detectZaiEndpoint: DetectZaiEndpoint; -}; - -function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ - dirName: "zai", - artifactBasename: "api.js", - }); -} - -/** @deprecated Z.AI provider-owned endpoint detection helper. */ -export const detectZaiEndpoint: DetectZaiEndpoint = ((...args) => - loadFacadeModule().detectZaiEndpoint(...args)) as DetectZaiEndpoint; diff --git a/src/plugin-sdk/reply-dedupe.ts b/src/plugin-sdk/reply-dedupe.ts deleted file mode 100644 index 134872008b47..000000000000 --- a/src/plugin-sdk/reply-dedupe.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Narrow reply dedupe helpers for tests and channel runtimes that do not need the full reply runtime. - -export { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js"; diff --git a/src/plugin-sdk/root-alias.cjs b/src/plugin-sdk/root-alias.cjs deleted file mode 100644 index 775c7eb8a4e0..000000000000 --- a/src/plugin-sdk/root-alias.cjs +++ /dev/null @@ -1,750 +0,0 @@ -// CommonJS root alias for plugin SDK and workspace package subpath loading. -"use strict"; - -const path = require("node:path"); -const fs = require("node:fs"); -const os = require("node:os"); - -let monolithicSdk = null; -let diagnosticEventsModule = null; -const moduleLoaders = new Map(); -const pluginSdkSubpathsCache = new Map(); -const pluginSdkPackageNames = ["openclaw/plugin-sdk", "@openclaw/plugin-sdk"]; -const pluginSdkSourceExtensions = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"]; -const privateQaExcludedPluginSdkSubpaths = new Set(["ssrf-runtime-internal"]); -// Subpath -> source entry for private/bundled workspace packages that internal -// source imports by package name. Dist paths mirror each package's export map. -const workspacePackageAliasEntries = { - "@openclaw/llm-core": { - dir: "llm-core", - subpaths: { - "": { srcFile: "src/index.ts", distFile: "dist/index.mjs" }, - diagnostics: { srcFile: "src/utils/diagnostics.ts", distFile: "dist/utils/diagnostics.mjs" }, - "event-stream": { - srcFile: "src/utils/event-stream.ts", - distFile: "dist/utils/event-stream.mjs", - }, - types: { srcFile: "src/types.ts", distFile: "dist/types.mjs" }, - validation: { srcFile: "src/validation.ts", distFile: "dist/validation.mjs" }, - }, - }, - "@openclaw/ai": { - dir: "ai", - subpaths: { - "": { srcFile: "src/index.ts", distFile: "dist/index.mjs" }, - providers: { srcFile: "src/providers.ts", distFile: "dist/providers.mjs" }, - diagnostics: { srcFile: "src/utils/diagnostics.ts", distFile: "dist/diagnostics.mjs" }, - "event-stream": { srcFile: "src/utils/event-stream.ts", distFile: "dist/event-stream.mjs" }, - types: { srcFile: "src/types.ts", distFile: "dist/types.mjs" }, - validation: { srcFile: "src/validation.ts", distFile: "dist/validation.mjs" }, - "internal/anthropic": { - srcFile: "src/internal/anthropic.ts", - distFile: "dist/internal/anthropic.mjs", - }, - "internal/openai": { - srcFile: "src/internal/openai.ts", - distFile: "dist/internal/openai.mjs", - }, - "internal/retry-after": { - srcFile: "src/internal/retry-after.ts", - distFile: "dist/internal/retry-after.mjs", - }, - "internal/runtime": { - srcFile: "src/internal/runtime.ts", - distFile: "dist/internal/runtime.mjs", - }, - "internal/shared": { - srcFile: "src/internal/shared.ts", - distFile: "dist/internal/shared.mjs", - }, - }, - }, - "@openclaw/markdown-core": { - dir: "markdown-core", - subpaths: { - "": { srcFile: "src/index.ts", distFile: "dist/index.mjs" }, - "code-spans": { srcFile: "src/code-spans.ts", distFile: "dist/code-spans.mjs" }, - fences: { srcFile: "src/fences.ts", distFile: "dist/fences.mjs" }, - frontmatter: { srcFile: "src/frontmatter.ts", distFile: "dist/frontmatter.mjs" }, - ir: { srcFile: "src/ir.ts", distFile: "dist/ir.mjs" }, - render: { srcFile: "src/render.ts", distFile: "dist/render.mjs" }, - "render-aware-chunking": { - srcFile: "src/render-aware-chunking.ts", - distFile: "dist/render-aware-chunking.mjs", - }, - tables: { srcFile: "src/tables.ts", distFile: "dist/tables.mjs" }, - types: { srcFile: "src/types.ts", distFile: "dist/types.mjs" }, - }, - }, - "@openclaw/normalization-core": { - dir: "normalization-core", - subpaths: { - "": { srcFile: "src/index.ts", distFile: "dist/index.mjs" }, - "boolean-coercion": { - srcFile: "src/boolean-coercion.ts", - distFile: "dist/boolean-coercion.mjs", - }, - "error-coercion": { - srcFile: "src/error-coercion.ts", - distFile: "dist/error-coercion.mjs", - }, - "number-coercion": { - srcFile: "src/number-coercion.ts", - distFile: "dist/number-coercion.mjs", - }, - "record-coerce": { - srcFile: "src/record-coerce.ts", - distFile: "dist/record-coerce.mjs", - }, - result: { srcFile: "src/result.ts", distFile: "dist/result.mjs" }, - "string-coerce": { - srcFile: "src/string-coerce.ts", - distFile: "dist/string-coerce.mjs", - }, - "string-normalization": { - srcFile: "src/string-normalization.ts", - distFile: "dist/string-normalization.mjs", - }, - "utf16-slice": { - srcFile: "src/utf16-slice.ts", - distFile: "dist/utf16-slice.mjs", - }, - }, - }, - "@openclaw/retry": { - dir: "retry", - subpaths: { - "": { srcFile: "src/index.ts", distFile: "dist/index.mjs" }, - }, - }, -}; -const workspacePackageAliases = Object.entries(workspacePackageAliasEntries).flatMap( - ([name, pkg]) => - Object.entries(pkg.subpaths).map(([subpath, files]) => ({ - name, - dir: pkg.dir, - subpath, - srcFile: files.srcFile, - distFile: files.distFile, - })), -); -const DIAGNOSTIC_EVENTS_STATE_KEY = Symbol.for("openclaw.diagnosticEvents.state.v1"); -const isDistRootAlias = __filename.includes( - `${path.sep}dist${path.sep}plugin-sdk${path.sep}root-alias.cjs`, -); -// Source plugin entry loading must stay on the source graph end-to-end. Mixing a -// source root alias with dist compat/runtime shims can split singleton deps -// (for example matrix-js-sdk) across two module graphs. -const shouldPreferSourceGraph = - !isDistRootAlias && - (process.env.NODE_ENV !== "production" || - Boolean(process.env.VITEST) || - process.env.OPENCLAW_PLUGIN_SDK_SOURCE_IN_TESTS === "1"); - -function emptyPluginConfigSchema() { - function error(message) { - return { success: false, error: { issues: [{ path: [], message }] } }; - } - - return { - safeParse(value) { - if (value === undefined) { - return { success: true, data: undefined }; - } - if (!value || typeof value !== "object" || Array.isArray(value)) { - return error("expected config object"); - } - if (Object.keys(value).length > 0) { - return error("config must be empty"); - } - return { success: true, data: value }; - }, - jsonSchema: { - type: "object", - additionalProperties: false, - properties: {}, - }, - }; -} - -function resolveCommandAuthorizedFromAuthorizers(params) { - const { useAccessGroups, authorizers } = params; - const mode = params.modeWhenAccessGroupsOff ?? "allow"; - if (!useAccessGroups) { - if (mode === "allow") { - return true; - } - if (mode === "deny") { - return false; - } - const anyConfigured = authorizers.some((entry) => entry.configured); - if (!anyConfigured) { - return true; - } - return authorizers.some((entry) => entry.configured && entry.allowed); - } - return authorizers.some((entry) => entry.configured && entry.allowed); -} - -function resolveControlCommandGate(params) { - const commandAuthorized = resolveCommandAuthorizedFromAuthorizers({ - useAccessGroups: params.useAccessGroups, - authorizers: params.authorizers, - modeWhenAccessGroupsOff: params.modeWhenAccessGroupsOff, - }); - const shouldBlock = params.allowTextCommands && params.hasControlCommand && !commandAuthorized; - return { commandAuthorized, shouldBlock }; -} - -function createDiagnosticEventsState() { - return { - marker: DIAGNOSTIC_EVENTS_STATE_KEY, - enabled: true, - seq: 0, - listeners: new Set(), - dispatchDepth: 0, - asyncQueue: [], - asyncDrainScheduled: false, - asyncDroppedEvents: 0, - asyncDroppedTrustedEvents: 0, - asyncDroppedUntrustedEvents: 0, - asyncDroppedPriorityEvents: 0, - }; -} - -function isDiagnosticEventsState(value) { - return ( - value && - typeof value === "object" && - value.marker === DIAGNOSTIC_EVENTS_STATE_KEY && - typeof value.enabled === "boolean" && - typeof value.seq === "number" && - value.listeners instanceof Set && - typeof value.dispatchDepth === "number" && - Array.isArray(value.asyncQueue) && - typeof value.asyncDrainScheduled === "boolean" - ); -} - -function getDiagnosticEventsState(create) { - const existing = globalThis[DIAGNOSTIC_EVENTS_STATE_KEY]; - if (isDiagnosticEventsState(existing)) { - existing.asyncDroppedEvents ??= 0; - existing.asyncDroppedTrustedEvents ??= 0; - existing.asyncDroppedUntrustedEvents ??= 0; - existing.asyncDroppedPriorityEvents ??= 0; - return existing; - } - if (!create) { - return null; - } - const state = createDiagnosticEventsState(); - Object.defineProperty(globalThis, DIAGNOSTIC_EVENTS_STATE_KEY, { - configurable: true, - enumerable: false, - value: state, - writable: false, - }); - return state; -} - -function onDiagnosticEventFromSharedState(listener) { - const state = getDiagnosticEventsState(true); - const internalListener = (event, metadata) => { - if (metadata && metadata.trusted) { - return; - } - if (event && event.type === "log.record") { - return; - } - listener(event); - }; - state.listeners.add(internalListener); - return () => { - state.listeners.delete(internalListener); - }; -} - -function snapshotDiagnosticListeners(state) { - return state && state.listeners instanceof Set ? new Set(state.listeners) : null; -} - -function removeAddedDiagnosticListeners(beforeListeners) { - const state = getDiagnosticEventsState(false); - if (!state || !(state.listeners instanceof Set)) { - return; - } - if (!beforeListeners) { - state.listeners.clear(); - return; - } - for (const listener of state.listeners) { - if (!beforeListeners.has(listener)) { - state.listeners.delete(listener); - } - } -} - -function trySubscribeDiagnosticEvents(diagnosticEvents, listener, beforeListeners) { - try { - const unsubscribe = diagnosticEvents.onDiagnosticEvent(listener); - if (typeof unsubscribe === "function") { - return unsubscribe; - } - } catch { - // Fall back to shared state if a stale dist chunk exposes a broken wrapper. - } - removeAddedDiagnosticListeners(beforeListeners); - return null; -} - -function onDiagnosticEvent(listener) { - const beforeState = getDiagnosticEventsState(false); - const beforeListeners = snapshotDiagnosticListeners(beforeState); - const beforeSize = beforeState?.listeners?.size; - const diagnosticEvents = loadDiagnosticEventsModule(); - if (!diagnosticEvents || typeof diagnosticEvents.onDiagnosticEvent !== "function") { - return onDiagnosticEventFromSharedState(listener); - } - const unsubscribeDiagnosticEvents = trySubscribeDiagnosticEvents( - diagnosticEvents, - listener, - beforeListeners, - ); - if (!unsubscribeDiagnosticEvents) { - return onDiagnosticEventFromSharedState(listener); - } - const afterState = getDiagnosticEventsState(false); - if (afterState && afterState.listeners.size > (beforeSize ?? 0)) { - return unsubscribeDiagnosticEvents; - } - // Keep legacy root listeners connected when a built alias resolves the lazy - // diagnostic module in a separate graph from the active core emitter. - const unsubscribeSharedState = onDiagnosticEventFromSharedState(listener); - return () => { - try { - unsubscribeDiagnosticEvents(); - } finally { - unsubscribeSharedState(); - } - }; -} - -function getPackageRoot() { - return path.resolve(__dirname, "..", ".."); -} - -function findDistChunkByPrefix(prefix) { - const distRoot = path.join(getPackageRoot(), "dist"); - try { - const entries = fs - .readdirSync(distRoot, { withFileTypes: true }) - .toSorted((left, right) => left.name.localeCompare(right.name)); - const match = entries.find( - (entry) => - entry.isFile() && entry.name.startsWith(`${prefix}-`) && entry.name.endsWith(".js"), - ); - return match ? path.join(distRoot, match.name) : null; - } catch { - return null; - } -} - -function listPluginSdkExportedSubpaths() { - const packageRoot = getPackageRoot(); - const cacheKey = `${packageRoot}::privateQa=${process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI === "1" ? "1" : "0"}`; - if (pluginSdkSubpathsCache.has(cacheKey)) { - return pluginSdkSubpathsCache.get(cacheKey); - } - - let subpaths; - try { - const packageJsonPath = path.join(packageRoot, "package.json"); - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); - subpaths = Object.keys(packageJson.exports ?? {}) - .filter((key) => key.startsWith("./plugin-sdk/")) - .map((key) => key.slice("./plugin-sdk/".length)) - .filter((subpath) => /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(subpath)) - .toSorted(); - } catch { - subpaths = []; - } - - pluginSdkSubpathsCache.set(cacheKey, subpaths); - return subpaths; -} - -function listPrivateLocalOnlyPluginSdkSubpaths() { - if (process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI !== "1") { - return []; - } - try { - const raw = fs.readFileSync( - path.join(getPackageRoot(), "scripts", "lib", "plugin-sdk-private-local-only-subpaths.json"), - "utf8", - ); - const parsed = JSON.parse(raw); - if (!Array.isArray(parsed)) { - return []; - } - return parsed.filter( - (subpath) => - typeof subpath === "string" && - /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(subpath) && - !privateQaExcludedPluginSdkSubpaths.has(subpath), - ); - } catch { - return []; - } -} - -function listPluginSdkRootAliasSubpaths() { - const exportedSubpaths = listPluginSdkExportedSubpaths(); - return [...new Set([...exportedSubpaths, ...listPrivateLocalOnlyPluginSdkSubpaths()])].toSorted( - (left, right) => left.localeCompare(right), - ); -} - -function buildPluginSdkAliasMap(useDist) { - const packageRoot = getPackageRoot(); - const pluginSdkDir = path.join(packageRoot, useDist ? "dist" : "src", "plugin-sdk"); - const normalizeTarget = (target) => - process.platform === "win32" ? target.replace(/\\/g, "/") : target; - const aliasMap = {}; - - for (const subpath of listPluginSdkRootAliasSubpaths()) { - if (useDist) { - const candidate = path.join(pluginSdkDir, `${subpath}.js`); - if (fs.existsSync(candidate)) { - for (const packageName of pluginSdkPackageNames) { - aliasMap[`${packageName}/${subpath}`] = normalizeTarget(candidate); - } - } - continue; - } - for (const ext of pluginSdkSourceExtensions) { - const candidate = path.join(pluginSdkDir, `${subpath}${ext}`); - if (!fs.existsSync(candidate)) { - continue; - } - for (const packageName of pluginSdkPackageNames) { - aliasMap[`${packageName}/${subpath}`] = normalizeTarget(candidate); - } - break; - } - } - - // Internal source still imports private workspace packages by package name. - // Keep live source-checkout tests on the same source graph instead of - // requiring prebuilt package dist files. - for (const entry of workspacePackageAliases) { - const alias = entry.subpath ? `${entry.name}/${entry.subpath}` : entry.name; - const preferred = path.join( - packageRoot, - "packages", - entry.dir, - useDist ? entry.distFile : entry.srcFile, - ); - const fallback = path.join( - packageRoot, - "packages", - entry.dir, - useDist ? entry.srcFile : entry.distFile, - ); - const target = fs.existsSync(preferred) ? preferred : fs.existsSync(fallback) ? fallback : null; - if (target) { - aliasMap[alias] = normalizeTarget(target); - } - } - - // Keep the bare root alias last so subpath aliases win under resolvers that - // perform prefix matching instead of exact-key lookup. - for (const packageName of pluginSdkPackageNames) { - aliasMap[packageName] = normalizeTarget(__filename); - } - - return aliasMap; -} - -function sanitizeJitiCachePathSegment(value) { - const normalized = String(value) - .replace(/[^A-Za-z0-9._-]+/g, "_") - .replace(/^_+|_+$/g, ""); - return normalized.length > 0 ? normalized : "unknown"; -} - -function resolveJitiFsCacheTmpDir() { - let tmpDir = os.tmpdir(); - if (process.env.TMPDIR && tmpDir === process.cwd() && !process.env.JITI_RESPECT_TMPDIR_ENV) { - const originalTmpDir = process.env.TMPDIR; - delete process.env.TMPDIR; - try { - tmpDir = os.tmpdir(); - } finally { - process.env.TMPDIR = originalTmpDir; - } - } - return tmpDir; -} - -function readJitiBooleanEnv(name, defaultValue) { - if (!(name in process.env)) { - return defaultValue; - } - try { - return Boolean(JSON.parse(process.env[name] ?? "")); - } catch { - return defaultValue; - } -} - -function shouldUseJitiFsCache() { - return readJitiBooleanEnv("JITI_FS_CACHE", readJitiBooleanEnv("JITI_CACHE", true)); -} - -function resolvePluginSdkJitiFsCacheDir() { - const packageRoot = getPackageRoot(); - const packageJsonPath = path.join(packageRoot, "package.json"); - let version = "unknown"; - let installMarker = "no-package-json"; - try { - const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); - if (typeof parsed.version === "string" && parsed.version.trim().length > 0) { - version = parsed.version; - } - } catch { - // Keep the root alias load path best-effort when package metadata is unavailable. - } - try { - const stat = fs.statSync(packageJsonPath); - installMarker = `${Math.trunc(stat.mtimeMs)}-${stat.size}`; - } catch { - // Package installs should have package.json, but source/test graphs may stub it. - } - return path.join( - resolveJitiFsCacheTmpDir(), - "jiti", - "openclaw", - sanitizeJitiCachePathSegment(version), - sanitizeJitiCachePathSegment(installMarker), - ); -} - -function resolvePluginSdkJitiFsCacheOption() { - return shouldUseJitiFsCache() ? resolvePluginSdkJitiFsCacheDir() : false; -} - -function getModuleLoader(tryNative) { - if (moduleLoaders.has(tryNative)) { - return moduleLoaders.get(tryNative); - } - - const { createJiti } = require("jiti"); - const moduleLoader = createJiti(__filename, { - alias: buildPluginSdkAliasMap(tryNative), - interopDefault: true, - fsCache: resolvePluginSdkJitiFsCacheOption(), - // Prefer Node's native sync ESM loader for built dist/plugin-sdk/*.js files - // so local plugins do not create a second transpiled OpenClaw core graph. - tryNative, - extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"], - }); - moduleLoaders.set(tryNative, moduleLoader); - return moduleLoader; -} - -function loadMonolithicSdk() { - if (monolithicSdk) { - return monolithicSdk; - } - - const distCandidate = path.resolve(__dirname, "..", "..", "dist", "plugin-sdk", "compat.js"); - if (!shouldPreferSourceGraph && fs.existsSync(distCandidate)) { - try { - monolithicSdk = getModuleLoader(true)(distCandidate); - return monolithicSdk; - } catch { - // Fall through to source alias if dist is unavailable or stale. - } - } - - monolithicSdk = getModuleLoader(false)( - path.join(getPackageRoot(), "src", "plugin-sdk", "compat.ts"), - ); - return monolithicSdk; -} - -function loadDiagnosticEventsModule() { - if (diagnosticEventsModule) { - return diagnosticEventsModule; - } - - const directDistCandidate = path.resolve( - __dirname, - "..", - "..", - "dist", - "infra", - "diagnostic-events.js", - ); - if (!shouldPreferSourceGraph) { - const distCandidate = - (fs.existsSync(directDistCandidate) && directDistCandidate) || - findDistChunkByPrefix("diagnostic-events"); - if (distCandidate) { - try { - diagnosticEventsModule = normalizeDiagnosticEventsModule( - getModuleLoader(true)(distCandidate), - ); - return diagnosticEventsModule; - } catch { - // Fall through to source path if dist is unavailable or stale. - } - } - } - - diagnosticEventsModule = normalizeDiagnosticEventsModule( - getModuleLoader(false)(path.join(getPackageRoot(), "src", "infra", "diagnostic-events.ts")), - ); - return diagnosticEventsModule; -} - -function normalizeDiagnosticEventsModule(mod) { - if (!mod || typeof mod !== "object") { - return mod; - } - if (typeof mod.onDiagnosticEvent === "function") { - return mod; - } - const fn = Object.values(mod).find( - (v) => typeof v === "function" && v.name === "onDiagnosticEvent", - ); - if (fn) { - return { - ...mod, - onDiagnosticEvent: fn, - }; - } - return mod; -} - -function tryLoadMonolithicSdk() { - try { - return loadMonolithicSdk(); - } catch { - return null; - } -} - -const fastExports = { - emptyPluginConfigSchema, - onDiagnosticEvent, - resolveControlCommandGate, -}; - -const target = { ...fastExports }; -let rootExports = null; - -function shouldResolveMonolithic(prop) { - if (typeof prop !== "string") { - return false; - } - return prop !== "then"; -} - -function getMonolithicSdk() { - const loaded = tryLoadMonolithicSdk(); - if (loaded && typeof loaded === "object") { - return loaded; - } - return null; -} - -function getExportValue(prop) { - if (Reflect.has(target, prop)) { - return Reflect.get(target, prop); - } - if (!shouldResolveMonolithic(prop)) { - return undefined; - } - const monolithic = getMonolithicSdk(); - if (!monolithic) { - return undefined; - } - return Reflect.get(monolithic, prop); -} - -function getExportDescriptor(prop) { - const ownDescriptor = Reflect.getOwnPropertyDescriptor(target, prop); - if (ownDescriptor) { - return ownDescriptor; - } - if (!shouldResolveMonolithic(prop)) { - return undefined; - } - - const monolithic = getMonolithicSdk(); - if (!monolithic) { - return undefined; - } - - const descriptor = Reflect.getOwnPropertyDescriptor(monolithic, prop); - if (!descriptor) { - return undefined; - } - - // Proxy invariants require descriptors returned for dynamic properties to be configurable. - return { - ...descriptor, - configurable: true, - }; -} - -rootExports = new Proxy(target, { - get(_target, prop, receiver) { - if (Reflect.has(target, prop)) { - return Reflect.get(target, prop, receiver); - } - return getExportValue(prop); - }, - has(_target, prop) { - if (Reflect.has(target, prop)) { - return true; - } - if (!shouldResolveMonolithic(prop)) { - return false; - } - const monolithic = getMonolithicSdk(); - return monolithic ? Reflect.has(monolithic, prop) : false; - }, - ownKeys() { - const keys = new Set(Reflect.ownKeys(target)); - if (monolithicSdk && typeof monolithicSdk === "object") { - for (const key of Reflect.ownKeys(monolithicSdk)) { - if (!keys.has(key)) { - keys.add(key); - } - } - } - return [...keys]; - }, - getOwnPropertyDescriptor(_target, prop) { - return getExportDescriptor(prop); - }, -}); - -Object.defineProperty(target, "__esModule", { - configurable: true, - enumerable: false, - writable: false, - value: true, -}); -Object.defineProperty(target, "default", { - configurable: true, - enumerable: false, - get() { - return rootExports; - }, -}); - -module.exports = rootExports; diff --git a/src/plugin-sdk/runtime-logger.internal.ts b/src/plugin-sdk/runtime-logger.internal.ts new file mode 100644 index 000000000000..080d637bf550 --- /dev/null +++ b/src/plugin-sdk/runtime-logger.internal.ts @@ -0,0 +1,41 @@ +import { format } from "node:util"; +import type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js"; + +type LoggerLike = { + info: (message: string) => void; + error: (message: string) => void; +}; + +export function createLoggerBackedRuntime(params: { + logger: LoggerLike; + exitError?: (code: number) => Error; +}): OutputRuntimeEnv { + return { + log: (...args) => params.logger.info(format(...args)), + error: (...args) => params.logger.error(format(...args)), + writeStdout: (value) => params.logger.info(value), + writeJson: (value, space = 2) => + params.logger.info(JSON.stringify(value, null, space > 0 ? space : undefined)), + exit: (code: number): never => { + throw params.exitError?.(code) ?? new Error(`exit ${code}`); + }, + }; +} + +export function resolveRuntimeEnv(params: { + runtime: RuntimeEnv; + logger: LoggerLike; + exitError?: (code: number) => Error; +}): RuntimeEnv; +export function resolveRuntimeEnv(params: { + runtime?: undefined; + logger: LoggerLike; + exitError?: (code: number) => Error; +}): OutputRuntimeEnv; +export function resolveRuntimeEnv(params: { + runtime?: RuntimeEnv; + logger: LoggerLike; + exitError?: (code: number) => Error; +}): RuntimeEnv | OutputRuntimeEnv { + return params.runtime ?? createLoggerBackedRuntime(params); +} diff --git a/src/plugin-sdk/runtime-logger.ts b/src/plugin-sdk/runtime-logger.ts deleted file mode 100644 index a268e0cb9260..000000000000 --- a/src/plugin-sdk/runtime-logger.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @deprecated Compatibility subpath. Import logger/runtime helpers from - * `openclaw/plugin-sdk/runtime` instead. - */ - -import { format } from "node:util"; -import type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js"; - -/** Minimal logger contract accepted by runtime-adapter helpers. */ -type LoggerLike = { - info: (message: string) => void; - error: (message: string) => void; -}; - -/** @deprecated Import from `openclaw/plugin-sdk/runtime` instead. */ -export function createLoggerBackedRuntime(params: { - logger: LoggerLike; - exitError?: (code: number) => Error; -}): OutputRuntimeEnv { - return { - log: (...args) => { - params.logger.info(format(...args)); - }, - error: (...args) => { - params.logger.error(format(...args)); - }, - writeStdout: (value) => { - params.logger.info(value); - }, - writeJson: (value, space = 2) => { - params.logger.info(JSON.stringify(value, null, space > 0 ? space : undefined)); - }, - exit: (code: number): never => { - throw params.exitError?.(code) ?? new Error(`exit ${code}`); - }, - }; -} - -/** @deprecated Import from `openclaw/plugin-sdk/runtime` instead. */ -export function resolveRuntimeEnv(params: { - runtime: RuntimeEnv; - logger: LoggerLike; - exitError?: (code: number) => Error; -}): RuntimeEnv; -/** @deprecated Import from `openclaw/plugin-sdk/runtime` instead. */ -export function resolveRuntimeEnv(params: { - runtime?: undefined; - logger: LoggerLike; - exitError?: (code: number) => Error; -}): OutputRuntimeEnv; -export function resolveRuntimeEnv(params: { - runtime?: RuntimeEnv; - logger: LoggerLike; - exitError?: (code: number) => Error; -}): RuntimeEnv | OutputRuntimeEnv { - return params.runtime ?? createLoggerBackedRuntime(params); -} - -/** @deprecated Import from `openclaw/plugin-sdk/runtime` instead. */ -export function resolveRuntimeEnvWithUnavailableExit(params: { - runtime: RuntimeEnv; - logger: LoggerLike; - unavailableMessage?: string; -}): RuntimeEnv; -/** @deprecated Import from `openclaw/plugin-sdk/runtime` instead. */ -export function resolveRuntimeEnvWithUnavailableExit(params: { - runtime?: undefined; - logger: LoggerLike; - unavailableMessage?: string; -}): OutputRuntimeEnv; -export function resolveRuntimeEnvWithUnavailableExit(params: { - runtime?: RuntimeEnv; - logger: LoggerLike; - unavailableMessage?: string; -}): RuntimeEnv | OutputRuntimeEnv { - if (params.runtime) { - return resolveRuntimeEnv({ - runtime: params.runtime, - logger: params.logger, - exitError: () => new Error(params.unavailableMessage ?? "Runtime exit not available"), - }); - } - return resolveRuntimeEnv({ - logger: params.logger, - exitError: () => new Error(params.unavailableMessage ?? "Runtime exit not available"), - }); -} diff --git a/src/plugin-sdk/runtime-secret-resolution.ts b/src/plugin-sdk/runtime-secret-resolution.ts deleted file mode 100644 index bc9903edddfb..000000000000 --- a/src/plugin-sdk/runtime-secret-resolution.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @deprecated Compatibility subpath. Import command secret helpers from - * `openclaw/plugin-sdk/runtime` and lower-level secret helpers from - * `openclaw/plugin-sdk/secret-ref-runtime` instead. - */ - -/** @deprecated Import from `openclaw/plugin-sdk/runtime` instead. */ -export { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js"; -/** @deprecated Import from `openclaw/plugin-sdk/runtime` instead. */ -export { getChannelsCommandSecretTargetIds } from "../cli/command-secret-targets.js"; -/** @deprecated Import from `openclaw/plugin-sdk/secret-ref-runtime` instead. */ -export { resolveSecretRefValues } from "../secrets/resolve.js"; -/** @deprecated Import from `openclaw/plugin-sdk/secret-ref-runtime` instead. */ -export { applyResolvedAssignments, createResolverContext } from "../secrets/runtime-shared.js"; diff --git a/src/plugin-sdk/runtime.ts b/src/plugin-sdk/runtime.ts index a507a1ce33ba..da769516d311 100644 --- a/src/plugin-sdk/runtime.ts +++ b/src/plugin-sdk/runtime.ts @@ -1,12 +1,11 @@ -/** - * Public SDK subpath for runtime logging, env, backup, and process helpers. - */ +/** Public SDK subpath for runtime logging, env, backup, and process helpers. */ + export type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js"; export { defaultRuntime } from "../runtime.js"; export { createNonExitingRuntime } from "../runtime.js"; export { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js"; export { getChannelsCommandSecretTargetIds } from "../cli/command-secret-targets.js"; -export { createLoggerBackedRuntime, resolveRuntimeEnv } from "./runtime-logger.js"; +export { createLoggerBackedRuntime, resolveRuntimeEnv } from "./runtime-logger.internal.js"; export { waitForAbortSignal } from "../infra/abort-signal.js"; export { diff --git a/src/plugin-sdk/secret-provider-integration.ts b/src/plugin-sdk/secret-provider-integration.ts deleted file mode 100644 index dc89b049db65..000000000000 --- a/src/plugin-sdk/secret-provider-integration.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Public SDK type surface for plugin-declared secret provider integrations. - */ -export type { PluginManifestSecretProviderIntegration } from "../plugins/manifest.js"; -export type { - SecretProviderIntegrationPreset, - SecretProviderIntegrationResolution, -} from "../secrets/provider-integrations.js"; -export type { PluginIntegrationSecretProviderConfig } from "../config/types.secrets.js"; diff --git a/src/plugin-sdk/self-hosted-provider-setup.ts b/src/plugin-sdk/self-hosted-provider-setup.ts deleted file mode 100644 index 2c5fc2e6c4da..000000000000 --- a/src/plugin-sdk/self-hosted-provider-setup.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Prefer provider-setup or plugin-owned setup helpers for new provider code. - */ -export type { - OpenClawPluginApi, - ProviderAuthContext, - ProviderAuthMethodNonInteractiveContext, - ProviderAuthResult, - ProviderDiscoveryContext, -} from "../plugins/types.js"; - -export { - applyProviderDefaultModel, - configureOpenAICompatibleSelfHostedProviderNonInteractive, - discoverOpenAICompatibleLocalModels, - discoverOpenAICompatibleSelfHostedProvider, - promptAndConfigureOpenAICompatibleSelfHostedProvider, - promptAndConfigureOpenAICompatibleSelfHostedProviderAuth, - SELF_HOSTED_DEFAULT_CONTEXT_WINDOW, - SELF_HOSTED_DEFAULT_COST, - SELF_HOSTED_DEFAULT_MAX_TOKENS, -} from "../plugins/provider-self-hosted-setup.js"; diff --git a/src/plugin-sdk/session-store-runtime.test.ts b/src/plugin-sdk/session-store-runtime.test.ts index 3a00fff91bd8..47522c4245fa 100644 --- a/src/plugin-sdk/session-store-runtime.test.ts +++ b/src/plugin-sdk/session-store-runtime.test.ts @@ -10,7 +10,7 @@ import { replaceSessionEntry as replaceInternalSessionEntry, } from "../config/sessions/session-accessor.js"; import type { InternalSessionEntry } from "../config/sessions/types.js"; -import type { SessionEntry as ConfigSessionEntry } from "./config-types.js"; +import type { SessionEntry as ConfigSessionEntry } from "../config/sessions/types.js"; import { cleanupSessionLifecycleArtifacts, deleteSessionEntry, diff --git a/src/plugin-sdk/setup-adapter-runtime.ts b/src/plugin-sdk/setup-adapter-runtime.ts deleted file mode 100644 index 685252f632f2..000000000000 --- a/src/plugin-sdk/setup-adapter-runtime.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @deprecated Compatibility subpath. Import setup helpers from - * `openclaw/plugin-sdk/setup-runtime` instead. - */ - -/** @deprecated Import from `openclaw/plugin-sdk/setup-runtime` instead. */ -export { createEnvPatchedAccountSetupAdapter } from "../channels/plugins/setup-helpers.js"; diff --git a/src/plugin-sdk/skills-runtime.ts b/src/plugin-sdk/skills-runtime.ts deleted file mode 100644 index 4196a1367800..000000000000 --- a/src/plugin-sdk/skills-runtime.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Runtime SDK subpath for skill snapshot invalidation and refresh listeners. - */ -export { - bumpSkillsSnapshotVersion, - getSkillsSnapshotVersion, - registerSkillsChangeListener, - shouldRefreshSnapshotForVersion, - type SkillsChangeEvent, -} from "../skills/runtime/refresh-state.js"; diff --git a/src/plugin-sdk/telegram-account.ts b/src/plugin-sdk/telegram-account.ts index bed45592cf9a..c0180b88e82f 100644 --- a/src/plugin-sdk/telegram-account.ts +++ b/src/plugin-sdk/telegram-account.ts @@ -1,5 +1,5 @@ // Telegram account helpers resolve Telegram plugin account config and display metadata. -import type { OpenClawConfig } from "./config-types.js"; +import type { OpenClawConfig } from "./config-contracts.js"; import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; /** diff --git a/src/plugin-sdk/telegram-command-config.test.ts b/src/plugin-sdk/telegram-command-config.test.ts deleted file mode 100644 index 5cd438c8bc43..000000000000 --- a/src/plugin-sdk/telegram-command-config.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Tests Telegram command config helpers and command UI behavior. - */ -import { describe, expect, it } from "vitest"; -import * as telegramCommandConfig from "./telegram-command-config.js"; - -describe("telegram command config", () => { - it("exposes the same regex via the helper", () => { - expect(telegramCommandConfig.getTelegramCommandNamePattern()).toBe( - telegramCommandConfig.TELEGRAM_COMMAND_NAME_PATTERN, - ); - expect(telegramCommandConfig.TELEGRAM_COMMAND_NAME_PATTERN.test("hello_world")).toBe(true); - }); - - it("validates and normalizes commands", () => { - expect(telegramCommandConfig.TELEGRAM_COMMAND_NAME_PATTERN.test("hello_world")).toBe(true); - expect(telegramCommandConfig.normalizeTelegramCommandName("/Hello-World")).toBe("hello_world"); - expect(telegramCommandConfig.normalizeTelegramCommandDescription(" hi ")).toBe("hi"); - - expect( - telegramCommandConfig.resolveTelegramCustomCommands({ - commands: [ - { command: "/Hello-World", description: " Says hi " }, - { command: "/Hello-World", description: "duplicate" }, - { command: "", description: "missing command" }, - { command: "/ok", description: "" }, - ], - }), - ).toEqual({ - commands: [{ command: "hello_world", description: "Says hi" }], - issues: [ - { - index: 1, - field: "command", - message: 'Telegram custom command "/hello_world" is duplicated.', - }, - { - index: 2, - field: "command", - message: "Telegram custom command is missing a command name.", - }, - { - index: 3, - field: "description", - message: 'Telegram custom command "/ok" is missing a description.', - }, - ], - }); - }); -}); diff --git a/src/plugin-sdk/telegram-command-config.ts b/src/plugin-sdk/telegram-command-config.ts deleted file mode 100644 index 92d0f576787d..000000000000 --- a/src/plugin-sdk/telegram-command-config.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @deprecated Public SDK subpath has no bundled extension production imports. - * Use plugin-local Telegram command config handling for new plugin code. - */ - -import { - normalizeCommandDescription, - normalizeSlashCommandName, - resolveCustomCommands, -} from "../shared/custom-command-config.js"; - -/** Raw Telegram bot command entry from config. */ -export type TelegramCustomCommandInput = { - /** User-provided command name; leading slash is optional and removed during normalization. */ - command?: string | null; - /** User-provided Bot API description, trimmed before validation. */ - description?: string | null; -}; - -/** Validation issue returned for one Telegram custom command entry. */ -export type TelegramCustomCommandIssue = { - /** Zero-based index of the command entry that failed validation. */ - index: number; - /** Field that should be corrected in the raw command entry. */ - field: "command" | "description"; - /** Operator-facing validation message with the normalized command when available. */ - message: string; -}; -const TELEGRAM_COMMAND_NAME_PATTERN_VALUE = /^[a-z0-9_]{1,32}$/; -const TELEGRAM_CUSTOM_COMMAND_CONFIG = { - label: "Telegram", - pattern: TELEGRAM_COMMAND_NAME_PATTERN_VALUE, - patternDescription: "use a-z, 0-9, underscore; max 32 chars", -} as const; - -function normalizeTelegramCommandNameImpl(value: string): string { - return normalizeSlashCommandName(value); -} - -function normalizeTelegramCommandDescriptionImpl(value: string): string { - return normalizeCommandDescription(value); -} - -function resolveTelegramCustomCommandsImpl(params: { - commands?: TelegramCustomCommandInput[] | null; - reservedCommands?: Set; - checkReserved?: boolean; - checkDuplicates?: boolean; -}): { - commands: Array<{ command: string; description: string }>; - issues: TelegramCustomCommandIssue[]; -} { - // Keep the deprecated SDK subpath on the shared command validator so config, - // doctor, and plugin-local Telegram flows report the same normalized issues. - return resolveCustomCommands({ - ...params, - config: TELEGRAM_CUSTOM_COMMAND_CONFIG, - }); -} - -/** Returns the Telegram command-name regex accepted by Bot API menu commands. */ -export function getTelegramCommandNamePattern(): RegExp { - return TELEGRAM_COMMAND_NAME_PATTERN_VALUE; -} - -/** Telegram Bot API command-name pattern: a-z, 0-9, underscore, max 32 chars. */ -export const TELEGRAM_COMMAND_NAME_PATTERN = TELEGRAM_COMMAND_NAME_PATTERN_VALUE; - -/** Normalizes user-provided Telegram command names into Bot API form. */ -export function normalizeTelegramCommandName( - /** Raw command name; leading slash is optional and dashes become underscores. */ - value: string, -): string { - return normalizeTelegramCommandNameImpl(value); -} - -/** Normalizes Telegram command descriptions for Bot API menu registration. */ -export function normalizeTelegramCommandDescription( - /** Raw command description, trimmed without other text rewriting. */ - value: string, -): string { - return normalizeTelegramCommandDescriptionImpl(value); -} - -/** Validates and normalizes configured Telegram custom commands. */ -export function resolveTelegramCustomCommands(params: { - /** Raw configured commands to normalize and validate in order. */ - commands?: TelegramCustomCommandInput[] | null; - /** Native command names that custom commands must not shadow when reserved checks are enabled. */ - reservedCommands?: Set; - /** Set false to allow names that overlap native commands. */ - checkReserved?: boolean; - /** Set false to allow duplicate normalized custom command names. */ - checkDuplicates?: boolean; -}): { - commands: Array<{ command: string; description: string }>; - issues: TelegramCustomCommandIssue[]; -} { - return resolveTelegramCustomCommandsImpl(params); -} diff --git a/src/plugin-sdk/test-helpers/directory-ids.ts b/src/plugin-sdk/test-helpers/directory-ids.ts index 4542edfd12cb..496a6d6d31d3 100644 --- a/src/plugin-sdk/test-helpers/directory-ids.ts +++ b/src/plugin-sdk/test-helpers/directory-ids.ts @@ -3,7 +3,7 @@ */ import { expect } from "vitest"; import type { ChannelDirectoryEntry } from "../channel-contract.js"; -import type { OpenClawConfig } from "../config-types.js"; +import type { OpenClawConfig } from "../config-contracts.js"; export type DirectoryListFn = (params: { cfg: OpenClawConfig; diff --git a/src/plugin-sdk/test-helpers/plugin-runtime-mock.ts b/src/plugin-sdk/test-helpers/plugin-runtime-mock.ts index f877313bb5a6..b4587d608b84 100644 --- a/src/plugin-sdk/test-helpers/plugin-runtime-mock.ts +++ b/src/plugin-sdk/test-helpers/plugin-runtime-mock.ts @@ -78,12 +78,6 @@ function createTaskFlowSessionMock() { }; } -function createDeprecatedRuntimeConfigError(name: "loadConfig" | "writeConfigFile"): Error { - return new Error( - `Plugin runtime config.${name}() is deprecated in tests; pass cfg/current() or use mutateConfigFile()/replaceConfigFile().`, - ); -} - function normalizeUntrustedGroupPrompt(value: unknown): string | undefined { if (typeof value !== "string") { return undefined; @@ -484,12 +478,6 @@ export function createPluginRuntimeMock(overrides: DeepPartial = afterWrite: { mode: "auto" }, followUp: { mode: "auto", requiresRestart: false }, })) as unknown as PluginRuntime["config"]["replaceConfigFile"], - loadConfig: vi.fn(() => { - throw createDeprecatedRuntimeConfigError("loadConfig"); - }) as unknown as PluginRuntime["config"]["loadConfig"], - writeConfigFile: vi.fn(async () => { - throw createDeprecatedRuntimeConfigError("writeConfigFile"); - }) as unknown as PluginRuntime["config"]["writeConfigFile"], }, agent: { defaults: { @@ -663,9 +651,6 @@ export function createPluginRuntimeMock(overrides: DeepPartial = listProviders: vi.fn() as unknown as PluginRuntime["webSearch"]["listProviders"], search: vi.fn() as unknown as PluginRuntime["webSearch"]["search"], }, - stt: { - transcribeAudioFile: vi.fn() as unknown as PluginRuntime["stt"]["transcribeAudioFile"], - }, channel: { text: { chunkByNewline: vi.fn((text: string) => (text ? [text] : [])), @@ -721,9 +706,6 @@ export function createPluginRuntimeMock(overrides: DeepPartial = formatAgentEnvelope: vi.fn( (opts: { body: string }) => opts.body, ) as unknown as PluginRuntime["channel"]["reply"]["formatAgentEnvelope"], - formatInboundEnvelope: vi.fn( - (opts: { body: string }) => opts.body, - ) as unknown as PluginRuntime["channel"]["reply"]["formatInboundEnvelope"], resolveEnvelopeFormatOptions: vi.fn(() => ({ template: "channel+name+time", })) as unknown as PluginRuntime["channel"]["reply"]["resolveEnvelopeFormatOptions"], @@ -936,9 +918,7 @@ export function createPluginRuntimeMock(overrides: DeepPartial = fromToolContext: vi.fn(), } as PluginRuntime["tasks"]["flows"], managedFlows: taskFlow, - flow: taskFlow, }, - taskFlow, modelAuth: { getApiKeyForModel: vi.fn() as unknown as PluginRuntime["modelAuth"]["getApiKeyForModel"], getRuntimeAuthForModel: @@ -950,7 +930,6 @@ export function createPluginRuntimeMock(overrides: DeepPartial = run: vi.fn(), waitForRun: vi.fn(), getSessionMessages: vi.fn(), - getSession: vi.fn(), deleteSession: vi.fn(), }, sandbox: { diff --git a/src/plugin-sdk/test-utils.ts b/src/plugin-sdk/test-utils.ts deleted file mode 100644 index 39965a5eba7e..000000000000 --- a/src/plugin-sdk/test-utils.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Prefer the focused `plugin-test-runtime` subpath. */ -export * from "./plugin-test-runtime.js"; diff --git a/src/plugin-sdk/webhook-ingress.ts b/src/plugin-sdk/webhook-ingress.ts index 8d9ffbf99c7d..c248fb64b4f6 100644 --- a/src/plugin-sdk/webhook-ingress.ts +++ b/src/plugin-sdk/webhook-ingress.ts @@ -33,6 +33,8 @@ export { registerWebhookTargetWithPluginRoute, resolveSingleWebhookTarget, resolveSingleWebhookTargetAsync, + normalizeWebhookPath, + resolveWebhookPath, resolveWebhookTargetWithAuthOrReject, resolveWebhookTargetWithAuthOrRejectSync, resolveWebhookTargets, @@ -42,7 +44,6 @@ export { type RegisteredWebhookTarget, type WebhookTargetMatchResult, } from "./webhook-targets.js"; -export { normalizeWebhookPath, resolveWebhookPath } from "./webhook-path.js"; export { resolveRequestClientIp } from "../gateway/net.js"; export { createAuthRateLimiter } from "../gateway/auth-rate-limit.js"; export type { AuthRateLimiter, RateLimitConfig } from "../gateway/auth-rate-limit.js"; diff --git a/src/plugin-sdk/webhook-path.ts b/src/plugin-sdk/webhook-path.ts deleted file mode 100644 index b76d8377e0bb..000000000000 --- a/src/plugin-sdk/webhook-path.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @deprecated Compatibility subpath. Import webhook path helpers from - * `openclaw/plugin-sdk/webhook-ingress` instead. - */ - -/** - * Normalizes plugin webhook paths to an absolute path without a trailing slash. - * Empty values resolve to `/` so route registration and request matching use the - * same canonical key. - * - * @deprecated Import from `openclaw/plugin-sdk/webhook-ingress` instead. - */ -export function normalizeWebhookPath(raw: string): string { - const trimmed = raw.trim(); - if (!trimmed) { - return "/"; - } - const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; - if (withSlash.length > 1 && withSlash.endsWith("/")) { - return withSlash.slice(0, -1); - } - return withSlash; -} - -/** - * Resolves a webhook path from explicit path config, then URL pathname, then - * caller default. Invalid webhook URLs resolve to `null` instead of guessing. - * - * @deprecated Import from `openclaw/plugin-sdk/webhook-ingress` instead. - */ -export function resolveWebhookPath(params: { - webhookPath?: string; - webhookUrl?: string; - defaultPath?: string | null; -}): string | null { - const trimmedPath = params.webhookPath?.trim(); - if (trimmedPath) { - return normalizeWebhookPath(trimmedPath); - } - if (params.webhookUrl?.trim()) { - try { - const parsed = new URL(params.webhookUrl); - return normalizeWebhookPath(parsed.pathname || "/"); - } catch { - return null; - } - } - return params.defaultPath ?? null; -} diff --git a/src/plugin-sdk/webhook-targets.ts b/src/plugin-sdk/webhook-targets.ts index c891c0dea350..5bbb885443d9 100644 --- a/src/plugin-sdk/webhook-targets.ts +++ b/src/plugin-sdk/webhook-targets.ts @@ -2,7 +2,6 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import { registerPluginHttpRoute } from "../plugins/http-registry.js"; import type { FixedWindowRateLimiter } from "./webhook-memory-guards.js"; -import { normalizeWebhookPath } from "./webhook-path.js"; import { beginWebhookRequestPipelineOrReject, type WebhookInFlightLimiter, @@ -28,6 +27,36 @@ type RegisterPluginHttpRouteParams = Parameters[ export { registerPluginHttpRoute }; +/** Normalize a webhook path to a leading slash without a trailing slash. */ +export function normalizeWebhookPath(raw: string): string { + const trimmed = raw.trim(); + if (!trimmed) { + return "/"; + } + const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; + return withSlash.length > 1 && withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash; +} + +/** Resolve a webhook path from explicit config, URL pathname, or a caller default. */ +export function resolveWebhookPath(params: { + webhookPath?: string; + webhookUrl?: string; + defaultPath?: string | null; +}): string | null { + const trimmedPath = params.webhookPath?.trim(); + if (trimmedPath) { + return normalizeWebhookPath(trimmedPath); + } + if (params.webhookUrl?.trim()) { + try { + return normalizeWebhookPath(new URL(params.webhookUrl).pathname || "/"); + } catch { + return null; + } + } + return params.defaultPath ?? null; +} + /** Plugin HTTP route options supplied when webhook paths are registered lazily. */ export type RegisterWebhookPluginRouteOptions = Omit< RegisterPluginHttpRouteParams, diff --git a/src/plugin-sdk/zalouser.ts b/src/plugin-sdk/zalouser.ts deleted file mode 100644 index 83238dc6ef53..000000000000 --- a/src/plugin-sdk/zalouser.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @deprecated Compatibility facade for published Lark/Zalo packages that imported - * command authorization through `openclaw/plugin-sdk/zalouser`. - */ -export { - resolveSenderCommandAuthorization, - resolveSenderCommandAuthorizationWithRuntime, -} from "./command-auth.js"; -export type { - CommandAuthorizationRuntime, - ResolveSenderCommandAuthorizationParams, - ResolveSenderCommandAuthorizationWithRuntimeParams, -} from "./command-auth.js"; diff --git a/src/plugins/agent-tool-result-middleware-types.ts b/src/plugins/agent-tool-result-middleware-types.ts index d505e562a3c8..d014cc71a9fa 100644 --- a/src/plugins/agent-tool-result-middleware-types.ts +++ b/src/plugins/agent-tool-result-middleware-types.ts @@ -4,10 +4,6 @@ import type { AgentToolResult } from "../agents/runtime/index.js"; export type OpenClawAgentToolResult = AgentToolResult; export type AgentToolResultMiddlewareRuntime = "openclaw" | "codex"; -/** @deprecated Use AgentToolResultMiddlewareRuntime. */ -export type AgentToolResultMiddlewareHarness = - | AgentToolResultMiddlewareRuntime - | "codex-app-server"; export type AgentToolResultMiddlewareEvent = { threadId?: string; @@ -22,8 +18,6 @@ export type AgentToolResultMiddlewareEvent = { export type AgentToolResultMiddlewareContext = { runtime: AgentToolResultMiddlewareRuntime; - /** @deprecated Use runtime. */ - harness?: AgentToolResultMiddlewareRuntime; agentId?: string; sessionId?: string; sessionKey?: string; @@ -41,6 +35,4 @@ export type AgentToolResultMiddleware = ( export type AgentToolResultMiddlewareOptions = { runtimes?: AgentToolResultMiddlewareRuntime[]; - /** @deprecated Use runtimes. */ - harnesses?: AgentToolResultMiddlewareHarness[]; }; diff --git a/src/plugins/agent-tool-result-middleware.test.ts b/src/plugins/agent-tool-result-middleware.test.ts index 64e89085f58f..d0b591bde63f 100644 --- a/src/plugins/agent-tool-result-middleware.test.ts +++ b/src/plugins/agent-tool-result-middleware.test.ts @@ -1,6 +1,9 @@ // Covers plugin middleware that can transform agent tool results. import { describe, expect, it } from "vitest"; -import { normalizeAgentToolResultMiddlewareRuntimes } from "./agent-tool-result-middleware.js"; +import { + normalizeAgentToolResultMiddlewareRuntimeIds, + normalizeAgentToolResultMiddlewareRuntimes, +} from "./agent-tool-result-middleware.js"; describe("normalizeAgentToolResultMiddlewareRuntimes", () => { it("defaults omitted runtimes to every supported runtime", () => { @@ -11,18 +14,9 @@ describe("normalizeAgentToolResultMiddlewareRuntimes", () => { expect(normalizeAgentToolResultMiddlewareRuntimes({ runtimes: [] })).toEqual([]); }); - it("normalizes legacy harness names", () => { - expect( - normalizeAgentToolResultMiddlewareRuntimes({ harnesses: ["codex-app-server", "openclaw"] }), - ).toEqual(["codex", "openclaw"]); - }); - - it("falls back to legacy harnesses when runtimes is undefined", () => { - expect( - normalizeAgentToolResultMiddlewareRuntimes({ - runtimes: undefined, - harnesses: ["codex-app-server"], - }), - ).toEqual(["codex"]); + it("ignores unknown runtime ids from manifest metadata", () => { + expect(normalizeAgentToolResultMiddlewareRuntimeIds(["codex-app-server", "openclaw"])).toEqual([ + "openclaw", + ]); }); }); diff --git a/src/plugins/agent-tool-result-middleware.ts b/src/plugins/agent-tool-result-middleware.ts index f430012890b9..b3315e318ef5 100644 --- a/src/plugins/agent-tool-result-middleware.ts +++ b/src/plugins/agent-tool-result-middleware.ts @@ -15,21 +15,10 @@ const AGENT_TOOL_RESULT_MIDDLEWARE_RUNTIME_SET = new Set( AGENT_TOOL_RESULT_MIDDLEWARE_RUNTIMES, ); -const LEGACY_AGENT_TOOL_RESULT_MIDDLEWARE_RUNTIMES = { - "codex-app-server": "codex", -} as const satisfies Record; - function normalizeAgentToolResultMiddlewareRuntime( runtime: string, ): AgentToolResultMiddlewareRuntime | undefined { const normalized = runtime.trim().toLowerCase(); - const legacyRuntime = - LEGACY_AGENT_TOOL_RESULT_MIDDLEWARE_RUNTIMES[ - normalized as keyof typeof LEGACY_AGENT_TOOL_RESULT_MIDDLEWARE_RUNTIMES - ]; - if (legacyRuntime) { - return legacyRuntime; - } return AGENT_TOOL_RESULT_MIDDLEWARE_RUNTIME_SET.has(normalized) ? (normalized as AgentToolResultMiddlewareRuntime) : undefined; @@ -38,7 +27,7 @@ function normalizeAgentToolResultMiddlewareRuntime( export function normalizeAgentToolResultMiddlewareRuntimes( options?: AgentToolResultMiddlewareOptions, ): AgentToolResultMiddlewareRuntime[] { - const requested = options?.runtimes ?? options?.harnesses; + const requested = options?.runtimes; if (!requested) { return [...AGENT_TOOL_RESULT_MIDDLEWARE_RUNTIMES]; } diff --git a/src/plugins/api-builder.ts b/src/plugins/api-builder.ts index 6227c5e9ec6f..d60168d087ec 100644 --- a/src/plugins/api-builder.ts +++ b/src/plugins/api-builder.ts @@ -80,12 +80,9 @@ type BuildPluginApiParams = { | "unscheduleSessionTurnsByTag" | "registerDetachedTaskRuntime" | "registerMemoryCapability" - | "registerMemoryPromptSection" | "registerMemoryPromptSupplement" | "registerMemoryPromptPreparation" | "registerMemoryCorpusSupplement" - | "registerMemoryFlushPlan" - | "registerMemoryRuntime" | "registerMemoryEmbeddingProvider" | "on" > @@ -177,15 +174,12 @@ const noopUnscheduleSessionTurnsByTag: OpenClawPluginApi["unscheduleSessionTurns async () => ({ removed: 0, failed: 0 }); const noopRegisterDetachedTaskRuntime: OpenClawPluginApi["registerDetachedTaskRuntime"] = () => {}; const noopRegisterMemoryCapability: OpenClawPluginApi["registerMemoryCapability"] = () => {}; -const noopRegisterMemoryPromptSection: OpenClawPluginApi["registerMemoryPromptSection"] = () => {}; const noopRegisterMemoryPromptSupplement: OpenClawPluginApi["registerMemoryPromptSupplement"] = () => {}; const noopRegisterMemoryPromptPreparation: OpenClawPluginApi["registerMemoryPromptPreparation"] = () => {}; const noopRegisterMemoryCorpusSupplement: OpenClawPluginApi["registerMemoryCorpusSupplement"] = () => {}; -const noopRegisterMemoryFlushPlan: OpenClawPluginApi["registerMemoryFlushPlan"] = () => {}; -const noopRegisterMemoryRuntime: OpenClawPluginApi["registerMemoryRuntime"] = () => {}; const noopRegisterMemoryEmbeddingProvider: OpenClawPluginApi["registerMemoryEmbeddingProvider"] = () => {}; const noopOn: OpenClawPluginApi["on"] = () => {}; @@ -292,16 +286,12 @@ export function buildPluginApi(params: BuildPluginApiParams): OpenClawPluginApi registerDetachedTaskRuntime: handlers.registerDetachedTaskRuntime ?? noopRegisterDetachedTaskRuntime, registerMemoryCapability: handlers.registerMemoryCapability ?? noopRegisterMemoryCapability, - registerMemoryPromptSection: - handlers.registerMemoryPromptSection ?? noopRegisterMemoryPromptSection, registerMemoryPromptSupplement: handlers.registerMemoryPromptSupplement ?? noopRegisterMemoryPromptSupplement, registerMemoryPromptPreparation: handlers.registerMemoryPromptPreparation ?? noopRegisterMemoryPromptPreparation, registerMemoryCorpusSupplement: handlers.registerMemoryCorpusSupplement ?? noopRegisterMemoryCorpusSupplement, - registerMemoryFlushPlan: handlers.registerMemoryFlushPlan ?? noopRegisterMemoryFlushPlan, - registerMemoryRuntime: handlers.registerMemoryRuntime ?? noopRegisterMemoryRuntime, registerMemoryEmbeddingProvider: handlers.registerMemoryEmbeddingProvider ?? noopRegisterMemoryEmbeddingProvider, resolvePath: params.resolvePath, diff --git a/src/plugins/bundled-capability-runtime.ts b/src/plugins/bundled-capability-runtime.ts index d02bdb4b8081..e121f88d005b 100644 --- a/src/plugins/bundled-capability-runtime.ts +++ b/src/plugins/bundled-capability-runtime.ts @@ -74,13 +74,8 @@ function applyVitestCapabilityAliasOverrides(params: { return params.aliasMap; } - const { - "openclaw/plugin-sdk": _ignoredLegacyRootAlias, - "@openclaw/plugin-sdk": _ignoredScopedRootAlias, - ...scopedAliasMap - } = params.aliasMap; return { - ...scopedAliasMap, + ...params.aliasMap, // Capability contract loads only need a narrow SDK slice. Keep those // helpers on a tiny source graph so Vitest does not pull the dist chunk // bundle that also drags Matrix/WhatsApp code into these tests. @@ -124,7 +119,7 @@ function resolvePluginModuleExport(moduleExport: unknown): { const definition = resolved as OpenClawPluginDefinition; return { definition, - register: definition.register ?? definition.activate, + register: definition.register, }; } return {}; diff --git a/src/plugins/bundled-load-path-aliases.ts b/src/plugins/bundled-load-path-aliases.ts index 511e592f9d65..e682b30db3c6 100644 --- a/src/plugins/bundled-load-path-aliases.ts +++ b/src/plugins/bundled-load-path-aliases.ts @@ -1,6 +1,5 @@ // Resolves bundled plugin load-path aliases for package output. import path from "node:path"; -import { isPathInside } from "./path-safety.js"; /** Alias class for current packaged paths and legacy bundled extension paths. */ type BundledPluginLoadPathAliasKind = "current" | "legacy"; @@ -127,31 +126,3 @@ export function buildBundledPluginLoadPathAliases(localPath: string): BundledPlu { kind: "legacy", path: legacyPath }, ]; } - -function isSameOrInside(baseDir: string, targetPath: string): boolean { - const base = path.resolve(normalizeBundledLookupPath(baseDir)); - const target = path.resolve(normalizeBundledLookupPath(targetPath)); - return target === base || isPathInside(base, target); -} - -/** Classifies a load path as current or legacy for a packaged bundled plugin root. */ -export function resolvePackagedBundledLoadPathAlias(params: { - bundledRoot?: string; - loadPath: string; -}): BundledPluginLoadPathAlias | null { - if (!params.bundledRoot) { - return null; - } - const packaged = findPackagedBundledRoot(params.bundledRoot); - if (!packaged) { - return null; - } - const legacyRoot = path.join(packaged.packageRoot, "extensions"); - if (isSameOrInside(params.bundledRoot, params.loadPath)) { - return { kind: "current", path: params.loadPath }; - } - if (isSameOrInside(legacyRoot, params.loadPath)) { - return { kind: "legacy", path: params.loadPath }; - } - return null; -} diff --git a/src/plugins/channel-plugin-ids.test.ts b/src/plugins/channel-plugin-ids.test.ts index 442cc89ac154..44d04fe7efb8 100644 --- a/src/plugins/channel-plugin-ids.test.ts +++ b/src/plugins/channel-plugin-ids.test.ts @@ -303,19 +303,13 @@ function createManifestRegistryFixture(): PluginManifestRegistry { { id: "external-env-channel-plugin", channels: ["external-env-channel"], - channelEnvVars: { - "external-env-channel": ["EXTERNAL_ENV_CHANNEL_TOKEN"], - }, - origin: "config", - enabledByDefault: undefined, - providers: [], - cliBackends: [], - }, - { - id: "ambient-env-channel-plugin", - channels: ["ambient-env-channel"], - channelEnvVars: { - "ambient-env-channel": ["HOME", "PATH"], + packageChannel: { + id: "external-env-channel", + configuredState: { + env: { + allOf: ["EXTERNAL_ENV_CHANNEL_HOST", "EXTERNAL_ENV_CHANNEL_NICK"], + }, + }, }, origin: "config", enabledByDefault: undefined, @@ -575,7 +569,6 @@ function filterManifestRegistryForInstalledIndex(params: { function createPluginPlanningTestEnv(overrides: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv { return { - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", ...overrides, }; } @@ -3248,22 +3241,6 @@ describe("resolveConfiguredChannelPluginIds", () => { ).toStrictEqual([]); }); - it("includes trusted external channel owners configured only by manifest env vars", () => { - expect( - resolveConfiguredChannelPluginIds({ - config: { - plugins: { - allow: ["external-env-channel-plugin"], - }, - } as OpenClawConfig, - workspaceDir: "/tmp", - env: { - EXTERNAL_ENV_CHANNEL_TOKEN: "token", - } as NodeJS.ProcessEnv, - }), - ).toEqual(["external-env-channel-plugin"]); - }); - it("blocks bundled activation owners when explicitly disabled", () => { expect( resolveConfiguredChannelPluginIds({ @@ -3550,6 +3527,36 @@ describe("listConfiguredChannelIdsForReadOnlyScope", () => { ).toStrictEqual([]); }); + it("requires every package manifest allOf env variable", () => { + const config = { + plugins: { + allow: ["external-env-channel-plugin"], + }, + } as OpenClawConfig; + + expect( + listConfiguredChannelIdsForReadOnlyScope({ + config, + workspaceDir: "/tmp", + env: { + EXTERNAL_ENV_CHANNEL_HOST: "irc.example.com", + } as NodeJS.ProcessEnv, + includePersistedAuthState: false, + }), + ).toStrictEqual([]); + expect( + listConfiguredChannelIdsForReadOnlyScope({ + config, + workspaceDir: "/tmp", + env: { + EXTERNAL_ENV_CHANNEL_HOST: "irc.example.com", + EXTERNAL_ENV_CHANNEL_NICK: "openclaw", + } as NodeJS.ProcessEnv, + includePersistedAuthState: false, + }), + ).toContain("external-env-channel"); + }); + it("lets explicit bundled channel config bypass restrictive allowlists", () => { const config = { channels: { @@ -3941,145 +3948,5 @@ describe("listConfiguredChannelIdsForReadOnlyScope", () => { }, ]); }); - - it("uses manifest env vars as read-only configured channel triggers", () => { - expect( - listConfiguredChannelIdsForReadOnlyScope({ - config: { - plugins: { - allow: ["external-env-channel-plugin"], - }, - } as OpenClawConfig, - workspaceDir: "/tmp", - env: { - EXTERNAL_ENV_CHANNEL_TOKEN: "token", - } as NodeJS.ProcessEnv, - includePersistedAuthState: false, - }), - ).toEqual(["external-env-channel"]); - }); - - it("ignores manifest env vars from untrusted external plugins", () => { - expect( - listConfiguredChannelIdsForReadOnlyScope({ - config: {} as OpenClawConfig, - workspaceDir: "/tmp", - env: { - EXTERNAL_ENV_CHANNEL_TOKEN: "token", - } as NodeJS.ProcessEnv, - includePersistedAuthState: false, - }), - ).toStrictEqual([]); - - expect( - hasConfiguredChannelsForReadOnlyScope({ - config: {} as OpenClawConfig, - workspaceDir: "/tmp", - env: { - EXTERNAL_ENV_CHANNEL_TOKEN: "token", - } as NodeJS.ProcessEnv, - includePersistedAuthState: false, - }), - ).toBe(false); - }); - - it("ignores ambient or malformed manifest env vars as read-only configured channel triggers", () => { - expect( - listConfiguredChannelIdsForReadOnlyScope({ - config: { - plugins: { - allow: ["ambient-env-channel-plugin"], - }, - } as OpenClawConfig, - workspaceDir: "/tmp", - env: { - HOME: "/tmp/user", - PATH: "/usr/bin", - lowercase_token: "token", - } as NodeJS.ProcessEnv, - includePersistedAuthState: false, - }), - ).toStrictEqual([]); - }); - - it("accepts lowercase or mixed-case manifest env vars as read-only configured channel triggers", () => { - expect( - listConfiguredChannelIdsForReadOnlyScope({ - config: { - plugins: { - allow: ["external-env-channel-plugin"], - }, - } as OpenClawConfig, - workspaceDir: "/tmp", - env: { - external_env_channel_token: "token", - } as NodeJS.ProcessEnv, - includePersistedAuthState: false, - manifestRecords: [ - { - id: "external-env-channel-plugin", - channels: ["external-env-channel"], - channelEnvVars: { - "external-env-channel": ["external_env_channel_token"], - }, - origin: "config", - enabledByDefault: undefined, - providers: [], - cliBackends: [], - } as never, - ], - }), - ).toEqual(["external-env-channel"]); - }); - - it("matches uppercase process env entries for lowercase manifest env var declarations", () => { - expect( - listConfiguredChannelIdsForReadOnlyScope({ - config: { - plugins: { - allow: ["external-env-channel-plugin"], - }, - } as OpenClawConfig, - workspaceDir: "/tmp", - env: { - EXTERNAL_ENV_CHANNEL_TOKEN: "token", - } as NodeJS.ProcessEnv, - includePersistedAuthState: false, - manifestRecords: [ - { - id: "external-env-channel-plugin", - channels: ["external-env-channel"], - channelEnvVars: { - "external-env-channel": ["external_env_channel_token"], - }, - origin: "config", - enabledByDefault: undefined, - providers: [], - cliBackends: [], - } as never, - ], - }), - ).toEqual(["external-env-channel"]); - }); - - it("uses manifest env vars for read-only channel presence checks", () => { - listPotentialConfiguredChannelIds.mockReturnValue([]); - listPotentialConfiguredChannelPresenceSignals.mockReturnValue([]); - - expect( - hasConfiguredChannelsForReadOnlyScope({ - config: { - plugins: { - allow: ["external-env-channel-plugin"], - }, - } as OpenClawConfig, - workspaceDir: "/tmp", - env: { - EXTERNAL_ENV_CHANNEL_TOKEN: "token", - } as NodeJS.ProcessEnv, - includePersistedAuthState: false, - }), - ).toBe(true); - }); }); /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/plugins/channel-presence-policy.ts b/src/plugins/channel-presence-policy.ts index acc2132ac43a..5e449068929d 100644 --- a/src/plugins/channel-presence-policy.ts +++ b/src/plugins/channel-presence-policy.ts @@ -147,8 +147,20 @@ function listManifestEnvConfiguredChannelSignals(params: { continue; } for (const channelId of record.channels) { - const envVars = record.channelEnvVars?.[channelId] ?? []; - if (!envVars.some((envVar) => hasNonEmptyEnvValue(params.env, envVar))) { + const packageChannel = record.packageChannel; + const configuredStateEnv = + normalizeOptionalLowercaseString(packageChannel?.id) === + normalizeOptionalLowercaseString(channelId) + ? packageChannel?.configuredState?.env + : undefined; + const allOf = configuredStateEnv?.allOf ?? []; + const anyOf = configuredStateEnv?.anyOf ?? []; + const hasEnvContract = allOf.length > 0 || anyOf.length > 0; + if ( + !hasEnvContract || + !allOf.every((envVar) => hasNonEmptyEnvValue(params.env, envVar)) || + (anyOf.length > 0 && !anyOf.some((envVar) => hasNonEmptyEnvValue(params.env, envVar))) + ) { continue; } if (seen.has(channelId)) { diff --git a/src/plugins/commands.test.ts b/src/plugins/commands.test.ts index da539c107f56..4a67f526a54e 100644 --- a/src/plugins/commands.test.ts +++ b/src/plugins/commands.test.ts @@ -548,7 +548,6 @@ describe("registerPluginCommand", () => { const env = { ...process.env, OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"), - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: "1", }; expect(getPluginCommandSpecs("discord", { env })).toStrictEqual([]); diff --git a/src/plugins/compat/registry.test.ts b/src/plugins/compat/registry.test.ts index a51cf6bd6b71..1c681024b2cb 100644 --- a/src/plugins/compat/registry.test.ts +++ b/src/plugins/compat/registry.test.ts @@ -27,15 +27,16 @@ const publicSdkContractNarrowingTiers = [ codeSuffix: "-unused-subpath", count: 5, replacement: "none needed — no known consumers; the subpath is removed without successor", - releaseNote: /no known consumers.*removal without a successor/u, + releaseNote: /removed without a successor.*no consumers/u, }, { name: "bundled-only public export", codeSuffix: "-public-demotion", - count: 157, + count: 152, replacement: "subpath becomes internal (private-local-only); no external successor — no known external consumers", - releaseNote: /public export.*module stays available for bundled plugins.*private-local-only/u, + releaseNote: + /public export.*removed.*module remains available to bundled plugins.*private-local-only/u, }, ] as const; @@ -81,16 +82,16 @@ describe("plugin compatibility registry", () => { }); it.each(publicSdkContractNarrowingTiers)( - "keeps the $name tier on its registry-backed window", + "records the removed $name tier", ({ codeSuffix, count, replacement, releaseNote }) => { - const records = listPluginCompatRecords().filter((record) => - record.code.endsWith(codeSuffix), + const records = listPluginCompatRecords().filter( + (record) => record.code.endsWith(codeSuffix) && record.status === "removed", ); expect(records).toHaveLength(count); for (const record of records) { expect(record).toMatchObject({ - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-07-15", deprecated: "2026-07-15", @@ -105,6 +106,28 @@ describe("plugin compatibility registry", () => { }, ); + it("keeps shipped public contracts pending when live callers still depend on them", () => { + const records = listPluginCompatRecords().filter( + (record) => + record.status === "removal-pending" && + record.removeAfter !== undefined && + record.removeAfter <= "2026-07-30", + ); + + expect(records.map((record) => record.code)).toEqual([ + "plugin-sdk-agent-media-payload-public-demotion", + "plugin-sdk-media-understanding-public-demotion", + "plugin-sdk-memory-host-core-public-demotion", + "plugin-sdk-plugin-config-runtime-public-demotion", + "plugin-sdk-tool-plugin-public-demotion", + "agent-harness-sdk-alias", + ]); + for (const record of records) { + expect(record.replacement).toMatch(/retain the public/u); + expect(record.releaseNote).toBeUndefined(); + } + }); + it("keeps deprecated explicit target parser calls inside compatibility shims", () => { expect(deprecatedTargetParserOffenders).toEqual([]); }); diff --git a/src/plugins/compat/registry.ts b/src/plugins/compat/registry.ts index 9968afece9fc..a234117e517f 100644 --- a/src/plugins/compat/registry.ts +++ b/src/plugins/compat/registry.ts @@ -2,10 +2,6 @@ import type { PluginCompatRecord } from "./types.js"; const CHANNEL_RUNTIME_SDK_SURFACE = ["openclaw/plugin-sdk/channel", "runtime"].join("-"); -const LEGACY_CONFIG_MIGRATE_TEST_PATH = [ - "src/commands/doctor/shared/legacy-config", - "migrate.test.ts", -].join("-"); type DeprecatedPluginSdkSubpathSeed = Pick< PluginCompatRecord, @@ -357,22 +353,30 @@ const DEPRECATED_PLUGIN_SDK_SUBPATH_SEEDS = [ ] as const satisfies readonly DeprecatedPluginSdkSubpathSeed[]; const DEPRECATED_PLUGIN_SDK_SUBPATH_RECORDS = DEPRECATED_PLUGIN_SDK_SUBPATH_SEEDS.map( - ({ code, subpath, owner, removeAfter, replacement }) => ({ - code, - status: "deprecated" as const, - owner, - introduced: "2026-07-06", - deprecated: "2026-07-06", - warningStarts: "2026-07-06", - removeAfter, - replacement, - docsPath: "/plugins/sdk-migration", - surfaces: [`openclaw/plugin-sdk/${subpath}`], - diagnostics: [ - "repository deprecated API usage guard for core and bundled plugins; no external runtime import warning", - ], - tests: ["src/plugins/compat/registry.test.ts"], - }), + ({ code, subpath, owner, removeAfter, replacement }) => { + const record = { + code, + status: removeAfter <= "2026-07-30" ? ("removed" as const) : ("deprecated" as const), + owner, + introduced: "2026-07-06", + deprecated: "2026-07-06", + warningStarts: "2026-07-06", + removeAfter, + replacement, + docsPath: "/plugins/sdk-migration", + surfaces: [`openclaw/plugin-sdk/${subpath}`], + diagnostics: [ + "repository deprecated API usage guard for core and bundled plugins; no external runtime import warning", + ], + tests: ["src/plugins/compat/registry.test.ts"], + } satisfies PluginCompatRecord; + if (removeAfter <= "2026-07-30") { + return Object.assign(record, { + releaseNote: `The deprecated public \`openclaw/plugin-sdk/${subpath}\` subpath was removed in the July 2026 compatibility sweep.`, + }); + } + return record; + }, ) satisfies readonly PluginCompatRecord[]; const UNUSED_PUBLIC_PLUGIN_SDK_SUBPATHS = [ @@ -543,10 +547,18 @@ const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATHS = [ "windows-spawn", ] as const; +const BLOCKED_PUBLIC_PLUGIN_SDK_DEMOTIONS = new Set([ + "agent-media-payload", + "media-understanding", + "memory-host-core", + "plugin-config-runtime", + "tool-plugin", +]); + const UNUSED_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS = UNUSED_PUBLIC_PLUGIN_SDK_SUBPATHS.map( (subpath) => ({ code: `plugin-sdk-${subpath}-unused-subpath` as const, - status: "deprecated" as const, + status: "removed" as const, owner: "sdk" as const, introduced: "2026-07-15", deprecated: "2026-07-15", @@ -559,27 +571,43 @@ const UNUSED_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS = UNUSED_PUBLIC_PLUGIN_SDK_SUBPAT "repository deprecated API usage guard for core and bundled plugins; no external runtime import warning", ], tests: ["src/plugins/compat/registry.test.ts"], - releaseNote: `The public \`openclaw/plugin-sdk/${subpath}\` subpath has no known consumers and is scheduled for removal without a successor.`, + releaseNote: `The unused public \`openclaw/plugin-sdk/${subpath}\` subpath was removed without a successor after the external-use corpus found no consumers.`, }), ) satisfies readonly PluginCompatRecord[]; const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS = BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATHS.map( - (subpath) => ({ - code: `plugin-sdk-${subpath}-public-demotion` as const, - status: "deprecated" as const, - owner: "sdk" as const, - introduced: "2026-07-15", - deprecated: "2026-07-15", - warningStarts: "2026-07-15", - removeAfter: "2026-07-30", - replacement: - "subpath becomes internal (private-local-only); no external successor — no known external consumers", - docsPath: "/plugins/sdk-migration", - surfaces: [`openclaw/plugin-sdk/${subpath}`], - diagnostics: ["registry-backed public SDK demotion window; no external runtime import warning"], - tests: ["src/plugins/compat/registry.test.ts"], - releaseNote: `Only the public export for \`openclaw/plugin-sdk/${subpath}\` is retiring; the module stays available for bundled plugins as a private-local-only subpath.`, - }), + (subpath) => { + const removalBlocked = BLOCKED_PUBLIC_PLUGIN_SDK_DEMOTIONS.has(subpath); + const record = { + code: `plugin-sdk-${subpath}-public-demotion` as const, + status: removalBlocked ? ("removal-pending" as const) : ("removed" as const), + owner: "sdk" as const, + introduced: "2026-07-15", + deprecated: "2026-07-15", + warningStarts: "2026-07-15", + removeAfter: "2026-07-30", + replacement: removalBlocked + ? subpath === "tool-plugin" + ? "retain the public subpath until `openclaw plugins init/build/validate` migrates off `defineToolPlugin`" + : "retain the public subpath while shipped plugin-authoring documentation prescribes it; define and document a public replacement before demotion" + : "subpath becomes internal (private-local-only); no external successor — no known external consumers", + docsPath: + removalBlocked && subpath === "tool-plugin" + ? "/plugins/tool-plugins" + : "/plugins/sdk-migration", + surfaces: [`openclaw/plugin-sdk/${subpath}`], + diagnostics: [ + "registry-backed public SDK demotion window; no external runtime import warning", + ], + tests: ["src/plugins/compat/registry.test.ts"], + } satisfies PluginCompatRecord; + if (!removalBlocked) { + return Object.assign(record, { + releaseNote: `The public export for \`openclaw/plugin-sdk/${subpath}\` was removed; the module remains available to bundled plugins as a private-local-only subpath.`, + }); + } + return record; + }, ) satisfies readonly PluginCompatRecord[]; const PLUGIN_COMPAT_RECORDS = [ @@ -605,7 +633,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "legacy-before-agent-start", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-24", @@ -615,9 +643,9 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["plugin hooks", "plugins inspect", "status diagnostics"], diagnostics: ["plugin compatibility notice"], - tests: ["src/plugins/status.test.ts", "src/plugins/contracts/shape.contract.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], releaseNote: - "Legacy `before_agent_start` hook compatibility remains wired while plugins migrate to modern hook stages.", + "Legacy `before_agent_start` hook compatibility was removed; plugins must use `before_model_resolve` and `before_prompt_build`.", }, { code: "legacy-deactivate-hook-alias", @@ -741,7 +769,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "legacy-root-sdk-import", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-24", @@ -751,11 +779,9 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["openclaw/plugin-sdk", "openclaw/plugin-sdk/compat"], diagnostics: ["OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED"], - tests: [ - "src/plugins/contracts/plugin-sdk-index.test.ts", - "src/plugins/contracts/plugin-sdk-root-alias.test.ts", - "src/plugins/contracts/plugin-sdk-subpaths.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by legacy-root-sdk-import were removed in the July 2026 sweep.", }, { code: "hook.before_tool_call.terminal-block-approval", @@ -870,7 +896,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "bundled-channel-config-schema-legacy", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-28", deprecated: "2026-04-28", @@ -881,10 +907,9 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["openclaw/plugin-sdk/channel-config-schema-legacy"], diagnostics: ["plugin SDK compatibility warning"], - tests: [ - "src/plugins/contracts/config-footprint-guardrails.test.ts", - "test/extension-test-boundary.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by bundled-channel-config-schema-legacy were removed in the July 2026 sweep.", }, { code: "plugin-sdk-testing-barrel", @@ -905,7 +930,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "channel-route-key-aliases", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-28", deprecated: "2026-04-28", @@ -918,10 +943,9 @@ const PLUGIN_COMPAT_RECORDS = [ "openclaw/plugin-sdk/channel-route channelRouteKey", ], diagnostics: ["plugin SDK compatibility warning"], - tests: [ - "src/plugin-sdk/channel-route.test.ts", - "src/plugins/contracts/plugin-sdk-subpaths.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by channel-route-key-aliases were removed in the July 2026 sweep.", }, { code: "channel-explicit-target-parser", @@ -998,7 +1022,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "provider-auth-env-vars", - status: "deprecated", + status: "removed", owner: "setup", introduced: "2026-04-24", deprecated: "2026-04-24", @@ -1008,11 +1032,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/manifest", surfaces: ["openclaw.plugin.json providerAuthEnvVars", "provider setup"], diagnostics: ["manifest compatibility diagnostic"], - tests: ["src/plugins/setup-registry.test.ts", "src/plugins/provider-auth-choices.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by provider-auth-env-vars were removed in the July 2026 sweep.", }, { code: "channel-env-vars", - status: "deprecated", + status: "removed", owner: "channel", introduced: "2026-04-24", deprecated: "2026-04-24", @@ -1022,10 +1048,9 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/manifest", surfaces: ["openclaw.plugin.json channelEnvVars", "channel setup"], diagnostics: ["manifest compatibility diagnostic"], - tests: [ - "src/plugins/setup-registry.test.ts", - "src/channels/plugins/setup-group-access.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by channel-env-vars were removed in the July 2026 sweep.", }, { code: "activation-agent-harness-hint", @@ -1107,7 +1132,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "embedded-harness-config-alias", - status: "deprecated", + status: "removed", owner: "agent-runtime", introduced: "2026-04-24", deprecated: "2026-04-25", @@ -1117,17 +1142,20 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-agent-harness", surfaces: ["agents.defaults.embeddedHarness", "model/provider runtime selection"], diagnostics: ["agent runtime config compatibility"], - tests: [LEGACY_CONFIG_MIGRATE_TEST_PATH], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by embedded-harness-config-alias were removed in the July 2026 sweep.", }, { code: "agent-harness-sdk-alias", - status: "deprecated", + status: "removal-pending", owner: "agent-runtime", introduced: "2026-04-24", deprecated: "2026-04-25", warningStarts: "2026-04-25", removeAfter: "2026-07-25", - replacement: "`openclaw/plugin-sdk/agent-runtime`", + replacement: + "`openclaw/plugin-sdk/agent-runtime`; retain the public aliases until the shipped SDK contract has a replacement window backed by external-usage proof", docsPath: "/plugins/sdk-agent-harness", surfaces: ["openclaw/plugin-sdk/agent-harness", "openclaw/plugin-sdk/agent-harness-runtime"], diagnostics: ["plugin SDK compatibility warning"], @@ -1158,7 +1186,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "agent-harness-id-alias", - status: "deprecated", + status: "removed", owner: "agent-runtime", introduced: "2026-04-24", deprecated: "2026-04-25", @@ -1168,10 +1196,9 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-agent-harness", surfaces: ["manifest/catalog execution policy", "runtime selection"], diagnostics: ["agent runtime compatibility warning"], - tests: [ - "src/plugins/provider-runtime.test.ts", - "packages/web-content-core/src/provider-runtime-shared.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by agent-harness-id-alias were removed in the July 2026 sweep.", }, { code: "generated-bundled-channel-config-fallback", @@ -1186,7 +1213,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "disable-persisted-plugin-registry-env", - status: "deprecated", + status: "removed", owner: "config", introduced: "2026-04-25", deprecated: "2026-04-25", @@ -1196,11 +1223,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/cli/plugins#registry", surfaces: ["OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY", "plugin registry reads"], diagnostics: ["persisted-registry-disabled"], - tests: ["src/plugins/plugin-registry.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by disable-persisted-plugin-registry-env were removed in the July 2026 sweep.", }, { code: "plugin-registry-install-migration-env", - status: "deprecated", + status: "removed", owner: "config", introduced: "2026-04-25", deprecated: "2026-04-25", @@ -1214,11 +1243,13 @@ const PLUGIN_COMPAT_RECORDS = [ "package postinstall plugin registry migration", ], diagnostics: ["postinstall migration skip", "postinstall migration force deprecation warning"], - tests: ["src/commands/doctor/shared/plugin-registry-migration.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by plugin-registry-install-migration-env were removed in the July 2026 sweep.", }, { code: "plugin-install-config-ledger", - status: "deprecated", + status: "removed", owner: "config", introduced: "2026-04-25", deprecated: "2026-04-26", @@ -1228,14 +1259,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/cli/plugins#registry", surfaces: ["plugins.installs authored config", "plugin install/update migration"], diagnostics: ["config write migration warning", "doctor registry migration"], - tests: [ - "src/config/io.write-config.test.ts", - "src/commands/doctor/shared/plugin-registry-migration.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by plugin-install-config-ledger were removed in the July 2026 sweep.", }, { code: "bundled-plugin-load-path-aliases", - status: "deprecated", + status: "removed", owner: "config", introduced: "2026-04-25", deprecated: "2026-04-26", @@ -1245,11 +1275,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/cli/plugins#registry", surfaces: ["plugins.load.paths entries pointing at bundled plugin source/dist paths"], diagnostics: ["doctor bundled plugin load-path warning"], - tests: ["src/commands/doctor/shared/bundled-plugin-load-paths.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by bundled-plugin-load-path-aliases were removed in the July 2026 sweep.", }, { code: "plugin-owned-web-search-config", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-26", deprecated: "2026-04-26", @@ -1259,11 +1291,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/tools/web", surfaces: ["tools.web.search.apiKey", "tools.web.search."], diagnostics: ["doctor legacy web-search config migration"], - tests: ["src/commands/doctor/shared/legacy-web-search-migrate.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by plugin-owned-web-search-config were removed in the July 2026 sweep.", }, { code: "plugin-owned-web-fetch-config", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-26", deprecated: "2026-04-26", @@ -1273,11 +1307,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/tools/web-fetch", surfaces: ["tools.web.fetch.firecrawl"], diagnostics: ["doctor legacy web-fetch config migration"], - tests: ["src/commands/doctor/shared/legacy-web-fetch-migrate.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by plugin-owned-web-fetch-config were removed in the July 2026 sweep.", }, { code: "plugin-owned-x-search-config", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-26", deprecated: "2026-04-26", @@ -1287,14 +1323,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/tools/grok-search", surfaces: ["tools.web.x_search.apiKey"], diagnostics: ["doctor legacy x_search config migration"], - tests: [ - "src/commands/doctor/shared/legacy-x-search-migrate.test.ts", - LEGACY_CONFIG_MIGRATE_TEST_PATH, - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by plugin-owned-x-search-config were removed in the July 2026 sweep.", }, { code: "plugin-activate-entrypoint-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1304,7 +1339,9 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-entrypoints", surfaces: ["plugin module `activate(api)`", "plugin loader registration"], diagnostics: ["loader compatibility path"], - tests: ["src/plugins/loader.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by plugin-activate-entrypoint-alias were removed in the July 2026 sweep.", }, { code: "setup-runtime-fallback", @@ -1319,7 +1356,7 @@ const PLUGIN_COMPAT_RECORDS = [ }, { code: "provider-discovery-hook-alias", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1329,11 +1366,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["provider plugin `discovery` hook", "provider catalog resolution"], diagnostics: ["provider validation warning when catalog and discovery both register"], - tests: ["src/plugins/provider-discovery.test.ts", "src/plugins/provider-validation.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by provider-discovery-hook-alias were removed in the July 2026 sweep.", }, { code: "channel-exposure-legacy-aliases", - status: "deprecated", + status: "removed", owner: "channel", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1343,11 +1382,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-setup", surfaces: ["openclaw.channel.showConfigured", "openclaw.channel.showInSetup"], diagnostics: ["channel exposure compatibility path"], - tests: ["src/commands/channel-setup/discovery.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by channel-exposure-legacy-aliases were removed in the July 2026 sweep.", }, { code: "channel-runtime-sdk-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1358,11 +1399,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: [CHANNEL_RUNTIME_SDK_SURFACE], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by channel-runtime-sdk-alias were removed in the July 2026 sweep.", }, { code: "command-auth-status-builders", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1376,11 +1419,13 @@ const PLUGIN_COMPAT_RECORDS = [ "openclaw/plugin-sdk/command-auth buildHelpMessage", ], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugin-sdk/command-auth.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by command-auth-status-builders were removed in the July 2026 sweep.", }, { code: "clawdbot-config-type-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1390,11 +1435,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["openclaw/plugin-sdk `ClawdbotConfig` type export"], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/contracts/plugin-sdk-index.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by clawdbot-config-type-alias were removed in the July 2026 sweep.", }, { code: "openclaw-schema-type-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-26", deprecated: "2026-04-26", @@ -1404,11 +1451,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["openclaw/plugin-sdk `OpenClawSchemaType` type export"], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/contracts/plugin-sdk-index.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by openclaw-schema-type-alias were removed in the July 2026 sweep.", }, { code: "legacy-extension-api-import", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1419,11 +1468,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["openclaw/extension-api"], diagnostics: ["OPENCLAW_EXTENSION_API_DEPRECATED"], - tests: ["src/plugins/sdk-alias.test.ts", "src/index.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by legacy-extension-api-import were removed in the July 2026 sweep.", }, { code: "memory-split-registration", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1438,11 +1489,13 @@ const PLUGIN_COMPAT_RECORDS = [ "src/plugins/memory-state split registration helpers", ], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/memory-state.test.ts", "src/plugins/loader.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by memory-split-registration were removed in the July 2026 sweep.", }, { code: "provider-static-capabilities-bag", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1453,14 +1506,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-provider-plugins", surfaces: ["ProviderPlugin.capabilities", "ProviderCapabilities"], diagnostics: ["provider validation warning"], - tests: [ - "src/plugins/provider-runtime.test.ts", - "src/plugins/contracts/provider-family-plugin-tests.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by provider-static-capabilities-bag were removed in the July 2026 sweep.", }, { code: "provider-discovery-type-aliases", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1476,11 +1528,13 @@ const PLUGIN_COMPAT_RECORDS = [ "ProviderPluginDiscovery", ], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/contracts/plugin-sdk-index.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by provider-discovery-type-aliases were removed in the July 2026 sweep.", }, { code: "provider-thinking-policy-hooks", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1494,11 +1548,13 @@ const PLUGIN_COMPAT_RECORDS = [ "ProviderPlugin.resolveDefaultThinkingLevel", ], diagnostics: ["provider runtime compatibility warning"], - tests: ["src/plugins/provider-runtime.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by provider-thinking-policy-hooks were removed in the July 2026 sweep.", }, { code: "provider-external-oauth-profiles-hook", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1508,11 +1564,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-provider-plugins", surfaces: ["ProviderPlugin.resolveExternalOAuthProfiles"], diagnostics: ["provider external auth fallback warning"], - tests: ["src/plugins/provider-runtime.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by provider-external-oauth-profiles-hook were removed in the July 2026 sweep.", }, { code: "agent-tool-result-harness-alias", - status: "deprecated", + status: "removed", owner: "agent-runtime", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1527,14 +1585,13 @@ const PLUGIN_COMPAT_RECORDS = [ "normalizeAgentToolResultMiddlewareHarnesses", ], diagnostics: ["agent runtime compatibility warning"], - tests: [ - "src/plugins/captured-registration.test.ts", - "src/agents/codex-app-server.extensions.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by agent-tool-result-harness-alias were removed in the July 2026 sweep.", }, { code: "runtime-config-load-write", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-27", deprecated: "2026-04-27", @@ -1549,15 +1606,13 @@ const PLUGIN_COMPAT_RECORDS = [ "deprecated API usage guard", "runtime channel config boundary guard", ], - tests: [ - "src/plugins/runtime/runtime-config.test.ts", - "src/plugins/contracts/deprecated-internal-config-api.test.ts", - "src/plugins/contracts/config-boundary-guard.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by runtime-config-load-write were removed in the July 2026 sweep.", }, { code: "runtime-taskflow-legacy-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1568,11 +1623,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-runtime", surfaces: ["api.runtime.taskFlow", "api.runtime.tasks.flow"], diagnostics: ["plugin runtime compatibility warning"], - tests: ["src/plugins/runtime/index.test.ts", "src/plugins/runtime/runtime-tasks.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by runtime-taskflow-legacy-alias were removed in the July 2026 sweep.", }, { code: "runtime-subagent-get-session-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1582,11 +1639,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-runtime", surfaces: ["api.runtime.subagent.getSession"], diagnostics: ["plugin runtime compatibility warning"], - tests: ["src/plugins/runtime/index.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by runtime-subagent-get-session-alias were removed in the July 2026 sweep.", }, { code: "runtime-stt-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1596,11 +1655,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-runtime", surfaces: ["api.runtime.stt.transcribeAudioFile"], diagnostics: ["plugin runtime compatibility warning"], - tests: ["src/plugins/runtime/index.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by runtime-stt-alias were removed in the July 2026 sweep.", }, { code: "runtime-inbound-envelope-alias", - status: "deprecated", + status: "removed", owner: "channel", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1610,11 +1671,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-runtime", surfaces: ["api.runtime.channel.reply.formatInboundEnvelope"], diagnostics: ["channel runtime compatibility warning"], - tests: ["src/plugins/runtime/index.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by runtime-inbound-envelope-alias were removed in the July 2026 sweep.", }, { code: "channel-native-message-schema-helpers", - status: "deprecated", + status: "removed", owner: "channel", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1627,11 +1690,13 @@ const PLUGIN_COMPAT_RECORDS = [ "openclaw/plugin-sdk/channel-actions createMessageToolCardSchema", ], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by channel-native-message-schema-helpers were removed in the July 2026 sweep.", }, { code: "channel-mention-gating-legacy-helpers", - status: "deprecated", + status: "removed", owner: "channel", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1646,11 +1711,13 @@ const PLUGIN_COMPAT_RECORDS = [ "openclaw/plugin-sdk/channel-mention-gating resolveMentionGatingWithBypass", ], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by channel-mention-gating-legacy-helpers were removed in the July 2026 sweep.", }, { code: "provider-web-search-core-wrapper", - status: "deprecated", + status: "removed", owner: "provider", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1660,11 +1727,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-provider-plugins", surfaces: ["openclaw/plugin-sdk/provider-web-search createPluginBackedWebSearchProvider"], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by provider-web-search-core-wrapper were removed in the July 2026 sweep.", }, { code: "approval-capability-approvals-alias", - status: "deprecated", + status: "removed", owner: "channel", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1675,11 +1744,13 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-channel-plugins", surfaces: ["createChannelApprovalCapability({ approvals })"], diagnostics: ["plugin SDK compatibility warning"], - tests: ["src/plugin-sdk/approval-delivery-helpers.test.ts"], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by approval-capability-approvals-alias were removed in the July 2026 sweep.", }, { code: "plugin-sdk-test-utils-alias", - status: "deprecated", + status: "removed", owner: "sdk", introduced: "2026-04-24", deprecated: "2026-04-26", @@ -1689,10 +1760,9 @@ const PLUGIN_COMPAT_RECORDS = [ docsPath: "/plugins/sdk-migration", surfaces: ["openclaw/plugin-sdk/test-utils"], diagnostics: ["plugin SDK compatibility warning"], - tests: [ - "src/plugins/compat/registry.test.ts", - "src/plugins/contracts/plugin-sdk-subpaths.test.ts", - ], + tests: ["src/plugins/compat/registry.test.ts"], + releaseNote: + "The deprecated compatibility surfaces tracked by plugin-sdk-test-utils-alias were removed in the July 2026 sweep.", }, ] as const satisfies readonly PluginCompatRecord[]; diff --git a/src/plugins/config-contracts.test.ts b/src/plugins/config-contracts.test.ts index 1b0404240900..05cfce43a85e 100644 --- a/src/plugins/config-contracts.test.ts +++ b/src/plugins/config-contracts.test.ts @@ -51,7 +51,6 @@ function createPluginRecord( rootDir: `/tmp/${overrides.id}`, manifestPath: `/tmp/${overrides.id}/openclaw.plugin.json`, channelConfigs: undefined, - providerAuthEnvVars: undefined, configUiHints: undefined, configSchema: undefined, configContracts: undefined, @@ -70,7 +69,6 @@ function createPluginRecord( providers: [], modelSupport: undefined, cliBackends: [], - channelEnvVars: undefined, providerAuthAliases: undefined, providerAuthChoices: undefined, skills: [], diff --git a/src/plugins/contracts/boundary-invariants.test.ts b/src/plugins/contracts/boundary-invariants.test.ts index 825c62146d6e..0ced772cfed2 100644 --- a/src/plugins/contracts/boundary-invariants.test.ts +++ b/src/plugins/contracts/boundary-invariants.test.ts @@ -380,12 +380,6 @@ describe("plugin contract boundary invariants", () => { expect(offenders).toStrictEqual([]); }); - it("keeps bundled plugin production code off legacy before_agent_start hooks", () => { - const files = listTsFiles("extensions", { excludeTests: true }); - const offenders = files.filter((file) => readRepoSource(file).includes("before_agent_start")); - expect(offenders).toStrictEqual([]); - }); - it("keeps bundled plugin typed hook registrations on an explicit allowlist", () => { const files = listTsFiles("extensions", { excludeTests: true }); const hookRegistrationFiles = files.filter((file) => /\bapi\.on\(/u.test(readRepoSource(file))); diff --git a/src/plugins/contracts/config-footprint-guardrails.test.ts b/src/plugins/contracts/config-footprint-guardrails.test.ts index a7aaf262dafc..6c1b53aaf42f 100644 --- a/src/plugins/contracts/config-footprint-guardrails.test.ts +++ b/src/plugins/contracts/config-footprint-guardrails.test.ts @@ -175,7 +175,6 @@ describe("config footprint guardrails", () => { it("keeps bundled channel schemas out of the generic channel config SDK surface", () => { const source = readSource("src/plugin-sdk/channel-config-schema.ts"); const bundledSource = readSource("src/plugin-sdk/bundled-channel-config-schema.ts"); - const legacySource = readSource("src/plugin-sdk/channel-config-schema-legacy.ts"); const bundledSection = bundledSource.slice( bundledSource.indexOf("Bundled-channel config schemas"), ); @@ -209,11 +208,8 @@ describe("config footprint guardrails", () => { } expect(bundledSource).toContain("Bundled-channel config schemas"); expect(bundledSource).toContain("openclaw/plugin-sdk/channel-config-schema"); - expect(legacySource).toContain("Compatibility surface for bundled channel schemas"); - expect(legacySource).toContain("openclaw/plugin-sdk/bundled-channel-config-schema"); - expect(legacySource).toContain('export * from "./bundled-channel-config-schema.js";'); - // Non-canonical facades are re-export shells over the canonical - // channel-config-schema module; only bundled provider schemas bypass it. + // The primitives facade re-exports the canonical channel-config-schema + // module; only bundled provider schemas bypass it. const primitivesSource = readSource("src/plugin-sdk/channel-config-primitives.ts"); expect(primitivesSource).toContain('from "./channel-config-schema.js";'); expect(primitivesSource).not.toContain("../channels/"); @@ -223,16 +219,14 @@ describe("config footprint guardrails", () => { }); it("keeps internal src imports off the non-canonical channel config schema facades", () => { - // channel-config-schema is the canonical internal module; the primitives, - // bundled, and legacy shells stay export-compatible for plugins only. + // channel-config-schema is the canonical internal module; the primitives + // and bundled shells stay export-compatible for plugins only. const allowedShellImporters = new Set([ - // The deprecated alias shell re-exports the bundled facade by design. - "src/plugin-sdk/channel-config-schema-legacy.ts", // This guardrail file embeds facade specifiers in shell-shape assertions. "src/plugins/contracts/config-footprint-guardrails.test.ts", ]); const facadeImportPattern = - /\bfrom\s*["'][^"']*(?:channel-config-primitives|bundled-channel-config-schema|channel-config-schema-legacy)(?:\.js)?["']/u; + /\bfrom\s*["'][^"']*(?:channel-config-primitives|bundled-channel-config-schema)(?:\.js)?["']/u; const files = listGitTrackedFiles({ repoRoot: REPO_ROOT, pathspecs: "src" }) ?? []; const offenders = files.filter((file) => { diff --git a/src/plugins/contracts/extension-package-project-boundaries.test.ts b/src/plugins/contracts/extension-package-project-boundaries.test.ts index 177efa83d6c1..da6e77de13ea 100644 --- a/src/plugins/contracts/extension-package-project-boundaries.test.ts +++ b/src/plugins/contracts/extension-package-project-boundaries.test.ts @@ -281,13 +281,6 @@ describe("opt-in extension package boundaries", () => { expect(packageJson.exports?.["./provider-model-types"]?.types).toBe( "./dist/src/plugin-sdk/provider-model-types.d.ts", ); - expect(packageJson.exports?.["./channel-runtime"]?.types).toBe( - "./dist/src/plugin-sdk/channel-runtime.d.ts", - ); - expect(packageJson.exports?.["./compat"]?.types).toBe("./dist/src/plugin-sdk/compat.d.ts"); - expect(packageJson.exports?.["./config-types"]?.types).toBe( - "./dist/src/plugin-sdk/config-types.d.ts", - ); expect(packageJson.exports?.["./infra-runtime"]?.types).toBe( "./dist/src/plugin-sdk/infra-runtime.d.ts", ); diff --git a/src/plugins/contracts/plugin-sdk-index.test.ts b/src/plugins/contracts/plugin-sdk-index.test.ts deleted file mode 100644 index 8bb116e67539..000000000000 --- a/src/plugins/contracts/plugin-sdk-index.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -// Plugin SDK index tests cover SDK export baselines and public subpath availability. -import fs from "node:fs/promises"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { expectDefined } from "@openclaw/normalization-core"; -import { describe, expect, expectTypeOf, it } from "vitest"; -import { buildPluginSdkPackageExports } from "../../plugin-sdk/entrypoints.js"; -import type { ClawdbotConfig, OpenClawConfig, OpenClawSchemaType } from "../../plugin-sdk/index.js"; - -const pluginSdkIndexPath = fileURLToPath(new URL("../../plugin-sdk/index.ts", import.meta.url)); - -async function collectRuntimeExports(filePath: string, seen = new Set()) { - const normalizedPath = path.resolve(filePath); - if (seen.has(normalizedPath)) { - return new Set(); - } - seen.add(normalizedPath); - - const source = await fs.readFile(normalizedPath, "utf8"); - const exportNames = new Set(); - - for (const match of source.matchAll(/export\s+(?!type\b)\{([\s\S]*?)\}\s+from\s+"([^"]+)";/g)) { - for (const part of expectDefined(match[1], "match[1] test invariant").split(",")) { - const trimmed = part.trim(); - if (trimmed.length === 0) { - continue; - } - const name = trimmed.split(/\s+as\s+/).at(-1) ?? trimmed; - exportNames.add(name); - } - } - - for (const match of source.matchAll(/export\s+\*\s+from\s+"([^"]+)";/g)) { - const specifier = expectDefined(match[1], "match[1] test invariant"); - if (!specifier.startsWith(".")) { - continue; - } - const nestedPath = path.resolve( - path.dirname(normalizedPath), - specifier.replace(/\.js$/, ".ts"), - ); - const nestedExports = await collectRuntimeExports(nestedPath, seen); - for (const name of nestedExports) { - exportNames.add(name); - } - } - - return exportNames; -} - -async function readIndexRuntimeExports() { - return await collectRuntimeExports(pluginSdkIndexPath); -} - -describe("plugin-sdk exports", () => { - it("does not expose runtime modules", async () => { - const runtimeExports = await readIndexRuntimeExports(); - const forbidden = [ - "chunkMarkdownText", - "chunkText", - "hasControlCommand", - "isControlCommandMessage", - "shouldComputeCommandAuthorized", - "shouldHandleTextCommands", - "buildMentionRegexes", - "matchesMentionPatterns", - "resolveStateDir", - "writeConfigFile", - "enqueueSystemEvent", - "readRemoteMediaBuffer", - "saveMediaBuffer", - "formatAgentEnvelope", - "buildPairingReply", - "resolveAgentRoute", - "dispatchReplyFromConfig", - "createReplyDispatcherWithTyping", - "dispatchReplyWithBufferedBlockDispatcher", - "resolveCommandAuthorizedFromAuthorizers", - "monitorSlackProvider", - "monitorTelegramProvider", - "monitorIMessageProvider", - "monitorSignalProvider", - "sendMessageSlack", - "sendMessageTelegram", - "sendMessageIMessage", - "sendMessageSignal", - "sendMessageWhatsApp", - "probeSlack", - "probeTelegram", - "probeIMessage", - "probeSignal", - ]; - - for (const key of forbidden) { - expect(runtimeExports.has(key)).toBe(false); - } - }); - - it("keeps the root runtime surface intentionally small", async () => { - const runtimeExports = await readIndexRuntimeExports(); - expect([...runtimeExports].toSorted()).toEqual([ - "ContextEngineRuntimeSettingsUnavailableError", - "ContextEngineRuntimeSettingsUnsupportedError", - "assertContextEngineHostSupport", - "buildMemorySystemPromptAddition", - "delegateCompactionToRuntime", - "emptyPluginConfigSchema", - "onDiagnosticEvent", - "optionalStringEnum", - "registerContextEngine", - "stringEnum", - ]); - }); - - it("keeps deprecated root config type aliases aligned", () => { - expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); - }); - - it("keeps package.json plugin-sdk exports synced with the manifest", async () => { - const packageJsonPath = path.join(process.cwd(), "package.json"); - const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8")) as { - exports?: Record; - }; - const currentPluginSdkExports = Object.fromEntries( - Object.entries(packageJson.exports ?? {}).filter(([key]) => key.startsWith("./plugin-sdk")), - ); - - expect(currentPluginSdkExports).toEqual(buildPluginSdkPackageExports()); - }); -}); diff --git a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts index 87ae95acf366..b7fb0a715dc5 100644 --- a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts @@ -47,7 +47,6 @@ const DEPRECATED_EXTENSION_SDK_SPECIFIERS = new Set([ ]); const DEPRECATED_TEST_ALIAS_SPECIFIERS = new Set(["openclaw/plugin-sdk/test-utils"]); const DEPRECATED_TEST_ALIAS_ALLOWED_REFERENCE_FILES = new Set([ - "src/plugin-sdk/test-utils.ts", "src/plugins/compat/registry.ts", "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", ]); @@ -785,7 +784,8 @@ describe("plugin-sdk package contract guardrails", () => { const unknownSupported = [...supported].filter((entrypoint) => !entrypoints.has(entrypoint)); const unknownLocalOnly = [...localOnly].filter((entrypoint) => !entrypoints.has(entrypoint)); const unclassifiedBundledFacades = collectBundledFacadeSdkEntrypoints().filter( - (entrypoint) => !reserved.has(entrypoint) && !supported.has(entrypoint), + (entrypoint) => + !reserved.has(entrypoint) && !supported.has(entrypoint) && !localOnly.has(entrypoint), ); const unreservedPrivateSurfaces = collectPrivateBundledSdkSurfaceEntrypoints().filter( (entrypoint) => !reserved.has(entrypoint) && !localOnly.has(entrypoint), diff --git a/src/plugins/contracts/plugin-sdk-root-alias.test.ts b/src/plugins/contracts/plugin-sdk-root-alias.test.ts deleted file mode 100644 index 985acb9cf97a..000000000000 --- a/src/plugins/contracts/plugin-sdk-root-alias.test.ts +++ /dev/null @@ -1,965 +0,0 @@ -// Plugin SDK root alias tests cover package root alias compatibility for plugin authors. -import fs from "node:fs"; -import { createRequire } from "node:module"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import vm from "node:vm"; -import { expectDefined } from "@openclaw/normalization-core"; -import { describe, expect, it } from "vitest"; - -const require = createRequire(import.meta.url); -const rootAliasPath = fileURLToPath(new URL("../../plugin-sdk/root-alias.cjs", import.meta.url)); -const rootSdk = require(rootAliasPath) as Record; -const rootAliasSource = fs.readFileSync(rootAliasPath, "utf-8"); -const compatPath = fileURLToPath(new URL("../../plugin-sdk/compat.ts", import.meta.url)); -const packageJsonPath = fileURLToPath(new URL("../../../package.json", import.meta.url)); -const diagnosticEventsStateKey = Symbol.for("openclaw.diagnosticEvents.state.v1"); -const legacyRootExportNames = [ - "registerContextEngine", - "buildMemorySystemPromptAddition", - "delegateCompactionToRuntime", - "optionalStringEnum", - "stringEnum", - "buildChannelConfigSchema", - "normalizeAccountId", - "createReplyPrefixContext", - "createReplyPrefixOptions", - "createTypingCallbacks", - "createChannelReplyPipeline", - "resolveChannelSourceReplyDeliveryMode", - "resolvePreferredOpenClawTmpDir", -] as const; - -type EmptySchema = { - safeParse: (value: unknown) => - | { success: true; data?: unknown } - | { - success: false; - error: { issues: Array<{ path: Array; message: string }> }; - }; -}; - -type DiagnosticEventsStateFixture = { - listeners: Set<(event: { type: string }, metadata: { trusted: boolean }) => void>; -}; - -function requirePropertyDescriptor( - target: Record, - propertyName: string, -): PropertyDescriptor { - const descriptor = Object.getOwnPropertyDescriptor(target, propertyName); - if (!descriptor) { - throw new Error(`expected ${propertyName} property descriptor`); - } - return descriptor; -} - -function expectEnumerableConfigurableDescriptor( - target: Record, - propertyName: string, -): void { - const descriptor = requirePropertyDescriptor(target, propertyName); - expect(descriptor.configurable).toBe(true); - expect(descriptor.enumerable).toBe(true); -} - -function loadRootAliasWithStubs(options?: { - distExists?: boolean; - distEntries?: string[]; - env?: Record; - monolithicExports?: Record; - aliasPath?: string; - cwd?: string; - defaultTmpDir?: string; - packageExports?: Record; - platform?: string; - existingPaths?: string[]; - privateLocalOnlySubpaths?: unknown; - packageVersion?: string; -}) { - let createJitiCalls = 0; - let jitiLoadCalls = 0; - const createJitiOptions: Record[] = []; - const loadedSpecifiers: string[] = []; - const monolithicExports = options?.monolithicExports ?? { - slowHelper: () => "loaded", - }; - const context = { - process: { - env: options?.env ?? {}, - platform: options?.platform ?? "darwin", - cwd: () => options?.cwd ?? "/workdir", - }, - }; - const wrapper = vm.runInNewContext( - `(function (exports, require, module, __filename, __dirname) {${rootAliasSource}\n})`, - context, - { filename: rootAliasPath }, - ) as ( - exports: Record, - require: NodeJS.Require, - module: { exports: Record }, - filename: string, - dirname: string, - ) => void; - const module = { exports: {} as Record }; - const aliasPath = options?.aliasPath ?? rootAliasPath; - const localRequire = ((id: string) => { - if (id === "node:path") { - return path; - } - if (id === "node:fs") { - return { - readFileSync: (targetPath: string) => { - if ( - targetPath.endsWith( - path.join("scripts", "lib", "plugin-sdk-private-local-only-subpaths.json"), - ) - ) { - return JSON.stringify(options?.privateLocalOnlySubpaths ?? []); - } - return JSON.stringify({ - version: options?.packageVersion ?? "0.0.0-test", - exports: { - "./plugin-sdk/group-access": { default: "./dist/plugin-sdk/group-access.js" }, - ...options?.packageExports, - }, - }); - }, - statSync: () => ({ mtimeMs: 12_345, size: 678 }), - existsSync: (targetPath: string) => { - if (targetPath.endsWith(path.join("dist", "infra", "diagnostic-events.js"))) { - return options?.distExists ?? false; - } - if (options?.existingPaths?.includes(targetPath)) { - return true; - } - return options?.distExists ?? false; - }, - readdirSync: () => - (options?.distEntries ?? []).map((name) => ({ - name, - isFile: () => true, - isDirectory: () => false, - })), - }; - } - if (id === "node:os") { - return { - tmpdir: () => - context.process.env.TMPDIR ?? options?.defaultTmpDir ?? "/tmp/openclaw-root-alias-test", - }; - } - if (id === "jiti") { - return { - createJiti(_filename: string, jitiOptions?: Record) { - createJitiCalls += 1; - createJitiOptions.push(jitiOptions ?? {}); - return (specifier: string) => { - jitiLoadCalls += 1; - loadedSpecifiers.push(specifier); - return monolithicExports; - }; - }, - }; - } - throw new Error(`unexpected require: ${id}`); - }) as NodeJS.Require; - wrapper(module.exports, localRequire, module, aliasPath, path.dirname(aliasPath)); - return { - moduleExports: module.exports, - get createJitiCalls() { - return createJitiCalls; - }, - get jitiLoadCalls() { - return jitiLoadCalls; - }, - get createJitiOptions() { - return createJitiOptions; - }, - loadedSpecifiers, - globalContext: context as Record, - }; -} - -function createPackageRoot() { - return path.dirname(path.dirname(rootAliasPath)); -} - -function createDistAliasPath() { - return path.join(createPackageRoot(), "dist", "plugin-sdk", "root-alias.cjs"); -} - -function loadDiagnosticEventsAlias(distEntries: string[]) { - return loadRootAliasWithStubs({ - aliasPath: createDistAliasPath(), - distExists: false, - distEntries, - monolithicExports: { - r: (): (() => void) => () => undefined, - slowHelper: (): string => "loaded", - }, - }); -} - -function ensureDiagnosticEventsStateFixture( - context: Record, -): DiagnosticEventsStateFixture { - const existing = context[diagnosticEventsStateKey] as DiagnosticEventsStateFixture | undefined; - if (existing) { - return existing; - } - const state = vm.runInNewContext( - `({ - marker: Symbol.for("openclaw.diagnosticEvents.state.v1"), - enabled: true, - seq: 0, - listeners: new Set(), - dispatchDepth: 0, - asyncQueue: [], - asyncDrainScheduled: false, - asyncDroppedEvents: 0, - asyncDroppedTrustedEvents: 0, - asyncDroppedUntrustedEvents: 0, - asyncDroppedPriorityEvents: 0, - })`, - context, - ) as DiagnosticEventsStateFixture; - Object.defineProperty(context, diagnosticEventsStateKey, { - configurable: true, - enumerable: false, - value: state, - writable: false, - }); - return state; -} - -function requireDiagnosticEventsStateFixture( - lazyModule: ReturnType, -): DiagnosticEventsStateFixture { - const state = lazyModule.globalContext[diagnosticEventsStateKey] as - | DiagnosticEventsStateFixture - | undefined; - if (!state) { - throw new Error("expected diagnostic events state fixture"); - } - return state; -} - -function emitFixtureDiagnosticEvent(state: DiagnosticEventsStateFixture): void { - for (const registered of state.listeners) { - registered({ type: "model.usage" }, { trusted: false }); - } -} - -function expectDiagnosticEventAccessor(lazyModule: ReturnType) { - expect( - typeof (lazyModule.moduleExports.onDiagnosticEvent as (listener: () => void) => () => void)( - () => undefined, - ), - ).toBe("function"); -} - -function collectRuntimeExports(filePath: string, seen = new Set()): Set { - const normalizedPath = path.resolve(filePath); - if (seen.has(normalizedPath)) { - return new Set(); - } - seen.add(normalizedPath); - const source = fs.readFileSync(normalizedPath, "utf-8"); - const exportNames = new Set(); - - for (const match of source.matchAll(/export\s+(?:const|function|class)\s+([A-Za-z_$][\w$]*)/g)) { - exportNames.add(expectDefined(match[1], "match[1] test invariant")); - } - for (const match of source.matchAll(/export\s+(?!type\b)\{([\s\S]*?)\}\s+from\s+"([^"]+)";/g)) { - const names = expectDefined(match[1], "match[1] test invariant") - .split(",") - .map((part) => part.trim()) - .filter((part) => part.length > 0 && !part.startsWith("type ")) - .map( - (part) => - part - .split(/\s+as\s+/u) - .at(-1) - ?.trim() ?? part, - ); - for (const name of names) { - exportNames.add(name); - } - } - for (const match of source.matchAll(/export\s+\*\s+from\s+"([^"]+)";/g)) { - const specifier = expectDefined(match[1], "match[1] test invariant"); - if (!specifier.startsWith(".")) { - continue; - } - const nestedPath = path.resolve( - path.dirname(normalizedPath), - specifier.replace(/\.(?:mjs|js)$/u, ".ts"), - ); - const nestedExports = collectRuntimeExports(nestedPath, seen); - for (const name of nestedExports) { - exportNames.add(name); - } - } - - return exportNames; -} - -describe("plugin-sdk root alias", () => { - it("exposes the fast empty config schema helper", () => { - const factory = rootSdk.emptyPluginConfigSchema as (() => EmptySchema) | undefined; - if (!factory) { - throw new Error("expected empty config schema factory"); - } - const schema = factory(); - expect(schema.safeParse(undefined)).toEqual({ success: true, data: undefined }); - expect(schema.safeParse({})).toEqual({ success: true, data: {} }); - const parsed = schema.safeParse({ invalid: true }); - expect(parsed.success).toBe(false); - }); - - it("does not load the monolithic sdk for fast helpers", () => { - const lazyModule = loadRootAliasWithStubs(); - const lazyRootSdk = lazyModule.moduleExports; - const factory = lazyRootSdk.emptyPluginConfigSchema as (() => EmptySchema) | undefined; - - expect(lazyModule.createJitiCalls).toBe(0); - expect(lazyModule.jitiLoadCalls).toBe(0); - if (!factory) { - throw new Error("expected lazy empty config schema factory"); - } - expect(factory().safeParse({})).toEqual({ success: true, data: {} }); - expect(lazyModule.createJitiCalls).toBe(0); - expect(lazyModule.jitiLoadCalls).toBe(0); - }); - - it("does not load the monolithic sdk for promise-like or symbol reflection probes", () => { - const lazyModule = loadRootAliasWithStubs(); - const lazyRootSdk = lazyModule.moduleExports; - - expect("then" in lazyRootSdk).toBe(false); - expect(Reflect.get(lazyRootSdk, Symbol.toStringTag)).toBeUndefined(); - expect(Object.getOwnPropertyDescriptor(lazyRootSdk, Symbol.toStringTag)).toBeUndefined(); - expect(lazyModule.createJitiCalls).toBe(0); - expect(lazyModule.jitiLoadCalls).toBe(0); - }); - - it("loads legacy root exports on demand and preserves reflection", () => { - const lazyModule = loadRootAliasWithStubs({ - monolithicExports: { - slowHelper: (): string => "loaded", - }, - packageVersion: "3.4.5", - }); - const lazyRootSdk = lazyModule.moduleExports; - - expect(lazyModule.createJitiCalls).toBe(0); - expect("slowHelper" in lazyRootSdk).toBe(true); - expect(lazyModule.createJitiCalls).toBe(1); - expect(lazyModule.jitiLoadCalls).toBe(1); - expect(lazyModule.createJitiOptions.at(-1)?.tryNative).toBe(false); - expect(lazyModule.createJitiOptions.at(-1)?.fsCache).toBe( - path.join("/tmp/openclaw-root-alias-test", "jiti", "openclaw", "3.4.5", "12345-678"), - ); - expect((lazyRootSdk.slowHelper as () => string)()).toBe("loaded"); - expect(Object.keys(lazyRootSdk)).toContain("slowHelper"); - expectEnumerableConfigurableDescriptor(lazyRootSdk, "slowHelper"); - }); - - it("preserves jiti's tmpdir guard when root-alias TMPDIR resolves to cwd", () => { - const lazyModule = loadRootAliasWithStubs({ - cwd: "/tmp/openclaw-root-alias-cwd", - defaultTmpDir: "/tmp/openclaw-root-alias-fallback", - env: { TMPDIR: "/tmp/openclaw-root-alias-cwd" }, - packageVersion: "3.4.5", - }); - - expect("slowHelper" in lazyModule.moduleExports).toBe(true); - expect(lazyModule.createJitiOptions.at(-1)?.fsCache).toBe( - path.join("/tmp/openclaw-root-alias-fallback", "jiti", "openclaw", "3.4.5", "12345-678"), - ); - }); - - it("preserves jiti's fs cache environment opt-out for root alias", () => { - const lazyModule = loadRootAliasWithStubs({ - env: { JITI_FS_CACHE: "false" }, - packageVersion: "3.4.5", - }); - - expect("slowHelper" in lazyModule.moduleExports).toBe(true); - expect(lazyModule.createJitiOptions.at(-1)?.fsCache).toBe(false); - }); - - it.each([ - { - name: "prefers source loading when the source root alias runs in development", - options: { - distExists: true, - env: { NODE_ENV: "development" }, - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }, - expectedTryNative: false, - }, - { - name: "prefers native loading when compat resolves to dist", - options: { - distExists: true, - env: { NODE_ENV: "production" }, - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }, - expectedTryNative: true, - }, - { - name: "prefers source loading under vitest even when compat resolves to dist", - options: { - distExists: true, - env: { VITEST: "1" }, - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }, - expectedTryNative: false, - }, - { - name: "prefers native loading on Windows when compat resolves to dist", - options: { - distExists: true, - env: { NODE_ENV: "production" }, - platform: "win32", - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }, - expectedTryNative: true, - }, - ])("$name", ({ options, expectedTryNative }) => { - const lazyModule = loadRootAliasWithStubs(options); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - expect(lazyModule.createJitiOptions.at(-1)?.tryNative).toBe(expectedTryNative); - }); - - it("falls back to src files even when the alias itself is loaded from dist", () => { - const packageRoot = createPackageRoot(); - const distAliasPath = createDistAliasPath(); - const lazyModule = loadRootAliasWithStubs({ - aliasPath: distAliasPath, - distExists: false, - monolithicExports: { - onDiagnosticEvent: (): (() => void) => () => undefined, - slowHelper: (): string => "loaded", - }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - expect(lazyModule.loadedSpecifiers).toContain( - path.join(packageRoot, "src", "plugin-sdk", "compat.ts"), - ); - expect( - typeof (lazyModule.moduleExports.onDiagnosticEvent as (listener: () => void) => () => void)( - () => undefined, - ), - ).toBe("function"); - expect(lazyModule.loadedSpecifiers).toContain( - path.join(packageRoot, "src", "infra", "diagnostic-events.ts"), - ); - }); - - it("builds scoped and unscoped plugin-sdk aliases for jiti loads", () => { - const lazyModule = loadRootAliasWithStubs({ - distExists: true, - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - const aliasMap = (lazyModule.createJitiOptions.at(-1)?.alias ?? {}) as Record; - expect(aliasMap["openclaw/plugin-sdk"]).toBe(rootAliasPath); - expect(aliasMap["@openclaw/plugin-sdk"]).toBe(rootAliasPath); - expect(aliasMap["openclaw/plugin-sdk/group-access"]).toContain( - path.join("src", "plugin-sdk", "group-access.ts"), - ); - expect(aliasMap["@openclaw/plugin-sdk/group-access"]).toContain( - path.join("src", "plugin-sdk", "group-access.ts"), - ); - }); - - it("keeps agent-core package imports on the source graph when llm-core dist is missing", () => { - const packageRoot = path.dirname(path.dirname(path.dirname(rootAliasPath))); - const sourceLlmCorePath = path.join(packageRoot, "packages", "llm-core", "src", "index.ts"); - const lazyModule = loadRootAliasWithStubs({ - existingPaths: [sourceLlmCorePath], - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - const aliasMap = (lazyModule.createJitiOptions.at(-1)?.alias ?? {}) as Record; - expect(aliasMap["@openclaw/llm-core"]).toBe(sourceLlmCorePath); - }); - - it("keeps AI runtime transitive package imports on the source graph", () => { - const packageRoot = path.dirname(path.dirname(path.dirname(rootAliasPath))); - const sourcePaths = { - aiRetryAfter: path.join(packageRoot, "packages", "ai", "src", "internal", "retry-after.ts"), - aiRuntime: path.join(packageRoot, "packages", "ai", "src", "internal", "runtime.ts"), - codeSpans: path.join(packageRoot, "packages", "markdown-core", "src", "code-spans.ts"), - fences: path.join(packageRoot, "packages", "markdown-core", "src", "fences.ts"), - numberCoercion: path.join( - packageRoot, - "packages", - "normalization-core", - "src", - "number-coercion.ts", - ), - result: path.join(packageRoot, "packages", "normalization-core", "src", "result.ts"), - retry: path.join(packageRoot, "packages", "retry", "src", "index.ts"), - }; - const lazyModule = loadRootAliasWithStubs({ - existingPaths: Object.values(sourcePaths), - monolithicExports: { slowHelper: (): string => "loaded" }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - const aliasMap = (lazyModule.createJitiOptions.at(-1)?.alias ?? {}) as Record; - expect(aliasMap["@openclaw/ai/internal/retry-after"]).toBe(sourcePaths.aiRetryAfter); - expect(aliasMap["@openclaw/ai/internal/runtime"]).toBe(sourcePaths.aiRuntime); - expect(aliasMap["@openclaw/markdown-core/code-spans"]).toBe(sourcePaths.codeSpans); - expect(aliasMap["@openclaw/markdown-core/fences"]).toBe(sourcePaths.fences); - expect(aliasMap["@openclaw/normalization-core/number-coercion"]).toBe( - sourcePaths.numberCoercion, - ); - expect(aliasMap["@openclaw/normalization-core/result"]).toBe(sourcePaths.result); - expect(aliasMap["@openclaw/retry"]).toBe(sourcePaths.retry); - }); - - it("keeps bootstrap plugin-sdk aliases deterministic and ignores unsafe subpaths", () => { - const lazyModule = loadRootAliasWithStubs({ - distExists: true, - packageExports: { - "./plugin-sdk/zeta": { default: "./dist/plugin-sdk/zeta.js" }, - "./plugin-sdk/../escape": { default: "./dist/plugin-sdk/escape.js" }, - "./plugin-sdk/alpha": { default: "./dist/plugin-sdk/alpha.js" }, - }, - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - const aliasKeys = Object.keys( - (lazyModule.createJitiOptions.at(-1)?.alias ?? {}) as Record, - ); - expect(aliasKeys).toEqual([ - "openclaw/plugin-sdk/alpha", - "@openclaw/plugin-sdk/alpha", - "openclaw/plugin-sdk/group-access", - "@openclaw/plugin-sdk/group-access", - "openclaw/plugin-sdk/zeta", - "@openclaw/plugin-sdk/zeta", - "@openclaw/llm-core", - "@openclaw/llm-core/diagnostics", - "@openclaw/llm-core/event-stream", - "@openclaw/llm-core/types", - "@openclaw/llm-core/validation", - "@openclaw/ai", - "@openclaw/ai/providers", - "@openclaw/ai/diagnostics", - "@openclaw/ai/event-stream", - "@openclaw/ai/types", - "@openclaw/ai/validation", - "@openclaw/ai/internal/anthropic", - "@openclaw/ai/internal/openai", - "@openclaw/ai/internal/retry-after", - "@openclaw/ai/internal/runtime", - "@openclaw/ai/internal/shared", - "@openclaw/markdown-core", - "@openclaw/markdown-core/code-spans", - "@openclaw/markdown-core/fences", - "@openclaw/markdown-core/frontmatter", - "@openclaw/markdown-core/ir", - "@openclaw/markdown-core/render", - "@openclaw/markdown-core/render-aware-chunking", - "@openclaw/markdown-core/tables", - "@openclaw/markdown-core/types", - "@openclaw/normalization-core", - "@openclaw/normalization-core/boolean-coercion", - "@openclaw/normalization-core/error-coercion", - "@openclaw/normalization-core/number-coercion", - "@openclaw/normalization-core/record-coerce", - "@openclaw/normalization-core/result", - "@openclaw/normalization-core/string-coerce", - "@openclaw/normalization-core/string-normalization", - "@openclaw/normalization-core/utf16-slice", - "@openclaw/retry", - "openclaw/plugin-sdk", - "@openclaw/plugin-sdk", - ]); - }); - - it("ignores unsafe private local-only plugin-sdk subpaths in the CJS root alias", () => { - const packageRoot = path.dirname(path.dirname(path.dirname(rootAliasPath))); - const qaLabPath = path.join(packageRoot, "src", "plugin-sdk", "qa-lab.ts"); - const ssrfRuntimeInternalPath = path.join( - packageRoot, - "src", - "plugin-sdk", - "ssrf-runtime-internal.ts", - ); - const lazyModule = loadRootAliasWithStubs({ - env: { OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1" }, - privateLocalOnlySubpaths: ["qa-lab", "../escape", "nested/path", "ssrf-runtime-internal"], - existingPaths: [qaLabPath, ssrfRuntimeInternalPath], - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - const aliasMap = (lazyModule.createJitiOptions.at(-1)?.alias ?? {}) as Record; - expect(aliasMap["openclaw/plugin-sdk/qa-lab"]).toBe(qaLabPath); - expect(aliasMap["@openclaw/plugin-sdk/qa-lab"]).toBe(qaLabPath); - expect(aliasMap).not.toHaveProperty("openclaw/plugin-sdk/../escape"); - expect(aliasMap).not.toHaveProperty("openclaw/plugin-sdk/nested/path"); - expect(aliasMap).not.toHaveProperty("openclaw/plugin-sdk/ssrf-runtime-internal"); - expect(aliasMap).not.toHaveProperty("@openclaw/plugin-sdk/ssrf-runtime-internal"); - }); - - it("keeps non-QA private local-only plugin-sdk subpaths out of the CJS root alias", () => { - const packageRoot = path.dirname(path.dirname(path.dirname(rootAliasPath))); - const sourceCodexMcpProjectionPath = path.join( - packageRoot, - "src", - "plugin-sdk", - "codex-mcp-projection.ts", - ); - const sourceQaRuntimePath = path.join(packageRoot, "src", "plugin-sdk", "qa-runtime.ts"); - const lazyModule = loadRootAliasWithStubs({ - privateLocalOnlySubpaths: ["codex-mcp-projection", "qa-runtime"], - existingPaths: [sourceCodexMcpProjectionPath, sourceQaRuntimePath], - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - const aliasMap = (lazyModule.createJitiOptions.at(-1)?.alias ?? {}) as Record; - expect(aliasMap).not.toHaveProperty("openclaw/plugin-sdk/codex-mcp-projection"); - expect(aliasMap).not.toHaveProperty("@openclaw/plugin-sdk/codex-mcp-projection"); - expect(aliasMap).not.toHaveProperty("openclaw/plugin-sdk/qa-runtime"); - }); - - it("builds source plugin-sdk subpath aliases through the wider source extension family", () => { - const packageRoot = path.dirname(path.dirname(path.dirname(rootAliasPath))); - const lazyModule = loadRootAliasWithStubs({ - packageExports: { - "./plugin-sdk/channel-runtime": { default: "./dist/plugin-sdk/channel-runtime.js" }, - }, - existingPaths: [path.join(packageRoot, "src", "plugin-sdk", "channel-runtime.mts")], - monolithicExports: { - slowHelper: (): string => "loaded", - }, - }); - - expect((lazyModule.moduleExports.slowHelper as () => string)()).toBe("loaded"); - const aliasMap = (lazyModule.createJitiOptions.at(-1)?.alias ?? {}) as Record; - expect(aliasMap["openclaw/plugin-sdk/channel-runtime"]).toBe( - path.join(packageRoot, "src", "plugin-sdk", "channel-runtime.mts"), - ); - expect(aliasMap["@openclaw/plugin-sdk/channel-runtime"]).toBe( - path.join(packageRoot, "src", "plugin-sdk", "channel-runtime.mts"), - ); - }); - - it("prefers hashed dist diagnostic events chunks before falling back to src", () => { - const packageRoot = createPackageRoot(); - const lazyModule = loadDiagnosticEventsAlias(["diagnostic-events-W3Hz61fI.js"]); - - expectDiagnosticEventAccessor(lazyModule); - expect(lazyModule.loadedSpecifiers).toContain( - path.join(packageRoot, "dist", "diagnostic-events-W3Hz61fI.js"), - ); - expect(lazyModule.loadedSpecifiers).not.toContain( - path.join(packageRoot, "src", "infra", "diagnostic-events.ts"), - ); - }); - - it("chooses hashed dist diagnostic events chunks deterministically", () => { - const packageRoot = createPackageRoot(); - const lazyModule = loadDiagnosticEventsAlias([ - "diagnostic-events-zeta.js", - "diagnostic-events-alpha.js", - ]); - - expectDiagnosticEventAccessor(lazyModule); - expect(lazyModule.loadedSpecifiers).toContain( - path.join(packageRoot, "dist", "diagnostic-events-alpha.js"), - ); - expect(lazyModule.loadedSpecifiers).not.toContain( - path.join(packageRoot, "dist", "diagnostic-events-zeta.js"), - ); - }); - - it("does not depend on single-letter bundled export aliases", () => { - expect(rootAliasSource).not.toMatch(/\bmod\.[A-Za-z_$]\b/u); - }); - - it("resolves the diagnostic event export by function name when dist aliases shift", () => { - let subscribeCount = 0; - let unsubscribeCount = 0; - const lazyModule = loadRootAliasWithStubs({ - aliasPath: createDistAliasPath(), - distEntries: ["diagnostic-events-W3Hz61fI.js"], - monolithicExports: { - r: function emitFailoverEvent(): void { - throw new Error("wrong diagnostic event alias selected"); - }, - u: function onDiagnosticEvent(_listener: () => void): () => void { - subscribeCount += 1; - return () => { - unsubscribeCount += 1; - }; - }, - }, - }); - - const unsubscribe = ( - lazyModule.moduleExports.onDiagnosticEvent as ( - listener: (event: { type: string }) => void, - ) => () => void - )(() => undefined); - unsubscribe(); - - expect(subscribeCount).toBe(1); - expect(unsubscribeCount).toBe(1); - }); - - it("falls back and removes stale diagnostic listeners when the dist subscription is invalid", () => { - const seen: string[] = []; - const preexistingListener = (): void => undefined; - const lazyModule: ReturnType = loadRootAliasWithStubs({ - aliasPath: createDistAliasPath(), - distEntries: ["diagnostic-events-W3Hz61fI.js"], - monolithicExports: { - onDiagnosticEvent(listener: (event: { type: string }) => void): undefined { - const state = ensureDiagnosticEventsStateFixture(lazyModule.globalContext); - state.listeners.add((event, metadata) => { - if (!metadata.trusted) { - listener(event); - } - }); - return undefined; - }, - }, - }); - const state = ensureDiagnosticEventsStateFixture(lazyModule.globalContext); - state.listeners.add(preexistingListener); - - const unsubscribe = ( - lazyModule.moduleExports.onDiagnosticEvent as ( - listener: (event: { type: string }) => void, - ) => () => void - )((event) => { - seen.push(event.type); - }); - - expect(state.listeners.size).toBe(2); - expect(state.listeners.has(preexistingListener)).toBe(true); - emitFixtureDiagnosticEvent(state); - unsubscribe(); - - expect(seen).toEqual(["model.usage"]); - expect(state.listeners.size).toBe(1); - expect(state.listeners.has(preexistingListener)).toBe(true); - }); - - it("falls back to shared diagnostic state when the dist subscription throws", () => { - const seen: string[] = []; - let subscribeCount = 0; - const lazyModule = loadRootAliasWithStubs({ - aliasPath: createDistAliasPath(), - distEntries: ["diagnostic-events-W3Hz61fI.js"], - monolithicExports: { - onDiagnosticEvent(): never { - subscribeCount += 1; - throw new Error("stale diagnostic subscription"); - }, - }, - }); - - const unsubscribe = ( - lazyModule.moduleExports.onDiagnosticEvent as ( - listener: (event: { type: string }) => void, - ) => () => void - )((event) => { - seen.push(event.type); - }); - const state = requireDiagnosticEventsStateFixture(lazyModule); - - expect(subscribeCount).toBe(1); - expect(state.listeners.size).toBe(1); - emitFixtureDiagnosticEvent(state); - unsubscribe(); - - expect(seen).toEqual(["model.usage"]); - expect(state.listeners.size).toBe(0); - }); - - it("removes the shared-state fallback listener when diagnostic cleanup throws", () => { - let diagnosticUnsubscribeCount = 0; - const lazyModule = loadRootAliasWithStubs({ - aliasPath: createDistAliasPath(), - distEntries: ["diagnostic-events-W3Hz61fI.js"], - monolithicExports: { - onDiagnosticEvent(): () => void { - return () => { - diagnosticUnsubscribeCount += 1; - throw new Error("diagnostic cleanup failed"); - }; - }, - }, - }); - - const unsubscribe = ( - lazyModule.moduleExports.onDiagnosticEvent as ( - listener: (event: { type: string }) => void, - ) => () => void - )(() => undefined); - const state = requireDiagnosticEventsStateFixture(lazyModule); - - expect(state.listeners.size).toBe(1); - expect(() => unsubscribe()).toThrow("diagnostic cleanup failed"); - expect(diagnosticUnsubscribeCount).toBe(1); - expect(state.listeners.size).toBe(0); - }); - - it("bridges diagnostic listeners through shared process state when the lazy module is isolated", () => { - const seen: string[] = []; - const lazyModule = loadDiagnosticEventsAlias(["diagnostic-events-W3Hz61fI.js"]); - const unsubscribe = ( - lazyModule.moduleExports.onDiagnosticEvent as ( - listener: (event: { type: string }) => void, - ) => () => void - )((event) => { - seen.push(event.type); - }); - const state = lazyModule.globalContext[Symbol.for("openclaw.diagnosticEvents.state.v1")] as { - listeners: Set<(event: { type: string }, metadata: { trusted: boolean }) => void>; - }; - - for (const listener of state.listeners) { - listener({ type: "model.usage" }, { trusted: false }); - listener({ type: "log.record" }, { trusted: false }); - listener({ type: "model.usage" }, { trusted: true }); - } - unsubscribe(); - - expect(seen).toEqual(["model.usage"]); - expect(state.listeners.size).toBe(0); - }); - - it.each([ - { - name: "forwards delegateCompactionToRuntime through the compat-backed root alias", - exportName: "delegateCompactionToRuntime", - exportValue: () => "delegated", - expectIdentity: true, - assertForwarded: (value: unknown) => { - if (typeof value !== "function") { - throw new Error("expected delegateCompactionToRuntime export"); - } - expect((value as () => string)()).toBe("delegated"); - }, - }, - { - name: "forwards onDiagnosticEvent through the compat-backed root alias", - exportName: "onDiagnosticEvent", - exportValue: () => () => undefined, - expectIdentity: false, - assertForwarded: (value: unknown) => { - if (typeof value !== "function") { - throw new Error("expected onDiagnosticEvent export"); - } - const unsubscribe = (value as (listener: () => void) => () => void)(() => undefined); - unsubscribe(); - }, - }, - ])("$name", ({ exportName, exportValue, expectIdentity, assertForwarded }) => { - const lazyModule = loadRootAliasWithStubs({ - monolithicExports: { - [exportName]: exportValue, - }, - }); - const forwarded = lazyModule.moduleExports[exportName]; - - assertForwarded(forwarded); - if (expectIdentity) { - expect(forwarded).toBe(exportValue); - } - expect(exportName in lazyModule.moduleExports).toBe(true); - }); - - it("forwards legacy root exports through the merged root wrapper", () => { - const monolithicExports = Object.fromEntries( - legacyRootExportNames.map((name) => [name, () => name]), - ); - const lazyModule = loadRootAliasWithStubs({ monolithicExports }); - - expect(rootSdk.emptyPluginConfigSchema).toBeTypeOf("function"); - expect(rootSdk.resolveControlCommandGate).toBeTypeOf("function"); - expect(rootSdk.onDiagnosticEvent).toBeTypeOf("function"); - - for (const name of legacyRootExportNames) { - expect(lazyModule.moduleExports[name]).toBe(monolithicExports[name]); - } - expect(lazyModule.jitiLoadCalls).toBe(1); - const exportKeys = Object.keys(lazyModule.moduleExports); - for (const name of legacyRootExportNames) { - expect(exportKeys).toContain(name); - } - expect(typeof rootSdk.default).toBe("object"); - expect(rootSdk.default).toBe(rootSdk); - expect(rootSdk["__esModule"]).toBe(true); - }); - - it("keeps legacy root export names present in the compat source", () => { - const compatExports = collectRuntimeExports(compatPath); - for (const name of legacyRootExportNames) { - expect(compatExports.has(name)).toBe(true); - } - }); - - it("does not publish private local-only plugin-sdk subpaths", () => { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as { - exports?: Record; - }; - const privateSubpathsPath = path.join( - path.dirname(packageJsonPath), - "scripts", - "lib", - "plugin-sdk-private-local-only-subpaths.json", - ); - const privateSubpaths = JSON.parse(fs.readFileSync(privateSubpathsPath, "utf-8")) as string[]; - - for (const subpath of privateSubpaths) { - expect(packageJson.exports?.[`./plugin-sdk/${subpath}`]).toBeUndefined(); - } - }); - - it("preserves reflection semantics for lazily resolved exports", { timeout: 240_000 }, () => { - expect("resolveControlCommandGate" in rootSdk).toBe(true); - expect("onDiagnosticEvent" in rootSdk).toBe(true); - const keys = Object.keys(rootSdk); - expect(keys).toContain("resolveControlCommandGate"); - expect(keys).toContain("onDiagnosticEvent"); - expectEnumerableConfigurableDescriptor(rootSdk, "resolveControlCommandGate"); - expect(typeof requirePropertyDescriptor(rootSdk, "onDiagnosticEvent").value).toBe("function"); - }); -}); diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index 31fa0fdcaf0d..4cf69f35eff4 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -16,19 +16,16 @@ import type { ChannelThreadingContext as ContractChannelThreadingContext, ChannelThreadingToolContext as ContractChannelThreadingToolContext, } from "openclaw/plugin-sdk/channel-contract"; -import * as commandAuthSdk from "openclaw/plugin-sdk/command-auth"; import type { ChannelMessageActionContext as CoreChannelMessageActionContext, OpenClawPluginApi as CoreOpenClawPluginApi, PluginRuntime as CorePluginRuntime, } from "openclaw/plugin-sdk/core"; -import * as providerEntrySdk from "openclaw/plugin-sdk/provider-entry"; import type { GetReplyOptions as ReplyRuntimeGetReplyOptions, ReplyDispatchBeforeDeliverOptions as ReplyRuntimeBeforeDeliverOptions, ReplyDispatcher as ReplyRuntimeDispatcher, } from "openclaw/plugin-sdk/reply-runtime"; -import * as zalouserSdk from "openclaw/plugin-sdk/zalouser"; import ts from "typescript"; import { beforeAll, describe, expect, expectTypeOf, it } from "vitest"; import type { ChannelMessageActionContext } from "../../channels/plugins/types.public.js"; @@ -55,8 +52,6 @@ import type { import * as channelReplyPipelineDirectSdk from "../../plugin-sdk/channel-reply-pipeline.js"; import * as coreDirectSdk from "../../plugin-sdk/core.js"; import { publicPluginSdkSubpaths as pluginSdkSubpaths } from "../../plugin-sdk/entrypoints.js"; -import * as globalSingletonDirectSdk from "../../plugin-sdk/global-singleton.js"; -import * as providerEntryDirectSdk from "../../plugin-sdk/provider-entry.js"; import { expectNoReaddirSyncDuring } from "../../test-utils/fs-scan-assertions.js"; import { listGitTrackedFiles, toRepoRelativePath } from "../../test-utils/repo-files.js"; import type { PluginRuntime } from "../runtime/types.js"; @@ -67,7 +62,10 @@ const REPO_ROOT = resolve(SRC_ROOT, ".."); const PLUGIN_SDK_DIR = resolve(SRC_ROOT, "plugin-sdk"); const sourceCache = new Map(); const repoTsFilesCache = new Map(); -const representativeRuntimeSmokeSubpaths = ["channel-runtime", "conversation-runtime"] as const; +const representativeRuntimeSmokeSubpaths = [ + "channel-runtime-context", + "conversation-runtime", +] as const; const PUBLIC_SDK_TEST_HELPER_SUBPATHS = [ "agent-runtime-test-contracts", "channel-contract-testing", @@ -498,11 +496,7 @@ describe("plugin-sdk subpath exports", () => { ], pattern: /(?:from\s+|import\s+(?:type\s+)?|import\s*\(\s*)["']openclaw\/plugin-sdk\/channel-runtime(?=["'])/u, - exclude: [ - "src/plugins/compat/registry.ts", - "src/plugins/sdk-alias.test.ts", - "src/plugins/contracts/plugin-sdk-root-alias.test.ts", - ], + exclude: ["src/plugins/compat/registry.ts", "src/plugins/sdk-alias.test.ts"], }); }); @@ -627,14 +621,6 @@ describe("plugin-sdk subpath exports", () => { "createChannelHistoryWindow", "recordPendingHistoryEntryIfEnabled", ]); - expectSourceMentions("mattermost", [ - "buildPendingHistoryContextFromMap", - "clearHistoryEntriesIfEnabled", - "createChannelHistoryWindow", - "formatPairingApproveHint", - "recordPendingHistoryEntryIfEnabled", - "resolveControlCommandGate", - ]); expectSourceMentions("matrix", ["runPluginCommandWithTimeout"]); expectSourceContract("reply-runtime", { omits: [ @@ -647,12 +633,7 @@ describe("plugin-sdk subpath exports", () => { ], }); expectSourceMentions("account-helpers", ["createAccountListHelpers"]); - expectSourceMentions("channel-actions", [ - "optionalStringEnum", - "stringEnum", - "createMessageToolButtonsSchema", - "createMessageToolCardSchema", - ]); + expectSourceMentions("channel-actions", ["optionalStringEnum", "stringEnum"]); expectSourceContract("channel-secret-basic-runtime", { mentions: [ "collectSimpleChannelFieldAssignments", @@ -734,15 +715,6 @@ describe("plugin-sdk subpath exports", () => { mentions: ["extractToolPayload", "ToolPayloadCarrier"], omits: ["createAnthropicToolPayloadCompatibilityWrapper", "extractToolSend"], }); - expectSourceMentions("compat", [ - "createPluginRuntimeStore", - "createScopedChannelConfigAdapter", - "collectOpenGroupPolicyConfiguredRouteWarnings", - "resolveControlCommandGate", - "delegateCompactionToRuntime", - "createReplyPrefixContext", - "createChannelReplyPipeline", - ]); expectSourceMentions("device-bootstrap", [ "approveDevicePairing", "issueDeviceBootstrapToken", @@ -941,8 +913,8 @@ describe("plugin-sdk subpath exports", () => { expect(matches).toStrictEqual([]); }); - it("exports channel runtime helpers from the dedicated subpath", () => { - expectSourceOmits("channel-runtime", [ + it("keeps channel runtime helpers on focused subpaths", () => { + expectSourceOmits("channel-runtime-context", [ "applyChannelMatchMeta", "createChannelDirectoryAdapter", "createEmptyChannelDirectoryAdapter", @@ -1057,8 +1029,6 @@ describe("plugin-sdk subpath exports", () => { "resolveInboundDebounceMs", "resolveEnvelopeFormatOptions", "resolveInboundSessionEnvelopeContext", - "resolveMentionGating", - "resolveMentionGatingWithBypass", "shouldDebounceTextInbound", "toLocationContext", ]); @@ -1089,13 +1059,7 @@ describe("plugin-sdk subpath exports", () => { "createTopLevelChannelDmPolicy", ]); expectSourceContract("channel-actions", { - mentions: [ - "createUnionActionGate", - "listTokenSourcedAccounts", - "resolveReactionMessageId", - "createMessageToolButtonsSchema", - "createMessageToolCardSchema", - ], + mentions: ["createUnionActionGate", "listTokenSourcedAccounts", "resolveReactionMessageId"], }); expectSourceMentions("channel-targets", [ "applyChannelMatchMeta", @@ -1139,17 +1103,8 @@ describe("plugin-sdk subpath exports", () => { "isRecord", "resolveEnabledConfiguredAccountId", ]); - expectSourceMentions("outbound-runtime", [ - "createRuntimeOutboundDelegates", - "resolveOutboundSendDep", - "resolveAgentOutboundIdentity", - ]); expectSourceMentions("command-auth", [ "buildCommandTextFromArgs", - "buildCommandsMessage", - "buildCommandsMessagePaginated", - "buildCommandsPaginationKeyboard", - "buildHelpMessage", "buildModelsProviderData", "hasControlCommand", "listNativeCommandSpecsForConfig", @@ -1268,17 +1223,6 @@ describe("plugin-sdk subpath exports", () => { ]); expectSourceMentions("setup-tools", ["formatCliCommand", "detectBinary", "formatDocsLink"]); expectSourceMentions("lazy-runtime", ["createLazyRuntimeSurface", "createLazyRuntimeModule"]); - expectSourceContract("self-hosted-provider-setup", { - mentions: [ - "applyProviderDefaultModel", - "discoverOpenAICompatibleLocalModels", - "discoverOpenAICompatibleSelfHostedProvider", - "configureOpenAICompatibleSelfHostedProviderNonInteractive", - ], - omits: ["buildVllmProvider", "buildSglangProvider"], - }); - expectSourceOmitsImportPattern("self-hosted-provider-setup", "./vllm.js"); - expectSourceOmitsImportPattern("self-hosted-provider-setup", "./sglang.js"); expectSourceOmitsSnippet("agent-runtime", "./sglang.js"); expectSourceOmitsSnippet("agent-runtime", "./vllm.js"); expectSourceOmitsSnippet("agent-runtime", "../../extensions/"); @@ -1378,9 +1322,6 @@ describe("plugin-sdk subpath exports", () => { const channelActionsSdk = await importResolvedPluginSdkSubpath( "openclaw/plugin-sdk/channel-actions", ); - const globalSingletonSdk = await importResolvedPluginSdkSubpath( - "openclaw/plugin-sdk/global-singleton", - ); const pluginEntrySdk = await importResolvedPluginSdkSubpath("openclaw/plugin-sdk/plugin-entry"); const channelLifecycleSdk = await importResolvedPluginSdkSubpath( "openclaw/plugin-sdk/channel-lifecycle", @@ -1403,15 +1344,6 @@ describe("plugin-sdk subpath exports", () => { ); expect(channelActionsSdk.optionalStringEnum).toBe(channelActionsDirectSdk.optionalStringEnum); expect(channelActionsSdk.stringEnum).toBe(channelActionsDirectSdk.stringEnum); - expect(globalSingletonSdk.resolveGlobalMap).toBe(globalSingletonDirectSdk.resolveGlobalMap); - expect(globalSingletonSdk.resolveGlobalSingleton).toBe( - globalSingletonDirectSdk.resolveGlobalSingleton, - ); - expect(globalSingletonSdk.createScopedExpiringIdCache).toBe( - globalSingletonDirectSdk.createScopedExpiringIdCache, - ); - expectSourceMentions("delivery-queue-runtime", ["drainPendingDeliveries"]); - expectSourceContains("delivery-queue-runtime", "../infra/outbound/deliver-runtime.js"); expectSourceMentions("error-runtime", [ "formatUncaughtError", "isApprovalNotFoundError", @@ -1487,17 +1419,5 @@ describe("plugin-sdk subpath exports", () => { ); expect(replyChunkingSdk.isSilentReplyText("Visible update\n\nNO_REPLY")).toBe(false); }); - - it("keeps the Zalouser command-auth compatibility facade importable", () => { - expect(zalouserSdk.resolveSenderCommandAuthorization).toBe( - commandAuthSdk.resolveSenderCommandAuthorization, - ); - }); - - it("exports single-provider plugin entry helpers from the dedicated subpath", () => { - expect(providerEntrySdk.defineSingleProviderPluginEntry).toBe( - providerEntryDirectSdk.defineSingleProviderPluginEntry, - ); - }); }); /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/plugins/contracts/registry.ts b/src/plugins/contracts/registry.ts index 49c9d60631fd..e40a09d0654b 100644 --- a/src/plugins/contracts/registry.ts +++ b/src/plugins/contracts/registry.ts @@ -41,15 +41,11 @@ function normalizeProviderEnvVars( function resolvePluginProviderEnvVars(plugin: { setup?: { providers?: Array<{ id: string; envVars?: string[] }> }; - providerAuthEnvVars?: Record; }): Record { const envVars: Record = {}; for (const provider of plugin.setup?.providers ?? []) { envVars[provider.id] = uniqueStrings(provider.envVars ?? []); } - for (const [providerId, keys] of Object.entries(plugin.providerAuthEnvVars ?? {})) { - envVars[providerId] = uniqueStrings([...(envVars[providerId] ?? []), ...keys]); - } return normalizeProviderEnvVars(envVars); } diff --git a/src/plugins/contracts/shape.contract.test.ts b/src/plugins/contracts/shape.contract.test.ts index 3d5794878e97..cc323f13127a 100644 --- a/src/plugins/contracts/shape.contract.test.ts +++ b/src/plugins/contracts/shape.contract.test.ts @@ -7,18 +7,16 @@ import { describe, expect, it } from "vitest"; import { buildPluginShapeSummary } from "../inspect-shape.js"; describe("plugin shape compatibility matrix", () => { - it("keeps legacy hook-only, plain capability, and hybrid capability shapes explicit", () => { + it("keeps hook-only, plain capability, and hybrid capability shapes explicit", () => { const { config, registry } = createPluginRegistryFixture(); registerVirtualTestPlugin({ registry, config, - id: "lca-legacy", - name: "LCA Legacy", + id: "hook-only", + name: "Hook Only", register(api) { - api.on("before_agent_start", () => ({ - prependContext: "legacy", - })); + api.on("before_prompt_build", () => ({ prependContext: "hook-only" })); }, }); @@ -135,7 +133,7 @@ describe("plugin shape compatibility matrix", () => { })), ).toEqual([ { - id: "lca-legacy", + id: "hook-only", shape: "hook-only", capabilityMode: "none", }, @@ -166,7 +164,6 @@ describe("plugin shape compatibility matrix", () => { }, ]); - expect(inspect[0]?.usesLegacyBeforeAgentStart).toBe(true); expect(inspect.map((entry) => entry.capabilities.map((capability) => capability.kind))).toEqual( [ [], diff --git a/src/plugins/discovery.test.ts b/src/plugins/discovery.test.ts index b77eb45d8414..8f9f7b16f85f 100644 --- a/src/plugins/discovery.test.ts +++ b/src/plugins/discovery.test.ts @@ -764,73 +764,6 @@ describe("discoverOpenClawPlugins", () => { expect(diagnostics).toStrictEqual([]); }); - it("ignores packaged bundled plugin paths in configured load paths", () => { - const stateDir = makeTempDir(); - const packageRoot = path.join(stateDir, "node_modules", "openclaw"); - const bundledRoot = path.join(packageRoot, "dist", "extensions"); - const bundledPluginDir = path.join(bundledRoot, "feishu"); - mkdirSafe(bundledPluginDir); - writePluginManifest({ pluginDir: bundledPluginDir, id: "feishu" }); - writePluginEntry(path.join(bundledPluginDir, "index.js")); - - const { candidates, diagnostics } = withOpenClawPackageArgv(packageRoot, () => - discoverOpenClawPlugins({ - extraPaths: [bundledPluginDir], - env: { - ...buildDiscoveryEnv(stateDir), - OPENCLAW_DISABLE_BUNDLED_PLUGINS: undefined, - OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot, - }, - }), - ); - - expectCandidateFields(findCandidateById(candidates, "feishu"), { origin: "bundled" }); - expect(countMatching(candidates, (candidate) => candidate.idHint === "feishu")).toBe(1); - expect(diagnostics).toHaveLength(1); - expectDiagnostic({ - diagnostics, - level: "warn", - source: bundledPluginDir, - messageIncludes: "ignored plugins.load.paths entry", - }); - }); - - it("ignores legacy bundled plugin load paths that would shadow packaged bundled plugins", () => { - const stateDir = makeTempDir(); - const packageRoot = path.join(stateDir, "node_modules", "openclaw"); - const bundledRoot = path.join(packageRoot, "dist-runtime", "extensions"); - const bundledPluginDir = path.join(bundledRoot, "telegram"); - const legacyPluginDir = path.join(packageRoot, "extensions", "telegram"); - mkdirSafe(bundledPluginDir); - mkdirSafe(legacyPluginDir); - mkdirSafe(path.join(packageRoot, "dist", "extensions")); - writePluginManifest({ pluginDir: bundledPluginDir, id: "telegram" }); - writePluginManifest({ pluginDir: legacyPluginDir, id: "telegram" }); - writePluginEntry(path.join(bundledPluginDir, "index.js")); - writePluginEntry(path.join(legacyPluginDir, "index.js")); - - const { candidates, diagnostics } = withOpenClawPackageArgv(packageRoot, () => - discoverOpenClawPlugins({ - extraPaths: [legacyPluginDir], - env: { - ...buildDiscoveryEnv(stateDir), - OPENCLAW_DISABLE_BUNDLED_PLUGINS: undefined, - OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot, - }, - }), - ); - - expectCandidateFields(findCandidateById(candidates, "telegram"), { origin: "bundled" }); - expect(countMatching(candidates, (candidate) => candidate.idHint === "telegram")).toBe(1); - expect(diagnostics).toHaveLength(1); - expectDiagnostic({ - diagnostics, - level: "warn", - source: legacyPluginDir, - messageIncludes: "legacy bundled plugin directory", - }); - }); - it("discovers bind-mounted bundled source overlays before packaged dist bundles", () => { const stateDir = makeTempDir(); const packageRoot = path.join(stateDir, "node_modules", "openclaw"); diff --git a/src/plugins/discovery.ts b/src/plugins/discovery.ts index aaad1c974e29..537c6125e64f 100644 --- a/src/plugins/discovery.ts +++ b/src/plugins/discovery.ts @@ -13,10 +13,7 @@ import { resolveUserPath } from "../utils.js"; import { resolveCompatibilityHostVersion } from "../version.js"; import { detectBundleManifestFormat, loadBundleManifest } from "./bundle-manifest.js"; import { resolveSourceCheckoutDependencyDiagnostic } from "./bundled-dir.js"; -import { - buildLegacyBundledRootPath, - resolvePackagedBundledLoadPathAlias, -} from "./bundled-load-path-aliases.js"; +import { buildLegacyBundledRootPath } from "./bundled-load-path-aliases.js"; import { listBundledSourceOverlayDirs } from "./bundled-source-overlays.js"; import { shouldRejectHardlinkedPluginFiles } from "./hardlink-policy.js"; import { readLegacyNpmPluginDeclaration } from "./legacy-npm-declaration.js"; @@ -1469,18 +1466,6 @@ function discoverConfiguredPluginLoadPathsInto(params: { if (!trimmed) { continue; } - const bundledAlias = resolvePackagedBundledLoadPathAlias({ - bundledRoot: params.bundledRoot, - loadPath: resolveUserPath(trimmed, params.env), - }); - if (bundledAlias) { - params.result.diagnostics.push({ - level: "warn", - source: trimmed, - message: `ignored plugins.load.paths entry that points at OpenClaw's ${bundledAlias.kind} bundled plugin directory; remove this redundant path or run openclaw doctor --fix`, - }); - continue; - } discoverFromPath({ rawPath: trimmed, origin: "config", diff --git a/src/plugins/hook-before-agent-start.types.ts b/src/plugins/hook-before-agent-start.types.ts index a96ceeeb5cf4..5c59f4c62013 100644 --- a/src/plugins/hook-before-agent-start.types.ts +++ b/src/plugins/hook-before-agent-start.types.ts @@ -40,51 +40,3 @@ export type PluginHookBeforePromptBuildResult = { */ appendSystemContext?: string; }; - -const PLUGIN_PROMPT_MUTATION_RESULT_FIELDS = [ - "systemPrompt", - "prependContext", - "appendContext", - "prependSystemContext", - "appendSystemContext", -] as const satisfies readonly (keyof PluginHookBeforePromptBuildResult)[]; - -type MissingPluginPromptMutationResultFields = Exclude< - keyof PluginHookBeforePromptBuildResult, - (typeof PLUGIN_PROMPT_MUTATION_RESULT_FIELDS)[number] ->; -type AssertAllPluginPromptMutationResultFieldsListed = - MissingPluginPromptMutationResultFields extends never ? true : never; -const assertAllPluginPromptMutationResultFieldsListed: AssertAllPluginPromptMutationResultFieldsListed = true; -void assertAllPluginPromptMutationResultFieldsListed; - -/** - * @deprecated Use before_model_resolve and before_prompt_build. - * - * Legacy compatibility hook that combines both phases. - */ -export type PluginHookBeforeAgentStartEvent = { - prompt: string; - runId?: string; - /** Optional because legacy hook can run in pre-session phase. */ - messages?: unknown[]; -}; - -/** @deprecated Use before_model_resolve and before_prompt_build result types. */ -export type PluginHookBeforeAgentStartResult = PluginHookBeforePromptBuildResult & - PluginHookBeforeModelResolveResult; - -export const stripPromptMutationFieldsFromLegacyHookResult = ( - result: PluginHookBeforeAgentStartResult | void, -): PluginHookBeforeModelResolveResult | void => { - if (!result || typeof result !== "object") { - return result; - } - const remaining: Partial = { ...result }; - for (const field of PLUGIN_PROMPT_MUTATION_RESULT_FIELDS) { - delete remaining[field]; - } - return Object.keys(remaining).length > 0 - ? (remaining as PluginHookBeforeModelResolveResult) - : undefined; -}; diff --git a/src/plugins/hook-lifecycle-gates.test.ts b/src/plugins/hook-lifecycle-gates.test.ts index a69fe4fb051d..6879b003364e 100644 --- a/src/plugins/hook-lifecycle-gates.test.ts +++ b/src/plugins/hook-lifecycle-gates.test.ts @@ -340,82 +340,6 @@ describe("before_agent_run invalid ask outcome", () => { }); }); -describe("before_agent_start hook default timeout (#48534)", () => { - afterEach(() => { - vi.useRealTimers(); - }); - - it("fails open with a warning when a handler exceeds the default 15s budget", async () => { - vi.useFakeTimers(); - const errors: string[] = []; - const logger = { - debug: () => {}, - info: () => {}, - warn: () => {}, - error: (msg: string) => { - errors.push(msg); - }, - }; - const registry = makeRegistry([ - { - pluginId: "hanging-plugin", - hookName: "before_agent_start", - handler: () => new Promise(() => {}), - source: "test", - }, - ]); - const runner = createHookRunner(registry, { logger }); - const resultPromise = runner.runBeforeAgentStart({ prompt: "hello" }, ctx); - - await vi.advanceTimersByTimeAsync(15_000); - const result = await resultPromise; - - // Fail-open: a hung handler must not block the run, and the runner - // returns undefined so the embedded setup path proceeds without hook - // modifications. The hook-runner logs the timeout for operator triage. - expect(result).toBeUndefined(); - expect(errors).toContain( - "[hooks] before_agent_start handler from hanging-plugin failed: timed out after 15000ms", - ); - }); - - it("does not block when one handler hangs and a second is registered", async () => { - vi.useFakeTimers(); - const errors: string[] = []; - const logger = { - debug: () => {}, - info: () => {}, - warn: () => {}, - error: (msg: string) => { - errors.push(msg); - }, - }; - const registry = makeRegistry([ - { - pluginId: "hanging-plugin", - hookName: "before_agent_start", - priority: 100, - handler: () => new Promise(() => {}), - source: "test", - }, - { - pluginId: "fast-plugin", - hookName: "before_agent_start", - priority: 50, - handler: async () => ({ modelOverride: "fallback-model" }), - source: "test", - }, - ]); - const runner = createHookRunner(registry, { logger }); - const resultPromise = runner.runBeforeAgentStart({ prompt: "hello" }, ctx); - await vi.advanceTimersByTimeAsync(15_000); - const result = await resultPromise; - - expect(result?.modelOverride).toBe("fallback-model"); - expect(errors.some((msg) => msg.includes("hanging-plugin"))).toBe(true); - }); -}); - describe("before_tool_call channelId forwarding", () => { it("passes channelId through to before_tool_call handlers", async () => { let receivedCtx: unknown; diff --git a/src/plugins/hook-types.ts b/src/plugins/hook-types.ts index ec63b48d801f..30c398b0b0a8 100644 --- a/src/plugins/hook-types.ts +++ b/src/plugins/hook-types.ts @@ -11,8 +11,6 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { TtsAutoMode } from "../config/types.tts.js"; import type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js"; import type { - PluginHookBeforeAgentStartEvent, - PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveEvent, PluginHookBeforeModelResolveResult, PluginHookBeforePromptBuildEvent, @@ -39,8 +37,6 @@ import type { } from "./host-hook-turn-types.js"; export type { - PluginHookBeforeAgentStartEvent, - PluginHookBeforeAgentStartResult, PluginHookBeforeModelResolveAttachment, PluginHookBeforeModelResolveEvent, PluginHookBeforeModelResolveResult, @@ -52,7 +48,6 @@ export type { PluginHookChannelContext, PluginHookChannelSenderContext, } from "./hook-channel-context.types.js"; -export { stripPromptMutationFieldsFromLegacyHookResult } from "./hook-before-agent-start.types.js"; export type { PluginAgentTurnPrepareEvent, PluginAgentTurnPrepareResult, @@ -78,7 +73,6 @@ export type PluginHookName = | "before_model_resolve" | "agent_turn_prepare" | "before_prompt_build" - | "before_agent_start" | "before_agent_reply" | "model_call_started" | "model_call_ended" @@ -128,7 +122,6 @@ const PLUGIN_HOOK_NAMES = [ "before_model_resolve", "agent_turn_prepare", "before_prompt_build", - "before_agent_start", "before_agent_reply", "model_call_started", "model_call_ended", @@ -233,7 +226,6 @@ export const isPluginHookName = (hookName: unknown): hookName is PluginHookName const PROMPT_INJECTION_HOOK_NAMES = [ "agent_turn_prepare", "before_prompt_build", - "before_agent_start", "heartbeat_prompt_contribution", ] as const satisfies readonly PluginHookName[]; @@ -1169,11 +1161,6 @@ export type PluginHookHandlerMap = { event: PluginHookBeforePromptBuildEvent, ctx: PluginHookAgentContext, ) => Promise | PluginHookBeforePromptBuildResult | void; - /** @deprecated Use before_model_resolve and before_prompt_build. */ - before_agent_start: ( - event: PluginHookBeforeAgentStartEvent, - ctx: PluginHookAgentContext, - ) => Promise | PluginHookBeforeAgentStartResult | void; before_agent_reply: ( event: PluginHookBeforeAgentReplyEvent, ctx: PluginHookAgentContext, diff --git a/src/plugins/hooks.before-agent-reply.test.ts b/src/plugins/hooks.before-agent-reply.test.ts index ec9642d9ce21..6fbb2537963d 100644 --- a/src/plugins/hooks.before-agent-reply.test.ts +++ b/src/plugins/hooks.before-agent-reply.test.ts @@ -119,6 +119,5 @@ describe("before_agent_reply hook runner (claiming pattern)", () => { const runner = createHookRunner(registry); expect(runner.hasHooks("before_agent_reply")).toBe(true); - expect(runner.hasHooks("before_agent_start")).toBe(false); }); }); diff --git a/src/plugins/hooks.before-agent-start.test.ts b/src/plugins/hooks.before-agent-start.test.ts deleted file mode 100644 index 45d0e4b80f7c..000000000000 --- a/src/plugins/hooks.before-agent-start.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Layer 1: Hook Merger Tests for before_agent_start - * - * Validates that modelOverride and providerOverride fields are correctly - * propagated through the hook merger, including priority ordering and - * backward compatibility. - */ -import { beforeEach, describe, expect, it } from "vitest"; -import { createHookRunner } from "./hooks.js"; -import { addStaticTestHooks, addTestHook, TEST_PLUGIN_AGENT_CTX } from "./hooks.test-fixtures.js"; -import { createEmptyPluginRegistry, type PluginRegistry } from "./registry.js"; -import type { PluginHookBeforeAgentStartResult, PluginHookRegistration } from "./types.js"; - -function addBeforeAgentStartHook( - registry: PluginRegistry, - pluginId: string, - handler: () => PluginHookBeforeAgentStartResult | Promise, - priority?: number, -) { - addTestHook({ - registry, - pluginId, - hookName: "before_agent_start", - handler: handler as PluginHookRegistration["handler"], - priority, - }); -} - -const stubCtx = TEST_PLUGIN_AGENT_CTX; -const EMPTY_BEFORE_AGENT_START_RESULT = { - appendContext: undefined, - appendSystemContext: undefined, - modelOverride: undefined, - prependContext: undefined, - prependSystemContext: undefined, - providerOverride: undefined, - systemPrompt: undefined, -} satisfies PluginHookBeforeAgentStartResult; - -describe("before_agent_start hook merger", () => { - let registry: PluginRegistry; - - beforeEach(() => { - registry = createEmptyPluginRegistry(); - }); - - const runWithSingleHook = async (result: PluginHookBeforeAgentStartResult, priority?: number) => { - addBeforeAgentStartHook(registry, "plugin-a", () => result, priority); - const runner = createHookRunner(registry); - return await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); - }; - - const expectSingleModelOverride = async (modelOverride: string) => { - const result = await runWithSingleHook({ modelOverride }); - expect(result?.modelOverride).toBe(modelOverride); - return result; - }; - - const expectMergedBeforeAgentStart = async ( - hooks: Array<{ - pluginId: string; - result: PluginHookBeforeAgentStartResult; - priority?: number; - }>, - expected: PluginHookBeforeAgentStartResult, - ) => { - const result = await runWithHooks(hooks); - expect(result).toEqual(expected); - return result; - }; - - const runWithHooks = async ( - hooks: Array<{ - pluginId: string; - result: PluginHookBeforeAgentStartResult; - priority?: number; - }>, - ) => { - addStaticTestHooks(registry, { - hookName: "before_agent_start", - hooks, - }); - const runner = createHookRunner(registry); - return await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); - }; - - it.each([ - [ - "returns modelOverride from a single plugin", - { modelOverride: "llama3.3:8b" }, - { - ...EMPTY_BEFORE_AGENT_START_RESULT, - modelOverride: "llama3.3:8b", - }, - ], - [ - "returns providerOverride from a single plugin", - { providerOverride: "ollama" }, - { - ...EMPTY_BEFORE_AGENT_START_RESULT, - providerOverride: "ollama", - }, - ], - [ - "returns both modelOverride and providerOverride together", - { - modelOverride: "llama3.3:8b", - providerOverride: "ollama", - }, - { - ...EMPTY_BEFORE_AGENT_START_RESULT, - modelOverride: "llama3.3:8b", - providerOverride: "ollama", - }, - ], - [ - "systemPrompt merges correctly alongside model overrides", - { - systemPrompt: "You are a helpful assistant", - modelOverride: "llama3.3:8b", - providerOverride: "ollama", - }, - { - ...EMPTY_BEFORE_AGENT_START_RESULT, - systemPrompt: "You are a helpful assistant", - modelOverride: "llama3.3:8b", - providerOverride: "ollama", - }, - ], - ] as const)("%s", async (_name, hookResult, expected) => { - await expectMergedBeforeAgentStart([{ pluginId: "plugin-a", result: hookResult }], expected); - }); - - it("higher-priority plugin wins for modelOverride", async () => { - const result = await expectMergedBeforeAgentStart( - [ - { pluginId: "low-priority", result: { modelOverride: "gpt-5.4" }, priority: 1 }, - { pluginId: "high-priority", result: { modelOverride: "llama3.3:8b" }, priority: 10 }, - ], - { ...EMPTY_BEFORE_AGENT_START_RESULT, modelOverride: "llama3.3:8b" }, - ); - expect(result?.modelOverride).toBe("llama3.3:8b"); - }); - - it("lower-priority plugin does not overwrite if it returns undefined", async () => { - const result = await runWithHooks([ - { - pluginId: "high-priority", - result: { modelOverride: "llama3.3:8b", providerOverride: "ollama" }, - priority: 10, - }, - { - pluginId: "low-priority", - result: { prependContext: "some context" }, - priority: 1, - }, - ]); - - // High-priority ran first (priority 10), low-priority ran second (priority 1). - // Low-priority didn't return modelOverride, so ?? falls back to acc's value. - expect(result?.modelOverride).toBe("llama3.3:8b"); - expect(result?.providerOverride).toBe("ollama"); - expect(result?.prependContext).toBe("some context"); - }); - - it("prependContext still concatenates when modelOverride is present", async () => { - const result = await runWithHooks([ - { - pluginId: "plugin-a", - result: { prependContext: "context A", modelOverride: "llama3.3:8b" }, - priority: 10, - }, - { - pluginId: "plugin-b", - result: { prependContext: "context B" }, - priority: 1, - }, - ]); - - expect(result?.prependContext).toBe("context A\n\ncontext B"); - expect(result?.modelOverride).toBe("llama3.3:8b"); - }); - - it("backward compat: plugin returning only prependContext produces no modelOverride", async () => { - const result = await runWithSingleHook({ prependContext: "legacy context" }); - - expect(result?.prependContext).toBe("legacy context"); - expect(result?.modelOverride).toBeUndefined(); - expect(result?.providerOverride).toBeUndefined(); - }); - - it("modelOverride without providerOverride leaves provider undefined", async () => { - const result = await expectSingleModelOverride("llama3.3:8b"); - expect(result?.providerOverride).toBeUndefined(); - }); - - it("returns undefined when no hooks are registered", async () => { - const runner = createHookRunner(registry); - const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); - - expect(result).toBeUndefined(); - }); - - it("passes runId through the agent context to hook handlers", async () => { - const registryLocal = createEmptyPluginRegistry(); - let capturedCtx: typeof stubCtx | undefined; - addTestHook({ - registry: registryLocal, - pluginId: "ctx-spy", - hookName: "before_agent_start", - handler: ((eventValue: unknown, ctx: typeof stubCtx) => { - capturedCtx = ctx; - return {}; - }) as PluginHookRegistration["handler"], - }); - - const runner = createHookRunner(registryLocal); - await runner.runBeforeAgentStart({ prompt: "test" }, stubCtx); - - expect(capturedCtx).toBe(stubCtx); - }); -}); diff --git a/src/plugins/hooks.correlation.test.ts b/src/plugins/hooks.correlation.test.ts index 72c2f7fc85cf..858e09b6be79 100644 --- a/src/plugins/hooks.correlation.test.ts +++ b/src/plugins/hooks.correlation.test.ts @@ -19,24 +19,6 @@ describe("hook correlation fields", () => { registry = createEmptyPluginRegistry(); }); - it("adds runId to legacy before_agent_start events from hook context", async () => { - const handler = vi.fn(() => undefined); - addTestHook({ - registry, - pluginId: "plugin-a", - hookName: "before_agent_start", - handler: handler as PluginHookRegistration["handler"], - }); - - const runner = createHookRunner(registry); - await runner.runBeforeAgentStart({ prompt: "hello" }, TEST_PLUGIN_AGENT_CTX); - - expect(handler).toHaveBeenCalledWith( - { prompt: "hello", runId: "test-run-id" }, - TEST_PLUGIN_AGENT_CTX, - ); - }); - it("adds runId to agent_end events from hook context", async () => { const handler = vi.fn(() => undefined); addTestHook({ diff --git a/src/plugins/hooks.model-override-wiring.test.ts b/src/plugins/hooks.model-override-wiring.test.ts index cce34d793da8..8b8cb61703ae 100644 --- a/src/plugins/hooks.model-override-wiring.test.ts +++ b/src/plugins/hooks.model-override-wiring.test.ts @@ -4,10 +4,8 @@ * Verifies: * 1. before_model_resolve applies deterministic provider/model overrides * 2. before_prompt_build receives session messages and prepends prompt context - * 3. before_agent_start remains a legacy compatibility fallback */ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { joinPresentTextSegments } from "../shared/text/join-segments.js"; import { createHookRunner } from "./hooks.js"; import { addTestHook, TEST_PLUGIN_AGENT_CTX } from "./hooks.test-fixtures.js"; import { createEmptyPluginRegistry, type PluginRegistry } from "./registry.js"; @@ -67,17 +65,6 @@ describe("model override pipeline wiring", () => { registry = createEmptyPluginRegistry(); }); - function addLegacyBeforeAgentStartHook( - result: PluginHookBeforeModelResolveResult | PluginHookBeforePromptBuildResult, - ) { - addTestHook({ - registry, - pluginId: "legacy-hook", - hookName: "before_agent_start", - handler: (() => result) as PluginHookRegistration["handler"], - }); - } - async function runPromptBuildWithMessages(messages: unknown[]) { const runner = createHookRunner(registry); return await runner.runBeforePromptBuild({ prompt: "test", messages }, stubCtx); @@ -123,41 +110,19 @@ describe("model override pipeline wiring", () => { async function expectPromptBuildPrependContext(params: { messages: unknown[]; expectedPrependContext: string; - legacyPrependContext?: string; }) { const handlerSpy = vi.fn( (event: PluginHookBeforePromptBuildEvent) => ({ - prependContext: params.legacyPrependContext - ? "new context" - : `Saw ${event.messages.length} messages`, + prependContext: `Saw ${event.messages.length} messages`, }) as PluginHookBeforePromptBuildResult, ); addBeforePromptBuildHook(registry, "context-plugin", handlerSpy); - if (params.legacyPrependContext) { - addLegacyBeforeAgentStartHook({ - prependContext: params.legacyPrependContext, - }); - } const result = await runPromptBuildWithMessages(params.messages); expect(handlerSpy).toHaveBeenCalledTimes(1); - if (!params.legacyPrependContext) { - expect(result?.prependContext).toBe(params.expectedPrependContext); - return result; - } - - const runner = createHookRunner(registry); - const legacy = await runner.runBeforeAgentStart( - { prompt: "test", messages: params.messages }, - stubCtx, - ); - const prependContext = joinPresentTextSegments([ - result?.prependContext, - legacy?.prependContext, - ]); - expect(prependContext).toBe(params.expectedPrependContext); + expect(result?.prependContext).toBe(params.expectedPrependContext); return result; } @@ -184,48 +149,13 @@ describe("model override pipeline wiring", () => { ] as const)("$name", async ({ event, expected, withBrokenHook, catchErrors }) => { await expectBeforeModelResolve({ event, expected, withBrokenHook, catchErrors }); }); - - it("new hook overrides beat legacy before_agent_start fallback", async () => { - addBeforeModelResolveHook(registry, "new-hook", () => ({ - modelOverride: "demo-local-model", - providerOverride: "demo-local-provider", - })); - addLegacyBeforeAgentStartHook({ - modelOverride: "demo-legacy-model", - providerOverride: "demo-legacy-provider", - }); - - const runner = createHookRunner(registry); - const explicit = await runner.runBeforeModelResolve({ prompt: "sensitive" }, stubCtx); - const legacy = await runner.runBeforeAgentStart({ prompt: "sensitive" }, stubCtx); - const merged = { - providerOverride: explicit?.providerOverride ?? legacy?.providerOverride, - modelOverride: explicit?.modelOverride ?? legacy?.modelOverride, - }; - - expect(merged.providerOverride).toBe("demo-local-provider"); - expect(merged.modelOverride).toBe("demo-local-model"); - }); }); describe("before_prompt_build (attempt.ts pattern)", () => { - it.each([ - { - name: "hook receives prompt and messages and can prepend context", - messages: [{}, {}] as unknown[], - expectedPrependContext: "Saw 2 messages", - }, - { - name: "legacy before_agent_start context can still be merged as fallback", - messages: [{ role: "user", content: "x" }] as unknown[], - legacyPrependContext: "legacy context", - expectedPrependContext: "new context\n\nlegacy context", - }, - ] as const)("$name", async ({ messages, legacyPrependContext, expectedPrependContext }) => { + it("passes prompt and messages to context hooks", async () => { await expectPromptBuildPrependContext({ - messages, - legacyPrependContext, - expectedPrependContext, + messages: [{}, {}], + expectedPrependContext: "Saw 2 messages", }); }); @@ -306,25 +236,17 @@ describe("model override pipeline wiring", () => { }); describe("graceful degradation + hook detection", () => { - it("hasHooks reports new and legacy hooks independently", () => { + it("hasHooks reports model and prompt hooks independently", () => { const runner1 = createHookRunner(registry); expect(runner1.hasHooks("before_model_resolve")).toBe(false); expect(runner1.hasHooks("before_prompt_build")).toBe(false); - expect(runner1.hasHooks("before_agent_start")).toBe(false); addBeforeModelResolveHook(registry, "plugin-a", () => ({})); addBeforePromptBuildHook(registry, "plugin-b", () => ({})); - addTestHook({ - registry, - pluginId: "plugin-c", - hookName: "before_agent_start", - handler: (() => ({})) as PluginHookRegistration["handler"], - }); const runner2 = createHookRunner(registry); expect(runner2.hasHooks("before_model_resolve")).toBe(true); expect(runner2.hasHooks("before_prompt_build")).toBe(true); - expect(runner2.hasHooks("before_agent_start")).toBe(true); }); }); }); diff --git a/src/plugins/hooks.ts b/src/plugins/hooks.ts index 239daf90c119..154b096a9bb8 100644 --- a/src/plugins/hooks.ts +++ b/src/plugins/hooks.ts @@ -25,8 +25,6 @@ import type { PluginHookBeforeAgentFinalizeResult, PluginHookBeforeAgentReplyEvent, PluginHookBeforeAgentReplyResult, - PluginHookBeforeAgentStartEvent, - PluginHookBeforeAgentStartResult, PluginHookBeforeDispatchContext, PluginHookBeforeDispatchEvent, PluginHookBeforeDispatchResult, @@ -152,13 +150,6 @@ const DEFAULT_VOID_HOOK_TIMEOUT_MS_BY_HOOK: Partial> = { before_agent_run: 15_000, - // Defensive default for the legacy compatibility hook (#48534). With - // before_agent_start unbudgeted, an unresponsive handler (e.g. a memory - // plugin waiting on a hung subprocess) blocked the entire agent pipeline - // because both the embedded setup and prompt-build paths await this hook. - // The runner is fail-open for this hook name, so a timed-out handler is - // logged and the run proceeds without its modifications. - before_agent_start: 15_000, // Terminal finalization hooks sit on the runner's completion path. A hung // handler must not freeze final delivery or keep compaction retry recovery // unresolved; timeout fail-opens with the original final answer. @@ -804,29 +795,6 @@ export function createHookRunner( ); } - /** - * @deprecated Use runBeforeModelResolve and runBeforePromptBuild. - * - * Run before_agent_start hook. - * Legacy compatibility hook that combines model resolve + prompt build phases. - */ - async function runBeforeAgentStart( - event: PluginHookBeforeAgentStartEvent, - ctx: PluginHookAgentContext, - ): Promise { - return runModifyingHook<"before_agent_start", PluginHookBeforeAgentStartResult>( - "before_agent_start", - withAgentRunId(event, ctx), - ctx, - { - mergeResults: (acc, next) => ({ - ...mergeBeforePromptBuild(acc, next), - ...mergeBeforeModelResolve(acc, next), - }), - }, - ); - } - /** * Run before_agent_reply hook. * Allows plugins to intercept messages and return a synthetic reply, @@ -1594,7 +1562,6 @@ export function createHookRunner( runBeforeModelResolve, runAgentTurnPrepare, runBeforePromptBuild, - runBeforeAgentStart, runBeforeAgentReply, runModelCallStarted, runModelCallEnded, diff --git a/src/plugins/inspect-shape.ts b/src/plugins/inspect-shape.ts index 85237d98c000..58ad852876a8 100644 --- a/src/plugins/inspect-shape.ts +++ b/src/plugins/inspect-shape.ts @@ -38,7 +38,6 @@ type PluginShapeSummary = { capabilityMode: "none" | "plain" | "hybrid"; capabilityCount: number; capabilities: PluginCapabilityEntry[]; - usesLegacyBeforeAgentStart: boolean; }; function buildPluginCapabilityEntries( @@ -151,8 +150,5 @@ export function buildPluginShapeSummary(params: { capabilityMode: capabilityCount === 0 ? "none" : capabilityCount === 1 ? "plain" : "hybrid", capabilityCount, capabilities, - usesLegacyBeforeAgentStart: params.report.typedHooks.some( - (entry) => entry.pluginId === params.plugin.id && entry.hookName === "before_agent_start", - ), }; } diff --git a/src/plugins/install-record-commit.ts b/src/plugins/install-record-commit.ts index 5faa1064f1bd..b5fe1cdf2a1e 100644 --- a/src/plugins/install-record-commit.ts +++ b/src/plugins/install-record-commit.ts @@ -12,7 +12,6 @@ import { type TransformConfigFileWithRetryParams, } from "../config/config.js"; import type { ConfigWriteOptions } from "../config/io.js"; -import { extractShippedPluginInstallConfigRecords } from "../config/plugin-install-config-migration.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { PluginInstallRecord } from "../config/types.plugins.js"; import { isPathInside } from "../infra/path-guards.js"; @@ -377,7 +376,7 @@ export async function commitConfigWriteWithPendingPluginInstalls(params: { movedInstallRecords: boolean; persistedHash: string | null; }> { - const sourceInstallRecords = extractShippedPluginInstallConfigRecords(params.sourceConfig); + const sourceInstallRecords = params.sourceConfig?.plugins?.installs ?? {}; const nextPendingConfig = params.sourceConfig ? stripPendingPluginInstallRecords( params.nextConfig, diff --git a/src/plugins/installed-plugin-index-record-builder.ts b/src/plugins/installed-plugin-index-record-builder.ts index 95dc7ef225bf..80e17e6388be 100644 --- a/src/plugins/installed-plugin-index-record-builder.ts +++ b/src/plugins/installed-plugin-index-record-builder.ts @@ -70,12 +70,6 @@ export function collectPluginManifestCompatCodes( record: PluginManifestRecord, ): readonly PluginCompatCode[] { const codes: PluginCompatCode[] = []; - if (record.providerAuthEnvVars && Object.keys(record.providerAuthEnvVars).length > 0) { - codes.push("provider-auth-env-vars"); - } - if (record.channelEnvVars && Object.keys(record.channelEnvVars).length > 0) { - codes.push("channel-env-vars"); - } if (record.activation?.onProviders?.length) { codes.push("activation-provider-hint"); } diff --git a/src/plugins/installed-plugin-index.test.ts b/src/plugins/installed-plugin-index.test.ts index 955c02e0fa7d..f5596f414f3c 100644 --- a/src/plugins/installed-plugin-index.test.ts +++ b/src/plugins/installed-plugin-index.test.ts @@ -157,13 +157,7 @@ function createRichPluginFixture(params: { id?: string; packageVersion?: string contracts: { tools: ["demo-tool"], }, - providerAuthEnvVars: { - demo: ["DEMO_API_KEY"], - }, syntheticAuthRefs: ["demo", "demo-cli"], - channelEnvVars: { - "demo-chat": ["DEMO_CHAT_TOKEN"], - }, activation: { onAgentHarnesses: ["codex"], onProviders: ["demo"], @@ -244,8 +238,6 @@ describe("installed plugin index", () => { "activation-agent-harness-hint", "activation-channel-hint", "activation-provider-hint", - "channel-env-vars", - "provider-auth-env-vars", ], }); expectRecordFields(readRecordField(plugin, "packageInstall", "package install"), { diff --git a/src/plugins/loader-module-runtime.ts b/src/plugins/loader-module-runtime.ts index e5872ca7a8ae..dc820bd2401a 100644 --- a/src/plugins/loader-module-runtime.ts +++ b/src/plugins/loader-module-runtime.ts @@ -28,7 +28,6 @@ const LAZY_RUNTIME_REFLECTION_KEYS = [ "media", "mediaUnderstanding", "tts", - "stt", "channel", "events", "logging", @@ -256,7 +255,7 @@ export function resolvePluginModuleExport(moduleExport: unknown): { } if (resolved && typeof resolved === "object") { const definition = resolved as OpenClawPluginDefinition; - const register = definition.register ?? definition.activate; + const register = definition.register; if (typeof register === "function") { return { definition, register }; } @@ -273,7 +272,7 @@ export function resolvePluginModuleExport(moduleExport: unknown): { } if (resolved && typeof resolved === "object") { const definition = resolved as OpenClawPluginDefinition; - return { definition, register: definition.register ?? definition.activate }; + return { definition, register: definition.register }; } return {}; } diff --git a/src/plugins/loader-sdk-import-guardrails.test.ts b/src/plugins/loader-sdk-import-guardrails.test.ts index cf18b00bea59..4a5e9468caa6 100644 --- a/src/plugins/loader-sdk-import-guardrails.test.ts +++ b/src/plugins/loader-sdk-import-guardrails.test.ts @@ -4,9 +4,6 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; const ALLOWED_PLUGIN_SDK_FIXTURE_IMPORTS = new Set([ - // Intentional legacy SDK-root compatibility smoke tests. - 'src/plugins/loader.test.ts:configSchema: (require("openclaw/plugin-sdk").emptyPluginConfigSchema)(),', - 'src/plugins/loader.test.ts:const { onDiagnosticEvent } = require("openclaw/plugin-sdk");', // Intentional jiti alias regression test. 'src/plugins/loader.git-path-regression.test.ts:`import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";', 'src/plugins/loader.git-path-regression.test.ts: "openclaw/plugin-sdk/channel-outbound": ${JSON.stringify(copiedChannelRuntimeShim)},', diff --git a/src/plugins/loader.discovery-and-security.test-utils.ts b/src/plugins/loader.discovery-and-security.test-utils.ts index 33415cce07a1..27a0add56e0b 100644 --- a/src/plugins/loader.discovery-and-security.test-utils.ts +++ b/src/plugins/loader.discovery-and-security.test-utils.ts @@ -3,7 +3,6 @@ import fs from "node:fs"; import path from "node:path"; import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; import { setRuntimeConfigSnapshot } from "../config/runtime-snapshot.js"; -import { emitDiagnosticEvent, waitForDiagnosticEventsDrained } from "../infra/diagnostic-events.js"; import { toSafeImportPath } from "../shared/import-specifier.js"; import { withEnv } from "../test-utils/env.js"; import { writePersistedInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-records.js"; @@ -1655,105 +1654,6 @@ describe("loadOpenClawPlugins", () => { expect(record?.status).toBe("loaded"); }); - it("supports legacy plugins importing monolithic plugin-sdk root", () => { - useNoBundledPlugins(); - const plugin = writePlugin({ - id: "legacy-root-import", - filename: "legacy-root-import.cjs", - body: `module.exports = { - id: "legacy-root-import", - configSchema: (require("openclaw/plugin-sdk").emptyPluginConfigSchema)(), - register() {}, - };`, - }); - - const registry = withEnv({ OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins" }, () => - loadOpenClawPlugins({ - cache: false, - workspaceDir: plugin.dir, - config: { - plugins: { - load: { paths: [plugin.file] }, - allow: ["legacy-root-import"], - }, - }, - }), - ); - const record = registry.plugins.find((entry) => entry.id === "legacy-root-import"); - expect( - record?.status, - JSON.stringify({ error: record?.error, diagnostics: registry.diagnostics }, null, 2), - ).toBe("loaded"); - }); - - it("supports legacy plugins subscribing to diagnostic events from the root sdk", async () => { - useNoBundledPlugins(); - const seenKey = "__openclawLegacyRootDiagnosticSeen"; - delete (globalThis as Record)[seenKey]; - - const plugin = writePlugin({ - id: "legacy-root-diagnostic-listener", - filename: "legacy-root-diagnostic-listener.cjs", - body: `module.exports = { - id: "legacy-root-diagnostic-listener", - configSchema: (require("openclaw/plugin-sdk").emptyPluginConfigSchema)(), - register() { - const { onDiagnosticEvent } = require("openclaw/plugin-sdk"); - if (typeof onDiagnosticEvent !== "function") { - throw new Error("missing onDiagnosticEvent root export"); - } - globalThis.${seenKey} = []; - onDiagnosticEvent((event) => { - globalThis.${seenKey}.push({ - type: event.type, - sessionKey: event.sessionKey, - }); - }); - }, - };`, - }); - - try { - const registry = withEnv( - { OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins" }, - () => - loadOpenClawPlugins({ - cache: false, - workspaceDir: plugin.dir, - config: { - plugins: { - load: { paths: [plugin.file] }, - allow: ["legacy-root-diagnostic-listener"], - }, - }, - }), - ); - const record = registry.plugins.find( - (entry) => entry.id === "legacy-root-diagnostic-listener", - ); - expect( - record?.status, - JSON.stringify({ error: record?.error, diagnostics: registry.diagnostics }, null, 2), - ).toBe("loaded"); - - emitDiagnosticEvent({ - type: "model.usage", - sessionKey: "agent:main:test:dm:peer", - usage: { total: 1 }, - }); - await waitForDiagnosticEventsDrained(); - - expect((globalThis as Record)[seenKey]).toEqual([ - { - type: "model.usage", - sessionKey: "agent:main:test:dm:peer", - }, - ]); - } finally { - delete (globalThis as Record)[seenKey]; - } - }); - it("suppresses trust warning logs for non-activating snapshot loads", () => { useNoBundledPlugins(); const stateDir = makeTempDir(); diff --git a/src/plugins/loader.hooks-and-runtime.test-utils.ts b/src/plugins/loader.hooks-and-runtime.test-utils.ts index 292ae5a18717..6d9344302d3c 100644 --- a/src/plugins/loader.hooks-and-runtime.test-utils.ts +++ b/src/plugins/loader.hooks-and-runtime.test-utils.ts @@ -1535,19 +1535,17 @@ describe("loadOpenClawPlugins", () => { expectDiagnosticContaining({ registry, message: "escapes" }); }); - it("blocks before_prompt_build but preserves legacy model overrides when prompt injection is disabled", async () => { + it("blocks before_prompt_build but preserves model resolution overrides when prompt injection is disabled", async () => { useNoBundledPlugins(); const plugin = writePlugin({ id: "hook-policy", filename: "hook-policy.cjs", body: `module.exports = { id: "hook-policy", register(api) { api.on("before_prompt_build", () => ({ prependContext: "prepend" })); - api.on("before_agent_start", () => ({ - prependContext: "legacy", - modelOverride: "demo-legacy-model", - providerOverride: "demo-legacy-provider", + api.on("before_model_resolve", () => ({ + modelOverride: "demo-model", + providerOverride: "demo-provider", })); - api.on("before_model_resolve", () => ({ providerOverride: "demo-explicit-provider" })); } };`, }); @@ -1567,15 +1565,12 @@ describe("loadOpenClawPlugins", () => { }); expect(registry.plugins.find((entry) => entry.id === "hook-policy")?.status).toBe("loaded"); - expect(registry.typedHooks.map((entry) => entry.hookName)).toEqual([ - "before_agent_start", - "before_model_resolve", - ]); + expect(registry.typedHooks.map((entry) => entry.hookName)).toEqual(["before_model_resolve"]); const runner = createHookRunner(registry); - const legacyResult = await runner.runBeforeAgentStart({ prompt: "hello", messages: [] }, {}); - expect(legacyResult).toEqual({ - modelOverride: "demo-legacy-model", - providerOverride: "demo-legacy-provider", + const result = await runner.runBeforeModelResolve({ prompt: "hello" }, {}); + expect(result).toEqual({ + modelOverride: "demo-model", + providerOverride: "demo-provider", }); const blockedDiagnostics = registry.diagnostics.filter((diag) => diag.message.includes( @@ -1583,12 +1578,6 @@ describe("loadOpenClawPlugins", () => { ), ); expect(blockedDiagnostics).toHaveLength(1); - const constrainedDiagnostics = registry.diagnostics.filter((diag) => - diag.message.includes( - "prompt fields constrained by plugins.entries.hook-policy.hooks.allowPromptInjection=false", - ), - ); - expect(constrainedDiagnostics).toHaveLength(1); }); it("blocks next-turn injections when prompt injection is disabled", () => { @@ -1638,7 +1627,6 @@ describe("loadOpenClawPlugins", () => { filename: "hook-policy-default.cjs", body: `module.exports = { id: "hook-policy-default", register(api) { api.on("before_prompt_build", () => ({ prependContext: "prepend" })); - api.on("before_agent_start", () => ({ prependContext: "legacy" })); } };`, }); @@ -1649,10 +1637,7 @@ describe("loadOpenClawPlugins", () => { }, }); - expect(registry.typedHooks.map((entry) => entry.hookName)).toEqual([ - "before_prompt_build", - "before_agent_start", - ]); + expect(registry.typedHooks.map((entry) => entry.hookName)).toEqual(["before_prompt_build"]); }); it("applies configured typed hook timeout overrides", () => { @@ -1663,7 +1648,6 @@ describe("loadOpenClawPlugins", () => { body: `module.exports = { id: "hook-timeouts", register(api) { api.on("before_prompt_build", () => ({ prependContext: "prepend" }), { timeoutMs: 5000 }); api.on("before_model_resolve", () => ({ providerOverride: "demo-provider" })); - api.on("before_agent_start", () => ({ modelOverride: "demo-model" })); } };`, }); @@ -1690,7 +1674,6 @@ describe("loadOpenClawPlugins", () => { ).toEqual({ before_prompt_build: 250, before_model_resolve: 750, - before_agent_start: 250, }); }); diff --git a/src/plugins/loader.registration.test-utils.ts b/src/plugins/loader.registration.test-utils.ts index a9004d042646..fdca1beb5bbe 100644 --- a/src/plugins/loader.registration.test-utils.ts +++ b/src/plugins/loader.registration.test-utils.ts @@ -533,21 +533,23 @@ describe("loadOpenClawPlugins", () => { id: "snapshot", create: async () => ({ provider: null }), }); - api.registerMemoryPromptSection(() => ["snapshot memory section"]); - api.registerMemoryFlushPlan(() => ({ - softThresholdTokens: 10, - forceFlushTranscriptBytes: 20, - reserveTokensFloor: 30, - prompt: "snapshot", - systemPrompt: "snapshot", - relativePath: "memory/snapshot.md", - })); - api.registerMemoryRuntime({ - async getMemorySearchManager() { - return { manager: null, error: "snapshot" }; - }, - resolveMemoryBackendConfig() { - return { backend: "qmd", qmd: {} }; + api.registerMemoryCapability({ + promptBuilder: () => ["snapshot memory section"], + flushPlanResolver: () => ({ + softThresholdTokens: 10, + forceFlushTranscriptBytes: 20, + reserveTokensFloor: 30, + prompt: "snapshot", + systemPrompt: "snapshot", + relativePath: "memory/snapshot.md", + }), + runtime: { + async getMemorySearchManager() { + return { manager: null, error: "snapshot" }; + }, + resolveMemoryBackendConfig() { + return { backend: "qmd", qmd: {} }; + }, }, }); }, @@ -727,29 +729,31 @@ describe("loadOpenClawPlugins", () => { id: "failed", create: async () => ({ provider: null }), }); - api.registerMemoryPromptSection(() => ["stale failure section"]); + api.registerMemoryCapability({ + promptBuilder: () => ["stale failure section"], + flushPlanResolver: () => ({ + softThresholdTokens: 10, + forceFlushTranscriptBytes: 20, + reserveTokensFloor: 30, + prompt: "failed", + systemPrompt: "failed", + relativePath: "memory/failed.md", + }), + runtime: { + async getMemorySearchManager() { + return { manager: null, error: "failed" }; + }, + resolveMemoryBackendConfig() { + return { backend: "builtin" }; + }, + }, + }); api.registerMemoryPromptSupplement(() => ["stale failure supplement"]); api.registerMemoryPromptPreparation(async () => ["stale prepared supplement"]); api.registerMemoryCorpusSupplement({ search: async () => [], get: async () => null, }); - api.registerMemoryFlushPlan(() => ({ - softThresholdTokens: 10, - forceFlushTranscriptBytes: 20, - reserveTokensFloor: 30, - prompt: "failed", - systemPrompt: "failed", - relativePath: "memory/failed.md", - })); - api.registerMemoryRuntime({ - async getMemorySearchManager() { - return { manager: null, error: "failed" }; - }, - resolveMemoryBackendConfig() { - return { backend: "builtin" }; - }, - }); throw new Error("memory register failed"); }, };`, diff --git a/src/plugins/manifest-registry-installed.ts b/src/plugins/manifest-registry-installed.ts index 34afe7a08c42..54e0a5b4dc71 100644 --- a/src/plugins/manifest-registry-installed.ts +++ b/src/plugins/manifest-registry-installed.ts @@ -466,8 +466,6 @@ function normalizePersistedPackageChannel(value: unknown): PluginPackageChannel for (const key of [ "selectionDocsOmitLabel", "markdownCapable", - "showConfigured", - "showInSetup", "quickstartAllowFrom", "forceAccountBinding", "preferSessionLookupForAnnounceTarget", diff --git a/src/plugins/manifest-registry.test.ts b/src/plugins/manifest-registry.test.ts index d272d86cd1fb..d6a278a6bc7b 100644 --- a/src/plugins/manifest-registry.test.ts +++ b/src/plugins/manifest-registry.test.ts @@ -1122,15 +1122,15 @@ describe("loadPluginManifestRegistry", () => { expect(registry.plugins[0]?.trustedOfficialInstall).toBeUndefined(); }); - it("preserves provider auth env metadata from plugin manifests", () => { + it("normalizes provider metadata from plugin manifests", () => { const dir = makeTempDir(); writeManifest(dir, { id: "openai", enabledByDefault: true, enabledByDefaultOnPlatforms: ["darwin", "not-a-platform"], providers: ["openai", "openai"], - providerAuthEnvVars: { - openai: ["OPENAI_API_KEY"], + setup: { + providers: [{ id: "openai", envVars: ["OPENAI_API_KEY"] }], }, providerEndpoints: [ { @@ -1208,9 +1208,6 @@ describe("loadPluginManifestRegistry", () => { origin: "bundled", }); - expect(registry.plugins[0]?.providerAuthEnvVars).toEqual({ - openai: ["OPENAI_API_KEY"], - }); expect(registry.plugins[0]?.providerEndpoints).toEqual([ { endpointClass: "openai-public", @@ -1599,88 +1596,6 @@ describe("loadPluginManifestRegistry", () => { ]); }); - it("reports non-bundled providerAuthEnvVars as deprecated compat metadata", () => { - const dir = makeTempDir(); - writeManifest(dir, { - id: "external-openai", - providers: ["openai"], - providerAuthEnvVars: { - openai: ["OPENAI_API_KEY"], - }, - configSchema: { type: "object" }, - }); - - const registry = loadSingleCandidateRegistry({ - idHint: "external-openai", - rootDir: dir, - origin: "global", - }); - - expect(registry.plugins[0]?.providerAuthEnvVars).toEqual({ - openai: ["OPENAI_API_KEY"], - }); - expectDiagnosticFields(registry, { - level: "warn", - pluginId: "external-openai", - source: path.join(dir, "openclaw.plugin.json"), - messageIncludes: "providerAuthEnvVars is deprecated compatibility metadata", - }); - }); - - it("does not report deprecated providerAuthEnvVars when setup providers mirror env vars", () => { - const dir = makeTempDir(); - writeManifest(dir, { - id: "external-openai", - providers: ["openai"], - setup: { - providers: [{ id: "openai", envVars: ["OPENAI_API_KEY"] }], - }, - providerAuthEnvVars: { - openai: ["OPENAI_API_KEY"], - }, - configSchema: { type: "object" }, - }); - - const registry = loadSingleCandidateRegistry({ - idHint: "external-openai", - rootDir: dir, - origin: "global", - }); - - expectNoRegistryDiagnosticContains( - registry, - "providerAuthEnvVars is deprecated compatibility metadata", - ); - }); - - it("sanitizes manifest-controlled fields in provider auth compatibility diagnostics", () => { - const dir = makeTempDir(); - const lineBreak = String.fromCharCode(10); - const ansiRed = `${String.fromCharCode(27)}[31m`; - writeManifest(dir, { - id: `external${lineBreak}openai${ansiRed}`, - providers: ["openai"], - providerAuthEnvVars: { - [`openai${lineBreak}${ansiRed}`]: ["OPENAI_API_KEY"], - }, - configSchema: { type: "object" }, - }); - - const registry = loadSingleCandidateRegistry({ - idHint: "external-openai", - rootDir: dir, - origin: "global", - }); - const diagnostic = registry.diagnostics.find((entry) => - entry.message.includes("providerAuthEnvVars is deprecated compatibility metadata"), - ); - - expect(diagnostic?.pluginId).toBe("externalopenai"); - expect(diagnostic?.message).toContain("openai"); - expect(diagnostic?.message).not.toContain(lineBreak); - expect(diagnostic?.message).not.toContain(ansiRed); - }); - it("reports non-bundled channel manifests without channel config descriptors", () => { const dir = makeTempDir(); writeManifest(dir, { @@ -2459,28 +2374,6 @@ describe("loadPluginManifestRegistry", () => { }); }); - it("preserves channel env metadata from plugin manifests", () => { - const dir = makeTempDir(); - writeManifest(dir, { - id: "slack", - channels: ["slack"], - channelEnvVars: { - slack: ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN", "SLACK_USER_TOKEN"], - }, - configSchema: { type: "object" }, - }); - - const registry = loadSingleCandidateRegistry({ - idHint: "slack", - rootDir: dir, - origin: "bundled", - }); - - expect(registry.plugins[0]?.channelEnvVars).toEqual({ - slack: ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN", "SLACK_USER_TOKEN"], - }); - }); - it("preserves qa runner descriptors from plugin manifests", () => { const dir = makeTempDir(); writeManifest(dir, { @@ -2597,7 +2490,7 @@ describe("loadPluginManifestRegistry", () => { configSchema: { type: "object" }, configContracts: { compatibilityMigrationPaths: ["models.bedrockDiscovery"], - compatibilityRuntimePaths: ["tools.web.search.apiKey"], + compatibilityRuntimePaths: ["legacyProvider.webhook"], dangerousFlags: [{ path: "permissionMode", equals: "approve-all" }], secretInputs: { bundledDefaultEnabled: false, @@ -2614,7 +2507,7 @@ describe("loadPluginManifestRegistry", () => { expect(registry.plugins[0]?.configContracts).toEqual({ compatibilityMigrationPaths: ["models.bedrockDiscovery"], - compatibilityRuntimePaths: ["tools.web.search.apiKey"], + compatibilityRuntimePaths: ["legacyProvider.webhook"], dangerousFlags: [{ path: "permissionMode", equals: "approve-all" }], secretInputs: { bundledDefaultEnabled: false, @@ -2632,7 +2525,7 @@ describe("loadPluginManifestRegistry", () => { webSearchProviders: ["brave"], }, configContracts: { - compatibilityRuntimePaths: ["tools.web.search.apiKey"], + compatibilityRuntimePaths: ["legacyProvider.webhook"], }, }); @@ -2664,7 +2557,7 @@ describe("loadPluginManifestRegistry", () => { (plugin) => (plugin.contracts?.webSearchProviders?.length ?? 0) > 0 && (plugin.configContracts?.compatibilityRuntimePaths ?? []).includes( - "tools.web.search.apiKey", + "legacyProvider.webhook", ), ) .map((plugin) => plugin.id), diff --git a/src/plugins/manifest-registry.ts b/src/plugins/manifest-registry.ts index daec4f8729ac..6d7e74adff69 100644 --- a/src/plugins/manifest-registry.ts +++ b/src/plugins/manifest-registry.ts @@ -234,10 +234,8 @@ export type PluginManifestRecord = { syntheticAuthRefs?: string[]; nonSecretAuthMarkers?: string[]; commandAliases?: PluginManifestCommandAlias[]; - providerAuthEnvVars?: Record; providerUsageAuthEnvVars?: Record; providerAuthAliases?: Record; - channelEnvVars?: Record; providerAuthChoices?: PluginManifest["providerAuthChoices"]; activation?: PluginManifestActivation; setup?: PluginManifestSetup; @@ -586,10 +584,8 @@ function buildRecord(params: { syntheticAuthRefs: params.manifest.syntheticAuthRefs ?? [], nonSecretAuthMarkers: params.manifest.nonSecretAuthMarkers ?? [], commandAliases: params.manifest.commandAliases, - providerAuthEnvVars: params.manifest.providerAuthEnvVars, providerUsageAuthEnvVars: params.manifest.providerUsageAuthEnvVars, providerAuthAliases: params.manifest.providerAuthAliases, - channelEnvVars: params.manifest.channelEnvVars, providerAuthChoices: params.manifest.providerAuthChoices, activation: params.manifest.activation, setup: params.manifest.setup, @@ -699,39 +695,6 @@ function buildBundleRecord(params: { }; } -function pushProviderAuthEnvVarsCompatDiagnostic(params: { - record: PluginManifestRecord; - diagnostics: PluginDiagnostic[]; -}): void { - if (params.record.origin === "bundled" || !params.record.providerAuthEnvVars) { - return; - } - const setupProviderEnvVars = new Map( - (params.record.setup?.providers ?? []).map( - (provider) => [provider.id, new Set(provider.envVars ?? [])] as const, - ), - ); - const providerIds = Object.entries(params.record.providerAuthEnvVars) - .filter(([providerId, envVars]) => { - if (!providerId.trim() || envVars.length === 0) { - return false; - } - const mirroredEnvVars = setupProviderEnvVars.get(providerId); - return !mirroredEnvVars || envVars.some((envVar) => !mirroredEnvVars.has(envVar)); - }) - .map(([providerId]) => providerId) - .toSorted((left, right) => left.localeCompare(right)); - if (providerIds.length === 0) { - return; - } - params.diagnostics.push({ - level: "warn", - pluginId: sanitizeForLog(params.record.id), - source: sanitizeForLog(params.record.manifestPath), - message: `providerAuthEnvVars is deprecated compatibility metadata for provider env-var lookup; mirror ${providerIds.map(sanitizeForLog).join(", ")} env vars to setup.providers[].envVars before the deprecation window closes`, - }); -} - function pushNonBundledChannelConfigDescriptorDiagnostic(params: { record: PluginManifestRecord; diagnostics: PluginDiagnostic[]; @@ -776,7 +739,6 @@ function pushManifestCompatibilityDiagnostics(params: { diagnostics: PluginDiagnostic[]; normalized?: ReturnType; }): void { - pushProviderAuthEnvVarsCompatDiagnostic(params); pushNonBundledChannelConfigDescriptorDiagnostic(params); } diff --git a/src/plugins/manifest-tool-availability.ts b/src/plugins/manifest-tool-availability.ts index 697258effe37..664c878bd544 100644 --- a/src/plugins/manifest-tool-availability.ts +++ b/src/plugins/manifest-tool-availability.ts @@ -210,11 +210,7 @@ export function manifestPluginSetupProviderEnvVars( plugin: PluginManifestRecord, providerId: string, ): readonly string[] { - const direct = plugin.setup?.providers?.find((provider) => provider.id === providerId)?.envVars; - if (direct && direct.length > 0) { - return direct; - } - return plugin.providerAuthEnvVars?.[providerId] ?? []; + return plugin.setup?.providers?.find((provider) => provider.id === providerId)?.envVars ?? []; } export function hasNonEmptyManifestEnvCandidate( diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 5c1045b2421b..bc2230742d05 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -353,20 +353,10 @@ export type PluginManifest = { * config diagnostics before runtime loads. */ commandAliases?: PluginManifestCommandAlias[]; - /** - * Cheap provider-auth env lookup without booting plugin runtime. - * - * @deprecated Prefer setup.providers[].envVars for generic setup/status env - * metadata. This field remains supported through the provider env-var - * compatibility adapter during the deprecation window. - */ - providerAuthEnvVars?: Record; /** Usage/billing credentials excluded from inference auth but included in secret scrubbing. */ providerUsageAuthEnvVars?: Record; /** Provider ids that should reuse another provider id for auth lookup. */ providerAuthAliases?: Record; - /** Cheap channel env lookup without booting plugin runtime. */ - channelEnvVars?: Record; /** * Cheap onboarding/auth-choice metadata used by config validation, CLI help, * and non-runtime auth-choice routing before provider runtime loads. @@ -1872,10 +1862,8 @@ export function loadPluginManifest( const syntheticAuthRefs = normalizeTrimmedStringList(raw.syntheticAuthRefs); const nonSecretAuthMarkers = normalizeTrimmedStringList(raw.nonSecretAuthMarkers); const commandAliases = normalizeManifestCommandAliases(raw.commandAliases); - const providerAuthEnvVars = normalizeStringListRecord(raw.providerAuthEnvVars); const providerUsageAuthEnvVars = normalizeStringListRecord(raw.providerUsageAuthEnvVars); const providerAuthAliases = normalizeStringRecord(raw.providerAuthAliases); - const channelEnvVars = normalizeStringListRecord(raw.channelEnvVars); const providerAuthChoices = normalizeProviderAuthChoices(raw.providerAuthChoices); const activation = normalizeManifestActivation(raw.activation); const setup = normalizeManifestSetup(raw.setup); @@ -1930,10 +1918,8 @@ export function loadPluginManifest( syntheticAuthRefs, nonSecretAuthMarkers, commandAliases, - providerAuthEnvVars, providerUsageAuthEnvVars, providerAuthAliases, - channelEnvVars, providerAuthChoices, activation, setup, @@ -1984,8 +1970,6 @@ export type PluginPackageChannel = { setup?: boolean; docs?: boolean; }; - showConfigured?: boolean; - showInSetup?: boolean; quickstartAllowFrom?: boolean; forceAccountBinding?: boolean; preferSessionLookupForAnnounceTarget?: boolean; diff --git a/src/plugins/memory-state.test-fixtures.ts b/src/plugins/memory-state.test-fixtures.ts index b8b1e89839c3..e8200b6c32ba 100644 --- a/src/plugins/memory-state.test-fixtures.ts +++ b/src/plugins/memory-state.test-fixtures.ts @@ -1,11 +1,8 @@ /** Test-only compatibility fixtures for plugin memory state. */ -import { - registerMemoryPromptSectionForPlugin, - type MemoryPromptSectionBuilder, -} from "./memory-state.js"; +import { registerMemoryCapability, type MemoryPromptSectionBuilder } from "./memory-state.js"; export * from "./memory-state.js"; -export function registerMemoryPromptSection(builder: MemoryPromptSectionBuilder): void { - registerMemoryPromptSectionForPlugin("test-memory", builder); +export function registerTestMemoryPromptBuilder(builder: MemoryPromptSectionBuilder): void { + registerMemoryCapability("test-memory", { promptBuilder: builder }); } diff --git a/src/plugins/memory-state.test.ts b/src/plugins/memory-state.test.ts index b63f29c254df..c2e66f6f9cf1 100644 --- a/src/plugins/memory-state.test.ts +++ b/src/plugins/memory-state.test.ts @@ -14,7 +14,7 @@ import { registerMemoryCorpusSupplement, registerMemoryPromptPreparation, registerMemoryPromptSupplement, - registerMemoryPromptSection, + registerTestMemoryPromptBuilder, resolveMemoryFlushPlan, restoreMemoryPluginState, type MemoryPluginPublicArtifact, @@ -84,7 +84,7 @@ describe("memory plugin state", () => { }); it("delegates prompt building to the registered memory plugin", () => { - registerMemoryPromptSection(({ availableTools }) => { + registerTestMemoryPromptBuilder(({ availableTools }) => { if (!availableTools.has("memory_search")) { return []; } @@ -266,7 +266,7 @@ describe("memory plugin state", () => { }); it("passes citations mode through to the prompt builder", () => { - registerMemoryPromptSection(({ citationsMode }) => [ + registerTestMemoryPromptBuilder(({ citationsMode }) => [ `citations: ${citationsMode ?? "default"}`, ]); @@ -281,7 +281,7 @@ describe("memory plugin state", () => { it("passes agent context through the primary and supplemental prompt builders", () => { const primary = vi.fn(() => ["primary"]); const supplemental = vi.fn(() => ["supplemental"]); - registerMemoryPromptSection(primary); + registerTestMemoryPromptBuilder(primary); registerMemoryPromptSupplement("memory-wiki", supplemental); const availableTools = new Set(["memory_search", "memory_get"]); @@ -306,7 +306,7 @@ describe("memory plugin state", () => { }); it("appends prompt supplements in plugin-id order", () => { - registerMemoryPromptSection(() => ["primary"]); + registerTestMemoryPromptBuilder(() => ["primary"]); registerMemoryPromptSupplement("memory-wiki", () => ["wiki"]); registerMemoryPromptSupplement("alpha-helper", () => ["alpha"]); @@ -318,7 +318,7 @@ describe("memory plugin state", () => { }); it("ignores malformed prompt builder output", () => { - registerMemoryPromptSection(() => ["primary", 1, undefined] as never); + registerTestMemoryPromptBuilder(() => ["primary", 1, undefined] as never); registerMemoryPromptSupplement("async-helper", () => Promise.resolve(["async"]) as never); registerMemoryPromptSupplement("valid-helper", () => ["valid", false] as never); diff --git a/src/plugins/memory-state.ts b/src/plugins/memory-state.ts index f747b198b67a..4146b5b42482 100644 --- a/src/plugins/memory-state.ts +++ b/src/plugins/memory-state.ts @@ -240,14 +240,6 @@ export function registerMemoryCapability( }; } -function patchMemoryCapability(pluginId: string, patch: MemoryPluginCapability): void { - const current = - memoryPluginState.capability?.pluginId === pluginId - ? memoryPluginState.capability.capability - : {}; - registerMemoryCapability(pluginId, { ...current, ...patch }); -} - export function getMemoryCapabilityRegistration(): MemoryPluginCapabilityRegistration | undefined { return memoryPluginState.capability ? { @@ -260,13 +252,6 @@ export function getMemoryCapabilityRegistration(): MemoryPluginCapabilityRegistr export function listMemoryCorpusSupplements(): MemoryCorpusSupplementRegistration[] { return [...memoryPluginState.corpusSupplements]; } -export function registerMemoryPromptSectionForPlugin( - pluginId: string, - builder: MemoryPromptSectionBuilder, -): void { - patchMemoryCapability(pluginId, { promptBuilder: builder }); -} - export function registerMemoryPromptSupplement( pluginId: string, builder: MemoryPromptSectionBuilder, @@ -421,26 +406,12 @@ export function listMemoryPromptSupplements(): MemoryPromptSupplementRegistratio export function listMemoryPromptPreparations(): MemoryPromptPreparationRegistration[] { return [...memoryPluginState.promptPreparations]; } -export function registerMemoryFlushPlanResolverForPlugin( - pluginId: string, - resolver: MemoryFlushPlanResolver, -): void { - patchMemoryCapability(pluginId, { flushPlanResolver: resolver }); -} - export function resolveMemoryFlushPlan(params: { cfg?: OpenClawConfig; nowMs?: number; }): MemoryFlushPlan | null { return memoryPluginState.capability?.capability.flushPlanResolver?.(params) ?? null; } -export function registerMemoryRuntimeForPlugin( - pluginId: string, - runtime: MemoryPluginRuntime, -): void { - patchMemoryCapability(pluginId, { runtime }); -} - export function getMemoryRuntime(): MemoryPluginRuntime | undefined { return memoryPluginState.capability?.capability.runtime; } diff --git a/src/plugins/native-module-require.test.ts b/src/plugins/native-module-require.test.ts index 6ee75d39883a..6d0532699392 100644 --- a/src/plugins/native-module-require.test.ts +++ b/src/plugins/native-module-require.test.ts @@ -77,7 +77,7 @@ describe("tryNativeRequireJavaScriptModule", () => { it("declines missing dependency errors when source-transform fallback is available", () => { const dir = makeTempDir(); const modulePath = path.join(dir, "plugin.cjs"); - fs.writeFileSync(modulePath, 'require("openclaw/plugin-sdk");\n', "utf8"); + fs.writeFileSync(modulePath, 'require("openclaw/plugin-sdk/core");\n', "utf8"); expect( tryNativeRequireJavaScriptModule(modulePath, { diff --git a/src/plugins/plugin-api.types.ts b/src/plugins/plugin-api.types.ts index 4f42adbea525..1eb9a74b6eda 100644 --- a/src/plugins/plugin-api.types.ts +++ b/src/plugins/plugin-api.types.ts @@ -438,13 +438,6 @@ export type OpenClawPluginApi = { registerMemoryCapability: ( capability: import("./memory-state.js").MemoryPluginCapability, ) => void; - /** - * Register the system prompt section builder for this memory plugin (exclusive slot). - * @deprecated Use registerMemoryCapability({ promptBuilder }) instead. - */ - registerMemoryPromptSection: ( - builder: import("./memory-state.js").MemoryPromptSectionBuilder, - ) => void; /** Register an additive memory-adjacent prompt section (non-exclusive). */ registerMemoryPromptSupplement: ( builder: import("./memory-state.js").MemoryPromptSectionBuilder, @@ -457,16 +450,6 @@ export type OpenClawPluginApi = { ) => void; /** Register an additive memory-adjacent search/read corpus supplement (non-exclusive). */ registerMemoryCorpusSupplement: (supplement: MemoryCorpusSupplement) => void; - /** - * Register the pre-compaction flush plan resolver for this memory plugin (exclusive slot). - * @deprecated Use registerMemoryCapability({ flushPlanResolver }) instead. - */ - registerMemoryFlushPlan: (resolver: import("./memory-state.js").MemoryFlushPlanResolver) => void; - /** - * Register the active memory runtime adapter for this memory plugin (exclusive slot). - * @deprecated Use registerMemoryCapability({ runtime }) instead. - */ - registerMemoryRuntime: (runtime: import("./memory-state.js").MemoryPluginRuntime) => void; /** * Register a memory embedding provider adapter. Multiple adapters may coexist. * @deprecated New embedding providers should use `registerEmbeddingProvider` diff --git a/src/plugins/plugin-definition.types.ts b/src/plugins/plugin-definition.types.ts index b2e6bfc9213d..ebfd8a08ecce 100644 --- a/src/plugins/plugin-definition.types.ts +++ b/src/plugins/plugin-definition.types.ts @@ -25,7 +25,6 @@ export type OpenClawPluginDefinition = { nodeHostCommands?: OpenClawPluginNodeHostCommand[]; securityAuditCollectors?: OpenClawPluginSecurityAuditCollector[]; register?: (api: OpenClawPluginApi) => void; - activate?: (api: OpenClawPluginApi) => void; }; export type OpenClawPluginModule = OpenClawPluginDefinition | ((api: OpenClawPluginApi) => void); diff --git a/src/plugins/plugin-metadata-snapshot.memo.test.ts b/src/plugins/plugin-metadata-snapshot.memo.test.ts index 8b51d55c906f..f40acfd54afc 100644 --- a/src/plugins/plugin-metadata-snapshot.memo.test.ts +++ b/src/plugins/plugin-metadata-snapshot.memo.test.ts @@ -646,7 +646,6 @@ describe("loadPluginMetadataSnapshot process memo", () => { it.each([ ["persisted-registry-missing", undefined], ["persisted-registry-stale-source", undefined], - ["persisted-registry-disabled", undefined], [undefined, { preferPersisted: false }], ])("memoizes derived snapshots for %s diagnostics within the process", (code, options) => { const stateDir = tempStateDir(); diff --git a/src/plugins/plugin-metadata-snapshot.ts b/src/plugins/plugin-metadata-snapshot.ts index 31719cabd786..2de384192804 100644 --- a/src/plugins/plugin-metadata-snapshot.ts +++ b/src/plugins/plugin-metadata-snapshot.ts @@ -69,7 +69,6 @@ const MEMO_RELEVANT_ENV_KEYS = [ "OPENCLAW_CONFIG_PATH", "OPENCLAW_DISABLE_BUNDLED_PLUGINS", "OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS", - "OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY", "OPENCLAW_HOME", "OPENCLAW_NIX_MODE", "OPENCLAW_STATE_DIR", @@ -180,13 +179,7 @@ function resolvePersistedRegistryFastMemoFingerprint(params: { preferPersisted?: boolean; stateDir?: string; }): Record { - const disabledByEnv = params.env.OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY?.trim().toLowerCase(); - const disabled = - params.preferPersisted === false || - (Boolean(disabledByEnv) && - disabledByEnv !== "0" && - disabledByEnv !== "false" && - disabledByEnv !== "no"); + const disabled = params.preferPersisted === false; if (disabled) { return { disabled: true }; } @@ -755,4 +748,3 @@ function loadPluginMetadataSnapshotImpl(params: LoadPluginMetadataSnapshotParams }, }; } -/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/plugins/plugin-metadata-snapshot.types.ts b/src/plugins/plugin-metadata-snapshot.types.ts index cd67237e135a..2b983dd89cb2 100644 --- a/src/plugins/plugin-metadata-snapshot.types.ts +++ b/src/plugins/plugin-metadata-snapshot.types.ts @@ -34,7 +34,6 @@ export type PluginMetadataSnapshotMetrics = { export type PluginMetadataSnapshotRegistryDiagnostic = { level: "info" | "warn"; code: - | "persisted-registry-disabled" | "persisted-registry-missing" | "persisted-registry-stale-policy" | "persisted-registry-stale-source"; diff --git a/src/plugins/plugin-module-loader-cache.test.ts b/src/plugins/plugin-module-loader-cache.test.ts index b3feebc4c858..29afa331ae5e 100644 --- a/src/plugins/plugin-module-loader-cache.test.ts +++ b/src/plugins/plugin-module-loader-cache.test.ts @@ -452,7 +452,6 @@ describe("getCachedPluginModuleLoader", () => { importerUrl: "file:///repo/src/plugins/public-surface-loader.ts", loaderFilename: "file:///repo/src/plugins/public-surface-loader.ts", aliasMap: { - "openclaw/plugin-sdk": "/repo/dist/plugin-sdk/root-alias.cjs", "openclaw/plugin-sdk/core": "/repo/dist/plugin-sdk/core.js", }, createLoader: asPluginModuleLoaderFactory(createJiti), @@ -682,7 +681,7 @@ describe("getCachedPluginModuleLoader", () => { modulePath: "/repo/dist/extensions/demo/api.js", importerUrl: "file:///repo/src/plugins/bundled-capability-runtime.ts", loaderFilename: "file:///repo/src/plugins/bundled-capability-runtime.ts", - aliasMap: { "openclaw/plugin-sdk": "/repo/shim.js" }, + aliasMap: { "openclaw/plugin-sdk/core": "/repo/core.js" }, tryNative: false, createLoader: asPluginModuleLoaderFactory(createJiti), }); diff --git a/src/plugins/plugin-registry-contributions.ts b/src/plugins/plugin-registry-contributions.ts index 29b03ffa1bcb..ac10e910334e 100644 --- a/src/plugins/plugin-registry-contributions.ts +++ b/src/plugins/plugin-registry-contributions.ts @@ -74,12 +74,6 @@ type ResolveManifestContractOwnerPluginIdParams = LoadPluginRegistryParams & { origin?: PluginOrigin; }; -type ResolveManifestContractPluginIdsByCompatibilityRuntimePathParams = LoadPluginRegistryParams & { - contract: PluginManifestContractListKey; - path: string | undefined; - origin?: PluginOrigin; -}; - function normalizeContributionId(value: string): string { return value.trim(); } @@ -377,24 +371,6 @@ export function resolveManifestContractPluginIds( .toSorted((left, right) => left.localeCompare(right)); } -export function resolveManifestContractPluginIdsByCompatibilityRuntimePath( - params: ResolveManifestContractPluginIdsByCompatibilityRuntimePathParams, -): string[] { - const normalizedPath = params.path?.trim(); - if (!normalizedPath) { - return []; - } - return loadManifestContractRegistry(params) - .plugins.filter( - (plugin) => - (!params.origin || plugin.origin === params.origin) && - listManifestContractValues(plugin, params.contract).length > 0 && - (plugin.configContracts?.compatibilityRuntimePaths ?? []).includes(normalizedPath), - ) - .map((plugin) => plugin.id) - .toSorted((left, right) => left.localeCompare(right)); -} - export function resolveManifestContractOwnerPluginId( params: ResolveManifestContractOwnerPluginIdParams, ): string | undefined { diff --git a/src/plugins/plugin-registry-snapshot.ts b/src/plugins/plugin-registry-snapshot.ts index a311f158af06..7e87845d3895 100644 --- a/src/plugins/plugin-registry-snapshot.ts +++ b/src/plugins/plugin-registry-snapshot.ts @@ -46,7 +46,6 @@ export type PluginRegistryRecord = InstalledPluginIndexRecord; type PluginRegistryInspection = InstalledPluginIndexStoreInspection; export type { PluginRegistrySnapshotSource } from "./plugin-registry-snapshot.types.js"; type PluginRegistrySnapshotDiagnosticCode = - | "persisted-registry-disabled" | "persisted-registry-missing" | "persisted-registry-stale-policy" | "persisted-registry-stale-source"; @@ -64,7 +63,6 @@ type PluginRegistrySnapshotResult = { discovery?: PluginDiscoveryResult; }; -const DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV = "OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY"; const MAX_PLUGIN_REGISTRY_SNAPSHOT_MEMOS = 8; const REGISTRY_SNAPSHOT_MEMO_ENV_KEYS = [ "APPDATA", @@ -74,7 +72,6 @@ const REGISTRY_SNAPSHOT_MEMO_ENV_KEYS = [ "OPENCLAW_CONFIG_PATH", "OPENCLAW_DISABLE_BUNDLED_PLUGINS", "OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS", - DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV, "OPENCLAW_HOME", "OPENCLAW_NIX_MODE", "OPENCLAW_STATE_DIR", @@ -95,10 +92,6 @@ function clearLoadPluginRegistrySnapshotMemo(): void { registerPluginMetadataProcessMemoLifecycleClear(clearLoadPluginRegistrySnapshotMemo); -function formatDeprecatedPersistedRegistryDisableWarning(): string { - return `${DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV} is a deprecated break-glass compatibility switch; use \`openclaw plugins registry --refresh\` or \`openclaw doctor --fix\` to repair registry state.`; -} - export type LoadPluginRegistryParams = LoadInstalledPluginIndexParams & InstalledPluginIndexStoreOptions & { index?: PluginRegistrySnapshot; @@ -109,11 +102,6 @@ type GetPluginRecordParams = LoadPluginRegistryParams & { pluginId: string; }; -function hasEnvFlag(env: NodeJS.ProcessEnv, name: string): boolean { - const value = env[name]?.trim().toLowerCase(); - return Boolean(value && value !== "0" && value !== "false" && value !== "no"); -} - function pickRegistrySnapshotMemoEnv(env: NodeJS.ProcessEnv): Record { return Object.fromEntries( REGISTRY_SNAPSHOT_MEMO_ENV_KEYS.flatMap((key) => { @@ -143,8 +131,7 @@ function resolvePluginRegistrySnapshotMemoKey( if (!canMemoizePluginRegistrySnapshot(params)) { return undefined; } - const persistedReadsEnabled = - params.preferPersisted !== false && !hasEnvFlag(env, DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV); + const persistedReadsEnabled = params.preferPersisted !== false; const persistedRegistryFingerprint = persistedReadsEnabled ? hashJson( readPersistedInstalledPluginIndexSync({ @@ -561,8 +548,7 @@ export function loadPluginRegistrySnapshotWithMetadata( } const diagnostics: PluginRegistrySnapshotDiagnostic[] = []; const disabledByCaller = params.preferPersisted === false; - const disabledByEnv = hasEnvFlag(env, DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV); - const persistedReadsEnabled = !disabledByCaller && !disabledByEnv; + const persistedReadsEnabled = !disabledByCaller; const persistedInstallRecordReadsEnabled = persistedReadsEnabled; let persistedIndex: InstalledPluginIndex | null; if (persistedInstallRecordReadsEnabled) { @@ -648,14 +634,6 @@ export function loadPluginRegistrySnapshotWithMetadata( message: "Persisted plugin registry is missing or invalid; using derived plugin index.", }); } - } else { - diagnostics.push({ - level: "warn", - code: "persisted-registry-disabled", - message: disabledByEnv - ? `${formatDeprecatedPersistedRegistryDisableWarning()} Using legacy derived plugin index.` - : "Persisted plugin registry reads are disabled by the caller; using derived plugin index.", - }); } const derived = loadInstalledPluginIndexWithDiscovery({ diff --git a/src/plugins/plugin-registry.test.ts b/src/plugins/plugin-registry.test.ts index 9931e846eae2..32341a64ac8a 100644 --- a/src/plugins/plugin-registry.test.ts +++ b/src/plugins/plugin-registry.test.ts @@ -31,13 +31,11 @@ import { refreshPluginRegistry, resolveManifestContractOwnerPluginId, resolveManifestContractPluginIds, - resolveManifestContractPluginIdsByCompatibilityRuntimePath, resolvePluginContributionOwners, } from "./plugin-registry.js"; import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js"; const tempDirs: string[] = []; -const DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV = "OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY"; function resolveProviderOwners( params: Omit< @@ -123,7 +121,7 @@ function createCandidate(rootDir: string, pluginId = "demo"): PluginCandidate { webSearchProviders: [`${pluginId}-search`], }, configContracts: { - compatibilityRuntimePaths: [`tools.web.search.${pluginId}-search.apiKey`], + compatibilityRuntimePaths: [`legacyProvider.${pluginId}-search.webhook`], }, }), "utf8", @@ -292,13 +290,6 @@ describe("plugin registry facade", () => { value: "demo-search", }), ).toBe("demo"); - expect( - resolveManifestContractPluginIdsByCompatibilityRuntimePath({ - index, - contract: "webSearchProviders", - path: "tools.web.search.demo-search.apiKey", - }), - ).toEqual(["demo"]); }); it("keeps disabled records inspectable while excluding owners by default", () => { @@ -964,26 +955,6 @@ describe("plugin registry facade", () => { expectSnapshotPluginIds(second.snapshot, ["second-external"]); }); - it("falls back to the derived registry when persisted reads are disabled", async () => { - const stateDir = makeTempDir(); - const rootDir = makeTempDir(); - const candidate = createCandidate(rootDir); - await writePersistedInstalledPluginIndex(createIndex("persisted"), { stateDir }); - - const result = loadPluginRegistrySnapshotWithMetadata({ - stateDir, - candidates: [candidate], - env: hermeticEnv({ [DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV]: "1" }), - }); - - expect(result.source).toBe("derived"); - expectDiagnosticCodes(result.diagnostics, ["persisted-registry-disabled"]); - expect(String(requireRecord(result.diagnostics[0], "diagnostic").message)).toContain( - "deprecated break-glass compatibility switch", - ); - expectSnapshotPluginIds(result.snapshot, ["demo"]); - }); - it("derives a fresh registry without persisted install records when caller disables persisted reads", async () => { const stateDir = makeTempDir(); const rootDir = makeTempDir(); diff --git a/src/plugins/plugin-sdk-native-resolver.test.ts b/src/plugins/plugin-sdk-native-resolver.test.ts index 4fa77b9213bb..1387978f784b 100644 --- a/src/plugins/plugin-sdk-native-resolver.test.ts +++ b/src/plugins/plugin-sdk-native-resolver.test.ts @@ -29,7 +29,6 @@ function writeFakeOpenClawPackage(root: string): { distRoot: string; loaderModul }, exports: { "./cli-entry": "./dist/cli-entry.js", - "./plugin-sdk": "./dist/plugin-sdk/root-alias.cjs", "./plugin-sdk/agent-runtime": "./dist/plugin-sdk/agent-runtime.js", "./plugin-sdk/channel-message": "./dist/plugin-sdk/channel-message.js", "./plugin-sdk/channel-outbound": "./dist/plugin-sdk/channel-outbound.js", @@ -40,7 +39,6 @@ function writeFakeOpenClawPackage(root: string): { distRoot: string; loaderModul const distRoot = path.join(root, "dist"); const pluginSdkDir = path.join(distRoot, "plugin-sdk"); fs.mkdirSync(pluginSdkDir, { recursive: true }); - fs.writeFileSync(path.join(pluginSdkDir, "root-alias.cjs"), "module.exports = {};\n", "utf8"); fs.writeFileSync( path.join(pluginSdkDir, "agent-runtime.js"), "export const agentRuntimeSource = import.meta.url;\n", @@ -294,13 +292,11 @@ describe("installOpenClawPluginSdkNativeResolver", () => { ' type: "module",', ' bin: { openclaw: "./openclaw.mjs" },', " exports: {", - ' "./plugin-sdk": "./dist/plugin-sdk/root-alias.cjs",', ' "./plugin-sdk/channel-outbound": "./dist/plugin-sdk/channel-outbound.js",', " },", "});", 'fs.writeFileSync(path.join(root, "openclaw.mjs"), "#!/usr/bin/env node\\n", "utf8");', 'fs.mkdirSync(path.join(root, "dist", "plugin-sdk"), { recursive: true });', - 'fs.writeFileSync(path.join(root, "dist", "plugin-sdk", "root-alias.cjs"), "module.exports = {};\\n", "utf8");', 'fs.writeFileSync(path.join(root, "dist", "plugin-sdk", "channel-outbound.js"), "export const defineChannelMessageAdapter = () => \\"adapter\\";\\n", "utf8");', 'const loaderModulePath = path.join(root, "dist", "plugins", "loader.js");', "fs.mkdirSync(path.dirname(loaderModulePath), { recursive: true });", diff --git a/src/plugins/plugin-sdk-native-resolver.ts b/src/plugins/plugin-sdk-native-resolver.ts index da2263431e81..beb1507b1076 100644 --- a/src/plugins/plugin-sdk-native-resolver.ts +++ b/src/plugins/plugin-sdk-native-resolver.ts @@ -124,9 +124,7 @@ function resolveLoaderModulePath(options: InstallOpenClawPluginSdkNativeResolver } function isPluginSdkAliasSpecifier(specifier: string): boolean { - return PLUGIN_SDK_PACKAGE_PREFIXES.some( - (prefix) => specifier === prefix || specifier.startsWith(`${prefix}/`), - ); + return PLUGIN_SDK_PACKAGE_PREFIXES.some((prefix) => specifier.startsWith(`${prefix}/`)); } function isNativeLoadableSdkTarget(targetPath: string): boolean { diff --git a/src/plugins/provider-catalog.types.ts b/src/plugins/provider-catalog.types.ts index 679e985b83a6..897593dbeb21 100644 --- a/src/plugins/provider-catalog.types.ts +++ b/src/plugins/provider-catalog.types.ts @@ -115,18 +115,3 @@ export type ProviderAugmentModelCatalogContext = { resolveProviderApiKey?: ProviderCatalogContext["resolveProviderApiKey"]; entries: ModelCatalogEntry[]; }; - -/** - * @deprecated Use ProviderCatalogOrder. - */ -export type ProviderDiscoveryOrder = ProviderCatalogOrder; - -/** - * @deprecated Use ProviderCatalogContext. - */ -export type ProviderDiscoveryContext = ProviderCatalogContext; - -/** - * @deprecated Use ProviderPluginCatalog. - */ -export type ProviderPluginDiscovery = ProviderPluginCatalog; diff --git a/src/plugins/provider-discovery.runtime.test.ts b/src/plugins/provider-discovery.runtime.test.ts index 68d3292b1cb0..e339c6ab11e0 100644 --- a/src/plugins/provider-discovery.runtime.test.ts +++ b/src/plugins/provider-discovery.runtime.test.ts @@ -159,7 +159,6 @@ function createManifestPluginWithEntryAndRuntimeDiscovery(): PluginManifestRecor function createManifestPluginWithoutDiscovery(params: { id: string; - providerAuthEnvVars?: Record; setupProviders?: NonNullable["providers"]; }): PluginManifestRecord { const { providerDiscoverySource: _providerDiscoverySource, ...plugin } = createManifestPlugin( @@ -168,7 +167,6 @@ function createManifestPluginWithoutDiscovery(params: { return { ...plugin, ...(params.setupProviders ? { setup: { providers: params.setupProviders } } : {}), - ...(params.providerAuthEnvVars ? { providerAuthEnvVars: params.providerAuthEnvVars } : {}), }; } @@ -523,11 +521,11 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => { createManifestPlugin("deepseek"), createManifestPluginWithoutDiscovery({ id: "kilocode", - providerAuthEnvVars: { kilocode: ["KILOCODE_API_KEY"] }, + setupProviders: [{ id: "kilocode", envVars: ["KILOCODE_API_KEY"] }], }), createManifestPluginWithoutDiscovery({ id: "unused", - providerAuthEnvVars: { unused: ["UNUSED_API_KEY"] }, + setupProviders: [{ id: "unused", envVars: ["UNUSED_API_KEY"] }], }), ], diagnostics: [], diff --git a/src/plugins/provider-discovery.runtime.ts b/src/plugins/provider-discovery.runtime.ts index 7a31db754a96..5682ce5ab202 100644 --- a/src/plugins/provider-discovery.runtime.ts +++ b/src/plugins/provider-discovery.runtime.ts @@ -109,9 +109,7 @@ function loadProviderDiscoveryModule(params: { } function hasLiveProviderDiscoveryHook(provider: ProviderPlugin): boolean { - return ( - typeof provider.catalog?.run === "function" || typeof provider.discovery?.run === "function" - ); + return typeof provider.catalog?.run === "function"; } function hasProviderCatalogHook(provider: ProviderPlugin): boolean { @@ -124,10 +122,7 @@ function hasProviderAuthEnvCredential( plugin: PluginManifestRecord, env: NodeJS.ProcessEnv, ): boolean { - const envVars = [ - ...(plugin.setup?.providers ?? []).flatMap((provider) => provider.envVars ?? []), - ...Object.values(plugin.providerAuthEnvVars ?? {}).flat(), - ]; + const envVars = (plugin.setup?.providers ?? []).flatMap((provider) => provider.envVars ?? []); return envVars.some((name) => { const value = env[name]?.trim(); return value !== undefined && value !== ""; diff --git a/src/plugins/provider-discovery.test.ts b/src/plugins/provider-discovery.test.ts index ce1efb387fe3..434f72af36fa 100644 --- a/src/plugins/provider-discovery.test.ts +++ b/src/plugins/provider-discovery.test.ts @@ -6,17 +6,15 @@ import type { ModelDefinitionConfig, ModelProviderConfig } from "../config/types import { groupPluginDiscoveryProvidersByOrder, normalizePluginDiscoveryResult, - runProviderCatalog, runProviderStaticCatalog, } from "./provider-discovery.js"; import * as providerDiscoveryModule from "./provider-discovery.js"; -import type { ProviderCatalogResult, ProviderDiscoveryOrder, ProviderPlugin } from "./types.js"; +import type { ProviderCatalogOrder, ProviderPlugin } from "./types.js"; function makeProvider(params: { id: string; label?: string; - order?: ProviderDiscoveryOrder; - mode?: "catalog" | "discovery"; + order?: ProviderCatalogOrder; aliases?: string[]; hookAliases?: string[]; }): ProviderPlugin { @@ -30,7 +28,7 @@ function makeProvider(params: { auth: [], ...(params.aliases ? { aliases: params.aliases } : {}), ...(params.hookAliases ? { hookAliases: params.hookAliases } : {}), - ...(params.mode === "discovery" ? { discovery: hook } : { catalog: hook }), + catalog: hook, }; } @@ -61,7 +59,7 @@ function makeModel(id: string): ModelDefinitionConfig { function expectGroupedProviderIds( providers: readonly ProviderPlugin[], - expected: Record, + expected: Record, ) { const grouped = groupPluginDiscoveryProvidersByOrder([...providers]); const actual = { @@ -73,34 +71,6 @@ function expectGroupedProviderIds( expect(actual).toEqual(expected); } -function createCatalogRuntimeContext() { - return { - config: {}, - env: {}, - resolveProviderApiKey: () => ({ apiKey: undefined }), - resolveProviderAuth: () => ({ - apiKey: undefined, - discoveryApiKey: undefined, - mode: "none" as const, - source: "none" as const, - }), - }; -} - -function createCatalogProvider(params: { - id?: string; - catalogRun?: () => Promise; - discoveryRun?: () => Promise; -}) { - return { - id: params.id ?? "demo", - label: "Demo", - auth: [], - ...(params.catalogRun ? { catalog: { run: params.catalogRun } } : {}), - ...(params.discoveryRun ? { discovery: { run: params.discoveryRun } } : {}), - }; -} - function expectNormalizedDiscoveryResult(params: { provider: ProviderPlugin; result: Parameters[0]["result"]; @@ -121,18 +91,6 @@ type NormalizePluginDiscoveryResultCase = { expected: Record; }; -async function expectProviderCatalogResult(params: { - provider: ProviderPlugin; - expected: Record; -}) { - await expect( - runProviderCatalog({ - provider: params.provider, - ...createCatalogRuntimeContext(), - }), - ).resolves.toEqual(params.expected); -} - describe("resolveInstalledPluginProviderContributionIds", () => { it("keeps current production callers off the ambiguous runtime-discovery alias", () => { const callerPaths = [ @@ -170,18 +128,6 @@ describe("groupPluginDiscoveryProvidersByOrder", () => { late: ["late-a", "late-b"], }, }, - { - name: "uses the legacy discovery hook when catalog is absent", - providers: [ - makeProvider({ id: "legacy", label: "Legacy", order: "profile", mode: "discovery" }), - ], - expected: { - simple: [], - profile: ["legacy"], - paired: [], - late: [], - }, - }, ] as const)("$name", ({ providers, expected }) => { expectGroupedProviderIds(providers, expected); }); @@ -488,27 +434,3 @@ describe("runProviderStaticCatalog", () => { expect(seenContexts[0]).not.toHaveProperty("workspaceDir"); }); }); - -describe("runProviderCatalog", () => { - it("prefers catalog over discovery when both exist", async () => { - const catalogRun = async () => ({ - provider: makeModelProviderConfig({ baseUrl: "http://catalog.example/v1" }), - }); - const discoveryRun = async () => ({ - provider: makeModelProviderConfig({ baseUrl: "http://discovery.example/v1" }), - }); - - await expectProviderCatalogResult({ - provider: createCatalogProvider({ - catalogRun, - discoveryRun, - }), - expected: { - provider: { - baseUrl: "http://catalog.example/v1", - models: [], - }, - }, - }); - }); -}); diff --git a/src/plugins/provider-discovery.ts b/src/plugins/provider-discovery.ts index 607bb396728e..0ed4d50f0a5e 100644 --- a/src/plugins/provider-discovery.ts +++ b/src/plugins/provider-discovery.ts @@ -5,9 +5,9 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { createLazyImportLoader } from "../shared/lazy-promise.js"; import type { PluginMetadataRegistryView } from "./plugin-metadata-snapshot.types.js"; import { copyProviderCatalogResultProjection } from "./provider-catalog-result.js"; -import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js"; +import type { ProviderCatalogOrder, ProviderPlugin } from "./types.js"; -const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"]; +const DISCOVERY_ORDER: readonly ProviderCatalogOrder[] = ["simple", "profile", "paired", "late"]; const DANGEROUS_PROVIDER_KEYS = new Set(["__proto__", "prototype", "constructor"]); const providerRuntimeLoader = createLazyImportLoader( () => import("./provider-discovery.runtime.js"), @@ -18,7 +18,7 @@ function loadProviderRuntime() { } function resolveProviderCatalogHook(provider: ProviderPlugin) { - return provider.catalog ?? provider.discovery; + return provider.catalog; } function resolveProviderCatalogOrderHook(provider: ProviderPlugin) { @@ -59,13 +59,13 @@ export async function resolveRuntimePluginDiscoveryProviders( /** Groups plugin providers into stable discovery phases for catalog probing. */ export function groupPluginDiscoveryProvidersByOrder( providers: ProviderPlugin[], -): Record { +): Record { const grouped = { simple: [], profile: [], paired: [], late: [], - } as Record; + } as Record; for (const provider of providers) { const order = resolveProviderCatalogOrderHook(provider)?.order ?? "late"; diff --git a/src/plugins/provider-external-auth.types.ts b/src/plugins/provider-external-auth.types.ts index 20e792eeb20c..51214e049da6 100644 --- a/src/plugins/provider-external-auth.types.ts +++ b/src/plugins/provider-external-auth.types.ts @@ -35,16 +35,9 @@ export type ProviderResolveExternalAuthProfilesContext = { store: AuthProfileStore; }; -/** OAuth-specific external auth profile resolution context. */ -export type ProviderResolveExternalOAuthProfilesContext = - ProviderResolveExternalAuthProfilesContext; - /** External auth profile credential resolved for a provider. */ export type ProviderExternalAuthProfile = { profileId: string; credential: OAuthCredential; persistence?: "runtime-only" | "persisted"; }; - -/** OAuth-specific provider external auth profile alias. */ -export type ProviderExternalOAuthProfile = ProviderExternalAuthProfile; diff --git a/src/plugins/provider-plugin.types.ts b/src/plugins/provider-plugin.types.ts index b9b308c6eb7c..12c446c10fb0 100644 --- a/src/plugins/provider-plugin.types.ts +++ b/src/plugins/provider-plugin.types.ts @@ -23,7 +23,6 @@ import type { ProviderBuiltInModelSuppressionResult, ProviderModernModelPolicyContext, ProviderAugmentModelCatalogContext, - ProviderPluginDiscovery, } from "./provider-catalog.types.js"; import type { ProviderApplyConfigDefaultsContext, @@ -32,15 +31,12 @@ import type { } from "./provider-config-context.types.js"; import type { ProviderExternalAuthProfile, - ProviderExternalOAuthProfile, ProviderResolveExternalAuthProfilesContext, - ProviderResolveExternalOAuthProfilesContext, ProviderResolveSyntheticAuthContext, ProviderSyntheticAuthResult, } from "./provider-external-auth.types.js"; import type { ProviderReasoningOutputMode, - ProviderCapabilities, ProviderReplayPolicy, ProviderReplayPolicyContext, ProviderSanitizeReplayHistoryContext, @@ -74,7 +70,6 @@ import type { import type { ProviderDefaultThinkingPolicyContext, ProviderThinkingProfile, - ProviderThinkingPolicyContext, } from "./provider-thinking.types.js"; import type { ProviderCreateStreamFnContext, @@ -146,13 +141,6 @@ export type ProviderPlugin = { * alongside `moonshotai/kimi-k2.5`). */ preserveLiteralProviderPrefix?: boolean; - /** - * @deprecated Use catalog. - * - * Legacy alias for catalog. - * Kept for compatibility with existing provider plugins. - */ - discovery?: ProviderPluginDiscovery; /** * Sync runtime fallback for model ids not present in the local catalog. * @@ -231,13 +219,6 @@ export type ProviderPlugin = { * markers rather than a normal API-key env var. */ resolveConfigApiKey?: (ctx: ProviderResolveConfigApiKeyContext) => string | null | undefined; - /** - * @deprecated Legacy static capability bag kept only for compatibility. - * - * New provider behavior should use explicit hooks instead. Core replay and - * stream/runtime logic no longer consumes this field. - */ - capabilities?: ProviderCapabilities; /** * Provider-owned replay/compaction policy override. * @@ -473,23 +454,6 @@ export type ProviderPlugin = { | Promise | ReadonlyArray | null | undefined> | null | undefined; - /** - * Provider-owned binary thinking toggle. - * - * Return true when the provider exposes a coarse on/off reasoning control - * instead of the normal multi-level ladder shown by `/think`. - * - * @deprecated Prefer `resolveThinkingProfile`. - */ - isBinaryThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined; - /** - * Provider-owned xhigh reasoning support. - * - * Return true only for models that should expose the `xhigh` thinking level. - * - * @deprecated Prefer `resolveThinkingProfile`. - */ - supportsXHighThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined; /** * Provider-owned thinking level profile. * @@ -501,17 +465,6 @@ export type ProviderPlugin = { resolveThinkingProfile?: ( ctx: ProviderDefaultThinkingPolicyContext, ) => ProviderThinkingProfile | null | undefined; - /** - * Provider-owned default thinking level. - * - * Use this to keep model-family defaults (for example Claude 4.6 => - * adaptive) out of core command logic. - * - * @deprecated Prefer `resolveThinkingProfile`. - */ - resolveDefaultThinkingLevel?: ( - ctx: ProviderDefaultThinkingPolicyContext, - ) => "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive" | null | undefined; /** * Provider-owned system-prompt contribution. * @@ -667,18 +620,6 @@ export type ProviderPlugin = { | ReadonlyArray | null | undefined; - /** - * @deprecated Declare `contracts.externalAuthProviders` in the plugin manifest - * and implement `resolveExternalAuthProfiles` instead. Kept at the public - * plugin boundary until the SDK removal window closes. - */ - resolveExternalOAuthProfiles?: ( - ctx: ProviderResolveExternalOAuthProfilesContext, - ) => - | Array - | ReadonlyArray - | null - | undefined; /** * Provider-owned precedence rule for stored synthetic auth profiles. * diff --git a/src/plugins/provider-replay.types.ts b/src/plugins/provider-replay.types.ts index 85d94308f4ed..836113bc3da2 100644 --- a/src/plugins/provider-replay.types.ts +++ b/src/plugins/provider-replay.types.ts @@ -9,16 +9,6 @@ type ProviderReplayToolCallIdMode = "strict" | "strict9"; export type ProviderReasoningOutputMode = "native" | "tagged"; -/** - * @deprecated Legacy static provider capability bag. - * - * Core replay/runtime ownership now lives on explicit provider hooks such as - * `buildReplayPolicy`, `normalizeToolSchemas`, and `wrapStreamFn`. OpenClaw no - * longer reads this bag at runtime, but the field remains typed so existing - * third-party plugins do not fail to compile immediately. - */ -export type ProviderCapabilities = Record; - /** * Provider-owned replay/compaction transcript policy. * diff --git a/src/plugins/provider-runtime.synthetic-auth-discovery.test.ts b/src/plugins/provider-runtime.synthetic-auth-discovery.test.ts index d75159a4f01e..756fcd5d0cf5 100644 --- a/src/plugins/provider-runtime.synthetic-auth-discovery.test.ts +++ b/src/plugins/provider-runtime.synthetic-auth-discovery.test.ts @@ -65,7 +65,6 @@ const resolveProviderOwnerIds = vi.hoisted(() => vi.mock("./providers.js", () => ({ resolveCatalogHookProviderPluginIds: vi.fn(() => []), - resolveExternalAuthProfileCompatFallbackPluginIds: vi.fn(() => []), resolveExternalAuthProfileProviderPluginIds: vi.fn(() => []), resolveOwningPluginIdsForProvider: resolveProviderOwnerIds, resolveOwningPluginIdsForProviderRef: resolveProviderOwnerIds, diff --git a/src/plugins/provider-runtime.test.ts b/src/plugins/provider-runtime.test.ts index 7341dcfa86ff..abc770980198 100644 --- a/src/plugins/provider-runtime.test.ts +++ b/src/plugins/provider-runtime.test.ts @@ -24,8 +24,6 @@ type ResolveCatalogHookProviderPluginIds = typeof import("./providers.js").resolveCatalogHookProviderPluginIds; type ResolveUsageHookProviderPluginContracts = typeof import("./providers.js").resolveUsageHookProviderPluginContracts; -type ResolveExternalAuthProfileCompatFallbackPluginIds = - typeof import("./providers.js").resolveExternalAuthProfileCompatFallbackPluginIds; type ResolveExternalAuthProfileProviderPluginIds = typeof import("./providers.js").resolveExternalAuthProfileProviderPluginIds; type ResolveOwningPluginIdsForProvider = @@ -41,8 +39,6 @@ const resolveCatalogHookProviderPluginIdsMock = vi.fn( (_) => [], ); -const resolveExternalAuthProfileCompatFallbackPluginIdsMock = - vi.fn((_) => [] as string[]); const resolveExternalAuthProfileProviderPluginIdsMock = vi.fn((_) => [] as string[]); const resolveOwningPluginIdsForProviderMock = vi.fn( @@ -74,10 +70,8 @@ let resolveProviderFollowupFallbackRoute: typeof import("./provider-runtime.js") let resolveProviderStreamFn: typeof import("./provider-runtime.js").resolveProviderStreamFn; let resolveProviderTransportTurnStateWithPlugin: typeof import("./provider-runtime.js").resolveProviderTransportTurnStateWithPlugin; let resolveProviderCacheTtlEligibility: typeof import("./provider-runtime.js").resolveProviderCacheTtlEligibility; -let resolveProviderBinaryThinking: typeof import("./provider-runtime.js").resolveProviderBinaryThinking; let createProviderEmbeddingProvider: typeof import("./provider-runtime.js").createProviderEmbeddingProvider; let resolveProviderThinkingProfile: typeof import("./provider-runtime.js").resolveProviderThinkingProfile; -let resolveProviderDefaultThinkingLevel: typeof import("./provider-runtime.js").resolveProviderDefaultThinkingLevel; let resolveProviderModernModelRef: typeof import("./provider-runtime.js").resolveProviderModernModelRef; let resolveProviderReasoningOutputModeWithPlugin: typeof import("./provider-runtime.js").resolveProviderReasoningOutputModeWithPlugin; let resolveProviderReplayPolicyWithPlugin: typeof import("./provider-runtime.js").resolveProviderReplayPolicyWithPlugin; @@ -88,7 +82,6 @@ let shouldDeferProviderSyntheticProfileAuthWithPlugin: typeof import("./provider let sanitizeProviderReplayHistoryWithPlugin: typeof import("./provider-runtime.js").sanitizeProviderReplayHistoryWithPlugin; let resolveProviderUsageSnapshotWithPlugin: typeof import("./provider-runtime.js").resolveProviderUsageSnapshotWithPlugin; let resolveProviderUsageAuthWithPlugin: typeof import("./provider-runtime.js").resolveProviderUsageAuthWithPlugin; -let resolveProviderXHighThinking: typeof import("./provider-runtime.js").resolveProviderXHighThinking; let normalizeProviderToolSchemasWithPlugin: typeof import("./provider-runtime.js").normalizeProviderToolSchemasWithPlugin; let inspectProviderToolSchemasWithPlugin: typeof import("./provider-runtime.js").inspectProviderToolSchemasWithPlugin; let listProviderUsagePluginDescriptors: typeof import("./provider-runtime.js").listProviderUsagePluginDescriptors; @@ -178,14 +171,6 @@ function firstMockArg(mock: { mock: { calls: unknown[][] } }): unknown { return mock.mock.calls[0]?.[0]; } -function firstMockStringArg(mock: { mock: { calls: unknown[][] } }, label: string): string { - const value = firstMockArg(mock); - if (typeof value !== "string") { - throw new Error(`Expected ${label} to be a string`); - } - return value; -} - function expectRecordFields(record: Record, fields: Record) { for (const [key, value] of Object.entries(fields)) { expect(record[key]).toEqual(value); @@ -298,8 +283,6 @@ describe("provider-runtime", () => { resolveCatalogHookProviderPluginIdsMock(params as never), resolveUsageHookProviderPluginContracts: (params: unknown) => resolveUsageHookProviderPluginContractsMock(params as never), - resolveExternalAuthProfileCompatFallbackPluginIds: (params: unknown) => - resolveExternalAuthProfileCompatFallbackPluginIdsMock(params as never), resolveExternalAuthProfileProviderPluginIds: (params: unknown) => resolveExternalAuthProfileProviderPluginIdsMock(params as never), resolveOwningPluginIdsForProvider: (params: unknown) => @@ -342,10 +325,8 @@ describe("provider-runtime", () => { resolveProviderStreamFn, resolveProviderTransportTurnStateWithPlugin, resolveProviderCacheTtlEligibility, - resolveProviderBinaryThinking, createProviderEmbeddingProvider, resolveProviderThinkingProfile, - resolveProviderDefaultThinkingLevel, resolveProviderModernModelRef, resolveProviderReasoningOutputModeWithPlugin, resolveProviderReplayPolicyWithPlugin, @@ -356,7 +337,6 @@ describe("provider-runtime", () => { sanitizeProviderReplayHistoryWithPlugin, resolveProviderUsageSnapshotWithPlugin, resolveProviderUsageAuthWithPlugin, - resolveProviderXHighThinking, normalizeProviderToolSchemasWithPlugin, inspectProviderToolSchemasWithPlugin, listProviderUsagePluginDescriptors, @@ -387,8 +367,6 @@ describe("provider-runtime", () => { resolveCatalogHookProviderPluginIdsMock.mockReturnValue([]); resolveUsageHookProviderPluginContractsMock.mockReset(); resolveUsageHookProviderPluginContractsMock.mockReturnValue([]); - resolveExternalAuthProfileCompatFallbackPluginIdsMock.mockReset(); - resolveExternalAuthProfileCompatFallbackPluginIdsMock.mockReturnValue([]); resolveExternalAuthProfileProviderPluginIdsMock.mockReset(); resolveExternalAuthProfileProviderPluginIdsMock.mockReturnValue([]); resolveOwningPluginIdsForProviderMock.mockReset(); @@ -936,47 +914,6 @@ describe("provider-runtime", () => { expect(resolvePluginProvidersMock).not.toHaveBeenCalled(); }); - it("keeps the deprecated external OAuth fallback at the plugin boundary", () => { - const unsafePluginId = "legacy-provider\nWARN forged"; - resolveExternalAuthProfileCompatFallbackPluginIdsMock.mockReturnValue([unsafePluginId]); - resolvePluginProvidersMock.mockReturnValue([ - { - id: "legacy-provider", - pluginId: unsafePluginId, - label: "Legacy Provider", - auth: [], - resolveExternalOAuthProfiles: () => [ - { - profileId: "legacy-provider:external", - credential: { - type: "oauth", - provider: "legacy-provider", - access: "access", - refresh: "refresh", - expires: Date.now() + 60_000, - }, - }, - ], - }, - ]); - - for (let i = 0; i < 2; i += 1) { - const [profile] = resolveExternalAuthProfilesWithPlugins({ - env: process.env, - context: { - env: process.env, - store: { version: 1, profiles: {} }, - }, - }); - expect(profile?.profileId).toBe("legacy-provider:external"); - } - - expect(providerRuntimeWarnMock).toHaveBeenCalledTimes(1); - const warning = firstMockStringArg(providerRuntimeWarnMock, "provider warning"); - expect(warning).toContain('Provider plugin "legacy-providerWARN forged"'); - expect(warning).not.toContain("\n"); - }); - it("resolves declared external auth plugins with different provider ids", () => { resolveExternalAuthProfileProviderPluginIdsMock.mockReturnValue(["demo-plugin"]); resolvePluginProvidersMock.mockReturnValue([ @@ -2085,9 +2022,6 @@ describe("provider-runtime", () => { resolveUsageAuth, fetchUsageSnapshot, isCacheTtlEligible: ({ modelId }) => modelId.startsWith("anthropic/"), - isBinaryThinking: () => true, - supportsXHighThinking: ({ modelId }) => modelId === "gpt-5.4", - resolveDefaultThinkingLevel: ({ reasoning }) => (reasoning ? "low" : "off"), isModernModelRef: ({ modelId }) => modelId.startsWith("gpt-5"), }, { @@ -2414,37 +2348,6 @@ describe("provider-runtime", () => { }), expected: true, }, - { - actual: () => - resolveProviderBinaryThinking({ - provider: DEMO_PROVIDER_ID, - context: createDemoProviderContext({ - modelId: "glm-5", - }), - }), - expected: true, - }, - { - actual: () => - resolveProviderXHighThinking({ - provider: DEMO_PROVIDER_ID, - context: createDemoProviderContext({ - modelId: "gpt-5.4", - }), - }), - expected: true, - }, - { - actual: () => - resolveProviderDefaultThinkingLevel({ - provider: DEMO_PROVIDER_ID, - context: createDemoProviderContext({ - modelId: "gpt-5.4", - reasoning: true, - }), - }), - expected: "low", - }, { actual: () => resolveProviderModernModelRef({ diff --git a/src/plugins/provider-runtime.ts b/src/plugins/provider-runtime.ts index 002aaf67d646..e6cb49108a31 100644 --- a/src/plugins/provider-runtime.ts +++ b/src/plugins/provider-runtime.ts @@ -4,11 +4,7 @@ import { normalizeProviderId, } from "@openclaw/model-catalog-core/provider-id"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { - sortUniqueStrings, - uniqueStrings, -} from "@openclaw/normalization-core/string-normalization"; -import { sanitizeForLog } from "../../packages/terminal-core/src/ansi.js"; +import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import type { AuthProfileCredential, OAuthCredential } from "../agents/auth-profiles/types.js"; import { resolveGpt5SystemPromptContribution } from "../agents/gpt5-prompt-overlay.js"; import { getRegisteredAgentHarness } from "../agents/harness/registry.js"; @@ -21,7 +17,6 @@ import type { ProviderSystemPromptContribution } from "../agents/system-prompt-c import type { ModelProviderConfig } from "../config/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { UsageProviderId } from "../infra/provider-usage.types.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; import { normalizeProviderModelIdWithManifest } from "./manifest-model-id-normalization.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; import { resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot.js"; @@ -50,7 +45,6 @@ import type { ProviderRuntimeModel } from "./provider-runtime-model.types.js"; import type { ProviderThinkingProfile } from "./provider-thinking.types.js"; import { resolveCatalogHookProviderPluginIds, - resolveExternalAuthProfileCompatFallbackPluginIds, resolveExternalAuthProfileProviderPluginIds, resolveOwningPluginIdsForProvider, resolveOwningPluginIdsForProviderRef, @@ -95,15 +89,11 @@ import type { ProviderResolveTransportTurnStateContext, ProviderSystemPromptContributionContext, ProviderTransformSystemPromptContext, - ProviderThinkingPolicyContext, ProviderTransportTurnState, ProviderValidateReplayTurnsContext, PluginTextTransforms, } from "./types.js"; -const log = createSubsystemLogger("plugins/provider-runtime"); -const warnedExternalAuthFallbackPluginIds = new Set(); - function matchesProviderPluginRef(provider: ProviderPlugin, providerId: string): boolean { const normalized = normalizeProviderId(providerId); if (!normalized) { @@ -177,9 +167,7 @@ export { wrapProviderStreamFn, }; -function resetExternalAuthFallbackWarningCacheForTest(): void { - warnedExternalAuthFallbackPluginIds.clear(); -} +function resetExternalAuthFallbackWarningCacheForTest(): void {} export const testing = { clearProviderRuntimePluginCacheForTest, @@ -871,26 +859,6 @@ export function resolveProviderCacheTtlEligibility(params: { return resolveProviderRuntimePlugin(params)?.isCacheTtlEligible?.(params.context); } -export function resolveProviderBinaryThinking(params: { - provider: string; - config?: OpenClawConfig; - workspaceDir?: string; - env?: NodeJS.ProcessEnv; - context: ProviderThinkingPolicyContext; -}) { - return resolveProviderRuntimePlugin(params)?.isBinaryThinking?.(params.context); -} - -export function resolveProviderXHighThinking(params: { - provider: string; - config?: OpenClawConfig; - workspaceDir?: string; - env?: NodeJS.ProcessEnv; - context: ProviderThinkingPolicyContext; -}) { - return resolveProviderRuntimePlugin(params)?.supportsXHighThinking?.(params.context); -} - export function resolveProviderThinkingProfile(params: { provider: string; config?: OpenClawConfig; @@ -905,16 +873,6 @@ export function resolveProviderThinkingProfile(params: { return resolveProviderRuntimePlugin(params)?.resolveThinkingProfile?.(params.context); } -export function resolveProviderDefaultThinkingLevel(params: { - provider: string; - config?: OpenClawConfig; - workspaceDir?: string; - env?: NodeJS.ProcessEnv; - context: ProviderDefaultThinkingPolicyContext; -}) { - return resolveProviderRuntimePlugin(params)?.resolveDefaultThinkingLevel?.(params.context); -} - export function applyProviderConfigDefaultsWithPlugin(params: { provider: string; config?: OpenClawConfig; @@ -1054,16 +1012,7 @@ export function resolveExternalAuthProfilesWithPlugins(params: { env, manifestRegistry, }); - const declaredPluginIds = new Set(externalAuthPluginIds); - const fallbackPluginIds = resolveExternalAuthProfileCompatFallbackPluginIds({ - config: params.config, - workspaceDir, - env, - declaredPluginIds, - manifestRegistry, - }); - const pluginIds = sortUniqueStrings([...externalAuthPluginIds, ...fallbackPluginIds]); - if (pluginIds.length === 0) { + if (externalAuthPluginIds.length === 0) { return []; } const matches: ProviderExternalAuthProfile[] = []; @@ -1071,21 +1020,12 @@ export function resolveExternalAuthProfilesWithPlugins(params: { ...params, workspaceDir, env, - onlyPluginIds: pluginIds, + onlyPluginIds: externalAuthPluginIds, })) { - const profiles = - plugin.resolveExternalAuthProfiles?.(params.context) ?? - plugin.resolveExternalOAuthProfiles?.(params.context); + const profiles = plugin.resolveExternalAuthProfiles?.(params.context); if (!profiles || profiles.length === 0) { continue; } - const pluginId = plugin.pluginId ?? plugin.id; - if (!declaredPluginIds.has(pluginId) && !warnedExternalAuthFallbackPluginIds.has(pluginId)) { - warnedExternalAuthFallbackPluginIds.add(pluginId); - log.warn( - `Provider plugin "${sanitizeForLog(pluginId)}" uses external auth hooks without declaring contracts.externalAuthProviders. This compatibility fallback is deprecated and will be removed in a future release.`, - ); - } matches.push(...profiles); } return matches; diff --git a/src/plugins/provider-self-hosted-setup.ts b/src/plugins/provider-self-hosted-setup.ts index 399ac462c5be..b62d91f494da 100644 --- a/src/plugins/provider-self-hosted-setup.ts +++ b/src/plugins/provider-self-hosted-setup.ts @@ -26,7 +26,7 @@ import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js import type { WizardPrompter } from "../wizard/prompts.js"; import { applyAuthProfileConfig } from "./provider-auth-helpers.js"; import type { - ProviderDiscoveryContext, + ProviderCatalogContext, ProviderAuthResult, ProviderAuthMethodNonInteractiveContext, ProviderNonInteractiveApiKeyResult, @@ -440,7 +440,7 @@ export async function promptAndConfigureOpenAICompatibleSelfHostedProviderAuth( export async function discoverOpenAICompatibleSelfHostedProvider< T extends Record, >(params: { - ctx: ProviderDiscoveryContext; + ctx: ProviderCatalogContext; providerId: string; buildProvider: (params: { apiKey?: string; baseUrl?: string }) => Promise; }): Promise<{ provider: T & { apiKey: string } } | null> { diff --git a/src/plugins/provider-thinking-active.ts b/src/plugins/provider-thinking-active.ts index 4651746f343f..b506b3ad4d30 100644 --- a/src/plugins/provider-thinking-active.ts +++ b/src/plugins/provider-thinking-active.ts @@ -3,7 +3,6 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import type { ProviderDefaultThinkingPolicyContext, ProviderThinkingProfile, - ProviderThinkingPolicyContext, } from "./provider-thinking.types.js"; import { PLUGIN_REGISTRY_STATE } from "./runtime-state-key.js"; @@ -11,14 +10,9 @@ type ActiveThinkingProvider = { id: string; aliases?: string[]; hookAliases?: string[]; - isBinaryThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined; - supportsXHighThinking?: (ctx: ProviderThinkingPolicyContext) => boolean | undefined; resolveThinkingProfile?: ( ctx: ProviderDefaultThinkingPolicyContext, ) => ProviderThinkingProfile | null | undefined; - resolveDefaultThinkingLevel?: ( - ctx: ProviderDefaultThinkingPolicyContext, - ) => "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive" | null | undefined; }; type ActiveThinkingRegistryState = { @@ -58,28 +52,8 @@ function resolveActiveThinkingProvider(providerId: string): ActiveThinkingProvid )?.provider; } -export function resolveActiveProviderBinaryThinking( - params: ThinkingHookParams, -) { - return resolveActiveThinkingProvider(params.provider)?.isBinaryThinking?.(params.context); -} - -export function resolveActiveProviderXHighThinking( - params: ThinkingHookParams, -) { - return resolveActiveThinkingProvider(params.provider)?.supportsXHighThinking?.(params.context); -} - export function resolveActiveProviderThinkingProfile( params: ThinkingHookParams, ) { return resolveActiveThinkingProvider(params.provider)?.resolveThinkingProfile?.(params.context); } - -export function resolveActiveProviderDefaultThinkingLevel( - params: ThinkingHookParams, -) { - return resolveActiveThinkingProvider(params.provider)?.resolveDefaultThinkingLevel?.( - params.context, - ); -} diff --git a/src/plugins/provider-thinking.ts b/src/plugins/provider-thinking.ts index c8e09862ddae..0baf9d3fa0e6 100644 --- a/src/plugins/provider-thinking.ts +++ b/src/plugins/provider-thinking.ts @@ -1,16 +1,8 @@ // Resolves provider thinking-level policy from active plugins or plugin metadata. import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js"; import { resolveProviderPolicySurface } from "./provider-public-artifacts.js"; -import { - resolveActiveProviderBinaryThinking, - resolveActiveProviderDefaultThinkingLevel, - resolveActiveProviderThinkingProfile, - resolveActiveProviderXHighThinking, -} from "./provider-thinking-active.js"; -import type { - ProviderDefaultThinkingPolicyContext, - ProviderThinkingPolicyContext, -} from "./provider-thinking.types.js"; +import { resolveActiveProviderThinkingProfile } from "./provider-thinking-active.js"; +import type { ProviderDefaultThinkingPolicyContext } from "./provider-thinking.types.js"; function resolveProviderPublicPolicySurface(providerId: string) { const metadataSnapshot = getCurrentPluginMetadataSnapshot({ @@ -27,20 +19,6 @@ type ThinkingHookParams = { context: TContext; }; -/** Resolves whether a provider treats thinking as binary on/off. */ -export function resolveProviderBinaryThinking( - params: ThinkingHookParams, -) { - return resolveActiveProviderBinaryThinking(params); -} - -/** Resolves whether a provider supports xhigh thinking. */ -export function resolveProviderXHighThinking( - params: ThinkingHookParams, -) { - return resolveActiveProviderXHighThinking(params); -} - /** Resolves a provider thinking profile from active plugins or bundled policy surface. */ export function resolveProviderThinkingProfile( params: ThinkingHookParams, @@ -57,10 +35,3 @@ export function resolveProviderThinkingProfile( params.context, ); } - -/** Resolves the provider default thinking level from the active plugin registry. */ -export function resolveProviderDefaultThinkingLevel( - params: ThinkingHookParams, -) { - return resolveActiveProviderDefaultThinkingLevel(params); -} diff --git a/src/plugins/provider-validation.test.ts b/src/plugins/provider-validation.test.ts index 3dd4d7f0e772..dc9c39709e4b 100644 --- a/src/plugins/provider-validation.test.ts +++ b/src/plugins/provider-validation.test.ts @@ -87,7 +87,7 @@ function expectProviderNormalizationResult(params: { describe("normalizeRegisteredProvider", () => { const primaryAuthRun = async () => ({ profiles: [] }); - it.each([ + const cases = [ { name: "drops invalid and duplicate auth methods, and clears bad wizard method bindings", provider: makeProvider({ @@ -211,88 +211,11 @@ describe("normalizeRegisteredProvider", () => { ]); }, }, - { - name: "prefers catalog when a provider registers both catalog and discovery", - provider: makeProvider({ - catalog: { - run: async () => null, - }, - discovery: { - run: async () => ({ - provider: { - baseUrl: "http://127.0.0.1:8000/v1", - models: [], - }, - }), - }, - }), - expectedDiagnosticText: [ - 'provider "demo" registered both catalog and discovery; using catalog', - ], - assert: ( - provider: ReturnType, - _diagnostics: PluginDiagnostic[], - inputProvider: ProviderPlugin, - ) => { - if (!provider) { - throw new Error("expected provider"); - } - expect(provider).toEqual({ - id: "demo", - label: "Demo", - auth: [], - catalog: inputProvider.catalog, - }); - }, - }, - { - name: "warns for legacy discovery-only providers", - provider: makeProvider({ - id: "legacy-discovery-only", - discovery: { - run: async () => ({ - provider: { - baseUrl: "http://127.0.0.1:8000/v1", - models: [], - }, - }), - }, - }), - expectedDiagnosticText: [ - 'provider "legacy-discovery-only" uses deprecated discovery; use catalog', - ], - assert: ( - provider: ReturnType, - _diagnostics: PluginDiagnostic[], - inputProvider: ProviderPlugin, - ) => { - if (!provider) { - throw new Error("expected provider"); - } - expect(provider).toEqual({ - id: "legacy-discovery-only", - label: "Demo", - auth: [], - discovery: inputProvider.discovery, - }); - }, - }, - ] as const)( - "$name", - ({ - provider: inputProvider, - expectedProvider, - expectedDiagnostics, - expectedDiagnosticText, - assert, - }) => { - expectProviderNormalizationResult({ - provider: inputProvider, - ...(expectedProvider ? { expectedProvider } : {}), - ...(expectedDiagnostics ? { expectedDiagnostics } : {}), - ...(expectedDiagnosticText ? { expectedDiagnosticText } : {}), - ...(assert ? { assert } : {}), - }); - }, - ); + ] satisfies readonly (Parameters[0] & { + name: string; + })[]; + + it.each(cases)("$name", (testCase) => { + expectProviderNormalizationResult(testCase); + }); }); diff --git a/src/plugins/provider-validation.ts b/src/plugins/provider-validation.ts index 8803c4f4427f..d5f1864ae434 100644 --- a/src/plugins/provider-validation.ts +++ b/src/plugins/provider-validation.ts @@ -5,8 +5,6 @@ import type { PluginDiagnostic } from "./manifest-types.js"; import type { ProviderAuthMethod, ProviderPlugin } from "./types.js"; import { pushPluginValidationDiagnostic } from "./validation-diagnostics.js"; -const warnedDeprecatedDiscoveryProviders = new Set(); - type ProviderWizardSetup = NonNullable["setup"]>; type ProviderWizardModelPicker = NonNullable["modelPicker"]>; type ProviderWizardModelAllowlist = NonNullable; @@ -353,36 +351,12 @@ export function normalizeRegisteredProvider(params: { pushDiagnostic: params.pushDiagnostic, }); const catalog = params.provider.catalog; - const discovery = params.provider.discovery; - if (catalog && discovery) { - pushPluginValidationDiagnostic({ - level: "warn", - pluginId: params.pluginId, - source: params.source, - message: `provider "${id}" registered both catalog and discovery; using catalog`, - pushDiagnostic: params.pushDiagnostic, - }); - } - if (!catalog && discovery) { - const warningKey = `${params.pluginId}:${id}:discovery`; - if (!warnedDeprecatedDiscoveryProviders.has(warningKey)) { - warnedDeprecatedDiscoveryProviders.add(warningKey); - pushPluginValidationDiagnostic({ - level: "warn", - pluginId: params.pluginId, - source: params.source, - message: `provider "${id}" uses deprecated discovery; use catalog`, - pushDiagnostic: params.pushDiagnostic, - }); - } - } const { wizard: _ignoredWizard, docsPath: _ignoredDocsPath, aliases: _ignoredAliases, envVars: _ignoredEnvVars, catalog: _ignoredCatalog, - discovery: _ignoredDiscovery, ...restProvider } = params.provider; return { @@ -396,7 +370,6 @@ export function normalizeRegisteredProvider(params: { ...(envVars ? { envVars } : {}), auth, ...(catalog ? { catalog } : {}), - ...(!catalog && discovery ? { discovery } : {}), ...(wizard ? { wizard } : {}), }; } diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index c1060ce4d320..e350b611e6a6 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -42,7 +42,6 @@ let resolveActivatableProviderOwnerPluginIds: typeof import("./providers.js").re let resolveEnabledProviderPluginIds: typeof import("./providers.js").resolveEnabledProviderPluginIds; let resolveCatalogHookProviderPluginIds: typeof import("./providers.js").resolveCatalogHookProviderPluginIds; let resolveUsageHookProviderPluginContracts: typeof import("./providers.js").resolveUsageHookProviderPluginContracts; -let resolveExternalAuthProfileCompatFallbackPluginIds: typeof import("./providers.js").resolveExternalAuthProfileCompatFallbackPluginIds; let resolveExternalAuthProfileProviderPluginIds: typeof import("./providers.js").resolveExternalAuthProfileProviderPluginIds; let resolveDiscoveredProviderPluginIds: typeof import("./providers.js").resolveDiscoveredProviderPluginIds; let resolveDiscoverableProviderOwnerPluginIds: typeof import("./providers.js").resolveDiscoverableProviderOwnerPluginIds; @@ -526,7 +525,6 @@ describe("resolvePluginProviders", () => { resolveEnabledProviderPluginIds, resolveCatalogHookProviderPluginIds, resolveUsageHookProviderPluginContracts, - resolveExternalAuthProfileCompatFallbackPluginIds, resolveExternalAuthProfileProviderPluginIds, resolveDiscoveredProviderPluginIds, resolveDiscoverableProviderOwnerPluginIds, @@ -943,38 +941,6 @@ describe("resolvePluginProviders", () => { expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled(); }); - it("keeps undeclared external auth provider fallback scoped to active external providers", () => { - setManifestPlugins([ - createManifestProviderPlugin({ - id: "declared-auth-owner", - providerIds: ["declared"], - origin: "workspace", - contracts: { externalAuthProviders: ["declared"] }, - }), - createManifestProviderPlugin({ - id: "legacy-auth-owner", - providerIds: ["legacy"], - origin: "workspace", - }), - ]); - const declaredPluginIds = new Set(["declared-auth-owner"]); - - expect( - resolveExternalAuthProfileCompatFallbackPluginIds({ - config: { - plugins: { - entries: { - "declared-auth-owner": { enabled: true }, - "legacy-auth-owner": { enabled: true }, - }, - }, - }, - env: {} as NodeJS.ProcessEnv, - declaredPluginIds, - }), - ).toEqual(["legacy-auth-owner"]); - }); - it("filters bundled provider plugins by allowlist by default", () => { setManifestPlugins([ createManifestProviderPlugin({ diff --git a/src/plugins/providers.ts b/src/plugins/providers.ts index f9beb792c42f..c2c08c3fdaf3 100644 --- a/src/plugins/providers.ts +++ b/src/plugins/providers.ts @@ -269,34 +269,6 @@ function resolveRegistryManifestContractPluginIds(params: { .toSorted((left, right) => left.localeCompare(right)); } -export function resolveExternalAuthProfileCompatFallbackPluginIds(params: { - config?: PluginLoadOptions["config"]; - workspaceDir?: string; - env?: PluginLoadOptions["env"]; - declaredPluginIds?: ReadonlySet; - manifestRegistry?: PluginManifestRegistry; -}): string[] { - const declaredPluginIds = - params.declaredPluginIds ?? new Set(resolveExternalAuthProfileProviderPluginIds(params)); - const registry = loadProviderRegistrySnapshot(params); - const providerSurfacePluginIds = resolveProviderSurfacePluginIdSet({ ...params, registry }); - const normalizedConfig = normalizePluginsConfigWithRegistry(params.config?.plugins, registry, { - manifestRegistry: params.manifestRegistry, - }); - return listRegistryPluginIds( - registry, - (plugin) => - plugin.origin !== "bundled" && - providerSurfacePluginIds.has(plugin.pluginId) && - !declaredPluginIds.has(plugin.pluginId) && - isProviderPluginEligibleForRuntimeOwnerActivation({ - plugin, - normalizedConfig, - rootConfig: params.config, - }), - ); -} - export function resolveDiscoveredProviderPluginIds(params: { config?: PluginLoadOptions["config"]; workspaceDir?: string; diff --git a/src/plugins/registry-api.ts b/src/plugins/registry-api.ts index e09711add9f3..36c14134606c 100644 --- a/src/plugins/registry-api.ts +++ b/src/plugins/registry-api.ts @@ -101,12 +101,9 @@ export function createPluginApiFactory( registerSessionAction, registerTypedHook, registerMemoryCapability, - registerMemoryPromptSection, registerMemoryPromptSupplement, registerMemoryPromptPreparation, registerMemoryCorpusSupplement, - registerMemoryFlushPlan, - registerMemoryRuntime, registerMemoryEmbeddingProvider, registerCli, registerChannel, @@ -360,16 +357,12 @@ export function createPluginApiFactory( }, registerMemoryCapability: (capability) => registerMemoryCapability(record, capability), - registerMemoryPromptSection: (builder) => - registerMemoryPromptSection(record, builder), registerMemoryPromptSupplement: (builder) => registerMemoryPromptSupplement(record, builder), registerMemoryPromptPreparation: (prepare) => registerMemoryPromptPreparation(record, prepare), registerMemoryCorpusSupplement: (supplement) => registerMemoryCorpusSupplement(record, supplement), - registerMemoryFlushPlan: (resolver) => registerMemoryFlushPlan(record, resolver), - registerMemoryRuntime: (runtime) => registerMemoryRuntime(record, runtime), registerMemoryEmbeddingProvider: (adapter) => registerMemoryEmbeddingProvider(record, adapter), on: (hookName, handler, opts) => diff --git a/src/plugins/registry-registrars-memory.ts b/src/plugins/registry-registrars-memory.ts index cf4ab4ffee87..f29321144afc 100644 --- a/src/plugins/registry-registrars-memory.ts +++ b/src/plugins/registry-registrars-memory.ts @@ -5,11 +5,8 @@ import { import { registerMemoryCapability as registerGlobalMemoryCapability, registerMemoryCorpusSupplement as registerGlobalMemoryCorpusSupplement, - registerMemoryFlushPlanResolverForPlugin, registerMemoryPromptPreparation as registerGlobalMemoryPromptPreparation, registerMemoryPromptSupplement as registerGlobalMemoryPromptSupplement, - registerMemoryPromptSectionForPlugin, - registerMemoryRuntimeForPlugin, } from "./memory-state.js"; import type { PluginRegistryState } from "./registry-state.js"; import type { PluginRecord } from "./registry-types.js"; @@ -44,15 +41,6 @@ export function createMemoryRegistrars(state: PluginRegistryState) { } }; - const registerMemoryPromptSection = ( - record: PluginRecord, - builder: Parameters[0], - ) => { - if (requireMemorySlot(record, "prompt section")) { - registerMemoryPromptSectionForPlugin(record.id, builder); - } - }; - const registerMemoryPromptSupplement = ( record: PluginRecord, builder: Parameters[0], @@ -92,24 +80,6 @@ export function createMemoryRegistrars(state: PluginRegistryState) { registerGlobalMemoryCorpusSupplement(record.id, supplement); }; - const registerMemoryFlushPlan = ( - record: PluginRecord, - resolver: Parameters[0], - ) => { - if (requireMemorySlot(record, "flush plan")) { - registerMemoryFlushPlanResolverForPlugin(record.id, resolver); - } - }; - - const registerMemoryRuntime = ( - record: PluginRecord, - runtime: Parameters[0], - ) => { - if (requireMemorySlot(record, "runtime")) { - registerMemoryRuntimeForPlugin(record.id, runtime); - } - }; - const registerMemoryEmbeddingProvider = ( record: PluginRecord, adapter: Parameters[0], @@ -150,12 +120,9 @@ export function createMemoryRegistrars(state: PluginRegistryState) { return { registerMemoryCapability, - registerMemoryPromptSection, registerMemoryPromptSupplement, registerMemoryPromptPreparation, registerMemoryCorpusSupplement, - registerMemoryFlushPlan, - registerMemoryRuntime, registerMemoryEmbeddingProvider, }; } diff --git a/src/plugins/registry-registrars-tools-hooks.ts b/src/plugins/registry-registrars-tools-hooks.ts index 95b23e01859d..c45cb29ba17e 100644 --- a/src/plugins/registry-registrars-tools-hooks.ts +++ b/src/plugins/registry-registrars-tools-hooks.ts @@ -31,7 +31,6 @@ import { isDeprecatedPluginHookName, isPluginHookName, isPromptInjectionHookName, - stripPromptMutationFieldsFromLegacyHookResult, } from "./types.js"; import type { OpenClawPluginApi, @@ -81,20 +80,6 @@ function canRegisterInstalledTrustedHook(record: PluginRecord): boolean { return record.origin === "bundled" || (record.enabled && record.explicitlyEnabled === true); } -const constrainLegacyPromptInjectionHook = ( - handler: PluginHookHandlerMap["before_agent_start"], -): PluginHookHandlerMap["before_agent_start"] => { - return (event, ctx) => { - const result = handler(event, ctx); - if (result && typeof result === "object" && "then" in result) { - return Promise.resolve(result).then((resolved) => - stripPromptMutationFieldsFromLegacyHookResult(resolved), - ); - } - return stripPromptMutationFieldsFromLegacyHookResult(result); - }; -}; - export function createToolHookRegistrars(state: PluginRegistryState) { const { registry, @@ -437,26 +422,15 @@ export function createToolHookRegistrars(state: PluginRegistryState) { }); } } - let effectiveHandler = handler; + const effectiveHandler = handler; if (policy?.allowPromptInjection === false && isPromptInjectionHookName(effectiveHookName)) { - if (effectiveHookName !== "before_agent_start") { - pushDiagnostic({ - level: "warn", - pluginId: record.id, - source: record.source, - message: `typed hook "${effectiveHookName}" blocked by plugins.entries.${record.id}.hooks.allowPromptInjection=false`, - }); - return; - } pushDiagnostic({ level: "warn", pluginId: record.id, source: record.source, - message: `typed hook "${effectiveHookName}" prompt fields constrained by plugins.entries.${record.id}.hooks.allowPromptInjection=false`, + message: `typed hook "${effectiveHookName}" blocked by plugins.entries.${record.id}.hooks.allowPromptInjection=false`, }); - effectiveHandler = constrainLegacyPromptInjectionHook( - handler as PluginHookHandlerMap["before_agent_start"], - ) as PluginHookHandlerMap[K]; + return; } if (isConversationHookName(effectiveHookName)) { const explicitConversationAccess = policy?.allowConversationAccess; diff --git a/src/plugins/registry-runtime.ts b/src/plugins/registry-runtime.ts index 5372ace3c046..e2e561ac536f 100644 --- a/src/plugins/registry-runtime.ts +++ b/src/plugins/registry-runtime.ts @@ -792,8 +792,6 @@ export function createPluginRuntimeResolver(state: PluginRegistryState) { withPluginRuntimePluginIdScope(pluginId, () => subagent.waitForRun(params)), getSessionMessages: (params) => withPluginRuntimePluginIdScope(pluginId, () => subagent.getSessionMessages(params)), - getSession: (params) => - withPluginRuntimePluginIdScope(pluginId, () => subagent.getSession(params)), deleteSession: async (params) => await withPluginRuntimePluginIdScope(pluginId, async () => { assertStoredSessionEntryOwned({ action: "delete", sessionKey: params.sessionKey }); diff --git a/src/plugins/registry.dual-kind-memory-gate.test.ts b/src/plugins/registry.dual-kind-memory-gate.test.ts index 0deef0b0fd38..3ef7bba7eb82 100644 --- a/src/plugins/registry.dual-kind-memory-gate.test.ts +++ b/src/plugins/registry.dual-kind-memory-gate.test.ts @@ -48,7 +48,7 @@ describe("dual-kind memory registration gate", () => { name: "Dual Plugin", kind: ["memory", "context-engine"], register(api) { - api.registerMemoryRuntime(createStubMemoryRuntime()); + api.registerMemoryCapability({ runtime: createStubMemoryRuntime() }); }, }); @@ -59,7 +59,7 @@ describe("dual-kind memory registration gate", () => { level: "warn", source: "/virtual/dual-plugin/index.ts", message: - "dual-kind plugin not selected for memory slot; skipping memory runtime registration", + "dual-kind plugin not selected for memory slot; skipping memory capability registration", }, ]); }); @@ -77,7 +77,7 @@ describe("dual-kind memory registration gate", () => { memorySlotSelected: true, }), register(api) { - api.registerMemoryRuntime(createStubMemoryRuntime()); + api.registerMemoryCapability({ runtime: createStubMemoryRuntime() }); }, }); @@ -101,7 +101,7 @@ describe("dual-kind memory registration gate", () => { name: "Memory Only", kind: "memory", register(api) { - api.registerMemoryRuntime(createStubMemoryRuntime()); + api.registerMemoryCapability({ runtime: createStubMemoryRuntime() }); }, }); diff --git a/src/plugins/registry.runtime-config.test.ts b/src/plugins/registry.runtime-config.test.ts index d2b87fa86fb4..127c1233a686 100644 --- a/src/plugins/registry.runtime-config.test.ts +++ b/src/plugins/registry.runtime-config.test.ts @@ -103,8 +103,6 @@ describe("plugin registry runtime config scope", () => { replaceScope = getPluginRuntimeGatewayRequestScope(); return replaceResult; }; - const loadConfig: PluginRuntime["config"]["loadConfig"] = () => config; - const writeConfigFile: PluginRuntime["config"]["writeConfigFile"] = async () => {}; const configRuntime = { current: vi.fn(() => { currentScope = getPluginRuntimeGatewayRequestScope(); @@ -112,8 +110,6 @@ describe("plugin registry runtime config scope", () => { }), mutateConfigFile, replaceConfigFile, - loadConfig, - writeConfigFile, } satisfies PluginRuntime["config"]; const runtime = createPluginRuntime(); runtime.config = configRuntime; @@ -429,7 +425,6 @@ describe("plugin registry runtime config scope", () => { run: vi.fn(async () => ({ runId: "subagent-run" })), waitForRun: vi.fn(async () => ({ status: "ok" as const })), getSessionMessages: vi.fn(async () => ({ messages: [] })), - getSession: vi.fn(async () => ({ messages: [] })), deleteSession: vi.fn(async () => {}), } satisfies PluginRuntime["subagent"]; const runtime = createPluginRuntime({ subagent }); diff --git a/src/plugins/runtime-plugin-boundary.whatsapp.test.ts b/src/plugins/runtime-plugin-boundary.whatsapp.test.ts index 0ce6dabd0c58..6b65a800c66e 100644 --- a/src/plugins/runtime-plugin-boundary.whatsapp.test.ts +++ b/src/plugins/runtime-plugin-boundary.whatsapp.test.ts @@ -37,8 +37,8 @@ function createBundledWhatsAppRuntimeFixture() { openclaw: "openclaw.mjs", }, exports: { - "./plugin-sdk": { - default: "./dist/plugin-sdk/index.js", + "./plugin-sdk/core": { + default: "./dist/plugin-sdk/core.js", }, }, }, @@ -46,6 +46,7 @@ function createBundledWhatsAppRuntimeFixture() { 2, ), "openclaw.mjs": "export {};\n", + "dist/plugin-sdk/core.js": "export {};\n", [bundledDistPluginFile("whatsapp", "index.js")]: "export default {};\n", [bundledDistPluginFile("whatsapp", "light-runtime-api.js")]: 'export { getActiveWebListener } from "../../active-listener.js";\n', diff --git a/src/plugins/runtime/index.test.ts b/src/plugins/runtime/index.test.ts index 7dfcdc72e052..713627fd7a71 100644 --- a/src/plugins/runtime/index.test.ts +++ b/src/plugins/runtime/index.test.ts @@ -46,7 +46,6 @@ function createGatewaySubagentRuntime() { run: vi.fn(), waitForRun: vi.fn(), getSessionMessages: vi.fn(), - getSession: vi.fn(), deleteSession: vi.fn(), }; } @@ -302,7 +301,7 @@ describe("plugin runtime command execution", () => { it.each([ { - name: "exposes runtime.mediaUnderstanding helpers and keeps stt as an alias", + name: "exposes runtime.mediaUnderstanding helpers", assert: (runtime: ReturnType) => { expectFunctionKeys(runtime.mediaUnderstanding as Record, [ "runFile", @@ -311,9 +310,7 @@ describe("plugin runtime command execution", () => { "extractStructuredWithModel", "describeVideoFile", ]); - expect(runtime.mediaUnderstanding.transcribeAudioFile).toBe( - runtime.stt.transcribeAudioFile, - ); + expect(runtime.mediaUnderstanding.transcribeAudioFile).toBeTypeOf("function"); }, }, { @@ -335,7 +332,7 @@ describe("plugin runtime command execution", () => { }, }, { - name: "exposes canonical runtime.tasks task runtimes while keeping legacy TaskFlow aliases", + name: "exposes canonical runtime.tasks task runtimes", assert: (runtime: ReturnType) => { expectFunctionKeys(runtime.tasks.runs as Record, [ "bindSession", @@ -349,12 +346,6 @@ describe("plugin runtime command execution", () => { "bindSession", "fromToolContext", ]); - expectFunctionKeys(runtime.tasks.flow as Record, [ - "bindSession", - "fromToolContext", - ]); - expect(runtime.tasks.managedFlows).toBe(runtime.tasks.flow); - expect(runtime.taskFlow).toBe(runtime.tasks.managedFlows); }, }, { diff --git a/src/plugins/runtime/index.ts b/src/plugins/runtime/index.ts index 4ebb498756d8..6a53942d4a5e 100644 --- a/src/plugins/runtime/index.ts +++ b/src/plugins/runtime/index.ts @@ -179,7 +179,6 @@ function createUnavailableSubagentRuntime(): PluginRuntime["subagent"] { run: unavailable, waitForRun: unavailable, getSessionMessages: unavailable, - getSession: unavailable, deleteSession: unavailable, }; } @@ -308,7 +307,7 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}): const mediaUnderstanding = createRuntimeMediaUnderstandingFacade(); const taskFlow = createRuntimeTaskFlow(); const tasks = createRuntimeTasks({ - legacyTaskFlow: taskFlow, + managedTaskFlow: taskFlow, }); const agent = createRuntimeAgent(); const runtime = { @@ -360,12 +359,10 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}): }, }, tasks, - taskFlow, } satisfies Omit< PluginRuntime, | "tts" | "mediaUnderstanding" - | "stt" | "modelAuth" | "imageGeneration" | "videoGeneration" @@ -377,7 +374,6 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}): PluginRuntime, | "tts" | "mediaUnderstanding" - | "stt" | "modelAuth" | "imageGeneration" | "videoGeneration" @@ -388,9 +384,6 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}): defineCachedValue(runtime, "tts", createRuntimeTts); defineCachedValue(runtime, "mediaUnderstanding", () => mediaUnderstanding); - defineCachedValue(runtime, "stt", () => ({ - transcribeAudioFile: mediaUnderstanding.transcribeAudioFile, - })); defineCachedValue(runtime, "modelAuth", createRuntimeModelAuth); defineCachedValue(runtime, "imageGeneration", createRuntimeImageGeneration); defineCachedValue(runtime, "videoGeneration", createRuntimeVideoGeneration); diff --git a/src/plugins/runtime/runtime-channel.ts b/src/plugins/runtime/runtime-channel.ts index 8f69ce4ea366..09cffa9e461a 100644 --- a/src/plugins/runtime/runtime-channel.ts +++ b/src/plugins/runtime/runtime-channel.ts @@ -17,11 +17,7 @@ import { } from "../../auto-reply/command-detection.js"; import { shouldHandleTextCommands } from "../../auto-reply/commands-registry.js"; import { settleReplyDispatcher, withReplyDispatcher } from "../../auto-reply/dispatch.js"; -import { - formatAgentEnvelope, - formatInboundEnvelope, - resolveEnvelopeFormatOptions, -} from "../../auto-reply/envelope.js"; +import { formatAgentEnvelope, resolveEnvelopeFormatOptions } from "../../auto-reply/envelope.js"; import { createInboundDebouncer, resolveInboundDebounceMs, @@ -118,8 +114,6 @@ export function createRuntimeChannel(): PluginRuntime["channel"] { settleReplyDispatcher, finalizeInboundContext, formatAgentEnvelope, - /** @deprecated Prefer `BodyForAgent` + structured user-context blocks (do not build plaintext envelopes for prompts). */ - formatInboundEnvelope, resolveEnvelopeFormatOptions, }, routing: { diff --git a/src/plugins/runtime/runtime-config.test.ts b/src/plugins/runtime/runtime-config.test.ts index 3a87a9ee54b2..fb97f64617a2 100644 --- a/src/plugins/runtime/runtime-config.test.ts +++ b/src/plugins/runtime/runtime-config.test.ts @@ -1,11 +1,9 @@ // Runtime config tests cover plugin runtime config normalization and lookup. import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../../config/types.openclaw.js"; const getRuntimeConfigMock = vi.fn(); const mutateConfigFileMock = vi.fn(); const replaceConfigFileMock = vi.fn(); -const logWarnMock = vi.fn(); vi.mock("../../config/config.js", () => ({ getRuntimeConfig: () => getRuntimeConfigMock(), @@ -16,124 +14,37 @@ vi.mock("../../config/mutate.js", () => ({ replaceConfigFile: (...args: unknown[]) => replaceConfigFileMock(...args), })); -vi.mock("../../logger.js", () => ({ - logWarn: (...args: unknown[]) => logWarnMock(...args), -})); - -const { withPluginRuntimePluginScope } = await import("./gateway-request-scope.js"); -const { createRuntimeConfig, resetRuntimeConfigDeprecationWarningStateForTest } = - await import("./runtime-config.js"); -const deprecatedConfigCode = "runtime-config-load-write"; +const { createRuntimeConfig } = await import("./runtime-config.js"); describe("createRuntimeConfig", () => { beforeEach(() => { - resetRuntimeConfigDeprecationWarningStateForTest(); getRuntimeConfigMock.mockReset(); mutateConfigFileMock.mockReset(); replaceConfigFileMock.mockReset(); - logWarnMock.mockClear(); getRuntimeConfigMock.mockReturnValue({ plugins: {} }); - mutateConfigFileMock.mockResolvedValue({ previousHash: null, persistedHash: "persisted-hash" }); - replaceConfigFileMock.mockResolvedValue({ - previousHash: null, - persistedHash: "persisted-hash", - }); }); - it("reads config from the runtime snapshot for current and deprecated loadConfig", () => { + it("reads config from the runtime snapshot", () => { const runtimeConfig = { plugins: { entries: {} } }; getRuntimeConfigMock.mockReturnValue(runtimeConfig); - const configApi = createRuntimeConfig(); - expect(configApi.current()).toBe(runtimeConfig); - expect(configApi.loadConfig()).toBe(runtimeConfig); - expect(getRuntimeConfigMock).toHaveBeenCalledTimes(2); - expect(logWarnMock).toHaveBeenCalledWith( - `plugin runtime config.loadConfig() is deprecated (${deprecatedConfigCode}); use config.current().`, - ); + expect(createRuntimeConfig().current()).toBe(runtimeConfig); }); - it("attributes deprecated loadConfig warnings to the active plugin scope", () => { - const runtimeConfig = { plugins: { entries: {} } }; - getRuntimeConfigMock.mockReturnValue(runtimeConfig); + it("exposes canonical mutation helpers", async () => { + mutateConfigFileMock.mockResolvedValue({ result: "updated" }); + replaceConfigFileMock.mockResolvedValue({ persistedHash: "hash" }); const configApi = createRuntimeConfig(); + const mutateParams = { mutate: vi.fn() }; + const replaceParams = { nextConfig: { plugins: {} } }; - const loaded = withPluginRuntimePluginScope( - { pluginId: "legacy-plugin", pluginSource: "/plugins/legacy-plugin/index.js" }, - () => configApi.loadConfig(), - ); - - expect(loaded).toBe(runtimeConfig); - expect(logWarnMock).toHaveBeenCalledWith( - `plugin "legacy-plugin" runtime config.loadConfig() is deprecated (${deprecatedConfigCode}); use config.current(). Source: /plugins/legacy-plugin/index.js`, - ); - }); - - it("keeps deprecated loadConfig warning attribution per plugin", () => { - const configApi = createRuntimeConfig(); - - withPluginRuntimePluginScope({ pluginId: "first" }, () => configApi.loadConfig()); - withPluginRuntimePluginScope({ pluginId: "first" }, () => configApi.loadConfig()); - withPluginRuntimePluginScope({ pluginId: "second" }, () => configApi.loadConfig()); - - expect(logWarnMock).toHaveBeenCalledTimes(2); - expect(logWarnMock).toHaveBeenNthCalledWith( - 1, - `plugin "first" runtime config.loadConfig() is deprecated (${deprecatedConfigCode}); use config.current().`, - ); - expect(logWarnMock).toHaveBeenNthCalledWith( - 2, - `plugin "second" runtime config.loadConfig() is deprecated (${deprecatedConfigCode}); use config.current().`, - ); - }); - - it("routes deprecated writeConfigFile through replaceConfigFile with afterWrite", async () => { - const configApi = createRuntimeConfig(); - const nextConfig = { plugins: { entries: {} } } as OpenClawConfig; - - await configApi.writeConfigFile(nextConfig); - - expect(logWarnMock).toHaveBeenCalledWith( - `plugin runtime config.writeConfigFile() is deprecated (${deprecatedConfigCode}); use config.mutateConfigFile(...) or config.replaceConfigFile(...).`, - ); - expect(replaceConfigFileMock).toHaveBeenCalledWith({ - nextConfig, - afterWrite: { mode: "auto" }, - writeOptions: undefined, + await expect(configApi.mutateConfigFile(mutateParams as never)).resolves.toEqual({ + result: "updated", }); - }); - - it("attributes deprecated writeConfigFile warnings to the active plugin scope", async () => { - const configApi = createRuntimeConfig(); - const nextConfig = { plugins: { entries: {} } } as OpenClawConfig; - - await withPluginRuntimePluginScope( - { pluginId: "legacy-plugin", pluginSource: "/plugins/legacy-plugin/index.js" }, - async () => await configApi.writeConfigFile(nextConfig), - ); - - expect(logWarnMock).toHaveBeenCalledWith( - `plugin "legacy-plugin" runtime config.writeConfigFile() is deprecated (${deprecatedConfigCode}); use config.mutateConfigFile(...) or config.replaceConfigFile(...). Source: /plugins/legacy-plugin/index.js`, - ); - expect(replaceConfigFileMock).toHaveBeenCalledWith({ - nextConfig, - afterWrite: { mode: "auto" }, - writeOptions: undefined, - }); - }); - - it("preserves explicit afterWrite intent for deprecated writeConfigFile", async () => { - const configApi = createRuntimeConfig(); - const nextConfig = { plugins: { entries: {} } } as OpenClawConfig; - - await configApi.writeConfigFile(nextConfig, { - afterWrite: { mode: "none", reason: "test-controlled" }, - }); - - expect(replaceConfigFileMock).toHaveBeenCalledWith({ - nextConfig, - afterWrite: { mode: "none", reason: "test-controlled" }, - writeOptions: { afterWrite: { mode: "none", reason: "test-controlled" } }, + await expect(configApi.replaceConfigFile(replaceParams as never)).resolves.toEqual({ + persistedHash: "hash", }); + expect(mutateConfigFileMock).toHaveBeenCalledWith(mutateParams); + expect(replaceConfigFileMock).toHaveBeenCalledWith(replaceParams); }); }); diff --git a/src/plugins/runtime/runtime-config.ts b/src/plugins/runtime/runtime-config.ts index 5bc7dc37fcbe..6954fd1bbe0a 100644 --- a/src/plugins/runtime/runtime-config.ts +++ b/src/plugins/runtime/runtime-config.ts @@ -4,51 +4,8 @@ import { mutateConfigFile as mutateConfigFileInternal, replaceConfigFile as replaceConfigFileInternal, } from "../../config/mutate.js"; -import { logWarn } from "../../logger.js"; -import { getPluginRuntimeGatewayRequestScope } from "./gateway-request-scope.js"; import type { PluginRuntime } from "./types.js"; -const RUNTIME_CONFIG_LOAD_WRITE_COMPAT_CODE = "runtime-config-load-write"; - -const warnedDeprecatedConfigApis = new Set(); - -function formatDeprecatedConfigApiSubject(name: "loadConfig" | "writeConfigFile"): string { - const scope = getPluginRuntimeGatewayRequestScope(); - if (!scope?.pluginId) { - return `plugin runtime config.${name}()`; - } - return `plugin "${scope.pluginId}" runtime config.${name}()`; -} - -function formatDeprecatedConfigApiSource(): string { - const scope = getPluginRuntimeGatewayRequestScope(); - return scope?.pluginSource ? ` Source: ${scope.pluginSource}` : ""; -} - -function formatDeprecatedConfigApiWarningKey(name: "loadConfig" | "writeConfigFile"): string { - const scope = getPluginRuntimeGatewayRequestScope(); - return `${name}:${scope?.pluginId ?? "anonymous"}`; -} - -function warnDeprecatedConfigApiOnce( - name: "loadConfig" | "writeConfigFile", - replacement: string, -): void { - const warningKey = formatDeprecatedConfigApiWarningKey(name); - if (warnedDeprecatedConfigApis.has(warningKey)) { - return; - } - warnedDeprecatedConfigApis.add(warningKey); - logWarn( - `${formatDeprecatedConfigApiSubject(name)} is deprecated (${RUNTIME_CONFIG_LOAD_WRITE_COMPAT_CODE}); use ${replacement}.${formatDeprecatedConfigApiSource()}`, - ); -} - -/** @internal Test-only reset for the runtime config compatibility warning cache. */ -export function resetRuntimeConfigDeprecationWarningStateForTest(): void { - warnedDeprecatedConfigApis.clear(); -} - export function createRuntimeConfig(): PluginRuntime["config"] { return { current: getRuntimeConfig, @@ -62,20 +19,5 @@ export function createRuntimeConfig(): PluginRuntime["config"] { ...params, writeOptions: params.writeOptions, }), - loadConfig: () => { - warnDeprecatedConfigApiOnce("loadConfig", "config.current()"); - return getRuntimeConfig(); - }, - writeConfigFile: async (cfg, options) => { - warnDeprecatedConfigApiOnce( - "writeConfigFile", - "config.mutateConfigFile(...) or config.replaceConfigFile(...)", - ); - await replaceConfigFileInternal({ - nextConfig: cfg, - afterWrite: options?.afterWrite ?? { mode: "auto" }, - writeOptions: options, - }); - }, }; } diff --git a/src/plugins/runtime/runtime-tasks.test.ts b/src/plugins/runtime/runtime-tasks.test.ts index d449e8fbf914..6511ae828ea3 100644 --- a/src/plugins/runtime/runtime-tasks.test.ts +++ b/src/plugins/runtime/runtime-tasks.test.ts @@ -47,7 +47,7 @@ describe("runtime tasks", () => { it("exposes canonical task and TaskFlow DTOs without leaking raw registry fields", () => { const runtimeTasks = createRuntimeTasks({ - legacyTaskFlow: createRuntimeTaskFlow(), + managedTaskFlow: createRuntimeTaskFlow(), }); const legacyTaskFlow = runtimeTasks.managedFlows.bindSession({ sessionKey: "agent:main:main", @@ -147,7 +147,7 @@ describe("runtime tasks", () => { it("maps task cancellation results onto canonical task DTOs", async () => { const runtimeTasks = createRuntimeTasks({ - legacyTaskFlow: createRuntimeTaskFlow(), + managedTaskFlow: createRuntimeTaskFlow(), }); const legacyTaskFlow = runtimeTasks.managedFlows.bindSession({ sessionKey: "agent:main:main", @@ -196,7 +196,7 @@ describe("runtime tasks", () => { it("routes runtime task cancellation through the detached task runtime seam", async () => { const runtimeTasks = createRuntimeTasks({ - legacyTaskFlow: createRuntimeTaskFlow(), + managedTaskFlow: createRuntimeTaskFlow(), }); const legacyTaskFlow = runtimeTasks.managedFlows.bindSession({ sessionKey: "agent:main:main", @@ -248,7 +248,7 @@ describe("runtime tasks", () => { it("does not allow cross-owner task cancellation or leak task details", async () => { const runtimeTasks = createRuntimeTasks({ - legacyTaskFlow: createRuntimeTaskFlow(), + managedTaskFlow: createRuntimeTaskFlow(), }); const legacyTaskFlow = runtimeTasks.managedFlows.bindSession({ sessionKey: "agent:main:main", diff --git a/src/plugins/runtime/runtime-tasks.ts b/src/plugins/runtime/runtime-tasks.ts index ace94235b74a..5fc539947054 100644 --- a/src/plugins/runtime/runtime-tasks.ts +++ b/src/plugins/runtime/runtime-tasks.ts @@ -206,12 +206,11 @@ function createRuntimeTaskFlows(): PluginRuntimeTaskFlows { } export function createRuntimeTasks(params: { - legacyTaskFlow: PluginRuntimeTaskFlow; + managedTaskFlow: PluginRuntimeTaskFlow; }): PluginRuntimeTasks { return { runs: createRuntimeTaskRuns(), flows: createRuntimeTaskFlows(), - managedFlows: params.legacyTaskFlow, - flow: params.legacyTaskFlow, + managedFlows: params.managedTaskFlow, }; } diff --git a/src/plugins/runtime/runtime-tasks.types.ts b/src/plugins/runtime/runtime-tasks.types.ts index 20ab92e02de0..70ce9cd2bcbc 100644 --- a/src/plugins/runtime/runtime-tasks.types.ts +++ b/src/plugins/runtime/runtime-tasks.types.ts @@ -57,6 +57,4 @@ export type PluginRuntimeTasks = { runs: PluginRuntimeTaskRuns; flows: PluginRuntimeTaskFlows; managedFlows: PluginRuntimeTaskFlow; - /** @deprecated Use runtime.tasks.flows for DTO-based TaskFlow access. */ - flow: PluginRuntimeTaskFlow; }; diff --git a/src/plugins/runtime/types-channel.ts b/src/plugins/runtime/types-channel.ts index 88a90fb09875..5d339f04495b 100644 --- a/src/plugins/runtime/types-channel.ts +++ b/src/plugins/runtime/types-channel.ts @@ -119,8 +119,6 @@ export type PluginRuntimeChannel = { */ finalizeInboundContext: typeof import("../../auto-reply/reply/inbound-context.js").finalizeInboundContext; formatAgentEnvelope: typeof import("../../auto-reply/envelope.js").formatAgentEnvelope; - /** @deprecated Prefer `BodyForAgent` + structured user-context blocks (do not build plaintext envelopes for prompts). */ - formatInboundEnvelope: typeof import("../../auto-reply/envelope.js").formatInboundEnvelope; resolveEnvelopeFormatOptions: typeof import("../../auto-reply/envelope.js").resolveEnvelopeFormatOptions; }; routing: { diff --git a/src/plugins/runtime/types-core.ts b/src/plugins/runtime/types-core.ts index e312f1818a94..343870d6339b 100644 --- a/src/plugins/runtime/types-core.ts +++ b/src/plugins/runtime/types-core.ts @@ -264,23 +264,6 @@ export type PluginRuntimeCore = { replaceConfigFile: ( params: RuntimeReplaceConfigFileParams, ) => Promise; - /** - * @deprecated Use current(), or pass the already loaded config through the - * call path. Runtime code must not reload config on demand. Bundled - * plugins and repo code are blocked from using this by the - * deprecated-internal-config-api architecture guard. - */ - loadConfig: () => import("../../config/types.openclaw.js").OpenClawConfig; - /** - * @deprecated Use mutateConfigFile() or replaceConfigFile() with an - * explicit afterWrite intent so restart behavior stays under host control. - * Bundled plugins and repo code are blocked from using this by the - * deprecated-internal-config-api architecture guard. - */ - writeConfigFile: ( - cfg: import("../../config/types.openclaw.js").OpenClawConfig, - options?: RuntimeWriteConfigOptions & { afterWrite?: RuntimeConfigAfterWrite }, - ) => Promise; }; agent: { defaults: { @@ -408,9 +391,6 @@ export type PluginRuntimeCore = { params: import("../../web-search/runtime-types.js").RunWebSearchParams, ) => Promise; }; - stt: { - transcribeAudioFile: MediaUnderstandingRuntime["transcribeAudioFile"]; - }; events: { onAgentEvent: typeof import("../../infra/agent-events.js").onAgentEvent; onSessionTranscriptUpdate: typeof import("../../sessions/transcript-events.js").onSessionTranscriptUpdate; @@ -460,11 +440,7 @@ export type PluginRuntimeCore = { runs: PluginRuntimeTaskRuns; flows: PluginRuntimeTaskFlows; managedFlows: import("./runtime-taskflow.types.js").PluginRuntimeTaskFlow; - /** @deprecated Use runtime.tasks.flows for DTO-based TaskFlow access. */ - flow: import("./runtime-taskflow.types.js").PluginRuntimeTaskFlow; }; - /** @deprecated Use runtime.tasks.flows for DTO-based TaskFlow access. */ - taskFlow: import("./runtime-taskflow.types.js").PluginRuntimeTaskFlow; llm: { complete: (params: LlmCompleteParams) => Promise; acquireLocalService: ( diff --git a/src/plugins/runtime/types.ts b/src/plugins/runtime/types.ts index 534555c16581..93eb1547f221 100644 --- a/src/plugins/runtime/types.ts +++ b/src/plugins/runtime/types.ts @@ -118,10 +118,6 @@ export type PluginRuntime = PluginRuntimeCore & { getSessionMessages: ( params: SubagentGetSessionMessagesParams, ) => Promise; - /** @deprecated Use getSessionMessages. */ - getSession: ( - params: SubagentGetSessionMessagesParams, - ) => Promise; deleteSession: (params: SubagentDeleteSessionParams) => Promise; }; nodes: { diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index 38adc3b62087..382ccf59e0ba 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -55,7 +55,7 @@ function createTrustedOpenClawPackageFixture(version: string) { name: "openclaw", version, bin: { openclaw: "openclaw.mjs" }, - exports: { "./plugin-sdk": { default: "./dist/plugin-sdk/index.js" } }, + exports: { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" } }, }, null, 2, @@ -85,8 +85,8 @@ function createPluginSdkAliasFixture(params?: { trustedRootIndicatorMode?: "bin+marker" | "cli-entry-only" | "none"; }) { const root = makeTempDir(); - const srcFile = path.join(root, "src", "plugin-sdk", params?.srcFile ?? "index.ts"); - const distFile = path.join(root, "dist", "plugin-sdk", params?.distFile ?? "index.js"); + const srcFile = path.join(root, "src", "plugin-sdk", params?.srcFile ?? "core.ts"); + const distFile = path.join(root, "dist", "plugin-sdk", params?.distFile ?? "core.js"); mkdirSafeDir(path.dirname(srcFile)); mkdirSafeDir(path.dirname(distFile)); const trustedRootIndicatorMode = @@ -107,7 +107,7 @@ function createPluginSdkAliasFixture(params?: { ? { "./cli-entry": { default: "./dist/cli-entry.js" } } : {}; packageJson.exports = { - "./plugin-sdk": { default: "./dist/plugin-sdk/index.js" }, + "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, ...trustedExports, ...params?.packageExports, }; @@ -140,27 +140,6 @@ function writePluginSdkSubpathArtifacts(root: string, subpaths: readonly string[ } } -function createExtensionApiAliasFixture(params?: { - srcBody?: string; - distBody?: string; - srcExtension?: ".ts" | ".mts" | ".js" | ".mjs" | ".cts" | ".cjs"; -}) { - const root = makeTempDir(); - const srcFile = path.join(root, "src", `extensionAPI${params?.srcExtension ?? ".ts"}`); - const distFile = path.join(root, "dist", "extensionAPI.js"); - mkdirSafeDir(path.dirname(srcFile)); - mkdirSafeDir(path.dirname(distFile)); - fs.writeFileSync( - path.join(root, "package.json"), - JSON.stringify({ name: "openclaw", type: "module" }, null, 2), - "utf-8", - ); - fs.writeFileSync(path.join(root, "openclaw.mjs"), "export {};\n", "utf-8"); - fs.writeFileSync(srcFile, params?.srcBody ?? "export {};\n", "utf-8"); - fs.writeFileSync(distFile, params?.distBody ?? "export {};\n", "utf-8"); - return { root, srcFile, distFile }; -} - function writeWorkspacePackageEntry(params: { root: string; packageDir: string; @@ -205,19 +184,17 @@ function createPluginSdkAliasTargetFixture(params?: { }) { const sourceChannelRuntimeExtension = params?.sourceChannelRuntimeExtension ?? ".ts"; const fixture = createPluginSdkAliasFixture({ - srcFile: `channel-runtime${sourceChannelRuntimeExtension}`, - distFile: "channel-runtime.js", + srcFile: `channel-runtime-context${sourceChannelRuntimeExtension}`, + distFile: "channel-runtime-context.js", packageExports: { - "./plugin-sdk/channel-runtime": { default: "./dist/plugin-sdk/channel-runtime.js" }, + "./plugin-sdk/channel-runtime-context": { + default: "./dist/plugin-sdk/channel-runtime-context.js", + }, "./plugin-sdk/plugin-entry": { default: "./dist/plugin-sdk/plugin-entry.js" }, }, }); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); - const distRootAlias = path.join(fixture.root, "dist", "plugin-sdk", "root-alias.cjs"); const sourcePluginEntryPath = path.join(fixture.root, "src", "plugin-sdk", "plugin-entry.ts"); const distPluginEntryPath = path.join(fixture.root, "dist", "plugin-sdk", "plugin-entry.js"); - fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8"); - fs.writeFileSync(distRootAlias, "module.exports = {};\n", "utf-8"); fs.writeFileSync( sourcePluginEntryPath, "export const definePluginEntry = (entry) => entry;\n", @@ -230,15 +207,18 @@ function createPluginSdkAliasTargetFixture(params?: { ); return { fixture, - sourceRootAlias, - distRootAlias, sourceChannelRuntimePath: path.join( fixture.root, "src", "plugin-sdk", - `channel-runtime${sourceChannelRuntimeExtension}`, + `channel-runtime-context${sourceChannelRuntimeExtension}`, + ), + distChannelRuntimePath: path.join( + fixture.root, + "dist", + "plugin-sdk", + "channel-runtime-context.js", ), - distChannelRuntimePath: path.join(fixture.root, "dist", "plugin-sdk", "channel-runtime.js"), sourcePluginEntryPath, distPluginEntryPath, }; @@ -313,7 +293,7 @@ function writeInstalledPluginEntry(params: { } function createUserInstalledPluginSdkAliasFixture() { - const { fixture, sourcePluginEntryPath, sourceRootAlias, sourceChannelRuntimePath } = + const { fixture, sourcePluginEntryPath, sourceChannelRuntimePath } = createPluginSdkAliasTargetFixture(); const externalPluginRoot = path.join(makeTempDir(), ".openclaw", "extensions", "demo"); const externalPluginEntry = path.join(externalPluginRoot, "index.ts"); @@ -332,7 +312,6 @@ function createUserInstalledPluginSdkAliasFixture() { externalPluginRoot, fixture, sourcePluginEntryPath, - sourceRootAlias, sourceChannelRuntimePath, }; } @@ -367,22 +346,15 @@ function expectResolvedFixturePath(params: { function expectPluginSdkAliasTargets( aliases: Record, params: { - rootAliasPath: string; channelRuntimePath?: string; pluginEntryPath?: string; }, ) { - expect(fs.realpathSync(aliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(params.rootAliasPath), - ); - expect(fs.realpathSync(aliases["@openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(params.rootAliasPath), - ); if (params.channelRuntimePath) { - expect(fs.realpathSync(aliases["openclaw/plugin-sdk/channel-runtime"] ?? "")).toBe( + expect(fs.realpathSync(aliases["openclaw/plugin-sdk/channel-runtime-context"] ?? "")).toBe( fs.realpathSync(params.channelRuntimePath), ); - expect(fs.realpathSync(aliases["@openclaw/plugin-sdk/channel-runtime"] ?? "")).toBe( + expect(fs.realpathSync(aliases["@openclaw/plugin-sdk/channel-runtime-context"] ?? "")).toBe( fs.realpathSync(params.channelRuntimePath), ); } @@ -396,28 +368,6 @@ function expectPluginSdkAliasTargets( } } -function expectExtensionApiAliasResolution(params: { - fixture: { root: string; srcFile: string; distFile: string }; - modulePath: (root: string) => string; - argv1?: (root: string) => string; - env?: NodeJS.ProcessEnv; - expected: "src" | "dist"; -}) { - const resolved = withEnv( - params.env ?? {}, - () => - buildPluginLoaderAliasMap( - params.modulePath(params.fixture.root), - params.argv1?.(params.fixture.root) ?? "", - )["openclaw/extension-api"] ?? null, - ); - expectResolvedFixturePath({ - resolved, - fixture: params.fixture, - expected: params.expected, - }); -} - function listPluginSdkExportedSubpaths(params: { modulePath: string; argv1?: string; @@ -459,7 +409,7 @@ function expectCwdFallbackPluginSdkAliasResolution(params: { { NODE_ENV: undefined }, () => buildPluginLoaderAliasMap("/tmp/tsx-cache/openclaw-loader.js", "")[ - "openclaw/plugin-sdk/channel-runtime" + "openclaw/plugin-sdk/channel-runtime-context" ] ?? null, ), ); @@ -479,197 +429,22 @@ afterAll(() => { }); describe("plugin sdk alias helpers", () => { - it.each([ - { - name: "prefers dist plugin-sdk alias when loader runs from dist", - buildFixture: () => createPluginSdkAliasFixture(), - modulePath: (root: string) => path.join(root, "dist", "plugins", "loader.js"), - srcFile: "index.ts", - distFile: "index.js", - expected: "dist" as const, - }, - { - name: "prefers src plugin-sdk alias when loader runs from src in non-production", - buildFixture: () => createPluginSdkAliasFixture(), - modulePath: (root: string) => path.join(root, "src", "plugins", "loader.ts"), - srcFile: "index.ts", - distFile: "index.js", - env: { NODE_ENV: undefined }, - expected: "src" as const, - }, - { - name: "falls back to src plugin-sdk alias when dist is missing in production", - buildFixture: () => { - const fixture = createPluginSdkAliasFixture(); - fs.rmSync(fixture.distFile); - return fixture; - }, - modulePath: (root: string) => path.join(root, "src", "plugins", "loader.ts"), - srcFile: "index.ts", - distFile: "index.js", - env: { NODE_ENV: "production", VITEST: undefined }, - expected: "src" as const, - }, - { - name: "prefers dist root-alias shim when loader runs from dist", - buildFixture: () => - createPluginSdkAliasFixture({ - srcFile: "root-alias.cjs", - distFile: "root-alias.cjs", - srcBody: "module.exports = {};\n", - distBody: "module.exports = {};\n", - }), - modulePath: (root: string) => path.join(root, "dist", "plugins", "loader.js"), - srcFile: "root-alias.cjs", - distFile: "root-alias.cjs", - expected: "dist" as const, - }, - { - name: "prefers src root-alias shim when loader runs from src in non-production", - buildFixture: () => - createPluginSdkAliasFixture({ - srcFile: "root-alias.cjs", - distFile: "root-alias.cjs", - srcBody: "module.exports = {};\n", - distBody: "module.exports = {};\n", - }), - modulePath: (root: string) => path.join(root, "src", "plugins", "loader.ts"), - srcFile: "root-alias.cjs", - distFile: "root-alias.cjs", - env: { NODE_ENV: undefined }, - expected: "src" as const, - }, - { - name: "resolves plugin-sdk alias from package root when loader runs from transpiler cache path", - buildFixture: () => - createPluginSdkAliasFixture({ - packageExports: { - "./plugin-sdk/index": { default: "./dist/plugin-sdk/index.js" }, - }, - }), - modulePath: () => "/tmp/tsx-cache/openclaw-loader.js", - argv1: (root: string) => path.join(root, "openclaw.mjs"), - srcFile: "index.ts", - distFile: "index.js", - env: { NODE_ENV: undefined }, - expected: "src" as const, - }, - ])("$name", ({ buildFixture, modulePath, argv1, srcFile, distFile, env, expected }) => { - const fixture = buildFixture(); - const sourceCandidate = path.join(fixture.root, "src", "plugin-sdk", srcFile); - const distCandidate = path.join(fixture.root, "dist", "plugin-sdk", distFile); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); - const distRootAlias = path.join(fixture.root, "dist", "plugin-sdk", "root-alias.cjs"); - if (fs.existsSync(sourceCandidate) && sourceCandidate !== sourceRootAlias) { - fs.copyFileSync(sourceCandidate, sourceRootAlias); - } - if (fs.existsSync(distCandidate) && distCandidate !== distRootAlias) { - fs.copyFileSync(distCandidate, distRootAlias); - } - const resolved = withEnv( - env ?? {}, - () => - buildPluginLoaderAliasMap(modulePath(fixture.root), argv1?.(fixture.root) ?? "")[ - "openclaw/plugin-sdk" - ] ?? null, - ); - expect(resolved).toBe(expected === "dist" ? distRootAlias : sourceRootAlias); - }); - - it.each([ - { - name: "prefers dist extension-api alias when loader runs from dist", - modulePath: (root: string) => path.join(root, "dist", "plugins", "loader.js"), - expected: "dist" as const, - }, - { - name: "prefers src extension-api alias when loader runs from src in non-production", - modulePath: (root: string) => path.join(root, "src", "plugins", "loader.ts"), - env: { NODE_ENV: undefined }, - expected: "src" as const, - }, - ])("$name", ({ modulePath, env, expected }) => { - const fixture = createExtensionApiAliasFixture(); - expectExtensionApiAliasResolution({ - fixture, - modulePath, - env, - expected, - }); - }); - - it("resolves source extension-api aliases through the wider source extension family", () => { - const fixture = createExtensionApiAliasFixture({ srcExtension: ".mts" }); - expectExtensionApiAliasResolution({ - fixture, - modulePath: (root: string) => path.join(root, "src", "plugins", "loader.ts"), - env: { NODE_ENV: undefined }, - expected: "src", - }); - }); - - it("resolves extension-api aliases from an explicit dev source root", () => { - const stableFixture = createExtensionApiAliasFixture({ - distBody: "export const stableExtensionApi = true;\n", - }); - const devFixture = createExtensionApiAliasFixture({ - distBody: "export const devExtensionApi = true;\n", - }); - mkdirSafeDir(path.join(devFixture.root, "extensions")); - const entry = path.join(stableFixture.root, "dist", "plugins", "loader.js"); - mkdirSafeDir(path.dirname(entry)); - fs.writeFileSync(entry, "export {};\n", "utf-8"); - - const aliases = buildPluginLoaderAliasMap(entry, undefined, undefined, "dist", devFixture.root); - - expect(fs.realpathSync(aliases["openclaw/extension-api"] ?? "")).toBe( - fs.realpathSync(devFixture.distFile), - ); - }); - - it.each([ - { - name: "prefers dist candidates first for production src runtime", - env: { NODE_ENV: "production", VITEST: undefined }, - expectedFirst: "dist" as const, - }, - { - name: "prefers src candidates first for non-production src runtime", - env: { NODE_ENV: undefined }, - expectedFirst: "src" as const, - }, - ])("$name", ({ env, expectedFirst }) => { - const fixture = createPluginSdkAliasFixture(); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); - const distRootAlias = path.join(fixture.root, "dist", "plugin-sdk", "root-alias.cjs"); - fs.copyFileSync(fixture.srcFile, sourceRootAlias); - fs.copyFileSync(fixture.distFile, distRootAlias); - const resolved = withEnv( - env ?? {}, - () => - buildPluginLoaderAliasMap(path.join(fixture.root, "src", "plugins", "loader.ts"), "")[ - "openclaw/plugin-sdk" - ], - ); - expect(resolved).toBe(expectedFirst === "dist" ? distRootAlias : sourceRootAlias); - }); - it("derives plugin-sdk subpaths from package exports", () => { const fixture = createPluginSdkAliasFixture({ packageExports: { - "./plugin-sdk/compat": { default: "./dist/plugin-sdk/compat.js" }, "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, + "./plugin-sdk/runtime": { default: "./dist/plugin-sdk/runtime.js" }, "./plugin-sdk/nested/value": { default: "./dist/plugin-sdk/nested/value.js" }, "./plugin-sdk/..\\..\\evil": { default: "./dist/plugin-sdk/evil.js" }, "./plugin-sdk/C:temp": { default: "./dist/plugin-sdk/drive.js" }, "./plugin-sdk/.hidden": { default: "./dist/plugin-sdk/hidden.js" }, }, }); - writePluginSdkSubpathArtifacts(fixture.root, ["compat", "core"]); + writePluginSdkSubpathArtifacts(fixture.root, ["core", "runtime"]); const subpaths = listPluginSdkExportedSubpaths({ modulePath: path.join(fixture.root, "src", "plugins", "loader.ts"), }); - expect(subpaths).toEqual(["compat", "core"]); + expect(subpaths).toEqual(["core", "runtime"]); }); it("adds private qa plugin-sdk subpaths for trusted local checkouts when enabled", () => { @@ -708,15 +483,18 @@ describe("plugin sdk alias helpers", () => { expect(subpaths).toEqual(["core", "qa-channel", "qa-channel-protocol", "qa-lab", "qa-runtime"]); }); - it("resolves the public QA runner alias without enabling private QA mode", () => { + it("resolves a private-local bundled helper without enabling private QA mode", () => { const fixture = createPluginSdkAliasFixture({ packageExports: { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, - "./plugin-sdk/qa-runner-runtime": { default: "./dist/plugin-sdk/qa-runner-runtime.js" }, }, }); + fs.writeFileSync( + path.join(fixture.root, "scripts", "lib", "plugin-sdk-private-local-only-subpaths.json"), + JSON.stringify(["qa-runner-runtime", "qa-runtime"], null, 2), + "utf-8", + ); writePluginSdkSubpathArtifacts(fixture.root, ["core"]); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); const sourceQaRunnerPath = path.join(fixture.root, "src", "plugin-sdk", "qa-runner-runtime.ts"); const sourcePrivateQaRuntimePath = path.join( fixture.root, @@ -724,7 +502,6 @@ describe("plugin sdk alias helpers", () => { "plugin-sdk", "qa-runtime.ts", ); - fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8"); fs.writeFileSync(sourceQaRunnerPath, "export const qaRunnerRuntime = true;\n", "utf-8"); fs.writeFileSync(sourcePrivateQaRuntimePath, "export const qaRuntime = true;\n", "utf-8"); const sourcePluginEntry = writePluginEntry( @@ -741,9 +518,6 @@ describe("plugin sdk alias helpers", () => { ); expect(subpaths).toEqual(["core", "qa-runner-runtime"]); - expect(fs.realpathSync(aliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(sourceRootAlias), - ); expect(fs.realpathSync(aliases["openclaw/plugin-sdk/qa-runner-runtime"] ?? "")).toBe( fs.realpathSync(sourceQaRunnerPath), ); @@ -906,7 +680,9 @@ describe("plugin sdk alias helpers", () => { trustedRootIndicators: false, packageExports: { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, - "./plugin-sdk/channel-runtime": { default: "./dist/plugin-sdk/channel-runtime.js" }, + "./plugin-sdk/channel-runtime-context": { + default: "./dist/plugin-sdk/channel-runtime-context.js", + }, }, }), expected: [], @@ -918,14 +694,16 @@ describe("plugin sdk alias helpers", () => { trustedRootIndicatorMode: "cli-entry-only", packageExports: { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, - "./plugin-sdk/channel-runtime": { default: "./dist/plugin-sdk/channel-runtime.js" }, + "./plugin-sdk/channel-runtime-context": { + default: "./dist/plugin-sdk/channel-runtime-context.js", + }, }, }), - expected: ["channel-runtime", "core"], + expected: ["channel-runtime-context", "core"], }, ] as const)("$name", ({ fixture: buildFixture, expected }) => { const fixture = buildFixture(); - writePluginSdkSubpathArtifacts(fixture.root, ["channel-runtime", "core"]); + writePluginSdkSubpathArtifacts(fixture.root, ["channel-runtime-context", "core"]); expectExportedSubpaths({ fixture, cwd: fixture.root, @@ -935,13 +713,8 @@ describe("plugin sdk alias helpers", () => { }); it("builds plugin-sdk aliases from the module being loaded, not the loader location", () => { - const { - fixture, - sourceRootAlias, - distRootAlias, - sourceChannelRuntimePath, - distChannelRuntimePath, - } = createPluginSdkAliasTargetFixture(); + const { fixture, sourceChannelRuntimePath, distChannelRuntimePath } = + createPluginSdkAliasTargetFixture(); const sourcePluginEntry = writePluginEntry( fixture.root, bundledPluginFile("demo", "src/index.ts"), @@ -951,7 +724,6 @@ describe("plugin sdk alias helpers", () => { buildPluginLoaderAliasMap(sourcePluginEntry), ); expectPluginSdkAliasTargets(sourceAliases, { - rootAliasPath: sourceRootAlias, channelRuntimePath: sourceChannelRuntimePath, }); @@ -964,7 +736,6 @@ describe("plugin sdk alias helpers", () => { buildPluginLoaderAliasMap(distPluginEntry), ); expectPluginSdkAliasTargets(distAliases, { - rootAliasPath: distRootAlias, channelRuntimePath: distChannelRuntimePath, }); }); @@ -975,7 +746,6 @@ describe("plugin sdk alias helpers", () => { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, }, }); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); const sourceQaChannelPath = path.join(fixture.root, "src", "plugin-sdk", "qa-channel.ts"); const sourceQaChannelProtocolPath = path.join( fixture.root, @@ -985,7 +755,6 @@ describe("plugin sdk alias helpers", () => { ); const sourceQaRuntimePath = path.join(fixture.root, "src", "plugin-sdk", "qa-runtime.ts"); const distQaLabPath = path.join(fixture.root, "dist", "plugin-sdk", "qa-lab.js"); - fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8"); fs.writeFileSync(sourceQaChannelPath, "export const qaChannel = true;\n", "utf-8"); fs.writeFileSync( sourceQaChannelProtocolPath, @@ -1003,9 +772,6 @@ describe("plugin sdk alias helpers", () => { buildPluginLoaderAliasMap(sourcePluginEntry), ); - expect(fs.realpathSync(aliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(sourceRootAlias), - ); expect(fs.realpathSync(aliases["openclaw/plugin-sdk/qa-runtime"] ?? "")).toBe( fs.realpathSync(sourceQaRuntimePath), ); @@ -1026,7 +792,6 @@ describe("plugin sdk alias helpers", () => { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, }, }); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); const sourceCodexMcpProjectionPath = path.join( fixture.root, "src", @@ -1039,7 +804,6 @@ describe("plugin sdk alias helpers", () => { "plugin-sdk", "codex-native-task-runtime.ts", ); - const distRootAlias = path.join(fixture.root, "dist", "plugin-sdk", "root-alias.cjs"); const distCodexMcpProjectionPath = path.join( fixture.root, "dist", @@ -1053,8 +817,6 @@ describe("plugin sdk alias helpers", () => { "codex-native-task-runtime.js", ); const sourceQaRuntimePath = path.join(fixture.root, "src", "plugin-sdk", "qa-runtime.ts"); - fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8"); - fs.writeFileSync(distRootAlias, "module.exports = {};\n", "utf-8"); fs.rmSync( path.join(fixture.root, "scripts", "lib", "plugin-sdk-private-local-only-subpaths.json"), { force: true }, @@ -1097,7 +859,6 @@ describe("plugin sdk alias helpers", () => { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, }, }); - const devRootAlias = path.join(devFixture.root, "dist", "plugin-sdk", "root-alias.cjs"); const devCodexMcpProjectionPath = path.join( devFixture.root, "dist", @@ -1111,7 +872,6 @@ describe("plugin sdk alias helpers", () => { "codex-native-task-runtime.js", ); mkdirSafeDir(path.join(devFixture.root, "extensions")); - fs.writeFileSync(devRootAlias, "module.exports = {};\n", "utf-8"); fs.writeFileSync( devCodexMcpProjectionPath, "export const devCodexMcpProjection = true;\n", @@ -1192,9 +952,6 @@ describe("plugin sdk alias helpers", () => { ), ); - expect(fs.realpathSync(aliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(sourceRootAlias), - ); expect(fs.realpathSync(aliases["openclaw/plugin-sdk/codex-mcp-projection"] ?? "")).toBe( fs.realpathSync(sourceCodexMcpProjectionPath), ); @@ -1207,9 +964,6 @@ describe("plugin sdk alias helpers", () => { expect( fs.realpathSync(installedAliases["openclaw/plugin-sdk/codex-native-task-runtime"] ?? ""), ).toBe(fs.realpathSync(distCodexNativeTaskRuntimePath)); - expect(fs.realpathSync(devRootAliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(devRootAlias), - ); expect(fs.realpathSync(devRootAliases["openclaw/plugin-sdk/codex-mcp-projection"] ?? "")).toBe( fs.realpathSync(devCodexMcpProjectionPath), ); @@ -1232,8 +986,6 @@ describe("plugin sdk alias helpers", () => { }, }); writePluginSdkSubpathArtifacts(fixture.root, ["core"]); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); - const distRootAlias = path.join(fixture.root, "dist", "plugin-sdk", "root-alias.cjs"); const sourceSsrFInternalPath = path.join( fixture.root, "src", @@ -1246,8 +998,6 @@ describe("plugin sdk alias helpers", () => { "plugin-sdk", "ssrf-runtime-internal.js", ); - fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8"); - fs.writeFileSync(distRootAlias, "module.exports = {};\n", "utf-8"); fs.rmSync(path.join(fixture.root, "scripts"), { force: true, recursive: true }); fs.writeFileSync(sourceSsrFInternalPath, "export const ssrfInternal = true;\n", "utf-8"); fs.writeFileSync(distSsrFInternalPath, "export const ssrfInternal = true;\n", "utf-8"); @@ -1446,7 +1196,7 @@ describe("plugin sdk alias helpers", () => { }); it("applies explicit dist resolution to plugin-sdk subpath aliases too", () => { - const { fixture, distRootAlias, distChannelRuntimePath } = createPluginSdkAliasTargetFixture(); + const { fixture, distChannelRuntimePath } = createPluginSdkAliasTargetFixture(); const sourcePluginEntry = writePluginEntry( fixture.root, bundledPluginFile("demo", "src/index.ts"), @@ -1457,7 +1207,6 @@ describe("plugin sdk alias helpers", () => { ); expectPluginSdkAliasTargets(distAliases, { - rootAliasPath: distRootAlias, channelRuntimePath: distChannelRuntimePath, }); }); @@ -1974,10 +1723,9 @@ describe("plugin sdk alias helpers", () => { }); it("builds source plugin-sdk subpath aliases through the wider source extension family", () => { - const { fixture, sourceRootAlias, sourceChannelRuntimePath } = - createPluginSdkAliasTargetFixture({ - sourceChannelRuntimeExtension: ".mts", - }); + const { fixture, sourceChannelRuntimePath } = createPluginSdkAliasTargetFixture({ + sourceChannelRuntimeExtension: ".mts", + }); const sourcePluginEntry = writePluginEntry( fixture.root, bundledPluginFile("demo", "src/index.ts"), @@ -1988,7 +1736,6 @@ describe("plugin sdk alias helpers", () => { ); expectPluginSdkAliasTargets(sourceAliases, { - rootAliasPath: sourceRootAlias, channelRuntimePath: sourceChannelRuntimePath, }); }); @@ -1999,7 +1746,6 @@ describe("plugin sdk alias helpers", () => { externalPluginRoot, fixture, sourcePluginEntryPath, - sourceRootAlias, sourceChannelRuntimePath, } = createUserInstalledPluginSdkAliasFixture(); @@ -2010,7 +1756,6 @@ describe("plugin sdk alias helpers", () => { ); expectPluginSdkAliasTargets(aliases, { - rootAliasPath: sourceRootAlias, channelRuntimePath: sourceChannelRuntimePath, pluginEntryPath: sourcePluginEntryPath, }); @@ -2022,7 +1767,6 @@ describe("plugin sdk alias helpers", () => { externalPluginRoot, fixture, sourcePluginEntryPath, - sourceRootAlias, sourceChannelRuntimePath, } = createUserInstalledPluginSdkAliasFixture(); @@ -2051,7 +1795,6 @@ describe("plugin sdk alias helpers", () => { ); expectPluginSdkAliasTargets(aliases, { - rootAliasPath: sourceRootAlias, channelRuntimePath: sourceChannelRuntimePath, pluginEntryPath: sourcePluginEntryPath, }); @@ -2062,11 +1805,13 @@ describe("plugin sdk alias helpers", () => { name: "does not resolve plugin-sdk alias files from cwd fallback when package root is not an OpenClaw root", fixture: () => createPluginSdkAliasFixture({ - srcFile: "channel-runtime.ts", - distFile: "channel-runtime.js", + srcFile: "channel-runtime-context.ts", + distFile: "channel-runtime-context.js", trustedRootIndicators: false, packageExports: { - "./plugin-sdk/channel-runtime": { default: "./dist/plugin-sdk/channel-runtime.js" }, + "./plugin-sdk/channel-runtime-context": { + default: "./dist/plugin-sdk/channel-runtime-context.js", + }, }, }), expected: null, @@ -2237,7 +1982,8 @@ describe("plugin sdk alias helpers", () => { }); it("scopes plugin loader module config by plugin-sdk resolution", () => { - const { fixture, sourceRootAlias, distRootAlias } = createPluginSdkAliasTargetFixture(); + const { fixture, sourceChannelRuntimePath, distChannelRuntimePath } = + createPluginSdkAliasTargetFixture(); const sourcePluginEntry = writePluginEntry( fixture.root, bundledPluginFile("demo", "src/index.ts"), @@ -2266,12 +2012,12 @@ describe("plugin sdk alias helpers", () => { expect(distAgain).toBe(dist); expect(auto).not.toBe(dist); - expect(fs.realpathSync(auto.aliasMap["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(sourceRootAlias), - ); - expect(fs.realpathSync(dist.aliasMap["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(distRootAlias), - ); + expect( + fs.realpathSync(auto.aliasMap["openclaw/plugin-sdk/channel-runtime-context"] ?? ""), + ).toBe(fs.realpathSync(sourceChannelRuntimePath)); + expect( + fs.realpathSync(dist.aliasMap["openclaw/plugin-sdk/channel-runtime-context"] ?? ""), + ).toBe(fs.realpathSync(distChannelRuntimePath)); }); it("loads source runtime shims through the non-native module loading boundary", async () => { @@ -2526,8 +2272,6 @@ export const syntheticRuntimeMarker = { describe("buildPluginLoaderAliasMap memoization", () => { it("returns the same object reference for identical effective context", () => { const fixture = createPluginSdkAliasFixture(); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); - fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8"); const sourcePluginEntry = writePluginEntry( fixture.root, bundledPluginFile("memo-demo", "src/index.ts"), @@ -2542,16 +2286,6 @@ describe("buildPluginLoaderAliasMap memoization", () => { it("returns different references for different modulePath inputs", () => { const fixtureA = createPluginSdkAliasFixture(); const fixtureB = createPluginSdkAliasFixture(); - fs.writeFileSync( - path.join(fixtureA.root, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf-8", - ); - fs.writeFileSync( - path.join(fixtureB.root, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf-8", - ); const entryA = writePluginEntry(fixtureA.root, bundledPluginFile("a", "src/index.ts")); const entryB = writePluginEntry(fixtureB.root, bundledPluginFile("b", "src/index.ts")); @@ -2559,16 +2293,10 @@ describe("buildPluginLoaderAliasMap memoization", () => { const aliasB = buildPluginLoaderAliasMap(entryB); expect(aliasA).not.toBe(aliasB); - expect(aliasA["openclaw/plugin-sdk"]).not.toBe(aliasB["openclaw/plugin-sdk"]); }); it("returns different references when pluginSdkResolution differs", () => { const fixture = createPluginSdkAliasFixture(); - fs.writeFileSync( - path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf-8", - ); const entry = writePluginEntry(fixture.root, bundledPluginFile("res", "src/index.ts")); const auto = buildPluginLoaderAliasMap(entry, undefined, undefined, "auto"); @@ -2579,11 +2307,6 @@ describe("buildPluginLoaderAliasMap memoization", () => { it("returns different references when argv1 differs", () => { const fixture = createPluginSdkAliasFixture(); - fs.writeFileSync( - path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf-8", - ); const entry = writePluginEntry(fixture.root, bundledPluginFile("argv", "src/index.ts")); const a = buildPluginLoaderAliasMap(entry, "/path/to/cli-a.mjs"); @@ -2595,11 +2318,7 @@ describe("buildPluginLoaderAliasMap memoization", () => { it("returns different references when an explicit dev source root differs", () => { const stableFixture = createPluginSdkAliasFixture(); const devFixture = createPluginSdkAliasFixture(); - const stableRootAlias = path.join(stableFixture.root, "dist", "plugin-sdk", "root-alias.cjs"); - const devRootAlias = path.join(devFixture.root, "dist", "plugin-sdk", "root-alias.cjs"); mkdirSafeDir(path.join(devFixture.root, "extensions")); - fs.writeFileSync(stableRootAlias, "module.exports = { stable: true };\n", "utf-8"); - fs.writeFileSync(devRootAlias, "module.exports = { dev: true };\n", "utf-8"); const entry = writePluginEntry( stableFixture.root, bundledPluginFile("dev-env", "src/index.ts"), @@ -2615,12 +2334,6 @@ describe("buildPluginLoaderAliasMap memoization", () => { ); expect(devAliases).not.toBe(stableAliases); - expect(fs.realpathSync(stableAliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(stableRootAlias), - ); - expect(fs.realpathSync(devAliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(devRootAlias), - ); }); it("does not reuse a public alias map after private qa aliases are enabled", () => { @@ -2629,9 +2342,7 @@ describe("buildPluginLoaderAliasMap memoization", () => { "./plugin-sdk/core": { default: "./dist/plugin-sdk/core.js" }, }, }); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); const sourceQaRuntimePath = path.join(fixture.root, "src", "plugin-sdk", "qa-runtime.ts"); - fs.writeFileSync(sourceRootAlias, "module.exports = {};\n", "utf-8"); fs.writeFileSync(sourceQaRuntimePath, "export const qaRuntime = true;\n", "utf-8"); const entry = writePluginEntry(fixture.root, bundledPluginFile("private-qa", "src/index.ts")); @@ -2651,10 +2362,6 @@ describe("buildPluginLoaderAliasMap memoization", () => { it("does not reuse a development alias map in production mode", () => { const fixture = createPluginSdkAliasFixture(); - const sourceRootAlias = path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"); - const distRootAlias = path.join(fixture.root, "dist", "plugin-sdk", "root-alias.cjs"); - fs.writeFileSync(sourceRootAlias, "module.exports = { source: true };\n", "utf-8"); - fs.writeFileSync(distRootAlias, "module.exports = { dist: true };\n", "utf-8"); const entry = writePluginEntry(fixture.root, bundledPluginFile("env-mode", "src/index.ts")); const developmentAliases = withEnv({ NODE_ENV: undefined }, () => @@ -2665,21 +2372,10 @@ describe("buildPluginLoaderAliasMap memoization", () => { ); expect(developmentAliases).not.toBe(productionAliases); - expect(fs.realpathSync(developmentAliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(sourceRootAlias), - ); - expect(fs.realpathSync(productionAliases["openclaw/plugin-sdk"] ?? "")).toBe( - fs.realpathSync(distRootAlias), - ); }); it("memoized result has identical content to a freshly computed map", () => { const fixture = createPluginSdkAliasFixture(); - fs.writeFileSync( - path.join(fixture.root, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf-8", - ); const entry = writePluginEntry(fixture.root, bundledPluginFile("eq", "src/index.ts")); const first = buildPluginLoaderAliasMap(entry); @@ -2701,7 +2397,7 @@ describe("buildPluginLoaderJitiOptions", () => { const options = withEnv({ TMPDIR: tmpDir }, () => buildPluginLoaderJitiOptions( - { "openclaw/plugin-sdk": path.join(root, "dist", "plugin-sdk", "root-alias.cjs") }, + { "openclaw/plugin-sdk/core": path.join(root, "dist", "plugin-sdk", "core.js") }, { modulePath: path.join(root, "dist", "plugins", "loader.js") }, ), ); @@ -2713,8 +2409,7 @@ describe("buildPluginLoaderJitiOptions", () => { const marker = Symbol.for("pathe:normalizedAlias"); const aliasMap = { "openclaw/plugin-sdk/core": "/repo/src/plugin-sdk/core.ts", - "openclaw/plugin-sdk": "/repo/src/plugin-sdk/root-alias.cjs", - "@openclaw/plugin-sdk": "/repo/src/plugin-sdk/root-alias.cjs", + "@openclaw/plugin-sdk/core": "/repo/src/plugin-sdk/core.ts", }; const first = buildPluginLoaderJitiOptions(aliasMap).alias as Record; diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index d8d50ac7cbde..a631b0aed491 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -172,8 +172,10 @@ function hasTrustedOpenClawRootIndicator(params: { packageJson: PluginSdkPackageJson; }): boolean { const packageExports = params.packageJson.exports ?? {}; - const hasPluginSdkRootExport = Object.hasOwn(packageExports, "./plugin-sdk"); - if (!hasPluginSdkRootExport) { + const hasPluginSdkSubpathExport = Object.keys(packageExports).some((key) => + key.startsWith("./plugin-sdk/"), + ); + if (!hasPluginSdkSubpathExport) { return false; } const hasCliEntryExport = Object.hasOwn(packageExports, "./cli-entry"); @@ -400,80 +402,6 @@ function resolvePluginSdkAliasCandidateOrder(params: { return isDistRuntime || params.isProduction ? ["dist", "src"] : ["src", "dist"]; } -function listPluginSdkAliasCandidates(params: { - srcFile: string; - distFile: string; - modulePath: string; - argv1?: string; - cwd?: string; - moduleUrl?: string; - devSourceRoot?: string | null; - pluginSdkResolution?: PluginSdkResolutionPreference; -}) { - const orderedKinds = resolvePluginSdkAliasCandidateOrder({ - modulePath: params.modulePath, - isProduction: process.env.NODE_ENV === "production", - pluginSdkResolution: params.pluginSdkResolution, - }); - const packageRoot = resolveLoaderPluginSdkPackageRoot(params); - if (packageRoot) { - const candidateMap = { - src: path.join(packageRoot, "src", "plugin-sdk", params.srcFile), - dist: path.join(packageRoot, "dist", "plugin-sdk", params.distFile), - } as const; - return orderedKinds.map((kind) => candidateMap[kind]); - } - let cursor = path.dirname(params.modulePath); - const candidates: string[] = []; - for (let i = 0; i < 6; i += 1) { - const candidateMap = { - src: path.join(cursor, "src", "plugin-sdk", params.srcFile), - dist: path.join(cursor, "dist", "plugin-sdk", params.distFile), - } as const; - for (const kind of orderedKinds) { - candidates.push(candidateMap[kind]); - } - const parent = path.dirname(cursor); - if (parent === cursor) { - break; - } - cursor = parent; - } - return candidates; -} - -function resolvePluginSdkAliasFile(params: { - srcFile: string; - distFile: string; - modulePath?: string; - argv1?: string; - cwd?: string; - moduleUrl?: string; - devSourceRoot?: string | null; - pluginSdkResolution?: PluginSdkResolutionPreference; -}): string | null { - try { - const modulePath = resolveLoaderModulePath(params); - for (const candidate of listPluginSdkAliasCandidates({ - srcFile: params.srcFile, - distFile: params.distFile, - modulePath, - argv1: params.argv1, - cwd: params.cwd, - moduleUrl: params.moduleUrl, - devSourceRoot: params.devSourceRoot, - pluginSdkResolution: params.pluginSdkResolution, - })) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - } catch { - // ignore - } - return null; -} - const MAX_PLUGIN_LOADER_ALIAS_CACHE_ENTRIES = 512; const cachedPluginSdkExportedSubpaths = new PluginLruCache( MAX_PLUGIN_LOADER_ALIAS_CACHE_ENTRIES, @@ -488,6 +416,31 @@ const PLUGIN_SDK_PACKAGE_NAMES = ["openclaw/plugin-sdk", "@openclaw/plugin-sdk"] const CODEX_NATIVE_TASK_RUNTIME_PLUGIN_SDK_SUBPATH = "codex-native-task-runtime"; const CODEX_MCP_PROJECTION_PLUGIN_SDK_SUBPATH = "codex-mcp-projection"; const OLLAMA_CONFIGURED_LOCAL_ORIGIN_RUNTIME_PLUGIN_SDK_SUBPATH = "ssrf-runtime-internal"; +const PRIVATE_QA_ONLY_PLUGIN_SDK_SUBPATHS = new Set([ + "agent-runtime-test-contracts", + "channel-contract-testing", + "channel-target-testing", + "channel-test-helpers", + "plugin-test-api", + "plugin-test-contracts", + "plugin-state-test-runtime", + "plugin-test-runtime", + "provider-http-test-mocks", + "provider-test-contracts", + "qa-channel", + "qa-channel-protocol", + "qa-lab", + "qa-runtime", + "reply-payload-testing", + "sqlite-runtime-testing", + "test-env", + "test-fixtures", + "test-live", + "test-live-auth", + "test-media-generation", + "test-media-understanding", + "test-node-mocks", +]); type PrivatePluginSdkSubpathOwner = { bundledPluginId: string; officialInstalledPackageName?: string; @@ -1382,6 +1335,13 @@ function isBundledPluginModulePath(params: { ); } +function isAnyBundledPluginModulePath(params: { packageRoot: string; modulePath: string }) { + const normalizedModulePath = path.resolve(params.modulePath); + return ["extensions", path.join("dist", "extensions"), path.join("dist-runtime", "extensions")] + .map((segment) => path.join(params.packageRoot, segment)) + .some((root) => normalizedModulePath.startsWith(`${root}${path.sep}`)); +} + function isOfficialInstalledPluginPackageRoot(params: { packageRoot: string; packageName: string; @@ -1476,9 +1436,14 @@ function shouldIncludePrivateLocalOnlyPluginSdkSubpath(params: { modulePath: string; subpath: string; }) { + if (PRIVATE_QA_ONLY_PLUGIN_SDK_SUBPATHS.has(params.subpath)) { + return shouldIncludePrivateLocalOnlyPluginSdkSubpaths(); + } const owners = findPrivatePluginSdkSubpathOwners(params.subpath); if (owners.length === 0) { - return shouldIncludePrivateLocalOnlyPluginSdkSubpaths(); + // Public demotions remain loadable by bundled plugins, but never by arbitrary installed + // plugins. Explicit owner records below impose tighter boundaries for sensitive helpers. + return isAnyBundledPluginModulePath(params) || shouldIncludePrivateLocalOnlyPluginSdkSubpaths(); } return owners.some( (owner) => @@ -1654,41 +1619,6 @@ function resolvePluginSdkScopedAliasMap( return aliasMap; } -function resolveExtensionApiAlias(params: LoaderModuleResolveParams = {}): string | null { - try { - const modulePath = resolveLoaderModulePath(params); - const packageRoot = - resolveDevSourceRootParam(params) ?? resolveLoaderPackageRoot({ ...params, modulePath }); - if (!packageRoot) { - return null; - } - - const orderedKinds = resolvePluginSdkAliasCandidateOrder({ - modulePath, - isProduction: process.env.NODE_ENV === "production", - pluginSdkResolution: params.pluginSdkResolution, - }); - for (const kind of orderedKinds) { - if (kind === "dist") { - const candidate = path.join(packageRoot, "dist", "extensionAPI.js"); - if (fs.existsSync(candidate)) { - return candidate; - } - continue; - } - for (const ext of PLUGIN_SDK_SOURCE_CANDIDATE_EXTENSIONS) { - const candidate = path.join(packageRoot, "src", `extensionAPI${ext}`); - if (fs.existsSync(candidate)) { - return candidate; - } - } - } - } catch { - // ignore - } - return null; -} - const JITI_NORMALIZED_ALIAS_SYMBOL = Symbol.for("pathe:normalizedAlias"); const JITI_ALIAS_ROOT_SENTINELS = new Set(["/", "\\", undefined]); const JITI_CONCRETE_ALIAS_TARGET_PATTERN = /^(?:[A-Za-z]:[/\\]|[/\\])/; @@ -1871,24 +1801,7 @@ export function buildPluginLoaderAliasMap( return cached; } - const pluginSdkAlias = resolvePluginSdkAliasFile({ - srcFile: "root-alias.cjs", - distFile: "root-alias.cjs", - modulePath, - argv1, - moduleUrl, - pluginSdkResolution, - devSourceRoot, - }); - const extensionApiAlias = resolveExtensionApiAlias({ - modulePath, - pluginSdkResolution, - devSourceRoot, - }); const result: Record = { - ...(extensionApiAlias - ? { "openclaw/extension-api": normalizeJitiAliasTargetPath(extensionApiAlias) } - : {}), ...resolveBundledPluginPackagePublicSurfaceAliasMap({ modulePath, argv1, @@ -1903,14 +1816,6 @@ export function buildPluginLoaderAliasMap( pluginSdkResolution, devSourceRoot, }), - ...(pluginSdkAlias - ? Object.fromEntries( - PLUGIN_SDK_PACKAGE_NAMES.map((packageName) => [ - packageName, - normalizeJitiAliasTargetPath(pluginSdkAlias), - ]), - ) - : {}), ...Object.fromEntries( Object.entries( resolvePluginSdkScopedAliasMap({ diff --git a/src/plugins/stage-bundled-plugin-runtime.test.ts b/src/plugins/stage-bundled-plugin-runtime.test.ts index 55ef59ff7e34..ecf0f4fe2c97 100644 --- a/src/plugins/stage-bundled-plugin-runtime.test.ts +++ b/src/plugins/stage-bundled-plugin-runtime.test.ts @@ -104,7 +104,7 @@ describe("stageBundledPluginRuntime", () => { recursive: true, }); setupRepoFiles(repoRoot, { - "dist/plugin-sdk/index.js": "export const sdk = true;\n", + "dist/plugin-sdk/core.js": "export const sdk = true;\n", "dist/plugin-sdk/channel-entry-contract.js": "export { contract } from '../channel-entry-contract-abc.js';\n", "dist/plugin-sdk/ssrf-runtime-internal.js": "export const internal = true;\n", @@ -144,7 +144,7 @@ describe("stageBundledPluginRuntime", () => { ), ).exports, ).toMatchObject({ - "./plugin-sdk": "./plugin-sdk/index.js", + "./plugin-sdk/core": "./plugin-sdk/core.js", "./plugin-sdk/channel-entry-contract": "./plugin-sdk/channel-entry-contract.js", }); expect( @@ -192,14 +192,14 @@ describe("stageBundledPluginRuntime", () => { name: "openclaw", type: "module", exports: { - "./plugin-sdk": "./dist/plugin-sdk/index.js", + "./plugin-sdk/core": "./dist/plugin-sdk/core.js", "./plugin-sdk/channel-entry-contract": "./dist/plugin-sdk/channel-entry-contract.js", }, }, null, 2, ), - "dist/plugin-sdk/index.js": "export const sdk = true;\n", + "dist/plugin-sdk/core.js": "export const sdk = true;\n", "dist/plugin-sdk/channel-entry-contract.js": "export const contract = true;\n", "dist/plugin-sdk/source-only.js": "export const sourceOnly = true;\n", "dist/plugin-sdk/ssrf-runtime-internal.js": "export const internal = true;\n", @@ -213,10 +213,10 @@ describe("stageBundledPluginRuntime", () => { fs.readFileSync(path.join(aliasRoot, "package.json"), "utf8"), ) as { exports: Record }; expect(packageJson.exports).toEqual({ - "./plugin-sdk": "./plugin-sdk/index.js", + "./plugin-sdk/core": "./plugin-sdk/core.js", "./plugin-sdk/channel-entry-contract": "./plugin-sdk/channel-entry-contract.js", }); - expect(fs.existsSync(path.join(aliasRoot, "plugin-sdk", "index.js"))).toBe(true); + expect(fs.existsSync(path.join(aliasRoot, "plugin-sdk", "core.js"))).toBe(true); expect(fs.existsSync(path.join(aliasRoot, "plugin-sdk", "channel-entry-contract.js"))).toBe( true, ); diff --git a/src/plugins/status.registry-snapshot.test.ts b/src/plugins/status.registry-snapshot.test.ts index 1429eae21bb1..c8230d9d4e01 100644 --- a/src/plugins/status.registry-snapshot.test.ts +++ b/src/plugins/status.registry-snapshot.test.ts @@ -72,7 +72,6 @@ describe("buildPluginRegistrySnapshotReport", () => { const env = { ...createColdPluginHermeticEnv(tempRoot, { bundledPluginsDir: makeTempDir(), - disablePersistedRegistry: false, }), OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1", OPENCLAW_STATE_DIR: stateDir, @@ -260,7 +259,6 @@ describe("buildPluginRegistrySnapshotReport", () => { const config = createColdPluginConfig(fixture.rootDir, fixture.pluginId); const env = createColdPluginHermeticEnv(workspaceDir, { bundledPluginsDir: makeTempDir(), - disablePersistedRegistry: false, }); await refreshPluginRegistry({ diff --git a/src/plugins/status.test-fixtures.ts b/src/plugins/status.test-fixtures.ts index b9449e26d5b1..fef65c5829d4 100644 --- a/src/plugins/status.test-fixtures.ts +++ b/src/plugins/status.test-fixtures.ts @@ -7,8 +7,6 @@ import type { PluginHookName } from "./types.js"; export { createPluginRecord }; -export const LEGACY_BEFORE_AGENT_START_MESSAGE = - "still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work."; export const HOOK_ONLY_MESSAGE = "is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet."; export const DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE = @@ -20,14 +18,6 @@ export function createCompatibilityNotice( params: Pick, ): PluginCompatibilityNotice { switch (params.code) { - case "legacy-before-agent-start": - return { - pluginId: params.pluginId, - code: params.code, - compatCode: "legacy-before-agent-start", - severity: "warn", - message: LEGACY_BEFORE_AGENT_START_MESSAGE, - }; case "hook-only": return { pluginId: params.pluginId, diff --git a/src/plugins/status.test.ts b/src/plugins/status.test.ts index ec83084f232d..4478c98f442d 100644 --- a/src/plugins/status.test.ts +++ b/src/plugins/status.test.ts @@ -8,10 +8,8 @@ import { createCustomHook, createPluginLoadResult, createPluginRecord, - createTypedHook, DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE, HOOK_ONLY_MESSAGE, - LEGACY_BEFORE_AGENT_START_MESSAGE, REMOVED_SESSION_TRANSCRIPT_FILE_API_MESSAGE, } from "./status.test-fixtures.js"; @@ -310,12 +308,11 @@ function expectAutoEnabledDemoCompatibilityNoticesPreserveRawConfig() { hookCount: 1, }), { - typedHooks: [createTypedHook({ pluginId: "demo", hookName: "before_agent_start" })], + hooks: [createCustomHook({ pluginId: "demo", events: ["message"] })], }, ); expect(buildPluginCompatibilityNotices({ config: rawConfig })).toEqual([ - createCompatibilityNotice({ pluginId: "demo", code: "legacy-before-agent-start" }), createCompatibilityNotice({ pluginId: "demo", code: "hook-only" }), ]); @@ -714,7 +711,6 @@ describe("plugin status reports", () => { }), ], diagnostics: [{ level: "warn", pluginId: "google", message: "watch this surface" }], - typedHooks: [createTypedHook({ pluginId: "google", hookName: "before_agent_start" })], }); const inspect = expectInspectReport("google"); @@ -724,10 +720,7 @@ describe("plugin status reports", () => { capabilityMode: "hybrid", capabilityKinds: ["text-inference", "media-understanding", "image-generation", "web-search"], }); - expect(inspect.usesLegacyBeforeAgentStart).toBe(true); - expect(inspect.compatibility).toEqual([ - createCompatibilityNotice({ pluginId: "google", code: "legacy-before-agent-start" }), - ]); + expect(inspect.compatibility).toStrictEqual([]); expectInspectPolicy(inspect, { allowPromptInjection: false, allowConversationAccess: true, @@ -761,14 +754,12 @@ describe("plugin status reports", () => { }), ], hooks: [createCustomHook({ pluginId: "lca", events: ["message"] })], - typedHooks: [createTypedHook({ pluginId: "lca", hookName: "before_agent_start" })], }); const inspect = buildAllPluginInspectReports(); expect(inspect.map((entry) => entry.plugin.id)).toEqual(["lca", "microsoft"]); expect(inspect.map((entry) => entry.shape)).toEqual(["hook-only", "hybrid-capability"]); - expect(inspect[0]?.usesLegacyBeforeAgentStart).toBe(true); expectCapabilityKinds(expectDefined(inspect[1], "inspect[1] test invariant"), [ "text-inference", "web-search", @@ -820,7 +811,7 @@ describe("plugin status reports", () => { expectNoCompatibilityWarnings(); }); - it("builds compatibility warnings for legacy compatibility paths", () => { + it("builds compatibility warnings for hook-only compatibility paths", () => { setPluginLoadResult({ plugins: [ createPluginRecord({ @@ -830,11 +821,11 @@ describe("plugin status reports", () => { hookCount: 1, }), ], - typedHooks: [createTypedHook({ pluginId: "lca", hookName: "before_agent_start" })], + hooks: [createCustomHook({ pluginId: "lca", events: ["message"] })], }); expectCompatibilityOutput({ - warnings: [`lca ${LEGACY_BEFORE_AGENT_START_MESSAGE}`, `lca ${HOOK_ONLY_MESSAGE}`], + warnings: [`lca ${HOOK_ONLY_MESSAGE}`], }); }); @@ -967,22 +958,12 @@ describe("plugin status reports", () => { name: "Hook Only", hookCount: 1, }), - createPluginRecord({ - id: "legacy-only", - name: "Legacy Only", - providerIds: ["legacy-only"], - hookCount: 1, - }), ], hooks: [createCustomHook({ pluginId: "hook-only", events: ["message"] })], - typedHooks: [createTypedHook({ pluginId: "legacy-only", hookName: "before_agent_start" })], }); expectCompatibilityOutput({ - notices: [ - createCompatibilityNotice({ pluginId: "hook-only", code: "hook-only" }), - createCompatibilityNotice({ pluginId: "legacy-only", code: "legacy-before-agent-start" }), - ], + notices: [createCompatibilityNotice({ pluginId: "hook-only", code: "hook-only" })], }); }); @@ -1051,21 +1032,11 @@ describe("plugin status reports", () => { }); it("formats and summarizes compatibility notices", () => { - const notice = createCompatibilityNotice({ - pluginId: "legacy-plugin", - code: "legacy-before-agent-start", - }); + const notice = createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }); - expect(formatPluginCompatibilityNotice(notice)).toBe( - `legacy-plugin ${LEGACY_BEFORE_AGENT_START_MESSAGE}`, - ); - expect( - summarizePluginCompatibility([ - notice, - createCompatibilityNotice({ pluginId: "legacy-plugin", code: "hook-only" }), - ]), - ).toEqual({ - noticeCount: 2, + expect(formatPluginCompatibilityNotice(notice)).toBe(`legacy-plugin ${HOOK_ONLY_MESSAGE}`); + expect(summarizePluginCompatibility([notice])).toEqual({ + noticeCount: 1, pluginCount: 1, }); }); diff --git a/src/plugins/status.ts b/src/plugins/status.ts index fb50c804eca9..d310d8f01823 100644 --- a/src/plugins/status.ts +++ b/src/plugins/status.ts @@ -45,7 +45,6 @@ export type { PluginCapabilityKind, PluginInspectShape } from "./inspect-shape.j export type PluginCompatibilityNotice = { pluginId: string; code: - | "legacy-before-agent-start" | "hook-only" | "deprecated-memory-embedding-provider-api" | "removed-session-transcript-file-api"; @@ -103,27 +102,16 @@ export type PluginInspectReport = { allowedModels: string[]; hasAllowedModelsConfig: boolean; }; - usesLegacyBeforeAgentStart: boolean; compatibility: PluginCompatibilityNotice[]; }; function buildCompatibilityNoticesForInspect( - inspect: Pick & { + inspect: Pick & { diagnostics: readonly PluginDiagnostic[]; hasRuntimeMemoryEmbeddingProviderRegistration: boolean; }, ): PluginCompatibilityNotice[] { const warnings: PluginCompatibilityNotice[] = []; - if (inspect.usesLegacyBeforeAgentStart) { - warnings.push({ - pluginId: inspect.plugin.id, - code: "legacy-before-agent-start", - compatCode: "legacy-before-agent-start", - severity: "warn", - message: - "still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.", - }); - } if (inspect.shape === "hook-only") { warnings.push({ pluginId: inspect.plugin.id, @@ -441,14 +429,12 @@ export function buildPluginInspectReport(params: { ]; } - const usesLegacyBeforeAgentStart = shapeSummary.usesLegacyBeforeAgentStart; const hasRuntimeMemoryEmbeddingProviderRegistration = report.memoryEmbeddingProviders.some( (entry) => entry.pluginId === plugin.id, ); const compatibility = buildCompatibilityNoticesForInspect({ plugin, shape, - usesLegacyBeforeAgentStart, diagnostics, hasRuntimeMemoryEmbeddingProviderRegistration, }); @@ -481,7 +467,6 @@ export function buildPluginInspectReport(params: { allowedModels: [...(policyEntry?.subagent?.allowedModels ?? [])], hasAllowedModelsConfig: policyEntry?.subagent?.hasAllowedModelsConfig === true, }, - usesLegacyBeforeAgentStart, compatibility, }; } diff --git a/src/plugins/test-helpers/cold-plugin-fixtures.ts b/src/plugins/test-helpers/cold-plugin-fixtures.ts index 89a666e863e1..27830c93ca99 100644 --- a/src/plugins/test-helpers/cold-plugin-fixtures.ts +++ b/src/plugins/test-helpers/cold-plugin-fixtures.ts @@ -111,14 +111,12 @@ export function createColdPluginConfig(pluginDir: string, pluginId: string): Ope export function createColdPluginHermeticEnv( homeDir: string, - options: { bundledPluginsDir?: string; disablePersistedRegistry?: boolean } = {}, + options: { bundledPluginsDir?: string } = {}, ): NodeJS.ProcessEnv { return { ...process.env, OPENCLAW_HOME: path.join(homeDir, "home"), OPENCLAW_BUNDLED_PLUGINS_DIR: options.bundledPluginsDir, - OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY: - options.disablePersistedRegistry === false ? undefined : "1", OPENCLAW_VERSION: "2026.4.25", VITEST: "true", }; diff --git a/src/plugins/tools.optional.test.ts b/src/plugins/tools.optional.test.ts index faab7a5f9a84..7675c7eb6ab2 100644 --- a/src/plugins/tools.optional.test.ts +++ b/src/plugins/tools.optional.test.ts @@ -379,8 +379,8 @@ function createXaiToolManifest() { enabledByDefault: true, channels: [], providers: ["xai"], - providerAuthEnvVars: { - xai: ["XAI_API_KEY"], + setup: { + providers: [{ id: "xai", envVars: ["XAI_API_KEY"] }], }, contracts: { tools: ["x_search"], @@ -1760,8 +1760,8 @@ describe("resolvePluginTools optional tools", () => { enabledByDefault: true, channels: [], providers: [], - providerAuthEnvVars: { - xai: ["XAI_API_KEY"], + setup: { + providers: [{ id: "xai", envVars: ["XAI_API_KEY"] }], }, contracts: { tools: ["other_tool", "optional_tool"], diff --git a/src/plugins/types.ts b/src/plugins/types.ts index 92229305e879..04b1e821f8ce 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -179,7 +179,6 @@ export type { ProviderBuiltInModelSuppressionResult, ProviderCatalogContext, ProviderCatalogResult, - ProviderDiscoveryContext, ProviderModernModelPolicyContext, UnifiedModelCatalogProviderContext, UnifiedModelCatalogProviderPlugin, @@ -247,5 +246,5 @@ export type { } from "./plugin-registration.types.js"; export type { PluginHookRegistration } from "./hook-types.js"; export type { PluginTextTransformRegistration } from "./plugin-api.types.js"; -export type { ProviderDiscoveryOrder } from "./provider-catalog.types.js"; +export type { ProviderCatalogOrder } from "./provider-catalog.types.js"; export type { ProviderPlugin } from "./provider-plugin.types.js"; diff --git a/src/plugins/web-provider-public-artifacts.test.ts b/src/plugins/web-provider-public-artifacts.test.ts index 4eb6b36a99bc..6090bfe89c36 100644 --- a/src/plugins/web-provider-public-artifacts.test.ts +++ b/src/plugins/web-provider-public-artifacts.test.ts @@ -6,29 +6,6 @@ import { loadBundledWebSearchProviderEntriesFromDir, } from "./web-provider-public-artifacts.explicit.js"; -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - -function supportsSecretRefWebSearchApiKey( - plugin: ReturnType["plugins"][number], -): boolean { - const configProperties = isRecord(plugin.configSchema?.["properties"]) - ? plugin.configSchema["properties"] - : undefined; - const webSearch = configProperties?.["webSearch"]; - if (!isRecord(webSearch)) { - return false; - } - const properties = isRecord(webSearch["properties"]) ? webSearch["properties"] : undefined; - const apiKey = properties?.["apiKey"]; - if (!isRecord(apiKey)) { - return false; - } - const typeValue = apiKey["type"]; - return Array.isArray(typeValue) && typeValue.includes("object"); -} - const registry = loadPluginManifestRegistry(); const webSearchPluginIds = bundledPluginIdsWithContract("webSearchProviders"); const webFetchPluginIds = bundledPluginIdsWithContract("webFetchProviders"); @@ -61,30 +38,4 @@ describe("web provider public artifacts", () => { loadBundledWebFetchProviderEntriesFromDir({ dirName: pluginId, pluginId }), ).not.toBeNull(); }); - - it("registers compatibility runtime paths for bundled SecretRef-capable web search providers", () => { - const expectedPluginIds = registry.plugins - .filter( - (plugin) => - plugin.origin === "bundled" && - (plugin.contracts?.webSearchProviders?.length ?? 0) > 0 && - supportsSecretRefWebSearchApiKey(plugin), - ) - .map((plugin) => plugin.id) - .toSorted((left, right) => left.localeCompare(right)); - - expect(expectedPluginIds).not.toHaveLength(0); - const actualPluginIds = registry.plugins - .filter( - (plugin) => - plugin.origin === "bundled" && - (plugin.contracts?.webSearchProviders?.length ?? 0) > 0 && - (plugin.configContracts?.compatibilityRuntimePaths ?? []).includes( - "tools.web.search.apiKey", - ), - ) - .map((plugin) => plugin.id) - .toSorted((left, right) => left.localeCompare(right)); - expect(actualPluginIds).toEqual(expectedPluginIds); - }); }); diff --git a/src/plugins/web-search-credential-presence.ts b/src/plugins/web-search-credential-presence.ts index df52a4d18e10..cf1e7eb5a44f 100644 --- a/src/plugins/web-search-credential-presence.ts +++ b/src/plugins/web-search-credential-presence.ts @@ -53,10 +53,7 @@ function hasManifestWebSearchEnvCredentialCandidate(params: { if ((plugin.contracts?.webSearchProviders?.length ?? 0) === 0) { return false; } - const envVars = [ - ...(plugin.setup?.providers ?? []).flatMap((provider) => provider.envVars ?? []), - ...Object.values(plugin.providerAuthEnvVars ?? {}).flat(), - ]; + const envVars = (plugin.setup?.providers ?? []).flatMap((provider) => provider.envVars ?? []); return envVars.some((envVar) => hasConfiguredCredentialValue(env[envVar])); }); } diff --git a/src/secrets/channel-env-vars.dynamic.test.ts b/src/secrets/channel-env-vars.dynamic.test.ts index 7c30c91f0e2b..9bfbd0a9c782 100644 --- a/src/secrets/channel-env-vars.dynamic.test.ts +++ b/src/secrets/channel-env-vars.dynamic.test.ts @@ -5,7 +5,10 @@ type MockManifestRegistry = { plugins: Array<{ id: string; origin: string; - channelEnvVars?: Record; + packageChannel?: { + id: string; + configuredState?: { env?: { allOf?: string[]; anyOf?: string[] } }; + }; }>; diagnostics: unknown[]; }; @@ -41,7 +44,7 @@ vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ loadPluginMetadataSnapshot: pluginRegistryMocks.loadPluginMetadataSnapshot, })); -describe("channel env vars dynamic manifest metadata", () => { +describe("channel env vars dynamic package metadata", () => { beforeEach(() => { vi.resetModules(); pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReset(); @@ -60,8 +63,11 @@ describe("channel env vars dynamic manifest metadata", () => { { id: "external-mattermost", origin: "global", - channelEnvVars: { - mattermost: ["MATTERMOST_BOT_TOKEN", "MATTERMOST_URL"], + packageChannel: { + id: "mattermost", + configuredState: { + env: { anyOf: ["MATTERMOST_BOT_TOKEN", "MATTERMOST_URL"] }, + }, }, }, ], diff --git a/src/secrets/channel-env-vars.ts b/src/secrets/channel-env-vars.ts index 5249130ef82e..9b7f73593f14 100644 --- a/src/secrets/channel-env-vars.ts +++ b/src/secrets/channel-env-vars.ts @@ -27,15 +27,15 @@ function resolveChannelEnvVars( }); const candidates: Record = {}; for (const plugin of snapshot.plugins) { - if (!plugin.channelEnvVars) { + const channelId = plugin.packageChannel?.id?.trim(); + const channelEnv = plugin.packageChannel?.configuredState?.env; + if (!channelId || !channelEnv) { continue; } - // Sort channel ids before merging so prompt/test snapshots do not depend on manifest order. - for (const [channelId, keys] of Object.entries(plugin.channelEnvVars).toSorted( - ([left], [right]) => left.localeCompare(right), - )) { - appendUniqueEnvVarCandidates(candidates, channelId, keys); - } + appendUniqueEnvVarCandidates(candidates, channelId, [ + ...(channelEnv.allOf ?? []), + ...(channelEnv.anyOf ?? []), + ]); } return candidates; } diff --git a/src/secrets/credential-matrix.ts b/src/secrets/credential-matrix.ts index 679675d8b9ea..d05a6264e045 100644 --- a/src/secrets/credential-matrix.ts +++ b/src/secrets/credential-matrix.ts @@ -26,18 +26,8 @@ export type SecretRefCredentialMatrixDocument = { export function buildSecretRefCredentialMatrix(): SecretRefCredentialMatrixDocument { const entriesByKey = new Map(); for (const entry of getSecretTargetRegistry({ sourceTree: true })) { - const isCanonicalFirecrawlWebFetchEntry = - entry.id === "plugins.entries.firecrawl.config.webFetch.apiKey"; - // Firecrawl web fetch moved to the plugin-owned path, but matrix docs keep the public - // tools.web.fetch.firecrawl path as the canonical operator-facing surface. - const canonicalId = isCanonicalFirecrawlWebFetchEntry - ? "tools.web.fetch.firecrawl.apiKey" - : entry.id; - const canonicalPath = isCanonicalFirecrawlWebFetchEntry - ? "tools.web.fetch.firecrawl.apiKey" - : entry.pathPattern; const matrixEntry = Object.assign( - { id: canonicalId, configFile: entry.configFile, path: canonicalPath }, + { id: entry.id, configFile: entry.configFile, path: entry.pathPattern }, entry.refPathPattern ? { refPath: entry.refPathPattern } : {}, entry.authProfileType ? { when: { type: entry.authProfileType } } : {}, { secretShape: entry.secretShape, optIn: true as const }, diff --git a/src/secrets/exec-secret-ref-id-parity.test.ts b/src/secrets/exec-secret-ref-id-parity.test.ts index e57401ba06b8..3f5b89a8199a 100644 --- a/src/secrets/exec-secret-ref-id-parity.test.ts +++ b/src/secrets/exec-secret-ref-id-parity.test.ts @@ -18,7 +18,6 @@ import { import { isSecretsApplyPlan } from "./plan.js"; import { isValidExecSecretRefId, isValidFileSecretRefId } from "./ref-contract.js"; import { compileTargetRegistryEntry, materializePathTokens } from "./target-registry-pattern.js"; -import { canonicalizeSecretTargetCoverageId } from "./target-registry-test-helpers.js"; import type { SecretTargetRegistryEntry } from "./target-registry-types.js"; import { listSecretTargetRegistryEntries } from "./target-registry.js"; @@ -172,62 +171,55 @@ describe("exec SecretRef id parity", () => { } function classifyTargetClass(id: string): string { - const canonicalId = canonicalizeSecretTargetCoverageId(id); - if (canonicalId.startsWith("auth-profiles.")) { + if (id.startsWith("auth-profiles.")) { return "auth-profiles"; } - if (canonicalId.startsWith("agents.")) { + if (id.startsWith("agents.")) { return "agents"; } - if (canonicalId.startsWith("channels.")) { + if (id.startsWith("channels.")) { return "channels"; } - if (canonicalId.startsWith("cron.")) { + if (id.startsWith("cron.")) { return "cron"; } - if (canonicalId.startsWith("gateway.auth.")) { + if (id.startsWith("gateway.auth.")) { return "gateway.auth"; } - if (canonicalId.startsWith("gateway.remote.")) { + if (id.startsWith("gateway.remote.")) { return "gateway.remote"; } - if (canonicalId.startsWith("messages.")) { + if (id.startsWith("messages.")) { return "messages"; } - if (canonicalId.startsWith("models.providers.") && canonicalId.includes(".headers.")) { + if (id.startsWith("models.providers.") && id.includes(".headers.")) { return "models.headers"; } - if (canonicalId.startsWith("models.providers.") && canonicalId.includes(".request.")) { + if (id.startsWith("models.providers.") && id.includes(".request.")) { return "models.request"; } - if (canonicalId.startsWith("models.providers.")) { + if (id.startsWith("models.providers.")) { return "models.apiKey"; } - if (canonicalId.startsWith("skills.entries.")) { + if (id.startsWith("skills.entries.")) { return "skills"; } - if (canonicalId.startsWith("talk.")) { + if (id.startsWith("talk.")) { return "talk"; } - if (canonicalId.startsWith("tools.web.fetch.")) { + if (id.startsWith("tools.web.fetch.")) { return "tools.web.fetch"; } - if ( - canonicalId.startsWith("plugins.entries.") && - canonicalId.includes(".config.webFetch.apiKey") - ) { + if (id.startsWith("plugins.entries.") && id.includes(".config.webFetch.apiKey")) { return "tools.web.fetch"; } - if ( - canonicalId.startsWith("plugins.entries.") && - canonicalId.includes(".config.webSearch.apiKey") - ) { + if (id.startsWith("plugins.entries.") && id.includes(".config.webSearch.apiKey")) { return "tools.web.search"; } - if (canonicalId.startsWith("tools.web.search.")) { + if (id.startsWith("tools.web.search.")) { return "tools.web.search"; } - if (canonicalId.startsWith("plugins.entries.")) { + if (id.startsWith("plugins.entries.")) { return "plugins.config"; } return "unclassified"; diff --git a/src/secrets/provider-env-vars.dynamic.test.ts b/src/secrets/provider-env-vars.dynamic.test.ts index 1d8535d4e9b9..d27d4ae83cc5 100644 --- a/src/secrets/provider-env-vars.dynamic.test.ts +++ b/src/secrets/provider-env-vars.dynamic.test.ts @@ -17,7 +17,6 @@ type MockManifestRegistry = { enabledByDefault?: boolean; kind?: "memory" | "context-engine" | Array<"memory" | "context-engine">; providers?: string[]; - providerAuthEnvVars?: Record; providerUsageAuthEnvVars?: Record; providerAuthAliases?: Record; setup?: { @@ -115,8 +114,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "external-fireworks", origin: "global", - providerAuthEnvVars: { - fireworks: ["FIREWORKS_ALT_API_KEY"], + setup: { + providers: [{ id: "fireworks", envVars: ["FIREWORKS_ALT_API_KEY"] }], }, providerAuthAliases: { "fireworks-plan": "fireworks", @@ -345,8 +344,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "load-path-provider", origin: "global", - providerAuthEnvVars: { - "load-path-provider": ["LOAD_PATH_PROVIDER_API_KEY"], + setup: { + providers: [{ id: "load-path-provider", envVars: ["LOAD_PATH_PROVIDER_API_KEY"] }], }, }, ], @@ -443,20 +442,17 @@ describe("provider env vars dynamic manifest metadata", () => { ]); }); - it("appends setup provider env vars after explicit provider auth env vars", () => { + it("deduplicates setup provider env vars in declaration order", () => { pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({ plugins: [ { id: "external-fireworks", origin: "global", - providerAuthEnvVars: { - fireworks: ["FIREWORKS_API_KEY"], - }, setup: { providers: [ { id: "fireworks", - envVars: ["FIREWORKS_SETUP_KEY", "FIREWORKS_API_KEY"], + envVars: ["FIREWORKS_API_KEY", "FIREWORKS_SETUP_KEY", "FIREWORKS_API_KEY"], }, ], }, @@ -477,8 +473,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "external-fireworks", origin: "global", - providerAuthEnvVars: { - fireworks: ["FIREWORKS_ALT_API_KEY"], + setup: { + providers: [{ id: "fireworks", envVars: ["FIREWORKS_ALT_API_KEY"] }], }, }, ], @@ -505,8 +501,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "workspace-audio", origin: "workspace", - providerAuthEnvVars: { - whisperx: ["WHISPERX_API_KEY"], + setup: { + providers: [{ id: "whisperx", envVars: ["WHISPERX_API_KEY"] }], }, }, ], @@ -526,11 +522,12 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "workspace-audio", origin: "workspace", - providerAuthEnvVars: { - whisperx: ["AWS_SECRET_ACCESS_KEY"], - }, setup: { providers: [ + { + id: "whisperx", + envVars: ["AWS_SECRET_ACCESS_KEY"], + }, { id: "workspace-setup", envVars: ["WORKSPACE_SETUP_SECRET"], @@ -576,8 +573,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "workspace-audio", origin: "workspace", - providerAuthEnvVars: { - whisperx: ["WHISPERX_API_KEY"], + setup: { + providers: [{ id: "whisperx", envVars: ["WHISPERX_API_KEY"] }], }, }, ], @@ -604,8 +601,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "workspace-audio", origin: "workspace", - providerAuthEnvVars: { - whisperx: ["AWS_SECRET_ACCESS_KEY"], + setup: { + providers: [{ id: "whisperx", envVars: ["AWS_SECRET_ACCESS_KEY"] }], }, }, ], @@ -635,8 +632,8 @@ describe("provider env vars dynamic manifest metadata", () => { id: "workspace-engine", origin: "workspace", kind: "context-engine", - providerAuthEnvVars: { - whisperx: ["WHISPERX_API_KEY"], + setup: { + providers: [{ id: "whisperx", envVars: ["WHISPERX_API_KEY"] }], }, }, ], @@ -665,8 +662,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "external-fireworks", origin: "global", - providerAuthEnvVars: { - fireworks: ["FIREWORKS_ALT_API_KEY"], + setup: { + providers: [{ id: "fireworks", envVars: ["FIREWORKS_ALT_API_KEY"] }], }, providerAuthAliases: { "fireworks-plan": "fireworks", @@ -690,9 +687,6 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "external-fireworks", origin: "global", - providerAuthEnvVars: { - fireworks: ["FIREWORKS_ALT_API_KEY"], - }, providerAuthAliases: { "fireworks-plan": "fireworks", }, @@ -700,6 +694,7 @@ describe("provider env vars dynamic manifest metadata", () => { providers: [ { id: "fireworks", + envVars: ["FIREWORKS_ALT_API_KEY"], authEvidence: [ { type: "local-file-with-env", @@ -781,8 +776,8 @@ describe("provider env vars dynamic manifest metadata", () => { { id: "load-path-provider", origin: "global", - providerAuthEnvVars: { - "load-path-provider": ["LOAD_PATH_PROVIDER_API_KEY"], + setup: { + providers: [{ id: "load-path-provider", envVars: ["LOAD_PATH_PROVIDER_API_KEY"] }], }, }, ], diff --git a/src/secrets/provider-env-vars.ts b/src/secrets/provider-env-vars.ts index 8df19b1b55cd..84315ca60a45 100644 --- a/src/secrets/provider-env-vars.ts +++ b/src/secrets/provider-env-vars.ts @@ -205,13 +205,6 @@ function resolveManifestProviderAuthEnvVarCandidatesFromSnapshot( if (!shouldUsePluginProviderEnvVars(plugin, params)) { continue; } - if (plugin.providerAuthEnvVars) { - for (const [providerId, keys] of Object.entries(plugin.providerAuthEnvVars).toSorted( - ([left], [right]) => left.localeCompare(right), - )) { - appendUniqueEnvVarCandidates(candidates, providerId, keys); - } - } for (const provider of plugin.setup?.providers ?? []) { appendUniqueEnvVarCandidates(candidates, provider.id, provider.envVars ?? []); } diff --git a/src/secrets/provider-integrations.ts b/src/secrets/provider-integrations.ts index 0ba46bfaf4b6..9b474956dc2d 100644 --- a/src/secrets/provider-integrations.ts +++ b/src/secrets/provider-integrations.ts @@ -25,7 +25,7 @@ export type SecretProviderIntegrationPreset = { }; /** Result of materializing a plugin integration into a manual exec provider config. */ -export type SecretProviderIntegrationResolution = +type SecretProviderIntegrationResolution = | { ok: true; providerConfig: ManualExecSecretProviderConfig; diff --git a/src/secrets/runtime-command-secrets.ts b/src/secrets/runtime-command-secrets.ts index 118a5222eaff..06c53374c5e1 100644 --- a/src/secrets/runtime-command-secrets.ts +++ b/src/secrets/runtime-command-secrets.ts @@ -4,12 +4,11 @@ import { uniqueStrings } from "@openclaw/normalization-core/string-normalization import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveSecretInputRef } from "../config/types.secrets.js"; import { resolveManifestContractOwnerPluginId } from "../plugins/plugin-registry.js"; -import { resolveBundledExplicitWebSearchProvidersFromPublicArtifacts } from "../plugins/web-provider-public-artifacts.explicit.js"; import { analyzeCommandSecretAssignmentsFromSnapshot, type CommandSecretAssignment, } from "./command-config.js"; -import { getPath, setPathExistingStrict } from "./path-utils.js"; +import { setPathExistingStrict } from "./path-utils.js"; import { resolveSecretRefValue } from "./resolve.js"; import { createResolverContext } from "./runtime-shared.js"; import { getActiveSecretsRuntimeEnv, getActiveSecretsRuntimeSnapshot } from "./runtime-state.js"; @@ -61,45 +60,8 @@ function pluginIdFromRuntimeWebPath(path: string): string | undefined { return /^plugins\.entries\.([^.]+)\.config\.(webSearch|webFetch)\.apiKey$/.exec(path)?.[1]; } -function searchProviderFromDirectWebPath(path: string): string | undefined { - return /^tools\.web\.search\.([^.]+)\.apiKey$/.exec(path)?.[1]; -} - -function fetchProviderFromDirectWebPath(path: string): string | undefined { - return /^tools\.web\.fetch\.([^.]+)\.apiKey$/.exec(path)?.[1]; -} - function isWebCommandSecretPath(path: string): boolean { - return ( - path === "tools.web.search.apiKey" || - /^tools\.web\.(search|fetch)\.[^.]+\.apiKey$/.test(path) || - /^plugins\.entries\.[^.]+\.config\.(webSearch|webFetch)\.apiKey$/.test(path) - ); -} - -function webSearchProviderUsesSharedSearchCredential(params: { - config: OpenClawConfig; - provider: string; -}): boolean { - const sentinel = "__openclaw_shared_web_search_probe__"; - const pluginId = resolveManifestContractOwnerPluginId({ - contract: "webSearchProviders", - value: params.provider, - origin: "bundled", - config: params.config, - }); - if (!pluginId) { - return false; - } - const providers = resolveBundledExplicitWebSearchProvidersFromPublicArtifacts({ - onlyPluginIds: [pluginId], - }); - const provider = providers?.find((entry) => entry.id === params.provider); - return ( - provider?.credentialPath === "tools.web.search.apiKey" || - provider?.getCredentialValue({ apiKey: sentinel }) === sentinel || - provider?.getConfiguredCredentialFallback?.(params.config)?.path === "tools.web.search.apiKey" - ); + return /^plugins\.entries\.[^.]+\.config\.(webSearch|webFetch)\.apiKey$/.test(path); } function isProviderOverridePath(params: { @@ -112,16 +74,6 @@ function isProviderOverridePath(params: { if (params.config.tools?.web?.search?.enabled === false) { return false; } - if (params.path === "tools.web.search.apiKey") { - return webSearchProviderUsesSharedSearchCredential({ - config: params.config, - provider: webSearch, - }); - } - const directProvider = searchProviderFromDirectWebPath(params.path); - if (directProvider) { - return directProvider === webSearch; - } const pluginId = pluginIdFromRuntimeWebPath(params.path); if (pluginId && params.path.endsWith(".config.webSearch.apiKey")) { return ( @@ -140,10 +92,6 @@ function isProviderOverridePath(params: { if (params.config.tools?.web?.fetch?.enabled === false) { return false; } - const directProvider = fetchProviderFromDirectWebPath(params.path); - if (directProvider) { - return directProvider === webFetch; - } const pluginId = pluginIdFromRuntimeWebPath(params.path); if (pluginId && params.path.endsWith(".config.webFetch.apiKey")) { return ( @@ -239,110 +187,6 @@ function filterInactiveRefPaths(params: { }); } -function mirrorResolvedProviderCredentialToDirectPath(params: { - config: OpenClawConfig; - resolvedConfig: OpenClawConfig; - contract: "webSearchProviders" | "webFetchProviders"; - provider: string | undefined; - directPathPrefix: string; - pluginConfigKey: "webSearch" | "webFetch"; -}): void { - const provider = normalizeOptionalString(params.provider); - if (!provider) { - return; - } - const pluginId = resolveManifestContractOwnerPluginId({ - contract: params.contract, - value: provider, - origin: "bundled", - config: params.config, - }); - if (!pluginId) { - return; - } - const directSegments = [...params.directPathPrefix.split("."), provider, "apiKey"]; - const directValue = getPath(params.config, directSegments); - if (directValue === undefined) { - return; - } - // Legacy direct provider targets still exist for command assignment discovery; mirror the - // plugin-owned resolved value only when the source config declares that direct path. - const resolvedValue = getPath(params.resolvedConfig, [ - "plugins", - "entries", - pluginId, - "config", - params.pluginConfigKey, - "apiKey", - ]); - if (typeof resolvedValue !== "string" || resolvedValue.length === 0) { - return; - } - setPathExistingStrict(params.resolvedConfig, directSegments, resolvedValue); -} - -function mirrorResolvedProviderCredentialToDirectPaths(params: { - config: OpenClawConfig; - resolvedConfig: OpenClawConfig; - providerOverrides: CommandSecretProviderOverrides | undefined; -}): void { - const configuredSearchProvider = - normalizeOptionalString(params.providerOverrides?.webSearch) ?? - normalizeOptionalString(params.config.tools?.web?.search?.provider); - const configuredFetchProvider = - normalizeOptionalString(params.providerOverrides?.webFetch) ?? - normalizeOptionalString(params.config.tools?.web?.fetch?.provider); - mirrorResolvedProviderCredentialToDirectPath({ - config: params.config, - resolvedConfig: params.resolvedConfig, - contract: "webSearchProviders", - provider: configuredSearchProvider, - directPathPrefix: "tools.web.search", - pluginConfigKey: "webSearch", - }); - mirrorResolvedProviderCredentialToDirectPath({ - config: params.config, - resolvedConfig: params.resolvedConfig, - contract: "webFetchProviders", - provider: configuredFetchProvider, - directPathPrefix: "tools.web.fetch", - pluginConfigKey: "webFetch", - }); - const webSearch = configuredSearchProvider; - if ( - webSearch && - webSearchProviderUsesSharedSearchCredential({ - config: params.config, - provider: webSearch, - }) && - getPath(params.config, ["tools", "web", "search", "apiKey"]) !== undefined - ) { - const pluginId = resolveManifestContractOwnerPluginId({ - contract: "webSearchProviders", - value: webSearch, - origin: "bundled", - config: params.config, - }); - const resolvedValue = pluginId - ? getPath(params.resolvedConfig, [ - "plugins", - "entries", - pluginId, - "config", - "webSearch", - "apiKey", - ]) - : undefined; - if (typeof resolvedValue === "string" && resolvedValue.length > 0) { - setPathExistingStrict( - params.resolvedConfig, - ["tools", "web", "search", "apiKey"], - resolvedValue, - ); - } - } -} - async function resolveForcedActiveCommandSecretTargets(params: { sourceConfig: OpenClawConfig; resolvedConfig: OpenClawConfig; @@ -474,11 +318,6 @@ async function resolveCommandSecretsFromSnapshot(params: { context, }); } - mirrorResolvedProviderCredentialToDirectPaths({ - config: sourceConfig, - resolvedConfig, - providerOverrides: params.providerOverrides, - }); await resolveForcedActiveCommandSecretTargets({ sourceConfig, resolvedConfig, diff --git a/src/secrets/runtime-fast-path.ts b/src/secrets/runtime-fast-path.ts index c0ee96b4e052..a99cb27ab2d9 100644 --- a/src/secrets/runtime-fast-path.ts +++ b/src/secrets/runtime-fast-path.ts @@ -170,7 +170,7 @@ function hasRuntimeWebToolConfigSurface(config: OpenClawConfig): boolean { (web as { fetch?: { enabled?: unknown } }).fetch?.enabled === false; if (web && typeof web === "object" && !Array.isArray(web)) { const webRecord = web as Record; - if ("search" in webRecord || "x_search" in webRecord) { + if ("search" in webRecord) { return true; } if ( diff --git a/src/secrets/runtime-legacy-x-search.test.ts b/src/secrets/runtime-legacy-x-search.test.ts deleted file mode 100644 index fb1744bb4c42..000000000000 --- a/src/secrets/runtime-legacy-x-search.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** Tests legacy X search secret resolution compatibility in the runtime matrix. */ -import { describe, expect, it, vi } from "vitest"; -import { - asConfig, - getResolvePluginWebSearchProvidersMock, - setupSecretsRuntimeSnapshotTestHooks, -} from "./runtime.test-support.ts"; - -vi.mock("../channels/plugins/bootstrap-registry.js", () => { - return { - getBootstrapChannelPlugin: (id: string) => - id === "telegram" - ? { - secrets: { - collectRuntimeConfigAssignments: () => {}, - }, - } - : undefined, - getBootstrapChannelSecrets: (id: string) => - id === "telegram" - ? { - collectRuntimeConfigAssignments: () => {}, - } - : undefined, - }; -}); - -const { prepareSecretsRuntimeSnapshot } = setupSecretsRuntimeSnapshotTestHooks(); -const EMPTY_LOADABLE_PLUGIN_ORIGINS = new Map(); - -describe("secrets runtime snapshot legacy x_search", () => { - it("keeps legacy x_search SecretRefs in place until doctor repairs them", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - x_search: { - apiKey: { source: "env", provider: "default", id: "X_SEARCH_KEY_REF" }, - enabled: true, - model: "grok-4-1-fast", - }, - }, - }, - }), - env: { - X_SEARCH_KEY_REF: "xai-runtime-key", - }, - includeAuthStoreRefs: false, - loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS, - }); - - expect((snapshot.config.tools?.web as Record | undefined)?.x_search).toEqual({ - apiKey: "xai-runtime-key", - enabled: true, - model: "grok-4-1-fast", - }); - expect(snapshot.config.plugins?.entries?.xai).toBeUndefined(); - expect(getResolvePluginWebSearchProvidersMock()).not.toHaveBeenCalled(); - }); - - it("still resolves legacy x_search auth in place even when unrelated legacy config is present", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - x_search: { - apiKey: { source: "env", provider: "default", id: "X_SEARCH_KEY_REF" }, - enabled: true, - }, - }, - }, - session: { - threadBindings: { - ttlHours: 24, - }, - }, - }), - env: { - X_SEARCH_KEY_REF: "xai-runtime-key-invalid-config", - }, - includeAuthStoreRefs: false, - loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS, - }); - - expect((snapshot.config.tools?.web as Record | undefined)?.x_search).toEqual({ - apiKey: "xai-runtime-key-invalid-config", - enabled: true, - }); - expect(snapshot.config.plugins?.entries?.xai).toBeUndefined(); - expect(getResolvePluginWebSearchProvidersMock()).not.toHaveBeenCalled(); - }); - - it("does not force-enable xai at runtime for knob-only x_search config", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - x_search: { - enabled: true, - model: "grok-4-1-fast", - }, - }, - }, - }), - env: {}, - includeAuthStoreRefs: false, - loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS, - }); - - expect((snapshot.config.tools?.web as Record | undefined)?.x_search).toEqual({ - enabled: true, - model: "grok-4-1-fast", - }); - expect(snapshot.config.plugins?.entries?.xai).toBeUndefined(); - expect(getResolvePluginWebSearchProvidersMock()).not.toHaveBeenCalled(); - }); -}); diff --git a/src/secrets/runtime-owner-contract.test.ts b/src/secrets/runtime-owner-contract.test.ts index e2d56b85e000..c28c83187d05 100644 --- a/src/secrets/runtime-owner-contract.test.ts +++ b/src/secrets/runtime-owner-contract.test.ts @@ -4,7 +4,7 @@ import { digestRuntimeWebOwnerContract } from "./runtime-owner-contract.js"; function digestWebContract(sourceConfig: OpenClawConfig): string { return digestRuntimeWebOwnerContract({ - scopePath: "tools.web.search.apiKey", + scopePath: "plugins.entries.web-search.config.webSearch.apiKey", configuredProvider: "brave", toolConfig: sourceConfig.tools?.web?.search, providers: [{ id: "brave", pluginId: "web-search" }], @@ -16,27 +16,20 @@ function digestWebContract(sourceConfig: OpenClawConfig): string { describe("runtime owner contracts", () => { it("canonicalizes equivalent web-owner SecretRef input forms", () => { const shorthand = { - tools: { web: { search: { provider: "brave", apiKey: "$BRAVE_API_KEY" } } }, plugins: { entries: { - "web-search": { config: { apiKey: "$BRAVE_API_KEY" } }, + "web-search": { config: { webSearch: { apiKey: "$BRAVE_API_KEY" } } }, }, }, } satisfies OpenClawConfig; const canonical = { - tools: { - web: { - search: { - provider: "brave", - apiKey: { source: "env", provider: "default", id: "BRAVE_API_KEY" }, - }, - }, - }, plugins: { entries: { "web-search": { config: { - apiKey: { source: "env", provider: "default", id: "BRAVE_API_KEY" }, + webSearch: { + apiKey: { source: "env", provider: "default", id: "BRAVE_API_KEY" }, + }, }, }, }, diff --git a/src/secrets/runtime-web-tools-manifest.runtime.ts b/src/secrets/runtime-web-tools-manifest.runtime.ts index e6553384b369..657c0093584d 100644 --- a/src/secrets/runtime-web-tools-manifest.runtime.ts +++ b/src/secrets/runtime-web-tools-manifest.runtime.ts @@ -5,5 +5,4 @@ export { resolveManifestContractOwnerPluginId, resolveManifestContractPluginIds, - resolveManifestContractPluginIdsByCompatibilityRuntimePath, } from "../plugins/plugin-registry.js"; diff --git a/src/secrets/runtime-web-tools.shared.ts b/src/secrets/runtime-web-tools.shared.ts index b7c3ca1b5abe..b98eee0e1df0 100644 --- a/src/secrets/runtime-web-tools.shared.ts +++ b/src/secrets/runtime-web-tools.shared.ts @@ -135,22 +135,6 @@ function pushInactiveProviderCredentialWarnings< } } -/** - * Ensures a nested config object exists and returns it for mutation. - */ -export function ensureObject( - target: Record, - key: string, -): Record { - const current = target[key]; - if (isRecord(current)) { - return current; - } - const next: Record = {}; - target[key] = next; - return next; -} - function normalizeKnownProvider( value: unknown, providers: Array<{ id: string }>, diff --git a/src/secrets/runtime-web-tools.test.ts b/src/secrets/runtime-web-tools.test.ts index a59289bffb78..b43fb23adab0 100644 --- a/src/secrets/runtime-web-tools.test.ts +++ b/src/secrets/runtime-web-tools.test.ts @@ -40,34 +40,30 @@ const { ), resolveBundledWebFetchProvidersFromPublicArtifactsMock: vi.fn(() => buildTestWebFetchProviders()), })); -const { - resolveManifestContractPluginIdsMock, - resolveManifestContractPluginIdsByCompatibilityRuntimePathMock, - resolveManifestContractOwnerPluginIdMock, -} = vi.hoisted(() => ({ - resolveManifestContractPluginIdsMock: vi.fn(() => [ - "brave", - "duckduckgo", - "google", - "moonshot", - "perplexity", - "xai", - ]), - resolveManifestContractPluginIdsByCompatibilityRuntimePathMock: vi.fn(() => ["brave"]), - resolveManifestContractOwnerPluginIdMock: vi.fn( - ({ value }: { value: string }) => - ( - ({ - brave: "brave", - firecrawl: "firecrawl", - gemini: "google", - grok: "xai", - kimi: "moonshot", - perplexity: "perplexity", - }) as Record - )[value], - ), -})); +const { resolveManifestContractPluginIdsMock, resolveManifestContractOwnerPluginIdMock } = + vi.hoisted(() => ({ + resolveManifestContractPluginIdsMock: vi.fn(() => [ + "brave", + "duckduckgo", + "google", + "moonshot", + "perplexity", + "xai", + ]), + resolveManifestContractOwnerPluginIdMock: vi.fn( + ({ value }: { value: string }) => + ( + ({ + brave: "brave", + firecrawl: "firecrawl", + gemini: "google", + grok: "xai", + kimi: "moonshot", + perplexity: "perplexity", + }) as Record + )[value], + ), + })); const { loadInstalledPluginIndexInstallRecordsSyncMock } = vi.hoisted(() => ({ loadInstalledPluginIndexInstallRecordsSyncMock: vi.fn(() => ({})), })); @@ -100,8 +96,6 @@ vi.mock("./runtime-web-tools-public-artifacts.runtime.js", () => ({ vi.mock("./runtime-web-tools-manifest.runtime.js", () => ({ resolveManifestContractPluginIds: resolveManifestContractPluginIdsMock, resolveManifestContractOwnerPluginId: resolveManifestContractOwnerPluginIdMock, - resolveManifestContractPluginIdsByCompatibilityRuntimePath: - resolveManifestContractPluginIdsByCompatibilityRuntimePathMock, })); vi.mock("../plugins/installed-plugin-index-records.js", () => ({ @@ -188,24 +182,9 @@ function createTestProvider(params: { entryConfig && typeof entryConfig === "object" ? (entryConfig as { webSearch?: { apiKey?: unknown } }).webSearch?.apiKey : undefined; - if (configuredValue !== undefined || params.provider !== "brave") { - return configuredValue; - } - const search = config?.tools?.web?.search; - return search && typeof search === "object" - ? (search as { apiKey?: unknown }).apiKey - : undefined; + return configuredValue; }, getConfiguredCredentialFallback: (config) => { - if (params.provider === "brave") { - const search = config?.tools?.web?.search; - return search && typeof search === "object" && "apiKey" in search - ? { - path: "tools.web.search.apiKey", - value: (search as { apiKey?: unknown }).apiKey, - } - : undefined; - } if (params.provider === "gemini") { const provider = config?.models?.providers?.google; return provider && typeof provider === "object" && "apiKey" in provider @@ -435,7 +414,6 @@ describe("runtime web tools resolution", () => { )[value], ); resolveManifestContractPluginIdsMock.mockClear(); - resolveManifestContractPluginIdsByCompatibilityRuntimePathMock.mockClear(); loadInstalledPluginIndexInstallRecordsSyncMock.mockReset(); loadInstalledPluginIndexInstallRecordsSyncMock.mockReturnValue({}); }); @@ -804,6 +782,118 @@ describe("runtime web tools resolution", () => { ]); }); + it("resolves search credentials through required external-provider accessors", async () => { + const pluginId = "external.search"; + const provider: PluginWebSearchProviderEntry = { + pluginId, + id: "external", + label: "External", + hint: "external provider", + envVars: ["EXTERNAL_SEARCH_API_KEY"], + placeholder: "external-...", + signupUrl: "https://example.com/search", + credentialPath: "tools.web.search.external.apiKey", + getCredentialValue: (searchConfig) => + (searchConfig?.external as { apiKey?: unknown } | undefined)?.apiKey, + setCredentialValue: (searchConfigTarget, value) => { + ensureRecord(searchConfigTarget, "external").apiKey = value; + }, + createTool: () => null, + }; + resolvePluginWebSearchProvidersMock.mockReturnValue([provider]); + loadInstalledPluginIndexInstallRecordsSyncMock.mockReturnValue({ + [pluginId]: { source: "npm", spec: "@openclaw/external-search" }, + }); + resolveManifestContractOwnerPluginIdMock.mockImplementation( + ({ value, origin }: { value: string; origin?: string }) => + value === "external" && origin !== "bundled" ? pluginId : undefined, + ); + + const { metadata, resolvedConfig } = await runRuntimeWebTools({ + config: asConfig({ + tools: { + web: { + search: { + enabled: true, + provider: "external", + external: { + apiKey: { + source: "env", + provider: "default", + id: "EXTERNAL_SEARCH_API_KEY", + }, + }, + }, + }, + }, + }), + env: { EXTERNAL_SEARCH_API_KEY: "test-token-placeholder" }, + }); + + expect(metadata.search.selectedProvider).toBe("external"); + expect(metadata.search.selectedProviderKeySource).toBe("secretRef"); + expect( + (resolvedConfig.tools?.web?.search as { external?: { apiKey?: unknown } } | undefined) + ?.external?.apiKey, + ).toBe("test-token-placeholder"); + }); + + it("resolves fetch credentials through required external-provider accessors", async () => { + const pluginId = "external.fetch"; + const provider: PluginWebFetchProviderEntry = { + pluginId, + id: "external-fetch", + label: "External Fetch", + hint: "external fetch provider", + envVars: ["EXTERNAL_FETCH_API_KEY"], + placeholder: "external-...", + signupUrl: "https://example.com/fetch", + credentialPath: "tools.web.fetch.external.apiKey", + getCredentialValue: (fetchConfig) => + (fetchConfig?.external as { apiKey?: unknown } | undefined)?.apiKey, + setCredentialValue: (fetchConfigTarget, value) => { + ensureRecord(fetchConfigTarget, "external").apiKey = value; + }, + createTool: () => null, + }; + resolvePluginWebFetchProvidersMock.mockReturnValueOnce([provider]); + loadInstalledPluginIndexInstallRecordsSyncMock.mockReturnValue({ + [pluginId]: { source: "npm", spec: "@openclaw/external-fetch" }, + }); + resolveManifestContractOwnerPluginIdMock.mockImplementation( + ({ value, origin }: { value: string; origin?: string }) => + value === "external-fetch" && origin !== "bundled" ? pluginId : undefined, + ); + + const { metadata, resolvedConfig } = await runRuntimeWebTools({ + config: asConfig({ + tools: { + web: { + fetch: { + enabled: true, + provider: "external-fetch", + external: { + apiKey: { + source: "env", + provider: "default", + id: "EXTERNAL_FETCH_API_KEY", + }, + }, + }, + }, + }, + }), + env: { EXTERNAL_FETCH_API_KEY: "test-token-placeholder" }, + }); + + expect(metadata.fetch.selectedProvider).toBe("external-fetch"); + expect(metadata.fetch.selectedProviderKeySource).toBe("secretRef"); + expect( + (resolvedConfig.tools?.web?.fetch as { external?: { apiKey?: unknown } } | undefined) + ?.external?.apiKey, + ).toBe("test-token-placeholder"); + }); + it("does not reuse a web credential after its plugin routing config changes", async () => { const pluginId = "external.search"; const provider: PluginWebSearchProviderEntry = { @@ -1395,54 +1485,6 @@ describe("runtime web tools resolution", () => { expect(resolvePluginWebSearchProvidersMock).not.toHaveBeenCalled(); }); - it("auto-detects Brave from legacy top-level web search apiKey", async () => { - const { metadata, resolvedConfig } = await runRuntimeWebTools({ - config: asConfig({ - tools: { - web: { - search: { - apiKey: { source: "env", provider: "default", id: "LEGACY_WEB_SEARCH_REF" }, - }, - }, - }, - }), - env: { - LEGACY_WEB_SEARCH_REF: "legacy-web-search-key", - }, - }); - - expect(metadata.search.providerSource).toBe("auto-detect"); - expect(metadata.search.selectedProvider).toBe("brave"); - expect(metadata.search.selectedProviderKeySource).toBe("secretRef"); - expect(readProviderKey(resolvedConfig, "brave")).toBe("legacy-web-search-key"); - expect(resolveManifestContractPluginIdsByCompatibilityRuntimePathMock).not.toHaveBeenCalled(); - expect(resolveBundledExplicitWebSearchProvidersFromPublicArtifactsMock).not.toHaveBeenCalled(); - expect(resolvePluginWebSearchProvidersMock).not.toHaveBeenCalled(); - }); - - it("prefers legacy top-level web search apiKey over provider env fallback", async () => { - const { metadata, resolvedConfig } = await runRuntimeWebTools({ - config: asConfig({ - tools: { - web: { - search: { - apiKey: { source: "env", provider: "default", id: "LEGACY_WEB_SEARCH_REF" }, - }, - }, - }, - }), - env: { - BRAVE_API_KEY: "ambient-brave-key", - LEGACY_WEB_SEARCH_REF: "legacy-web-search-key", - }, - }); - - expect(metadata.search.providerSource).toBe("auto-detect"); - expect(metadata.search.selectedProvider).toBe("brave"); - expect(metadata.search.selectedProviderKeySource).toBe("secretRef"); - expect(readProviderKey(resolvedConfig, "brave")).toBe("legacy-web-search-key"); - }); - it("does not resolve web fetch provider SecretRef when web fetch is inactive", async () => { const resolveSpy = vi.spyOn(secretResolve, "resolveSecretRefValues"); restoreResolveSecretRefValuesSpy = () => resolveSpy.mockRestore(); @@ -1520,7 +1562,6 @@ describe("runtime web tools resolution", () => { web: { search: { enabled: false, - apiKey: { source: "env", provider: "default", id: "DISABLED_WEB_SEARCH_API_KEY" }, }, }, }, @@ -1795,35 +1836,6 @@ describe("runtime web tools resolution", () => { ]); }); - it("attributes an invalid legacy x_search value to the Grok capability", async () => { - const refId = "X_SEARCH_KEY_REF"; - const resolveSpy = vi - .spyOn(secretResolve, "resolveSecretRefValues") - .mockResolvedValue(new Map([[`env:default:${refId}`, { value: "fixture-api-key" }]])); - restoreResolveSecretRefValuesSpy = () => resolveSpy.mockRestore(); - - const error = await runRuntimeWebTools({ - config: asConfig({ - tools: { - web: { - x_search: { - apiKey: { source: "env", provider: "default", id: refId }, - }, - }, - }, - }), - }).catch((caught: unknown) => caught); - - expect(listSecretResolutionErrorOwners(error)).toEqual([ - expect.objectContaining({ - ownerKind: "capability", - ownerId: "web-search:grok", - reason: "resolved secret value was invalid", - failureMatched: true, - }), - ]); - }); - it("rejects denied providers instead of restoring stale web credentials", async () => { const refId = "FIRECRAWL_API_KEY"; const error = await runRuntimeWebTools({ @@ -1932,36 +1944,6 @@ describe("runtime web tools resolution", () => { ).toBe("firecrawl-runtime-key"); }); - it("resolves legacy Firecrawl web fetch SecretRefs through the plugin-owned path", async () => { - const { metadata, resolvedConfig } = await runRuntimeWebTools({ - config: asConfig({ - tools: { - web: { - fetch: { - firecrawl: { - apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" }, - }, - }, - }, - }, - }), - env: { - FIRECRAWL_API_KEY: "firecrawl-legacy-key", - }, - }); - - expect(metadata.fetch.providerSource).toBe("auto-detect"); - expect(metadata.fetch.selectedProvider).toBe("firecrawl"); - expect(metadata.fetch.selectedProviderKeySource).toBe("env"); - expect( - ( - resolvedConfig.plugins?.entries?.firecrawl?.config as - | { webFetch?: { apiKey?: unknown } } - | undefined - )?.webFetch?.apiKey, - ).toBe("firecrawl-legacy-key"); - }); - it("fails fast when active web fetch provider SecretRef is unresolved with no fallback", async () => { const sourceConfig = asConfig({ plugins: { diff --git a/src/secrets/runtime-web-tools.ts b/src/secrets/runtime-web-tools.ts index e6bd25d0a06c..a896de72b989 100644 --- a/src/secrets/runtime-web-tools.ts +++ b/src/secrets/runtime-web-tools.ts @@ -34,13 +34,11 @@ import { classifySecretOwnerDegradationState, warnDegradedSecretOwner, } from "./runtime-owner-assignments.js"; -import { digestRuntimeWebOwnerContract } from "./runtime-owner-contract.js"; import { hasCredentialBearingObjectValue } from "./runtime-secret-scan.js"; import type { ResolverContext, SecretDefaults } from "./runtime-shared.js"; import { getActiveSecretsRuntimeSnapshot } from "./runtime-state.js"; import { runtimeWebSecretOwnerId } from "./runtime-web-secret-owner.js"; import { - ensureObject, hasConfiguredSecretRef, isRecord, resolveRuntimeWebProviderSurface, @@ -81,6 +79,16 @@ type SecretResolutionSource = | WebSearchCredentialResolutionSource | WebFetchCredentialResolutionSource; +function ensureConfigObject(target: Record, key: string): Record { + const current = target[key]; + if (isRecord(current)) { + return current; + } + const next: Record = {}; + target[key] = next; + return next; +} + type ResolvedRuntimeWebTools = { metadata: RuntimeWebToolsMetadata; degradedOwners: DegradedSecretOwner[]; @@ -547,9 +555,9 @@ function setResolvedWebSearchApiKey(params: { params.provider.setConfiguredCredentialValue(params.resolvedConfig, params.value); return; } - const tools = ensureObject(params.resolvedConfig as Record, "tools"); - const web = ensureObject(tools, "web"); - const search = ensureObject(web, "search"); + const tools = ensureConfigObject(params.resolvedConfig as Record, "tools"); + const web = ensureConfigObject(tools, "web"); + const search = ensureConfigObject(web, "search"); params.provider.setCredentialValue(search, params.value); } @@ -690,13 +698,13 @@ function setResolvedWebFetchApiKey(params: { provider: PluginWebFetchProviderEntry; value: string; }): void { - const tools = ensureObject(params.resolvedConfig as Record, "tools"); - const web = ensureObject(tools, "web"); - const fetch = ensureObject(web, "fetch"); if (params.provider.setConfiguredCredentialValue) { params.provider.setConfiguredCredentialValue(params.resolvedConfig, params.value); return; } + const tools = ensureConfigObject(params.resolvedConfig as Record, "tools"); + const web = ensureConfigObject(tools, "web"); + const fetch = ensureConfigObject(web, "fetch"); params.provider.setCredentialValue(fetch, params.value); } @@ -705,8 +713,10 @@ function readConfiguredFetchProviderCredential(params: { config: OpenClawConfig; fetch: Record | undefined; }): unknown { - const configuredValue = params.provider.getConfiguredCredentialValue?.(params.config); - return configuredValue ?? params.provider.getCredentialValue(params.fetch); + return ( + params.provider.getConfiguredCredentialValue?.(params.config) ?? + params.provider.getCredentialValue(params.fetch) + ); } function readConfiguredFetchProviderCredentialFallback(params: { @@ -750,10 +760,6 @@ export async function resolveRuntimeWebTools(params: { const sourceTools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined; const sourceWeb = isRecord(sourceTools?.web) ? sourceTools.web : undefined; - const resolvedTools = isRecord(params.resolvedConfig.tools) - ? params.resolvedConfig.tools - : undefined; - const resolvedWeb = isRecord(resolvedTools?.web) ? resolvedTools.web : undefined; let hasCustomWebSearchRisk: Promise | undefined; const getHasCustomWebSearchRisk = (): Promise => { hasCustomWebSearchRisk ??= hasCustomWebProviderPluginRisk({ @@ -772,42 +778,6 @@ export async function resolveRuntimeWebTools(params: { }); return hasCustomWebFetchRisk; }; - const legacyXSearchSource = isRecord(sourceWeb?.x_search) ? sourceWeb.x_search : undefined; - const legacyXSearchResolved = isRecord(resolvedWeb?.x_search) ? resolvedWeb.x_search : undefined; - - // Doctor owns the migration, but runtime still needs to resolve the legacy SecretRef surface - // so existing configs do not silently stop working before users repair them. - if ( - legacyXSearchSource && - legacyXSearchResolved && - Object.hasOwn(legacyXSearchSource, "apiKey") - ) { - const legacyXSearchSourceRecord = legacyXSearchSource as Record; - const legacyXSearchResolvedRecord = legacyXSearchResolved as Record; - const resolution = await resolveSecretInputWithEnvFallback({ - kind: "search", - providerId: "grok", - sourceConfig: params.sourceConfig, - context: params.context, - defaults, - value: legacyXSearchSourceRecord.apiKey, - path: "tools.web.x_search.apiKey", - envVars: ["XAI_API_KEY"], - providerFailuresByRefKey, - contractDigest: digestRuntimeWebOwnerContract({ - scopePath: "tools.web.x_search", - configuredProvider: "grok", - toolConfig: legacyXSearchSource, - providers: [{ id: "grok" }], - providerId: "grok", - sourceConfig: params.sourceConfig, - }), - }); - if (resolution.value) { - legacyXSearchResolvedRecord.apiKey = resolution.value; - } - } - const hasPluginWebSearchConfig = hasPluginScopedWebToolConfig(params.sourceConfig, "webSearch"); const hasPluginWebFetchConfig = hasPluginScopedWebToolConfig(params.sourceConfig, "webFetch"); if (!sourceWeb && !hasPluginWebSearchConfig && !hasPluginWebFetchConfig) { diff --git a/src/secrets/runtime.coverage.test.ts b/src/secrets/runtime.coverage.test.ts index f286f2f13e1a..ebd88a1503ef 100644 --- a/src/secrets/runtime.coverage.test.ts +++ b/src/secrets/runtime.coverage.test.ts @@ -11,7 +11,6 @@ import type { PluginWebSearchProviderEntry, } from "../plugins/types.js"; import { getPath, setPathCreateStrict } from "./path-utils.js"; -import { canonicalizeSecretTargetCoverageId } from "./target-registry-test-helpers.js"; const COVERAGE_WEB_PROVIDER_PLUGIN_IDS = vi.hoisted(() => ({ search: [ @@ -37,11 +36,25 @@ vi.mock("../plugins/installed-plugin-index-records.js", () => ({ loadInstalledPluginIndexInstallRecordsSync: () => ({}), })); -vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ - loadPluginMetadataSnapshot: () => { - throw new Error("runtime coverage expects bundled channel secret contracts"); - }, -})); +vi.mock("../plugins/plugin-metadata-snapshot.js", () => { + const plugins = COVERAGE_WEB_PROVIDER_PLUGIN_IDS.search.map((id) => ({ + id, + origin: "bundled", + contracts: { + webSearchProviders: [id], + ...(COVERAGE_WEB_PROVIDER_PLUGIN_IDS.fetch.includes(id) ? { webFetchProviders: [id] } : {}), + }, + })); + const createSnapshot = () => ({ + index: { diagnostics: [], plugins: [] }, + manifestRegistry: { diagnostics: [], plugins }, + plugins, + }); + return { + loadPluginMetadataSnapshot: createSnapshot, + resolvePluginMetadataSnapshot: createSnapshot, + }; +}); vi.mock("./runtime-web-tools-manifest.runtime.js", () => ({ resolveManifestContractPluginIds: ({ contract }: { contract: string }) => { @@ -286,10 +299,6 @@ const COVERAGE_LOADABLE_PLUGIN_ORIGINS = buildCoverageLoadablePluginOrigins(COVERAGE_REGISTRY_ENTRIES); const PLUGIN_OWNED_OPENCLAW_COVERAGE_EXCLUSIONS = new Set([ "channels.googlechat.accounts.*.serviceAccount", - // Doctor migrates legacy web search config into plugin-owned webSearch config. - "tools.web.search.apiKey", - "tools.web.search.*.apiKey", - "tools.web.fetch.firecrawl.apiKey", ]); let applyResolvedAssignments: typeof import("./runtime-shared.js").applyResolvedAssignments; @@ -355,8 +364,7 @@ function toConcretePathSegments(pathPattern: string, wildcardToken = "sample"): } function resolveCoverageEnvId(entry: SecretRegistryEntry, fallbackEnvId: string): string { - return entry.id === "plugins.entries.firecrawl.config.webFetch.apiKey" || - entry.id === "tools.web.fetch.firecrawl.apiKey" + return entry.id === "plugins.entries.firecrawl.config.webFetch.apiKey" ? "FIRECRAWL_API_KEY" : fallbackEnvId; } @@ -374,7 +382,7 @@ function toCoverageEnvRefId(prefix: string, id: string): string { } function resolveCoverageResolvedPath(entry: SecretRegistryEntry): string { - return canonicalizeSecretTargetCoverageId(entry.id); + return entry.id; } function resolveCoverageWildcardToken(index: number): string { diff --git a/src/secrets/runtime.test-support.ts b/src/secrets/runtime.test-support.ts index 9cfff60fc1a5..14e1bca27b54 100644 --- a/src/secrets/runtime.test-support.ts +++ b/src/secrets/runtime.test-support.ts @@ -111,10 +111,6 @@ export function resetPluginWebSearchProvidersMock() { resolvePluginWebSearchProvidersMock.mockReturnValue(buildTestWebSearchProviders()); } -export function getResolvePluginWebSearchProvidersMock() { - return resolvePluginWebSearchProvidersMock; -} - export function setupSecretsRuntimeSnapshotTestHooks(): { prepareSecretsRuntimeSnapshot: PrepareSecretsRuntimeSnapshot; } { diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts index 34f8b72393fb..a1fee021022a 100644 --- a/src/secrets/target-registry-data.ts +++ b/src/secrets/target-registry-data.ts @@ -458,40 +458,6 @@ const CORE_SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [ includeInAudit: true, providerIdPathSegmentIndex: 3, }, - { - id: "tools.web.search.apiKey", - targetType: "tools.web.search.apiKey", - configFile: "openclaw.json", - pathPattern: "tools.web.search.apiKey", - secretShape: SECRET_INPUT_SHAPE, - expectedResolvedValue: "string", - includeInPlan: true, - includeInConfigure: true, - includeInAudit: true, - }, - { - id: "tools.web.fetch.firecrawl.apiKey", - targetType: "tools.web.fetch.firecrawl.apiKey", - configFile: "openclaw.json", - pathPattern: "tools.web.fetch.firecrawl.apiKey", - secretShape: SECRET_INPUT_SHAPE, - expectedResolvedValue: "string", - includeInPlan: true, - includeInConfigure: true, - includeInAudit: true, - }, - { - id: "tools.web.search.*.apiKey", - targetType: "tools.web.search.*.apiKey", - configFile: "openclaw.json", - pathPattern: "tools.web.search.*.apiKey", - secretShape: SECRET_INPUT_SHAPE, - expectedResolvedValue: "string", - includeInPlan: true, - includeInConfigure: false, - includeInAudit: true, - providerIdPathSegmentIndex: 3, - }, ]; let cachedSecretTargetRegistry: SecretTargetRegistryEntry[] | null = null; diff --git a/src/secrets/target-registry-test-helpers.ts b/src/secrets/target-registry-test-helpers.ts deleted file mode 100644 index f4a602808f69..000000000000 --- a/src/secrets/target-registry-test-helpers.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Canonicalize legacy coverage IDs to current registry IDs so historical tests - * can compare the same target surface after web tools moved under plugins. - */ -export function canonicalizeSecretTargetCoverageId(id: string): string { - if (id === "tools.web.x_search.apiKey") { - return "plugins.entries.xai.config.webSearch.apiKey"; - } - if (id === "tools.web.fetch.firecrawl.apiKey") { - return "plugins.entries.firecrawl.config.webFetch.apiKey"; - } - return id; -} diff --git a/src/skills/runtime/refresh-state.ts b/src/skills/runtime/refresh-state.ts index ea93101ba099..cdd15120f6f4 100644 --- a/src/skills/runtime/refresh-state.ts +++ b/src/skills/runtime/refresh-state.ts @@ -1,5 +1,5 @@ // Skill refresh state types describe change notifications emitted by runtime reloads. -export type SkillsChangeEvent = { +type SkillsChangeEvent = { workspaceDir?: string; reason: "watch" | "watch-targets" | "manual" | "remote-node" | "config-change" | "workshop"; changedPath?: string; diff --git a/src/skills/runtime/refresh.test.ts b/src/skills/runtime/refresh.test.ts index 005ec8d47553..1d257717c9f2 100644 --- a/src/skills/runtime/refresh.test.ts +++ b/src/skills/runtime/refresh.test.ts @@ -7,9 +7,10 @@ import { bumpSkillsSnapshotVersion, getSkillsSnapshotVersion, shouldRefreshSnapshotForVersion, - type SkillsChangeEvent, } from "./refresh-state.js"; +type SkillsChangeEvent = NonNullable[0]>; + type WatchEvent = "add" | "addDir" | "all" | "change" | "unlink" | "unlinkDir" | "raw" | "error"; type WatchCallback = (...args: unknown[]) => void; diff --git a/src/status/status-plugin-health.test.ts b/src/status/status-plugin-health.test.ts index 9cf67ea92581..e023cb0527aa 100644 --- a/src/status/status-plugin-health.test.ts +++ b/src/status/status-plugin-health.test.ts @@ -132,8 +132,8 @@ describe("plugin health status formatting", () => { { pluginId: "compat-only", severity: "warn", - code: "legacy-before-agent-start", - message: "still uses legacy before_agent_start", + code: "hook-only", + message: "is hook-only", }, ], }, @@ -192,8 +192,8 @@ describe("plugin health status formatting", () => { expect(snapshot.compatibilityNotices).toContainEqual({ pluginId: "compat-only", severity: "warn", - code: "legacy-before-agent-start", - message: "still uses legacy before_agent_start", + code: "hook-only", + message: "is hook-only", }); }); diff --git a/src/version.test.ts b/src/version.test.ts index 1c0d80c4313f..77d26fee5867 100644 --- a/src/version.test.ts +++ b/src/version.test.ts @@ -33,7 +33,7 @@ async function withVersionFixtureDir(run: (root: string) => Promise): Prom return await run(await versionFixtureRoot.make("case")); } -async function ensureModuleFixture(root: string, relativePath = "dist/plugin-sdk/index.js") { +async function ensureModuleFixture(root: string, relativePath = "dist/plugin-sdk/core.js") { await fs.mkdir(path.dirname(path.join(root, relativePath)), { recursive: true }); return moduleUrlFrom(root, relativePath); } diff --git a/src/web-fetch/runtime.ts b/src/web-fetch/runtime.ts index 734e615397b7..48d87fac4111 100644 --- a/src/web-fetch/runtime.ts +++ b/src/web-fetch/runtime.ts @@ -68,7 +68,6 @@ function hasEntryCredential( | "envVars" | "getConfiguredCredentialFallback" | "getConfiguredCredentialValue" - | "getCredentialValue" | "requiresCredential" >, config: OpenClawConfig | undefined, @@ -78,9 +77,8 @@ function hasEntryCredential( provider, config, toolConfig: fetch as Record | undefined, - resolveRawValue: ({ provider: currentProvider, config: currentConfig, toolConfig }) => - currentProvider.getConfiguredCredentialValue?.(currentConfig) ?? - currentProvider.getCredentialValue(toolConfig), + resolveRawValue: ({ provider: currentProvider, config: currentConfig }) => + currentProvider.getConfiguredCredentialValue?.(currentConfig), resolveFallbackRawValue: ({ provider: currentProvider, config: currentConfig }) => currentProvider.getConfiguredCredentialFallback?.(currentConfig)?.value, resolveEnvValue: ({ provider: currentProvider }) => @@ -94,7 +92,6 @@ function hasAutoDetectCredential( | "envVars" | "getConfiguredCredentialFallback" | "getConfiguredCredentialValue" - | "getCredentialValue" | "requiresCredential" >, config: OpenClawConfig | undefined, diff --git a/src/web-search/runtime.ts b/src/web-search/runtime.ts index 7f66b0d5eec3..fd283098d0ba 100644 --- a/src/web-search/runtime.ts +++ b/src/web-search/runtime.ts @@ -77,7 +77,6 @@ function hasEntryCredential( | "envVars" | "getConfiguredCredentialValue" | "getConfiguredCredentialFallback" - | "getCredentialValue" | "requiresCredential" >, config: OpenClawConfig | undefined, @@ -112,7 +111,6 @@ function hasImplicitProviderSelectionSignal( | "envVars" | "getConfiguredCredentialValue" | "getConfiguredCredentialFallback" - | "getCredentialValue" | "requiresCredential" >, config: OpenClawConfig | undefined, diff --git a/src/wizard/setup.finalize.test.ts b/src/wizard/setup.finalize.test.ts index fd500db01ccb..665c8aeb3a4d 100644 --- a/src/wizard/setup.finalize.test.ts +++ b/src/wizard/setup.finalize.test.ts @@ -539,13 +539,6 @@ describe("finalizeSetupWizard", () => { }, }, }, - tools: { - web: { - search: { - apiKey: "", - }, - }, - }, }, workspaceDir: "/tmp", settings: { diff --git a/src/wizard/setup.migration-import.test.ts b/src/wizard/setup.migration-import.test.ts index 2cd0195af027..f31f6758c119 100644 --- a/src/wizard/setup.migration-import.test.ts +++ b/src/wizard/setup.migration-import.test.ts @@ -128,9 +128,7 @@ describe("setup migration import options", () => { it("offers official installable Codex when bundled plugins are unavailable", async () => { const previousDisableBundled = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; - const previousDisablePersisted = process.env.OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY; process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1"; - process.env.OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY = "1"; try { const options = await listSetupMigrationOptions({ baseConfig: {}, @@ -146,11 +144,6 @@ describe("setup migration import options", () => { } else { process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = previousDisableBundled; } - if (previousDisablePersisted === undefined) { - delete process.env.OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY; - } else { - process.env.OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY = previousDisablePersisted; - } } }); }); diff --git a/src/wizard/setup.test.ts b/src/wizard/setup.test.ts index 313a5df5f86a..ea67a78d3227 100644 --- a/src/wizard/setup.test.ts +++ b/src/wizard/setup.test.ts @@ -1525,11 +1525,11 @@ describe("runSetupWizard", () => { buildPluginCompatibilitySnapshotNotices.mockReturnValue([ { pluginId: "legacy-plugin", - code: "legacy-before-agent-start", - compatCode: "legacy-before-agent-start", - severity: "warn", + code: "hook-only", + compatCode: "hook-only-plugin-shape", + severity: "info", message: - "still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work.", + "is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet.", }, ]); readConfigFileSnapshot.mockResolvedValueOnce({ diff --git a/test/release-check.test.ts b/test/release-check.test.ts index 49942a833fb4..5864e41c5c7e 100644 --- a/test/release-check.test.ts +++ b/test/release-check.test.ts @@ -11,7 +11,8 @@ import { } from "../scripts/lib/package-dist-inventory.ts"; import { listPluginSdkDistArtifacts, - listPrivateLocalOnlyPluginSdkDistArtifacts, + listPackagedPrivatePluginSdkRuntimeArtifacts, + listUnpackagedPrivatePluginSdkDistArtifacts, } from "../scripts/lib/plugin-sdk-entries.mjs"; import { WORKSPACE_TEMPLATE_PACK_PATHS, @@ -58,8 +59,9 @@ function withProcessEnv(env: Record, callback: () => T): T { return withEnv(env, callback); } -const requiredPluginSdkPackPaths = [...listPluginSdkDistArtifacts(), "dist/plugin-sdk/compat.js"]; -const privateLocalOnlyPluginSdkPackPaths = listPrivateLocalOnlyPluginSdkDistArtifacts(); +const requiredPluginSdkPackPaths = listPluginSdkDistArtifacts(); +const packagedPrivatePluginSdkRuntimePaths = listPackagedPrivatePluginSdkRuntimeArtifacts(); +const unpackagedPrivatePluginSdkPaths = listUnpackagedPrivatePluginSdkDistArtifacts(); const requiredBundledPluginPackPaths = listBundledPluginPackArtifacts(); describe("collectAppcastSparkleVersionErrors", () => { @@ -529,6 +531,7 @@ describe("collectForbiddenPackPaths", () => { "dist/plugin-sdk/src/plugin-sdk/provider-entry.d.ts", ]), ).toEqual([ + "dist/plugin-sdk/index.d.ts", "dist/plugin-sdk/src/channels/plugins/types.public.d.ts", "dist/plugin-sdk/src/plugin-sdk/provider-entry.d.ts", ]); @@ -563,20 +566,30 @@ describe("collectForbiddenPackPaths", () => { expect(pkg.files).toContain("!dist/plugin-sdk/src/**"); }); - it("blocks private local-only plugin SDK artifacts from npm pack output", () => { + it("blocks private declarations and non-production SDK artifacts from npm pack output", () => { expect( - collectForbiddenPackPaths(["dist/index.js", ...privateLocalOnlyPluginSdkPackPaths]), - ).toEqual([...privateLocalOnlyPluginSdkPackPaths].toSorted()); + collectForbiddenPackPaths(["dist/index.js", ...unpackagedPrivatePluginSdkPaths]), + ).toEqual([...unpackagedPrivatePluginSdkPaths].toSorted()); }); - it("keeps private local-only plugin SDK artifacts excluded by package files", () => { + it("keeps private declarations and non-production SDK artifacts excluded by package files", () => { const pkg = JSON.parse(readFileSync("package.json", "utf8")) as { files?: string[] }; - for (const entry of privateLocalOnlyPluginSdkPackPaths) { + for (const entry of unpackagedPrivatePluginSdkPaths) { expect(pkg.files).toContain(`!${entry}`); } }); + it("keeps production-private plugin SDK runtime facades inside package files", () => { + const pkg = JSON.parse(readFileSync("package.json", "utf8")) as { files?: string[] }; + + expect(packagedPrivatePluginSdkRuntimePaths.length).toBeGreaterThan(0); + for (const entry of packagedPrivatePluginSdkRuntimePaths) { + expect(pkg.files).not.toContain(`!${entry}`); + expect(collectForbiddenPackPaths([entry])).toEqual([]); + } + }); + it("blocks legacy runtime dependency stamps from npm pack output", () => { expect( collectForbiddenPackPaths([ @@ -687,10 +700,6 @@ describe("collectMissingPackPaths", () => { const missing = collectMissingPackPaths([ "dist/index.js", "dist/entry.js", - "dist/plugin-sdk/compat.js", - "dist/plugin-sdk/index.js", - "dist/plugin-sdk/index.d.ts", - "dist/plugin-sdk/root-alias.cjs", "dist/build-info.json", ]); @@ -735,6 +744,7 @@ describe("collectMissingPackPaths", () => { "dist/extensions/acpx/mcp-proxy.mjs", ...requiredBundledPluginPackPaths, ...requiredPluginSdkPackPaths, + ...packagedPrivatePluginSdkRuntimePaths, ...WORKSPACE_TEMPLATE_PACK_PATHS, "scripts/npm-runner.mjs", "scripts/prepare-git-hooks.mjs", @@ -745,7 +755,6 @@ describe("collectMissingPackPaths", () => { "scripts/lib/recommended-tool-installs.json", "scripts/lib/package-dist-imports.mjs", "scripts/postinstall-bundled-plugins.mjs", - "dist/plugin-sdk/root-alias.cjs", "dist/agents/compaction-planning.worker.js", "dist/agents/model-provider-auth.worker.js", "dist/audit/audit-event-writer.worker.js", @@ -800,34 +809,6 @@ describe("collectMissingPackPaths", () => { } }); - it("rejects packed plugin SDK root aliases that depend on minified export letters", () => { - const root = mkdtempSync(join(tmpdir(), "release-check-packed-root-alias-")); - try { - const packageRoot = join(root, "openclaw"); - const pluginSdkDir = join(packageRoot, "dist", "plugin-sdk"); - mkdirSync(pluginSdkDir, { recursive: true }); - writeFileSync( - join(packageRoot, "package.json"), - `${JSON.stringify({ name: "openclaw", version: "2026.5.14-beta.3", dependencies: {} })}\n`, - ); - writeFileSync( - join(pluginSdkDir, "root-alias.cjs"), - "module.exports = { onDiagnosticEvent: mod.r };\n", - ); - - expect( - collectPackedInstalledPackageVerificationErrors({ - expectedVersion: "2026.5.14-beta.3", - packageRoot, - }), - ).toContain( - "installed package dist/plugin-sdk/root-alias.cjs depends on a single-letter bundled export alias.", - ); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }); - it("requires bundled plugin runtime sidecars that dynamic plugin boundaries resolve at runtime", () => { expect(requiredBundledPluginPackPaths).not.toContain( bundledDistPluginFile("slack", "runtime-api.js"), @@ -892,8 +873,8 @@ describe("createPackedPluginSdkTypescriptSmokeProject", () => { expect(packageJson.dependencies?.["@openclaw/ai"]).toBe("file:/tmp/openclaw-ai.tgz"); expect(tsconfig.compilerOptions?.skipLibCheck).toBe(true); expect(source).toBe(fixtureSource); - expect(source).toContain('"openclaw/plugin-sdk"'); - expect(source).toContain('"openclaw/plugin-sdk/provider-entry"'); + expect(source).toContain('"openclaw/plugin-sdk/core"'); + expect(source).toContain('"openclaw/plugin-sdk/plugin-entry"'); expect(source).toContain('"openclaw/plugin-sdk/channel-entry-contract"'); expect(source).toContain('"openclaw/plugin-sdk/config-contracts"'); expect(source).toContain('"openclaw/plugin-sdk/runtime-env"'); diff --git a/test/scripts/build-all.test.ts b/test/scripts/build-all.test.ts index 139e8625392d..99c355f5d401 100644 --- a/test/scripts/build-all.test.ts +++ b/test/scripts/build-all.test.ts @@ -695,7 +695,6 @@ describe("resolveBuildAllSteps", () => { expect(step.cache?.inputs).not.toContain("src/plugin-sdk"); expect(step.cache?.outputs).toEqual( expect.arrayContaining([ - "dist/plugin-sdk/webhook-path.js", "dist/plugin-sdk/.boundary-entry-shims.stamp", "packages/plugin-sdk/dist/src/plugin-sdk/provider-entry.d.ts", ]), diff --git a/test/scripts/check-deprecated-api-usage.test.ts b/test/scripts/check-deprecated-api-usage.test.ts index 1175aa559cc2..bd2b5aaabb8c 100644 --- a/test/scripts/check-deprecated-api-usage.test.ts +++ b/test/scripts/check-deprecated-api-usage.test.ts @@ -44,14 +44,15 @@ describe("scripts/check-deprecated-api-usage", () => { } }); - it("keeps root and private compatibility aliases explicit", () => { - expect(buildDeprecatedPluginSdkModuleSpecifiers()).toEqual( - expect.arrayContaining([ - "openclaw/plugin-sdk", - "openclaw/plugin-sdk/agent-dir-compat", - "openclaw/plugin-sdk/test-utils", - ]), - ); + it("keeps removed root and private compatibility aliases out of the inventory", () => { + const specifiers = buildDeprecatedPluginSdkModuleSpecifiers(); + for (const removedSpecifier of [ + "openclaw/plugin-sdk", + "openclaw/plugin-sdk/agent-dir-compat", + "openclaw/plugin-sdk/test-utils", + ]) { + expect(specifiers).not.toContain(removedSpecifier); + } }); it("bans the scoped @openclaw/plugin-sdk spelling of every deprecated specifier", () => { @@ -70,9 +71,7 @@ describe("scripts/check-deprecated-api-usage", () => { ); for (const facade of [ - "src/plugin-sdk/channel-envelope", "src/plugin-sdk/channel-message", - "src/plugin-sdk/channel-message-runtime", "src/plugin-sdk/channel-reply-pipeline", "src/plugin-sdk/inbound-reply-dispatch", "src/channels/message/inbound-reply-dispatch", @@ -97,9 +96,7 @@ describe("scripts/check-deprecated-api-usage", () => { 'import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";', 'export { runInboundReplyTurn } from "./message/inbound-reply-dispatch.js";', 'const facade = await import ("../plugin-sdk/channel-message.js", { with: {} });', - 'import { formatInboundEnvelope } from "@openclaw/plugin-sdk/channel-envelope";', ].join("\n"), - "src/plugin-sdk/channel-message-runtime.ts": 'export * from "./channel-message.js";', }); expect(result.status).toBe(1); @@ -108,17 +105,10 @@ describe("scripts/check-deprecated-api-usage", () => { ); expect(result.stderr).toContain("src/channels/probe.ts:2: ./message/inbound-reply-dispatch.js"); expect(result.stderr).toContain("src/channels/probe.ts:3: ../plugin-sdk/channel-message.js"); - expect(result.stderr).toContain( - "src/channels/probe.ts:4: @openclaw/plugin-sdk/channel-envelope", - ); - expect(result.stderr).toContain( - "src/plugin-sdk/channel-message-runtime.ts:1: ./channel-message.js", - ); }); it("allows canonical compat re-exports and test files", () => { const result = runFacadeImportRule({ - "src/plugin-sdk/channel-message-runtime.ts": 'export * from "./channel-outbound.js";', "src/plugin-sdk/channel-inbound.ts": 'export { runChannelInboundEvent } from "../channels/message/inbound-reply-dispatch.js";', "src/plugin-sdk/channel-message.test.ts": diff --git a/test/scripts/ci-changed-node-test-plan.test.ts b/test/scripts/ci-changed-node-test-plan.test.ts index b088eae1e303..4529499ab9f2 100644 --- a/test/scripts/ci-changed-node-test-plan.test.ts +++ b/test/scripts/ci-changed-node-test-plan.test.ts @@ -160,7 +160,7 @@ describe("CI changed Node test plan", () => { }); it("fails safe when public SDK changes affect extension imports", () => { - expect(createChangedNodeTestShards(["src/plugin-sdk/index.ts"])).toBeNull(); + expect(createChangedNodeTestShards(["src/plugin-sdk/core.ts"])).toBeNull(); }); it("fails safe when a core change reaches package consumers through the public SDK", () => { diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index d0d4ac63326b..f731e80f6921 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -3698,9 +3698,7 @@ heartbeat_elapsed="\${BASH_REMATCH[1]}" it("keeps private bundled plugins discoverable in the functional Docker E2E image", () => { const dockerfile = readFileSync("scripts/e2e/Dockerfile", "utf8"); - expect(dockerfile).toContain( - "OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION=1 node /app/scripts/postinstall-bundled-plugins.mjs", - ); + expect(dockerfile).toContain("node /app/scripts/postinstall-bundled-plugins.mjs"); }); it("keeps onboarding Docker E2E resource-guarded", () => { diff --git a/test/scripts/postinstall-bundled-plugins.test.ts b/test/scripts/postinstall-bundled-plugins.test.ts index 2a65d78ebb4e..d706f1790c6f 100644 --- a/test/scripts/postinstall-bundled-plugins.test.ts +++ b/test/scripts/postinstall-bundled-plugins.test.ts @@ -482,29 +482,6 @@ describe("bundled plugin postinstall", () => { ); }); - it("surfaces deprecated plugin registry migration break-glass warnings", async () => { - const warn = vi.fn(); - const migratePluginRegistryForInstall = vi.fn(async () => ({ - status: "skip-existing", - migrated: false, - preflight: { - deprecationWarnings: ["OPENCLAW_FORCE_PLUGIN_REGISTRY_MIGRATION is deprecated"], - }, - })); - const importModule = vi.fn(async () => ({ migratePluginRegistryForInstall })); - - await runPluginRegistryPostinstallMigration({ - packageRoot: "/pkg", - existsSync: vi.fn(() => true), - importModule, - log: { log: vi.fn(), warn }, - }); - - expect(warn).toHaveBeenCalledWith( - "[postinstall] OPENCLAW_FORCE_PLUGIN_REGISTRY_MIGRATION is deprecated", - ); - }); - it("keeps plugin registry postinstall migration non-fatal when dist entries are unavailable", async () => { const warn = vi.fn(); @@ -521,54 +498,6 @@ describe("bundled plugin postinstall", () => { expect(warn).not.toHaveBeenCalled(); }); - it("honors plugin registry postinstall migration disable env", async () => { - const importModule = vi.fn(async () => { - throw new Error("dist migration module should not import when migration is disabled"); - }); - await expect( - runPluginRegistryPostinstallMigration({ - packageRoot: "/pkg", - env: { OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION: "1" }, - existsSync: vi.fn(() => true), - importModule, - log: { log: vi.fn(), warn: vi.fn() }, - }), - ).resolves.toEqual({ - status: "disabled", - migrated: false, - reason: "disabled-env", - }); - expect(importModule).not.toHaveBeenCalled(); - }); - - it("does not disable plugin registry migration for falsey env flag strings", async () => { - const migratePluginRegistryForInstall = vi.fn(async () => ({ - status: "skip-existing", - migrated: false, - preflight: {}, - })); - const importModule = vi.fn(async () => ({ migratePluginRegistryForInstall })); - - await expect( - runPluginRegistryPostinstallMigration({ - packageRoot: "/pkg", - env: { OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION: "0" }, - existsSync: vi.fn(() => true), - importModule, - log: { log: vi.fn(), warn: vi.fn() }, - }), - ).resolves.toEqual({ - status: "skip-existing", - migrated: false, - preflight: {}, - }); - expect(importModule).toHaveBeenCalledOnce(); - expect(migratePluginRegistryForInstall).toHaveBeenCalledWith({ - env: { OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION: "0" }, - packageRoot: "/pkg", - }); - }); - it("prunes stale dist files from packaged installs", async () => { const packageRoot = await createTempDirAsync("openclaw-packaged-install-"); const currentFile = path.join(packageRoot, "dist", "channel-BOa4MfoC.js"); diff --git a/test/scripts/prepare-extension-package-boundary-artifacts.test.ts b/test/scripts/prepare-extension-package-boundary-artifacts.test.ts index b7557395882d..71f8325a1f76 100644 --- a/test/scripts/prepare-extension-package-boundary-artifacts.test.ts +++ b/test/scripts/prepare-extension-package-boundary-artifacts.test.ts @@ -564,7 +564,7 @@ describe("prepare-extension-package-boundary-artifacts", () => { tempRoots.add(rootDir); const inputPath = path.join(rootDir, "scripts", "write-plugin-sdk-entry-dts.ts"); const stampPath = path.join(rootDir, "dist", "plugin-sdk", ".boundary-entry-shims.stamp"); - const rootDtsPath = path.join(rootDir, "dist", "plugin-sdk", "index.d.ts"); + const rootDtsPath = path.join(rootDir, "dist", "plugin-sdk", "core.d.ts"); const packageDtsPath = path.join( rootDir, "packages", @@ -572,7 +572,7 @@ describe("prepare-extension-package-boundary-artifacts", () => { "dist", "src", "plugin-sdk", - "index.d.ts", + "core.d.ts", ); fs.mkdirSync(path.dirname(inputPath), { recursive: true }); @@ -595,8 +595,8 @@ describe("prepare-extension-package-boundary-artifacts", () => { inputPaths: ["scripts/write-plugin-sdk-entry-dts.ts"], outputPaths: [ "dist/plugin-sdk/.boundary-entry-shims.stamp", - "dist/plugin-sdk/index.d.ts", - "packages/plugin-sdk/dist/src/plugin-sdk/index.d.ts", + "dist/plugin-sdk/core.d.ts", + "packages/plugin-sdk/dist/src/plugin-sdk/core.d.ts", ], }), ).toBe(true); @@ -609,17 +609,30 @@ describe("prepare-extension-package-boundary-artifacts", () => { inputPaths: ["scripts/write-plugin-sdk-entry-dts.ts"], outputPaths: [ "dist/plugin-sdk/.boundary-entry-shims.stamp", - "dist/plugin-sdk/index.d.ts", - "packages/plugin-sdk/dist/src/plugin-sdk/index.d.ts", + "dist/plugin-sdk/core.d.ts", + "packages/plugin-sdk/dist/src/plugin-sdk/core.d.ts", ], }), ).toBe(false); - expect(resolveBoundaryEntryShimRequiredOutputs({})).toContain("dist/plugin-sdk/index.d.ts"); + expect(resolveBoundaryEntryShimRequiredOutputs({})).toContain("dist/plugin-sdk/core.d.ts"); expect(resolveBoundaryEntryShimRequiredOutputs({})).toContain( - "packages/plugin-sdk/dist/src/plugin-sdk/index.d.ts", + "packages/plugin-sdk/dist/src/plugin-sdk/core.d.ts", ); }); + it("keeps bundled-private runtime shims in production while gating QA helpers", () => { + const productionOutputs = resolveBoundaryEntryShimRequiredOutputs({}); + const privateQaOutputs = resolveBoundaryEntryShimRequiredOutputs({ + OPENCLAW_BUILD_PRIVATE_QA: "1", + }); + + expect(productionOutputs).toContain("dist/plugin-sdk/provider-auth-runtime.d.ts"); + expect(productionOutputs).not.toContain("dist/plugin-sdk/codex-native-task-runtime.d.ts"); + expect(productionOutputs).not.toContain("dist/plugin-sdk/test-fixtures.d.ts"); + expect(privateQaOutputs).toContain("dist/plugin-sdk/provider-auth-runtime.d.ts"); + expect(privateQaOutputs).toContain("dist/plugin-sdk/test-fixtures.d.ts"); + }); + it("parses prep mode and rejects unknown values", () => { expect(parseMode([])).toBe("all"); expect(parseMode(["--mode=package-boundary"])).toBe("package-boundary"); diff --git a/test/scripts/runtime-postbuild.test.ts b/test/scripts/runtime-postbuild.test.ts index 294428019823..290b8fab9c91 100644 --- a/test/scripts/runtime-postbuild.test.ts +++ b/test/scripts/runtime-postbuild.test.ts @@ -187,12 +187,6 @@ describe("runtime postbuild static assets", () => { const distAsset = "dist/extensions/diffs/assets/viewer-runtime.js"; const runtimeAsset = "dist-runtime/extensions/diffs/assets/viewer-runtime.js"; - await fs.mkdir(path.join(rootDir, "src", "plugin-sdk"), { recursive: true }); - await fs.writeFile( - path.join(rootDir, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf8", - ); await fs.mkdir(path.join(rootDir, "extensions", "diffs", "assets"), { recursive: true }); await fs.writeFile( path.join(rootDir, "extensions", "diffs", "package.json"), @@ -235,12 +229,6 @@ describe("runtime postbuild static assets", () => { const distPluginDir = path.join(rootDir, "dist", "extensions", "diffs"); const runtimeAsset = path.join(rootDir, "dist-runtime", "extensions", "diffs", output); - await fs.mkdir(path.join(rootDir, "src", "plugin-sdk"), { recursive: true }); - await fs.writeFile( - path.join(rootDir, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf8", - ); await fs.mkdir(path.join(distPluginDir, "assets"), { recursive: true }); await fs.writeFile(path.join(distPluginDir, "index.js"), "export default {};\n", "utf8"); await fs.writeFile( @@ -278,12 +266,6 @@ describe("runtime postbuild static assets", () => { const warn = vi.fn(); const output = "assets/viewer-runtime.js"; - await fs.mkdir(path.join(rootDir, "src", "plugin-sdk"), { recursive: true }); - await fs.writeFile( - path.join(rootDir, "src", "plugin-sdk", "root-alias.cjs"), - "module.exports = {};\n", - "utf8", - ); await fs.mkdir(path.join(rootDir, "extensions", "diffs"), { recursive: true }); await fs.writeFile( path.join(rootDir, "extensions", "diffs", "package.json"), diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index 879257359efa..7125b0e7338d 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -1831,7 +1831,6 @@ describe("scripts/test-projects changed-target routing", () => { "scripts/lib/plugin-sdk-deprecated-barrel-subpaths.json", [ "src/plugins/contracts/plugin-sdk-index.bundle.test.ts", - "src/plugins/contracts/plugin-sdk-index.test.ts", "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/contracts/extension-package-project-boundaries.test.ts", @@ -1856,7 +1855,6 @@ describe("scripts/test-projects changed-target routing", () => { "scripts/lib/plugin-sdk-entrypoints.json", [ "src/plugins/contracts/plugin-sdk-index.bundle.test.ts", - "src/plugins/contracts/plugin-sdk-index.test.ts", "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/contracts/extension-package-project-boundaries.test.ts", @@ -1872,7 +1870,6 @@ describe("scripts/test-projects changed-target routing", () => { "scripts/lib/plugin-sdk-entries.mjs", [ "src/plugins/contracts/plugin-sdk-index.bundle.test.ts", - "src/plugins/contracts/plugin-sdk-index.test.ts", "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/contracts/extension-package-project-boundaries.test.ts", @@ -1888,7 +1885,6 @@ describe("scripts/test-projects changed-target routing", () => { "scripts/lib/plugin-sdk-private-local-only-subpaths.json", [ "src/plugins/contracts/plugin-sdk-index.bundle.test.ts", - "src/plugins/contracts/plugin-sdk-index.test.ts", "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/contracts/extension-package-project-boundaries.test.ts", diff --git a/test/vitest/vitest.shared.config.ts b/test/vitest/vitest.shared.config.ts index b6e63f43422c..f15618151453 100644 --- a/test/vitest/vitest.shared.config.ts +++ b/test/vitest/vitest.shared.config.ts @@ -173,10 +173,6 @@ export const sharedVitestConfig = { "discord-api-types-payloads-v10-runtime.ts", ), }, - { - find: "openclaw/extension-api", - replacement: path.join(repoRoot, "src", "extensionAPI.ts"), - }, { find: "@openclaw/qa-channel/api.js", replacement: path.join(repoRoot, "extensions", "qa-channel", "api.ts"), @@ -508,10 +504,6 @@ export const sharedVitestConfig = { find: `@openclaw/plugin-sdk/${subpath}`, replacement: path.join(repoRoot, "packages", "plugin-sdk", "src", `${subpath}.ts`), })), - { - find: "openclaw/plugin-sdk", - replacement: path.join(repoRoot, "src", "plugin-sdk", "index.ts"), - }, ], }, test: { diff --git a/tsconfig.json b/tsconfig.json index cc80f03f7560..cf0ff7060418 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,8 +25,6 @@ "useDefineForClassFields": false, "verbatimModuleSyntax": true, "paths": { - "openclaw/extension-api": ["./src/extensionAPI.ts"], - "openclaw/plugin-sdk": ["./src/plugin-sdk/index.ts"], "openclaw/plugin-sdk/*": ["./src/plugin-sdk/*.ts"], "@openclaw/ai": ["./packages/ai/src/index.ts"], "@openclaw/ai/diagnostics": ["./packages/ai/src/utils/diagnostics.ts"], @@ -246,7 +244,6 @@ "@openclaw/speech-core/voice-models": ["./packages/speech-core/voice-models.ts"], "@openclaw/speech-core/*": ["./packages/speech-core/*"], "@openclaw/sdk": ["./packages/sdk/src/index.ts"], - "@openclaw/plugin-sdk": ["./src/plugin-sdk/index.ts"], "@openclaw/plugin-sdk/*": ["./src/plugin-sdk/*.ts"], "openclaw/plugin-sdk/account-id": ["./src/plugin-sdk/account-id.ts"], "@openclaw/*": ["./extensions/*"] diff --git a/tsdown.config.ts b/tsdown.config.ts index 9af230fb625a..dbe8f5728b55 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -9,7 +9,7 @@ import { import { buildPluginSdkEntrySources, pluginSdkEntrypoints, - publicPluginSdkEntrypoints, + productionPluginSdkEntrypoints, } from "./scripts/lib/plugin-sdk-entries.mjs"; import { TSDOWN_PACKAGE_CONFIG_GROUP, @@ -165,9 +165,9 @@ function nodeWorkspacePackageBuildConfig(packageDir: string, config: UserConfig const bundledPluginBuildEntries = collectBundledPluginBuildEntries(); const shouldBuildPrivateQaEntries = process.env.OPENCLAW_BUILD_PRIVATE_QA === "1"; -const productionPluginSdkEntrypoints = shouldBuildPrivateQaEntries +const selectedPluginSdkEntrypoints = shouldBuildPrivateQaEntries ? pluginSdkEntrypoints - : publicPluginSdkEntrypoints; + : productionPluginSdkEntrypoints; function buildBundledHookEntries(): Record { const hooksRoot = path.join(process.cwd(), "src", "hooks", "bundled"); @@ -303,7 +303,6 @@ function buildCoreDistEntries(): Record { "plugins/loader": "src/plugins/loader.ts", "plugins/sdk-alias": "src/plugins/sdk-alias.ts", "facade-activation-check.runtime": "src/plugin-sdk/facade-activation-check.runtime.ts", - extensionAPI: "src/extensionAPI.ts", "infra/warning-filter": "src/infra/warning-filter.ts", "telegram-ingress-worker.runtime": bundledPluginFile( "telegram", @@ -502,12 +501,10 @@ function buildUnifiedDistEntries(): Record { source, ]), ), - // Internal compat artifact for the root-alias.cjs lazy loader. - "plugin-sdk/compat": "src/plugin-sdk/compat.ts", // Private bundled Codex helper for app-server user MCP config projection. "plugin-sdk/codex-mcp-projection": "src/plugin-sdk/codex-mcp-projection.ts", ...Object.fromEntries( - Object.entries(buildPluginSdkEntrySources(productionPluginSdkEntrypoints)).map( + Object.entries(buildPluginSdkEntrySources(selectedPluginSdkEntrypoints)).map( ([entry, source]) => [`plugin-sdk/${entry}`, source], ), ),