opencode-feishu 1.2.0 → 1.3.0
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 +335 -178
- 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;
|
|
@@ -112623,94 +112625,49 @@ var CardKitClient = class {
|
|
|
112623
112625
|
}
|
|
112624
112626
|
};
|
|
112625
112627
|
|
|
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
|
-
}
|
|
112628
|
+
// src/utils/ttl-map.ts
|
|
112629
|
+
var TtlMap = class {
|
|
112630
|
+
constructor(defaultTtlMs) {
|
|
112631
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
112632
|
+
}
|
|
112633
|
+
data = /* @__PURE__ */ new Map();
|
|
112634
|
+
timers = /* @__PURE__ */ new Map();
|
|
112635
|
+
get(key) {
|
|
112636
|
+
return this.data.get(key);
|
|
112637
|
+
}
|
|
112638
|
+
has(key) {
|
|
112639
|
+
return this.data.has(key);
|
|
112640
|
+
}
|
|
112641
|
+
set(key, value, ttlMs) {
|
|
112642
|
+
this.delete(key);
|
|
112643
|
+
this.data.set(key, value);
|
|
112644
|
+
const timer = setTimeout(() => {
|
|
112645
|
+
this.data.delete(key);
|
|
112646
|
+
this.timers.delete(key);
|
|
112647
|
+
}, ttlMs ?? this.defaultTtlMs);
|
|
112648
|
+
timer.unref();
|
|
112649
|
+
this.timers.set(key, timer);
|
|
112650
|
+
}
|
|
112651
|
+
delete(key) {
|
|
112652
|
+
const timer = this.timers.get(key);
|
|
112653
|
+
if (timer) {
|
|
112654
|
+
clearTimeout(timer);
|
|
112655
|
+
this.timers.delete(key);
|
|
112674
112656
|
}
|
|
112675
|
-
|
|
112657
|
+
this.data.delete(key);
|
|
112658
|
+
}
|
|
112659
|
+
};
|
|
112660
|
+
|
|
112661
|
+
// src/feishu/session-chat-map.ts
|
|
112662
|
+
var sessionToChat = new TtlMap(24 * 60 * 60 * 1e3);
|
|
112663
|
+
function registerSessionChat(sessionId, chatId, chatType) {
|
|
112664
|
+
sessionToChat.set(sessionId, { chatId, chatType });
|
|
112676
112665
|
}
|
|
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
|
-
};
|
|
112666
|
+
function getChatIdBySession(sessionId) {
|
|
112667
|
+
return sessionToChat.get(sessionId)?.chatId;
|
|
112668
|
+
}
|
|
112669
|
+
function getChatInfoBySession(sessionId) {
|
|
112670
|
+
return sessionToChat.get(sessionId);
|
|
112714
112671
|
}
|
|
112715
112672
|
|
|
112716
112673
|
// src/feishu/sender.ts
|
|
@@ -112786,38 +112743,150 @@ async function sendCardMessage(client, chatId, cardId) {
|
|
|
112786
112743
|
);
|
|
112787
112744
|
}
|
|
112788
112745
|
|
|
112789
|
-
// src/
|
|
112790
|
-
var
|
|
112791
|
-
|
|
112792
|
-
|
|
112793
|
-
|
|
112794
|
-
|
|
112795
|
-
|
|
112796
|
-
|
|
112797
|
-
|
|
112798
|
-
|
|
112799
|
-
|
|
112800
|
-
|
|
112746
|
+
// src/feishu/markdown.ts
|
|
112747
|
+
var MAX_CARD_BYTES = 28 * 1024;
|
|
112748
|
+
var TRUNCATION_SUFFIX = "\n\n*\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD*";
|
|
112749
|
+
var TRUNCATION_SUFFIX_BYTES = new TextEncoder().encode(TRUNCATION_SUFFIX).length;
|
|
112750
|
+
var CODE_FENCE_BYTES = 4;
|
|
112751
|
+
var HTML_TAG_RE = /<\/?\w+(?:\s[^>]*)?\/?>/g;
|
|
112752
|
+
function cleanMarkdown(text) {
|
|
112753
|
+
let result = text.replace(/<br\s*\/?>/gi, "\n");
|
|
112754
|
+
const { segments, codeBlocks } = extractCodeBlocks(result);
|
|
112755
|
+
result = segments.map((seg) => seg.replace(HTML_TAG_RE, "")).join("\0");
|
|
112756
|
+
let idx = 0;
|
|
112757
|
+
result = result.replace(/\0/g, () => codeBlocks[idx++] ?? "");
|
|
112758
|
+
result = closeCodeBlocks(result);
|
|
112759
|
+
return result;
|
|
112760
|
+
}
|
|
112761
|
+
function truncateMarkdown(text, limit = MAX_CARD_BYTES) {
|
|
112762
|
+
const bytes = new TextEncoder().encode(text);
|
|
112763
|
+
if (bytes.length <= limit) return text;
|
|
112764
|
+
const effectiveLimit = limit - TRUNCATION_SUFFIX_BYTES - CODE_FENCE_BYTES;
|
|
112765
|
+
if (effectiveLimit <= 0) return TRUNCATION_SUFFIX;
|
|
112766
|
+
const truncated = new TextDecoder().decode(bytes.slice(0, effectiveLimit));
|
|
112767
|
+
const lastNewline = truncated.lastIndexOf("\n");
|
|
112768
|
+
const cutPoint = lastNewline > effectiveLimit * 0.8 ? lastNewline : truncated.length;
|
|
112769
|
+
let result = truncated.slice(0, cutPoint);
|
|
112770
|
+
result = closeCodeBlocks(result);
|
|
112771
|
+
return result + TRUNCATION_SUFFIX;
|
|
112772
|
+
}
|
|
112773
|
+
function extractCodeBlocks(text) {
|
|
112774
|
+
const segments = [];
|
|
112775
|
+
const codeBlocks = [];
|
|
112776
|
+
const re = /```[\s\S]*?```/g;
|
|
112777
|
+
let lastIndex = 0;
|
|
112778
|
+
let match;
|
|
112779
|
+
while ((match = re.exec(text)) !== null) {
|
|
112780
|
+
segments.push(text.slice(lastIndex, match.index));
|
|
112781
|
+
codeBlocks.push(match[0]);
|
|
112782
|
+
lastIndex = match.index + match[0].length;
|
|
112801
112783
|
}
|
|
112802
|
-
|
|
112803
|
-
|
|
112804
|
-
|
|
112805
|
-
|
|
112806
|
-
|
|
112807
|
-
|
|
112808
|
-
|
|
112809
|
-
timer.unref();
|
|
112810
|
-
this.timers.set(key, timer);
|
|
112784
|
+
segments.push(text.slice(lastIndex));
|
|
112785
|
+
return { segments, codeBlocks };
|
|
112786
|
+
}
|
|
112787
|
+
function closeCodeBlocks(text) {
|
|
112788
|
+
const matches = text.match(/```/g);
|
|
112789
|
+
if (matches && matches.length % 2 !== 0) {
|
|
112790
|
+
return text + "\n```";
|
|
112811
112791
|
}
|
|
112812
|
-
|
|
112813
|
-
|
|
112814
|
-
|
|
112815
|
-
|
|
112816
|
-
|
|
112792
|
+
return text;
|
|
112793
|
+
}
|
|
112794
|
+
|
|
112795
|
+
// src/tools/send-card.ts
|
|
112796
|
+
var z2 = tool.schema;
|
|
112797
|
+
var TEMPLATE_COLORS = ["blue", "green", "orange", "red", "purple", "grey"];
|
|
112798
|
+
function createSendCardTool(deps) {
|
|
112799
|
+
return tool({
|
|
112800
|
+
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",
|
|
112801
|
+
args: {
|
|
112802
|
+
title: z2.string().describe("\u5361\u7247\u6807\u9898"),
|
|
112803
|
+
template: z2.enum(TEMPLATE_COLORS).default("blue").describe("\u6807\u9898\u989C\u8272\u4E3B\u9898"),
|
|
112804
|
+
sections: z2.array(
|
|
112805
|
+
z2.object({
|
|
112806
|
+
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"),
|
|
112807
|
+
content: z2.string().optional().describe("\u533A\u5757\u5185\u5BB9\uFF08markdown \u683C\u5F0F\uFF0Cdivider/actions \u7C7B\u578B\u65E0\u9700\u6B64\u5B57\u6BB5\uFF09"),
|
|
112808
|
+
buttons: z2.array(
|
|
112809
|
+
z2.object({
|
|
112810
|
+
text: z2.string().describe("\u6309\u94AE\u663E\u793A\u6587\u672C\uFF082-6\u5B57\uFF09"),
|
|
112811
|
+
value: z2.string().describe("\u70B9\u51FB\u540E\u4F5C\u4E3A\u7528\u6237\u6D88\u606F\u53D1\u9001\u7684\u5185\u5BB9"),
|
|
112812
|
+
style: z2.enum(["primary", "default", "danger"]).default("default").describe("\u6309\u94AE\u6837\u5F0F")
|
|
112813
|
+
})
|
|
112814
|
+
).optional().describe("\u6309\u94AE\u5217\u8868\uFF08\u4EC5 actions \u7C7B\u578B\u4F7F\u7528\uFF09")
|
|
112815
|
+
})
|
|
112816
|
+
).min(1).describe("\u5361\u7247\u6B63\u6587\u533A\u5757\u5217\u8868")
|
|
112817
|
+
},
|
|
112818
|
+
async execute(args, context) {
|
|
112819
|
+
const chatId = getChatIdBySession(context.sessionID);
|
|
112820
|
+
if (!chatId) {
|
|
112821
|
+
return "\u9519\u8BEF\uFF1A\u5F53\u524D\u4F1A\u8BDD\u4E0D\u5173\u8054\u98DE\u4E66\u804A\u5929\uFF0C\u65E0\u6CD5\u53D1\u9001\u5361\u7247";
|
|
112822
|
+
}
|
|
112823
|
+
const chatInfo = getChatInfoBySession(context.sessionID);
|
|
112824
|
+
const card = buildCardFromDSL(args, chatId, chatInfo?.chatType ?? "p2p");
|
|
112825
|
+
const result = await sendInteractiveCard(deps.feishuClient, chatId, card);
|
|
112826
|
+
if (result.ok) {
|
|
112827
|
+
deps.log("info", "Agent \u5361\u7247\u5DF2\u53D1\u9001", {
|
|
112828
|
+
sessionId: context.sessionID,
|
|
112829
|
+
chatId,
|
|
112830
|
+
title: args.title,
|
|
112831
|
+
messageId: result.messageId
|
|
112832
|
+
});
|
|
112833
|
+
return `\u5361\u7247\u5DF2\u53D1\u9001\uFF1A\u300C${args.title}\u300D`;
|
|
112834
|
+
}
|
|
112835
|
+
deps.log("warn", "Agent \u5361\u7247\u53D1\u9001\u5931\u8D25", {
|
|
112836
|
+
sessionId: context.sessionID,
|
|
112837
|
+
chatId,
|
|
112838
|
+
title: args.title,
|
|
112839
|
+
error: result.error
|
|
112840
|
+
});
|
|
112841
|
+
return `\u5361\u7247\u53D1\u9001\u5931\u8D25\uFF1A${result.error}`;
|
|
112817
112842
|
}
|
|
112818
|
-
|
|
112819
|
-
|
|
112820
|
-
|
|
112843
|
+
});
|
|
112844
|
+
}
|
|
112845
|
+
function buildCardFromDSL(args, chatId, chatType) {
|
|
112846
|
+
return {
|
|
112847
|
+
schema: "2.0",
|
|
112848
|
+
config: { wide_screen_mode: true },
|
|
112849
|
+
header: {
|
|
112850
|
+
title: { tag: "plain_text", content: args.title },
|
|
112851
|
+
template: args.template
|
|
112852
|
+
},
|
|
112853
|
+
body: {
|
|
112854
|
+
elements: args.sections.map((s) => {
|
|
112855
|
+
switch (s.type) {
|
|
112856
|
+
case "divider":
|
|
112857
|
+
return { tag: "hr" };
|
|
112858
|
+
case "note":
|
|
112859
|
+
return {
|
|
112860
|
+
tag: "note",
|
|
112861
|
+
elements: [{ tag: "plain_text", content: s.content ?? "" }]
|
|
112862
|
+
};
|
|
112863
|
+
case "actions":
|
|
112864
|
+
if (!s.buttons?.length) return null;
|
|
112865
|
+
return {
|
|
112866
|
+
tag: "action",
|
|
112867
|
+
actions: s.buttons.map((btn) => ({
|
|
112868
|
+
tag: "button",
|
|
112869
|
+
text: { tag: "plain_text", content: btn.text },
|
|
112870
|
+
type: btn.style,
|
|
112871
|
+
value: JSON.stringify(btn.actionPayload ?? {
|
|
112872
|
+
action: "send_message",
|
|
112873
|
+
chatId,
|
|
112874
|
+
chatType,
|
|
112875
|
+
text: btn.value
|
|
112876
|
+
})
|
|
112877
|
+
}))
|
|
112878
|
+
};
|
|
112879
|
+
case "markdown":
|
|
112880
|
+
default:
|
|
112881
|
+
return {
|
|
112882
|
+
tag: "markdown",
|
|
112883
|
+
content: truncateMarkdown(s.content ?? "", 28e3)
|
|
112884
|
+
};
|
|
112885
|
+
}
|
|
112886
|
+
}).filter(Boolean)
|
|
112887
|
+
}
|
|
112888
|
+
};
|
|
112889
|
+
}
|
|
112821
112890
|
|
|
112822
112891
|
// src/handler/interactive.ts
|
|
112823
112892
|
var seenIds = new TtlMap(10 * 60 * 1e3);
|
|
@@ -112826,14 +112895,14 @@ function markSeen(requestId) {
|
|
|
112826
112895
|
seenIds.set(requestId, true);
|
|
112827
112896
|
return true;
|
|
112828
112897
|
}
|
|
112829
|
-
function handlePermissionRequested(request, chatId, deps) {
|
|
112898
|
+
function handlePermissionRequested(request, chatId, deps, chatType = "p2p") {
|
|
112830
112899
|
if (!deps.v2Client) {
|
|
112831
112900
|
deps.log("warn", "v2Client \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u6743\u9650\u5361\u7247\u53D1\u9001", { requestId: String(request.id ?? "") });
|
|
112832
112901
|
return;
|
|
112833
112902
|
}
|
|
112834
112903
|
const requestId = String(request.id ?? "");
|
|
112835
112904
|
if (!requestId || !markSeen(requestId)) return;
|
|
112836
|
-
const card =
|
|
112905
|
+
const card = buildPermissionCardDSL(request, chatId, chatType);
|
|
112837
112906
|
sendInteractiveCard(deps.feishuClient, chatId, card).catch((err) => {
|
|
112838
112907
|
deps.log("warn", "\u53D1\u9001\u6743\u9650\u5361\u7247\u5931\u8D25", {
|
|
112839
112908
|
requestId,
|
|
@@ -112841,14 +112910,14 @@ function handlePermissionRequested(request, chatId, deps) {
|
|
|
112841
112910
|
});
|
|
112842
112911
|
});
|
|
112843
112912
|
}
|
|
112844
|
-
function handleQuestionRequested(request, chatId, deps) {
|
|
112913
|
+
function handleQuestionRequested(request, chatId, deps, chatType = "p2p") {
|
|
112845
112914
|
if (!deps.v2Client) {
|
|
112846
112915
|
deps.log("warn", "v2Client \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u95EE\u7B54\u5361\u7247\u53D1\u9001", { requestId: String(request.id ?? "") });
|
|
112847
112916
|
return;
|
|
112848
112917
|
}
|
|
112849
112918
|
const requestId = String(request.id ?? "");
|
|
112850
112919
|
if (!requestId || !markSeen(requestId)) return;
|
|
112851
|
-
const card =
|
|
112920
|
+
const card = buildQuestionCardDSL(request, chatId, chatType);
|
|
112852
112921
|
sendInteractiveCard(deps.feishuClient, chatId, card).catch((err) => {
|
|
112853
112922
|
deps.log("warn", "\u53D1\u9001\u95EE\u7B54\u5361\u7247\u5931\u8D25", {
|
|
112854
112923
|
requestId,
|
|
@@ -112858,12 +112927,39 @@ function handleQuestionRequested(request, chatId, deps) {
|
|
|
112858
112927
|
}
|
|
112859
112928
|
async function handleCardAction(action, deps) {
|
|
112860
112929
|
if (!action.actionValue) return;
|
|
112861
|
-
{
|
|
112930
|
+
if (!deps.v2Client) {
|
|
112862
112931
|
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
112932
|
actionValue: action.actionValue
|
|
112864
112933
|
});
|
|
112865
112934
|
return;
|
|
112866
112935
|
}
|
|
112936
|
+
let value;
|
|
112937
|
+
try {
|
|
112938
|
+
value = JSON.parse(action.actionValue);
|
|
112939
|
+
} catch {
|
|
112940
|
+
return;
|
|
112941
|
+
}
|
|
112942
|
+
const requestId = value.requestId;
|
|
112943
|
+
if (!requestId) return;
|
|
112944
|
+
try {
|
|
112945
|
+
if (value.action === "permission_reply" && "reply" in value) {
|
|
112946
|
+
await deps.v2Client.permission.reply({
|
|
112947
|
+
requestID: requestId,
|
|
112948
|
+
reply: value.reply
|
|
112949
|
+
});
|
|
112950
|
+
} else if (value.action === "question_reply" && "answers" in value) {
|
|
112951
|
+
await deps.v2Client.question.reply({
|
|
112952
|
+
requestID: requestId,
|
|
112953
|
+
answers: value.answers
|
|
112954
|
+
});
|
|
112955
|
+
}
|
|
112956
|
+
} catch (err) {
|
|
112957
|
+
deps.log("error", "\u4EA4\u4E92\u56DE\u8C03\u5904\u7406\u5931\u8D25", {
|
|
112958
|
+
action: value.action,
|
|
112959
|
+
requestId,
|
|
112960
|
+
error: err instanceof Error ? err.message : String(err)
|
|
112961
|
+
});
|
|
112962
|
+
}
|
|
112867
112963
|
}
|
|
112868
112964
|
function buildCallbackResponse(action) {
|
|
112869
112965
|
if (!action.actionValue) return {};
|
|
@@ -112887,8 +112983,71 @@ function buildCallbackResponse(action) {
|
|
|
112887
112983
|
toast: { type: "success", content: "\u2705 \u5DF2\u56DE\u7B54" }
|
|
112888
112984
|
};
|
|
112889
112985
|
}
|
|
112986
|
+
if (value.action === "send_message") {
|
|
112987
|
+
return {
|
|
112988
|
+
toast: { type: "info", content: "\u{1F4E8} \u5DF2\u53D1\u9001" }
|
|
112989
|
+
};
|
|
112990
|
+
}
|
|
112890
112991
|
return {};
|
|
112891
112992
|
}
|
|
112993
|
+
function buildPermissionCardDSL(request, chatId, chatType) {
|
|
112994
|
+
const permission = String(request.permission ?? "unknown");
|
|
112995
|
+
const patterns = Array.isArray(request.patterns) ? request.patterns.map(String) : [];
|
|
112996
|
+
const requestId = String(request.id ?? "");
|
|
112997
|
+
const patternsText = patterns.length > 0 ? patterns.map((p) => `- \`${p}\``).join("\n") : "\uFF08\u65E0\u5177\u4F53\u8DEF\u5F84\uFF09";
|
|
112998
|
+
const buttons = [
|
|
112999
|
+
{
|
|
113000
|
+
text: "\u2705 \u5141\u8BB8\u4E00\u6B21",
|
|
113001
|
+
value: "",
|
|
113002
|
+
style: "primary",
|
|
113003
|
+
actionPayload: { action: "permission_reply", requestId, reply: "once" }
|
|
113004
|
+
},
|
|
113005
|
+
{
|
|
113006
|
+
text: "\u{1F513} \u59CB\u7EC8\u5141\u8BB8",
|
|
113007
|
+
value: "",
|
|
113008
|
+
style: "default",
|
|
113009
|
+
actionPayload: { action: "permission_reply", requestId, reply: "always" }
|
|
113010
|
+
},
|
|
113011
|
+
{
|
|
113012
|
+
text: "\u274C \u62D2\u7EDD",
|
|
113013
|
+
value: "",
|
|
113014
|
+
style: "danger",
|
|
113015
|
+
actionPayload: { action: "permission_reply", requestId, reply: "reject" }
|
|
113016
|
+
}
|
|
113017
|
+
];
|
|
113018
|
+
const sections = [
|
|
113019
|
+
{ type: "markdown", content: `AI \u8BF7\u6C42\u4EE5\u4E0B\u6743\u9650:
|
|
113020
|
+
|
|
113021
|
+
${patternsText}` },
|
|
113022
|
+
{ type: "actions", buttons }
|
|
113023
|
+
];
|
|
113024
|
+
const dsl = { title: `\u{1F510} \u6743\u9650\u8BF7\u6C42: ${permission}`, template: "orange", sections };
|
|
113025
|
+
return { type: "card_kit", data: buildCardFromDSL(dsl, chatId, chatType) };
|
|
113026
|
+
}
|
|
113027
|
+
function buildQuestionCardDSL(request, chatId, chatType) {
|
|
113028
|
+
const questions = request.questions ?? [];
|
|
113029
|
+
const requestId = String(request.id ?? "");
|
|
113030
|
+
const q = questions[0];
|
|
113031
|
+
const header = String(q?.header ?? "AI \u63D0\u95EE");
|
|
113032
|
+
const questionText = String(q?.question ?? "\u8BF7\u9009\u62E9");
|
|
113033
|
+
const options = Array.isArray(q?.options) ? q.options : [];
|
|
113034
|
+
const buttons = options.map((opt, idx) => ({
|
|
113035
|
+
text: String(opt.label ?? opt.value ?? `\u9009\u9879 ${idx + 1}`),
|
|
113036
|
+
value: "",
|
|
113037
|
+
style: idx === 0 ? "primary" : "default",
|
|
113038
|
+
actionPayload: {
|
|
113039
|
+
action: "question_reply",
|
|
113040
|
+
requestId,
|
|
113041
|
+
answers: [[String(opt.value ?? opt.label ?? "")]]
|
|
113042
|
+
}
|
|
113043
|
+
}));
|
|
113044
|
+
const sections = [
|
|
113045
|
+
{ type: "markdown", content: questionText },
|
|
113046
|
+
...buttons.length > 0 ? [{ type: "actions", buttons }] : []
|
|
113047
|
+
];
|
|
113048
|
+
const dsl = { title: header, template: "blue", sections };
|
|
113049
|
+
return { type: "card_kit", data: buildCardFromDSL(dsl, chatId, chatType) };
|
|
113050
|
+
}
|
|
112892
113051
|
|
|
112893
113052
|
// src/feishu/dedup.ts
|
|
112894
113053
|
var dedup = new TtlMap(10 * 60 * 1e3);
|
|
@@ -113255,6 +113414,25 @@ function startFeishuGateway(options) {
|
|
|
113255
113414
|
chatId: String(evt.context?.open_chat_id ?? evt.open_chat_id ?? ""),
|
|
113256
113415
|
operatorId: String(evt.operator?.open_id ?? "")
|
|
113257
113416
|
};
|
|
113417
|
+
const sendMsg = parseSendMessageAction(action);
|
|
113418
|
+
if (sendMsg) {
|
|
113419
|
+
const syntheticCtx = {
|
|
113420
|
+
chatId: sendMsg.chatId,
|
|
113421
|
+
messageId: `btn-${randomUUID()}`,
|
|
113422
|
+
messageType: "text",
|
|
113423
|
+
content: sendMsg.text,
|
|
113424
|
+
rawContent: JSON.stringify({ text: sendMsg.text }),
|
|
113425
|
+
chatType: sendMsg.chatType,
|
|
113426
|
+
senderId: action.operatorId ?? "",
|
|
113427
|
+
shouldReply: true
|
|
113428
|
+
};
|
|
113429
|
+
void Promise.resolve(onMessage(syntheticCtx)).catch((err) => {
|
|
113430
|
+
log("error", "send_message \u6309\u94AE\u5904\u7406\u5931\u8D25", {
|
|
113431
|
+
error: err instanceof Error ? err.message : String(err)
|
|
113432
|
+
});
|
|
113433
|
+
});
|
|
113434
|
+
return buildCallbackResponse(action);
|
|
113435
|
+
}
|
|
113258
113436
|
if (onCardAction) {
|
|
113259
113437
|
void onCardAction(action).catch((err) => {
|
|
113260
113438
|
log("error", "card action \u5904\u7406\u5931\u8D25", {
|
|
@@ -113302,6 +113480,20 @@ function startFeishuGateway(options) {
|
|
|
113302
113480
|
};
|
|
113303
113481
|
return { client: larkClient, stop };
|
|
113304
113482
|
}
|
|
113483
|
+
function parseSendMessageAction(action) {
|
|
113484
|
+
if (!action.actionValue) return void 0;
|
|
113485
|
+
try {
|
|
113486
|
+
const value = JSON.parse(action.actionValue);
|
|
113487
|
+
if (value.action !== "send_message") return void 0;
|
|
113488
|
+
const text = typeof value.text === "string" ? value.text : "";
|
|
113489
|
+
const chatId = typeof value.chatId === "string" ? value.chatId : "";
|
|
113490
|
+
if (!text || !chatId) return void 0;
|
|
113491
|
+
const chatType = value.chatType === "group" ? "group" : "p2p";
|
|
113492
|
+
return { chatId, chatType, text };
|
|
113493
|
+
} catch {
|
|
113494
|
+
return void 0;
|
|
113495
|
+
}
|
|
113496
|
+
}
|
|
113305
113497
|
|
|
113306
113498
|
// src/handler/action-bus.ts
|
|
113307
113499
|
var subscribers = /* @__PURE__ */ new Map();
|
|
@@ -113698,55 +113890,6 @@ async function getOrCreateSession(client, sessionKey, directory) {
|
|
|
113698
113890
|
return session;
|
|
113699
113891
|
}
|
|
113700
113892
|
|
|
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
113893
|
// src/feishu/streaming-card.ts
|
|
113751
113894
|
var StreamingCard = class {
|
|
113752
113895
|
constructor(cardkit, feishuClient, chatId, log) {
|
|
@@ -113809,9 +113952,9 @@ var StreamingCard = class {
|
|
|
113809
113952
|
/**
|
|
113810
113953
|
* 更新工具状态到 tools 元素
|
|
113811
113954
|
*/
|
|
113812
|
-
async setToolStatus(callID,
|
|
113955
|
+
async setToolStatus(callID, tool2, state) {
|
|
113813
113956
|
if (this.closed || !this.cardId) return;
|
|
113814
|
-
this.toolStates.set(callID, { tool, state });
|
|
113957
|
+
this.toolStates.set(callID, { tool: tool2, state });
|
|
113815
113958
|
this.enqueue(() => this.doUpdateTools());
|
|
113816
113959
|
}
|
|
113817
113960
|
/**
|
|
@@ -113899,6 +114042,7 @@ async function handleChat(ctx, deps, signal) {
|
|
|
113899
114042
|
const query = directory ? { directory } : void 0;
|
|
113900
114043
|
const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
|
|
113901
114044
|
const session = await getOrCreateSession(client, sessionKey, directory);
|
|
114045
|
+
registerSessionChat(session.id, chatId, chatType);
|
|
113902
114046
|
const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
|
|
113903
114047
|
if (!parts.length) return void 0;
|
|
113904
114048
|
log("info", "\u6536\u5230\u7528\u6237\u6D88\u606F", {
|
|
@@ -113984,12 +114128,12 @@ async function handleChat(ctx, deps, signal) {
|
|
|
113984
114128
|
break;
|
|
113985
114129
|
case "permission-requested":
|
|
113986
114130
|
if (deps.interactiveDeps) {
|
|
113987
|
-
handlePermissionRequested(action.request, chatId, deps.interactiveDeps);
|
|
114131
|
+
handlePermissionRequested(action.request, chatId, deps.interactiveDeps, chatType);
|
|
113988
114132
|
}
|
|
113989
114133
|
break;
|
|
113990
114134
|
case "question-requested":
|
|
113991
114135
|
if (deps.interactiveDeps) {
|
|
113992
|
-
handleQuestionRequested(action.request, chatId, deps.interactiveDeps);
|
|
114136
|
+
handleQuestionRequested(action.request, chatId, deps.interactiveDeps, chatType);
|
|
113993
114137
|
}
|
|
113994
114138
|
break;
|
|
113995
114139
|
}
|
|
@@ -114494,11 +114638,17 @@ function formatHistoryAsContext(messages) {
|
|
|
114494
114638
|
return `${header}
|
|
114495
114639
|
${body}`;
|
|
114496
114640
|
}
|
|
114497
|
-
|
|
114498
|
-
// src/index.ts
|
|
114499
114641
|
var SERVICE_NAME = "opencode-feishu";
|
|
114500
114642
|
var LOG_PREFIX = "[feishu]";
|
|
114501
114643
|
var isDebug = !!process.env.FEISHU_DEBUG;
|
|
114644
|
+
function loadFeishuSkill() {
|
|
114645
|
+
const skillPath = join(fileURLToPath(import.meta.url), "../../skills/feishu-card-interaction.md");
|
|
114646
|
+
if (existsSync(skillPath)) {
|
|
114647
|
+
return readFileSync(skillPath, "utf-8");
|
|
114648
|
+
}
|
|
114649
|
+
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";
|
|
114650
|
+
}
|
|
114651
|
+
var feishuSystemPrompt = loadFeishuSkill();
|
|
114502
114652
|
var FeishuPlugin = async (ctx) => {
|
|
114503
114653
|
const { client } = ctx;
|
|
114504
114654
|
let gateway = null;
|
|
@@ -114541,7 +114691,7 @@ ${details}`);
|
|
|
114541
114691
|
});
|
|
114542
114692
|
const cardkit = new CardKitClient(larkClient, log);
|
|
114543
114693
|
const botOpenId = await fetchBotOpenId(larkClient, log);
|
|
114544
|
-
const v2Client = void 0;
|
|
114694
|
+
const v2Client = createOpencodeClient({ directory: resolvedConfig.directory || void 0 });
|
|
114545
114695
|
gateway = startFeishuGateway({
|
|
114546
114696
|
config: resolvedConfig,
|
|
114547
114697
|
larkClient,
|
|
@@ -114595,6 +114745,13 @@ ${details}`);
|
|
|
114595
114745
|
event: async ({ event }) => {
|
|
114596
114746
|
if (!gateway) return;
|
|
114597
114747
|
await handleEvent(event, { log, directory: resolvedConfig.directory });
|
|
114748
|
+
},
|
|
114749
|
+
tool: {
|
|
114750
|
+
feishu_send_card: createSendCardTool({ feishuClient: larkClient, log })
|
|
114751
|
+
},
|
|
114752
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
114753
|
+
if (!input.sessionID || !getChatIdBySession(input.sessionID)) return;
|
|
114754
|
+
output.system.push(feishuSystemPrompt);
|
|
114598
114755
|
}
|
|
114599
114756
|
};
|
|
114600
114757
|
return hooks;
|