mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-12 04:20:48 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
293
openclaw/extensions/qqbot/src/config.test.ts
Normal file
293
openclaw/extensions/qqbot/src/config.test.ts
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { qqbotConfigAdapter, qqbotSetupAdapterShared } from "./channel-config-shared.js";
|
||||
import { QQBotConfigSchema } from "./config-schema.js";
|
||||
import { DEFAULT_ACCOUNT_ID, resolveDefaultQQBotAccountId, resolveQQBotAccount } from "./config.js";
|
||||
|
||||
describe("qqbot config", () => {
|
||||
it("honors configured defaultAccount when resolving the default QQ Bot account id", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
qqbot: {
|
||||
defaultAccount: "bot2",
|
||||
accounts: {
|
||||
bot2: {
|
||||
appId: "654321",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(resolveDefaultQQBotAccountId(cfg)).toBe("bot2");
|
||||
});
|
||||
|
||||
it("accepts SecretRef-backed credentials in the runtime schema", () => {
|
||||
const parsed = QQBotConfigSchema.safeParse({
|
||||
defaultAccount: "bot2",
|
||||
appId: "123456",
|
||||
clientSecret: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "QQBOT_CLIENT_SECRET",
|
||||
},
|
||||
allowFrom: ["*"],
|
||||
audioFormatPolicy: {
|
||||
sttDirectFormats: [".wav"],
|
||||
uploadDirectFormats: [".mp3"],
|
||||
transcodeEnabled: false,
|
||||
},
|
||||
urlDirectUpload: false,
|
||||
upgradeUrl: "https://docs.openclaw.ai/channels/qqbot",
|
||||
upgradeMode: "doc",
|
||||
accounts: {
|
||||
bot2: {
|
||||
appId: "654321",
|
||||
clientSecret: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "QQBOT_CLIENT_SECRET_BOT2",
|
||||
},
|
||||
allowFrom: ["user-1"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts account-level speech overrides as forward-compatible config", () => {
|
||||
const parsed = QQBotConfigSchema.safeParse({
|
||||
accounts: {
|
||||
bot2: {
|
||||
appId: "654321",
|
||||
tts: {
|
||||
provider: "openai",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.success).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves top-level media and upgrade config on the default account", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
qqbot: {
|
||||
appId: "123456",
|
||||
clientSecret: "secret-value",
|
||||
audioFormatPolicy: {
|
||||
sttDirectFormats: [".wav"],
|
||||
uploadDirectFormats: [".mp3"],
|
||||
transcodeEnabled: false,
|
||||
},
|
||||
urlDirectUpload: false,
|
||||
upgradeUrl: "https://docs.openclaw.ai/channels/qqbot",
|
||||
upgradeMode: "hot-reload",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const resolved = resolveQQBotAccount(cfg, DEFAULT_ACCOUNT_ID);
|
||||
|
||||
expect(resolved.clientSecret).toBe("secret-value");
|
||||
expect(resolved.config.audioFormatPolicy).toEqual({
|
||||
sttDirectFormats: [".wav"],
|
||||
uploadDirectFormats: [".mp3"],
|
||||
transcodeEnabled: false,
|
||||
});
|
||||
expect(resolved.config.urlDirectUpload).toBe(false);
|
||||
expect(resolved.config.upgradeUrl).toBe("https://docs.openclaw.ai/channels/qqbot");
|
||||
expect(resolved.config.upgradeMode).toBe("hot-reload");
|
||||
});
|
||||
|
||||
it("uses configured defaultAccount when accountId is omitted", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
qqbot: {
|
||||
defaultAccount: "bot2",
|
||||
accounts: {
|
||||
bot2: {
|
||||
appId: "654321",
|
||||
clientSecret: "secret-value",
|
||||
name: "Bot Two",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const resolved = resolveQQBotAccount(cfg);
|
||||
|
||||
expect(resolved.accountId).toBe("bot2");
|
||||
expect(resolved.appId).toBe("654321");
|
||||
expect(resolved.clientSecret).toBe("secret-value");
|
||||
expect(resolved.name).toBe("Bot Two");
|
||||
});
|
||||
|
||||
it("rejects unresolved SecretRefs on runtime resolution", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
qqbot: {
|
||||
appId: "123456",
|
||||
clientSecret: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "QQBOT_CLIENT_SECRET",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(() => resolveQQBotAccount(cfg, DEFAULT_ACCOUNT_ID)).toThrow(
|
||||
'channels.qqbot.clientSecret: unresolved SecretRef "env:default:QQBOT_CLIENT_SECRET"',
|
||||
);
|
||||
});
|
||||
|
||||
it("allows unresolved SecretRefs for setup/status flows", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
qqbot: {
|
||||
appId: "123456",
|
||||
clientSecret: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "QQBOT_CLIENT_SECRET",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const resolved = resolveQQBotAccount(cfg, DEFAULT_ACCOUNT_ID, {
|
||||
allowUnresolvedSecretRef: true,
|
||||
});
|
||||
|
||||
expect(resolved.clientSecret).toBe("");
|
||||
expect(resolved.secretSource).toBe("config");
|
||||
expect(qqbotConfigAdapter.isConfigured(resolved)).toBe(true);
|
||||
expect(qqbotConfigAdapter.describeAccount(resolved).configured).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
inputAccountId: DEFAULT_ACCOUNT_ID,
|
||||
expectedPath: ["channels", "qqbot"],
|
||||
},
|
||||
{
|
||||
accountId: "bot2",
|
||||
inputAccountId: "bot2",
|
||||
expectedPath: ["channels", "qqbot", "accounts", "bot2"],
|
||||
},
|
||||
])("splits --token on the first colon for $accountId", ({ inputAccountId, expectedPath }) => {
|
||||
const next = qqbotSetupAdapterShared.applyAccountConfig({
|
||||
cfg: {} as OpenClawConfig,
|
||||
accountId: inputAccountId,
|
||||
input: {
|
||||
token: "102905186:Oi2Mg1Mh2Ni3:Pl7TpBXuHe1OmAYwKi7W",
|
||||
},
|
||||
}) as Record<string, unknown>;
|
||||
|
||||
const accountConfig = expectedPath.reduce<unknown>((value, key) => {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return (value as Record<string, unknown>)[key];
|
||||
}, next) as Record<string, unknown> | undefined;
|
||||
|
||||
expect(accountConfig).toMatchObject({
|
||||
enabled: true,
|
||||
appId: "102905186",
|
||||
clientSecret: "Oi2Mg1Mh2Ni3:Pl7TpBXuHe1OmAYwKi7W",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects malformed --token in shared setup config", () => {
|
||||
const runtimeSetup = qqbotSetupAdapterShared;
|
||||
expect(runtimeSetup).toBeDefined();
|
||||
|
||||
const input = { token: "broken", name: "Bad" };
|
||||
|
||||
expect(
|
||||
runtimeSetup.validateInput?.({
|
||||
cfg: {} as OpenClawConfig,
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
input,
|
||||
} as never),
|
||||
).toBe("QQBot --token must be in appId:clientSecret format");
|
||||
expect(
|
||||
runtimeSetup.applyAccountConfig?.({
|
||||
cfg: {} as OpenClawConfig,
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
input,
|
||||
} as never),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it("preserves the --use-env add flow in shared setup config", () => {
|
||||
const runtimeSetup = qqbotSetupAdapterShared;
|
||||
expect(runtimeSetup).toBeDefined();
|
||||
|
||||
const input = { useEnv: true, name: "Env Bot" };
|
||||
|
||||
expect(
|
||||
runtimeSetup.applyAccountConfig?.({
|
||||
cfg: {} as OpenClawConfig,
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
input,
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
channels: {
|
||||
qqbot: {
|
||||
enabled: true,
|
||||
allowFrom: ["*"],
|
||||
name: "Env Bot",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("uses configured defaultAccount when runtime setup accountId is omitted", () => {
|
||||
const runtimeSetup = qqbotSetupAdapterShared;
|
||||
expect(runtimeSetup).toBeDefined();
|
||||
|
||||
expect(
|
||||
runtimeSetup.resolveAccountId?.({
|
||||
cfg: {
|
||||
channels: {
|
||||
qqbot: {
|
||||
defaultAccount: "bot2",
|
||||
accounts: {
|
||||
bot2: { appId: "123456" },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
accountId: undefined,
|
||||
} as never),
|
||||
).toBe("bot2");
|
||||
});
|
||||
|
||||
it("rejects --use-env for named accounts in shared setup config", () => {
|
||||
const runtimeSetup = qqbotSetupAdapterShared;
|
||||
expect(runtimeSetup).toBeDefined();
|
||||
|
||||
const input = { useEnv: true, name: "Env Bot" };
|
||||
|
||||
expect(
|
||||
runtimeSetup.validateInput?.({
|
||||
cfg: {} as OpenClawConfig,
|
||||
accountId: "bot2",
|
||||
input,
|
||||
} as never),
|
||||
).toBe("QQBot --use-env only supports the default account");
|
||||
expect(
|
||||
runtimeSetup.applyAccountConfig?.({
|
||||
cfg: {} as OpenClawConfig,
|
||||
accountId: "bot2",
|
||||
input,
|
||||
} as never),
|
||||
).toEqual({});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue