refactor(extensions): dedupe channel config, onboarding, and monitors

This commit is contained in:
Peter Steinberger
2026-03-02 08:53:11 +00:00
parent d358b3ac88
commit ad8d766f65
43 changed files with 677 additions and 776 deletions

View File

@@ -1,5 +1,19 @@
import { Type, type Static } from "@sinclair/typebox";
const tableCreationProperties = {
doc_token: Type.String({ description: "Document token" }),
parent_block_id: Type.Optional(
Type.String({ description: "Parent block ID (default: document root)" }),
),
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
column_width: Type.Optional(
Type.Array(Type.Number({ minimum: 1 }), {
description: "Column widths in px (length should match column_size)",
}),
),
};
export const FeishuDocSchema = Type.Union([
Type.Object({
action: Type.Literal("read"),
@@ -59,17 +73,7 @@ export const FeishuDocSchema = Type.Union([
// Table creation (explicit structure)
Type.Object({
action: Type.Literal("create_table"),
doc_token: Type.String({ description: "Document token" }),
parent_block_id: Type.Optional(
Type.String({ description: "Parent block ID (default: document root)" }),
),
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
column_width: Type.Optional(
Type.Array(Type.Number({ minimum: 1 }), {
description: "Column widths in px (length should match column_size)",
}),
),
...tableCreationProperties,
}),
Type.Object({
action: Type.Literal("write_table_cells"),
@@ -82,17 +86,7 @@ export const FeishuDocSchema = Type.Union([
}),
Type.Object({
action: Type.Literal("create_table_with_values"),
doc_token: Type.String({ description: "Document token" }),
parent_block_id: Type.Optional(
Type.String({ description: "Parent block ID (default: document root)" }),
),
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
column_width: Type.Optional(
Type.Array(Type.Number({ minimum: 1 }), {
description: "Column widths in px (length should match column_size)",
}),
),
...tableCreationProperties,
values: Type.Array(Type.Array(Type.String()), {
description: "2D matrix values[row][col] to write into table cells",
minItems: 1,

View File

@@ -21,8 +21,8 @@ vi.mock("@larksuiteoapi/node-sdk", () => {
});
describe("feishu_doc account selection", () => {
test("uses agentAccountId context when params omit accountId", async () => {
const cfg = {
function createDocEnabledConfig(): OpenClawPluginApi["config"] {
return {
channels: {
feishu: {
enabled: true,
@@ -33,6 +33,10 @@ describe("feishu_doc account selection", () => {
},
},
} as OpenClawPluginApi["config"];
}
test("uses agentAccountId context when params omit accountId", async () => {
const cfg = createDocEnabledConfig();
const { api, resolveTool } = createToolFactoryHarness(cfg);
registerFeishuDocTools(api);
@@ -49,17 +53,7 @@ describe("feishu_doc account selection", () => {
});
test("explicit accountId param overrides agentAccountId context", async () => {
const cfg = {
channels: {
feishu: {
enabled: true,
accounts: {
a: { appId: "app-a", appSecret: "sec-a", tools: { doc: true } },
b: { appId: "app-b", appSecret: "sec-b", tools: { doc: true } },
},
},
},
} as OpenClawPluginApi["config"];
const cfg = createDocEnabledConfig();
const { api, resolveTool } = createToolFactoryHarness(cfg);
registerFeishuDocTools(api);

View File

@@ -114,6 +114,29 @@ describe("feishu_doc image fetch hardening", () => {
scopeListMock.mockResolvedValue({ code: 0, data: { scopes: [] } });
});
function resolveFeishuDocTool(context: Record<string, unknown> = {}) {
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const tool = registerTool.mock.calls
.map((call) => call[0])
.map((candidate) => (typeof candidate === "function" ? candidate(context) : candidate))
.find((candidate) => candidate.name === "feishu_doc");
expect(tool).toBeDefined();
return tool as { execute: (callId: string, params: Record<string, unknown>) => Promise<any> };
}
it("inserts blocks sequentially to preserve document order", async () => {
const blocks = [
{ block_type: 3, block_id: "h1" },
@@ -135,22 +158,7 @@ describe("feishu_doc image fetch hardening", () => {
data: { children: [{ block_type: 3, block_id: "h1" }] },
});
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: { appId: "app_id", appSecret: "app_secret" },
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool();
const result = await feishuDocTool.execute("tool-call", {
action: "append",
@@ -194,22 +202,7 @@ describe("feishu_doc image fetch hardening", () => {
},
}));
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: { appId: "app_id", appSecret: "app_secret" },
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool();
const longMarkdown = Array.from(
{ length: 120 },
@@ -254,22 +247,7 @@ describe("feishu_doc image fetch hardening", () => {
data: { children: data.children },
}));
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: { appId: "app_id", appSecret: "app_secret" },
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool();
const fencedMarkdown = [
"## Section",
@@ -306,25 +284,7 @@ describe("feishu_doc image fetch hardening", () => {
new Error("Blocked: resolves to private/internal IP address"),
);
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool();
const result = await feishuDocTool.execute("tool-call", {
action: "write",
@@ -341,29 +301,10 @@ describe("feishu_doc image fetch hardening", () => {
});
it("create grants permission only to trusted Feishu requester", async () => {
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) =>
typeof tool === "function"
? tool({ messageChannel: "feishu", requesterSenderId: "ou_123" })
: tool,
)
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool({
messageChannel: "feishu",
requesterSenderId: "ou_123",
});
const result = await feishuDocTool.execute("tool-call", {
action: "create",
@@ -386,25 +327,9 @@ describe("feishu_doc image fetch hardening", () => {
});
it("create skips requester grant when trusted requester identity is unavailable", async () => {
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({ messageChannel: "feishu" }) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool({
messageChannel: "feishu",
});
const result = await feishuDocTool.execute("tool-call", {
action: "create",
@@ -417,29 +342,10 @@ describe("feishu_doc image fetch hardening", () => {
});
it("create never grants permissions when grant_to_requester is false", async () => {
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) =>
typeof tool === "function"
? tool({ messageChannel: "feishu", requesterSenderId: "ou_123" })
: tool,
)
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool({
messageChannel: "feishu",
requesterSenderId: "ou_123",
});
const result = await feishuDocTool.execute("tool-call", {
action: "create",
@@ -457,25 +363,7 @@ describe("feishu_doc image fetch hardening", () => {
data: { document: { title: "Created Doc" } },
});
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool();
const result = await feishuDocTool.execute("tool-call", {
action: "create",
@@ -496,25 +384,7 @@ describe("feishu_doc image fetch hardening", () => {
const localPath = join(tmpdir(), `feishu-docx-upload-${Date.now()}.txt`);
await fs.writeFile(localPath, "hello from local file", "utf8");
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool();
const result = await feishuDocTool.execute("tool-call", {
action: "upload_file",
@@ -557,25 +427,7 @@ describe("feishu_doc image fetch hardening", () => {
await fs.writeFile(localPath, "hello from local file", "utf8");
try {
const registerTool = vi.fn();
registerFeishuDocTools({
config: {
channels: {
feishu: {
appId: "app_id",
appSecret: "app_secret",
},
},
} as any,
logger: { debug: vi.fn(), info: vi.fn() } as any,
registerTool,
} as any);
const feishuDocTool = registerTool.mock.calls
.map((call) => call[0])
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const feishuDocTool = resolveFeishuDocTool();
const result = await feishuDocTool.execute("tool-call", {
action: "upload_file",

View File

@@ -1,18 +1,7 @@
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
import { afterEach, describe, expect, it, vi } from "vitest";
const probeFeishuMock = vi.hoisted(() => vi.fn());
vi.mock("./probe.js", () => ({
probeFeishu: probeFeishuMock,
}));
vi.mock("./client.js", () => ({
createFeishuWSClient: vi.fn(() => ({ start: vi.fn() })),
createEventDispatcher: vi.fn(() => ({ register: vi.fn() })),
}));
import { monitorFeishuProvider, stopFeishuMonitor } from "./monitor.js";
import { probeFeishuMock } from "./monitor.test-mocks.js";
function buildMultiAccountWebsocketConfig(accountIds: string[]): ClawdbotConfig {
return {

View File

@@ -0,0 +1,12 @@
import { vi } from "vitest";
export const probeFeishuMock = vi.hoisted(() => vi.fn());
vi.mock("./probe.js", () => ({
probeFeishu: probeFeishuMock,
}));
vi.mock("./client.js", () => ({
createFeishuWSClient: vi.fn(() => ({ start: vi.fn() })),
createEventDispatcher: vi.fn(() => ({ register: vi.fn() })),
}));

View File

@@ -2,8 +2,7 @@ import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
import { afterEach, describe, expect, it, vi } from "vitest";
const probeFeishuMock = vi.hoisted(() => vi.fn());
import { probeFeishuMock } from "./monitor.test-mocks.js";
vi.mock("@larksuiteoapi/node-sdk", () => ({
adaptDefault: vi.fn(
@@ -14,15 +13,6 @@ vi.mock("@larksuiteoapi/node-sdk", () => ({
),
}));
vi.mock("./probe.js", () => ({
probeFeishu: probeFeishuMock,
}));
vi.mock("./client.js", () => ({
createFeishuWSClient: vi.fn(() => ({ start: vi.fn() })),
createEventDispatcher: vi.fn(() => ({ register: vi.fn() })),
}));
import {
clearFeishuWebhookRateLimitStateForTest,
getFeishuWebhookRateLimitStateSizeForTest,