style: fix extension lint violations

This commit is contained in:
Peter Steinberger
2026-04-06 14:45:04 +01:00
parent e8141716b4
commit af62a2c2e4
380 changed files with 2067 additions and 1501 deletions

View File

@@ -176,7 +176,7 @@ function resolveConfiguredMcpServers(params: {
pluginToolsMcpBridge: boolean;
moduleUrl?: string;
}): Record<string, McpServerConfig> {
const resolved = { ...(params.mcpServers ?? {}) };
const resolved = { ...params.mcpServers };
if (!params.pluginToolsMcpBridge) {
return resolved;
}

View File

@@ -120,7 +120,7 @@ function resolveStatusTextForTag(params: {
}
if (tag === "plan") {
const entries = Array.isArray(payload.entries) ? payload.entries : [];
const first = entries.find((entry) => isRecord(entry)) as Record<string, unknown> | undefined;
const first = entries.find((entry) => isRecord(entry));
const content = asTrimmedString(first?.content);
return content ? `plan: ${content}` : null;
}
@@ -227,12 +227,12 @@ export function parsePromptEventLine(line: string): AcpRuntimeEvent | null {
case "tool_call":
return createToolCallEvent({
payload,
tag: (tag ?? "tool_call") as AcpSessionUpdateTag,
tag: tag ?? "tool_call",
});
case "tool_call_update":
return createToolCallEvent({
payload,
tag: (tag ?? "tool_call_update") as AcpSessionUpdateTag,
tag: tag ?? "tool_call_update",
});
case "agent_message_chunk":
return resolveTextChunk({

View File

@@ -227,7 +227,7 @@ export async function discoverMantleModels(params: {
contextWindow: DEFAULT_CONTEXT_WINDOW,
maxTokens: DEFAULT_MAX_TOKENS,
}))
.sort((a, b) => a.id.localeCompare(b.id));
.toSorted((a, b) => a.id.localeCompare(b.id));
discoveryCache.set(cacheKey, { models, fetchedAt: now() });
return models;

View File

@@ -177,12 +177,16 @@ function resolveBaseModelId(profile: InferenceProfileSummary): string | undefine
const firstArn = profile.models?.[0]?.modelArn;
if (firstArn) {
const arnMatch = /foundation-model\/(.+)$/.exec(firstArn);
if (arnMatch) return arnMatch[1];
if (arnMatch) {
return arnMatch[1];
}
}
if (profile.type === "SYSTEM_DEFINED") {
const id = profile.inferenceProfileId ?? "";
const prefixMatch = /^(?:us|eu|ap|jp|global)\.(.+)$/i.exec(id);
if (prefixMatch) return prefixMatch[1];
if (prefixMatch) {
return prefixMatch[1];
}
}
return undefined;
}
@@ -236,8 +240,12 @@ function resolveInferenceProfiles(
): ModelDefinitionConfig[] {
const discovered: ModelDefinitionConfig[] = [];
for (const profile of profiles) {
if (!profile.inferenceProfileId?.trim()) continue;
if (profile.status !== "ACTIVE") continue;
if (!profile.inferenceProfileId?.trim()) {
continue;
}
if (profile.status !== "ACTIVE") {
continue;
}
// Apply provider filter: check if any of the underlying models match.
if (providerFilter.length > 0) {
@@ -246,7 +254,9 @@ function resolveInferenceProfiles(
const provider = m.modelArn?.split("/")?.[1]?.split(".")?.[0];
return provider ? providerFilter.includes(provider.toLowerCase()) : false;
});
if (!matchesFilter) continue;
if (!matchesFilter) {
continue;
}
}
// Look up the underlying foundation model to inherit its capabilities.
@@ -366,7 +376,9 @@ export async function discoverBedrockModels(params: {
return discovered.toSorted((a, b) => {
const aGlobal = a.id.startsWith("global.") ? 0 : 1;
const bGlobal = b.id.startsWith("global.") ? 0 : 1;
if (aGlobal !== bGlobal) return aGlobal - bGlobal;
if (aGlobal !== bGlobal) {
return aGlobal - bGlobal;
}
return a.name.localeCompare(b.name);
});
})();
@@ -409,8 +421,8 @@ export async function resolveImplicitBedrockProvider(params: {
}): Promise<ModelProviderConfig | null> {
const env = params.env ?? process.env;
const discoveryConfig = {
...(params.config?.models?.bedrockDiscovery ?? {}),
...(params.pluginConfig?.discovery ?? {}),
...params.config?.models?.bedrockDiscovery,
...params.pluginConfig?.discovery,
};
const enabled = discoveryConfig?.enabled;
const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined;

View File

@@ -33,7 +33,9 @@ async function registerWithConfig(
});
await amazonBedrockPlugin.register(api);
const provider = providers[0];
if (!provider) throw new Error("provider registration missing");
if (!provider) {
throw new Error("provider registration missing");
}
return provider;
}

View File

@@ -40,7 +40,9 @@ function createGuardrailWrapStreamFn(
): (ctx: { modelId: string; streamFn?: StreamFn }) => StreamFn | null | undefined {
return (ctx) => {
const inner = innerWrapStreamFn(ctx);
if (!inner) return inner;
if (!inner) {
return inner;
}
return (model, context, options) => {
return streamWithPayloadPatch(inner, model, context, options, (payload) => {
const gc: Record<string, unknown> = {
@@ -88,7 +90,9 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
/** Extract the AWS region from a bedrock-runtime baseUrl. */
function extractRegionFromBaseUrl(baseUrl: string | undefined): string | undefined {
if (!baseUrl) return undefined;
if (!baseUrl) {
return undefined;
}
return bedrockRegionRe.exec(baseUrl)?.[1];
}
@@ -108,13 +112,19 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
const exact = (providers[providerId] as { baseUrl?: string } | undefined)?.baseUrl;
if (exact) {
const region = extractRegionFromBaseUrl(exact);
if (region) return region;
if (region) {
return region;
}
}
// Fall back to alias matches (e.g. "bedrock" instead of "amazon-bedrock").
for (const [key, value] of Object.entries(providers)) {
if (key === providerId || normalizeProviderId(key) !== providerId) continue;
if (key === providerId || normalizeProviderId(key) !== providerId) {
continue;
}
const region = extractRegionFromBaseUrl((value as { baseUrl?: string }).baseUrl);
if (region) return region;
if (region) {
return region;
}
}
}
return config?.models?.bedrockDiscovery?.region;

View File

@@ -1,4 +1,4 @@
import type { CliBackendPlugin, CliBackendConfig } from "openclaw/plugin-sdk/cli-backend";
import type { CliBackendPlugin } from "openclaw/plugin-sdk/cli-backend";
import {
CLI_FRESH_WATCHDOG_DEFAULTS,
CLI_RESUME_WATCHDOG_DEFAULTS,

View File

@@ -8,18 +8,15 @@ import type {
} from "openclaw/plugin-sdk/plugin-entry";
import {
applyAuthProfileConfig,
createProviderApiKeyAuthMethod,
buildTokenProfileId,
ensureApiKeyFromOptionEnvOrPrompt,
listProfilesForProvider,
normalizeApiKeyInput,
type OpenClawConfig as ProviderAuthConfig,
suggestOAuthProfileIdForLegacyDefault,
type AuthProfileStore,
buildTokenProfileId,
createProviderApiKeyAuthMethod,
listProfilesForProvider,
type OpenClawConfig as ProviderAuthConfig,
type ProviderAuthResult,
suggestOAuthProfileIdForLegacyDefault,
upsertAuthProfile,
validateAnthropicSetupToken,
validateApiKeyInput,
} from "openclaw/plugin-sdk/provider-auth";
import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shared";
import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage";
@@ -54,7 +51,7 @@ const ANTHROPIC_MODERN_MODEL_PREFIXES = [
"claude-sonnet-4-5",
"claude-haiku-4-5",
] as const;
const ANTHROPIC_OAUTH_ALLOWLIST = [
const _ANTHROPIC_OAUTH_ALLOWLIST = [
"anthropic/claude-sonnet-4-6",
"anthropic/claude-opus-4-6",
"anthropic/claude-opus-4-5",
@@ -372,7 +369,7 @@ export function registerAnthropicPlugin(api: OpenClawPluginApi): void {
const claudeCliProfileId = "anthropic:claude-cli";
const providerId = "anthropic";
const defaultAnthropicModel = "anthropic/claude-sonnet-4-6";
const anthropicOauthAllowlist = [
const _anthropicOauthAllowlist = [
"anthropic/claude-sonnet-4-6",
"anthropic/claude-opus-4-6",
"anthropic/claude-opus-4-5",

View File

@@ -52,7 +52,7 @@ export function resolveBlueBubblesAccount(params: {
const merged = mergeBlueBubblesAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const serverUrl = normalizeSecretInputString(merged.serverUrl);
const password = normalizeSecretInputString(merged.password);
const _password = normalizeSecretInputString(merged.password);
const configured = Boolean(serverUrl && hasConfiguredSecretInput(merged.password));
const baseUrl = serverUrl ? normalizeBlueBubblesServerUrl(serverUrl) : undefined;
return {

View File

@@ -10,12 +10,11 @@ import { resolveRequestUrl } from "./request-url.js";
import type { OpenClawConfig } from "./runtime-api.js";
import { getBlueBubblesRuntime, warnBlueBubbles } from "./runtime.js";
import { extractBlueBubblesMessageId, resolveBlueBubblesSendTarget } from "./send-helpers.js";
import { resolveChatGuidForTarget, createChatForHandle } from "./send.js";
import { createChatForHandle, resolveChatGuidForTarget } from "./send.js";
import {
blueBubblesFetchWithTimeout,
buildBlueBubblesApiUrl,
type BlueBubblesAttachment,
type BlueBubblesSendTarget,
type SsrFPolicy,
} from "./types.js";
@@ -127,10 +126,12 @@ export async function downloadBlueBubblesAttachment(
};
} catch (error) {
if (readMediaFetchErrorCode(error) === "max_bytes") {
throw new Error(`BlueBubbles attachment too large (limit ${maxBytes} bytes)`);
throw new Error(`BlueBubbles attachment too large (limit ${maxBytes} bytes)`, {
cause: error,
});
}
const text = error instanceof Error ? error.message : String(error);
throw new Error(`BlueBubbles attachment download failed: ${text}`);
throw new Error(`BlueBubbles attachment download failed: ${text}`, { cause: error });
}
}

View File

@@ -7,7 +7,6 @@ import {
createOpenGroupPolicyRestrictSendersWarningCollector,
projectAccountWarningCollector,
} from "openclaw/plugin-sdk/channel-policy";
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
import {
buildProbeChannelStatusSummary,
PAIRING_APPROVED_MESSAGE,
@@ -18,20 +17,15 @@ import {
createComputedAccountStatusAdapter,
createDefaultChannelRuntimeState,
} from "openclaw/plugin-sdk/status-helpers";
import {
listBlueBubblesAccountIds,
type ResolvedBlueBubblesAccount,
resolveBlueBubblesAccount,
resolveDefaultBlueBubblesAccountId,
} from "./accounts.js";
import { type ResolvedBlueBubblesAccount } from "./accounts.js";
import { bluebubblesMessageActions } from "./actions.js";
import {
bluebubblesCapabilities,
bluebubblesConfigAdapter,
bluebubblesConfigSchema,
bluebubblesMeta as meta,
bluebubblesReload,
describeBlueBubblesAccount,
bluebubblesMeta as meta,
} from "./channel-shared.js";
import type { BlueBubblesProbe } from "./channel.runtime.js";
import { createBlueBubblesConversationBindingManager } from "./conversation-bindings.js";

View File

@@ -202,7 +202,7 @@ export function createBlueBubblesConversationBindingManager(params: {
},
unbindBySessionKey: (targetSessionKey) => {
const removed: BlueBubblesConversationBindingRecord[] = [];
for (const record of [...getState().bindingsByAccountConversation.values()]) {
for (const record of getState().bindingsByAccountConversation.values()) {
if (record.accountId !== accountId || record.targetSessionKey !== targetSessionKey) {
continue;
}
@@ -214,7 +214,7 @@ export function createBlueBubblesConversationBindingManager(params: {
return removed;
},
stop: () => {
for (const key of [...getState().bindingsByAccountConversation.keys()]) {
for (const key of getState().bindingsByAccountConversation.keys()) {
if (key.startsWith(`${accountId}:`)) {
getState().bindingsByAccountConversation.delete(key);
}

View File

@@ -169,7 +169,7 @@ export async function fetchBlueBubblesHistory(
entries: historyEntries.slice(0, effectiveLimit), // Ensure we don't exceed the requested limit
resolved: true,
};
} catch (error) {
} catch {
// Continue to next path
continue;
}

View File

@@ -68,7 +68,7 @@ export function rememberBlueBubblesReplyCache(
break;
}
while (blueBubblesReplyCacheByMessageId.size > REPLY_CACHE_MAX) {
const oldest = blueBubblesReplyCacheByMessageId.keys().next().value as string | undefined;
const oldest = blueBubblesReplyCacheByMessageId.keys().next().value;
if (!oldest) {
break;
}

View File

@@ -1,7 +1,6 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
import { getBlueBubblesRuntime } from "./runtime.js";
import type { BlueBubblesAccountConfig } from "./types.js";
export {
DEFAULT_WEBHOOK_PATH,
normalizeWebhookPath,

View File

@@ -1,20 +1,18 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
import { fetchBlueBubblesHistory } from "./history.js";
import { createBlueBubblesDebounceRegistry } from "./monitor-debounce.js";
import type { NormalizedWebhookMessage } from "./monitor-normalize.js";
import { resetBlueBubblesSelfChatCache } from "./monitor-self-chat-cache.js";
import { handleBlueBubblesWebhookRequest, resolveBlueBubblesMessageId } from "./monitor.js";
import { resolveBlueBubblesMessageId } from "./monitor.js";
import {
createMockAccount,
createNewMessagePayloadForTest,
createMockRequest,
createTimestampedNewMessagePayloadForTest,
createNewMessagePayloadForTest,
createTimestampedMessageReactionPayloadForTest,
dispatchWebhookRequestForTest,
createTimestampedNewMessagePayloadForTest,
dispatchWebhookPayloadForTest,
flushAsync,
dispatchWebhookRequestForTest,
setupWebhookTargetForTest,
setupWebhookTargetsForTest,
trackWebhookRegistrationForTest,
@@ -805,7 +803,7 @@ describe("BlueBubbles webhook monitor", () => {
const core = createMockRuntime();
installTimingAwareInboundDebouncer(core);
const registration = trackWebhookRegistrationForTest(
const _registration = trackWebhookRegistrationForTest(
setupWebhookTargetForTest({
createCore: createMockRuntime,
core,

View File

@@ -1,20 +1,19 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
import { fetchBlueBubblesHistory } from "./history.js";
import { handleBlueBubblesWebhookRequest, resolveBlueBubblesMessageId } from "./monitor.js";
import {
LOOPBACK_REMOTE_ADDRESSES_FOR_TEST,
createWebhookDispatchForTest,
createMockAccount,
createHangingWebhookRequestForTest,
createLoopbackWebhookRequestParamsForTest,
createMockAccount,
createPasswordQueryRequestParamsForTest,
createProtectedWebhookAccountForTest,
createRemoteWebhookRequestParamsForTest,
createTimestampedNewMessagePayloadForTest,
createWebhookDispatchForTest,
dispatchWebhookPayloadForTest,
expectWebhookRequestStatusForTest,
expectWebhookStatusForTest,
LOOPBACK_REMOTE_ADDRESSES_FOR_TEST,
setupWebhookTargetForTest,
setupWebhookTargetsForTest,
trackWebhookRegistrationForTest,

View File

@@ -8,11 +8,7 @@ import {
type ChannelSetupWizard,
type OpenClawConfig,
} from "openclaw/plugin-sdk/setup";
import {
listBlueBubblesAccountIds,
resolveBlueBubblesAccount,
resolveDefaultBlueBubblesAccountId,
} from "./accounts.js";
import { resolveBlueBubblesAccount, resolveDefaultBlueBubblesAccountId } from "./accounts.js";
import { applyBlueBubblesConnectionConfig } from "./config-apply.js";
import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
import {
@@ -80,7 +76,7 @@ const promptBlueBubblesAllowFrom = createPromptParsedAllowFromForAccount({
});
function validateBlueBubblesServerUrlInput(value: unknown): string | undefined {
const trimmed = String(value ?? "").trim();
const trimmed = typeof value === "string" ? value.trim() : "";
if (!trimmed) {
return "Required";
}

View File

@@ -457,7 +457,7 @@ function createBraveToolDefinition(
return missingBraveKeyPayload();
}
const params = args as Record<string, unknown>;
const params = args;
const query = readStringParam(params, "query", { required: true });
const count =
readNumberParam(params, "count", { integer: true }) ??

View File

@@ -12,17 +12,17 @@ import {
validateApiKeyInput,
} from "openclaw/plugin-sdk/provider-auth";
import { buildCloudflareAiGatewayCatalogProvider } from "./catalog-provider.js";
import {
buildCloudflareAiGatewayModelDefinition,
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
resolveCloudflareAiGatewayBaseUrl,
} from "./models.js";
import { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "./models.js";
import { applyCloudflareAiGatewayConfig, buildCloudflareAiGatewayConfigPatch } from "./onboard.js";
const PROVIDER_ID = "cloudflare-ai-gateway";
const PROVIDER_ENV_VAR = "CLOUDFLARE_AI_GATEWAY_API_KEY";
const PROFILE_ID = "cloudflare-ai-gateway:default";
function readRequiredTextInput(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}
async function resolveCloudflareGatewayMetadataInteractive(ctx: {
accountId?: string;
gatewayId?: string;
@@ -38,16 +38,16 @@ async function resolveCloudflareGatewayMetadataInteractive(ctx: {
if (!accountId) {
const value = await ctx.prompter.text({
message: "Enter Cloudflare Account ID",
validate: (val) => (String(val ?? "").trim() ? undefined : "Account ID is required"),
validate: (val) => (readRequiredTextInput(val) ? undefined : "Account ID is required"),
});
accountId = String(value ?? "").trim();
accountId = readRequiredTextInput(value);
}
if (!gatewayId) {
const value = await ctx.prompter.text({
message: "Enter Cloudflare AI Gateway ID",
validate: (val) => (String(val ?? "").trim() ? undefined : "Gateway ID is required"),
validate: (val) => (readRequiredTextInput(val) ? undefined : "Gateway ID is required"),
});
gatewayId = String(value ?? "").trim();
gatewayId = readRequiredTextInput(value);
}
return { accountId, gatewayId };
}

View File

@@ -24,7 +24,7 @@ function withPluginsEnabled<T>(cfg: T): T {
return {
...record,
plugins: {
...(record.plugins && typeof record.plugins === "object" ? (record.plugins as object) : {}),
...(record.plugins && typeof record.plugins === "object" ? record.plugins : {}),
enabled: true,
},
} as T;

View File

@@ -80,11 +80,11 @@ function createApi(params?: {
},
pluginConfig: {
publicUrl: "ws://51.79.175.165:18789",
...(params?.pluginConfig ?? {}),
...params?.pluginConfig,
},
runtime: (params?.runtime ?? {}) as OpenClawPluginApi["runtime"],
registerCommand: params?.registerCommand,
}) as OpenClawPluginApi;
});
}
function registerPairCommand(params?: {

View File

@@ -106,7 +106,7 @@ describe("PlaywrightDiffScreenshotter", () => {
});
it("renders PDF output when format is pdf", async () => {
const { pages, browser, screenshotter } = await createScreenshotterHarness();
const { pages, _browser, screenshotter } = await createScreenshotterHarness();
const pdfPath = path.join(rootDir, "preview.pdf");
await screenshotter.screenshotHtml({

View File

@@ -258,6 +258,7 @@ export class PlaywrightDiffScreenshotter implements DiffScreenshotter {
const reason = error instanceof Error ? error.message : String(error);
throw new Error(
`Diff PNG/PDF rendering requires a Chromium-compatible browser. Set browser.executablePath or install Chrome/Chromium. ${reason}`,
{ cause: error },
);
} finally {
await page?.close().catch(() => {});

View File

@@ -80,7 +80,7 @@ function createApi(): OpenClawPluginApi {
},
},
runtime: {} as OpenClawPluginApi["runtime"],
}) as OpenClawPluginApi;
});
}
function createPngScreenshotter(

View File

@@ -485,7 +485,7 @@ function createApi(pluginConfig?: Record<string, unknown>): OpenClawPluginApi {
},
pluginConfig,
runtime: {} as OpenClawPluginApi["runtime"],
}) as OpenClawPluginApi;
});
}
function createToolWithScreenshotter(

View File

@@ -33,11 +33,11 @@ import { getDiscordApprovalCapability } from "./approval-native.js";
import { discordMessageActions as discordMessageActionsImpl } from "./channel-actions.js";
import {
buildTokenChannelStatusSummary,
type ChannelPlugin,
DEFAULT_ACCOUNT_ID,
PAIRING_APPROVED_MESSAGE,
projectCredentialSnapshotFields,
resolveConfiguredFromCredentialStatuses,
type ChannelPlugin,
type OpenClawConfig,
} from "./channel-api.js";
import { shouldSuppressLocalDiscordExecApprovalPrompt } from "./exec-approvals.js";

View File

@@ -11,8 +11,7 @@ import {
import { ChannelType } from "discord-api-types/v10";
import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
import { resolveCommandAuthorizedFromAuthorizers } from "openclaw/plugin-sdk/command-auth-native";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordAccountConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
@@ -33,12 +32,12 @@ import {
isDiscordGroupAllowedByPolicy,
normalizeDiscordAllowList,
normalizeDiscordSlug,
resolveGroupDmAllow,
resolveDiscordAllowListMatch,
resolveDiscordChannelConfigWithFallback,
resolveDiscordGuildEntry,
resolveDiscordMemberAccessState,
resolveDiscordOwnerAccess,
resolveGroupDmAllow,
} from "./allow-list.js";
import { formatDiscordUserTag } from "./format.js";

View File

@@ -26,8 +26,7 @@ import {
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { createNonExitingRuntime } from "openclaw/plugin-sdk/runtime-env";
import { createNonExitingRuntime, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
import { logDebug, logError } from "openclaw/plugin-sdk/text-runtime";
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
@@ -36,8 +35,10 @@ import {
parseDiscordModalCustomIdForCarbon,
} from "../component-custom-id.js";
import { resolveDiscordComponentEntry, resolveDiscordModalEntry } from "../components-registry.js";
import { type DiscordInteractiveHandlerContext } from "../interactive-dispatch.js";
import { dispatchDiscordPluginInteractiveHandler } from "../interactive-dispatch.js";
import {
dispatchDiscordPluginInteractiveHandler,
type DiscordInteractiveHandlerContext,
} from "../interactive-dispatch.js";
import { editDiscordComponentMessage } from "../send.components.js";
import {
AGENT_BUTTON_KEY,
@@ -56,14 +57,17 @@ import {
parseDiscordModalId,
resolveAgentComponentRoute,
resolveComponentCommandAuthorized,
type ComponentInteractionContext,
resolveDiscordChannelContext,
type DiscordChannelContext,
resolveDiscordInteractionId,
resolveInteractionContextWithDmAuth,
resolveInteractionCustomId,
resolveModalFieldValues,
resolvePinnedMainDmOwnerFromAllowlist,
type AgentComponentContext,
type AgentComponentInteraction,
type AgentComponentMessageInteraction,
type ComponentInteractionContext,
type DiscordChannelContext,
} from "./agent-components-helpers.js";
import {
enqueueSystemEvent,
@@ -77,8 +81,8 @@ import {
} from "./allow-list.js";
import { formatDiscordUserTag } from "./format.js";
import {
buildDiscordInboundAccessContext,
buildDiscordGroupSystemPrompt,
buildDiscordInboundAccessContext,
} from "./inbound-context.js";
import { buildDirectLabel, buildGuildLabel } from "./reply-context.js";
import { deliverDiscordReply } from "./reply-delivery.js";
@@ -100,6 +104,10 @@ async function loadComponentsRuntime() {
return await componentsRuntimePromise;
}
async function loadReplyRuntime() {
replyRuntimePromise ??= import("openclaw/plugin-sdk/reply-runtime");
return await replyRuntimePromise;
}
async function loadReplyPipelineRuntime() {
replyPipelineRuntimePromise ??= import("openclaw/plugin-sdk/channel-reply-pipeline");
return await replyPipelineRuntimePromise;

View File

@@ -10,15 +10,7 @@ import {
type TopLevelComponents,
} from "@buape/carbon";
import { ButtonStyle, Routes } from "discord-api-types/v10";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime";
import {
createChannelNativeApprovalRuntime,
type ExecApprovalChannelRuntime,
} from "openclaw/plugin-sdk/infra-runtime";
import { buildExecApprovalActionDescriptors } from "openclaw/plugin-sdk/infra-runtime";
import { resolveExecApprovalCommandDisplay } from "openclaw/plugin-sdk/infra-runtime";
import { getExecApprovalApproverDmNoticeText } from "openclaw/plugin-sdk/infra-runtime";
import type { DiscordExecApprovalConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type {
ExecApprovalActionDescriptor,
ExecApprovalDecision,
@@ -27,6 +19,13 @@ import type {
PluginApprovalRequest,
PluginApprovalResolved,
} from "openclaw/plugin-sdk/infra-runtime";
import {
buildExecApprovalActionDescriptors,
createChannelNativeApprovalRuntime,
getExecApprovalApproverDmNoticeText,
resolveExecApprovalCommandDisplay,
type ExecApprovalChannelRuntime,
} from "openclaw/plugin-sdk/infra-runtime";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { logDebug, logError } from "openclaw/plugin-sdk/text-runtime";
import {
@@ -560,7 +559,12 @@ export class DiscordExecApprovalHandler {
},
};
},
deliverTarget: async ({ plannedTarget, preparedTarget, pendingContent }) => {
deliverTarget: async ({
plannedTarget,
preparedTarget,
pendingContent,
request: _request,
}) => {
const { rest, request: discordRequest } = createDiscordClient(
{ token: this.opts.token, accountId: this.opts.accountId },
this.opts.cfg,

View File

@@ -187,13 +187,12 @@ function createAllowedGuildEntries(requireMention = false) {
function createHydratedGuildClient(restPayload: Record<string, unknown>) {
const restGet = vi.fn(async () => restPayload);
const baseClient = createGuildTextClient(CHANNEL_ID);
const client = {
...Object.assign(Object.create(Object.getPrototypeOf(baseClient)), baseClient),
const client = Object.assign(createGuildTextClient(CHANNEL_ID), {
rest: {
get: restGet,
},
} as unknown as Parameters<typeof preflightDiscordMessage>[0]["client"];
});
}) as unknown as Parameters<typeof preflightDiscordMessage>[0]["client"];
return { client, restGet };
}

View File

@@ -480,13 +480,12 @@ describe("preflightDiscordMessage", () => {
bot: true,
},
}));
const baseClient = createThreadClient({ threadId, parentId });
const client = {
...Object.assign(Object.create(Object.getPrototypeOf(baseClient)), baseClient),
const client = Object.assign(createThreadClient({ threadId, parentId }), {
rest: {
get: restGet,
},
} as unknown as DiscordClient;
});
}) as unknown as DiscordClient;
const result = await preflightDiscordMessage({
...createPreflightArgs({

View File

@@ -10,16 +10,14 @@ import {
import { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth-native";
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime";
import { isDangerousNameMatchingEnabled, loadConfig } from "openclaw/plugin-sdk/config-runtime";
import type { SessionBindingRecord } from "openclaw/plugin-sdk/conversation-binding-runtime";
import { enqueueSystemEvent, recordChannelActivity } from "openclaw/plugin-sdk/infra-runtime";
import {
recordPendingHistoryEntryIfEnabled,
type HistoryEntry,
} from "openclaw/plugin-sdk/reply-history";
import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
import { getChildLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
import { logDebug } from "openclaw/plugin-sdk/text-runtime";
import { resolveDefaultDiscordAccountId } from "../accounts.js";
import {
@@ -271,31 +269,42 @@ function mergeFetchedDiscordMessage(base: Message, fetched: APIMessage): Message
globalName: mention.global_name ?? undefined,
}))
: undefined;
const baseReferencedMessage = (base as { referencedMessage?: object }).referencedMessage;
const assignWithPrototype = <T extends object>(baseObject: T, ...sources: object[]): T =>
Object.assign(
Object.create(Object.getPrototypeOf(baseObject) ?? Object.prototype),
baseObject,
...sources,
) as T;
const referencedMessage = fetched.referenced_message
? ({
...baseReferencedMessage,
...fetched.referenced_message,
mentionedUsers: Array.isArray(fetched.referenced_message.mentions)
? fetched.referenced_message.mentions.map((mention) => ({
...mention,
globalName: mention.global_name ?? undefined,
}))
: (baseReferenced?.mentionedUsers ?? []),
mentionedRoles:
fetched.referenced_message.mention_roles ?? baseReferenced?.mentionedRoles ?? [],
mentionedEveryone:
fetched.referenced_message.mention_everyone ?? baseReferenced?.mentionedEveryone ?? false,
} satisfies Record<string, unknown>)
? assignWithPrototype(
((base as { referencedMessage?: Message }).referencedMessage ?? {}) as Message,
fetched.referenced_message,
{
mentionedUsers: Array.isArray(fetched.referenced_message.mentions)
? fetched.referenced_message.mentions.map((mention) => ({
...mention,
globalName: mention.global_name ?? undefined,
}))
: (baseReferenced?.mentionedUsers ?? []),
mentionedRoles:
fetched.referenced_message.mention_roles ?? baseReferenced?.mentionedRoles ?? [],
mentionedEveryone:
fetched.referenced_message.mention_everyone ??
baseReferenced?.mentionedEveryone ??
false,
} satisfies Record<string, unknown>,
)
: (base as { referencedMessage?: Message }).referencedMessage;
const baseRawData = (base as { rawData?: Record<string, unknown> }).rawData;
const rawData = {
...baseRawData,
message_snapshots: fetched.message_snapshots ?? baseRawData?.message_snapshots,
...(base as { rawData?: Record<string, unknown> }).rawData,
message_snapshots:
fetched.message_snapshots ??
(base as { rawData?: { message_snapshots?: unknown } }).rawData?.message_snapshots,
sticker_items:
(fetched as { sticker_items?: unknown }).sticker_items ?? baseRawData?.sticker_items,
};
return Object.assign(Object.create(Object.getPrototypeOf(base)), base, fetched, {
return assignWithPrototype(base, fetched, {
content: fetched.content ?? base.content,
attachments: fetched.attachments ?? base.attachments,
embeds: fetched.embeds ?? base.embeds,
@@ -308,7 +317,7 @@ function mergeFetchedDiscordMessage(base: Message, fetched: APIMessage): Message
mentionedEveryone: fetched.mention_everyone ?? base.mentionedEveryone,
referencedMessage,
rawData,
}) as Message;
}) as unknown as Message;
}
async function hydrateDiscordMessageIfEmpty(params: {

View File

@@ -1,6 +1,9 @@
import { ChannelType, type RequestClient } from "@buape/carbon";
import { resolveAckReaction, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
import { EmbeddedBlockChunker } from "openclaw/plugin-sdk/agent-runtime";
import {
EmbeddedBlockChunker,
resolveAckReaction,
resolveHumanDelayConfig,
} from "openclaw/plugin-sdk/agent-runtime";
import {
createStatusReactionController,
DEFAULT_TIMING,
@@ -14,28 +17,32 @@ import {
} from "openclaw/plugin-sdk/channel-inbound";
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
import { resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-streaming";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime";
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/config-runtime";
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/config-runtime";
import {
isDangerousNameMatchingEnabled,
readSessionUpdatedAt,
resolveChannelContextVisibilityMode,
resolveMarkdownTableMode,
resolveStorePath,
} from "openclaw/plugin-sdk/config-runtime";
import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import {
buildPendingHistoryContextFromMap,
clearHistoryEntriesIfEnabled,
} from "openclaw/plugin-sdk/reply-history";
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
import { buildAgentSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime";
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-runtime";
import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-runtime";
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-runtime";
import {
convertMarkdownTables,
stripInlineDirectiveTagsForDelivery,
stripReasoningTagsFromText,
truncateUtf16Safe,
} from "openclaw/plugin-sdk/text-runtime";
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
import { chunkDiscordTextWithMode } from "../chunk.js";
import { resolveDiscordDraftStreamingChunking } from "../draft-chunking.js";

View File

@@ -5,8 +5,7 @@ import type {
StringSelectMenuInteraction,
} from "@buape/carbon";
import { ChannelType } from "discord-api-types/v10";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordAccountConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { buildPluginBindingApprovalCustomId } from "openclaw/plugin-sdk/conversation-runtime";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { type DiscordComponentEntry, type DiscordModalEntry } from "../components.js";

View File

@@ -3,12 +3,12 @@ import {
ChannelType,
Command,
StringSelectMenu,
type TopLevelComponents,
type AutocompleteInteraction,
type ButtonInteraction,
type CommandInteraction,
type CommandOptions,
type StringSelectMenuInteraction,
type TopLevelComponents,
} from "@buape/carbon";
import { ApplicationCommandOptionType } from "discord-api-types/v10";
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
@@ -45,8 +45,7 @@ import {
resolveSendableOutboundReplyParts,
resolveTextChunksWithFallback,
} from "openclaw/plugin-sdk/reply-payload";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
@@ -54,13 +53,13 @@ import { chunkDiscordTextWithMode } from "../chunk.js";
import {
normalizeDiscordAllowList,
normalizeDiscordSlug,
resolveDiscordChannelPolicyCommandAuthorizer,
resolveGroupDmAllow,
resolveDiscordChannelConfigWithFallback,
resolveDiscordAllowListMatch,
resolveDiscordChannelConfigWithFallback,
resolveDiscordChannelPolicyCommandAuthorizer,
resolveDiscordGuildEntry,
resolveDiscordMemberAccessState,
resolveDiscordOwnerAccess,
resolveGroupDmAllow,
} from "./allow-list.js";
import { resolveDiscordDmCommandAccess } from "./dm-command-auth.js";
import { handleDiscordDmCommandDecision } from "./dm-command-decision.js";

View File

@@ -16,6 +16,7 @@ const {
clientFetchUserMock,
clientGetPluginMock,
clientHandleDeployRequestMock,
_createDiscordAutoPresenceControllerMock,
createDiscordMessageHandlerMock,
createDiscordNativeCommandMock,
createdBindingManagers,
@@ -28,6 +29,7 @@ const {
listSkillCommandsForAgentsMock,
monitorLifecycleMock,
reconcileAcpThreadBindingsOnStartupMock,
_resolveDiscordAllowlistConfigMock,
resolveDiscordAccountMock,
resolveNativeCommandsEnabledMock,
resolveNativeSkillsEnabledMock,

View File

@@ -672,3 +672,34 @@ export function resolveDiscordReplyDeliveryPlan(params: {
});
return { deliverTarget, replyTarget, replyReference };
}
/**
* Extract text from forwarded message snapshots for thread starter resolution.
* Discord forwarded messages have empty `content` and store the original text
* in `message_snapshots[0].message.content`.
*/
function resolveStarterForwardedText(
snapshots?: Array<{
message?: {
content?: string | null;
attachments?: unknown[];
embeds?: Array<{ title?: string | null; description?: string | null }>;
sticker_items?: unknown[];
};
}>,
): string {
if (!Array.isArray(snapshots) || snapshots.length === 0) {
return "";
}
const blocks: string[] = [];
for (const snapshot of snapshots) {
const msg = snapshot.message;
if (!msg) {
continue;
}
const text = msg.content?.trim() || resolveDiscordEmbedText(msg.embeds?.[0]) || "";
if (text) {
blocks.push(`[Forwarded message]\n${text}`);
}
}
return blocks.join("\n\n");
}

View File

@@ -1,6 +1,5 @@
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
import type { DiscordGuildEntry } from "openclaw/plugin-sdk/config-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordGuildEntry, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { ChannelSetupDmPolicy, ChannelSetupWizard } from "openclaw/plugin-sdk/setup-runtime";
import { createStandardChannelSetupStatus } from "openclaw/plugin-sdk/setup-runtime";
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";

View File

@@ -1,7 +1,7 @@
import {
type ChannelSetupWizard,
type OpenClawConfig,
type WizardPrompter,
type ChannelSetupWizard,
} from "openclaw/plugin-sdk/setup-runtime";
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import { resolveDiscordChannelAllowlist } from "./resolve-channels.js";

View File

@@ -12,7 +12,7 @@ vi.mock("./ddg-client.js", () => ({
describe("duckduckgo web search provider", () => {
let createDuckDuckGoWebSearchProvider: typeof import("./ddg-search-provider.js").createDuckDuckGoWebSearchProvider;
let ddgClientTesting: typeof import("./ddg-client.js").__testing;
let plugin: typeof import("../index.js").default;
let _plugin: typeof import("../index.js").default;
beforeAll(async () => {
({ createDuckDuckGoWebSearchProvider } = await import("./ddg-search-provider.js"));

View File

@@ -158,9 +158,9 @@ function mergeVoiceSettingsOverride(
next: Record<string, unknown>,
): SpeechProviderOverrides {
return {
...(ctx.currentOverrides ?? {}),
...ctx.currentOverrides,
voiceSettings: {
...(asObject(ctx.currentOverrides?.voiceSettings) ?? {}),
...asObject(ctx.currentOverrides?.voiceSettings),
...next,
},
};
@@ -181,7 +181,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
}
return {
handled: true,
overrides: { ...(ctx.currentOverrides ?? {}), voiceId: ctx.value },
overrides: { ...ctx.currentOverrides, voiceId: ctx.value },
};
case "model":
case "modelid":
@@ -193,7 +193,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
}
return {
handled: true,
overrides: { ...(ctx.currentOverrides ?? {}), modelId: ctx.value },
overrides: { ...ctx.currentOverrides, modelId: ctx.value },
};
case "stability": {
if (!ctx.policy.allowVoiceSettings) {
@@ -269,7 +269,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
return {
handled: true,
overrides: {
...(ctx.currentOverrides ?? {}),
...ctx.currentOverrides,
applyTextNormalization: normalizeApplyTextNormalization(ctx.value),
},
};
@@ -282,7 +282,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
return {
handled: true,
overrides: {
...(ctx.currentOverrides ?? {}),
...ctx.currentOverrides,
languageCode: normalizeLanguageCode(ctx.value),
},
};
@@ -293,7 +293,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
return {
handled: true,
overrides: {
...(ctx.currentOverrides ?? {}),
...ctx.currentOverrides,
seed: normalizeSeed(Number.parseInt(ctx.value, 10)),
},
};

View File

@@ -1,5 +1,4 @@
import { describe, expect, it } from "vitest";
import plugin from "../index.js";
import { __testing, createExaWebSearchProvider } from "./exa-web-search-provider.js";
describe("exa web search provider", () => {

View File

@@ -187,11 +187,9 @@ function parseExaContents(
if ("maxCharacters" in obj && parsePositiveInteger(obj.maxCharacters) === undefined) {
return invalidContentsPayload("contents.text.maxCharacters must be a positive integer.");
}
return {
...(parsePositiveInteger(obj.maxCharacters)
? { maxCharacters: parsePositiveInteger(obj.maxCharacters) }
: {}),
};
return parsePositiveInteger(obj.maxCharacters)
? { maxCharacters: parsePositiveInteger(obj.maxCharacters) }
: {};
};
const parseHighlights = (
@@ -457,7 +455,7 @@ function createExaToolDefinition(
"Search the web using Exa AI. Supports neural or keyword search, publication date filters, and optional highlights or text extraction.",
parameters: createExaSchema(),
execute: async (args) => {
const params = args as Record<string, unknown>;
const params = args;
const exaConfig = resolveExaConfig(searchConfig);
const apiKey = resolveExaApiKey(exaConfig);
if (!apiKey) {

View File

@@ -16,7 +16,7 @@ import type {
} from "./types.js";
const {
listConfiguredAccountIds,
_listConfiguredAccountIds,
listAccountIds: listFeishuAccountIds,
resolveDefaultAccountId,
} = createAccountListHelpers("feishu", {

View File

@@ -279,7 +279,7 @@ async function cleanupNewBitable(
});
cleanedFields++;
} catch (err) {
logger.debug(`Failed to rename primary field: ${err}`);
logger.debug(`Failed to rename primary field: ${String(err)}`);
}
}
@@ -300,7 +300,7 @@ async function cleanupNewBitable(
});
cleanedFields++;
} catch (err) {
logger.debug(`Failed to delete default field ${field.field_name}: ${err}`);
logger.debug(`Failed to delete default field ${field.field_name}: ${String(err)}`);
}
}
}
@@ -334,7 +334,7 @@ async function cleanupNewBitable(
});
cleanedRows++;
} catch (err) {
logger.debug(`Failed to delete empty row ${recordId}: ${err}`);
logger.debug(`Failed to delete empty row ${recordId}: ${String(err)}`);
}
}
}
@@ -381,7 +381,7 @@ async function createApp(
}
}
} catch (err) {
log.debug(`Cleanup failed (non-critical): ${err}`);
log.debug(`Cleanup failed (non-critical): ${String(err)}`);
}
return {

View File

@@ -2,15 +2,9 @@ import type * as ConversationRuntime from "openclaw/plugin-sdk/conversation-runt
import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
import type { ClawdbotConfig, PluginRuntime } from "../runtime-api.js";
import type { FeishuMessageEvent } from "./bot.js";
import {
buildBroadcastSessionKey,
buildFeishuAgentBody,
handleFeishuMessage,
resolveBroadcastAgents,
toMessageResourceType,
} from "./bot.js";
import { handleFeishuMessage } from "./bot.js";
import { setFeishuRuntime } from "./runtime.js";
type ConfiguredBindingRoute = ReturnType<typeof ConversationRuntime.resolveConfiguredBindingRoute>;
@@ -166,7 +160,7 @@ function buildDefaultResolveRoute(): ResolvedAgentRoute {
};
}
function createUnboundConfiguredRoute(
function _createUnboundConfiguredRoute(
route: NonNullable<ConfiguredBindingRoute>["route"],
): ConfiguredBindingRoute {
return { bindingResolution: null, route };
@@ -202,7 +196,7 @@ function createFeishuBotRuntime(overrides: DeepPartial<PluginRuntime> = {}): Plu
upsertPairingRequest: vi.fn(),
buildPairingReply: vi.fn(),
},
...(overrides.channel ?? {}),
...overrides.channel,
},
...(overrides.system ? { system: overrides.system as PluginRuntime["system"] } : {}),
...(overrides.media ? { media: overrides.media as PluginRuntime["media"] } : {}),

View File

@@ -111,9 +111,13 @@ export type FeishuBotAddedEvent = {
// Returns null if no broadcast config exists or the peer is not in the broadcast list.
export function resolveBroadcastAgents(cfg: ClawdbotConfig, peerId: string): string[] | null {
const broadcast = (cfg as Record<string, unknown>).broadcast;
if (!broadcast || typeof broadcast !== "object") return null;
if (!broadcast || typeof broadcast !== "object") {
return null;
}
const agents = (broadcast as Record<string, unknown>)[peerId];
if (!Array.isArray(agents) || agents.length === 0) return null;
if (!Array.isArray(agents) || agents.length === 0) {
return null;
}
return agents as string[];
}
@@ -383,7 +387,9 @@ export async function handleFeishuMessage(params: {
senderId: ctx.senderOpenId,
log,
});
if (senderResult.name) ctx = { ...ctx, senderName: senderResult.name };
if (senderResult.name) {
ctx = { ...ctx, senderName: senderResult.name };
}
// Track permission error to inform agent later (with cooldown to avoid repetition)
if (senderResult.permissionError) {
@@ -1092,9 +1098,10 @@ export async function handleFeishuMessage(params: {
}
// --- Broadcast dispatch: send message to all configured agents ---
const strategy =
((cfg as Record<string, unknown>).broadcast as Record<string, unknown> | undefined)
?.strategy || "parallel";
const rawStrategy = (
(cfg as Record<string, unknown>).broadcast as Record<string, unknown> | undefined
)?.strategy;
const strategy = rawStrategy === "sequential" ? "sequential" : "parallel";
const activeAgentId =
ctx.mentionedBot || !requireMention ? normalizeAgentId(route.agentId) : null;
const agentIds = (cfg.agents?.list ?? []).map((a: { id: string }) => normalizeAgentId(a.id));

View File

@@ -25,14 +25,20 @@ import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-run
import { createComputedAccountStatusAdapter } from "openclaw/plugin-sdk/status-helpers";
import {
inspectFeishuCredentials,
listEnabledFeishuAccounts,
listFeishuAccountIds,
resolveDefaultFeishuAccountId,
resolveFeishuAccount,
resolveFeishuRuntimeAccount,
listFeishuAccountIds,
listEnabledFeishuAccounts,
resolveDefaultFeishuAccountId,
} from "./accounts.js";
import { feishuApprovalAuth } from "./approval-auth.js";
import { FEISHU_CARD_INTERACTION_VERSION } from "./card-interaction.js";
import type {
ChannelMessageActionName,
ChannelMeta,
ChannelPlugin,
ClawdbotConfig,
} from "./channel-runtime-api.js";
import {
buildChannelConfigSchema,
buildProbeChannelStatusSummary,
@@ -42,35 +48,25 @@ import {
DEFAULT_ACCOUNT_ID,
PAIRING_APPROVED_MESSAGE,
} from "./channel-runtime-api.js";
import type {
ChannelMessageActionName,
ChannelMeta,
ChannelPlugin,
ClawdbotConfig,
} from "./channel-runtime-api.js";
import { createFeishuClient } from "./client.js";
import { FeishuConfigSchema } from "./config-schema.js";
import {
buildFeishuModelOverrideParentCandidates,
buildFeishuConversationId,
buildFeishuModelOverrideParentCandidates,
parseFeishuConversationId,
parseFeishuDirectConversationId,
parseFeishuTargetId,
} from "./conversation-id.js";
import { listFeishuDirectoryPeers, listFeishuDirectoryGroups } from "./directory.static.js";
import { listFeishuDirectoryGroups, listFeishuDirectoryPeers } from "./directory.static.js";
import { messageActionTargetAliases } from "./message-action-contract.js";
import { resolveFeishuGroupToolPolicy } from "./policy.js";
import { getFeishuRuntime } from "./runtime.js";
import { collectRuntimeConfigAssignments, secretTargetRegistryEntries } from "./secret-contract.js";
import { collectFeishuSecurityAuditFindings } from "./security-audit.js";
import {
resolveFeishuParentConversationCandidates,
resolveFeishuSessionConversation,
} from "./session-conversation.js";
import { resolveFeishuSessionConversation } from "./session-conversation.js";
import { resolveFeishuOutboundSessionRoute } from "./session-route.js";
import { feishuSetupAdapter } from "./setup-core.js";
import { feishuSetupWizard } from "./setup-surface.js";
import { normalizeFeishuTarget, looksLikeFeishuId, formatFeishuTarget } from "./targets.js";
import { looksLikeFeishuId, normalizeFeishuTarget } from "./targets.js";
import type { FeishuConfig, FeishuProbeResult, ResolvedFeishuAccount } from "./types.js";
function readFeishuMediaParam(params: Record<string, unknown>): string | undefined {
@@ -231,8 +227,7 @@ function setFeishuNamedAccountEnabled(
const feishuConfigAdapter = createHybridChannelConfigAdapter<
ResolvedFeishuAccount,
ResolvedFeishuAccount,
ClawdbotConfig
ResolvedFeishuAccount
>({
sectionKey: "feishu",
listAccountIds: listFeishuAccountIds,

View File

@@ -1,6 +1,6 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { FeishuConfigSchema } from "./config-schema.js";
import type { FeishuConfig, ResolvedFeishuAccount } from "./types.js";
import type { ResolvedFeishuAccount } from "./types.js";
type CreateFeishuClient = typeof import("./client.js").createFeishuClient;
type CreateFeishuWSClient = typeof import("./client.js").createFeishuWSClient;

View File

@@ -42,7 +42,9 @@ function getWsProxyAgent(): HttpsProxyAgent<string> | undefined {
process.env.HTTPS_PROXY ||
process.env.http_proxy ||
process.env.HTTP_PROXY;
if (!proxyUrl) return undefined;
if (!proxyUrl) {
return undefined;
}
return new httpsProxyAgentCtor(proxyUrl);
}

View File

@@ -48,11 +48,15 @@ function collectDescendants(
const visited = new Set<string>();
function collect(blockId: string) {
if (visited.has(blockId)) return;
if (visited.has(blockId)) {
return;
}
visited.add(blockId);
const block = blockMap.get(blockId);
if (!block) return;
if (!block) {
return;
}
result.push(block);

View File

@@ -114,13 +114,13 @@ export function calculateAdaptiveColumnWidths(
// Calculate weighted length (CJK chars count as 2)
// CJK (Chinese/Japanese/Korean) characters render ~2x wider than ASCII
function getWeightedLength(text: string): number {
return [...text].reduce((sum, char) => {
return Array.from(text).reduce((sum, char) => {
return sum + (char.charCodeAt(0) > 255 ? 2 : 1);
}, 0);
}
// Find max content length per column
const maxLengths: number[] = new Array(column_size).fill(0);
const maxLengths = Array.from({ length: column_size }, () => 0);
for (let row = 0; row < row_size; row++) {
for (let col = 0; col < column_size; col++) {
@@ -143,7 +143,7 @@ export function calculateAdaptiveColumnWidths(
MIN_COLUMN_WIDTH,
Math.min(MAX_COLUMN_WIDTH, Math.floor(totalWidth / column_size)),
);
return new Array(column_size).fill(equalWidth);
return Array.from({ length: column_size }, () => equalWidth);
}
// Calculate proportional widths
@@ -160,11 +160,15 @@ export function calculateAdaptiveColumnWidths(
while (remaining > 0) {
// Find columns that can still grow (not at max)
const growable = widths.map((w, i) => (w < MAX_COLUMN_WIDTH ? i : -1)).filter((i) => i >= 0);
if (growable.length === 0) break;
if (growable.length === 0) {
break;
}
// Distribute evenly among growable columns
const perColumn = Math.floor(remaining / growable.length);
if (perColumn === 0) break;
if (perColumn === 0) {
break;
}
for (const i of growable) {
const add = Math.min(perColumn, MAX_COLUMN_WIDTH - widths[i]);

View File

@@ -155,7 +155,7 @@ describe("feishu_doc image fetch hardening", () => {
registerFeishuDocTools(harness.api);
const tool = harness.resolveTool("feishu_doc", context);
expect(tool).toBeDefined();
return tool as ToolLike;
return tool;
}
async function executeFeishuDocTool(

View File

@@ -205,7 +205,7 @@ function normalizeConvertedBlockTree(
const parentId = typeof block?.parent_id === "string" ? block.parent_id : "";
return !childIds.has(blockId) && (!parentId || !byId.has(parentId));
})
.sort(
.toSorted(
(a, b) =>
(originalOrder.get(a.block_id ?? "__missing__") ?? 0) -
(originalOrder.get(b.block_id ?? "__missing__") ?? 0),
@@ -396,7 +396,7 @@ async function chunkedConvertMarkdown(client: Lark.Client, markdown: string) {
}
/** Insert blocks in batches of MAX_BLOCKS_PER_INSERT to avoid API 400 errors */
async function chunkedInsertBlocks(
async function _chunkedInsertBlocks(
client: Lark.Client,
docToken: string,
blocks: FeishuDocxBlock[],
@@ -894,7 +894,7 @@ async function createDoc(
}
const shouldGrantToRequester = options?.grantToRequester !== false;
const requesterOpenId = options?.requesterOpenId?.trim();
const requesterPermType: "edit" = "edit";
const requesterPermType = "edit" as const;
let requesterPermissionAdded = false;
let requesterPermissionSkippedReason: string | undefined;
@@ -1009,7 +1009,9 @@ async function insertDoc(
const blockInfo = await client.docx.documentBlock.get({
path: { document_id: docToken, block_id: afterBlockId },
});
if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
if (blockInfo.code !== 0) {
throw new Error(blockInfo.msg);
}
const parentId = blockInfo.data?.block?.parent_id ?? docToken;
@@ -1023,7 +1025,9 @@ async function insertDoc(
path: { document_id: docToken, block_id: parentId },
params: pageToken ? { page_token: pageToken } : {},
});
if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
if (childrenRes.code !== 0) {
throw new Error(childrenRes.msg);
}
items.push(...(childrenRes.data?.items ?? []));
pageToken = childrenRes.data?.page_token ?? undefined;
} while (pageToken);
@@ -1039,7 +1043,9 @@ async function insertDoc(
logger?.info?.("feishu_doc: Converting markdown...");
const { blocks, firstLevelBlockIds } = await chunkedConvertMarkdown(client, markdown);
if (blocks.length === 0) throw new Error("Content is empty");
if (blocks.length === 0) {
throw new Error("Content is empty");
}
const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
logger?.info?.(
@@ -1144,8 +1150,8 @@ async function writeTableCells(
}
const tableData = tableBlock.table;
const rows = tableData?.property?.row_size as number | undefined;
const cols = tableData?.property?.column_size as number | undefined;
const rows = tableData?.property?.row_size;
const cols = tableData?.property?.column_size;
const cellIds = tableData?.cells ?? [];
if (!rows || !cols || !cellIds.length) {
@@ -1163,7 +1169,9 @@ async function writeTableCells(
for (let c = 0; c < writeCols; c++) {
const cellId = cellIds[r * cols + c];
if (!cellId) continue;
if (!cellId) {
continue;
}
// table cell is a container block: clear existing children, then create text child blocks
const childrenRes = await client.docx.documentBlockChildren.get({

View File

@@ -334,12 +334,17 @@ function applyCommentFileTypeDefault<
function formatDriveApiError(error: unknown): string {
if (!isRecord(error)) {
return String(error);
return typeof error === "string" ? error : JSON.stringify(error);
}
const response = isRecord(error.response) ? error.response : undefined;
const responseData = isRecord(response?.data) ? response?.data : undefined;
return JSON.stringify({
message: typeof error.message === "string" ? error.message : String(error),
message:
typeof error.message === "string"
? error.message
: typeof error === "string"
? error
: JSON.stringify(error),
code: readString(error.code),
method: readString(isRecord(error.config) ? error.config.method : undefined),
url: readString(isRecord(error.config) ? error.config.url : undefined),
@@ -360,12 +365,17 @@ function extractDriveApiErrorMeta(error: unknown): {
feishuLogId?: string;
} {
if (!isRecord(error)) {
return { message: String(error) };
return { message: typeof error === "string" ? error : JSON.stringify(error) };
}
const response = isRecord(error.response) ? error.response : undefined;
const responseData = isRecord(response?.data) ? response?.data : undefined;
return {
message: typeof error.message === "string" ? error.message : String(error),
message:
typeof error.message === "string"
? error.message
: typeof error === "string"
? error
: JSON.stringify(error),
httpStatus: typeof response?.status === "number" ? response.status : undefined,
feishuCode:
typeof responseData?.code === "number" ? responseData.code : readString(responseData?.code),
@@ -665,7 +675,7 @@ export async function replyComment(
)}/replies`;
const query = { file_type: params.file_type };
try {
const response = (await requestDriveApi<FeishuDriveApiResponse<Record<string, unknown>>>({
const response = await requestDriveApi<FeishuDriveApiResponse<Record<string, unknown>>>({
client,
method: "POST",
url,
@@ -682,7 +692,7 @@ export async function replyComment(
],
},
},
})) as FeishuDriveApiResponse<Record<string, unknown>>;
});
if (response.code === 0) {
return {
success: true,

View File

@@ -83,12 +83,12 @@ const {
createFeishuReplyDispatcherMock,
resolveBoundConversationMock,
touchBindingMock,
resolveAgentRouteMock,
_resolveAgentRouteMock,
resolveConfiguredBindingRouteMock,
ensureConfiguredBindingRouteReadyMock,
dispatchReplyFromConfigMock,
withReplyDispatcherMock,
finalizeInboundContextMock,
_dispatchReplyFromConfigMock,
_withReplyDispatcherMock,
_finalizeInboundContextMock,
getMessageFeishuMock,
listFeishuThreadMessagesMock,
sendMessageFeishuMock,

View File

@@ -26,7 +26,7 @@ const {
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
let lastRuntime: RuntimeEnv | null = null;
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const { cfg: lifecycleConfig, account: lifecycleAccount } = createFeishuLifecycleFixture({

View File

@@ -1,22 +1,21 @@
import "./lifecycle.test-support.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
import type { RuntimeEnv } from "../runtime-api.js";
import "./lifecycle.test-support.js";
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
import {
createFeishuLifecycleConfig,
createFeishuLifecycleReplyDispatcher,
createResolvedFeishuLifecycleAccount,
expectFeishuReplyDispatcherSentFinalReplyOnce,
expectFeishuReplyPipelineDedupedAcrossReplay,
expectFeishuSingleEffectAcrossReplay,
expectFeishuReplyDispatcherSentFinalReplyOnce,
installFeishuLifecycleReplyRuntime,
mockFeishuReplyOnceDispatch,
restoreFeishuLifecycleStateDir,
setFeishuLifecycleStateDir,
setupFeishuLifecycleHandler,
} from "./test-support/lifecycle-test-support.js";
import type { ResolvedFeishuAccount } from "./types.js";
const {
createEventDispatcherMock,
@@ -30,7 +29,7 @@ const {
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
let lastRuntime: RuntimeEnv | null = null;
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const lifecycleConfig = createFeishuLifecycleConfig({
@@ -43,7 +42,7 @@ const lifecycleConfig = createFeishuLifecycleConfig({
accountConfig: {
dmPolicy: "open",
},
}) as ClawdbotConfig;
});
const lifecycleAccount = createResolvedFeishuLifecycleAccount({
accountId: "acct-menu",
@@ -52,7 +51,7 @@ const lifecycleAccount = createResolvedFeishuLifecycleAccount({
config: {
dmPolicy: "open",
},
}) as ResolvedFeishuAccount;
});
function createBotMenuEvent(params: { eventKey: string; timestamp: string }) {
return {

View File

@@ -23,7 +23,7 @@ const {
finalizeInboundContextMock,
resolveAgentRouteMock,
resolveBoundConversationMock,
sendMessageFeishuMock,
_sendMessageFeishuMock,
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();

View File

@@ -1,24 +1,23 @@
import "./lifecycle.test-support.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
import type { RuntimeEnv } from "../runtime-api.js";
import { resetProcessedFeishuCardActionTokensForTests } from "./card-action.js";
import { createFeishuCardInteractionEnvelope } from "./card-interaction.js";
import "./lifecycle.test-support.js";
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
import {
createFeishuLifecycleConfig,
createFeishuLifecycleReplyDispatcher,
createResolvedFeishuLifecycleAccount,
expectFeishuReplyDispatcherSentFinalReplyOnce,
expectFeishuReplyPipelineDedupedAcrossReplay,
expectFeishuReplyPipelineDedupedAfterPostSendFailure,
expectFeishuReplyDispatcherSentFinalReplyOnce,
installFeishuLifecycleReplyRuntime,
mockFeishuReplyOnceDispatch,
restoreFeishuLifecycleStateDir,
setFeishuLifecycleStateDir,
setupFeishuLifecycleHandler,
} from "./test-support/lifecycle-test-support.js";
import type { ResolvedFeishuAccount } from "./types.js";
const {
createEventDispatcherMock,
@@ -33,7 +32,7 @@ const {
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
let lastRuntime: RuntimeEnv | null = null;
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const lifecycleConfig = createFeishuLifecycleConfig({
@@ -46,7 +45,7 @@ const lifecycleConfig = createFeishuLifecycleConfig({
accountConfig: {
dmPolicy: "open",
},
}) as ClawdbotConfig;
});
const lifecycleAccount = createResolvedFeishuLifecycleAccount({
accountId: "acct-card",
@@ -55,7 +54,7 @@ const lifecycleAccount = createResolvedFeishuLifecycleAccount({
config: {
dmPolicy: "open",
},
}) as ResolvedFeishuAccount;
});
function createCardActionEvent(params: {
token: string;

View File

@@ -206,12 +206,17 @@ async function requestFeishuOpenApi<T>(params: {
}): Promise<T | null> {
const formatErrorDetails = (error: unknown): string => {
if (!isRecord(error)) {
return String(error);
return typeof error === "string" ? error : JSON.stringify(error);
}
const response = isRecord(error.response) ? error.response : undefined;
const responseData = isRecord(response?.data) ? response?.data : undefined;
const details = {
message: typeof error.message === "string" ? error.message : String(error),
message:
typeof error.message === "string"
? error.message
: typeof error === "string"
? error
: JSON.stringify(error),
code: readString(error.code),
method: readString(isRecord(error.config) ? error.config.method : undefined),
url: readString(isRecord(error.config) ? error.config.url : undefined),
@@ -230,7 +235,7 @@ async function requestFeishuOpenApi<T>(params: {
url: params.url,
data: params.data ?? {},
timeout: params.timeoutMs,
}) as Promise<T>,
}),
{ timeoutMs: params.timeoutMs },
)
.then((resolved) => (resolved.status === "resolved" ? resolved.value : null))

View File

@@ -4,10 +4,7 @@ import {
resolveInboundDebounceMs,
} from "openclaw/plugin-sdk/reply-runtime";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
createNonExitingTypedRuntimeEnv,
createRuntimeEnv,
} from "../../../test/helpers/plugins/runtime-env.js";
import { createNonExitingTypedRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
import { parseFeishuMessageEvent, type FeishuMessageEvent } from "./bot.js";
import * as dedup from "./dedup.js";

View File

@@ -1,23 +1,22 @@
import "./lifecycle.test-support.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
import type { RuntimeEnv } from "../runtime-api.js";
import "./lifecycle.test-support.js";
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
import {
createFeishuLifecycleConfig,
createFeishuLifecycleReplyDispatcher,
createFeishuTextMessageEvent,
createResolvedFeishuLifecycleAccount,
expectFeishuReplyDispatcherSentFinalReplyOnce,
expectFeishuReplyPipelineDedupedAcrossReplay,
expectFeishuReplyPipelineDedupedAfterPostSendFailure,
expectFeishuReplyDispatcherSentFinalReplyOnce,
installFeishuLifecycleReplyRuntime,
mockFeishuReplyOnceDispatch,
restoreFeishuLifecycleStateDir,
setFeishuLifecycleStateDir,
setupFeishuLifecycleHandler,
} from "./test-support/lifecycle-test-support.js";
import type { ResolvedFeishuAccount } from "./types.js";
const {
createEventDispatcherMock,
@@ -30,7 +29,7 @@ const {
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
let lastRuntime: RuntimeEnv | null = null;
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const lifecycleConfig = createFeishuLifecycleConfig({
@@ -47,7 +46,7 @@ const lifecycleConfig = createFeishuLifecycleConfig({
},
},
},
}) as ClawdbotConfig;
});
const lifecycleAccount = createResolvedFeishuLifecycleAccount({
accountId: "acct-lifecycle",
@@ -63,7 +62,7 @@ const lifecycleAccount = createResolvedFeishuLifecycleAccount({
},
},
},
}) as ResolvedFeishuAccount;
});
async function setupLifecycleMonitor() {
lastRuntime = createRuntimeEnv();

View File

@@ -33,9 +33,9 @@ export type FeishuMonitorBotIdentity = {
};
function isTimeoutErrorMessage(message: string | undefined): boolean {
return message?.toLowerCase().includes("timeout") || message?.toLowerCase().includes("timed out")
? true
: false;
return !!(
message?.toLowerCase().includes("timeout") || message?.toLowerCase().includes("timed out")
);
}
function isAbortErrorMessage(message: string | undefined): boolean {

View File

@@ -105,7 +105,9 @@ const feishuWebhookAnomalyTracker = createWebhookAnomalyTracker({
});
function closeWsClient(client: Lark.WSClient | undefined): void {
if (!client) return;
if (!client) {
return;
}
try {
client.close();
} catch {

View File

@@ -102,7 +102,9 @@ export async function monitorWebSocket({
let cleanedUp = false;
const cleanup = () => {
if (cleanedUp) return;
if (cleanedUp) {
return;
}
cleanedUp = true;
abortSignal?.removeEventListener("abort", handleAbort);
try {
@@ -131,7 +133,7 @@ export async function monitorWebSocket({
abortSignal?.addEventListener("abort", handleAbort, { once: true });
try {
wsClient.start({ eventDispatcher });
void wsClient.start({ eventDispatcher });
log(`feishu[${accountId}]: WebSocket client started`);
} catch (err) {
cleanup();

View File

@@ -7,34 +7,47 @@ import { parseFeishuCommentTarget } from "./comment-target.js";
import { replyComment } from "./drive.js";
import { sendMediaFeishu } from "./media.js";
import { chunkTextForOutbound, type ChannelOutboundAdapter } from "./outbound-runtime-api.js";
import { getFeishuRuntime } from "./runtime.js";
import { sendMarkdownCardFeishu, sendMessageFeishu, sendStructuredCardFeishu } from "./send.js";
function normalizePossibleLocalImagePath(text: string | undefined): string | null {
const raw = text?.trim();
if (!raw) return null;
if (!raw) {
return null;
}
// Only auto-convert when the message is a pure path-like payload.
// Avoid converting regular sentences that merely contain a path.
const hasWhitespace = /\s/.test(raw);
if (hasWhitespace) return null;
if (hasWhitespace) {
return null;
}
// Ignore links/data URLs; those should stay in normal mediaUrl/text paths.
if (/^(https?:\/\/|data:|file:\/\/)/i.test(raw)) return null;
if (/^(https?:\/\/|data:|file:\/\/)/i.test(raw)) {
return null;
}
const ext = path.extname(raw).toLowerCase();
const isImageExt = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".ico", ".tiff"].includes(
ext,
);
if (!isImageExt) return null;
if (!isImageExt) {
return null;
}
if (!path.isAbsolute(raw)) return null;
if (!fs.existsSync(raw)) return null;
if (!path.isAbsolute(raw)) {
return null;
}
if (!fs.existsSync(raw)) {
return null;
}
// Fix race condition: wrap statSync in try-catch to handle file deletion
// between existsSync and statSync
try {
if (!fs.statSync(raw).isFile()) return null;
if (!fs.statSync(raw).isFile()) {
return null;
}
} catch {
// File may have been deleted or became inaccessible between checks
return null;

View File

@@ -143,8 +143,6 @@ export function resolveFeishuReplyPolicy(params: {
? groupRequireMention
: typeof resolvedCfg.requireMention === "boolean"
? resolvedCfg.requireMention
: params.groupPolicy === "open"
? false
: true,
: params.groupPolicy !== "open",
};
}

View File

@@ -1,7 +1,7 @@
import { normalizeFeishuExternalKey } from "./external-keys.js";
const FALLBACK_POST_TEXT = "[Rich text message]";
const MARKDOWN_SPECIAL_CHARS = /([\\`*_{}\[\]()#+\-!|>~])/g;
const MARKDOWN_SPECIAL_CHARS = /([\\`*_{}[\]()#+\-!|>~])/g;
type PostParseResult = {
textContent: string;

View File

@@ -201,7 +201,9 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
type StreamTextUpdateMode = "snapshot" | "delta";
const formatReasoningPrefix = (thinking: string): string => {
if (!thinking) return "";
if (!thinking) {
return "";
}
const withoutLabel = thinking.replace(/^Reasoning:\n/, "");
const plain = withoutLabel.replace(/^_(.*)_$/gm, "$1");
const lines = plain.split("\n").map((line) => `> ${line}`);
@@ -210,9 +212,15 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
const buildCombinedStreamText = (thinking: string, answer: string): string => {
const parts: string[] = [];
if (thinking) parts.push(formatReasoningPrefix(thinking));
if (thinking && answer) parts.push("\n\n---\n\n");
if (answer) parts.push(answer);
if (thinking) {
parts.push(formatReasoningPrefix(thinking));
}
if (thinking && answer) {
parts.push("\n\n---\n\n");
}
if (answer) {
parts.push(answer);
}
return parts.join("");
};
@@ -250,7 +258,9 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
};
const queueReasoningUpdate = (nextThinking: string) => {
if (!nextThinking) return;
if (!nextThinking) {
return;
}
reasoningText = nextThinking;
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
};

View File

@@ -4,9 +4,8 @@ import type { ClawdbotConfig } from "../runtime-api.js";
import { resolveFeishuRuntimeAccount } from "./accounts.js";
import { createFeishuClient } from "./client.js";
import type { MentionTarget } from "./mention.js";
import { buildMentionedMessage, buildMentionedCardContent } from "./mention.js";
import { buildMentionedCardContent, buildMentionedMessage } from "./mention.js";
import { parsePostContent } from "./post.js";
import { getFeishuRuntime } from "./runtime.js";
import { assertFeishuMessageApiSuccess, toFeishuSendResult } from "./send-result.js";
import { resolveFeishuSendTarget } from "./send-target.js";
import type { FeishuChatType, FeishuMessageInfo, FeishuSendResult } from "./types.js";
@@ -186,7 +185,7 @@ function parseInteractiveCardContent(parsed: unknown): string {
const elements = Array.isArray(candidate.elements)
? candidate.elements
: Array.isArray(candidate.body?.elements)
? candidate.body!.elements
? candidate.body.elements
: null;
if (!elements) {
return "[Interactive Card]";
@@ -385,8 +384,12 @@ export async function listFeishuThreadMessages(params: {
const results: FeishuThreadMessageInfo[] = [];
for (const item of items) {
if (currentMessageId && item.message_id === currentMessageId) continue;
if (rootMessageId && item.message_id === rootMessageId) continue;
if (currentMessageId && item.message_id === currentMessageId) {
continue;
}
if (rootMessageId && item.message_id === rootMessageId) {
continue;
}
const parsed = parseFeishuMessageItem(item);
@@ -399,7 +402,9 @@ export async function listFeishuThreadMessages(params: {
createTime: parsed.createTime,
});
if (results.length >= limit) break;
if (results.length >= limit) {
break;
}
}
// Restore chronological order (oldest first) since we fetched newest-first.

View File

@@ -5,7 +5,6 @@ import {
createPluginSetupWizardStatus,
createTestWizardPrompter,
runSetupWizardConfigure,
runSetupWizardFinalize,
type WizardPrompter,
} from "../../../test/helpers/plugins/setup-wizard.js";

View File

@@ -14,12 +14,10 @@ import {
} from "openclaw/plugin-sdk/setup";
import {
inspectFeishuCredentials,
listFeishuAccountIds,
resolveDefaultFeishuAccountId,
resolveFeishuAccount,
} from "./accounts.js";
import { probeFeishu } from "./probe.js";
import { feishuSetupAdapter } from "./setup-core.js";
import type { FeishuAccountConfig, FeishuConfig } from "./types.js";
const channel = "feishu" as const;
@@ -39,7 +37,7 @@ function getScopedFeishuConfig(cfg: OpenClawConfig, accountId: string): ScopedFe
if (accountId === DEFAULT_ACCOUNT_ID) {
return feishuCfg ?? {};
}
return (feishuCfg?.accounts?.[accountId] as FeishuAccountConfig | undefined) ?? {};
return feishuCfg?.accounts?.[accountId] ?? {};
}
function patchFeishuConfig(
@@ -57,7 +55,7 @@ function patchFeishuConfig(
});
}
const nextAccountPatch = {
...((feishuCfg?.accounts?.[accountId] as Record<string, unknown> | undefined) ?? {}),
...(feishuCfg?.accounts?.[accountId] as Record<string, unknown> | undefined),
enabled: true,
...patch,
};
@@ -67,7 +65,7 @@ function patchFeishuConfig(
enabled: true,
patch: {
accounts: {
...(feishuCfg?.accounts ?? {}),
...feishuCfg?.accounts,
[accountId]: nextAccountPatch,
},
},

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it } from "vitest";
import {
getRequiredHookHandler,
registerHookHandlersForTest,
@@ -6,8 +6,8 @@ import {
import type { ClawdbotConfig, OpenClawPluginApi } from "../runtime-api.js";
import { registerFeishuSubagentHooks } from "./subagent-hooks.js";
import {
__testing as threadBindingTesting,
createFeishuThreadBindingManager,
__testing as threadBindingTesting,
} from "./thread-bindings.js";
const baseConfig: ClawdbotConfig = {

View File

@@ -218,7 +218,7 @@ export function createFeishuThreadBindingManager(params: {
},
unbindBySessionKey: (targetSessionKey) => {
const removed: FeishuThreadBindingRecord[] = [];
for (const record of [...getState().bindingsByAccountConversation.values()]) {
for (const record of getState().bindingsByAccountConversation.values()) {
if (record.accountId !== accountId || record.targetSessionKey !== targetSessionKey) {
continue;
}
@@ -230,7 +230,7 @@ export function createFeishuThreadBindingManager(params: {
return removed;
},
stop: () => {
for (const key of [...getState().bindingsByAccountConversation.keys()]) {
for (const key of getState().bindingsByAccountConversation.keys()) {
if (key.startsWith(`${accountId}:`)) {
getState().bindingsByAccountConversation.delete(key);
}

View File

@@ -20,7 +20,9 @@ type RegisteredTool = {
};
function toToolList(value: AnyAgentTool | AnyAgentTool[] | null | undefined): AnyAgentTool[] {
if (!value) return [];
if (!value) {
return [];
}
return Array.isArray(value) ? value : [value];
}

View File

@@ -53,8 +53,8 @@ export function createFirecrawlWebFetchProvider(): WebFetchProviderPlugin {
firecrawlEntry.config &&
typeof firecrawlEntry.config === "object" &&
!Array.isArray(firecrawlEntry.config)
? (firecrawlEntry.config as Record<string, unknown>)
: ((firecrawlEntry.config = {}), firecrawlEntry.config as Record<string, unknown>);
? firecrawlEntry.config
: ((firecrawlEntry.config = {}), firecrawlEntry.config);
const webFetch =
pluginConfig.webFetch &&
typeof pluginConfig.webFetch === "object" &&

View File

@@ -12,7 +12,7 @@ vi.mock("./register.runtime.js", () => ({
import plugin from "./index.js";
function registerProvider() {
function _registerProvider() {
return registerProviderWithPluginConfig({});
}

View File

@@ -8,7 +8,7 @@ import {
streamWithPayloadPatch,
} from "openclaw/plugin-sdk/provider-stream-shared";
type StreamContext = Parameters<StreamFn>[1];
type _StreamContext = Parameters<StreamFn>[1];
export function wrapCopilotAnthropicStream(baseStreamFn: StreamFn | undefined): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
@@ -25,10 +25,10 @@ export function wrapCopilotAnthropicStream(baseStreamFn: StreamFn | undefined):
...options,
headers: {
...buildCopilotDynamicHeaders({
messages: context.messages as StreamContext["messages"],
hasImages: hasCopilotVisionInput(context.messages as StreamContext["messages"]),
messages: context.messages,
hasImages: hasCopilotVisionInput(context.messages),
}),
...(options?.headers ?? {}),
...options?.headers,
},
},
applyAnthropicEphemeralCacheControlMarkers,

View File

@@ -567,7 +567,7 @@ describe("loginGeminiCliOAuth", () => {
if (Array.isArray(headers)) {
return headers.find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
}
return (headers as Record<string, string>)[name];
return headers[name];
}
function responseJson(body: unknown, status = 200): Response {

View File

@@ -181,7 +181,7 @@ function createGeminiToolDefinition(
"Search the web using Gemini with Google Search grounding. Returns AI-synthesized answers with citations from Google Search.",
parameters: createGeminiSchema(),
execute: async (args) => {
const params = args as Record<string, unknown>;
const params = args;
const unsupportedResponse = buildUnsupportedSearchFilterResponse(params, "gemini");
if (unsupportedResponse) {
return unsupportedResponse;

View File

@@ -44,11 +44,7 @@ function mergeGoogleChatAccountConfig(
accountId,
omitKeys: ["defaultAccount"],
});
const defaultAccountConfig =
resolveAccountEntry(
raw.accounts as Record<string, GoogleChatAccountConfig> | undefined,
DEFAULT_ACCOUNT_ID,
) ?? {};
const defaultAccountConfig = resolveAccountEntry(raw.accounts, DEFAULT_ACCOUNT_ID) ?? {};
if (accountId === DEFAULT_ACCOUNT_ID) {
return base;
}

View File

@@ -3,7 +3,7 @@ import {
createDirectoryTestRuntime,
expectDirectorySurface,
} from "../../../test/helpers/plugins/directory.ts";
import type { OpenClawConfig, PluginRuntime } from "../runtime-api.js";
import type { OpenClawConfig } from "../runtime-api.js";
const uploadGoogleChatAttachmentMock = vi.hoisted(() => vi.fn());
const sendGoogleChatMessageMock = vi.hoisted(() => vi.fn());

View File

@@ -7,8 +7,6 @@ import {
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
import {
composeAccountWarningCollectors,
composeWarningCollectors,
createAllowlistProviderGroupPolicyWarningCollector,
createAllowlistProviderOpenWarningCollector,
} from "openclaw/plugin-sdk/channel-policy";
import {
@@ -32,18 +30,18 @@ import {
DEFAULT_ACCOUNT_ID,
fetchRemoteMedia,
GoogleChatConfigSchema,
isGoogleChatSpaceTarget,
isGoogleChatUserTarget,
listGoogleChatAccountIds,
loadOutboundMediaFromUrl,
missingTargetError,
normalizeGoogleChatTarget,
PAIRING_APPROVED_MESSAGE,
resolveChannelMediaMaxBytes,
resolveDefaultGoogleChatAccountId,
resolveGoogleChatAccount,
resolveGoogleChatOutboundSpace,
runPassiveAccountLifecycle,
isGoogleChatSpaceTarget,
isGoogleChatUserTarget,
normalizeGoogleChatTarget,
type ChannelMessageActionAdapter,
type ChannelStatusIssue,
type OpenClawConfig,
@@ -51,7 +49,6 @@ import {
} from "./channel.deps.runtime.js";
import { collectGoogleChatMutableAllowlistWarnings } from "./doctor.js";
import { resolveGoogleChatGroupRequireMention } from "./group-policy.js";
import { getGoogleChatRuntime } from "./runtime.js";
import { collectRuntimeConfigAssignments, secretTargetRegistryEntries } from "./secret-contract.js";
import { googlechatSetupAdapter } from "./setup-core.js";
import { googlechatSetupWizard } from "./setup-surface.js";

View File

@@ -129,7 +129,7 @@ function warnDeprecatedUsersEmailEntries(logVerbose: (message: string) => void,
}
const key = deprecated
.map((v) => v.toLowerCase())
.sort()
.toSorted()
.join(",");
if (warnedDeprecatedUsersEmailAllowFrom.has(key)) {
return;
@@ -152,7 +152,7 @@ function warnMutableGroupKeysConfigured(
}
const warningKey = mutableKeys
.map((key) => key.toLowerCase())
.sort()
.toSorted()
.join(",");
if (warnedMutableGroupKeys.has(warningKey)) {
return;

View File

@@ -15,7 +15,13 @@ import type {
} from "./types.js";
function extractBearerToken(header: unknown): string {
const authHeader = Array.isArray(header) ? String(header[0] ?? "") : String(header ?? "");
const authHeader = Array.isArray(header)
? typeof header[0] === "string"
? header[0]
: ""
: typeof header === "string"
? header
: "";
return authHeader.toLowerCase().startsWith("bearer ")
? authHeader.slice("bearer ".length).trim()
: "";
@@ -63,7 +69,10 @@ function parseGoogleChatInboundPayload(
user: chat.user,
eventTime: chat.eventTime,
};
addOnBearerToken = String(rawObj.authorizationEventObject?.systemIdToken ?? "").trim();
addOnBearerToken =
typeof rawObj.authorizationEventObject?.systemIdToken === "string"
? rawObj.authorizationEventObject.systemIdToken.trim()
: "";
}
const event = eventPayload as GoogleChatEvent;

View File

@@ -1,7 +1,6 @@
import {
createPatchedAccountSetupAdapter,
createSetupInputPresenceValidator,
DEFAULT_ACCOUNT_ID,
} from "openclaw/plugin-sdk/setup-runtime";
const channel = "googlechat" as const;

View File

@@ -10,14 +10,8 @@ import {
splitSetupEntries,
type ChannelSetupDmPolicy,
type ChannelSetupWizard,
type OpenClawConfig,
} from "openclaw/plugin-sdk/setup";
import {
listGoogleChatAccountIds,
resolveDefaultGoogleChatAccountId,
resolveGoogleChatAccount,
} from "./accounts.js";
import { googlechatSetupAdapter } from "./setup-core.js";
import { resolveDefaultGoogleChatAccountId, resolveGoogleChatAccount } from "./accounts.js";
const channel = "googlechat" as const;
const ENV_SERVICE_ACCOUNT = "GOOGLE_CHAT_SERVICE_ACCOUNT";
@@ -41,7 +35,7 @@ const promptAllowFrom = createPromptParsedAllowFromForAccount({
accountId,
patch: {
dm: {
...(resolveGoogleChatAccount({ cfg, accountId }).config.dm ?? {}),
...resolveGoogleChatAccount({ cfg, accountId }).config.dm,
allowFrom,
},
},

View File

@@ -20,7 +20,7 @@ vi.mock("./onboard.js", () => ({
import plugin from "./index.js";
function registerProvider() {
function _registerProvider() {
return registerProviderWithPluginConfig({});
}

View File

@@ -1,22 +1,22 @@
import type {
ChannelSetupAdapter,
ChannelSetupWizard,
ChannelSetupWizardTextInput,
} from "openclaw/plugin-sdk/setup-runtime";
import {
createCliPathTextInput,
createDelegatedSetupWizardProxy,
createDelegatedTextInputShouldPrompt,
createPatchedAccountSetupAdapter,
mergeAllowFromEntries,
patchChannelConfigForAccount,
parseSetupEntriesAllowingWildcard,
patchChannelConfigForAccount,
promptParsedAllowFromForAccount,
setAccountAllowFromForChannel,
setSetupChannelEnabled,
type OpenClawConfig,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup-runtime";
import type {
ChannelSetupAdapter,
ChannelSetupWizard,
ChannelSetupWizardTextInput,
} from "openclaw/plugin-sdk/setup-runtime";
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import { resolveDefaultIMessageAccountId, resolveIMessageAccount } from "./accounts.js";
import { normalizeIMessageHandle } from "./targets.js";

View File

@@ -8,7 +8,6 @@ import {
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
import {
composeAccountWarningCollectors,
composeWarningCollectors,
createAllowlistProviderOpenWarningCollector,
} from "openclaw/plugin-sdk/channel-policy";
import {
@@ -29,8 +28,8 @@ import {
} from "./accounts.js";
import {
buildBaseChannelStatusSummary,
createAccountStatusSink,
chunkTextForOutbound,
createAccountStatusSink,
DEFAULT_ACCOUNT_ID,
PAIRING_APPROVED_MESSAGE,
type ChannelPlugin,
@@ -38,14 +37,13 @@ import {
import { IrcChannelConfigSchema } from "./config-schema.js";
import { collectIrcMutableAllowlistWarnings } from "./doctor.js";
import {
normalizeIrcMessagingTarget,
looksLikeIrcTargetId,
isChannelTarget,
looksLikeIrcTargetId,
normalizeIrcAllowEntry,
normalizeIrcMessagingTarget,
} from "./normalize.js";
import { resolveIrcGroupMatch, resolveIrcRequireMention } from "./policy.js";
import { probeIrc } from "./probe.js";
import { getIrcRuntime } from "./runtime.js";
import { collectRuntimeConfigAssignments, secretTargetRegistryEntries } from "./secret-contract.js";
import { ircSetupAdapter } from "./setup-core.js";
import { ircSetupWizard } from "./setup-surface.js";

View File

@@ -83,7 +83,7 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string):
function buildFallbackNick(nick: string): string {
const normalized = nick.replace(/\s+/g, "");
const safe = normalized.replace(/[^A-Za-z0-9_\-\[\]\\`^{}|]/g, "");
const safe = normalized.replace(/[^A-Za-z0-9_\-[\]\\`^{}|]/g, "");
const base = safe || "openclaw";
const suffix = "_";
const maxNickLen = 30;

View File

@@ -1,12 +1,10 @@
import {
buildChannelConfigSchema,
BlockStreamingCoalesceSchema,
DmConfigSchema,
DmPolicySchema,
GroupPolicySchema,
MarkdownConfigSchema,
ReplyRuntimeConfigSchemaShape,
ToolPolicySchema,
buildChannelConfigSchema,
requireOpenAllowFrom,
} from "openclaw/plugin-sdk/channel-config-schema";
import { z } from "openclaw/plugin-sdk/zod";

View File

@@ -1,16 +1,17 @@
import type { DmPolicy } from "openclaw/plugin-sdk/config-runtime";
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
import type {
ChannelSetupDmPolicy,
ChannelSetupWizard,
WizardPrompter,
} from "openclaw/plugin-sdk/setup";
import {
createAllowFromSection,
createPromptParsedAllowFromForAccount,
createStandardChannelSetupStatus,
formatDocsLink,
setSetupChannelEnabled,
} from "openclaw/plugin-sdk/setup";
import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "openclaw/plugin-sdk/setup";
import type { WizardPrompter } from "openclaw/plugin-sdk/setup";
import { listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount } from "./accounts.js";
import { resolveDefaultIrcAccountId, resolveIrcAccount } from "./accounts.js";
import {
isChannelTarget,
normalizeIrcAllowEntry,
@@ -25,7 +26,7 @@ import {
setIrcNickServ,
updateIrcAccountConfig,
} from "./setup-core.js";
import type { CoreConfig, IrcAccountConfig, IrcNickServConfig } from "./types.js";
import type { CoreConfig } from "./types.js";
const channel = "irc" as const;
const USE_ENV_FLAG = "__ircUseEnv";
@@ -258,7 +259,7 @@ export const ircSetupWizard: ChannelSetupWizard = {
shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1",
initialValue: ({ cfg, accountId, credentialValues }) => {
const resolved = resolveIrcAccount({ cfg: cfg as CoreConfig, accountId });
const tls = credentialValues[TLS_FLAG] === "0" ? false : true;
const tls = credentialValues[TLS_FLAG] !== "0";
const defaultPort = resolved.config.port ?? (tls ? 6697 : 6667);
return String(defaultPort);
},

View File

@@ -275,7 +275,7 @@ describe("irc setup", () => {
).toBeNull();
expect(
applyAccountConfig!({
applyAccountConfig({
cfg: { channels: { irc: {} } },
accountId: "default",
input: {

View File

@@ -28,7 +28,7 @@ function findExplicitProviderConfig(
return isRecord(match?.[1]) ? match[1] : undefined;
}
function buildKimiReplayPolicy() {
function _buildKimiReplayPolicy() {
return {
preserveSignatures: false,
};

View File

@@ -197,6 +197,15 @@ type LineWebhookContext = Parameters<typeof import("./bot-handlers.js").handleLi
const createRuntime = () => ({ log: vi.fn(), error: vi.fn(), exit: vi.fn() });
function buildDefaultLineMessageContext() {
return {
ctxPayload: { From: "line:group:group-1" },
replyToken: "reply-token",
route: { agentId: "default" },
isGroup: true,
accountId: "default",
};
}
function createReplayMessageEvent(params: {
messageId: string;
groupId: string;

View File

@@ -9,6 +9,7 @@ import {
type LineConfig,
} from "./setup-runtime-api.js";
const channel = "line" as const;
export function patchLineAccountConfig(params: {
cfg: OpenClawConfig;
accountId: string;

View File

@@ -2,8 +2,7 @@ import crypto from "node:crypto";
import type { IncomingMessage, ServerResponse } from "node:http";
import { describe, expect, it, vi } from "vitest";
import { createMockIncomingRequest } from "../../../test/helpers/mock-incoming-request.js";
import { createLineNodeWebhookHandler } from "./webhook-node.js";
import { readLineWebhookRequestBody } from "./webhook-node.js";
import { createLineNodeWebhookHandler, readLineWebhookRequestBody } from "./webhook-node.js";
import { createLineWebhookMiddleware } from "./webhook.js";
const sign = (body: string, secret: string) =>

Some files were not shown because too many files have changed in this diff Show More