mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 12:11:20 +00:00
style: fix extension lint violations
This commit is contained in:
@@ -16,7 +16,7 @@ import type {
|
||||
} from "./types.js";
|
||||
|
||||
const {
|
||||
listConfiguredAccountIds,
|
||||
_listConfiguredAccountIds,
|
||||
listAccountIds: listFeishuAccountIds,
|
||||
resolveDefaultAccountId,
|
||||
} = createAccountListHelpers("feishu", {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"] } : {}),
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -83,12 +83,12 @@ const {
|
||||
createFeishuReplyDispatcherMock,
|
||||
resolveBoundConversationMock,
|
||||
touchBindingMock,
|
||||
resolveAgentRouteMock,
|
||||
_resolveAgentRouteMock,
|
||||
resolveConfiguredBindingRouteMock,
|
||||
ensureConfiguredBindingRouteReadyMock,
|
||||
dispatchReplyFromConfigMock,
|
||||
withReplyDispatcherMock,
|
||||
finalizeInboundContextMock,
|
||||
_dispatchReplyFromConfigMock,
|
||||
_withReplyDispatcherMock,
|
||||
_finalizeInboundContextMock,
|
||||
getMessageFeishuMock,
|
||||
listFeishuThreadMessagesMock,
|
||||
sendMessageFeishuMock,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -23,7 +23,7 @@ const {
|
||||
finalizeInboundContextMock,
|
||||
resolveAgentRouteMock,
|
||||
resolveBoundConversationMock,
|
||||
sendMessageFeishuMock,
|
||||
_sendMessageFeishuMock,
|
||||
withReplyDispatcherMock,
|
||||
} = getFeishuLifecycleTestMocks();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -105,7 +105,9 @@ const feishuWebhookAnomalyTracker = createWebhookAnomalyTracker({
|
||||
});
|
||||
|
||||
function closeWsClient(client: Lark.WSClient | undefined): void {
|
||||
if (!client) return;
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
client.close();
|
||||
} catch {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -143,8 +143,6 @@ export function resolveFeishuReplyPolicy(params: {
|
||||
? groupRequireMention
|
||||
: typeof resolvedCfg.requireMention === "boolean"
|
||||
? resolvedCfg.requireMention
|
||||
: params.groupPolicy === "open"
|
||||
? false
|
||||
: true,
|
||||
: params.groupPolicy !== "open",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
createPluginSetupWizardStatus,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
runSetupWizardFinalize,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/plugins/setup-wizard.js";
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user