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

@@ -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];
}