openclaw-lark-multi-agent 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/feishu-bot.d.ts +1 -0
- package/dist/feishu-bot.js +13 -17
- package/dist/markdown.d.ts +31 -0
- package/dist/markdown.js +203 -0
- package/package.json +1 -1
package/dist/feishu-bot.d.ts
CHANGED
package/dist/feishu-bot.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as lark from "@larksuiteoapi/node-sdk";
|
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
3
3
|
import { basename, extname, resolve } from "path";
|
|
4
4
|
import { getBridgeAttachmentsDir } from "./paths.js";
|
|
5
|
+
import { buildFeishuCardElements } from "./markdown.js";
|
|
5
6
|
const MAX_BOT_STREAK = 10;
|
|
6
7
|
const BRIDGE_ATTACHMENTS_DIR = getBridgeAttachmentsDir();
|
|
7
8
|
/**
|
|
@@ -758,16 +759,18 @@ export class FeishuBot {
|
|
|
758
759
|
}
|
|
759
760
|
return s;
|
|
760
761
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
},
|
|
769
|
-
],
|
|
762
|
+
buildMarkdownCard(text) {
|
|
763
|
+
return {
|
|
764
|
+
schema: "2.0",
|
|
765
|
+
config: { wide_screen_mode: true },
|
|
766
|
+
body: {
|
|
767
|
+
elements: buildFeishuCardElements(text),
|
|
768
|
+
},
|
|
770
769
|
};
|
|
770
|
+
}
|
|
771
|
+
async replyMessage(messageId, text) {
|
|
772
|
+
// Use Feishu CardKit v2 markdown component for full Markdown rendering.
|
|
773
|
+
const card = this.buildMarkdownCard(text);
|
|
771
774
|
try {
|
|
772
775
|
await this.client.im.message.reply({
|
|
773
776
|
path: { message_id: messageId },
|
|
@@ -923,14 +926,7 @@ ${doc.url}`);
|
|
|
923
926
|
* Send a proactive message to a chat (not a reply).
|
|
924
927
|
*/
|
|
925
928
|
async sendMessage(chatId, text) {
|
|
926
|
-
const card =
|
|
927
|
-
elements: [
|
|
928
|
-
{
|
|
929
|
-
tag: "markdown",
|
|
930
|
-
content: text,
|
|
931
|
-
},
|
|
932
|
-
],
|
|
933
|
-
};
|
|
929
|
+
const card = this.buildMarkdownCard(text);
|
|
934
930
|
try {
|
|
935
931
|
await this.client.im.message.create({
|
|
936
932
|
params: { receive_id_type: "chat_id" },
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare function optimizeMarkdownStyle(text: string, cardVersion?: number): string;
|
|
2
|
+
declare function convertMarkdownTables(markdown: string): string;
|
|
3
|
+
type FeishuMarkdownElement = {
|
|
4
|
+
tag: "markdown";
|
|
5
|
+
content: string;
|
|
6
|
+
};
|
|
7
|
+
type FeishuTableElement = {
|
|
8
|
+
tag: "table";
|
|
9
|
+
page_size?: number;
|
|
10
|
+
row_height?: "low";
|
|
11
|
+
header_style?: Record<string, unknown>;
|
|
12
|
+
columns: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
display_name: string;
|
|
15
|
+
data_type: "lark_md";
|
|
16
|
+
width?: string;
|
|
17
|
+
vertical_align?: "top" | "center" | "bottom";
|
|
18
|
+
horizontal_align?: "left" | "center" | "right";
|
|
19
|
+
}>;
|
|
20
|
+
rows: Array<Record<string, string>>;
|
|
21
|
+
};
|
|
22
|
+
export type FeishuCardElement = FeishuMarkdownElement | FeishuTableElement;
|
|
23
|
+
declare function buildTableElement(lines: string[], index: number): FeishuTableElement | null;
|
|
24
|
+
export declare function buildFeishuCardElements(markdown: string): FeishuCardElement[];
|
|
25
|
+
export declare function prepareMarkdownForFeishu(text: string): string;
|
|
26
|
+
export declare const __test__: {
|
|
27
|
+
convertMarkdownTables: typeof convertMarkdownTables;
|
|
28
|
+
optimizeMarkdownStyle: typeof optimizeMarkdownStyle;
|
|
29
|
+
buildTableElement: typeof buildTableElement;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const MARKDOWN_STYLE_MARKERS = {
|
|
2
|
+
bold: { open: "**", close: "**" },
|
|
3
|
+
italic: { open: "_", close: "_" },
|
|
4
|
+
strikethrough: { open: "~~", close: "~~" },
|
|
5
|
+
code: { open: "`", close: "`" },
|
|
6
|
+
code_block: { open: "```\n", close: "```" },
|
|
7
|
+
};
|
|
8
|
+
const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
|
|
9
|
+
function protectCodeBlocks(text) {
|
|
10
|
+
const blocks = [];
|
|
11
|
+
const protectedText = text.replace(/(^|\n)(`{3,})([^\n]*)\n[\s\S]*?\n\2(?=\n|$)/g, (match, prefix = "") => {
|
|
12
|
+
const block = match.slice(String(prefix).length);
|
|
13
|
+
const token = `___LMA_CB_${blocks.length}___`;
|
|
14
|
+
blocks.push({ token, text: block });
|
|
15
|
+
return `${prefix}${token}`;
|
|
16
|
+
});
|
|
17
|
+
return { text: protectedText, blocks };
|
|
18
|
+
}
|
|
19
|
+
function restoreCodeBlocks(text, blocks, cardVersion = 2) {
|
|
20
|
+
let restored = text;
|
|
21
|
+
for (const { token, text: block } of blocks) {
|
|
22
|
+
restored = restored.replace(token, cardVersion >= 2 ? `\n<br>\n${block}\n<br>\n` : block);
|
|
23
|
+
}
|
|
24
|
+
return restored;
|
|
25
|
+
}
|
|
26
|
+
function stripInvalidImageKeys(text) {
|
|
27
|
+
if (!text.includes("!["))
|
|
28
|
+
return text;
|
|
29
|
+
return text.replace(IMAGE_RE, (fullMatch, _alt, value) => {
|
|
30
|
+
if (String(value).startsWith("img_"))
|
|
31
|
+
return fullMatch;
|
|
32
|
+
return "";
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function optimizeMarkdownStyle(text, cardVersion = 2) {
|
|
36
|
+
try {
|
|
37
|
+
const protectedResult = protectCodeBlocks(text);
|
|
38
|
+
let r = protectedResult.text;
|
|
39
|
+
const hasH1toH3 = /^#{1,3} /m.test(text);
|
|
40
|
+
if (hasH1toH3) {
|
|
41
|
+
r = r.replace(/^#{2,6} (.+)$/gm, "##### $1");
|
|
42
|
+
r = r.replace(/^# (.+)$/gm, "#### $1");
|
|
43
|
+
}
|
|
44
|
+
if (cardVersion >= 2) {
|
|
45
|
+
r = r.replace(/^(#{4,5} .+)\n{1,2}(#{4,5} )/gm, "$1\n<br>\n$2");
|
|
46
|
+
r = r.replace(/^([^|\n].*)\n(\|.+\|)/gm, "$1\n\n$2");
|
|
47
|
+
r = r.replace(/\n\n((?:\|.+\|[^\S\n]*\n?)+)/g, "\n\n<br>\n\n$1");
|
|
48
|
+
r = r.replace(/((?:^\|.+\|[^\S\n]*\n?)+)/gm, (match, _table, offset) => {
|
|
49
|
+
const after = r.slice(offset + match.length).replace(/^\n+/, "");
|
|
50
|
+
if (!after || /^(---|#{4,5} |\*\*)/.test(after))
|
|
51
|
+
return match;
|
|
52
|
+
return `${match}\n<br>\n`;
|
|
53
|
+
});
|
|
54
|
+
r = r.replace(/^((?!#{4,5} )(?!\*\*).+)\n\n(<br>)\n\n(\|)/gm, "$1\n$2\n$3");
|
|
55
|
+
r = r.replace(/^(\*\*.+)\n\n(<br>)\n\n(\|)/gm, "$1\n$2\n\n$3");
|
|
56
|
+
r = r.replace(/(\|[^\n]*\n)\n(<br>\n)((?!#{4,5} )(?!\*\*))/gm, "$1$2$3");
|
|
57
|
+
}
|
|
58
|
+
r = restoreCodeBlocks(r, protectedResult.blocks, cardVersion);
|
|
59
|
+
r = r.replace(/\n{3,}/g, "\n\n");
|
|
60
|
+
return stripInvalidImageKeys(r);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return text;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function isMarkdownTableSeparator(line) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
if (!trimmed.includes("|"))
|
|
69
|
+
return false;
|
|
70
|
+
const cells = trimmed.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
71
|
+
return cells.length >= 2 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
72
|
+
}
|
|
73
|
+
function isMarkdownTableRow(line) {
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
return trimmed.includes("|") && trimmed.replace(/^\|/, "").replace(/\|$/, "").split("|").length >= 2;
|
|
76
|
+
}
|
|
77
|
+
function splitMarkdownTableRow(line) {
|
|
78
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
79
|
+
}
|
|
80
|
+
function formatTableAsCodeBlock(lines) {
|
|
81
|
+
const rows = lines.filter((line) => !isMarkdownTableSeparator(line)).map(splitMarkdownTableRow);
|
|
82
|
+
if (rows.length === 0)
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
const width = Math.max(...rows.map((row) => row.length));
|
|
85
|
+
const widths = Array.from({ length: width }, (_, i) => Math.max(...rows.map((row) => row[i]?.length ?? 0), 1));
|
|
86
|
+
const renderedRows = rows.map((row, rowIndex) => {
|
|
87
|
+
const rendered = widths.map((w, i) => (row[i] ?? "").padEnd(w)).join(" ").trimEnd();
|
|
88
|
+
if (rowIndex === 0 && rows.length > 1) {
|
|
89
|
+
const sep = widths.map((w) => "─".repeat(w)).join(" ");
|
|
90
|
+
return `${rendered}\n${sep}`;
|
|
91
|
+
}
|
|
92
|
+
return rendered;
|
|
93
|
+
});
|
|
94
|
+
return `\n\`\`\`\n${renderedRows.join("\n")}\n\`\`\`\n`;
|
|
95
|
+
}
|
|
96
|
+
function convertMarkdownTables(markdown) {
|
|
97
|
+
const { text, blocks } = protectCodeBlocks(markdown);
|
|
98
|
+
const lines = text.split("\n");
|
|
99
|
+
const out = [];
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (i < lines.length) {
|
|
102
|
+
if (i + 1 < lines.length && isMarkdownTableRow(lines[i]) && isMarkdownTableSeparator(lines[i + 1])) {
|
|
103
|
+
const tableLines = [lines[i], lines[i + 1]];
|
|
104
|
+
i += 2;
|
|
105
|
+
while (i < lines.length && isMarkdownTableRow(lines[i])) {
|
|
106
|
+
tableLines.push(lines[i]);
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
out.push(formatTableAsCodeBlock(tableLines));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
out.push(lines[i]);
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
return restoreCodeBlocks(out.join("\n"), blocks, 1);
|
|
116
|
+
}
|
|
117
|
+
function buildTableElement(lines, index) {
|
|
118
|
+
const rows = lines.filter((line) => !isMarkdownTableSeparator(line)).map(splitMarkdownTableRow);
|
|
119
|
+
if (rows.length < 2)
|
|
120
|
+
return null;
|
|
121
|
+
const headers = rows[0];
|
|
122
|
+
if (headers.length === 0)
|
|
123
|
+
return null;
|
|
124
|
+
const width = Math.min(Math.max(...rows.map((row) => row.length)), 50);
|
|
125
|
+
const columns = Array.from({ length: width }, (_, i) => ({
|
|
126
|
+
name: `c${index}_${i}`,
|
|
127
|
+
display_name: headers[i] || `列 ${i + 1}`,
|
|
128
|
+
data_type: "lark_md",
|
|
129
|
+
width: "auto",
|
|
130
|
+
vertical_align: "top",
|
|
131
|
+
horizontal_align: "left",
|
|
132
|
+
}));
|
|
133
|
+
const dataRows = rows.slice(1).map((row) => {
|
|
134
|
+
const item = {};
|
|
135
|
+
for (let i = 0; i < columns.length; i++)
|
|
136
|
+
item[columns[i].name] = row[i] || "";
|
|
137
|
+
return item;
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
tag: "table",
|
|
141
|
+
page_size: Math.min(Math.max(dataRows.length, 1), 10),
|
|
142
|
+
header_style: {
|
|
143
|
+
text_align: "left",
|
|
144
|
+
text_size: "normal",
|
|
145
|
+
background_style: "grey",
|
|
146
|
+
text_color: "default",
|
|
147
|
+
bold: true,
|
|
148
|
+
lines: 2,
|
|
149
|
+
},
|
|
150
|
+
columns,
|
|
151
|
+
rows: dataRows,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function pushMarkdownElement(elements, text) {
|
|
155
|
+
const content = optimizeMarkdownStyle(text.trim(), 2).trim();
|
|
156
|
+
if (!content)
|
|
157
|
+
return;
|
|
158
|
+
elements.push({ tag: "markdown", content });
|
|
159
|
+
}
|
|
160
|
+
export function buildFeishuCardElements(markdown) {
|
|
161
|
+
const { text, blocks } = protectCodeBlocks(markdown);
|
|
162
|
+
const lines = text.split("\n");
|
|
163
|
+
const elements = [];
|
|
164
|
+
const buffer = [];
|
|
165
|
+
let tableCount = 0;
|
|
166
|
+
let i = 0;
|
|
167
|
+
while (i < lines.length) {
|
|
168
|
+
if (i + 1 < lines.length && isMarkdownTableRow(lines[i]) && isMarkdownTableSeparator(lines[i + 1])) {
|
|
169
|
+
const tableLines = [lines[i], lines[i + 1]];
|
|
170
|
+
i += 2;
|
|
171
|
+
while (i < lines.length && isMarkdownTableRow(lines[i])) {
|
|
172
|
+
tableLines.push(lines[i]);
|
|
173
|
+
i++;
|
|
174
|
+
}
|
|
175
|
+
pushMarkdownElement(elements, restoreCodeBlocks(buffer.join("\n"), blocks, 1));
|
|
176
|
+
buffer.length = 0;
|
|
177
|
+
// Feishu cards support up to 5 table components per card. Fall back to a
|
|
178
|
+
// readable code-block table after that instead of sending an invalid card.
|
|
179
|
+
if (tableCount < 5) {
|
|
180
|
+
const table = buildTableElement(tableLines, tableCount);
|
|
181
|
+
if (table) {
|
|
182
|
+
elements.push(table);
|
|
183
|
+
tableCount++;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
buffer.push(formatTableAsCodeBlock(tableLines));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
buffer.push(formatTableAsCodeBlock(tableLines));
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
buffer.push(lines[i]);
|
|
195
|
+
i++;
|
|
196
|
+
}
|
|
197
|
+
pushMarkdownElement(elements, restoreCodeBlocks(buffer.join("\n"), blocks, 1));
|
|
198
|
+
return elements.length > 0 ? elements : [{ tag: "markdown", content: "" }];
|
|
199
|
+
}
|
|
200
|
+
export function prepareMarkdownForFeishu(text) {
|
|
201
|
+
return optimizeMarkdownStyle(convertMarkdownTables(text), 2);
|
|
202
|
+
}
|
|
203
|
+
export const __test__ = { convertMarkdownTables, optimizeMarkdownStyle, buildTableElement };
|