@modelzen/feishu-codex-bridge 0.1.8 → 0.2.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/README.md +5 -3
- package/dist/cli.js +345 -118
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,6 +121,8 @@ feishu-codex-bridge bot rm <名> # 移除一个机器人配置
|
|
|
121
121
|
|
|
122
122
|
> 「**文档评论回复**」功能另需 `docs:document.comment:read`、`docs:document.comment:create`、`wiki:wiki:readonly` 三项(见 `COMMENT_SCOPES`)。它们**已预勾选进同一个开通链接**,但**不属于** `REQUIRED_SCOPES` —— 不开通也不会卡住后台服务安装,只是该功能静默关闭。
|
|
123
123
|
|
|
124
|
+
> 「**把我加进已有群**」功能另需 `im:chat:readonly`(读群名)、`im:chat.members:write_only`(解绑时机器人退群)两项(见 `JOIN_GROUP_SCOPES`)。同样**已预勾选进同一个开通链接**、**不属于** `REQUIRED_SCOPES`,不开通只是该功能静默关闭。
|
|
125
|
+
|
|
124
126
|
### 2)订阅事件 + 回调(长连接模式)
|
|
125
127
|
|
|
126
128
|
`run` / `start` 初始化到这步会**自动打开**「**事件与回调**」页(`https://open.feishu.cn/app/<app_id>/event`)。这页顶部有「**事件配置**」「**回调配置**」两个独立标签,要分别配(飞书对事件/回调**既无开通 API、也无预选深链、连查询订阅状态的接口都没有**,只能手点):
|
|
@@ -130,6 +132,8 @@ feishu-codex-bridge bot rm <名> # 移除一个机器人配置
|
|
|
130
132
|
- `im.message.receive_v1` —— 收群/私聊消息
|
|
131
133
|
- `application.bot.menu_v6` —— 机器人菜单点击
|
|
132
134
|
- `drive.notice.comment_add_v1` —— 云文档新增评论(**仅「文档评论回复」功能需要**;不加则该功能静默关闭,其余照常)
|
|
135
|
+
- `im.chat.member.bot.added_v1` —— 机器人被加入群(**仅「把我加进已有群」功能需要**;触发私聊推送绑定卡,不加则拉我进群没反应)
|
|
136
|
+
- `im.chat.member.bot.deleted_v1` —— 机器人被移出群(同上;触发自动解绑项目,不加则被踢后项目不会自动清理)
|
|
133
137
|
|
|
134
138
|
**「回调配置」标签** → 「订阅方式」改**长连接** → 点「添加回调」:
|
|
135
139
|
|
|
@@ -267,9 +271,7 @@ src/
|
|
|
267
271
|
- 🐛 **反馈 / 贡献**:<https://github.com/modelzen/feishu-codex-bridge/issues>
|
|
268
272
|
- 👥 **交流群**:扫码加入「Vonvon 灵感研究所」👇
|
|
269
273
|
|
|
270
|
-
<p align="center"><img src="docs/assets/vonvon-group-qr.png" alt="Vonvon 灵感研究所 群二维码" width="
|
|
271
|
-
|
|
272
|
-
> 该群二维码永久有效,扫码即可加入。
|
|
274
|
+
<p align="center"><img src="docs/assets/vonvon-group-qr.png" alt="Vonvon 灵感研究所 群二维码" width="300"></p>
|
|
273
275
|
|
|
274
276
|
---
|
|
275
277
|
|
package/dist/cli.js
CHANGED
|
@@ -679,7 +679,11 @@ var COMMENT_SCOPES = [
|
|
|
679
679
|
"docs:document.comment:create",
|
|
680
680
|
"wiki:wiki:readonly"
|
|
681
681
|
];
|
|
682
|
-
var
|
|
682
|
+
var JOIN_GROUP_SCOPES = [
|
|
683
|
+
"im:chat:readonly",
|
|
684
|
+
"im:chat.members:write_only"
|
|
685
|
+
];
|
|
686
|
+
var GRANT_SCOPES = [...REQUIRED_SCOPES, ...COMMENT_SCOPES, ...JOIN_GROUP_SCOPES];
|
|
683
687
|
var SCOPE_LABELS = {
|
|
684
688
|
"im:message.group_at_msg:readonly": "\u63A5\u6536\u7FA4\u91CC @\u673A\u5668\u4EBA \u7684\u6D88\u606F",
|
|
685
689
|
"im:message.group_msg": "\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\uFF08\u514D@\uFF09",
|
|
@@ -695,6 +699,8 @@ var SCOPE_LABELS = {
|
|
|
695
699
|
"im:chat.announcement:write_only": "\u7F16\u8F91\u7FA4\u516C\u544A",
|
|
696
700
|
"im:chat.top_notice:write_only": "\u7F6E\u9876\u7FA4\u516C\u544A\u6A2A\u5E45",
|
|
697
701
|
"im:chat.tabs:write_only": "\u6DFB\u52A0\u7FA4\u6807\u7B7E\u9875",
|
|
702
|
+
"im:chat:readonly": "\u8BFB\u53D6\u7FA4\u4FE1\u606F\uFF08\u7FA4\u540D/\u7FA4\u4E3B\uFF0C\u52A0\u5165\u5B58\u91CF\u7FA4\u7528\uFF09",
|
|
703
|
+
"im:chat.members:write_only": "\u7FA4\u6210\u5458\u589E\u51CF\uFF08\u7ED1\u5B9A\u7684\u5B58\u91CF\u7FA4\u89E3\u7ED1\u65F6\u673A\u5668\u4EBA\u9000\u7FA4\uFF09",
|
|
698
704
|
"cardkit:card:write": "\u4EA4\u4E92\u6309\u94AE\u5361\u7247",
|
|
699
705
|
"docs:document.comment:read": "\u8BFB\u53D6\u6587\u6863\u8BC4\u8BBA",
|
|
700
706
|
"docs:document.comment:create": "\u53D1\u8868\u6587\u6863\u8BC4\u8BBA\u56DE\u590D",
|
|
@@ -746,8 +752,15 @@ async function validateAppCredentials(appId, appSecret, tenant) {
|
|
|
746
752
|
}
|
|
747
753
|
const token = data.tenant_access_token;
|
|
748
754
|
const info = await fetchBotInfo(base, token).catch(() => void 0);
|
|
749
|
-
const
|
|
750
|
-
|
|
755
|
+
const granted = await fetchGrantedScopes(base, token).catch(() => void 0);
|
|
756
|
+
const missing = (list) => granted ? list.filter((s) => !granted.has(s)) : void 0;
|
|
757
|
+
return {
|
|
758
|
+
ok: true,
|
|
759
|
+
botName: info?.bot?.app_name,
|
|
760
|
+
botOpenId: info?.bot?.open_id,
|
|
761
|
+
missingScopes: missing(REQUIRED_SCOPES),
|
|
762
|
+
missingJoinScopes: missing(JOIN_GROUP_SCOPES)
|
|
763
|
+
};
|
|
751
764
|
}
|
|
752
765
|
async function fetchBotInfo(base, token) {
|
|
753
766
|
const resp = await fetch(`${base}/open-apis/bot/v3/info`, {
|
|
@@ -756,15 +769,14 @@ async function fetchBotInfo(base, token) {
|
|
|
756
769
|
if (!resp.ok) return void 0;
|
|
757
770
|
return await resp.json();
|
|
758
771
|
}
|
|
759
|
-
async function
|
|
772
|
+
async function fetchGrantedScopes(base, token) {
|
|
760
773
|
const resp = await fetch(`${base}/open-apis/application/v6/scopes`, {
|
|
761
774
|
headers: { Authorization: `Bearer ${token}` }
|
|
762
775
|
});
|
|
763
776
|
if (!resp.ok) return void 0;
|
|
764
777
|
const body = await resp.json();
|
|
765
778
|
if (!body.data?.scopes) return void 0;
|
|
766
|
-
|
|
767
|
-
return REQUIRED_SCOPES.filter((s) => !granted.has(s));
|
|
779
|
+
return new Set(body.data.scopes.filter((s) => s.grant_status === 1).map((s) => s.scope_name));
|
|
768
780
|
}
|
|
769
781
|
|
|
770
782
|
// src/utils/open-url.ts
|
|
@@ -1071,6 +1083,10 @@ async function confirmReadyForDaemon(result) {
|
|
|
1071
1083
|
console.log(" \u2022 \uFF08\u53EF\u9009\uFF09\u60F3\u8981\u300C\u5728\u98DE\u4E66\u6587\u6863\u8BC4\u8BBA\u91CC @\u673A\u5668\u4EBA\u5C31\u81EA\u52A8\u56DE\u590D\u300D\uFF0C\u518D\u52A0\u8FD9\u4E00\u4E2A\u4E8B\u4EF6\uFF1A");
|
|
1072
1084
|
console.log(" drive.notice.comment_add_v1\uFF08\u4E91\u6587\u6863\u65B0\u589E\u8BC4\u8BBA\uFF09");
|
|
1073
1085
|
console.log(" \u5B83\u4F9D\u8D56\u300C\u6587\u6863\u8BC4\u8BBA\u300D\u6743\u9650\uFF08docs:document.comment:read / :create\uFF0C\u6388\u6743\u94FE\u63A5\u5DF2\u9884\u52FE\u9009\uFF09\uFF1B\u4E0D\u52A0\u5219\u8BE5\u529F\u80FD\u9759\u9ED8\u5173\u95ED\u3002");
|
|
1086
|
+
console.log(" \u2022 \uFF08\u53EF\u9009\uFF09\u60F3\u8981\u300C\u628A\u6211\u52A0\u8FDB\u5DF2\u6709\u7FA4\u5C31\u80FD\u7ED1\u5B9A\u6210\u9879\u76EE\u300D\uFF0C\u518D\u52A0\u8FD9\u4E24\u4E2A\u4E8B\u4EF6\uFF1A");
|
|
1087
|
+
console.log(" im.chat.member.bot.added_v1\uFF08\u673A\u5668\u4EBA\u88AB\u52A0\u5165\u7FA4 \u2192 \u79C1\u804A\u63A8\u9001\u7ED1\u5B9A\u5361\uFF09");
|
|
1088
|
+
console.log(" im.chat.member.bot.deleted_v1\uFF08\u673A\u5668\u4EBA\u88AB\u79FB\u51FA\u7FA4 \u2192 \u81EA\u52A8\u89E3\u7ED1\u9879\u76EE\uFF09");
|
|
1089
|
+
console.log(" \u5B83\u4EEC\u4F9D\u8D56\u300C\u7FA4\u4FE1\u606F/\u7FA4\u6210\u5458\u300D\u6743\u9650\uFF08im:chat:readonly / im:chat.members:write_only\uFF0C\u5DF2\u9884\u52FE\u9009\uFF09\uFF1B\u4E0D\u52A0\u5219\u8BE5\u529F\u80FD\u9759\u9ED8\u5173\u95ED\u3002");
|
|
1074
1090
|
console.log(" \u2022 \u5207\u5230\u300C\u56DE\u8C03\u914D\u7F6E\u300D\u6807\u7B7E \u2192 \u300C\u8BA2\u9605\u65B9\u5F0F\u300D\u6539\u300C\u957F\u8FDE\u63A5\u300D\u2192 \u70B9\u300C\u6DFB\u52A0\u56DE\u8C03\u300D\u52FE\u9009\uFF1A");
|
|
1075
1091
|
console.log(" card.action.trigger\uFF08\u5361\u7247\u56DE\u4F20\u4EA4\u4E92\uFF09");
|
|
1076
1092
|
console.log(" \u26A0\uFE0F \u5B83\u662F\u300C\u56DE\u8C03\u300D\u4E0D\u662F\u300C\u4E8B\u4EF6\u300D\u2014\u2014\u5728\u4E0A\u9762\u300C\u6DFB\u52A0\u4E8B\u4EF6\u300D\u91CC\u641C\u4E0D\u5230\uFF0C\u5FC5\u987B\u5207\u5230\u300C\u56DE\u8C03\u914D\u7F6E\u300D\u8FD9\u4E2A\u6807\u7B7E\u3002");
|
|
@@ -1707,7 +1723,7 @@ function stampRenderToken(card2) {
|
|
|
1707
1723
|
};
|
|
1708
1724
|
visit(card2);
|
|
1709
1725
|
}
|
|
1710
|
-
async function sendManagedCard(channel,
|
|
1726
|
+
async function sendManagedCard(channel, to, card2, replyTo, replyInThread = false, receiveIdType = "chat_id") {
|
|
1711
1727
|
stampRenderToken(card2);
|
|
1712
1728
|
const created = await channel.rawClient.cardkit.v1.card.create({
|
|
1713
1729
|
data: { type: "card_json", data: JSON.stringify(card2) }
|
|
@@ -1726,8 +1742,8 @@ async function sendManagedCard(channel, chatId, card2, replyTo, replyInThread =
|
|
|
1726
1742
|
messageId = sent.data?.message_id;
|
|
1727
1743
|
} else {
|
|
1728
1744
|
const sent = await channel.rawClient.im.v1.message.create({
|
|
1729
|
-
params: { receive_id_type:
|
|
1730
|
-
data: { receive_id:
|
|
1745
|
+
params: { receive_id_type: receiveIdType },
|
|
1746
|
+
data: { receive_id: to, msg_type: "interactive", content }
|
|
1731
1747
|
});
|
|
1732
1748
|
messageId = sent.data?.message_id;
|
|
1733
1749
|
}
|
|
@@ -2183,14 +2199,20 @@ function pickerTime(unixSeconds) {
|
|
|
2183
2199
|
const md2 = `${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
|
|
2184
2200
|
return d.getFullYear() === now.getFullYear() ? `${md2} ${hm}` : `${d.getFullYear()}-${md2} ${hm}`;
|
|
2185
2201
|
}
|
|
2186
|
-
function
|
|
2202
|
+
function talkLine(noMention, tail) {
|
|
2203
|
+
return noMention ? `\xB7 \u76F4\u63A5\u53D1\u6D88\u606F\uFF08\u514D@\uFF09\u2192 ${tail}` : `\xB7 **@\u6211 + \u5185\u5BB9** \u2192 ${tail}\uFF08\u672C\u7FA4\u9ED8\u8BA4\u9700 @\uFF1B\`/settings\` \u53EF\u5F00\u542F\u514D@\uFF09`;
|
|
2204
|
+
}
|
|
2205
|
+
function buildHelpCard(scope, noMention = true) {
|
|
2187
2206
|
const elements = [];
|
|
2188
2207
|
if (scope === "single") {
|
|
2189
2208
|
elements.push(
|
|
2190
2209
|
md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"),
|
|
2191
2210
|
hr(),
|
|
2192
2211
|
md(
|
|
2193
|
-
"\
|
|
2212
|
+
`${talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406")}
|
|
2213
|
+
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2214
|
+
\xB7 \`/settings\` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09
|
|
2215
|
+
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2194
2216
|
)
|
|
2195
2217
|
);
|
|
2196
2218
|
} else if (scope === "topic") {
|
|
@@ -2198,7 +2220,9 @@ function buildHelpCard(scope) {
|
|
|
2198
2220
|
md("\u{1F9F5} **\u8BDD\u9898\u5185** \u2014 \u6BCF\u4E2A\u8BDD\u9898\u662F\u4E00\u4E2A\u72EC\u7ACB\u4F1A\u8BDD\u3002"),
|
|
2199
2221
|
hr(),
|
|
2200
2222
|
md(
|
|
2201
|
-
"\
|
|
2223
|
+
`${talkLine(noMention, "\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD")}
|
|
2224
|
+
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2225
|
+
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2202
2226
|
),
|
|
2203
2227
|
note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
|
|
2204
2228
|
);
|
|
@@ -2213,7 +2237,7 @@ function buildHelpCard(scope) {
|
|
|
2213
2237
|
}
|
|
2214
2238
|
return card(elements, { header: { title: "\u{1F916} \u53EF\u7528\u547D\u4EE4", template: "blue" }, summary: "\u53EF\u7528\u547D\u4EE4" });
|
|
2215
2239
|
}
|
|
2216
|
-
function buildWelcomeCard(kind, docUrl) {
|
|
2240
|
+
function buildWelcomeCard(kind, docUrl, noMention = true) {
|
|
2217
2241
|
const elements = [
|
|
2218
2242
|
md("\u{1F44B} **\u6B22\u8FCE\u4F7F\u7528 Codex Bridge** \u2014 \u672C\u7FA4\u5DF2\u7ED1\u5B9A\u4E00\u4E2A\u9879\u76EE\u76EE\u5F55\uFF0C\u5728\u7FA4\u91CC\u5C31\u80FD\u9A71\u52A8\u672C\u673A Codex \u5E72\u6D3B\u3002"),
|
|
2219
2243
|
hr()
|
|
@@ -2222,7 +2246,10 @@ function buildWelcomeCard(kind, docUrl) {
|
|
|
2222
2246
|
elements.push(
|
|
2223
2247
|
md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4**\uFF08\u6574\u7FA4\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\uFF09"),
|
|
2224
2248
|
md(
|
|
2225
|
-
"\
|
|
2249
|
+
`${talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406")}
|
|
2250
|
+
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2251
|
+
\xB7 \`/settings\` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09
|
|
2252
|
+
\xB7 \`/help\` \u2192 \u547D\u4EE4\u901F\u67E5\u5361`
|
|
2226
2253
|
)
|
|
2227
2254
|
);
|
|
2228
2255
|
} else {
|
|
@@ -2895,6 +2922,71 @@ async function uploadBuffer(channel, buffer) {
|
|
|
2895
2922
|
return key;
|
|
2896
2923
|
}
|
|
2897
2924
|
|
|
2925
|
+
// src/project/registry.ts
|
|
2926
|
+
import { mkdir as mkdir4, readFile as readFile6, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
2927
|
+
import { dirname as dirname5 } from "path";
|
|
2928
|
+
function defaultNoMention(p) {
|
|
2929
|
+
return !((p.origin ?? "created") === "joined" && (p.kind ?? "multi") === "single");
|
|
2930
|
+
}
|
|
2931
|
+
var FILE_VERSION2 = 1;
|
|
2932
|
+
async function read() {
|
|
2933
|
+
try {
|
|
2934
|
+
const text = await readFile6(paths.projectsFile, "utf8");
|
|
2935
|
+
const parsed = JSON.parse(text);
|
|
2936
|
+
return Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
2937
|
+
} catch (err) {
|
|
2938
|
+
if (err.code === "ENOENT") return [];
|
|
2939
|
+
throw err;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
async function write(projects) {
|
|
2943
|
+
await mkdir4(dirname5(paths.projectsFile), { recursive: true });
|
|
2944
|
+
const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
|
|
2945
|
+
const body = { version: FILE_VERSION2, projects };
|
|
2946
|
+
await writeFile4(tmp, `${JSON.stringify(body, null, 2)}
|
|
2947
|
+
`, "utf8");
|
|
2948
|
+
await rename4(tmp, paths.projectsFile);
|
|
2949
|
+
}
|
|
2950
|
+
async function listProjects() {
|
|
2951
|
+
return read();
|
|
2952
|
+
}
|
|
2953
|
+
async function getProjectByChatId(chatId) {
|
|
2954
|
+
return (await read()).find((p) => p.chatId === chatId);
|
|
2955
|
+
}
|
|
2956
|
+
async function getProjectByName(name) {
|
|
2957
|
+
return (await read()).find((p) => p.name === name);
|
|
2958
|
+
}
|
|
2959
|
+
async function addProject(p) {
|
|
2960
|
+
const projects = await read();
|
|
2961
|
+
if (projects.some((x) => x.name === p.name)) {
|
|
2962
|
+
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
2963
|
+
}
|
|
2964
|
+
if (p.chatId) {
|
|
2965
|
+
const bound = projects.find((x) => x.chatId === p.chatId);
|
|
2966
|
+
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
2967
|
+
}
|
|
2968
|
+
projects.push(p);
|
|
2969
|
+
await write(projects);
|
|
2970
|
+
}
|
|
2971
|
+
async function updateProject(name, patch) {
|
|
2972
|
+
const projects = await read();
|
|
2973
|
+
const p = projects.find((x) => x.name === name);
|
|
2974
|
+
if (!p) return;
|
|
2975
|
+
const target = p;
|
|
2976
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
2977
|
+
if (v !== void 0) target[k] = v;
|
|
2978
|
+
}
|
|
2979
|
+
await write(projects);
|
|
2980
|
+
}
|
|
2981
|
+
async function removeProject(name) {
|
|
2982
|
+
const projects = await read();
|
|
2983
|
+
const idx = projects.findIndex((p) => p.name === name);
|
|
2984
|
+
if (idx === -1) return void 0;
|
|
2985
|
+
const [removed] = projects.splice(idx, 1);
|
|
2986
|
+
await write(projects);
|
|
2987
|
+
return removed;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2898
2990
|
// src/card/dm-cards.ts
|
|
2899
2991
|
function openChatUrl(chatId) {
|
|
2900
2992
|
return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
|
|
@@ -2904,6 +2996,7 @@ var DM = {
|
|
|
2904
2996
|
menu: "dm.menu",
|
|
2905
2997
|
newProject: "dm.newProject",
|
|
2906
2998
|
newProjectSubmit: "dm.newProject.submit",
|
|
2999
|
+
joinGroupSubmit: "dm.joinGroup.submit",
|
|
2907
3000
|
projects: "dm.projects",
|
|
2908
3001
|
settings: "dm.settings",
|
|
2909
3002
|
doctor: "dm.doctor",
|
|
@@ -3057,6 +3150,27 @@ ${i.missingScopes.map((s) => `\xB7 ${labelScope(s)}`).join("\n")}`),
|
|
|
3057
3150
|
actions([linkButton("\u{1F511} \u4E00\u952E\u53BB\u5F00\u901A\u8FD9\u4E9B\u6743\u9650", i.scopeGrantUrl)])
|
|
3058
3151
|
];
|
|
3059
3152
|
}
|
|
3153
|
+
function joinFeatureDiagnosis(i) {
|
|
3154
|
+
const out = [md("**\u52A0\u5165\u5B58\u91CF\u7FA4\uFF08\u53EF\u9009\uFF09**")];
|
|
3155
|
+
if (i.missingJoinScopes === void 0) {
|
|
3156
|
+
out.push(md("- \u6743\u9650\uFF1A\u26A0\uFE0F \u672A\u80FD\u81EA\u52A8\u68C0\u67E5\uFF08\u51ED\u636E\u5931\u6548\u6216\u7F51\u7EDC\u4E0D\u901A\uFF09"), actions([linkButton("\u{1F511} \u53BB\u5F00\u901A", i.joinScopeGrantUrl)]));
|
|
3157
|
+
} else if (i.missingJoinScopes.length === 0) {
|
|
3158
|
+
out.push(md("- \u6743\u9650\uFF1A\u2705 \u5DF2\u5F00\u901A\uFF08`im:chat:readonly` / `im:chat.members:write_only`\uFF09"));
|
|
3159
|
+
} else {
|
|
3160
|
+
out.push(
|
|
3161
|
+
md(`- \u6743\u9650\uFF1A\u274C \u7F3A ${i.missingJoinScopes.length} \u9879 \u2014\u2014 \u5F00\u901A\u540E\u624D\u80FD\u628A\u6211\u52A0\u8FDB\u5DF2\u6709\u7FA4\uFF08\u7ED1\u5B9A / \u9000\u7FA4\uFF09`),
|
|
3162
|
+
note(`\u5F85\u5F00\u901A\uFF1A
|
|
3163
|
+
${i.missingJoinScopes.map((s) => `\xB7 ${labelScope(s)}`).join("\n")}`),
|
|
3164
|
+
actions([linkButton("\u{1F511} \u4E00\u952E\u5F00\u901A\u8FD9\u4E24\u9879\u6743\u9650", i.joinScopeGrantUrl)])
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
out.push(
|
|
3168
|
+
note(
|
|
3169
|
+
"\u26A0\uFE0F \u8FD8\u9700\u5728\u540E\u53F0\u300C\u4E8B\u4EF6\u4E0E\u56DE\u8C03\u300D\u624B\u52A8\u8BA2\u9605 `im.chat.member.bot.added_v1`\uFF08\u88AB\u62C9\u8FDB\u7FA4\u2192\u63A8\u9001\u7ED1\u5B9A\u5361\uFF09\u548C `im.chat.member.bot.deleted_v1`\uFF08\u88AB\u79FB\u51FA\u7FA4\u2192\u81EA\u52A8\u89E3\u7ED1\uFF09\u2014\u2014 \u98DE\u4E66\u65E0\u67E5\u8BE2\u63A5\u53E3\uFF0C\u8FD9\u91CC\u65E0\u6CD5\u81EA\u52A8\u68C0\u6D4B\u3002"
|
|
3170
|
+
)
|
|
3171
|
+
);
|
|
3172
|
+
return out;
|
|
3173
|
+
}
|
|
3060
3174
|
function codexDiagnosePrompt(i) {
|
|
3061
3175
|
return [
|
|
3062
3176
|
"\u6211\u5728\u7528 feishu-codex-bridge\uFF08\u98DE\u4E66 \u2194 \u672C\u5730 Codex \u6865\u63A5\uFF09\u9047\u5230\u95EE\u9898\uFF0C\u8BF7\u5E2E\u6211\u5B9A\u4F4D\u539F\u56E0\u5E76\u7ED9\u51FA\u4FEE\u590D\u6B65\u9AA4\u3002",
|
|
@@ -3099,6 +3213,8 @@ function buildDoctorCard(i) {
|
|
|
3099
3213
|
...scopeDiagnosis(i),
|
|
3100
3214
|
note(`bridge v${i.bridgeVer}\u3000\xB7\u3000Node ${i.node}\u3000\xB7\u3000${i.platform}`),
|
|
3101
3215
|
hr(),
|
|
3216
|
+
...joinFeatureDiagnosis(i),
|
|
3217
|
+
hr(),
|
|
3102
3218
|
md("**\u65E5\u5FD7\u8DEF\u5F84**"),
|
|
3103
3219
|
note(`\u540E\u53F0\u5B88\u62A4\u8F93\u51FA\uFF1A\`${i.logStdout}\``),
|
|
3104
3220
|
note(`\u540E\u53F0\u5B88\u62A4\u9519\u8BEF\uFF1A\`${i.logStderr}\``),
|
|
@@ -3132,14 +3248,35 @@ function buildNewProjectFormCard(opts = {}) {
|
|
|
3132
3248
|
);
|
|
3133
3249
|
return card(elements, { header: { title: "\u2795 \u65B0\u5EFA\u9879\u76EE", template: "turquoise" } });
|
|
3134
3250
|
}
|
|
3251
|
+
function buildJoinGroupFormCard(opts) {
|
|
3252
|
+
const elements = [];
|
|
3253
|
+
if (opts.error) elements.push(md(`\u274C **\u7ED1\u5B9A\u5931\u8D25**\uFF1A${opts.error}`));
|
|
3254
|
+
elements.push(
|
|
3255
|
+
md("\u6211\u5DF2\u88AB\u52A0\u5165\u8FD9\u4E2A\u7FA4\u3002\u586B\u4E00\u4E0B\u8981\u7ED1\u5B9A\u7684\u9879\u76EE\u4FE1\u606F\u5373\u53EF\u5F00\u59CB\u7528\u3002"),
|
|
3256
|
+
md("\u9879\u76EE\u540D\u9ED8\u8BA4\u7528\u7FA4\u540D\uFF0C\u53EF\u6539\u3002**\u6587\u4EF6\u5939\u8DEF\u5F84\u7559\u7A7A** = \u81EA\u52A8\u65B0\u5EFA\u7A7A\u767D\u9879\u76EE\uFF1B**\u586B\u7EDD\u5BF9\u8DEF\u5F84** = \u7528\u7535\u8111\u4E0A\u5DF2\u6709\u7684\u6587\u4EF6\u5939\u3002"),
|
|
3257
|
+
form("join_group", [
|
|
3258
|
+
input({ name: "name", label: "\u9879\u76EE\u540D", placeholder: "my-app", value: opts.name, required: true }),
|
|
3259
|
+
input({ name: "cwd", label: "\u6587\u4EF6\u5939\u8DEF\u5F84\uFF08\u9009\u586B\uFF0C\u7559\u7A7A\u81EA\u52A8\u65B0\u5EFA\uFF09", placeholder: "/Users/you/code/my-app", value: opts.cwd }),
|
|
3260
|
+
note("\u9009\u7FA4\u7C7B\u578B(\u76F4\u63A5\u70B9\u5BF9\u5E94\u6309\u94AE\u521B\u5EFA)\uFF1A\u{1F465} \u591A\u8BDD\u9898\u7FA4 = @\u6211\u5F00\u8BDD\u9898\u3001\u6BCF\u8BDD\u9898\u72EC\u7ACB\u4F1A\u8BDD\uFF1B\u{1F4AC} \u5355\u4F1A\u8BDD\u7FA4 = \u6574\u7FA4\u4E00\u4E2A\u4F1A\u8BDD\u3001\u8FDE\u7EED\u4E0A\u4E0B\u6587\uFF08\u9ED8\u8BA4\u4E0D\u514D@\uFF09\u3002"),
|
|
3261
|
+
actions([
|
|
3262
|
+
submitButton("\u{1F465} \u7ED1\u5B9A\xB7\u591A\u8BDD\u9898\u7FA4", { a: DM.joinGroupSubmit, kind: "multi", chatId: opts.chatId }, "primary", "submit_multi"),
|
|
3263
|
+
submitButton("\u{1F4AC} \u7ED1\u5B9A\xB7\u5355\u4F1A\u8BDD\u7FA4", { a: DM.joinGroupSubmit, kind: "single", chatId: opts.chatId }, "primary", "submit_single")
|
|
3264
|
+
])
|
|
3265
|
+
])
|
|
3266
|
+
);
|
|
3267
|
+
return card(elements, { header: { title: "\u{1F517} \u7ED1\u5B9A\u5DF2\u6709\u7FA4", template: "turquoise" } });
|
|
3268
|
+
}
|
|
3135
3269
|
function buildNewProjectDoneCard(p) {
|
|
3270
|
+
const joined = (p.origin ?? "created") === "joined";
|
|
3271
|
+
const verb = joined ? "\u5DF2\u7ED1\u5B9A\u7FA4" : "\u5DF2\u521B\u5EFA\u9879\u76EE";
|
|
3272
|
+
const title = joined ? "\u{1F517} \u7ED1\u5B9A\u5DF2\u6709\u7FA4" : "\u2795 \u65B0\u5EFA\u9879\u76EE";
|
|
3136
3273
|
const elements = [
|
|
3137
|
-
md(`\u2705
|
|
3274
|
+
md(`\u2705 ${verb} **${p.name}**${p.blank ? " _(\u7A7A\u767D\u9879\u76EE)_" : ""}`),
|
|
3138
3275
|
note(`\u{1F4C2} \`${p.cwd}\` \xB7 ${kindLabel(p.kind)}`),
|
|
3139
|
-
md(p.chatId ? "\
|
|
3276
|
+
md(p.chatId ? "\u{1F449} \u53BB\u7FA4\u91CC **@\u6211** \u5E72\u6D3B\u3002" : "\u53D1\u6211\u4EFB\u610F\u6D88\u606F\u53EF\u518D\u6B21\u6253\u5F00\u7BA1\u7406\u53F0\u3002")
|
|
3140
3277
|
];
|
|
3141
3278
|
if (p.chatId) elements.push(actions([linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId), "primary")]));
|
|
3142
|
-
return card(elements, { header: { title
|
|
3279
|
+
return card(elements, { header: { title, template: "green" } });
|
|
3143
3280
|
}
|
|
3144
3281
|
function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
|
|
3145
3282
|
if (projects.length === 0) {
|
|
@@ -3154,7 +3291,7 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3154
3291
|
elements.push(note(`\u{1F4C2} \`${p.cwd}\`${p.branch && p.branch !== "\u2014" ? ` \u{1F33F} ${p.branch}` : ""}`));
|
|
3155
3292
|
elements.push(
|
|
3156
3293
|
note(
|
|
3157
|
-
p.chatId ? `\u{1F4AC} \u7FA4\uFF1A**${p.name}** \xB7 ${kindLabel(p.kind)} \xB7 \u514D@\uFF1A${p.noMention ??
|
|
3294
|
+
p.chatId ? `\u{1F4AC} \u7FA4\uFF1A**${p.name}** \xB7 ${kindLabel(p.kind)}${(p.origin ?? "created") === "joined" ? " \xB7 \u{1F517}\u5DF2\u52A0\u5165" : ""} \xB7 \u514D@\uFF1A${p.noMention ?? defaultNoMention(p) ? "\u5F00" : "\u5173"}` : "\u26A0\uFE0F \u672A\u7ED1\u5B9A\u7FA4"
|
|
3158
3295
|
)
|
|
3159
3296
|
);
|
|
3160
3297
|
const sessions = (p.chatId ? sessionsByChat.get(p.chatId) : void 0) ?? [];
|
|
@@ -3177,11 +3314,12 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3177
3314
|
elements.push(actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]));
|
|
3178
3315
|
return card(elements, { header: { title: "\u{1F4C1} \u9879\u76EE\u5217\u8868", template: "wathet" } });
|
|
3179
3316
|
}
|
|
3180
|
-
function buildRmConfirmCard(name) {
|
|
3317
|
+
function buildRmConfirmCard(name, origin) {
|
|
3318
|
+
const note_ = (origin ?? "created") === "joined" ? "\u4EC5\u89E3\u7ED1\uFF08\u79FB\u9664\u6CE8\u518C\uFF09\uFF0C**\u4E0D\u5220\u4EE3\u7801\u76EE\u5F55**\u3002\u786E\u8BA4\u540E**\u6211\u4F1A\u9000\u51FA\u8BE5\u7FA4**\uFF08\u7FA4\u662F\u4F60\u4EEC\u7684\uFF0C\u4E0D\u4F1A\u89E3\u6563\uFF09\u3002" : "\u4EC5\u89E3\u7ED1\uFF08\u79FB\u9664\u6CE8\u518C + \u64A4\u9500\u7F6E\u9876\u6A2A\u5E45\uFF09\uFF0C**\u4E0D\u5220\u4EE3\u7801\u76EE\u5F55**\u3002\u7FA4\u4E3B\u4F1A\u8F6C\u7ED9\u4F60\uFF0C\u518D\u7531\u4F60\u81EA\u884C\u5728\u98DE\u4E66\u89E3\u6563\u7FA4\u3002";
|
|
3181
3319
|
return card(
|
|
3182
3320
|
[
|
|
3183
3321
|
md(`\u786E\u5B9A\u5220\u9664\u9879\u76EE **${name}**\uFF1F`),
|
|
3184
|
-
note(
|
|
3322
|
+
note(note_),
|
|
3185
3323
|
actions([
|
|
3186
3324
|
button("\u2705 \u786E\u8BA4\u5220\u9664", { a: DM.rmDo, n: name }, "danger"),
|
|
3187
3325
|
button("\u53D6\u6D88", { a: DM.rmCancel })
|
|
@@ -3229,7 +3367,7 @@ function buildSettingsCard(cfg) {
|
|
|
3229
3367
|
}
|
|
3230
3368
|
function buildGroupSettingsCard(project) {
|
|
3231
3369
|
const kind = project.kind ?? "multi";
|
|
3232
|
-
const noMention = project.noMention ??
|
|
3370
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3233
3371
|
const scopeNote = kind === "single" ? "\u5F00\u542F\u540E\uFF1A\u672C\u7FA4\u6240\u6709\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u4EA4\u7ED9\u6211\u5904\u7406\u3002" : "\u5F00\u542F\u540E\uFF1A\u8BDD\u9898\u5185\u7684\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u4EA4\u7ED9\u6211\u5904\u7406\uFF1B**\u5F00\u65B0\u8BDD\u9898\u4ECD\u9700 @\u6211**\u3002";
|
|
3234
3372
|
return card(
|
|
3235
3373
|
[
|
|
@@ -3249,16 +3387,16 @@ function buildGroupSettingsCard(project) {
|
|
|
3249
3387
|
// src/service/update.ts
|
|
3250
3388
|
import { execFile, spawn as spawn5 } from "child_process";
|
|
3251
3389
|
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
3252
|
-
import { dirname as
|
|
3390
|
+
import { dirname as dirname7, join as join8, resolve as resolve4 } from "path";
|
|
3253
3391
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3254
3392
|
import { promisify } from "util";
|
|
3255
3393
|
|
|
3256
3394
|
// src/service/launchd.ts
|
|
3257
3395
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
3258
3396
|
import { existsSync as existsSync4 } from "fs";
|
|
3259
|
-
import { appendFile, mkdir as
|
|
3397
|
+
import { appendFile, mkdir as mkdir5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
3260
3398
|
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
3261
|
-
import { dirname as
|
|
3399
|
+
import { dirname as dirname6, join as join7, resolve as resolve3 } from "path";
|
|
3262
3400
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3263
3401
|
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
3264
3402
|
function launchAgentPlistPath() {
|
|
@@ -3271,7 +3409,7 @@ function serviceStderrPath() {
|
|
|
3271
3409
|
return join7(paths.appDir, "service.err.log");
|
|
3272
3410
|
}
|
|
3273
3411
|
function resolveCliBinPath() {
|
|
3274
|
-
const distDir =
|
|
3412
|
+
const distDir = dirname6(fileURLToPath2(import.meta.url));
|
|
3275
3413
|
return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3276
3414
|
}
|
|
3277
3415
|
function escapeXml(value) {
|
|
@@ -3312,9 +3450,9 @@ function buildPlist() {
|
|
|
3312
3450
|
}
|
|
3313
3451
|
async function installLaunchd() {
|
|
3314
3452
|
const plistPath = launchAgentPlistPath();
|
|
3315
|
-
await
|
|
3453
|
+
await mkdir5(dirname6(plistPath), { recursive: true });
|
|
3316
3454
|
await ensureLogFiles();
|
|
3317
|
-
await
|
|
3455
|
+
await writeFile5(plistPath, buildPlist(), "utf8");
|
|
3318
3456
|
if (isLoaded()) {
|
|
3319
3457
|
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
3320
3458
|
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
@@ -3396,7 +3534,7 @@ async function waitUntilUnloaded(timeoutMs = 5e3) {
|
|
|
3396
3534
|
throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
|
|
3397
3535
|
}
|
|
3398
3536
|
async function ensureLogFiles() {
|
|
3399
|
-
await
|
|
3537
|
+
await mkdir5(paths.appDir, { recursive: true });
|
|
3400
3538
|
await appendFile(serviceStdoutPath(), "");
|
|
3401
3539
|
await appendFile(serviceStderrPath(), "");
|
|
3402
3540
|
}
|
|
@@ -3438,7 +3576,7 @@ function getServiceAdapter() {
|
|
|
3438
3576
|
var execFileP = promisify(execFile);
|
|
3439
3577
|
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3440
3578
|
function pkgRoot() {
|
|
3441
|
-
return resolve4(
|
|
3579
|
+
return resolve4(dirname7(fileURLToPath3(import.meta.url)), "..");
|
|
3442
3580
|
}
|
|
3443
3581
|
function pkgJson() {
|
|
3444
3582
|
try {
|
|
@@ -3503,64 +3641,6 @@ async function restartDaemon() {
|
|
|
3503
3641
|
await getServiceAdapter().restart();
|
|
3504
3642
|
}
|
|
3505
3643
|
|
|
3506
|
-
// src/project/registry.ts
|
|
3507
|
-
import { mkdir as mkdir5, readFile as readFile6, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
3508
|
-
import { dirname as dirname7 } from "path";
|
|
3509
|
-
var FILE_VERSION2 = 1;
|
|
3510
|
-
async function read() {
|
|
3511
|
-
try {
|
|
3512
|
-
const text = await readFile6(paths.projectsFile, "utf8");
|
|
3513
|
-
const parsed = JSON.parse(text);
|
|
3514
|
-
return Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
3515
|
-
} catch (err) {
|
|
3516
|
-
if (err.code === "ENOENT") return [];
|
|
3517
|
-
throw err;
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
async function write(projects) {
|
|
3521
|
-
await mkdir5(dirname7(paths.projectsFile), { recursive: true });
|
|
3522
|
-
const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
|
|
3523
|
-
const body = { version: FILE_VERSION2, projects };
|
|
3524
|
-
await writeFile5(tmp, `${JSON.stringify(body, null, 2)}
|
|
3525
|
-
`, "utf8");
|
|
3526
|
-
await rename4(tmp, paths.projectsFile);
|
|
3527
|
-
}
|
|
3528
|
-
async function listProjects() {
|
|
3529
|
-
return read();
|
|
3530
|
-
}
|
|
3531
|
-
async function getProjectByChatId(chatId) {
|
|
3532
|
-
return (await read()).find((p) => p.chatId === chatId);
|
|
3533
|
-
}
|
|
3534
|
-
async function getProjectByName(name) {
|
|
3535
|
-
return (await read()).find((p) => p.name === name);
|
|
3536
|
-
}
|
|
3537
|
-
async function addProject(p) {
|
|
3538
|
-
const projects = await read();
|
|
3539
|
-
if (projects.some((x) => x.name === p.name)) {
|
|
3540
|
-
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
3541
|
-
}
|
|
3542
|
-
projects.push(p);
|
|
3543
|
-
await write(projects);
|
|
3544
|
-
}
|
|
3545
|
-
async function updateProject(name, patch) {
|
|
3546
|
-
const projects = await read();
|
|
3547
|
-
const p = projects.find((x) => x.name === name);
|
|
3548
|
-
if (!p) return;
|
|
3549
|
-
const target = p;
|
|
3550
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
3551
|
-
if (v !== void 0) target[k] = v;
|
|
3552
|
-
}
|
|
3553
|
-
await write(projects);
|
|
3554
|
-
}
|
|
3555
|
-
async function removeProject(name) {
|
|
3556
|
-
const projects = await read();
|
|
3557
|
-
const idx = projects.findIndex((p) => p.name === name);
|
|
3558
|
-
if (idx === -1) return void 0;
|
|
3559
|
-
const [removed] = projects.splice(idx, 1);
|
|
3560
|
-
await write(projects);
|
|
3561
|
-
return removed;
|
|
3562
|
-
}
|
|
3563
|
-
|
|
3564
3644
|
// src/project/lifecycle.ts
|
|
3565
3645
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
3566
3646
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -3665,21 +3745,23 @@ var HELP_DOC_URL = "https://my.feishu.cn/wiki/PZ23wGr7JiKK5RkIG4rcZXzGn5g";
|
|
|
3665
3745
|
async function onboardGroup(channel, project) {
|
|
3666
3746
|
const kind = project.kind ?? "multi";
|
|
3667
3747
|
const chatId = project.chatId;
|
|
3748
|
+
const decorate = (project.origin ?? "created") !== "joined";
|
|
3668
3749
|
try {
|
|
3669
|
-
const
|
|
3750
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3751
|
+
const content = JSON.stringify(buildWelcomeCard(kind, HELP_DOC_URL || void 0, noMention));
|
|
3670
3752
|
const sent = await channel.rawClient.im.v1.message.create({
|
|
3671
3753
|
params: { receive_id_type: "chat_id" },
|
|
3672
3754
|
data: { receive_id: chatId, msg_type: "interactive", content }
|
|
3673
3755
|
});
|
|
3674
3756
|
const messageId = sent.data?.message_id;
|
|
3675
|
-
if (messageId) {
|
|
3757
|
+
if (messageId && decorate) {
|
|
3676
3758
|
await channel.rawClient.im.v1.pin.create({ data: { message_id: messageId } });
|
|
3677
3759
|
log.info("project", "onboard-pin", { name: project.name });
|
|
3678
3760
|
}
|
|
3679
3761
|
} catch (err) {
|
|
3680
3762
|
log.fail("project", err, { phase: "onboard-welcome" });
|
|
3681
3763
|
}
|
|
3682
|
-
if (HELP_DOC_URL) {
|
|
3764
|
+
if (decorate && HELP_DOC_URL) {
|
|
3683
3765
|
try {
|
|
3684
3766
|
await channel.rawClient.im.v1.chatTab.create({
|
|
3685
3767
|
path: { chat_id: chatId },
|
|
@@ -3695,21 +3777,21 @@ async function onboardGroup(channel, project) {
|
|
|
3695
3777
|
}
|
|
3696
3778
|
|
|
3697
3779
|
// src/project/lifecycle.ts
|
|
3780
|
+
async function resolveCwd(name, existingPath) {
|
|
3781
|
+
if (existingPath) {
|
|
3782
|
+
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve5(existingPath);
|
|
3783
|
+
if (!existsSync6(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
3784
|
+
return { cwd: cwd2, blank: false };
|
|
3785
|
+
}
|
|
3786
|
+
const cwd = join9(paths.projectsRootDir, name);
|
|
3787
|
+
await mkdir6(cwd, { recursive: true });
|
|
3788
|
+
return { cwd, blank: true };
|
|
3789
|
+
}
|
|
3698
3790
|
async function createProject(channel, input2) {
|
|
3699
3791
|
const name = input2.name.trim();
|
|
3700
3792
|
if (!name) throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3701
3793
|
if (await getProjectByName(name)) throw new Error(`\u9879\u76EE\u540D\u300C${name}\u300D\u5DF2\u5B58\u5728\uFF0C\u6362\u4E2A\u540D\u6216\u7528 /projects \u770B\u5DF2\u6709\u7684`);
|
|
3702
|
-
|
|
3703
|
-
let blank;
|
|
3704
|
-
if (input2.existingPath) {
|
|
3705
|
-
cwd = isAbsolute2(input2.existingPath) ? input2.existingPath : resolve5(input2.existingPath);
|
|
3706
|
-
if (!existsSync6(cwd)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd}`);
|
|
3707
|
-
blank = false;
|
|
3708
|
-
} else {
|
|
3709
|
-
cwd = join9(paths.projectsRootDir, name);
|
|
3710
|
-
await mkdir6(cwd, { recursive: true });
|
|
3711
|
-
blank = true;
|
|
3712
|
-
}
|
|
3794
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
3713
3795
|
const res = await channel.rawClient.im.v1.chat.create({
|
|
3714
3796
|
params: { user_id_type: "open_id" },
|
|
3715
3797
|
data: { name, user_id_list: [input2.ownerOpenId] }
|
|
@@ -3721,13 +3803,35 @@ async function createProject(channel, input2) {
|
|
|
3721
3803
|
params: { member_id_type: "open_id" },
|
|
3722
3804
|
data: { manager_ids: [input2.ownerOpenId] }
|
|
3723
3805
|
}).catch((err) => log.fail("project", err, { phase: "add-manager" }));
|
|
3724
|
-
const project = { name, chatId, cwd, blank, createdAt: Date.now(), kind: input2.kind ?? "multi" };
|
|
3806
|
+
const project = { name, chatId, cwd, blank, createdAt: Date.now(), kind: input2.kind ?? "multi", origin: "created" };
|
|
3725
3807
|
await addProject(project);
|
|
3726
3808
|
log.info("project", "create", { name, chatId, cwd, blank });
|
|
3727
3809
|
await setAnnouncement(channel, project).catch((err) => log.fail("project", err, { phase: "announcement" }));
|
|
3728
3810
|
await onboardGroup(channel, project).catch((err) => log.fail("project", err, { phase: "onboard" }));
|
|
3729
3811
|
return project;
|
|
3730
3812
|
}
|
|
3813
|
+
async function joinExistingGroup(channel, input2) {
|
|
3814
|
+
const name = input2.name.trim();
|
|
3815
|
+
if (!name) throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3816
|
+
if (await getProjectByName(name)) throw new Error(`\u9879\u76EE\u540D\u300C${name}\u300D\u5DF2\u5B58\u5728\uFF0C\u6362\u4E2A\u540D\u6216\u7528 /projects \u770B\u5DF2\u6709\u7684`);
|
|
3817
|
+
const bound = await getProjectByChatId(input2.chatId);
|
|
3818
|
+
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
3819
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
3820
|
+
const project = {
|
|
3821
|
+
name,
|
|
3822
|
+
chatId: input2.chatId,
|
|
3823
|
+
cwd,
|
|
3824
|
+
blank,
|
|
3825
|
+
createdAt: Date.now(),
|
|
3826
|
+
kind: input2.kind ?? "multi",
|
|
3827
|
+
origin: "joined",
|
|
3828
|
+
addedBy: input2.addedBy
|
|
3829
|
+
};
|
|
3830
|
+
await addProject(project);
|
|
3831
|
+
log.info("project", "join", { name, chatId: input2.chatId, cwd, blank, kind: project.kind });
|
|
3832
|
+
await onboardGroup(channel, project).catch((err) => log.fail("project", err, { phase: "onboard-join" }));
|
|
3833
|
+
return project;
|
|
3834
|
+
}
|
|
3731
3835
|
|
|
3732
3836
|
// src/project/group-ops.ts
|
|
3733
3837
|
async function transferOwnership(channel, chatId, toOpenId) {
|
|
@@ -3738,6 +3842,13 @@ async function transferOwnership(channel, chatId, toOpenId) {
|
|
|
3738
3842
|
});
|
|
3739
3843
|
log.info("project", "owner-transfer", { chatId: chatId.slice(-6), to: toOpenId.slice(-6) });
|
|
3740
3844
|
}
|
|
3845
|
+
async function leaveChat(channel, chatId) {
|
|
3846
|
+
await channel.rawClient.request({
|
|
3847
|
+
method: "PATCH",
|
|
3848
|
+
url: `/open-apis/im/v1/chats/${encodeURIComponent(chatId)}/members/me_leave`
|
|
3849
|
+
});
|
|
3850
|
+
log.info("project", "leave-chat", { chatId: chatId.slice(-6) });
|
|
3851
|
+
}
|
|
3741
3852
|
|
|
3742
3853
|
// src/bot/session-store.ts
|
|
3743
3854
|
import { mkdir as mkdir7, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "fs/promises";
|
|
@@ -4268,11 +4379,22 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4268
4379
|
log.info("intake", "reject", { reason: "not_allowed", chatId: msg.chatId.slice(-6) });
|
|
4269
4380
|
return;
|
|
4270
4381
|
}
|
|
4382
|
+
if (!project) {
|
|
4383
|
+
log.info("intake", "unbound-group", { chatId: msg.chatId.slice(-6), atBot: msg.mentionedBot });
|
|
4384
|
+
if (msg.mentionedBot) {
|
|
4385
|
+
await channel.send(
|
|
4386
|
+
msg.chatId,
|
|
4387
|
+
{ markdown: "\u672C\u7FA4\u8FD8\u6CA1\u7ED1\u5B9A\u4E3A\u9879\u76EE\u3002\u8BF7**\u628A\u6211\u62C9\u8FDB\u7FA4\u7684\u7BA1\u7406\u5458**\u5728\u4E0E\u6211\u7684\u79C1\u804A\u91CC\u5B8C\u6210\u7ED1\u5B9A\u540E\u518D @\u6211\u3002" },
|
|
4388
|
+
{ replyTo: msg.messageId }
|
|
4389
|
+
).catch(() => void 0);
|
|
4390
|
+
}
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4271
4393
|
const text = msg.content.trim();
|
|
4272
4394
|
const cmd = parseCommand(text);
|
|
4273
4395
|
if ((project?.kind ?? "multi") === "single") {
|
|
4274
4396
|
if (cmd === "help") {
|
|
4275
|
-
await postHelpCard(msg, "single");
|
|
4397
|
+
await postHelpCard(msg, "single", false, project);
|
|
4276
4398
|
return;
|
|
4277
4399
|
}
|
|
4278
4400
|
if (cmd === "settings") {
|
|
@@ -4288,7 +4410,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4288
4410
|
}
|
|
4289
4411
|
if (msg.threadId) {
|
|
4290
4412
|
if (cmd === "help") {
|
|
4291
|
-
await postHelpCard(msg, "topic", true);
|
|
4413
|
+
await postHelpCard(msg, "topic", true, project);
|
|
4292
4414
|
return;
|
|
4293
4415
|
}
|
|
4294
4416
|
if (cmd === "model") {
|
|
@@ -4299,7 +4421,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4299
4421
|
return;
|
|
4300
4422
|
}
|
|
4301
4423
|
if (cmd === "help") {
|
|
4302
|
-
await postHelpCard(msg, "main");
|
|
4424
|
+
await postHelpCard(msg, "main", false, project);
|
|
4303
4425
|
return;
|
|
4304
4426
|
}
|
|
4305
4427
|
if (cmd === "resume") {
|
|
@@ -4322,7 +4444,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4322
4444
|
return name === "resume" || name === "model" || name === "settings" || name === "help" ? name : null;
|
|
4323
4445
|
}
|
|
4324
4446
|
function shouldRespondWithoutMention(project, msg) {
|
|
4325
|
-
if (!(project.noMention ??
|
|
4447
|
+
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
4326
4448
|
if (msg.mentionAll || msg.mentions.some((m) => !m.isBot)) return false;
|
|
4327
4449
|
if ((project.kind ?? "multi") === "single") return true;
|
|
4328
4450
|
return Boolean(msg.threadId) || parseCommand(msg.content.trim()) !== null;
|
|
@@ -4519,9 +4641,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4519
4641
|
log.info("card", "model", { threadId: sessionKey, model: state.model, effort: state.effort });
|
|
4520
4642
|
});
|
|
4521
4643
|
}
|
|
4522
|
-
async function postHelpCard(msg, scope, inThread = false) {
|
|
4644
|
+
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
4645
|
+
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
4523
4646
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
4524
|
-
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope), msg.messageId, inThread).catch(
|
|
4647
|
+
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention), msg.messageId, inThread).catch(
|
|
4525
4648
|
(err) => log.fail("card", err, { cmd: "help", scope })
|
|
4526
4649
|
);
|
|
4527
4650
|
log.info("card", "help", { scope });
|
|
@@ -4659,6 +4782,32 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4659
4782
|
(e) => log.fail("console", e, { phase: "new-project-result" })
|
|
4660
4783
|
);
|
|
4661
4784
|
})();
|
|
4785
|
+
}).on(DM.joinGroupSubmit, ({ evt, formValue, value }) => {
|
|
4786
|
+
const op = evt.operator?.openId;
|
|
4787
|
+
if (!dmAdmin(op)) return;
|
|
4788
|
+
const name = String(formValue?.name ?? "").trim();
|
|
4789
|
+
const cwdIn = String(formValue?.cwd ?? "").trim();
|
|
4790
|
+
const chatId = typeof value.chatId === "string" ? value.chatId : "";
|
|
4791
|
+
const kind = value.kind === "single" ? "single" : "multi";
|
|
4792
|
+
void (async () => {
|
|
4793
|
+
let result;
|
|
4794
|
+
if (!chatId)
|
|
4795
|
+
result = buildJoinGroupFormCard({ chatId: "", name, cwd: cwdIn, error: "\u7F3A\u5C11\u7FA4\u6807\u8BC6\uFF0C\u8BF7\u91CD\u65B0\u4ECE\u8FDB\u7FA4\u901A\u77E5\u91CC\u6253\u5F00\u7ED1\u5B9A\u5361" });
|
|
4796
|
+
else if (!name) result = buildJoinGroupFormCard({ chatId, cwd: cwdIn, error: "\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A" });
|
|
4797
|
+
else if (!op) result = buildJoinGroupFormCard({ chatId, name, cwd: cwdIn, error: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u8005\u8EAB\u4EFD" });
|
|
4798
|
+
else {
|
|
4799
|
+
try {
|
|
4800
|
+
const p = await joinExistingGroup(channel, { name, chatId, addedBy: op, existingPath: cwdIn || void 0, kind });
|
|
4801
|
+
log.info("console", "join-group", { name: p.name, blank: p.blank });
|
|
4802
|
+
result = buildNewProjectDoneCard(p);
|
|
4803
|
+
} catch (err) {
|
|
4804
|
+
result = buildJoinGroupFormCard({ chatId, name, cwd: cwdIn, error: err instanceof Error ? err.message : String(err) });
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
await sendManagedCard(channel, evt.chatId, result).catch(
|
|
4808
|
+
(e) => log.fail("console", e, { phase: "join-group-result" })
|
|
4809
|
+
);
|
|
4810
|
+
})();
|
|
4662
4811
|
}).on(DM.projects, ({ evt }) => {
|
|
4663
4812
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4664
4813
|
patch(evt, renderProjectList);
|
|
@@ -4671,6 +4820,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4671
4820
|
const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
|
|
4672
4821
|
const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
|
|
4673
4822
|
const missingScopes = scopeCheck?.missingScopes;
|
|
4823
|
+
const missingJoinScopes = scopeCheck?.missingJoinScopes;
|
|
4674
4824
|
const info = {
|
|
4675
4825
|
codexOk: await backend.isAvailable().catch(() => false),
|
|
4676
4826
|
codexVer: codexBin ? codexVersion(codexBin) : null,
|
|
@@ -4687,7 +4837,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4687
4837
|
app.id,
|
|
4688
4838
|
app.tenant,
|
|
4689
4839
|
missingScopes && missingScopes.length ? missingScopes : void 0
|
|
4690
|
-
)
|
|
4840
|
+
),
|
|
4841
|
+
missingJoinScopes,
|
|
4842
|
+
// 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
|
|
4843
|
+
joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
|
|
4691
4844
|
};
|
|
4692
4845
|
await sendManagedCard(channel, evt.chatId, buildDoctorCard(info), evt.messageId).catch(
|
|
4693
4846
|
(err) => log.fail("console", err, { cmd: "doctor" })
|
|
@@ -4748,7 +4901,8 @@ SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8B
|
|
|
4748
4901
|
}).on(DM.rmConfirm, async ({ evt, value }) => {
|
|
4749
4902
|
const name = typeof value.n === "string" ? value.n : void 0;
|
|
4750
4903
|
if (!dmAdmin(evt.operator?.openId) || !name) return;
|
|
4751
|
-
await
|
|
4904
|
+
const proj = (await listProjects()).find((p) => p.name === name);
|
|
4905
|
+
await patch(evt, buildRmConfirmCard(name, proj?.origin));
|
|
4752
4906
|
}).on(DM.rmCancel, ({ evt }) => {
|
|
4753
4907
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4754
4908
|
patch(evt, renderProjectList);
|
|
@@ -4758,15 +4912,25 @@ SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8B
|
|
|
4758
4912
|
if (!dmAdmin(op) || !name) return;
|
|
4759
4913
|
patch(evt, async () => {
|
|
4760
4914
|
const removed = await removeProject(name);
|
|
4761
|
-
let
|
|
4762
|
-
if (removed
|
|
4763
|
-
|
|
4764
|
-
log.fail("console", err, { phase: "
|
|
4915
|
+
let tail;
|
|
4916
|
+
if (removed && (removed.origin ?? "created") === "joined") {
|
|
4917
|
+
const left = removed.chatId ? await leaveChat(channel, removed.chatId).then(() => true).catch((err) => {
|
|
4918
|
+
log.fail("console", err, { phase: "leave-chat" });
|
|
4765
4919
|
return false;
|
|
4766
|
-
});
|
|
4920
|
+
}) : false;
|
|
4921
|
+
log.info("console", "rm", { name, origin: "joined", left });
|
|
4922
|
+
tail = left ? "\u6211\u5DF2\u9000\u51FA\u8BE5\u7FA4\uFF08\u7FA4\u662F\u4F60\u4EEC\u7684\uFF0C\u4E0D\u4F1A\u89E3\u6563\uFF09\u3002" : "\u26A0\uFE0F \u6211\u9000\u7FA4\u5931\u8D25\uFF08\u53EF\u80FD\u6743\u9650\u4E0D\u8DB3\uFF09\uFF0C\u53EF\u5728\u7FA4\u91CC\u624B\u52A8\u628A\u6211\u79FB\u9664\u3002";
|
|
4923
|
+
} else {
|
|
4924
|
+
let transferred = false;
|
|
4925
|
+
if (removed?.chatId && op) {
|
|
4926
|
+
transferred = await transferOwnership(channel, removed.chatId, op).then(() => true).catch((err) => {
|
|
4927
|
+
log.fail("console", err, { phase: "owner-transfer" });
|
|
4928
|
+
return false;
|
|
4929
|
+
});
|
|
4930
|
+
}
|
|
4931
|
+
log.info("console", "rm", { name, origin: "created", transferred });
|
|
4932
|
+
tail = transferred ? "\u7FA4\u4E3B\u5DF2\u8F6C\u7ED9\u4F60 \u2192 \u8BF7\u5728\u98DE\u4E66\u91CC**\u81EA\u884C\u89E3\u6563\u8BE5\u7FA4**\uFF08\u673A\u5668\u4EBA\u4E0D\u4E3B\u52A8\u89E3\u6563\uFF09\u3002" : "\u26A0\uFE0F \u7FA4\u4E3B\u8F6C\u8BA9\u5931\u8D25\uFF08\u53EF\u80FD bot \u975E\u7FA4\u4E3B\uFF09\uFF0C\u8BF7\u7528\u300C\u{1F6AA} \u7FA4\u7BA1\u7406\u300D\u624B\u52A8\u8F6C\u8BA9\u540E\u89E3\u6563\u3002";
|
|
4767
4933
|
}
|
|
4768
|
-
log.info("console", "rm", { name, transferred });
|
|
4769
|
-
const tail = transferred ? "\u7FA4\u4E3B\u5DF2\u8F6C\u7ED9\u4F60 \u2192 \u8BF7\u5728\u98DE\u4E66\u91CC**\u81EA\u884C\u89E3\u6563\u8BE5\u7FA4**\uFF08\u673A\u5668\u4EBA\u4E0D\u4E3B\u52A8\u89E3\u6563\uFF09\u3002" : "\u26A0\uFE0F \u7FA4\u4E3B\u8F6C\u8BA9\u5931\u8D25\uFF08\u53EF\u80FD bot \u975E\u7FA4\u4E3B\uFF09\uFF0C\u8BF7\u7528\u300C\u{1F6AA} \u7FA4\u7BA1\u7406\u300D\u624B\u52A8\u8F6C\u8BA9\u540E\u89E3\u6563\u3002";
|
|
4770
4934
|
await channel.send(evt.chatId, { markdown: `\u2705 \u5DF2\u5220\u9664\u9879\u76EE\u300C${name}\u300D\uFF08\u89E3\u7ED1\uFF0C\u672A\u5220\u4EE3\u7801\u76EE\u5F55\uFF09\u3002
|
|
4771
4935
|
${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
4772
4936
|
return renderProjectList();
|
|
@@ -5102,13 +5266,57 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5102
5266
|
});
|
|
5103
5267
|
return fresh;
|
|
5104
5268
|
}
|
|
5269
|
+
async function onBotAddedToChat(evt) {
|
|
5270
|
+
await withTrace({ chatId: evt.chatId }, async () => {
|
|
5271
|
+
const op = evt.operator?.openId;
|
|
5272
|
+
if (await getProjectByChatId(evt.chatId)) {
|
|
5273
|
+
log.info("intake", "bot-added-bound", { chatId: evt.chatId.slice(-6) });
|
|
5274
|
+
return;
|
|
5275
|
+
}
|
|
5276
|
+
if (!op || !isAdmin(cfg, op)) {
|
|
5277
|
+
log.info("intake", "bot-added-nonadmin", { chatId: evt.chatId.slice(-6), op: op?.slice(-6) });
|
|
5278
|
+
return;
|
|
5279
|
+
}
|
|
5280
|
+
const info = await channel.getChatInfo(evt.chatId).catch((err) => {
|
|
5281
|
+
log.fail("intake", err, { phase: "bot-added-chatinfo" });
|
|
5282
|
+
return void 0;
|
|
5283
|
+
});
|
|
5284
|
+
const name = (info?.name ?? "").trim();
|
|
5285
|
+
await sendManagedCard(
|
|
5286
|
+
channel,
|
|
5287
|
+
op,
|
|
5288
|
+
buildJoinGroupFormCard({ chatId: evt.chatId, name }),
|
|
5289
|
+
void 0,
|
|
5290
|
+
false,
|
|
5291
|
+
"open_id"
|
|
5292
|
+
).catch((err) => log.fail("intake", err, { phase: "bot-added-bindcard" }));
|
|
5293
|
+
log.info("intake", "bot-added", { chatId: evt.chatId.slice(-6), op: op.slice(-6), named: Boolean(name) });
|
|
5294
|
+
}).catch((err) => log.fail("intake", err, { phase: "bot-added" }));
|
|
5295
|
+
}
|
|
5296
|
+
async function onBotRemovedFromChat(chatId) {
|
|
5297
|
+
const project = await getProjectByChatId(chatId);
|
|
5298
|
+
if (!project) return;
|
|
5299
|
+
const removed = await removeProject(project.name);
|
|
5300
|
+
if (!removed) return;
|
|
5301
|
+
log.info("intake", "bot-removed-unbind", { name: removed.name, chatId: chatId.slice(-6) });
|
|
5302
|
+
if (removed.addedBy) {
|
|
5303
|
+
await channel.rawClient.im.v1.message.create({
|
|
5304
|
+
params: { receive_id_type: "open_id" },
|
|
5305
|
+
data: {
|
|
5306
|
+
receive_id: removed.addedBy,
|
|
5307
|
+
msg_type: "text",
|
|
5308
|
+
content: JSON.stringify({ text: `\u2139\uFE0F \u6211\u5DF2\u88AB\u79FB\u51FA\u7FA4\u300C${removed.name}\u300D\uFF0C\u5BF9\u5E94\u9879\u76EE\u5DF2\u81EA\u52A8\u89E3\u7ED1\u3002` })
|
|
5309
|
+
}
|
|
5310
|
+
}).catch(() => void 0);
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5105
5313
|
async function shutdown() {
|
|
5106
5314
|
const live = [...sessions.values()];
|
|
5107
5315
|
sessions.clear();
|
|
5108
5316
|
await Promise.allSettled(live.map((t) => t.close()));
|
|
5109
5317
|
log.info("bridge", "shutdown", { closed: live.length });
|
|
5110
5318
|
}
|
|
5111
|
-
return { onMessage, onComment, dispatcher, shutdown };
|
|
5319
|
+
return { onMessage, onComment, onBotAddedToChat, onBotRemovedFromChat, dispatcher, shutdown };
|
|
5112
5320
|
}
|
|
5113
5321
|
async function getThreadId(channel, messageId) {
|
|
5114
5322
|
try {
|
|
@@ -5146,6 +5354,24 @@ async function startBridge(opts) {
|
|
|
5146
5354
|
channel.on("message", orchestrator.onMessage);
|
|
5147
5355
|
channel.on("cardAction", orchestrator.dispatcher.handle);
|
|
5148
5356
|
channel.on("comment", orchestrator.onComment);
|
|
5357
|
+
channel.on("botAdded", orchestrator.onBotAddedToChat);
|
|
5358
|
+
try {
|
|
5359
|
+
const tap = channel.dispatcher;
|
|
5360
|
+
if (tap?.register) {
|
|
5361
|
+
tap.register({
|
|
5362
|
+
"im.chat.member.bot.deleted_v1": (raw) => {
|
|
5363
|
+
const ev = raw;
|
|
5364
|
+
const chatId = ev?.chat_id ?? ev?.event?.chat_id;
|
|
5365
|
+
if (chatId) void orchestrator.onBotRemovedFromChat(chatId);
|
|
5366
|
+
}
|
|
5367
|
+
});
|
|
5368
|
+
log.info("ws", "bot-removed-tap");
|
|
5369
|
+
} else {
|
|
5370
|
+
log.info("ws", "bot-removed-tap-unavailable");
|
|
5371
|
+
}
|
|
5372
|
+
} catch (err) {
|
|
5373
|
+
log.fail("ws", err, { phase: "bot-removed-tap" });
|
|
5374
|
+
}
|
|
5149
5375
|
channel.on("reject", (evt) => log.info("intake", "reject", { reason: evt.reason, msgId: evt.messageId }));
|
|
5150
5376
|
channel.on("error", (err) => log.fail("ws", err));
|
|
5151
5377
|
channel.on("reconnecting", () => log.info("ws", "reconnecting"));
|
|
@@ -5357,6 +5583,7 @@ async function runBotInit(name) {
|
|
|
5357
5583
|
}
|
|
5358
5584
|
console.log("\n\u4E0B\u4E00\u6B65\uFF08\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u540E\u53F0\uFF0C\u9700\u624B\u52A8\u4E00\u6B21 https://open.feishu.cn/app \uFF09\uFF1A");
|
|
5359
5585
|
console.log(" 1) \u4E8B\u4EF6\u4E0E\u56DE\u8C03 \u2192 \u957F\u8FDE\u63A5 \u2192 \u8BA2\u9605\uFF1Aim.message.receive_v1 / card.action.trigger / application.bot.menu_v6");
|
|
5586
|
+
console.log(" \uFF08\u53EF\u9009\uFF09\u300C\u52A0\u8FDB\u5DF2\u6709\u7FA4\u300D\u529F\u80FD\u518D\u8BA2\u9605\uFF1Aim.chat.member.bot.added_v1 / im.chat.member.bot.deleted_v1");
|
|
5360
5587
|
console.log(" 2) \u521B\u5EFA\u5E76\u53D1\u5E03\u5E94\u7528\u7248\u672C");
|
|
5361
5588
|
console.log("\n`bot list` \u67E5\u770B\u5168\u90E8\uFF1B`bot use <\u540D>` \u5207\u6362\u5F53\u524D\uFF1B`run` \u524D\u53F0\u8DD1 / `start` \u540E\u53F0\u5E38\u9A7B\u3002\n");
|
|
5362
5589
|
}
|