mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
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
This commit is contained in:
committed by
GitHub
parent
a95c059f11
commit
b40a8da70e
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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<NonNullable<typeof clickClackSetupAdapter.validateInput>>[0]["input"];
|
||||
input: ClickClackSetupInput;
|
||||
}) {
|
||||
return clickClackSetupAdapter.validateInput?.({
|
||||
cfg: params.cfg ?? {},
|
||||
@@ -38,12 +46,7 @@ function validate(params: {
|
||||
});
|
||||
}
|
||||
|
||||
async function prepare(
|
||||
input: Parameters<
|
||||
NonNullable<typeof clickClackSetupAdapter.prepareAccountConfigInput>
|
||||
>[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({
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<string, string> {
|
||||
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",
|
||||
|
||||
@@ -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<string>(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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<NonNullable<ChannelPlugin["setup"]>["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,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -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<string, Record<string, unknown>>;
|
||||
};
|
||||
|
||||
type SignalSetupInput = ChannelSetupInput & { signalNumber?: string };
|
||||
|
||||
function formatChannelStatusJoined(channelAccounts: Record<string, unknown>) {
|
||||
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
|
||||
|
||||
@@ -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<string, readonly string[]> = {
|
||||
discord: [],
|
||||
|
||||
@@ -20,12 +20,11 @@ describe("ChannelSetupInput", () => {
|
||||
|
||||
expectTypeOf<ChannelSetupInput["botToken"]>().toEqualTypeOf<string | undefined>();
|
||||
expectTypeOf<ChannelSetupInput["appToken"]>().toEqualTypeOf<string | undefined>();
|
||||
expectTypeOf<ChannelSetupInput["signalNumber"]>().toEqualTypeOf<string | undefined>();
|
||||
expectTypeOf<ChannelSetupInput["homeserver"]>().toEqualTypeOf<string | undefined>();
|
||||
expectTypeOf<ChannelSetupInput["mode"]>().toEqualTypeOf<
|
||||
"socket" | "http" | "relay" | undefined
|
||||
>();
|
||||
expectTypeOf<ChannelSetupInput["identity"]>().toEqualTypeOf<"bot" | "user" | undefined>();
|
||||
expectTypeOf<ChannelSetupInput["privateKey"]>().toEqualTypeOf<string | undefined>();
|
||||
expectTypeOf<ChannelSetupInput["password"]>().toEqualTypeOf<string | undefined>();
|
||||
|
||||
const assignable: ChannelSetupInput = {} as ThirdPartySetupInput;
|
||||
expectTypeOf(assignable).toEqualTypeOf<ChannelSetupInput>();
|
||||
|
||||
@@ -111,11 +111,11 @@ export function installChannelActionsContractSuite(params: {
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelSetupContractCase<ResolvedAccount> = {
|
||||
type ChannelSetupContractCase<ResolvedAccount, SetupInput extends ChannelSetupInput> = {
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string;
|
||||
input: ChannelSetupInput;
|
||||
input: SetupInput;
|
||||
expectedAccountId?: string;
|
||||
expectedValidation?: string | null;
|
||||
beforeTest?: () => void;
|
||||
@@ -123,9 +123,12 @@ type ChannelSetupContractCase<ResolvedAccount> = {
|
||||
assertResolvedAccount?: (account: ResolvedAccount, cfg: OpenClawConfig) => void;
|
||||
};
|
||||
|
||||
export function installChannelSetupContractSuite<ResolvedAccount>(params: {
|
||||
export function installChannelSetupContractSuite<
|
||||
ResolvedAccount,
|
||||
SetupInput extends ChannelSetupInput = ChannelSetupInput,
|
||||
>(params: {
|
||||
plugin: Pick<ChannelPlugin<ResolvedAccount>, "id" | "config" | "setup">;
|
||||
cases: readonly ChannelSetupContractCase<ResolvedAccount>[];
|
||||
cases: readonly ChannelSetupContractCase<ResolvedAccount, SetupInput>[];
|
||||
}) {
|
||||
it("exposes the base setup contract", () => {
|
||||
expect(params.plugin.setup).toBeDefined();
|
||||
|
||||
@@ -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?:");
|
||||
|
||||
Reference in New Issue
Block a user