opencode-feishu 1.2.0 → 1.3.1
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/index.js +418 -214
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/skills/CLAUDE.md +3 -0
- package/skills/feishu-card-interaction.md +149 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs, { existsSync, readFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import url, { fileURLToPath } from 'url';
|
|
3
4
|
import { homedir } from 'os';
|
|
4
|
-
import crypto2 from 'crypto';
|
|
5
|
-
import url from 'url';
|
|
5
|
+
import crypto2, { randomUUID } from 'crypto';
|
|
6
6
|
import http from 'http';
|
|
7
7
|
import https from 'https';
|
|
8
8
|
import http2 from 'http2';
|
|
@@ -13,6 +13,8 @@ import { EventEmitter } from 'events';
|
|
|
13
13
|
import qs from 'querystring';
|
|
14
14
|
import WebSocket from 'ws';
|
|
15
15
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
16
|
+
import { tool } from '@opencode-ai/plugin';
|
|
17
|
+
import { createOpencodeClient } from '@opencode-ai/sdk/v2/client';
|
|
16
18
|
|
|
17
19
|
var __create = Object.create;
|
|
18
20
|
var __defProp = Object.defineProperty;
|
|
@@ -112523,6 +112525,7 @@ var FeishuConfigSchema = external_exports.object({
|
|
|
112523
112525
|
pollInterval: external_exports.number().int().positive().default(1e3),
|
|
112524
112526
|
stablePolls: external_exports.number().int().positive().default(3),
|
|
112525
112527
|
dedupTtl: external_exports.number().int().positive().default(10 * 60 * 1e3),
|
|
112528
|
+
maxResourceSize: external_exports.number().int().positive().max(500 * 1024 * 1024).default(500 * 1024 * 1024),
|
|
112526
112529
|
directory: external_exports.string().optional(),
|
|
112527
112530
|
autoPrompt: AutoPromptSchema.default(() => AutoPromptSchema.parse({}))
|
|
112528
112531
|
});
|
|
@@ -112600,6 +112603,29 @@ var CardKitClient = class {
|
|
|
112600
112603
|
});
|
|
112601
112604
|
}
|
|
112602
112605
|
}
|
|
112606
|
+
/**
|
|
112607
|
+
* 在卡片末尾追加新组件(用于流式卡片动态添加元素)
|
|
112608
|
+
*/
|
|
112609
|
+
async addElement(cardId, elements, sequence) {
|
|
112610
|
+
const res = await this.larkClient.cardkit.v1.cardElement.create({
|
|
112611
|
+
data: {
|
|
112612
|
+
type: "append",
|
|
112613
|
+
elements: JSON.stringify(elements),
|
|
112614
|
+
sequence
|
|
112615
|
+
},
|
|
112616
|
+
path: {
|
|
112617
|
+
card_id: cardId
|
|
112618
|
+
}
|
|
112619
|
+
});
|
|
112620
|
+
if (res?.code !== 0) {
|
|
112621
|
+
this.log?.("warn", "CardKit addElement \u5931\u8D25", {
|
|
112622
|
+
cardId,
|
|
112623
|
+
code: res?.code,
|
|
112624
|
+
msg: res?.msg
|
|
112625
|
+
});
|
|
112626
|
+
throw new Error(`CardKit addElement \u5931\u8D25: ${res?.msg ?? "unknown"} (code: ${res?.code})`);
|
|
112627
|
+
}
|
|
112628
|
+
}
|
|
112603
112629
|
/**
|
|
112604
112630
|
* 关闭卡片流式模式
|
|
112605
112631
|
*/
|
|
@@ -112623,94 +112649,49 @@ var CardKitClient = class {
|
|
|
112623
112649
|
}
|
|
112624
112650
|
};
|
|
112625
112651
|
|
|
112626
|
-
// src/
|
|
112627
|
-
|
|
112628
|
-
|
|
112629
|
-
|
|
112630
|
-
|
|
112631
|
-
|
|
112632
|
-
|
|
112633
|
-
|
|
112634
|
-
data
|
|
112635
|
-
|
|
112636
|
-
|
|
112637
|
-
|
|
112638
|
-
|
|
112639
|
-
|
|
112640
|
-
|
|
112641
|
-
|
|
112642
|
-
|
|
112643
|
-
|
|
112644
|
-
|
|
112645
|
-
|
|
112646
|
-
|
|
112647
|
-
|
|
112648
|
-
|
|
112649
|
-
|
|
112650
|
-
|
|
112651
|
-
|
|
112652
|
-
|
|
112653
|
-
|
|
112654
|
-
text: { tag: "plain_text", content: "\u2705 \u5141\u8BB8\u4E00\u6B21" },
|
|
112655
|
-
type: "primary",
|
|
112656
|
-
value: JSON.stringify({ action: "permission_reply", requestId, reply: "once" })
|
|
112657
|
-
},
|
|
112658
|
-
{
|
|
112659
|
-
tag: "button",
|
|
112660
|
-
text: { tag: "plain_text", content: "\u{1F513} \u59CB\u7EC8\u5141\u8BB8" },
|
|
112661
|
-
type: "default",
|
|
112662
|
-
value: JSON.stringify({ action: "permission_reply", requestId, reply: "always" })
|
|
112663
|
-
},
|
|
112664
|
-
{
|
|
112665
|
-
tag: "button",
|
|
112666
|
-
text: { tag: "plain_text", content: "\u274C \u62D2\u7EDD" },
|
|
112667
|
-
type: "danger",
|
|
112668
|
-
value: JSON.stringify({ action: "permission_reply", requestId, reply: "reject" })
|
|
112669
|
-
}
|
|
112670
|
-
]
|
|
112671
|
-
}
|
|
112672
|
-
]
|
|
112673
|
-
}
|
|
112652
|
+
// src/utils/ttl-map.ts
|
|
112653
|
+
var TtlMap = class {
|
|
112654
|
+
constructor(defaultTtlMs) {
|
|
112655
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
112656
|
+
}
|
|
112657
|
+
data = /* @__PURE__ */ new Map();
|
|
112658
|
+
timers = /* @__PURE__ */ new Map();
|
|
112659
|
+
get(key) {
|
|
112660
|
+
return this.data.get(key);
|
|
112661
|
+
}
|
|
112662
|
+
has(key) {
|
|
112663
|
+
return this.data.has(key);
|
|
112664
|
+
}
|
|
112665
|
+
set(key, value, ttlMs) {
|
|
112666
|
+
this.delete(key);
|
|
112667
|
+
this.data.set(key, value);
|
|
112668
|
+
const timer = setTimeout(() => {
|
|
112669
|
+
this.data.delete(key);
|
|
112670
|
+
this.timers.delete(key);
|
|
112671
|
+
}, ttlMs ?? this.defaultTtlMs);
|
|
112672
|
+
timer.unref();
|
|
112673
|
+
this.timers.set(key, timer);
|
|
112674
|
+
}
|
|
112675
|
+
delete(key) {
|
|
112676
|
+
const timer = this.timers.get(key);
|
|
112677
|
+
if (timer) {
|
|
112678
|
+
clearTimeout(timer);
|
|
112679
|
+
this.timers.delete(key);
|
|
112674
112680
|
}
|
|
112675
|
-
|
|
112681
|
+
this.data.delete(key);
|
|
112682
|
+
}
|
|
112683
|
+
};
|
|
112684
|
+
|
|
112685
|
+
// src/feishu/session-chat-map.ts
|
|
112686
|
+
var sessionToChat = new TtlMap(24 * 60 * 60 * 1e3);
|
|
112687
|
+
function registerSessionChat(sessionId, chatId, chatType) {
|
|
112688
|
+
sessionToChat.set(sessionId, { chatId, chatType });
|
|
112676
112689
|
}
|
|
112677
|
-
function
|
|
112678
|
-
|
|
112679
|
-
|
|
112680
|
-
|
|
112681
|
-
|
|
112682
|
-
const questionText = String(q?.question ?? "\u8BF7\u9009\u62E9");
|
|
112683
|
-
const options = Array.isArray(q?.options) ? q.options : [];
|
|
112684
|
-
const buttons = options.map((opt, idx) => ({
|
|
112685
|
-
tag: "button",
|
|
112686
|
-
text: { tag: "plain_text", content: String(opt.label ?? opt.value ?? `\u9009\u9879 ${idx + 1}`) },
|
|
112687
|
-
type: idx === 0 ? "primary" : "default",
|
|
112688
|
-
value: JSON.stringify({
|
|
112689
|
-
action: "question_reply",
|
|
112690
|
-
requestId,
|
|
112691
|
-
answers: [[String(opt.value ?? opt.label ?? "")]]
|
|
112692
|
-
})
|
|
112693
|
-
}));
|
|
112694
|
-
return {
|
|
112695
|
-
type: "card_kit",
|
|
112696
|
-
data: {
|
|
112697
|
-
schema: "2.0",
|
|
112698
|
-
config: { wide_screen_mode: true },
|
|
112699
|
-
header: {
|
|
112700
|
-
title: { tag: "plain_text", content: header },
|
|
112701
|
-
template: "blue"
|
|
112702
|
-
},
|
|
112703
|
-
body: {
|
|
112704
|
-
elements: [
|
|
112705
|
-
{
|
|
112706
|
-
tag: "markdown",
|
|
112707
|
-
content: questionText
|
|
112708
|
-
},
|
|
112709
|
-
...buttons.length > 0 ? [{ tag: "action", actions: buttons }] : []
|
|
112710
|
-
]
|
|
112711
|
-
}
|
|
112712
|
-
}
|
|
112713
|
-
};
|
|
112690
|
+
function getChatIdBySession(sessionId) {
|
|
112691
|
+
return sessionToChat.get(sessionId)?.chatId;
|
|
112692
|
+
}
|
|
112693
|
+
function getChatInfoBySession(sessionId) {
|
|
112694
|
+
return sessionToChat.get(sessionId);
|
|
112714
112695
|
}
|
|
112715
112696
|
|
|
112716
112697
|
// src/feishu/sender.ts
|
|
@@ -112786,38 +112767,150 @@ async function sendCardMessage(client, chatId, cardId) {
|
|
|
112786
112767
|
);
|
|
112787
112768
|
}
|
|
112788
112769
|
|
|
112789
|
-
// src/
|
|
112790
|
-
var
|
|
112791
|
-
|
|
112792
|
-
|
|
112793
|
-
|
|
112794
|
-
|
|
112795
|
-
|
|
112796
|
-
|
|
112797
|
-
|
|
112798
|
-
|
|
112799
|
-
|
|
112800
|
-
|
|
112770
|
+
// src/feishu/markdown.ts
|
|
112771
|
+
var MAX_CARD_BYTES = 28 * 1024;
|
|
112772
|
+
var TRUNCATION_SUFFIX = "\n\n*\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD*";
|
|
112773
|
+
var TRUNCATION_SUFFIX_BYTES = new TextEncoder().encode(TRUNCATION_SUFFIX).length;
|
|
112774
|
+
var CODE_FENCE_BYTES = 4;
|
|
112775
|
+
var HTML_TAG_RE = /<\/?\w+(?:\s[^>]*)?\/?>/g;
|
|
112776
|
+
function cleanMarkdown(text) {
|
|
112777
|
+
let result = text.replace(/<br\s*\/?>/gi, "\n");
|
|
112778
|
+
const { segments, codeBlocks } = extractCodeBlocks(result);
|
|
112779
|
+
result = segments.map((seg) => seg.replace(HTML_TAG_RE, "")).join("\0");
|
|
112780
|
+
let idx = 0;
|
|
112781
|
+
result = result.replace(/\0/g, () => codeBlocks[idx++] ?? "");
|
|
112782
|
+
result = closeCodeBlocks(result);
|
|
112783
|
+
return result;
|
|
112784
|
+
}
|
|
112785
|
+
function truncateMarkdown(text, limit = MAX_CARD_BYTES) {
|
|
112786
|
+
const bytes = new TextEncoder().encode(text);
|
|
112787
|
+
if (bytes.length <= limit) return text;
|
|
112788
|
+
const effectiveLimit = limit - TRUNCATION_SUFFIX_BYTES - CODE_FENCE_BYTES;
|
|
112789
|
+
if (effectiveLimit <= 0) return TRUNCATION_SUFFIX;
|
|
112790
|
+
const truncated = new TextDecoder().decode(bytes.slice(0, effectiveLimit));
|
|
112791
|
+
const lastNewline = truncated.lastIndexOf("\n");
|
|
112792
|
+
const cutPoint = lastNewline > effectiveLimit * 0.8 ? lastNewline : truncated.length;
|
|
112793
|
+
let result = truncated.slice(0, cutPoint);
|
|
112794
|
+
result = closeCodeBlocks(result);
|
|
112795
|
+
return result + TRUNCATION_SUFFIX;
|
|
112796
|
+
}
|
|
112797
|
+
function extractCodeBlocks(text) {
|
|
112798
|
+
const segments = [];
|
|
112799
|
+
const codeBlocks = [];
|
|
112800
|
+
const re = /```[\s\S]*?```/g;
|
|
112801
|
+
let lastIndex = 0;
|
|
112802
|
+
let match;
|
|
112803
|
+
while ((match = re.exec(text)) !== null) {
|
|
112804
|
+
segments.push(text.slice(lastIndex, match.index));
|
|
112805
|
+
codeBlocks.push(match[0]);
|
|
112806
|
+
lastIndex = match.index + match[0].length;
|
|
112801
112807
|
}
|
|
112802
|
-
|
|
112803
|
-
|
|
112804
|
-
|
|
112805
|
-
|
|
112806
|
-
|
|
112807
|
-
|
|
112808
|
-
|
|
112809
|
-
timer.unref();
|
|
112810
|
-
this.timers.set(key, timer);
|
|
112808
|
+
segments.push(text.slice(lastIndex));
|
|
112809
|
+
return { segments, codeBlocks };
|
|
112810
|
+
}
|
|
112811
|
+
function closeCodeBlocks(text) {
|
|
112812
|
+
const matches = text.match(/```/g);
|
|
112813
|
+
if (matches && matches.length % 2 !== 0) {
|
|
112814
|
+
return text + "\n```";
|
|
112811
112815
|
}
|
|
112812
|
-
|
|
112813
|
-
|
|
112814
|
-
|
|
112815
|
-
|
|
112816
|
-
|
|
112816
|
+
return text;
|
|
112817
|
+
}
|
|
112818
|
+
|
|
112819
|
+
// src/tools/send-card.ts
|
|
112820
|
+
var z2 = tool.schema;
|
|
112821
|
+
var TEMPLATE_COLORS = ["blue", "green", "orange", "red", "purple", "grey"];
|
|
112822
|
+
function createSendCardTool(deps) {
|
|
112823
|
+
return tool({
|
|
112824
|
+
description: "\u53D1\u9001\u683C\u5F0F\u5316\u5361\u7247\u6D88\u606F\u5230\u5F53\u524D\u98DE\u4E66\u4F1A\u8BDD\u3002\u652F\u6301 markdown \u6B63\u6587\u3001\u5206\u5272\u7EBF\u3001\u5907\u6CE8\u548C\u4EA4\u4E92\u6309\u94AE\u3002\u6309\u94AE\u70B9\u51FB\u7B49\u540C\u7528\u6237\u53D1\u9001\u6D88\u606F\u3002\u5361\u7247\u4F5C\u4E3A\u72EC\u7ACB\u6D88\u606F\u53D1\u9001\uFF0C\u4E0D\u5F71\u54CD\u6D41\u5F0F\u56DE\u590D\u3002",
|
|
112825
|
+
args: {
|
|
112826
|
+
title: z2.string().describe("\u5361\u7247\u6807\u9898"),
|
|
112827
|
+
template: z2.enum(TEMPLATE_COLORS).default("blue").describe("\u6807\u9898\u989C\u8272\u4E3B\u9898"),
|
|
112828
|
+
sections: z2.array(
|
|
112829
|
+
z2.object({
|
|
112830
|
+
type: z2.enum(["markdown", "divider", "note", "actions"]).default("markdown").describe("\u533A\u5757\u7C7B\u578B\uFF1Amarkdown\uFF08\u6B63\u6587\uFF09\u3001divider\uFF08\u5206\u5272\u7EBF\uFF09\u3001note\uFF08\u5907\u6CE8\uFF09\u3001actions\uFF08\u6309\u94AE\u7EC4\uFF09"),
|
|
112831
|
+
content: z2.string().optional().describe("\u533A\u5757\u5185\u5BB9\uFF08markdown \u683C\u5F0F\uFF0Cdivider/actions \u7C7B\u578B\u65E0\u9700\u6B64\u5B57\u6BB5\uFF09"),
|
|
112832
|
+
buttons: z2.array(
|
|
112833
|
+
z2.object({
|
|
112834
|
+
text: z2.string().describe("\u6309\u94AE\u663E\u793A\u6587\u672C\uFF082-6\u5B57\uFF09"),
|
|
112835
|
+
value: z2.string().describe("\u70B9\u51FB\u540E\u4F5C\u4E3A\u7528\u6237\u6D88\u606F\u53D1\u9001\u7684\u5185\u5BB9"),
|
|
112836
|
+
style: z2.enum(["primary", "default", "danger"]).default("default").describe("\u6309\u94AE\u6837\u5F0F")
|
|
112837
|
+
})
|
|
112838
|
+
).optional().describe("\u6309\u94AE\u5217\u8868\uFF08\u4EC5 actions \u7C7B\u578B\u4F7F\u7528\uFF09")
|
|
112839
|
+
})
|
|
112840
|
+
).min(1).describe("\u5361\u7247\u6B63\u6587\u533A\u5757\u5217\u8868")
|
|
112841
|
+
},
|
|
112842
|
+
async execute(args, context) {
|
|
112843
|
+
const chatId = getChatIdBySession(context.sessionID);
|
|
112844
|
+
if (!chatId) {
|
|
112845
|
+
return "\u9519\u8BEF\uFF1A\u5F53\u524D\u4F1A\u8BDD\u4E0D\u5173\u8054\u98DE\u4E66\u804A\u5929\uFF0C\u65E0\u6CD5\u53D1\u9001\u5361\u7247";
|
|
112846
|
+
}
|
|
112847
|
+
const chatInfo = getChatInfoBySession(context.sessionID);
|
|
112848
|
+
const card = buildCardFromDSL(args, chatId, chatInfo?.chatType ?? "p2p");
|
|
112849
|
+
const result = await sendInteractiveCard(deps.feishuClient, chatId, card);
|
|
112850
|
+
if (result.ok) {
|
|
112851
|
+
deps.log("info", "Agent \u5361\u7247\u5DF2\u53D1\u9001", {
|
|
112852
|
+
sessionId: context.sessionID,
|
|
112853
|
+
chatId,
|
|
112854
|
+
title: args.title,
|
|
112855
|
+
messageId: result.messageId
|
|
112856
|
+
});
|
|
112857
|
+
return `\u5361\u7247\u5DF2\u53D1\u9001\uFF1A\u300C${args.title}\u300D`;
|
|
112858
|
+
}
|
|
112859
|
+
deps.log("warn", "Agent \u5361\u7247\u53D1\u9001\u5931\u8D25", {
|
|
112860
|
+
sessionId: context.sessionID,
|
|
112861
|
+
chatId,
|
|
112862
|
+
title: args.title,
|
|
112863
|
+
error: result.error
|
|
112864
|
+
});
|
|
112865
|
+
return `\u5361\u7247\u53D1\u9001\u5931\u8D25\uFF1A${result.error}`;
|
|
112817
112866
|
}
|
|
112818
|
-
|
|
112819
|
-
|
|
112820
|
-
|
|
112867
|
+
});
|
|
112868
|
+
}
|
|
112869
|
+
function buildCardFromDSL(args, chatId, chatType) {
|
|
112870
|
+
return {
|
|
112871
|
+
schema: "2.0",
|
|
112872
|
+
config: { wide_screen_mode: true },
|
|
112873
|
+
header: {
|
|
112874
|
+
title: { tag: "plain_text", content: args.title },
|
|
112875
|
+
template: args.template
|
|
112876
|
+
},
|
|
112877
|
+
body: {
|
|
112878
|
+
elements: args.sections.map((s) => {
|
|
112879
|
+
switch (s.type) {
|
|
112880
|
+
case "divider":
|
|
112881
|
+
return { tag: "hr" };
|
|
112882
|
+
case "note":
|
|
112883
|
+
return {
|
|
112884
|
+
tag: "note",
|
|
112885
|
+
elements: [{ tag: "plain_text", content: s.content ?? "" }]
|
|
112886
|
+
};
|
|
112887
|
+
case "actions":
|
|
112888
|
+
if (!s.buttons?.length) return null;
|
|
112889
|
+
return {
|
|
112890
|
+
tag: "action",
|
|
112891
|
+
actions: s.buttons.map((btn) => ({
|
|
112892
|
+
tag: "button",
|
|
112893
|
+
text: { tag: "plain_text", content: btn.text },
|
|
112894
|
+
type: btn.style,
|
|
112895
|
+
value: JSON.stringify(btn.actionPayload ?? {
|
|
112896
|
+
action: "send_message",
|
|
112897
|
+
chatId,
|
|
112898
|
+
chatType,
|
|
112899
|
+
text: btn.value
|
|
112900
|
+
})
|
|
112901
|
+
}))
|
|
112902
|
+
};
|
|
112903
|
+
case "markdown":
|
|
112904
|
+
default:
|
|
112905
|
+
return {
|
|
112906
|
+
tag: "markdown",
|
|
112907
|
+
content: truncateMarkdown(s.content ?? "", 28e3)
|
|
112908
|
+
};
|
|
112909
|
+
}
|
|
112910
|
+
}).filter(Boolean)
|
|
112911
|
+
}
|
|
112912
|
+
};
|
|
112913
|
+
}
|
|
112821
112914
|
|
|
112822
112915
|
// src/handler/interactive.ts
|
|
112823
112916
|
var seenIds = new TtlMap(10 * 60 * 1e3);
|
|
@@ -112826,14 +112919,14 @@ function markSeen(requestId) {
|
|
|
112826
112919
|
seenIds.set(requestId, true);
|
|
112827
112920
|
return true;
|
|
112828
112921
|
}
|
|
112829
|
-
function handlePermissionRequested(request, chatId, deps) {
|
|
112922
|
+
function handlePermissionRequested(request, chatId, deps, chatType = "p2p") {
|
|
112830
112923
|
if (!deps.v2Client) {
|
|
112831
112924
|
deps.log("warn", "v2Client \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u6743\u9650\u5361\u7247\u53D1\u9001", { requestId: String(request.id ?? "") });
|
|
112832
112925
|
return;
|
|
112833
112926
|
}
|
|
112834
112927
|
const requestId = String(request.id ?? "");
|
|
112835
112928
|
if (!requestId || !markSeen(requestId)) return;
|
|
112836
|
-
const card =
|
|
112929
|
+
const card = buildPermissionCardDSL(request, chatId, chatType);
|
|
112837
112930
|
sendInteractiveCard(deps.feishuClient, chatId, card).catch((err) => {
|
|
112838
112931
|
deps.log("warn", "\u53D1\u9001\u6743\u9650\u5361\u7247\u5931\u8D25", {
|
|
112839
112932
|
requestId,
|
|
@@ -112841,14 +112934,14 @@ function handlePermissionRequested(request, chatId, deps) {
|
|
|
112841
112934
|
});
|
|
112842
112935
|
});
|
|
112843
112936
|
}
|
|
112844
|
-
function handleQuestionRequested(request, chatId, deps) {
|
|
112937
|
+
function handleQuestionRequested(request, chatId, deps, chatType = "p2p") {
|
|
112845
112938
|
if (!deps.v2Client) {
|
|
112846
112939
|
deps.log("warn", "v2Client \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u95EE\u7B54\u5361\u7247\u53D1\u9001", { requestId: String(request.id ?? "") });
|
|
112847
112940
|
return;
|
|
112848
112941
|
}
|
|
112849
112942
|
const requestId = String(request.id ?? "");
|
|
112850
112943
|
if (!requestId || !markSeen(requestId)) return;
|
|
112851
|
-
const card =
|
|
112944
|
+
const card = buildQuestionCardDSL(request, chatId, chatType);
|
|
112852
112945
|
sendInteractiveCard(deps.feishuClient, chatId, card).catch((err) => {
|
|
112853
112946
|
deps.log("warn", "\u53D1\u9001\u95EE\u7B54\u5361\u7247\u5931\u8D25", {
|
|
112854
112947
|
requestId,
|
|
@@ -112858,12 +112951,39 @@ function handleQuestionRequested(request, chatId, deps) {
|
|
|
112858
112951
|
}
|
|
112859
112952
|
async function handleCardAction(action, deps) {
|
|
112860
112953
|
if (!action.actionValue) return;
|
|
112861
|
-
{
|
|
112954
|
+
if (!deps.v2Client) {
|
|
112862
112955
|
deps.log("warn", "v2Client \u672A\u914D\u7F6E\uFF0C\u4EA4\u4E92\u56DE\u8C03\u88AB\u5FFD\u7565\uFF08\u6309\u94AE\u70B9\u51FB\u4E0D\u4F1A\u8F6C\u53D1\u5230 OpenCode\uFF09", {
|
|
112863
112956
|
actionValue: action.actionValue
|
|
112864
112957
|
});
|
|
112865
112958
|
return;
|
|
112866
112959
|
}
|
|
112960
|
+
let value;
|
|
112961
|
+
try {
|
|
112962
|
+
value = JSON.parse(action.actionValue);
|
|
112963
|
+
} catch {
|
|
112964
|
+
return;
|
|
112965
|
+
}
|
|
112966
|
+
const requestId = value.requestId;
|
|
112967
|
+
if (!requestId) return;
|
|
112968
|
+
try {
|
|
112969
|
+
if (value.action === "permission_reply" && "reply" in value) {
|
|
112970
|
+
await deps.v2Client.permission.reply({
|
|
112971
|
+
requestID: requestId,
|
|
112972
|
+
reply: value.reply
|
|
112973
|
+
});
|
|
112974
|
+
} else if (value.action === "question_reply" && "answers" in value) {
|
|
112975
|
+
await deps.v2Client.question.reply({
|
|
112976
|
+
requestID: requestId,
|
|
112977
|
+
answers: value.answers
|
|
112978
|
+
});
|
|
112979
|
+
}
|
|
112980
|
+
} catch (err) {
|
|
112981
|
+
deps.log("error", "\u4EA4\u4E92\u56DE\u8C03\u5904\u7406\u5931\u8D25", {
|
|
112982
|
+
action: value.action,
|
|
112983
|
+
requestId,
|
|
112984
|
+
error: err instanceof Error ? err.message : String(err)
|
|
112985
|
+
});
|
|
112986
|
+
}
|
|
112867
112987
|
}
|
|
112868
112988
|
function buildCallbackResponse(action) {
|
|
112869
112989
|
if (!action.actionValue) return {};
|
|
@@ -112887,8 +113007,71 @@ function buildCallbackResponse(action) {
|
|
|
112887
113007
|
toast: { type: "success", content: "\u2705 \u5DF2\u56DE\u7B54" }
|
|
112888
113008
|
};
|
|
112889
113009
|
}
|
|
113010
|
+
if (value.action === "send_message") {
|
|
113011
|
+
return {
|
|
113012
|
+
toast: { type: "info", content: "\u{1F4E8} \u5DF2\u53D1\u9001" }
|
|
113013
|
+
};
|
|
113014
|
+
}
|
|
112890
113015
|
return {};
|
|
112891
113016
|
}
|
|
113017
|
+
function buildPermissionCardDSL(request, chatId, chatType) {
|
|
113018
|
+
const permission = String(request.permission ?? "unknown");
|
|
113019
|
+
const patterns = Array.isArray(request.patterns) ? request.patterns.map(String) : [];
|
|
113020
|
+
const requestId = String(request.id ?? "");
|
|
113021
|
+
const patternsText = patterns.length > 0 ? patterns.map((p) => `- \`${p}\``).join("\n") : "\uFF08\u65E0\u5177\u4F53\u8DEF\u5F84\uFF09";
|
|
113022
|
+
const buttons = [
|
|
113023
|
+
{
|
|
113024
|
+
text: "\u2705 \u5141\u8BB8\u4E00\u6B21",
|
|
113025
|
+
value: "",
|
|
113026
|
+
style: "primary",
|
|
113027
|
+
actionPayload: { action: "permission_reply", requestId, reply: "once" }
|
|
113028
|
+
},
|
|
113029
|
+
{
|
|
113030
|
+
text: "\u{1F513} \u59CB\u7EC8\u5141\u8BB8",
|
|
113031
|
+
value: "",
|
|
113032
|
+
style: "default",
|
|
113033
|
+
actionPayload: { action: "permission_reply", requestId, reply: "always" }
|
|
113034
|
+
},
|
|
113035
|
+
{
|
|
113036
|
+
text: "\u274C \u62D2\u7EDD",
|
|
113037
|
+
value: "",
|
|
113038
|
+
style: "danger",
|
|
113039
|
+
actionPayload: { action: "permission_reply", requestId, reply: "reject" }
|
|
113040
|
+
}
|
|
113041
|
+
];
|
|
113042
|
+
const sections = [
|
|
113043
|
+
{ type: "markdown", content: `AI \u8BF7\u6C42\u4EE5\u4E0B\u6743\u9650:
|
|
113044
|
+
|
|
113045
|
+
${patternsText}` },
|
|
113046
|
+
{ type: "actions", buttons }
|
|
113047
|
+
];
|
|
113048
|
+
const dsl = { title: `\u{1F510} \u6743\u9650\u8BF7\u6C42: ${permission}`, template: "orange", sections };
|
|
113049
|
+
return { type: "card_kit", data: buildCardFromDSL(dsl, chatId, chatType) };
|
|
113050
|
+
}
|
|
113051
|
+
function buildQuestionCardDSL(request, chatId, chatType) {
|
|
113052
|
+
const questions = request.questions ?? [];
|
|
113053
|
+
const requestId = String(request.id ?? "");
|
|
113054
|
+
const q = questions[0];
|
|
113055
|
+
const header = String(q?.header ?? "AI \u63D0\u95EE");
|
|
113056
|
+
const questionText = String(q?.question ?? "\u8BF7\u9009\u62E9");
|
|
113057
|
+
const options = Array.isArray(q?.options) ? q.options : [];
|
|
113058
|
+
const buttons = options.map((opt, idx) => ({
|
|
113059
|
+
text: String(opt.label ?? opt.value ?? `\u9009\u9879 ${idx + 1}`),
|
|
113060
|
+
value: "",
|
|
113061
|
+
style: idx === 0 ? "primary" : "default",
|
|
113062
|
+
actionPayload: {
|
|
113063
|
+
action: "question_reply",
|
|
113064
|
+
requestId,
|
|
113065
|
+
answers: [[String(opt.value ?? opt.label ?? "")]]
|
|
113066
|
+
}
|
|
113067
|
+
}));
|
|
113068
|
+
const sections = [
|
|
113069
|
+
{ type: "markdown", content: questionText },
|
|
113070
|
+
...buttons.length > 0 ? [{ type: "actions", buttons }] : []
|
|
113071
|
+
];
|
|
113072
|
+
const dsl = { title: header, template: "blue", sections };
|
|
113073
|
+
return { type: "card_kit", data: buildCardFromDSL(dsl, chatId, chatType) };
|
|
113074
|
+
}
|
|
112892
113075
|
|
|
112893
113076
|
// src/feishu/dedup.ts
|
|
112894
113077
|
var dedup = new TtlMap(10 * 60 * 1e3);
|
|
@@ -112903,8 +113086,7 @@ function isDuplicate(messageId) {
|
|
|
112903
113086
|
}
|
|
112904
113087
|
|
|
112905
113088
|
// src/feishu/resource.ts
|
|
112906
|
-
|
|
112907
|
-
async function downloadMessageResource(client, messageId, fileKey, type, log) {
|
|
113089
|
+
async function downloadMessageResource(client, messageId, fileKey, type, log, maxSize) {
|
|
112908
113090
|
try {
|
|
112909
113091
|
const res = await client.im.messageResource.get({
|
|
112910
113092
|
path: { message_id: messageId, file_key: fileKey },
|
|
@@ -112912,7 +113094,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
|
|
|
112912
113094
|
});
|
|
112913
113095
|
if (!res) {
|
|
112914
113096
|
log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u8FD4\u56DE\u7A7A\u6570\u636E", { messageId, fileKey, type });
|
|
112915
|
-
return null;
|
|
113097
|
+
return { resource: null, reason: "error" };
|
|
112916
113098
|
}
|
|
112917
113099
|
const stream4 = res.getReadableStream();
|
|
112918
113100
|
const chunks = [];
|
|
@@ -112920,10 +113102,10 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
|
|
|
112920
113102
|
for await (const chunk of stream4) {
|
|
112921
113103
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
112922
113104
|
totalSize += buf.length;
|
|
112923
|
-
if (totalSize >
|
|
112924
|
-
log("warn", "\u8D44\u6E90\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D", { messageId, fileKey, totalSize });
|
|
113105
|
+
if (totalSize > maxSize) {
|
|
113106
|
+
log("warn", "\u8D44\u6E90\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u4E0B\u8F7D", { messageId, fileKey, totalSize, maxSize });
|
|
112925
113107
|
stream4.destroy();
|
|
112926
|
-
return null;
|
|
113108
|
+
return { resource: null, reason: "too_large", totalSize };
|
|
112927
113109
|
}
|
|
112928
113110
|
chunks.push(buf);
|
|
112929
113111
|
}
|
|
@@ -112932,7 +113114,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
|
|
|
112932
113114
|
const contentType = headers?.["content-type"] ?? guessMimeByType(type);
|
|
112933
113115
|
const base643 = buffer.toString("base64");
|
|
112934
113116
|
const dataUrl = `data:${contentType};base64,${base643}`;
|
|
112935
|
-
return { dataUrl, mime: contentType };
|
|
113117
|
+
return { resource: { dataUrl, mime: contentType }, reason: "ok" };
|
|
112936
113118
|
} catch (err) {
|
|
112937
113119
|
log("warn", "\u8D44\u6E90\u4E0B\u8F7D\u5931\u8D25", {
|
|
112938
113120
|
messageId,
|
|
@@ -112940,7 +113122,7 @@ async function downloadMessageResource(client, messageId, fileKey, type, log) {
|
|
|
112940
113122
|
type,
|
|
112941
113123
|
error: err instanceof Error ? err.message : String(err)
|
|
112942
113124
|
});
|
|
112943
|
-
return null;
|
|
113125
|
+
return { resource: null, reason: "error" };
|
|
112944
113126
|
}
|
|
112945
113127
|
}
|
|
112946
113128
|
function guessMimeByType(type) {
|
|
@@ -112980,19 +113162,19 @@ function guessMimeByFilename(filename) {
|
|
|
112980
113162
|
}
|
|
112981
113163
|
|
|
112982
113164
|
// src/feishu/content-extractor.ts
|
|
112983
|
-
async function extractParts(feishuClient, messageId, messageType, rawContent, log) {
|
|
113165
|
+
async function extractParts(feishuClient, messageId, messageType, rawContent, log, maxResourceSize) {
|
|
112984
113166
|
try {
|
|
112985
113167
|
switch (messageType) {
|
|
112986
113168
|
case "text":
|
|
112987
113169
|
return extractText(rawContent);
|
|
112988
113170
|
case "image":
|
|
112989
|
-
return await extractImage(feishuClient, messageId, rawContent, log);
|
|
113171
|
+
return await extractImage(feishuClient, messageId, rawContent, log, maxResourceSize);
|
|
112990
113172
|
case "post":
|
|
112991
113173
|
return extractPost(rawContent);
|
|
112992
113174
|
case "file":
|
|
112993
|
-
return await extractFile(feishuClient, messageId, rawContent, log);
|
|
113175
|
+
return await extractFile(feishuClient, messageId, rawContent, log, maxResourceSize);
|
|
112994
113176
|
case "audio":
|
|
112995
|
-
return await extractAudio(feishuClient, messageId, rawContent, log);
|
|
113177
|
+
return await extractAudio(feishuClient, messageId, rawContent, log, maxResourceSize);
|
|
112996
113178
|
case "media":
|
|
112997
113179
|
return extractMediaFallback();
|
|
112998
113180
|
case "sticker":
|
|
@@ -113063,13 +113245,15 @@ function extractText(rawContent) {
|
|
|
113063
113245
|
if (!text) return [];
|
|
113064
113246
|
return [{ type: "text", text }];
|
|
113065
113247
|
}
|
|
113066
|
-
async function extractImage(client, messageId, rawContent, log) {
|
|
113248
|
+
async function extractImage(client, messageId, rawContent, log, maxResourceSize) {
|
|
113067
113249
|
const parsed = JSON.parse(rawContent);
|
|
113068
113250
|
const imageKey = parsed.image_key;
|
|
113069
113251
|
if (!imageKey) return [{ type: "text", text: "[\u56FE\u7247: \u65E0\u6CD5\u83B7\u53D6]" }];
|
|
113070
|
-
const
|
|
113071
|
-
if (!resource)
|
|
113072
|
-
|
|
113252
|
+
const result = await downloadMessageResource(client, messageId, imageKey, "image", log, maxResourceSize);
|
|
113253
|
+
if (!result.resource) {
|
|
113254
|
+
return [{ type: "text", text: formatDownloadFailure("\u56FE\u7247", result, maxResourceSize) }];
|
|
113255
|
+
}
|
|
113256
|
+
return [{ type: "file", mime: result.resource.mime, url: result.resource.dataUrl }];
|
|
113073
113257
|
}
|
|
113074
113258
|
function extractPost(rawContent) {
|
|
113075
113259
|
const text = extractPostText(rawContent);
|
|
@@ -113104,27 +113288,39 @@ function extractPostText(rawContent) {
|
|
|
113104
113288
|
return "";
|
|
113105
113289
|
}
|
|
113106
113290
|
}
|
|
113107
|
-
async function extractFile(client, messageId, rawContent, log) {
|
|
113291
|
+
async function extractFile(client, messageId, rawContent, log, maxResourceSize) {
|
|
113108
113292
|
const parsed = JSON.parse(rawContent);
|
|
113109
113293
|
const fileKey = parsed.file_key;
|
|
113110
113294
|
const fileName = parsed.file_name ?? "\u672A\u77E5\u6587\u4EF6";
|
|
113111
113295
|
if (!fileKey) return [{ type: "text", text: `[\u6587\u4EF6: ${fileName}]` }];
|
|
113112
113296
|
const mime = guessMimeByFilename(fileName);
|
|
113113
|
-
const
|
|
113114
|
-
if (!resource)
|
|
113115
|
-
|
|
113297
|
+
const result = await downloadMessageResource(client, messageId, fileKey, "file", log, maxResourceSize);
|
|
113298
|
+
if (!result.resource) {
|
|
113299
|
+
return [{ type: "text", text: formatDownloadFailure(fileName, result, maxResourceSize) }];
|
|
113300
|
+
}
|
|
113301
|
+
return [{ type: "file", mime: result.resource.mime || mime, url: result.resource.dataUrl, filename: fileName }];
|
|
113116
113302
|
}
|
|
113117
|
-
async function extractAudio(client, messageId, rawContent, log) {
|
|
113303
|
+
async function extractAudio(client, messageId, rawContent, log, maxResourceSize) {
|
|
113118
113304
|
const parsed = JSON.parse(rawContent);
|
|
113119
113305
|
const fileKey = parsed.file_key;
|
|
113120
113306
|
if (!fileKey) return [{ type: "text", text: "[\u8BED\u97F3: \u65E0\u6CD5\u83B7\u53D6]" }];
|
|
113121
|
-
const
|
|
113122
|
-
if (!resource)
|
|
113123
|
-
|
|
113307
|
+
const result = await downloadMessageResource(client, messageId, fileKey, "file", log, maxResourceSize);
|
|
113308
|
+
if (!result.resource) {
|
|
113309
|
+
return [{ type: "text", text: formatDownloadFailure("\u8BED\u97F3", result, maxResourceSize) }];
|
|
113310
|
+
}
|
|
113311
|
+
return [{ type: "file", mime: result.resource.mime || "audio/opus", url: result.resource.dataUrl }];
|
|
113124
113312
|
}
|
|
113125
113313
|
function extractMediaFallback() {
|
|
113126
113314
|
return [{ type: "text", text: "[\u89C6\u9891\u6D88\u606F]" }];
|
|
113127
113315
|
}
|
|
113316
|
+
function formatDownloadFailure(label, result, maxSize) {
|
|
113317
|
+
if (result.reason === "too_large" && result.totalSize) {
|
|
113318
|
+
const sizeMB = (result.totalSize / (1024 * 1024)).toFixed(1);
|
|
113319
|
+
const limitMB = (maxSize / (1024 * 1024)).toFixed(0);
|
|
113320
|
+
return `[\u6587\u4EF6\u8FC7\u5927: ${label}, \u5DF2\u4E0B\u8F7D ${sizeMB}MB \u65F6\u8D85\u51FA ${limitMB}MB \u9650\u5236]`;
|
|
113321
|
+
}
|
|
113322
|
+
return `[\u4E0B\u8F7D\u5931\u8D25: ${label}]`;
|
|
113323
|
+
}
|
|
113128
113324
|
function extractInteractive(rawContent) {
|
|
113129
113325
|
try {
|
|
113130
113326
|
const parsed = JSON.parse(rawContent);
|
|
@@ -113255,6 +113451,25 @@ function startFeishuGateway(options) {
|
|
|
113255
113451
|
chatId: String(evt.context?.open_chat_id ?? evt.open_chat_id ?? ""),
|
|
113256
113452
|
operatorId: String(evt.operator?.open_id ?? "")
|
|
113257
113453
|
};
|
|
113454
|
+
const sendMsg = parseSendMessageAction(action);
|
|
113455
|
+
if (sendMsg) {
|
|
113456
|
+
const syntheticCtx = {
|
|
113457
|
+
chatId: sendMsg.chatId,
|
|
113458
|
+
messageId: `btn-${randomUUID()}`,
|
|
113459
|
+
messageType: "text",
|
|
113460
|
+
content: sendMsg.text,
|
|
113461
|
+
rawContent: JSON.stringify({ text: sendMsg.text }),
|
|
113462
|
+
chatType: sendMsg.chatType,
|
|
113463
|
+
senderId: action.operatorId ?? "",
|
|
113464
|
+
shouldReply: true
|
|
113465
|
+
};
|
|
113466
|
+
void Promise.resolve(onMessage(syntheticCtx)).catch((err) => {
|
|
113467
|
+
log("error", "send_message \u6309\u94AE\u5904\u7406\u5931\u8D25", {
|
|
113468
|
+
error: err instanceof Error ? err.message : String(err)
|
|
113469
|
+
});
|
|
113470
|
+
});
|
|
113471
|
+
return buildCallbackResponse(action);
|
|
113472
|
+
}
|
|
113258
113473
|
if (onCardAction) {
|
|
113259
113474
|
void onCardAction(action).catch((err) => {
|
|
113260
113475
|
log("error", "card action \u5904\u7406\u5931\u8D25", {
|
|
@@ -113302,6 +113517,20 @@ function startFeishuGateway(options) {
|
|
|
113302
113517
|
};
|
|
113303
113518
|
return { client: larkClient, stop };
|
|
113304
113519
|
}
|
|
113520
|
+
function parseSendMessageAction(action) {
|
|
113521
|
+
if (!action.actionValue) return void 0;
|
|
113522
|
+
try {
|
|
113523
|
+
const value = JSON.parse(action.actionValue);
|
|
113524
|
+
if (value.action !== "send_message") return void 0;
|
|
113525
|
+
const text = typeof value.text === "string" ? value.text : "";
|
|
113526
|
+
const chatId = typeof value.chatId === "string" ? value.chatId : "";
|
|
113527
|
+
if (!text || !chatId) return void 0;
|
|
113528
|
+
const chatType = value.chatType === "group" ? "group" : "p2p";
|
|
113529
|
+
return { chatId, chatType, text };
|
|
113530
|
+
} catch {
|
|
113531
|
+
return void 0;
|
|
113532
|
+
}
|
|
113533
|
+
}
|
|
113305
113534
|
|
|
113306
113535
|
// src/handler/action-bus.ts
|
|
113307
113536
|
var subscribers = /* @__PURE__ */ new Map();
|
|
@@ -113698,55 +113927,6 @@ async function getOrCreateSession(client, sessionKey, directory) {
|
|
|
113698
113927
|
return session;
|
|
113699
113928
|
}
|
|
113700
113929
|
|
|
113701
|
-
// src/feishu/markdown.ts
|
|
113702
|
-
var MAX_CARD_BYTES = 28 * 1024;
|
|
113703
|
-
var TRUNCATION_SUFFIX = "\n\n*\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD*";
|
|
113704
|
-
var TRUNCATION_SUFFIX_BYTES = new TextEncoder().encode(TRUNCATION_SUFFIX).length;
|
|
113705
|
-
var CODE_FENCE_BYTES = 4;
|
|
113706
|
-
var HTML_TAG_RE = /<\/?\w+(?:\s[^>]*)?\/?>/g;
|
|
113707
|
-
function cleanMarkdown(text) {
|
|
113708
|
-
let result = text.replace(/<br\s*\/?>/gi, "\n");
|
|
113709
|
-
const { segments, codeBlocks } = extractCodeBlocks(result);
|
|
113710
|
-
result = segments.map((seg) => seg.replace(HTML_TAG_RE, "")).join("\0");
|
|
113711
|
-
let idx = 0;
|
|
113712
|
-
result = result.replace(/\0/g, () => codeBlocks[idx++] ?? "");
|
|
113713
|
-
result = closeCodeBlocks(result);
|
|
113714
|
-
return result;
|
|
113715
|
-
}
|
|
113716
|
-
function truncateMarkdown(text, limit = MAX_CARD_BYTES) {
|
|
113717
|
-
const bytes = new TextEncoder().encode(text);
|
|
113718
|
-
if (bytes.length <= limit) return text;
|
|
113719
|
-
const effectiveLimit = limit - TRUNCATION_SUFFIX_BYTES - CODE_FENCE_BYTES;
|
|
113720
|
-
if (effectiveLimit <= 0) return TRUNCATION_SUFFIX;
|
|
113721
|
-
const truncated = new TextDecoder().decode(bytes.slice(0, effectiveLimit));
|
|
113722
|
-
const lastNewline = truncated.lastIndexOf("\n");
|
|
113723
|
-
const cutPoint = lastNewline > effectiveLimit * 0.8 ? lastNewline : truncated.length;
|
|
113724
|
-
let result = truncated.slice(0, cutPoint);
|
|
113725
|
-
result = closeCodeBlocks(result);
|
|
113726
|
-
return result + TRUNCATION_SUFFIX;
|
|
113727
|
-
}
|
|
113728
|
-
function extractCodeBlocks(text) {
|
|
113729
|
-
const segments = [];
|
|
113730
|
-
const codeBlocks = [];
|
|
113731
|
-
const re = /```[\s\S]*?```/g;
|
|
113732
|
-
let lastIndex = 0;
|
|
113733
|
-
let match;
|
|
113734
|
-
while ((match = re.exec(text)) !== null) {
|
|
113735
|
-
segments.push(text.slice(lastIndex, match.index));
|
|
113736
|
-
codeBlocks.push(match[0]);
|
|
113737
|
-
lastIndex = match.index + match[0].length;
|
|
113738
|
-
}
|
|
113739
|
-
segments.push(text.slice(lastIndex));
|
|
113740
|
-
return { segments, codeBlocks };
|
|
113741
|
-
}
|
|
113742
|
-
function closeCodeBlocks(text) {
|
|
113743
|
-
const matches = text.match(/```/g);
|
|
113744
|
-
if (matches && matches.length % 2 !== 0) {
|
|
113745
|
-
return text + "\n```";
|
|
113746
|
-
}
|
|
113747
|
-
return text;
|
|
113748
|
-
}
|
|
113749
|
-
|
|
113750
113930
|
// src/feishu/streaming-card.ts
|
|
113751
113931
|
var StreamingCard = class {
|
|
113752
113932
|
constructor(cardkit, feishuClient, chatId, log) {
|
|
@@ -113762,6 +113942,7 @@ var StreamingCard = class {
|
|
|
113762
113942
|
textBuffer = "";
|
|
113763
113943
|
toolStates = /* @__PURE__ */ new Map();
|
|
113764
113944
|
closed = false;
|
|
113945
|
+
toolsElementAdded = false;
|
|
113765
113946
|
/**
|
|
113766
113947
|
* 创建卡片 + 发送 interactive 消息 → messageId
|
|
113767
113948
|
*/
|
|
@@ -113769,15 +113950,14 @@ var StreamingCard = class {
|
|
|
113769
113950
|
const schema = {
|
|
113770
113951
|
data: {
|
|
113771
113952
|
schema: "2.0",
|
|
113772
|
-
config: { streaming_mode: true
|
|
113953
|
+
config: { streaming_mode: true },
|
|
113773
113954
|
header: {
|
|
113774
113955
|
title: { tag: "plain_text", content: "AI \u56DE\u590D" },
|
|
113775
113956
|
template: "blue"
|
|
113776
113957
|
},
|
|
113777
113958
|
body: {
|
|
113778
113959
|
elements: [
|
|
113779
|
-
{ tag: "markdown", element_id: "content", content: "\u6B63\u5728\u601D\u8003..." }
|
|
113780
|
-
{ tag: "markdown", element_id: "tools", content: "" }
|
|
113960
|
+
{ tag: "markdown", element_id: "content", content: "\u6B63\u5728\u601D\u8003..." }
|
|
113781
113961
|
]
|
|
113782
113962
|
}
|
|
113783
113963
|
}
|
|
@@ -113809,9 +113989,9 @@ var StreamingCard = class {
|
|
|
113809
113989
|
/**
|
|
113810
113990
|
* 更新工具状态到 tools 元素
|
|
113811
113991
|
*/
|
|
113812
|
-
async setToolStatus(callID,
|
|
113992
|
+
async setToolStatus(callID, tool2, state) {
|
|
113813
113993
|
if (this.closed || !this.cardId) return;
|
|
113814
|
-
this.toolStates.set(callID, { tool, state });
|
|
113994
|
+
this.toolStates.set(callID, { tool: tool2, state });
|
|
113815
113995
|
this.enqueue(() => this.doUpdateTools());
|
|
113816
113996
|
}
|
|
113817
113997
|
/**
|
|
@@ -113867,12 +114047,22 @@ var StreamingCard = class {
|
|
|
113867
114047
|
const icon = ts.state === "completed" ? "\u2705" : ts.state === "error" ? "\u274C" : "\u{1F504}";
|
|
113868
114048
|
lines.push(`${icon} ${ts.tool}`);
|
|
113869
114049
|
}
|
|
113870
|
-
|
|
113871
|
-
|
|
113872
|
-
|
|
113873
|
-
|
|
113874
|
-
|
|
113875
|
-
|
|
114050
|
+
const content = lines.join("\n");
|
|
114051
|
+
if (!this.toolsElementAdded) {
|
|
114052
|
+
this.toolsElementAdded = true;
|
|
114053
|
+
await this.cardkit.addElement(
|
|
114054
|
+
this.cardId,
|
|
114055
|
+
[{ tag: "markdown", element_id: "tools", content }],
|
|
114056
|
+
++this.seq
|
|
114057
|
+
);
|
|
114058
|
+
} else {
|
|
114059
|
+
await this.cardkit.updateElement(
|
|
114060
|
+
this.cardId,
|
|
114061
|
+
"tools",
|
|
114062
|
+
content,
|
|
114063
|
+
++this.seq
|
|
114064
|
+
);
|
|
114065
|
+
}
|
|
113876
114066
|
}
|
|
113877
114067
|
};
|
|
113878
114068
|
|
|
@@ -113899,7 +114089,8 @@ async function handleChat(ctx, deps, signal) {
|
|
|
113899
114089
|
const query = directory ? { directory } : void 0;
|
|
113900
114090
|
const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
|
|
113901
114091
|
const session = await getOrCreateSession(client, sessionKey, directory);
|
|
113902
|
-
|
|
114092
|
+
registerSessionChat(session.id, chatId, chatType);
|
|
114093
|
+
const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log, config2.maxResourceSize);
|
|
113903
114094
|
if (!parts.length) return void 0;
|
|
113904
114095
|
log("info", "\u6536\u5230\u7528\u6237\u6D88\u606F", {
|
|
113905
114096
|
sessionKey,
|
|
@@ -113984,12 +114175,12 @@ async function handleChat(ctx, deps, signal) {
|
|
|
113984
114175
|
break;
|
|
113985
114176
|
case "permission-requested":
|
|
113986
114177
|
if (deps.interactiveDeps) {
|
|
113987
|
-
handlePermissionRequested(action.request, chatId, deps.interactiveDeps);
|
|
114178
|
+
handlePermissionRequested(action.request, chatId, deps.interactiveDeps, chatType);
|
|
113988
114179
|
}
|
|
113989
114180
|
break;
|
|
113990
114181
|
case "question-requested":
|
|
113991
114182
|
if (deps.interactiveDeps) {
|
|
113992
|
-
handleQuestionRequested(action.request, chatId, deps.interactiveDeps);
|
|
114183
|
+
handleQuestionRequested(action.request, chatId, deps.interactiveDeps, chatType);
|
|
113993
114184
|
}
|
|
113994
114185
|
break;
|
|
113995
114186
|
}
|
|
@@ -114073,7 +114264,7 @@ async function handleChat(ctx, deps, signal) {
|
|
|
114073
114264
|
unregisterPending(activeSessionId);
|
|
114074
114265
|
}
|
|
114075
114266
|
}
|
|
114076
|
-
async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
|
|
114267
|
+
async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log, maxResourceSize) {
|
|
114077
114268
|
if (messageType === "text") {
|
|
114078
114269
|
let promptText = textContent;
|
|
114079
114270
|
if (chatType === "group" && senderId) {
|
|
@@ -114081,7 +114272,7 @@ async function buildPromptParts(feishuClient, messageId, messageType, rawContent
|
|
|
114081
114272
|
}
|
|
114082
114273
|
return [{ type: "text", text: promptText }];
|
|
114083
114274
|
}
|
|
114084
|
-
const parts = await extractParts(feishuClient, messageId, messageType, rawContent, log);
|
|
114275
|
+
const parts = await extractParts(feishuClient, messageId, messageType, rawContent, log, maxResourceSize);
|
|
114085
114276
|
if (chatType === "group" && senderId && parts.length > 0) {
|
|
114086
114277
|
return [{ type: "text", text: `[${senderId}]:` }, ...parts];
|
|
114087
114278
|
}
|
|
@@ -114494,11 +114685,17 @@ function formatHistoryAsContext(messages) {
|
|
|
114494
114685
|
return `${header}
|
|
114495
114686
|
${body}`;
|
|
114496
114687
|
}
|
|
114497
|
-
|
|
114498
|
-
// src/index.ts
|
|
114499
114688
|
var SERVICE_NAME = "opencode-feishu";
|
|
114500
114689
|
var LOG_PREFIX = "[feishu]";
|
|
114501
114690
|
var isDebug = !!process.env.FEISHU_DEBUG;
|
|
114691
|
+
function loadFeishuSkill() {
|
|
114692
|
+
const skillPath = join(fileURLToPath(import.meta.url), "../../skills/feishu-card-interaction.md");
|
|
114693
|
+
if (existsSync(skillPath)) {
|
|
114694
|
+
return readFileSync(skillPath, "utf-8");
|
|
114695
|
+
}
|
|
114696
|
+
return "\u5F53\u524D\u7528\u6237\u901A\u8FC7\u98DE\u4E66\uFF08Feishu/Lark\uFF09\u4E0E\u4F60\u5BF9\u8BDD\u3002\u4F60\u53EF\u4EE5\u4F7F\u7528 feishu_send_card \u5DE5\u5177\u53D1\u9001\u683C\u5F0F\u5316\u5361\u7247\u6D88\u606F\uFF08\u652F\u6301\u6309\u94AE\u4EA4\u4E92\uFF09\u3002";
|
|
114697
|
+
}
|
|
114698
|
+
var feishuSystemPrompt = loadFeishuSkill();
|
|
114502
114699
|
var FeishuPlugin = async (ctx) => {
|
|
114503
114700
|
const { client } = ctx;
|
|
114504
114701
|
let gateway = null;
|
|
@@ -114541,7 +114738,7 @@ ${details}`);
|
|
|
114541
114738
|
});
|
|
114542
114739
|
const cardkit = new CardKitClient(larkClient, log);
|
|
114543
114740
|
const botOpenId = await fetchBotOpenId(larkClient, log);
|
|
114544
|
-
const v2Client = void 0;
|
|
114741
|
+
const v2Client = createOpencodeClient({ directory: resolvedConfig.directory || void 0 });
|
|
114545
114742
|
gateway = startFeishuGateway({
|
|
114546
114743
|
config: resolvedConfig,
|
|
114547
114744
|
larkClient,
|
|
@@ -114595,6 +114792,13 @@ ${details}`);
|
|
|
114595
114792
|
event: async ({ event }) => {
|
|
114596
114793
|
if (!gateway) return;
|
|
114597
114794
|
await handleEvent(event, { log, directory: resolvedConfig.directory });
|
|
114795
|
+
},
|
|
114796
|
+
tool: {
|
|
114797
|
+
feishu_send_card: createSendCardTool({ feishuClient: larkClient, log })
|
|
114798
|
+
},
|
|
114799
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
114800
|
+
if (!input.sessionID || !getChatIdBySession(input.sessionID)) return;
|
|
114801
|
+
output.system.push(feishuSystemPrompt);
|
|
114598
114802
|
}
|
|
114599
114803
|
};
|
|
114600
114804
|
return hooks;
|