mirror of https://github.com/openclaw/openclaw.git
refactor: split feishu runtime and inspect secret resolution
This commit is contained in:
parent
8e9e2d2f4e
commit
ba95d43e3c
|
|
@ -1,9 +1,12 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
FeishuSecretRefUnavailableError,
|
||||
inspectFeishuCredentials,
|
||||
resolveDefaultFeishuAccountId,
|
||||
resolveDefaultFeishuAccountSelection,
|
||||
resolveFeishuAccount,
|
||||
resolveFeishuCredentials,
|
||||
resolveFeishuRuntimeAccount,
|
||||
} from "./accounts.js";
|
||||
import type { FeishuConfig } from "./types.js";
|
||||
|
||||
|
|
@ -169,6 +172,18 @@ describe("resolveFeishuCredentials", () => {
|
|||
expect(creds).toBeNull();
|
||||
});
|
||||
|
||||
it("supports explicit inspect mode for unresolved SecretRefs", () => {
|
||||
const creds = resolveFeishuCredentials(
|
||||
asConfig({
|
||||
appId: "cli_123",
|
||||
appSecret: { source: "file", provider: "default", id: "path/to/secret" } as never,
|
||||
}),
|
||||
{ mode: "inspect" },
|
||||
);
|
||||
|
||||
expect(creds).toBeNull();
|
||||
});
|
||||
|
||||
it("throws unresolved SecretRef error when env SecretRef points to missing env var", () => {
|
||||
const key = "FEISHU_APP_SECRET_MISSING_TEST";
|
||||
withEnvVar(key, undefined, () => {
|
||||
|
|
@ -274,6 +289,24 @@ describe("resolveFeishuCredentials", () => {
|
|||
domain: "feishu",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps required credentials when optional event SecretRefs are unresolved in inspect mode", () => {
|
||||
const creds = inspectFeishuCredentials(
|
||||
asConfig({
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_456",
|
||||
verificationToken: { source: "file", provider: "default", id: "path/to/token" } as never,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(creds).toEqual({
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_456", // pragma: allowlist secret
|
||||
encryptKey: undefined,
|
||||
verificationToken: undefined,
|
||||
domain: "feishu",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveFeishuAccount", () => {
|
||||
|
|
@ -348,6 +381,59 @@ describe("resolveFeishuAccount", () => {
|
|||
expect(account.appSecret).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps account configured when optional event SecretRefs are unresolved in inspect mode", () => {
|
||||
const account = resolveFeishuAccount({
|
||||
cfg: {
|
||||
channels: {
|
||||
feishu: {
|
||||
accounts: {
|
||||
main: {
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_456",
|
||||
verificationToken: {
|
||||
source: "file",
|
||||
provider: "default",
|
||||
id: "path/to/token",
|
||||
},
|
||||
} as never,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
accountId: "main",
|
||||
});
|
||||
|
||||
expect(account.configured).toBe(true);
|
||||
expect(account.appSecret).toBe("secret_456");
|
||||
expect(account.verificationToken).toBeUndefined();
|
||||
});
|
||||
|
||||
it("throws typed SecretRef errors in runtime account resolution", () => {
|
||||
let caught: unknown;
|
||||
try {
|
||||
resolveFeishuRuntimeAccount({
|
||||
cfg: {
|
||||
channels: {
|
||||
feishu: {
|
||||
accounts: {
|
||||
main: {
|
||||
appId: "cli_123",
|
||||
appSecret: { source: "file", provider: "default", id: "path/to/secret" },
|
||||
} as never,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
accountId: "main",
|
||||
});
|
||||
} catch (error) {
|
||||
caught = error;
|
||||
}
|
||||
|
||||
expect(caught).toBeInstanceOf(FeishuSecretRefUnavailableError);
|
||||
expect((caught as Error).message).toMatch(/channels\.feishu\.appSecret: unresolved SecretRef/i);
|
||||
});
|
||||
|
||||
it("does not throw when account name is non-string", () => {
|
||||
expect(() =>
|
||||
resolveFeishuAccount({
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import {
|
|||
normalizeOptionalAccountId,
|
||||
resolveMergedAccountConfig,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import { coerceSecretRef } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
|
||||
import type {
|
||||
FeishuConfig,
|
||||
FeishuAccountConfig,
|
||||
|
|
@ -25,11 +25,125 @@ const {
|
|||
|
||||
export { listFeishuAccountIds };
|
||||
|
||||
function isUnresolvedSecretRefError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) {
|
||||
return false;
|
||||
type FeishuCredentialResolutionMode = "inspect" | "strict";
|
||||
type FeishuResolvedSecretRef = NonNullable<ReturnType<typeof coerceSecretRef>>;
|
||||
|
||||
function normalizeString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
return /unresolved SecretRef/i.test(error.message);
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function formatSecretRefLabel(ref: FeishuResolvedSecretRef): string {
|
||||
return `${ref.source}:${ref.provider}:${ref.id}`;
|
||||
}
|
||||
|
||||
export class FeishuSecretRefUnavailableError extends Error {
|
||||
path: string;
|
||||
|
||||
constructor(path: string, ref: FeishuResolvedSecretRef) {
|
||||
super(
|
||||
`${path}: unresolved SecretRef "${formatSecretRefLabel(ref)}". ` +
|
||||
"Resolve this command against an active gateway runtime snapshot before reading it.",
|
||||
);
|
||||
this.name = "FeishuSecretRefUnavailableError";
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
|
||||
export function isFeishuSecretRefUnavailableError(
|
||||
error: unknown,
|
||||
): error is FeishuSecretRefUnavailableError {
|
||||
return error instanceof FeishuSecretRefUnavailableError;
|
||||
}
|
||||
|
||||
function resolveFeishuSecretLike(params: {
|
||||
value: unknown;
|
||||
path: string;
|
||||
mode: FeishuCredentialResolutionMode;
|
||||
allowEnvSecretRefRead?: boolean;
|
||||
}): string | undefined {
|
||||
const asString = normalizeString(params.value);
|
||||
if (asString) {
|
||||
return asString;
|
||||
}
|
||||
|
||||
const ref = coerceSecretRef(params.value);
|
||||
if (!ref) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (params.mode === "inspect") {
|
||||
if (params.allowEnvSecretRefRead && ref.source === "env") {
|
||||
const envValue = normalizeString(process.env[ref.id]);
|
||||
if (envValue) {
|
||||
return envValue;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
throw new FeishuSecretRefUnavailableError(params.path, ref);
|
||||
}
|
||||
|
||||
function resolveFeishuBaseCredentials(
|
||||
cfg: FeishuConfig | undefined,
|
||||
mode: FeishuCredentialResolutionMode,
|
||||
): {
|
||||
appId: string;
|
||||
appSecret: string;
|
||||
domain: FeishuDomain;
|
||||
} | null {
|
||||
const appId = resolveFeishuSecretLike({
|
||||
value: cfg?.appId,
|
||||
path: "channels.feishu.appId",
|
||||
mode,
|
||||
allowEnvSecretRefRead: true,
|
||||
});
|
||||
const appSecret = resolveFeishuSecretLike({
|
||||
value: cfg?.appSecret,
|
||||
path: "channels.feishu.appSecret",
|
||||
mode,
|
||||
allowEnvSecretRefRead: true,
|
||||
});
|
||||
|
||||
if (!appId || !appSecret) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
appId,
|
||||
appSecret,
|
||||
domain: cfg?.domain ?? "feishu",
|
||||
};
|
||||
}
|
||||
|
||||
function resolveFeishuEventSecrets(
|
||||
cfg: FeishuConfig | undefined,
|
||||
mode: FeishuCredentialResolutionMode,
|
||||
): {
|
||||
encryptKey?: string;
|
||||
verificationToken?: string;
|
||||
} {
|
||||
return {
|
||||
encryptKey:
|
||||
(cfg?.connectionMode ?? "websocket") === "webhook"
|
||||
? resolveFeishuSecretLike({
|
||||
value: cfg?.encryptKey,
|
||||
path: "channels.feishu.encryptKey",
|
||||
mode,
|
||||
allowEnvSecretRefRead: true,
|
||||
})
|
||||
: normalizeString(cfg?.encryptKey),
|
||||
verificationToken: resolveFeishuSecretLike({
|
||||
value: cfg?.verificationToken,
|
||||
path: "channels.feishu.verificationToken",
|
||||
mode,
|
||||
allowEnvSecretRefRead: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -94,7 +208,10 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|||
} | null;
|
||||
export function resolveFeishuCredentials(
|
||||
cfg: FeishuConfig | undefined,
|
||||
options: { allowUnresolvedSecretRef?: boolean },
|
||||
options: {
|
||||
mode?: FeishuCredentialResolutionMode;
|
||||
allowUnresolvedSecretRef?: boolean;
|
||||
},
|
||||
): {
|
||||
appId: string;
|
||||
appSecret: string;
|
||||
|
|
@ -104,7 +221,10 @@ export function resolveFeishuCredentials(
|
|||
} | null;
|
||||
export function resolveFeishuCredentials(
|
||||
cfg?: FeishuConfig,
|
||||
options?: { allowUnresolvedSecretRef?: boolean },
|
||||
options?: {
|
||||
mode?: FeishuCredentialResolutionMode;
|
||||
allowUnresolvedSecretRef?: boolean;
|
||||
},
|
||||
): {
|
||||
appId: string;
|
||||
appSecret: string;
|
||||
|
|
@ -112,68 +232,28 @@ export function resolveFeishuCredentials(
|
|||
verificationToken?: string;
|
||||
domain: FeishuDomain;
|
||||
} | null {
|
||||
const normalizeString = (value: unknown): string | undefined => {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
};
|
||||
|
||||
const resolveSecretLike = (value: unknown, path: string): string | undefined => {
|
||||
const asString = normalizeString(value);
|
||||
if (asString) {
|
||||
return asString;
|
||||
}
|
||||
|
||||
// In relaxed/setup paths only: allow direct env SecretRef reads for UX.
|
||||
// Default resolution path must preserve unresolved-ref diagnostics/policy semantics.
|
||||
if (options?.allowUnresolvedSecretRef && typeof value === "object" && value !== null) {
|
||||
const rec = value as Record<string, unknown>;
|
||||
const source = normalizeString(rec.source)?.toLowerCase();
|
||||
const id = normalizeString(rec.id);
|
||||
if (source === "env" && id) {
|
||||
const envValue = normalizeString(process.env[id]);
|
||||
if (envValue) {
|
||||
return envValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.allowUnresolvedSecretRef) {
|
||||
return normalizeSecretInputString(value);
|
||||
}
|
||||
return normalizeResolvedSecretInputString({ value, path });
|
||||
};
|
||||
|
||||
const appId = resolveSecretLike(cfg?.appId, "channels.feishu.appId");
|
||||
const appSecret = resolveSecretLike(cfg?.appSecret, "channels.feishu.appSecret");
|
||||
|
||||
if (!appId || !appSecret) {
|
||||
const mode = options?.mode ?? (options?.allowUnresolvedSecretRef ? "inspect" : "strict");
|
||||
const base = resolveFeishuBaseCredentials(cfg, mode);
|
||||
if (!base) {
|
||||
return null;
|
||||
}
|
||||
const connectionMode = cfg?.connectionMode ?? "websocket";
|
||||
const eventSecrets = resolveFeishuEventSecrets(cfg, mode);
|
||||
|
||||
return {
|
||||
appId,
|
||||
appSecret,
|
||||
encryptKey:
|
||||
connectionMode === "webhook"
|
||||
? resolveSecretLike(cfg?.encryptKey, "channels.feishu.encryptKey")
|
||||
: normalizeString(cfg?.encryptKey),
|
||||
verificationToken: resolveSecretLike(
|
||||
cfg?.verificationToken,
|
||||
"channels.feishu.verificationToken",
|
||||
),
|
||||
domain: cfg?.domain ?? "feishu",
|
||||
...base,
|
||||
...eventSecrets,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a complete Feishu account with merged config.
|
||||
*/
|
||||
export function resolveFeishuAccount(params: {
|
||||
export function inspectFeishuCredentials(cfg?: FeishuConfig) {
|
||||
return resolveFeishuCredentials(cfg, { mode: "inspect" });
|
||||
}
|
||||
|
||||
function buildResolvedFeishuAccount(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | null;
|
||||
baseMode: FeishuCredentialResolutionMode;
|
||||
eventSecretMode: FeishuCredentialResolutionMode;
|
||||
}): ResolvedFeishuAccount {
|
||||
const hasExplicitAccountId =
|
||||
typeof params.accountId === "string" && params.accountId.trim() !== "";
|
||||
|
|
@ -188,44 +268,62 @@ export function resolveFeishuAccount(params: {
|
|||
: (defaultSelection?.source ?? "fallback");
|
||||
const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
||||
|
||||
// Base enabled state (top-level)
|
||||
const baseEnabled = feishuCfg?.enabled !== false;
|
||||
|
||||
// Merge configs
|
||||
const merged = mergeFeishuAccountConfig(params.cfg, accountId);
|
||||
|
||||
// Account-level enabled state
|
||||
const accountEnabled = merged.enabled !== false;
|
||||
const enabled = baseEnabled && accountEnabled;
|
||||
|
||||
// Resolve credentials from merged config.
|
||||
// CLI startup can parse config before gateway-backed SecretRef resolution is available.
|
||||
// Treat unresolved refs as "not configured" here instead of crashing plugin load.
|
||||
let creds: ReturnType<typeof resolveFeishuCredentials> = null;
|
||||
try {
|
||||
creds = resolveFeishuCredentials(merged);
|
||||
} catch (error) {
|
||||
if (!isUnresolvedSecretRefError(error)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const baseCreds = resolveFeishuBaseCredentials(merged, params.baseMode);
|
||||
const eventSecrets = resolveFeishuEventSecrets(merged, params.eventSecretMode);
|
||||
const accountName = (merged as FeishuAccountConfig).name;
|
||||
|
||||
return {
|
||||
accountId,
|
||||
selectionSource,
|
||||
enabled,
|
||||
configured: Boolean(creds),
|
||||
configured: Boolean(baseCreds),
|
||||
name: typeof accountName === "string" ? accountName.trim() || undefined : undefined,
|
||||
appId: creds?.appId,
|
||||
appSecret: creds?.appSecret,
|
||||
encryptKey: creds?.encryptKey,
|
||||
verificationToken: creds?.verificationToken,
|
||||
domain: creds?.domain ?? "feishu",
|
||||
appId: baseCreds?.appId,
|
||||
appSecret: baseCreds?.appSecret,
|
||||
encryptKey: eventSecrets.encryptKey,
|
||||
verificationToken: eventSecrets.verificationToken,
|
||||
domain: baseCreds?.domain ?? "feishu",
|
||||
config: merged,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a read-only Feishu account snapshot for CLI/config surfaces.
|
||||
* Unresolved SecretRefs are treated as unavailable instead of throwing.
|
||||
*/
|
||||
export function resolveFeishuAccount(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | null;
|
||||
}): ResolvedFeishuAccount {
|
||||
return buildResolvedFeishuAccount({
|
||||
...params,
|
||||
baseMode: "inspect",
|
||||
eventSecretMode: "inspect",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a runtime Feishu account.
|
||||
* Required app credentials stay strict; event-only secrets can be required by callers.
|
||||
*/
|
||||
export function resolveFeishuRuntimeAccount(
|
||||
params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | null;
|
||||
},
|
||||
options?: { requireEventSecrets?: boolean },
|
||||
): ResolvedFeishuAccount {
|
||||
return buildResolvedFeishuAccount({
|
||||
...params,
|
||||
baseMode: "strict",
|
||||
eventSecretMode: options?.requireEventSecrets ? "strict" : "inspect",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List all enabled and configured accounts.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ import {
|
|||
FEISHU_APPROVAL_REQUEST_ACTION,
|
||||
} from "./card-ux-approval.js";
|
||||
|
||||
// Mock resolveFeishuAccount
|
||||
// Mock account resolution
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveFeishuAccount: vi.fn().mockReturnValue({ accountId: "mock-account" }),
|
||||
resolveFeishuRuntimeAccount: vi.fn().mockReturnValue({ accountId: "mock-account" }),
|
||||
}));
|
||||
|
||||
// Mock bot.js to verify handleFeishuMessage call
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
resolveDefaultGroupPolicy,
|
||||
warnMissingProviderGroupPolicyFallbackOnce,
|
||||
} from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import {
|
||||
checkBotMentioned,
|
||||
normalizeFeishuCommandProbeBody,
|
||||
|
|
@ -240,7 +240,7 @@ export async function handleFeishuMessage(params: {
|
|||
} = params;
|
||||
|
||||
// Resolve account with merged config
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
const feishuCfg = account.config;
|
||||
|
||||
const log = runtime?.log ?? console.log;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
|
||||
import { decodeFeishuCardAction, buildFeishuCardActionTextFallback } from "./card-interaction.js";
|
||||
import {
|
||||
|
|
@ -174,7 +174,7 @@ export async function handleFeishuCardAction(params: {
|
|||
accountId?: string;
|
||||
}): Promise<void> {
|
||||
const { cfg, event, runtime, accountId } = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
const log = runtime?.log ?? console.log;
|
||||
const decoded = decodeFeishuCardAction({ event });
|
||||
const claimedToken = beginFeishuCardActionToken({
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ import {
|
|||
} from "../runtime-api.js";
|
||||
import type { ChannelMessageActionName } from "../runtime-api.js";
|
||||
import {
|
||||
inspectFeishuCredentials,
|
||||
resolveFeishuAccount,
|
||||
resolveFeishuCredentials,
|
||||
resolveFeishuRuntimeAccount,
|
||||
listFeishuAccountIds,
|
||||
listEnabledFeishuAccounts,
|
||||
resolveDefaultFeishuAccountId,
|
||||
|
|
@ -100,7 +101,7 @@ function describeFeishuMessageTool({
|
|||
>[0]): ChannelMessageToolDiscovery {
|
||||
const enabled =
|
||||
cfg.channels?.feishu?.enabled !== false &&
|
||||
Boolean(resolveFeishuCredentials(cfg.channels?.feishu as FeishuConfig | undefined));
|
||||
Boolean(inspectFeishuCredentials(cfg.channels?.feishu as FeishuConfig | undefined));
|
||||
if (listEnabledFeishuAccounts(cfg).length === 0) {
|
||||
return {
|
||||
actions: [],
|
||||
|
|
@ -977,7 +978,10 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount, FeishuProbeResul
|
|||
gateway: {
|
||||
startAccount: async (ctx) => {
|
||||
const { monitorFeishuProvider } = await import("./monitor.js");
|
||||
const account = resolveFeishuAccount({ cfg: ctx.cfg, accountId: ctx.accountId });
|
||||
const account = resolveFeishuRuntimeAccount(
|
||||
{ cfg: ctx.cfg, accountId: ctx.accountId },
|
||||
{ requireEventSecrets: true },
|
||||
);
|
||||
const port = account.config?.webhookPort ?? null;
|
||||
ctx.setStatus({ accountId: ctx.accountId, port });
|
||||
ctx.log?.info(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ vi.mock("./client.js", () => ({
|
|||
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveFeishuAccount: resolveFeishuAccountMock,
|
||||
resolveFeishuRuntimeAccount: resolveFeishuAccountMock,
|
||||
}));
|
||||
|
||||
vi.mock("./targets.js", () => ({
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import path from "path";
|
|||
import { Readable } from "stream";
|
||||
import { mediaKindFromMime } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { withTempDownloadPath, type ClawdbotConfig } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { normalizeFeishuExternalKey } from "./external-keys.js";
|
||||
import { getFeishuRuntime } from "./runtime.js";
|
||||
|
|
@ -24,10 +24,10 @@ export type DownloadMessageResourceResult = {
|
|||
};
|
||||
|
||||
function createConfiguredFeishuMediaClient(params: { cfg: ClawdbotConfig; accountId?: string }): {
|
||||
account: ReturnType<typeof resolveFeishuAccount>;
|
||||
account: ReturnType<typeof resolveFeishuRuntimeAccount>;
|
||||
client: ReturnType<typeof createFeishuClient>;
|
||||
} {
|
||||
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
@ -547,7 +547,7 @@ export async function sendMediaFeishu(params: {
|
|||
accountId,
|
||||
mediaLocalRoots,
|
||||
} = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { listEnabledFeishuAccounts, resolveFeishuAccount } from "./accounts.js";
|
||||
import { listEnabledFeishuAccounts, resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import {
|
||||
monitorSingleAccount,
|
||||
resolveReactionSyntheticEvent,
|
||||
|
|
@ -37,7 +37,10 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
|||
const log = opts.runtime?.log ?? console.log;
|
||||
|
||||
if (opts.accountId) {
|
||||
const account = resolveFeishuAccount({ cfg, accountId: opts.accountId });
|
||||
const account = resolveFeishuRuntimeAccount(
|
||||
{ cfg, accountId: opts.accountId },
|
||||
{ requireEventSecrets: true },
|
||||
);
|
||||
if (!account.enabled || !account.configured) {
|
||||
throw new Error(`Feishu account "${opts.accountId}" not configured or disabled`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
|
||||
export type FeishuPin = {
|
||||
|
|
@ -37,7 +37,7 @@ export async function createPinFeishu(params: {
|
|||
messageId: string;
|
||||
accountId?: string;
|
||||
}): Promise<FeishuPin | null> {
|
||||
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ export async function removePinFeishu(params: {
|
|||
messageId: string;
|
||||
accountId?: string;
|
||||
}): Promise<void> {
|
||||
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ export async function listPinsFeishu(params: {
|
|||
pageToken?: string;
|
||||
accountId?: string;
|
||||
}): Promise<{ chatId: string; pins: FeishuPin[]; hasMore: boolean; pageToken?: string }> {
|
||||
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
|
||||
export type FeishuReaction = {
|
||||
|
|
@ -10,7 +10,7 @@ export type FeishuReaction = {
|
|||
};
|
||||
|
||||
function resolveConfiguredFeishuClient(params: { cfg: ClawdbotConfig; accountId?: string }) {
|
||||
const account = resolveFeishuAccount(params);
|
||||
const account = resolveFeishuRuntimeAccount(params);
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ const addTypingIndicatorMock = vi.hoisted(() => vi.fn(async () => ({ messageId:
|
|||
const removeTypingIndicatorMock = vi.hoisted(() => vi.fn(async () => {}));
|
||||
const streamingInstances = vi.hoisted(() => [] as any[]);
|
||||
|
||||
vi.mock("./accounts.js", () => ({ resolveFeishuAccount: resolveFeishuAccountMock }));
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveFeishuAccount: resolveFeishuAccountMock,
|
||||
resolveFeishuRuntimeAccount: resolveFeishuAccountMock,
|
||||
}));
|
||||
vi.mock("./runtime.js", () => ({ getFeishuRuntime: getFeishuRuntimeMock }));
|
||||
vi.mock("./send.js", () => ({
|
||||
sendMessageFeishu: sendMessageFeishuMock,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
type ReplyPayload,
|
||||
type RuntimeEnv,
|
||||
} from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { sendMediaFeishu } from "./media.js";
|
||||
import type { MentionTarget } from "./mention.js";
|
||||
|
|
@ -110,7 +110,7 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
|||
const sendReplyToMessageId = skipReplyToInMessages ? undefined : replyToMessageId;
|
||||
const threadReplyMode = threadReply === true;
|
||||
const effectiveReplyInThread = threadReplyMode ? true : replyInThread;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
const prefixContext = createReplyPrefixContext({ cfg, agentId });
|
||||
|
||||
let typingState: TypingIndicatorState | null = null;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const createFeishuClientMock = vi.hoisted(() => vi.fn());
|
|||
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveFeishuAccount: resolveFeishuAccountMock,
|
||||
resolveFeishuRuntimeAccount: resolveFeishuAccountMock,
|
||||
}));
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { resolveReceiveIdType, normalizeFeishuTarget } from "./targets.js";
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ export function resolveFeishuSendTarget(params: {
|
|||
accountId?: string;
|
||||
}) {
|
||||
const target = params.to.trim();
|
||||
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ vi.mock("./client.js", () => ({
|
|||
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveFeishuAccount: mockResolveFeishuAccount,
|
||||
resolveFeishuRuntimeAccount: mockResolveFeishuAccount,
|
||||
}));
|
||||
|
||||
vi.mock("./runtime.js", () => ({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import type { MentionTarget } from "./mention.js";
|
||||
import { buildMentionedMessage, buildMentionedCardContent } from "./mention.js";
|
||||
|
|
@ -286,7 +286,7 @@ export async function getMessageFeishu(params: {
|
|||
accountId?: string;
|
||||
}): Promise<FeishuMessageInfo | null> {
|
||||
const { cfg, messageId, accountId } = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
@ -343,7 +343,7 @@ export async function listFeishuThreadMessages(params: {
|
|||
accountId?: string;
|
||||
}): Promise<FeishuThreadMessageInfo[]> {
|
||||
const { cfg, threadId, currentMessageId, rootMessageId, limit = 20, accountId } = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
@ -506,7 +506,7 @@ export async function editMessageFeishu(params: {
|
|||
accountId?: string;
|
||||
}): Promise<{ messageId: string; contentType: "post" | "interactive" }> {
|
||||
const { cfg, messageId, text, card, accountId } = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
@ -558,7 +558,7 @@ export async function updateCardFeishu(params: {
|
|||
accountId?: string;
|
||||
}): Promise<void> {
|
||||
const { cfg, messageId, card, accountId } = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
if (!account.configured) {
|
||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
type OpenClawConfig,
|
||||
type SecretInput,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { listFeishuAccountIds, resolveFeishuCredentials } from "./accounts.js";
|
||||
import { inspectFeishuCredentials, listFeishuAccountIds } from "./accounts.js";
|
||||
import { probeFeishu } from "./probe.js";
|
||||
import { feishuSetupAdapter } from "./setup-core.js";
|
||||
import type { FeishuConfig } from "./types.js";
|
||||
|
|
@ -165,9 +165,7 @@ export const feishuSetupWizard: ChannelSetupWizard = {
|
|||
resolveConfigured: ({ cfg }) => isFeishuConfigured(cfg),
|
||||
resolveStatusLines: async ({ cfg, configured }) => {
|
||||
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
||||
const resolvedCredentials = resolveFeishuCredentials(feishuCfg, {
|
||||
allowUnresolvedSecretRef: true,
|
||||
});
|
||||
const resolvedCredentials = inspectFeishuCredentials(feishuCfg);
|
||||
let probeResult = null;
|
||||
if (configured && resolvedCredentials) {
|
||||
try {
|
||||
|
|
@ -186,9 +184,7 @@ export const feishuSetupWizard: ChannelSetupWizard = {
|
|||
credentials: [],
|
||||
finalize: async ({ cfg, prompter, options }) => {
|
||||
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
||||
const resolved = resolveFeishuCredentials(feishuCfg, {
|
||||
allowUnresolvedSecretRef: true,
|
||||
});
|
||||
const resolved = inspectFeishuCredentials(feishuCfg);
|
||||
const hasConfigSecret = hasConfiguredSecretInput(feishuCfg?.appSecret);
|
||||
const hasConfigCreds = Boolean(
|
||||
typeof feishuCfg?.appId === "string" && feishuCfg.appId.trim() && hasConfigSecret,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||
import type { OpenClawPluginApi } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { resolveToolsConfig } from "./tools-config.js";
|
||||
import type { FeishuToolsConfig, ResolvedFeishuAccount } from "./types.js";
|
||||
|
|
@ -29,7 +29,7 @@ export function resolveFeishuToolAccount(params: {
|
|||
if (!params.api.config) {
|
||||
throw new Error("Feishu config unavailable");
|
||||
}
|
||||
return resolveFeishuAccount({
|
||||
return resolveFeishuRuntimeAccount({
|
||||
cfg: params.api.config,
|
||||
accountId:
|
||||
normalizeOptionalAccountId(params.executeParams?.accountId) ??
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { getFeishuRuntime } from "./runtime.js";
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ export async function addTypingIndicator(params: {
|
|||
runtime?: RuntimeEnv;
|
||||
}): Promise<TypingIndicatorState> {
|
||||
const { cfg, messageId, accountId, runtime } = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
if (!account.configured) {
|
||||
return { messageId, reactionId: null };
|
||||
}
|
||||
|
|
@ -168,7 +168,7 @@ export async function removeTypingIndicator(params: {
|
|||
return;
|
||||
}
|
||||
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
||||
if (!account.configured) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue