From b40a8da70e358cb96dd59d51fd18186be5d094bf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 22 Jul 2026 01:15:58 -0700 Subject: [PATCH] refactor(channels): trim deprecated setup-input fields and legacy promotion keys to published readers (#112587) * refactor(channels): trim deprecated setup-input fields and legacy promotion keys to published readers * ci: retrigger after dropped ready event * fix(channels): update setup-input guardrails and dead code for the trimmed tier --- docs/plugins/sdk-migration.md | 21 ++++- docs/plugins/sdk-setup.md | 10 +- extensions/clickclack/src/setup-core.test.ts | 31 ++++--- .../matrix/src/channel.directory.test.ts | 9 +- extensions/matrix/src/setup-core.test.ts | 17 ++-- extensions/raft/src/accounts.test.ts | 5 +- .../plugins/setup-promotion-helpers.test.ts | 39 ++------ .../plugins/setup-promotion-helpers.ts | 44 ++------- src/channels/plugins/setup-wizard-types.ts | 15 ++- src/channels/plugins/types.core.ts | 93 ++++++------------- src/commands/channels.add.test.ts | 15 ++- ....adds-non-default-telegram-account.test.ts | 6 +- src/commands/doctor-config-flow.test.ts | 21 +---- src/plugin-sdk/channel-setup.test.ts | 5 +- .../test-helpers/channel-contract-suites.ts | 11 ++- .../config-footprint-guardrails.test.ts | 13 ++- 16 files changed, 142 insertions(+), 213 deletions(-) diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index ae74fa5af504..9ecbbb00c6be 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -96,13 +96,28 @@ permanently. Channel-specific fields remain typed in a deprecated compatibility tier so existing external plugins still compile while plugin authors move those fields into plugin-local setup input types. +OpenClaw does not ship major releases. A registry sweep on 2026-07-22 inspected +426 published out-of-tree channel plugins and removed 21 fields with no readers. +The 22 retained fields each have a known published reader. Each further field is +deleted as soon as no published plugin reads it; the retained set shrinks as +plugin authors migrate to plugin-local setup input types. + +The same sweep removed 23 legacy undeclared-adapter promotion keys with no +published dependents. Six common keys and the setup-only `rooms` key remain. +That set also shrinks as published plugins declare `singleAccountKeysToMove`. + The shared type has no index signature. Plugin-owned keys can still be present on runtime input objects; declare them in a plugin-local intersection or narrow them through the owning plugin's setup schema. -| `code` | `owner` | `replacement` | `removeAfter` | Removal condition | -| --------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `plugin-sdk-channel-setup-input-fields` | `channel` | Intersect `ChannelSetupInput` with a plugin-local type that declares the owning channel's fields | `next-plugin-sdk-major` | Remove at the first Plugin SDK major after the bundled-plugin migration in [#112238](https://github.com/openclaw/openclaw/issues/112238) | +| `code` | `owner` | `replacement` | Removal condition | +| --------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- | +| `plugin-sdk-channel-setup-input-fields` | `channel` | Intersect `ChannelSetupInput` with a plugin-local type that declares the owning channel's fields | Delete a field when the published-plugin registry sweep has no reader | + +The legacy undeclared-adapter promotion tier follows the same reader-driven +policy. Declare `singleAccountKeysToMove`, including an empty array when the +plugin needs no extra promotion keys, so the shared fallback can be retired one +key at a time. This is a source/type compatibility record only. It has no runtime adapter or compatibility-registry entry because runtime setup input objects and setup diff --git a/docs/plugins/sdk-setup.md b/docs/plugins/sdk-setup.md index cb83948a0f8b..e31a637d4a9d 100644 --- a/docs/plugins/sdk-setup.md +++ b/docs/plugins/sdk-setup.md @@ -387,9 +387,11 @@ export const acmeSetupAdapter: ChannelSetupAdapter = { Channel-specific fields that were previously declared directly on `ChannelSetupInput` remain temporarily typed for external source compatibility. -They are deprecated and will be removed at the next Plugin SDK major after -[#112238](https://github.com/openclaw/openclaw/issues/112238). New and bundled -plugins must not rely on that tier; declare the fields they own locally. +They are deprecated. A 2026-07-22 registry sweep of 426 published out-of-tree +channel plugins removed 21 fields with no readers and retained 22 with known +readers. Each retained field is deleted as soon as no published plugin reads it; +no version boundary is required. New and bundled plugins must not rely on this +tier; declare the fields they own locally. ### Channel-owned single-account promotion @@ -401,7 +403,7 @@ Every channel plugin can extend or narrow that promotion through its setup adapt - `namedAccountPromotionKeys`: when named accounts already exist, only these keys move into the promoted account; shared policy/delivery keys stay at the channel root - `resolveSingleAccountPromotionTarget(...)`: choose which existing account receives promoted values -The presence of `singleAccountKeysToMove` marks the promotion contract complete. Declare the field even when it is an empty array to opt out of legacy key promotion. Adapters that omit the field retain the pre-declaration promotion tiers for compatibility with already-published plugins; this compatibility tier is scheduled for removal at the next Plugin SDK major after the migration documented in [#112238](https://github.com/openclaw/openclaw/issues/112238). +The presence of `singleAccountKeysToMove` marks the promotion contract complete. Declare the field even when it is an empty array to opt out of legacy key promotion. Adapters that omit the field retain a reader-backed pre-declaration promotion tier for already-published plugins. The 2026-07-22 registry sweep removed 23 keys with no published dependents and retained six common keys plus the setup-only `rooms` key. Each retained key is deleted as soon as its published readers migrate to declarations; no version boundary is required. Declare `openclaw.setupFeatures.configPromotion: true` in the plugin package manifest when doctor must load these declarations from the lightweight bundled setup artifact. The setup-only plugin surface and the full channel plugin must expose the same declarations. diff --git a/extensions/clickclack/src/setup-core.test.ts b/extensions/clickclack/src/setup-core.test.ts index c58079462500..eea6e561f485 100644 --- a/extensions/clickclack/src/setup-core.test.ts +++ b/extensions/clickclack/src/setup-core.test.ts @@ -1,5 +1,6 @@ // ClickClack tests cover non-interactive setup validation and config writes. import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id"; +import type { ChannelSetupInput } from "openclaw/plugin-sdk/channel-setup"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { createNonExitingRuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime"; import { beforeEach, describe, expect, it, vi } from "vitest"; @@ -20,6 +21,13 @@ import { normalizeClickClackBaseUrl, } from "./setup-core.js"; +type ClickClackSetupInput = ChannelSetupInput & { + baseUrl?: string; + code?: string; + workspace?: string; + agentActivity?: boolean; +}; + // Structural stand-in for the internal claim error: the setup formatter // duck-types on a numeric `status`, so tests need only that shape. function makeClaimError(status: number, detail: string): Error { @@ -29,7 +37,7 @@ function makeClaimError(status: number, detail: string): Error { function validate(params: { cfg?: OpenClawConfig; accountId?: string; - input: Parameters>[0]["input"]; + input: ClickClackSetupInput; }) { return clickClackSetupAdapter.validateInput?.({ cfg: params.cfg ?? {}, @@ -38,12 +46,7 @@ function validate(params: { }); } -async function prepare( - input: Parameters< - NonNullable - >[0]["input"], - cfg: OpenClawConfig = {}, -) { +async function prepare(input: ClickClackSetupInput, cfg: OpenClawConfig = {}) { return await clickClackSetupAdapter.prepareAccountConfigInput?.({ cfg, accountId: DEFAULT_ACCOUNT_ID, @@ -331,7 +334,7 @@ describe("ClickClack setup adapter", () => { defaultTo: " channel:general ", allowFrom: ["*"], agentActivity: true, - }, + } as ClickClackSetupInput, }), ).toEqual({ channels: { @@ -432,7 +435,7 @@ describe("ClickClack setup adapter", () => { token: "ccb_default", baseUrl: "https://clickclack.example/", workspace: " default ", - }, + } as ClickClackSetupInput, }), ).toEqual({ channels: { @@ -455,7 +458,7 @@ describe("ClickClack setup adapter", () => { tokenFile: "/run/secrets/clickclack", baseUrl: "https://work.clickclack.example/", workspace: "wsp_work", - }, + } as ClickClackSetupInput, }), ).toEqual({ channels: { @@ -485,7 +488,7 @@ describe("ClickClack setup adapter", () => { useEnv: true, baseUrl: "https://clickclack.example/", workspace: "default", - }, + } as ClickClackSetupInput, }), ).toEqual({ channels: { @@ -522,7 +525,7 @@ describe("ClickClack setup adapter", () => { token: "ccb_new", baseUrl: "https://clickclack.example", workspace: "default", - }, + } as ClickClackSetupInput, }); expect(withToken.channels?.clickclack).toMatchObject({ token: "ccb_new" }); expect(withToken.channels?.clickclack).not.toHaveProperty("tokenFile"); @@ -541,7 +544,7 @@ describe("ClickClack setup adapter", () => { tokenFile: "/run/secrets/new-token", baseUrl: "https://clickclack.example", workspace: "default", - }, + } as ClickClackSetupInput, }); expect(withFile.channels?.clickclack).toMatchObject({ tokenFile: "/run/secrets/new-token", @@ -584,7 +587,7 @@ describe("ClickClack setup adapter", () => { token: "ccb_work", baseUrl: "https://clickclack.example", workspace: "work", - }, + } as ClickClackSetupInput, }); expect(namedWithToken.channels?.clickclack).not.toHaveProperty("tokenFile"); expect(namedWithToken.channels?.clickclack?.accounts).toMatchObject({ diff --git a/extensions/matrix/src/channel.directory.test.ts b/extensions/matrix/src/channel.directory.test.ts index 6420e8ae7494..4cdeb25f9bec 100644 --- a/extensions/matrix/src/channel.directory.test.ts +++ b/extensions/matrix/src/channel.directory.test.ts @@ -5,6 +5,7 @@ import type { RuntimeEnv } from "../runtime-api.js"; import { matrixPlugin } from "./channel.js"; import { resolveMatrixAccount } from "./matrix/accounts.js"; import { resolveMatrixConfigForAccount } from "./matrix/client/config.js"; +import type { MatrixSetupInput } from "./setup-config.js"; import { installMatrixTestRuntime } from "./test-runtime.js"; import type { CoreConfig } from "./types.js"; @@ -360,7 +361,7 @@ describe("matrix directory", () => { homeserver: "https://matrix.example.org", userId: "@ops:example.org", accessToken: "ops-token", - }, + } as MatrixSetupInput, }) as CoreConfig; expect(updated.channels?.["matrix"]?.accessToken).toBeUndefined(); @@ -405,7 +406,7 @@ describe("matrix directory", () => { homeserver: "https://matrix.example.org", userId: "@bot:example.org", accessToken: "bot-token", - }, + } as MatrixSetupInput, }) as CoreConfig; const matrixConfig = updated.channels?.["matrix"]; @@ -580,7 +581,7 @@ describe("matrix directory", () => { homeserver: "https://matrix.example.org", userId: "@bot:example.org", password: "new-password", // pragma: allowlist secret - }, + } as MatrixSetupInput, }) as CoreConfig; expect(updated.channels?.["matrix"]?.accounts?.default?.password).toBe("new-password"); @@ -608,7 +609,7 @@ describe("matrix directory", () => { input: { homeserver: "https://matrix.example.org", accessToken: "new-token", - }, + } as MatrixSetupInput, }) as CoreConfig; expect(updated.channels?.["matrix"]?.accounts?.default?.accessToken).toBe("new-token"); diff --git a/extensions/matrix/src/setup-core.test.ts b/extensions/matrix/src/setup-core.test.ts index 7a18f252a825..1283993cb2b3 100644 --- a/extensions/matrix/src/setup-core.test.ts +++ b/extensions/matrix/src/setup-core.test.ts @@ -1,6 +1,7 @@ // Matrix tests cover setup core plugin behavior. import type { ChannelSetupWizardAdapter } from "openclaw/plugin-sdk/setup"; import { describe, expect, it, vi } from "vitest"; +import type { MatrixSetupInput } from "./setup-config.js"; import { createMatrixSetupWizardProxy, matrixSetupAdapter } from "./setup-core.js"; import type { CoreConfig } from "./types.js"; @@ -12,7 +13,7 @@ function applyOpsAccountConfig(cfg: CoreConfig): CoreConfig { name: "Ops", homeserver: "https://matrix.example.org", accessToken: "ops-token", - }, + } as MatrixSetupInput, }) as CoreConfig; } @@ -201,7 +202,7 @@ describe("matrixSetupAdapter", () => { homeserver: "https://matrix.example.org", userId: "@ops:example.org", accessToken: "ops-token", - }, + } as MatrixSetupInput, }) as CoreConfig; expect(next.channels?.matrix?.homeserver).toBeUndefined(); @@ -346,7 +347,7 @@ describe("matrixSetupAdapter", () => { name: "Ops", useEnv: true, avatarUrl: " mxc://example.org/ops-avatar ", - }, + } as MatrixSetupInput, }) as CoreConfig; expectFields(next.channels?.matrix?.accounts?.ops, { @@ -366,7 +367,7 @@ describe("matrixSetupAdapter", () => { homeserver: "https://matrix.example.org", accessToken: "ops-token", proxy: "http://127.0.0.1:7890", - }, + } as MatrixSetupInput, }) as CoreConfig; expectFields(next.channels?.matrix?.accounts?.ops, { @@ -385,7 +386,7 @@ describe("matrixSetupAdapter", () => { homeserver: "https://matrix.example.org", accessToken: "ops-token", avatarUrl: " mxc://example.org/ops-avatar ", - }, + } as MatrixSetupInput, }) as CoreConfig; expectFields(next.channels?.matrix?.accounts?.ops, { @@ -404,7 +405,7 @@ describe("matrixSetupAdapter", () => { homeserver: "https://matrix.example.org", accessToken: "ops-token", avatarUrl: "file:///tmp/avatar.png", - }, + } as MatrixSetupInput, }); expect(validationError).toBe("Matrix avatar URL must be an mxc:// URI or an http(s) URL."); @@ -418,7 +419,7 @@ describe("matrixSetupAdapter", () => { homeserver: "http://matrix.internal:8008", accessToken: "ops-token", dangerouslyAllowPrivateNetwork: true, - }, + } as MatrixSetupInput, }) as CoreConfig; expectFields(next.channels?.matrix?.accounts?.ops, { @@ -458,7 +459,7 @@ describe("matrixSetupAdapter", () => { homeserver: "https://matrix.example.org", userId: "@ops:example.org", accessToken: "ops-token", - }, + } as MatrixSetupInput, }) as CoreConfig; expect(next.channels?.matrix?.streaming).toEqual({ block: { enabled: true } }); diff --git a/extensions/raft/src/accounts.test.ts b/extensions/raft/src/accounts.test.ts index 83b49f7dc4c4..129472703998 100644 --- a/extensions/raft/src/accounts.test.ts +++ b/extensions/raft/src/accounts.test.ts @@ -1,9 +1,12 @@ +import type { ChannelSetupInput } from "openclaw/plugin-sdk/channel-setup"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { afterEach, describe, expect, it } from "vitest"; import { listRaftAccountIds, resolveRaftAccount } from "./accounts.js"; import { raftChannelConfigSchema } from "./config-schema.js"; import { raftSetupPlugin } from "./setup.js"; +type RaftSetupInput = ChannelSetupInput & { profile?: string }; + const originalProfile = process.env.RAFT_PROFILE; const runtimeConfigSchema = raftChannelConfigSchema.runtime; if (!runtimeConfigSchema) { @@ -64,7 +67,7 @@ describe("Raft account resolution", () => { accountId: "support", input: { profile: "support-profile", - }, + } as RaftSetupInput, }); expect(next.channels?.raft).toEqual({ diff --git a/src/channels/plugins/setup-promotion-helpers.test.ts b/src/channels/plugins/setup-promotion-helpers.test.ts index 7452f25fd5b0..e4b1247d50cb 100644 --- a/src/channels/plugins/setup-promotion-helpers.test.ts +++ b/src/channels/plugins/setup-promotion-helpers.test.ts @@ -24,39 +24,14 @@ import { } from "./setup-promotion-helpers.js"; const legacyCommonKeys = [ - "appToken", - "account", - "signalNumber", - "authDir", - "cliPath", - "dbPath", - "httpUrl", - "httpHost", - "httpPort", - "webhookSecret", - "service", - "region", - "homeserver", - "userId", "accessToken", + "appToken", + "httpUrl", "password", - "deviceName", - "url", - "code", -] as const; -const legacySetupOnlyKeys = [ - "deviceId", - "avatarUrl", - "initialSyncLimit", - "encryption", - "allowlistOnly", - "threadReplies", - "startupVerification", - "startupVerificationCooldownHours", - "autoJoin", - "autoJoinAllowlist", - "rooms", + "userId", + "webhookSecret", ] as const; +const legacySetupOnlyKeys = ["rooms"] as const; function valuesFor(keys: readonly string[]): Record { return Object.fromEntries(keys.map((key) => [key, `value:${key}`])); @@ -101,7 +76,7 @@ describe("setup promotion helpers", () => { expect(resolveBundledSurfaceMock).not.toHaveBeenCalled(); }); - it("restores the exact former common tier when no declarations resolve", () => { + it("retains the published-reader common tier when no declarations resolve", () => { expect( resolveSingleAccountKeysToMove({ channelKey: "demo", @@ -113,7 +88,7 @@ describe("setup promotion helpers", () => { ).toEqual(legacyCommonKeys); }); - it("adds the exact former setup-only tier on direct setup paths", () => { + it("adds the published-reader setup-only tier on direct setup paths", () => { expect( resolveSingleAccountKeysToMove({ channelKey: "demo", diff --git a/src/channels/plugins/setup-promotion-helpers.ts b/src/channels/plugins/setup-promotion-helpers.ts index b97117cf556b..14cd93fb47af 100644 --- a/src/channels/plugins/setup-promotion-helpers.ts +++ b/src/channels/plugins/setup-promotion-helpers.ts @@ -31,44 +31,14 @@ type SingleAccountPromotionParams = { resolveBundledSurface?: (channelKey: string) => ChannelSetupPromotionSurface | null; }; -// Shipped Plugin SDK compatibility: out-of-tree setup adapters published before -// promotion declarations existed still inherit these former core tiers. Remove at -// the next SDK major after #112238 / PR 3 makes declarations mandatory. +// Published undeclared adapters still depend on these keys: Chatu, GroupMe, OneBot, +// and WhatsApp Cloud use accessToken; Claworld uses appToken; OneBot uses httpUrl; +// MQTT and TrueConf use password; Rocket.Chat uses rooms and userId; WorkClaw and +// TIMBot use userId; Vama, Pinto, and Roam use webhookSecret (2026-07-22 sweep). +// Delete each key as soon as no published plugin reads it; no version boundary is needed. const LEGACY_UNDECLARED_ADAPTER_PROMOTION_KEYS = { - common: [ - "appToken", - "account", - "signalNumber", - "authDir", - "cliPath", - "dbPath", - "httpUrl", - "httpHost", - "httpPort", - "webhookSecret", - "service", - "region", - "homeserver", - "userId", - "accessToken", - "password", - "deviceName", - "url", - "code", - ], - setupOnly: [ - "deviceId", - "avatarUrl", - "initialSyncLimit", - "encryption", - "allowlistOnly", - "threadReplies", - "startupVerification", - "startupVerificationCooldownHours", - "autoJoin", - "autoJoinAllowlist", - "rooms", - ], + common: ["accessToken", "appToken", "httpUrl", "password", "userId", "webhookSecret"], + setupOnly: ["rooms"], } as const; const legacyUndeclaredAdapterCommonPromotionKeys = new Set( diff --git a/src/channels/plugins/setup-wizard-types.ts b/src/channels/plugins/setup-wizard-types.ts index 7adaa4f4b9e0..c9db4fdd8633 100644 --- a/src/channels/plugins/setup-wizard-types.ts +++ b/src/channels/plugins/setup-wizard-types.ts @@ -9,12 +9,7 @@ import type { RuntimeEnv } from "../../runtime.js"; import type { WizardPrompter } from "../../wizard/prompts.js"; import type { ChannelAccessPolicy } from "./setup-group-access.js"; import type { ChannelConfigAdapter, ChannelSetupAdapter } from "./types.adapters.js"; -import type { - ChannelCapabilities, - ChannelId, - ChannelMeta, - ChannelSetupInput, -} from "./types.core.js"; +import type { ChannelCapabilities, ChannelId, ChannelMeta } from "./types.core.js"; export type ChannelSetupPlugin = { id: ChannelId; @@ -88,7 +83,8 @@ type ChannelSetupWizardEnvShortcut = { /** Declarative secret/input step for a channel account credential. */ export type ChannelSetupWizardCredential = { - inputKey: keyof ChannelSetupInput; + /** Plugin-owned key written into the runtime setup input. */ + inputKey: string; providerHint: string; credentialLabel: string; preferredEnvVar?: string; @@ -124,7 +120,8 @@ export type ChannelSetupWizardCredential = { /** Declarative non-secret text step that can depend on resolved credentials. */ export type ChannelSetupWizardTextInput = { - inputKey: keyof ChannelSetupInput; + /** Plugin-owned key written into the runtime setup input. */ + inputKey: string; message: string; placeholder?: string; required?: boolean; @@ -179,7 +176,7 @@ export type ChannelSetupWizardAllowFromEntry = { type ChannelSetupWizardAllowFrom = { helpTitle?: string; helpLines?: string[]; - credentialInputKey?: keyof ChannelSetupInput; + credentialInputKey?: string; message: string; placeholder: string; invalidWithoutCredentialNote: string; diff --git a/src/channels/plugins/types.core.ts b/src/channels/plugins/types.core.ts index b61c6a671eff..33b93a34062c 100644 --- a/src/channels/plugins/types.core.ts +++ b/src/channels/plugins/types.core.ts @@ -104,96 +104,55 @@ type ChannelSetupEnvelope = { allowFrom?: string[]; }; +/** + * Compatibility fields with known published readers in the 2026-07-22 registry sweep. + * Each field is deleted as soon as no published plugin reads it; no version boundary is needed. + */ type DeprecatedChannelSetupFields = { - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ privateKey?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ secret?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - secretFile?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ botToken?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ appToken?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - userToken?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ signingSecret?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - identity?: "bot" | "user"; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ mode?: "socket" | "http" | "relay"; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - signalNumber?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ cliPath?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - dbPath?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - service?: "imessage" | "sms" | "auto"; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - region?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ authDir?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ httpUrl?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - httpHost?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ httpPort?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ webhookPath?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ webhookUrl?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - audienceType?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - audience?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - homeserver?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - dangerouslyAllowPrivateNetwork?: boolean; - /** - * Compatibility alias for `dangerouslyAllowPrivateNetwork`. - * @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). - */ - allowPrivateNetwork?: boolean; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - proxy?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ userId?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ accessToken?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ password?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ deviceName?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - avatarUrl?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - initialSyncLimit?: number; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - profile?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - ship?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ url?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ baseUrl?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - relayUrls?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ code?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ groupChannels?: string[]; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ dmAllowlist?: string[]; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ + /** @deprecated Declare this field in the owning plugin's setup input type. */ autoDiscoverChannels?: boolean; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - workspace?: string; - /** @deprecated Channel-owned setup field; declare it in the owning plugin's setup input type. Removed at the next Plugin SDK major (#112238). */ - agentActivity?: boolean; }; /** Generic setup envelope used by CLI, onboarding, and channel-owned setup adapters. */ diff --git a/src/commands/channels.add.test.ts b/src/commands/channels.add.test.ts index 43a32783ada5..b2f8336688c6 100644 --- a/src/commands/channels.add.test.ts +++ b/src/commands/channels.add.test.ts @@ -2,6 +2,7 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getBundledChannelSetupPlugin } from "../channels/plugins/bundled.js"; import type { ChannelPluginCatalogEntry } from "../channels/plugins/catalog.js"; +import type { ChannelSetupInput } from "../channels/plugins/types.core.js"; import type { ChannelPlugin } from "../channels/plugins/types.public.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { PluginInstallRecord } from "../config/types.plugins.js"; @@ -379,6 +380,10 @@ type ResolveAccountIdParams = Parameters< type PrepareAccountConfigInputParams = Parameters< NonNullable["prepareAccountConfigInput"]> >[0]; +type SignalSetupInput = ChannelSetupInput & { signalNumber?: string }; +type NextcloudTalkSetupInput = ChannelSetupInput & { secretFile?: string }; +type MatrixSetupInput = ChannelSetupInput & { initialSyncLimit?: number }; +type PreparedChatSetupInput = ChannelSetupInput & { workspace?: string }; function createSignalPlugin( afterAccountConfigWritten: SignalAfterAccountConfigWritten, @@ -397,7 +402,7 @@ function createSignalPlugin( enabled: true, accounts: { [accountId]: { - account: input.signalNumber, + account: (input as SignalSetupInput).signalNumber, }, }, }, @@ -619,7 +624,7 @@ describe("channelsAddCommand", () => { enabled: true, baseUrl: input.baseUrl, botSecret: input.secret, - botSecretFile: input.secretFile, + botSecretFile: (input as NextcloudTalkSetupInput).secretFile, }, }, })); @@ -638,7 +643,7 @@ describe("channelsAddCommand", () => { ...input, baseUrl: input.baseUrl ?? input.url, secret: input.secret ?? input.token ?? input.password, - secretFile: input.secretFile ?? input.tokenFile, + secretFile: (input as NextcloudTalkSetupInput).secretFile ?? input.tokenFile, }), applyAccountConfig, }, @@ -777,7 +782,7 @@ describe("channelsAddCommand", () => { "prepared-chat": { enabled: true, token: input.token, - workspace: input.workspace, + workspace: (input as PreparedChatSetupInput).workspace, }, }, }; @@ -1076,7 +1081,7 @@ describe("channelsAddCommand", () => { ...cfg.channels, matrix: { enabled: true, - initialSyncLimit: input.initialSyncLimit, + initialSyncLimit: (input as MatrixSetupInput).initialSyncLimit, }, }, })); diff --git a/src/commands/channels.adds-non-default-telegram-account.test.ts b/src/commands/channels.adds-non-default-telegram-account.test.ts index e0522a849e00..9ee2c3a20625 100644 --- a/src/commands/channels.adds-non-default-telegram-account.test.ts +++ b/src/commands/channels.adds-non-default-telegram-account.test.ts @@ -1,7 +1,7 @@ // Channels account tests cover non-default Telegram account setup, status, removal, and binding behavior. import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { createPatchedAccountSetupAdapter } from "../channels/plugins/setup-helpers.js"; -import type { ChannelStatusIssue } from "../channels/plugins/types.core.js"; +import type { ChannelSetupInput, ChannelStatusIssue } from "../channels/plugins/types.core.js"; import type { ChannelPlugin } from "../channels/plugins/types.public.js"; import { createScopedChannelConfigAdapter } from "../plugin-sdk/channel-config-helpers.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; @@ -36,6 +36,8 @@ type ChannelSectionConfig = { accounts?: Record>; }; +type SignalSetupInput = ChannelSetupInput & { signalNumber?: string }; + function formatChannelStatusJoined(channelAccounts: Record) { return formatGatewayChannelsStatusLines({ channelLabels: { @@ -125,7 +127,7 @@ function createScopedCommandTestPlugin(params: { token: input.token, botToken: input.botToken, appToken: input.appToken, - signalNumber: input.signalNumber, + signalNumber: (input as SignalSetupInput).signalNumber, }), }), ...(params.singleAccountKeysToMove diff --git a/src/commands/doctor-config-flow.test.ts b/src/commands/doctor-config-flow.test.ts index 211b8c37d6f6..6d14eb1640fa 100644 --- a/src/commands/doctor-config-flow.test.ts +++ b/src/commands/doctor-config-flow.test.ts @@ -575,25 +575,12 @@ vi.mock("../channels/plugins/setup-promotion-helpers.js", () => { "defaultTo", ]); const legacyCommonSingleAccountKeys = new Set([ - "appToken", - "account", - "signalNumber", - "authDir", - "cliPath", - "dbPath", - "httpUrl", - "httpHost", - "httpPort", - "webhookSecret", - "service", - "region", - "homeserver", - "userId", "accessToken", + "appToken", + "httpUrl", "password", - "deviceName", - "url", - "code", + "userId", + "webhookSecret", ]); const declaredSingleAccountKeys: Record = { discord: [], diff --git a/src/plugin-sdk/channel-setup.test.ts b/src/plugin-sdk/channel-setup.test.ts index fcd5c0f5c079..63baf9722171 100644 --- a/src/plugin-sdk/channel-setup.test.ts +++ b/src/plugin-sdk/channel-setup.test.ts @@ -20,12 +20,11 @@ describe("ChannelSetupInput", () => { expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf< "socket" | "http" | "relay" | undefined >(); - expectTypeOf().toEqualTypeOf<"bot" | "user" | undefined>(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); const assignable: ChannelSetupInput = {} as ThirdPartySetupInput; expectTypeOf(assignable).toEqualTypeOf(); diff --git a/src/plugin-sdk/test-helpers/channel-contract-suites.ts b/src/plugin-sdk/test-helpers/channel-contract-suites.ts index 63d1588d2775..076d2f8e01c2 100644 --- a/src/plugin-sdk/test-helpers/channel-contract-suites.ts +++ b/src/plugin-sdk/test-helpers/channel-contract-suites.ts @@ -111,11 +111,11 @@ export function installChannelActionsContractSuite(params: { } } -type ChannelSetupContractCase = { +type ChannelSetupContractCase = { name: string; cfg: OpenClawConfig; accountId?: string; - input: ChannelSetupInput; + input: SetupInput; expectedAccountId?: string; expectedValidation?: string | null; beforeTest?: () => void; @@ -123,9 +123,12 @@ type ChannelSetupContractCase = { assertResolvedAccount?: (account: ResolvedAccount, cfg: OpenClawConfig) => void; }; -export function installChannelSetupContractSuite(params: { +export function installChannelSetupContractSuite< + ResolvedAccount, + SetupInput extends ChannelSetupInput = ChannelSetupInput, +>(params: { plugin: Pick, "id" | "config" | "setup">; - cases: readonly ChannelSetupContractCase[]; + cases: readonly ChannelSetupContractCase[]; }) { it("exposes the base setup contract", () => { expect(params.plugin.setup).toBeDefined(); diff --git a/src/plugins/contracts/config-footprint-guardrails.test.ts b/src/plugins/contracts/config-footprint-guardrails.test.ts index 6c1b53aaf42f..ad06921e54a8 100644 --- a/src/plugins/contracts/config-footprint-guardrails.test.ts +++ b/src/plugins/contracts/config-footprint-guardrails.test.ts @@ -149,11 +149,18 @@ describe("config footprint guardrails", () => { expect(source).not.toContain("nativeStreaming:"); }); - it("keeps shared setup input canonical-first", () => { + it("keeps Matrix setup input canonical-first after plugin ownership", () => { + const source = readSource("extensions/matrix/src/setup-config.ts"); + const canonicalIndex = source.indexOf("dangerouslyAllowPrivateNetwork?: boolean;"); + const aliasIndex = source.indexOf("allowPrivateNetwork?: boolean;"); + + expect(canonicalIndex).toBeGreaterThanOrEqual(0); + expect(aliasIndex).toBeGreaterThan(canonicalIndex); + }); + + it("keeps retired config aliases out of the shared setup input", () => { const source = readSource("src/channels/plugins/types.core.ts"); - expect(source).toContain("dangerouslyAllowPrivateNetwork?: boolean;"); - expect(source).toContain("allowPrivateNetwork?: boolean;"); expect(source).not.toContain("streamMode?:"); expect(source).not.toContain("groupMentionsOnly?:"); expect(source).not.toContain("perSession?:");