@modelzen/feishu-codex-bridge 0.1.7 → 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 +656 -144
- 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
|
@@ -538,7 +538,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
538
538
|
const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
539
539
|
const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
|
|
540
540
|
const providerName = ref.provider ?? DEFAULT_PROVIDER;
|
|
541
|
-
return new Promise((
|
|
541
|
+
return new Promise((resolve6, reject) => {
|
|
542
542
|
const env = {};
|
|
543
543
|
if (pc.passEnv) for (const k of pc.passEnv) {
|
|
544
544
|
const v = process.env[k];
|
|
@@ -583,7 +583,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
583
583
|
try {
|
|
584
584
|
const parsed = JSON.parse(stdout);
|
|
585
585
|
const value = parsed.values?.[ref.id];
|
|
586
|
-
if (typeof value === "string") return
|
|
586
|
+
if (typeof value === "string") return resolve6(value);
|
|
587
587
|
const err = parsed.errors?.[ref.id]?.message;
|
|
588
588
|
reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
|
|
589
589
|
} catch (err) {
|
|
@@ -679,7 +679,37 @@ 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];
|
|
687
|
+
var SCOPE_LABELS = {
|
|
688
|
+
"im:message.group_at_msg:readonly": "\u63A5\u6536\u7FA4\u91CC @\u673A\u5668\u4EBA \u7684\u6D88\u606F",
|
|
689
|
+
"im:message.group_msg": "\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\uFF08\u514D@\uFF09",
|
|
690
|
+
"im:message.p2p_msg:readonly": "\u63A5\u6536\u79C1\u804A\u6D88\u606F\uFF08\u7BA1\u7406\u53F0\uFF09",
|
|
691
|
+
"im:message:send_as_bot": "\u53D1\u9001\u6D88\u606F / \u5361\u7247",
|
|
692
|
+
"im:message.pins:write_only": "\u7F6E\u9876\u6D88\u606F\u5230\u7FA4 Pin",
|
|
693
|
+
"im:message.reactions:write_only": "\u6D88\u606F\u8868\u60C5\u56DE\u590D\uFF08\u8FD0\u884C\u72B6\u6001\uFF09",
|
|
694
|
+
"im:resource": "\u56FE\u7247 / \u6587\u4EF6\u4E0A\u4F20\u4E0E\u4E0B\u8F7D",
|
|
695
|
+
"im:chat:create": "\u521B\u5EFA\u9879\u76EE\u7FA4",
|
|
696
|
+
"im:chat:update": "\u8F6C\u79FB\u7FA4\u4E3B\uFF08\u89E3\u7ED1\u65F6\uFF09",
|
|
697
|
+
"im:chat.managers:write_only": "\u8BBE\u7F6E\u7FA4\u7BA1\u7406\u5458",
|
|
698
|
+
"im:chat.announcement:read": "\u8BFB\u53D6\u7FA4\u516C\u544A",
|
|
699
|
+
"im:chat.announcement:write_only": "\u7F16\u8F91\u7FA4\u516C\u544A",
|
|
700
|
+
"im:chat.top_notice:write_only": "\u7F6E\u9876\u7FA4\u516C\u544A\u6A2A\u5E45",
|
|
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",
|
|
704
|
+
"cardkit:card:write": "\u4EA4\u4E92\u6309\u94AE\u5361\u7247",
|
|
705
|
+
"docs:document.comment:read": "\u8BFB\u53D6\u6587\u6863\u8BC4\u8BBA",
|
|
706
|
+
"docs:document.comment:create": "\u53D1\u8868\u6587\u6863\u8BC4\u8BBA\u56DE\u590D",
|
|
707
|
+
"wiki:wiki:readonly": "\u8BFB\u53D6\u77E5\u8BC6\u5E93\u8282\u70B9"
|
|
708
|
+
};
|
|
709
|
+
function labelScope(scope) {
|
|
710
|
+
const label = SCOPE_LABELS[scope];
|
|
711
|
+
return label ? `${label}\uFF08${scope}\uFF09` : scope;
|
|
712
|
+
}
|
|
683
713
|
var HOSTS = {
|
|
684
714
|
feishu: "open.feishu.cn",
|
|
685
715
|
lark: "open.larksuite.com"
|
|
@@ -722,8 +752,15 @@ async function validateAppCredentials(appId, appSecret, tenant) {
|
|
|
722
752
|
}
|
|
723
753
|
const token = data.tenant_access_token;
|
|
724
754
|
const info = await fetchBotInfo(base, token).catch(() => void 0);
|
|
725
|
-
const
|
|
726
|
-
|
|
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
|
+
};
|
|
727
764
|
}
|
|
728
765
|
async function fetchBotInfo(base, token) {
|
|
729
766
|
const resp = await fetch(`${base}/open-apis/bot/v3/info`, {
|
|
@@ -732,15 +769,14 @@ async function fetchBotInfo(base, token) {
|
|
|
732
769
|
if (!resp.ok) return void 0;
|
|
733
770
|
return await resp.json();
|
|
734
771
|
}
|
|
735
|
-
async function
|
|
772
|
+
async function fetchGrantedScopes(base, token) {
|
|
736
773
|
const resp = await fetch(`${base}/open-apis/application/v6/scopes`, {
|
|
737
774
|
headers: { Authorization: `Bearer ${token}` }
|
|
738
775
|
});
|
|
739
776
|
if (!resp.ok) return void 0;
|
|
740
777
|
const body = await resp.json();
|
|
741
778
|
if (!body.data?.scopes) return void 0;
|
|
742
|
-
|
|
743
|
-
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));
|
|
744
780
|
}
|
|
745
781
|
|
|
746
782
|
// src/utils/open-url.ts
|
|
@@ -1047,6 +1083,10 @@ async function confirmReadyForDaemon(result) {
|
|
|
1047
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");
|
|
1048
1084
|
console.log(" drive.notice.comment_add_v1\uFF08\u4E91\u6587\u6863\u65B0\u589E\u8BC4\u8BBA\uFF09");
|
|
1049
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");
|
|
1050
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");
|
|
1051
1091
|
console.log(" card.action.trigger\uFF08\u5361\u7247\u56DE\u4F20\u4EA4\u4E92\uFF09");
|
|
1052
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");
|
|
@@ -1112,7 +1152,7 @@ var AsyncQueue = class {
|
|
|
1112
1152
|
continue;
|
|
1113
1153
|
}
|
|
1114
1154
|
if (this.closed) return;
|
|
1115
|
-
const next = await new Promise((
|
|
1155
|
+
const next = await new Promise((resolve6) => this.waiters.push(resolve6));
|
|
1116
1156
|
if (next.done) return;
|
|
1117
1157
|
yield next.value;
|
|
1118
1158
|
}
|
|
@@ -1163,8 +1203,8 @@ var AppServerClient = class {
|
|
|
1163
1203
|
const id = ++this.nextId;
|
|
1164
1204
|
const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
|
|
1165
1205
|
`;
|
|
1166
|
-
return new Promise((
|
|
1167
|
-
this.pending.set(id, { resolve:
|
|
1206
|
+
return new Promise((resolve6, reject) => {
|
|
1207
|
+
this.pending.set(id, { resolve: resolve6, reject });
|
|
1168
1208
|
this.child.stdin.write(payload, (err) => {
|
|
1169
1209
|
if (err) {
|
|
1170
1210
|
this.pending.delete(id);
|
|
@@ -1188,14 +1228,14 @@ var AppServerClient = class {
|
|
|
1188
1228
|
const child = this.child;
|
|
1189
1229
|
if (!child || child.exitCode !== null) return;
|
|
1190
1230
|
child.kill("SIGTERM");
|
|
1191
|
-
await new Promise((
|
|
1231
|
+
await new Promise((resolve6) => {
|
|
1192
1232
|
const t = setTimeout(() => {
|
|
1193
1233
|
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1194
|
-
|
|
1234
|
+
resolve6();
|
|
1195
1235
|
}, graceMs);
|
|
1196
1236
|
child.once("exit", () => {
|
|
1197
1237
|
clearTimeout(t);
|
|
1198
|
-
|
|
1238
|
+
resolve6();
|
|
1199
1239
|
});
|
|
1200
1240
|
});
|
|
1201
1241
|
}
|
|
@@ -1312,14 +1352,28 @@ function mapItemComplete(item) {
|
|
|
1312
1352
|
// src/agent/codex-appserver/backend.ts
|
|
1313
1353
|
var APPROVAL_POLICY = "never";
|
|
1314
1354
|
var SANDBOX = "danger-full-access";
|
|
1355
|
+
var BRIDGE_DEVELOPER_INSTRUCTIONS = [
|
|
1356
|
+
"\u4F60\u73B0\u5728\u901A\u8FC7\u300C\u98DE\u4E66\u6865\u300D\u4E0E\u7528\u6237\u5BF9\u8BDD\uFF1A\u4F60\u7684\u56DE\u590D\u4F1A\u88AB\u6E32\u67D3\u6210\u98DE\u4E66\u6D88\u606F\u3002\u8BF7\u9075\u5B88\u4E24\u6761\u8F93\u51FA\u7EA6\u5B9A\u3002",
|
|
1357
|
+
"",
|
|
1358
|
+
"1) \u56FE\u7247\uFF1A\u8981\u914D\u56FE\u65F6\uFF0C\u7528\u6807\u51C6 Markdown \u56FE\u7247\u8BED\u6CD5  \u5F15\u7528\u4E00\u4E2A\u3010\u771F\u5B9E\u5B58\u5728\u3011\u7684\u56FE\u7247\uFF0C",
|
|
1359
|
+
"\u98DE\u4E66\u6865\u4F1A\u81EA\u52A8\u4E0A\u4F20\u5E76\u5728\u98DE\u4E66\u91CC\u6E32\u67D3\u3002\u8DEF\u5F84\u53EF\u4EE5\u662F\u76F8\u5BF9\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u7684\u76F8\u5BF9\u8DEF\u5F84\u3001\u5DE5\u4F5C\u76EE\u5F55\u5185\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C",
|
|
1360
|
+
"\u6216\u4E00\u4E2A http(s) \u56FE\u7247 URL\u3002\u7EDD\u4E0D\u8981\u7F16\u9020\u4E0D\u5B58\u5728\u7684\u56FE\u7247\u5360\u4F4D\uFF08\u4F8B\u5982\u5199 ![\u7BA1\u7406\u53F0\u622A\u56FE] \u5374\u6CA1\u6709\u5BF9\u5E94\u6587\u4EF6\uFF09\u2014\u2014",
|
|
1361
|
+
"\u6CA1\u6709\u771F\u5B9E\u56FE\u7247\u5C31\u4E0D\u8981\u5199\u56FE\u7247\u8BED\u6CD5\u3002",
|
|
1362
|
+
"",
|
|
1363
|
+
"2) \u5361\u7247\uFF1A\u4EC5\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u300C\u7528\u5361\u7247\u56DE\u590D / \u505A\u6210\u98DE\u4E66\u5361\u7247 / \u5361\u7247\u5F62\u5F0F\u5C55\u793A / changelog \u5361\u7247\u300D\u4E4B\u7C7B\u65F6\uFF0C",
|
|
1364
|
+
"\u628A\u8981\u5C55\u793A\u7684\u5185\u5BB9\u5305\u8FDB\u4E00\u4E2A ```feishu-card \u4EE3\u7801\u5757\uFF0C\u5757\u5185\u7528 Markdown \u4E66\u5199\uFF1A",
|
|
1365
|
+
"\u9996\u884C\u7528 `# \u6807\u9898` \u4F5C\u4E3A\u5361\u7247\u6807\u9898\u680F\uFF1B\u7528 `---` \u4F5C\u5206\u9694\u7EBF\uFF1B\u7528 `> \u6587\u5B57` \u4F5C\u7070\u8272\u6CE8\u811A\uFF1B",
|
|
1366
|
+
"`**\u7C97\u4F53**`\u3001\u5217\u8868\u3001\u94FE\u63A5\u7167\u5E38\u4F7F\u7528\uFF1B\u914D\u56FE\u540C\u6837\u7528 \u3002",
|
|
1367
|
+
"\u4E0D\u8981\u624B\u5199\u98DE\u4E66\u5361\u7247\u7684 JSON\u3002\u666E\u901A\u95EE\u7B54\u6B63\u5E38\u56DE\u590D\u5373\u53EF\uFF0C\u53EA\u6709\u7528\u6237\u8981\u5361\u7247\u65F6\u624D\u7528 ```feishu-card \u4EE3\u7801\u5757\u3002"
|
|
1368
|
+
].join("\n");
|
|
1315
1369
|
var READ_HISTORY_TIMEOUT_MS = 2e4;
|
|
1316
1370
|
function withDeadline(p, ms, label) {
|
|
1317
|
-
return new Promise((
|
|
1371
|
+
return new Promise((resolve6, reject) => {
|
|
1318
1372
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
1319
1373
|
p.then(
|
|
1320
1374
|
(v) => {
|
|
1321
1375
|
clearTimeout(t);
|
|
1322
|
-
|
|
1376
|
+
resolve6(v);
|
|
1323
1377
|
},
|
|
1324
1378
|
(e) => {
|
|
1325
1379
|
clearTimeout(t);
|
|
@@ -1359,11 +1413,11 @@ var CodexThread = class {
|
|
|
1359
1413
|
if (self.model) params.model = self.model;
|
|
1360
1414
|
if (self.effort) params.effort = self.effort;
|
|
1361
1415
|
let startError;
|
|
1362
|
-
const startFailed = new Promise((
|
|
1416
|
+
const startFailed = new Promise((resolve6) => {
|
|
1363
1417
|
self.client.request("turn/start", params).then(void 0, (err) => {
|
|
1364
1418
|
startError = err instanceof Error ? err : new Error(String(err));
|
|
1365
1419
|
log.fail("agent", startError, { phase: "turn/start" });
|
|
1366
|
-
|
|
1420
|
+
resolve6("start-failed");
|
|
1367
1421
|
});
|
|
1368
1422
|
});
|
|
1369
1423
|
const stream2 = self.client.stream()[Symbol.asyncIterator]();
|
|
@@ -1487,6 +1541,7 @@ var CodexAppServerBackend = class {
|
|
|
1487
1541
|
cwd: opts.cwd,
|
|
1488
1542
|
approvalPolicy: APPROVAL_POLICY,
|
|
1489
1543
|
sandbox: SANDBOX,
|
|
1544
|
+
developerInstructions: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
1490
1545
|
...opts.model ? { model: opts.model } : {}
|
|
1491
1546
|
});
|
|
1492
1547
|
return new CodexThread(client, res.thread.id, opts.model, opts.effort);
|
|
@@ -1498,6 +1553,7 @@ var CodexAppServerBackend = class {
|
|
|
1498
1553
|
cwd: opts.cwd,
|
|
1499
1554
|
approvalPolicy: APPROVAL_POLICY,
|
|
1500
1555
|
sandbox: SANDBOX,
|
|
1556
|
+
developerInstructions: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
1501
1557
|
...opts.model ? { model: opts.model } : {}
|
|
1502
1558
|
});
|
|
1503
1559
|
return new CodexThread(client, res.thread.id, opts.model, opts.effort);
|
|
@@ -1667,7 +1723,7 @@ function stampRenderToken(card2) {
|
|
|
1667
1723
|
};
|
|
1668
1724
|
visit(card2);
|
|
1669
1725
|
}
|
|
1670
|
-
async function sendManagedCard(channel,
|
|
1726
|
+
async function sendManagedCard(channel, to, card2, replyTo, replyInThread = false, receiveIdType = "chat_id") {
|
|
1671
1727
|
stampRenderToken(card2);
|
|
1672
1728
|
const created = await channel.rawClient.cardkit.v1.card.create({
|
|
1673
1729
|
data: { type: "card_json", data: JSON.stringify(card2) }
|
|
@@ -1686,8 +1742,8 @@ async function sendManagedCard(channel, chatId, card2, replyTo, replyInThread =
|
|
|
1686
1742
|
messageId = sent.data?.message_id;
|
|
1687
1743
|
} else {
|
|
1688
1744
|
const sent = await channel.rawClient.im.v1.message.create({
|
|
1689
|
-
params: { receive_id_type:
|
|
1690
|
-
data: { receive_id:
|
|
1745
|
+
params: { receive_id_type: receiveIdType },
|
|
1746
|
+
data: { receive_id: to, msg_type: "interactive", content }
|
|
1691
1747
|
});
|
|
1692
1748
|
messageId = sent.data?.message_id;
|
|
1693
1749
|
}
|
|
@@ -1928,6 +1984,15 @@ function card(elements, opts = {}) {
|
|
|
1928
1984
|
function md(content) {
|
|
1929
1985
|
return { tag: "markdown", content };
|
|
1930
1986
|
}
|
|
1987
|
+
function image(imgKey, alt = "") {
|
|
1988
|
+
return {
|
|
1989
|
+
tag: "img",
|
|
1990
|
+
img_key: imgKey,
|
|
1991
|
+
alt: { tag: "plain_text", content: alt },
|
|
1992
|
+
mode: "fit_horizontal",
|
|
1993
|
+
preview: true
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1931
1996
|
function note(content) {
|
|
1932
1997
|
return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: "grey" } };
|
|
1933
1998
|
}
|
|
@@ -2134,14 +2199,20 @@ function pickerTime(unixSeconds) {
|
|
|
2134
2199
|
const md2 = `${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
|
|
2135
2200
|
return d.getFullYear() === now.getFullYear() ? `${md2} ${hm}` : `${d.getFullYear()}-${md2} ${hm}`;
|
|
2136
2201
|
}
|
|
2137
|
-
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) {
|
|
2138
2206
|
const elements = [];
|
|
2139
2207
|
if (scope === "single") {
|
|
2140
2208
|
elements.push(
|
|
2141
2209
|
md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"),
|
|
2142
2210
|
hr(),
|
|
2143
2211
|
md(
|
|
2144
|
-
"\
|
|
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`
|
|
2145
2216
|
)
|
|
2146
2217
|
);
|
|
2147
2218
|
} else if (scope === "topic") {
|
|
@@ -2149,7 +2220,9 @@ function buildHelpCard(scope) {
|
|
|
2149
2220
|
md("\u{1F9F5} **\u8BDD\u9898\u5185** \u2014 \u6BCF\u4E2A\u8BDD\u9898\u662F\u4E00\u4E2A\u72EC\u7ACB\u4F1A\u8BDD\u3002"),
|
|
2150
2221
|
hr(),
|
|
2151
2222
|
md(
|
|
2152
|
-
"\
|
|
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`
|
|
2153
2226
|
),
|
|
2154
2227
|
note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
|
|
2155
2228
|
);
|
|
@@ -2164,7 +2237,7 @@ function buildHelpCard(scope) {
|
|
|
2164
2237
|
}
|
|
2165
2238
|
return card(elements, { header: { title: "\u{1F916} \u53EF\u7528\u547D\u4EE4", template: "blue" }, summary: "\u53EF\u7528\u547D\u4EE4" });
|
|
2166
2239
|
}
|
|
2167
|
-
function buildWelcomeCard(kind, docUrl) {
|
|
2240
|
+
function buildWelcomeCard(kind, docUrl, noMention = true) {
|
|
2168
2241
|
const elements = [
|
|
2169
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"),
|
|
2170
2243
|
hr()
|
|
@@ -2173,7 +2246,10 @@ function buildWelcomeCard(kind, docUrl) {
|
|
|
2173
2246
|
elements.push(
|
|
2174
2247
|
md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4**\uFF08\u6574\u7FA4\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\uFF09"),
|
|
2175
2248
|
md(
|
|
2176
|
-
"\
|
|
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`
|
|
2177
2253
|
)
|
|
2178
2254
|
);
|
|
2179
2255
|
} else {
|
|
@@ -2337,6 +2413,92 @@ function truncateTail(s, n) {
|
|
|
2337
2413
|
return t.length > n ? `\u2026${t.slice(t.length - n)}` : t;
|
|
2338
2414
|
}
|
|
2339
2415
|
|
|
2416
|
+
// src/card/markdown-render.ts
|
|
2417
|
+
var NO_IMAGES = /* @__PURE__ */ new Map();
|
|
2418
|
+
var IMG_RE = /!\[([^\]]*)\]\(\s*(<[^>]+>|[^)\s]+)(?:\s+(?:"[^"]*"|'[^']*'))?\s*\)/g;
|
|
2419
|
+
var FENCE_RE = /```feishu-card[^\n]*\n([\s\S]*?)```/g;
|
|
2420
|
+
function cleanSrc(raw) {
|
|
2421
|
+
let s = raw.trim();
|
|
2422
|
+
if (s.startsWith("<") && s.endsWith(">")) s = s.slice(1, -1).trim();
|
|
2423
|
+
return s;
|
|
2424
|
+
}
|
|
2425
|
+
function extractCardFences(text) {
|
|
2426
|
+
const fences = [];
|
|
2427
|
+
const re = new RegExp(FENCE_RE.source, "g");
|
|
2428
|
+
const stripped = text.replace(re, (_full, inner) => {
|
|
2429
|
+
fences.push(inner.trim());
|
|
2430
|
+
return "";
|
|
2431
|
+
});
|
|
2432
|
+
return { fences, stripped };
|
|
2433
|
+
}
|
|
2434
|
+
function renderRichText(text, images = NO_IMAGES) {
|
|
2435
|
+
const body = extractCardFences(text).stripped;
|
|
2436
|
+
if (!body.includes("![")) {
|
|
2437
|
+
const t = body.trim();
|
|
2438
|
+
return t ? [md(t)] : [];
|
|
2439
|
+
}
|
|
2440
|
+
const els = [];
|
|
2441
|
+
let buf = "";
|
|
2442
|
+
const flush = () => {
|
|
2443
|
+
const t = buf.trim();
|
|
2444
|
+
if (t) els.push(md(t));
|
|
2445
|
+
buf = "";
|
|
2446
|
+
};
|
|
2447
|
+
const re = new RegExp(IMG_RE.source, "g");
|
|
2448
|
+
let last = 0;
|
|
2449
|
+
let m;
|
|
2450
|
+
while ((m = re.exec(body)) !== null) {
|
|
2451
|
+
buf += body.slice(last, m.index);
|
|
2452
|
+
const alt = m[1] ?? "";
|
|
2453
|
+
const src = cleanSrc(m[2] ?? "");
|
|
2454
|
+
const key = images.get(src);
|
|
2455
|
+
if (key) {
|
|
2456
|
+
flush();
|
|
2457
|
+
els.push(image(key, alt));
|
|
2458
|
+
} else {
|
|
2459
|
+
buf += m[0];
|
|
2460
|
+
}
|
|
2461
|
+
last = m.index + m[0].length;
|
|
2462
|
+
}
|
|
2463
|
+
buf += body.slice(last);
|
|
2464
|
+
flush();
|
|
2465
|
+
return els;
|
|
2466
|
+
}
|
|
2467
|
+
function buildCleanCard(fenceMarkdown, images = NO_IMAGES, template = "blue") {
|
|
2468
|
+
const lines = fenceMarkdown.split("\n");
|
|
2469
|
+
let start = 0;
|
|
2470
|
+
while (start < lines.length && lines[start]?.trim() === "") start++;
|
|
2471
|
+
const headingMatch = lines[start]?.match(/^#{1,6}\s+(.+?)\s*$/);
|
|
2472
|
+
const title = headingMatch ? headingMatch[1] : "";
|
|
2473
|
+
if (headingMatch) start++;
|
|
2474
|
+
const bodyMarkdown = lines.slice(start).join("\n").trim();
|
|
2475
|
+
const elements = renderCleanBody(bodyMarkdown, images);
|
|
2476
|
+
const body = elements.length > 0 ? elements : [md(title || "\xAD")];
|
|
2477
|
+
return card(body, {
|
|
2478
|
+
...title ? { header: { title, template } } : {},
|
|
2479
|
+
summary: title || "\u5361\u7247"
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
function renderCleanBody(bodyMarkdown, images) {
|
|
2483
|
+
const out = [];
|
|
2484
|
+
for (const raw of bodyMarkdown.split(/\n{2,}/)) {
|
|
2485
|
+
const block = raw.trim();
|
|
2486
|
+
if (!block) continue;
|
|
2487
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(block)) {
|
|
2488
|
+
out.push(hr());
|
|
2489
|
+
continue;
|
|
2490
|
+
}
|
|
2491
|
+
const blockLines = block.split("\n");
|
|
2492
|
+
if (blockLines.every((l) => l.trim() === "" || /^\s*>\s?/.test(l))) {
|
|
2493
|
+
const noteText = blockLines.map((l) => l.replace(/^\s*>\s?/, "")).join("\n").trim();
|
|
2494
|
+
if (noteText) out.push(note(noteText));
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
out.push(...renderRichText(block, images));
|
|
2498
|
+
}
|
|
2499
|
+
return out;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2340
2502
|
// src/card/tool-render.ts
|
|
2341
2503
|
var HEADER_TITLE_MAX = 80;
|
|
2342
2504
|
var OUTPUT_MAX = 1200;
|
|
@@ -2415,7 +2577,7 @@ function renderTerminal(state, rc) {
|
|
|
2415
2577
|
})
|
|
2416
2578
|
);
|
|
2417
2579
|
}
|
|
2418
|
-
if (answer) elements.push(
|
|
2580
|
+
if (answer) elements.push(...renderRichText(answer, rc.images));
|
|
2419
2581
|
if (state.terminal === "interrupted") {
|
|
2420
2582
|
elements.push(noteMd("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_"));
|
|
2421
2583
|
} else if (state.terminal === "idle_timeout") {
|
|
@@ -2624,6 +2786,207 @@ var RunCardStream = class {
|
|
|
2624
2786
|
}
|
|
2625
2787
|
};
|
|
2626
2788
|
|
|
2789
|
+
// src/card/outbound-images.ts
|
|
2790
|
+
import { readFile as readFile5, stat as stat2 } from "fs/promises";
|
|
2791
|
+
import { extname, isAbsolute, resolve as resolve2, sep } from "path";
|
|
2792
|
+
var MAX_IMAGES = 9;
|
|
2793
|
+
var MAX_BYTES = 10 * 1024 * 1024;
|
|
2794
|
+
var DOWNLOAD_TIMEOUT_MS = 1e4;
|
|
2795
|
+
var ALLOWED_EXT = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "webp", "gif", "tif", "tiff", "bmp", "ico"]);
|
|
2796
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2797
|
+
var IMG_RE2 = /!\[([^\]]*)\]\(\s*(<[^>]+>|[^)\s]+)(?:\s+(?:"[^"]*"|'[^']*'))?\s*\)/g;
|
|
2798
|
+
function cleanSrc2(raw) {
|
|
2799
|
+
let s = raw.trim();
|
|
2800
|
+
if (s.startsWith("<") && s.endsWith(">")) s = s.slice(1, -1).trim();
|
|
2801
|
+
return s;
|
|
2802
|
+
}
|
|
2803
|
+
function imageSources(text) {
|
|
2804
|
+
const out = [];
|
|
2805
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2806
|
+
const re = new RegExp(IMG_RE2.source, "g");
|
|
2807
|
+
let m;
|
|
2808
|
+
while ((m = re.exec(text)) !== null) {
|
|
2809
|
+
const src = cleanSrc2(m[2] ?? "");
|
|
2810
|
+
if (src && !seen.has(src)) {
|
|
2811
|
+
seen.add(src);
|
|
2812
|
+
out.push(src);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
return out;
|
|
2816
|
+
}
|
|
2817
|
+
async function uploadOutboundImages(channel, sources, cwd) {
|
|
2818
|
+
const picked = sources.slice(0, MAX_IMAGES);
|
|
2819
|
+
if (sources.length > picked.length) {
|
|
2820
|
+
log.warn("outbound", "image-cap", { skipped: sources.length - picked.length });
|
|
2821
|
+
}
|
|
2822
|
+
const results = await Promise.all(
|
|
2823
|
+
picked.map(async (src) => {
|
|
2824
|
+
try {
|
|
2825
|
+
return [src, await resolveAndUpload(channel, src, cwd)];
|
|
2826
|
+
} catch (err) {
|
|
2827
|
+
log.warn("outbound", "image-failed", { src: src.slice(0, 80), err: String(err) });
|
|
2828
|
+
return [src, void 0];
|
|
2829
|
+
}
|
|
2830
|
+
})
|
|
2831
|
+
);
|
|
2832
|
+
const out = /* @__PURE__ */ new Map();
|
|
2833
|
+
for (const [src, key] of results) if (key) out.set(src, key);
|
|
2834
|
+
if (out.size > 0) log.info("outbound", "images", { want: sources.length, uploaded: out.size });
|
|
2835
|
+
return out;
|
|
2836
|
+
}
|
|
2837
|
+
async function resolveAndUpload(channel, src, cwd) {
|
|
2838
|
+
const { buffer, cacheKey } = await loadSource(src, cwd);
|
|
2839
|
+
if (!buffer) return void 0;
|
|
2840
|
+
const hit = cache.get(cacheKey);
|
|
2841
|
+
if (hit) return hit;
|
|
2842
|
+
const key = await uploadBuffer(channel, buffer);
|
|
2843
|
+
if (key) cache.set(cacheKey, key);
|
|
2844
|
+
return key;
|
|
2845
|
+
}
|
|
2846
|
+
async function loadSource(src, cwd) {
|
|
2847
|
+
if (/^https?:\/\//i.test(src)) return loadRemote(src);
|
|
2848
|
+
return loadLocal(src, cwd);
|
|
2849
|
+
}
|
|
2850
|
+
async function loadLocal(src, cwd) {
|
|
2851
|
+
const cwdAbs = resolve2(cwd);
|
|
2852
|
+
const abs = isAbsolute(src) ? resolve2(src) : resolve2(cwdAbs, src);
|
|
2853
|
+
if (abs !== cwdAbs && !abs.startsWith(cwdAbs + sep)) {
|
|
2854
|
+
log.warn("outbound", "image-outside-cwd", { src: src.slice(0, 80) });
|
|
2855
|
+
return { cacheKey: `local:${abs}` };
|
|
2856
|
+
}
|
|
2857
|
+
const ext = extname(abs).slice(1).toLowerCase();
|
|
2858
|
+
if (!ALLOWED_EXT.has(ext)) {
|
|
2859
|
+
log.warn("outbound", "image-ext", { ext, src: src.slice(0, 80) });
|
|
2860
|
+
return { cacheKey: `local:${abs}` };
|
|
2861
|
+
}
|
|
2862
|
+
let size;
|
|
2863
|
+
let mtimeMs;
|
|
2864
|
+
try {
|
|
2865
|
+
const st = await stat2(abs);
|
|
2866
|
+
if (!st.isFile()) throw new Error("not a file");
|
|
2867
|
+
size = st.size;
|
|
2868
|
+
mtimeMs = st.mtimeMs;
|
|
2869
|
+
} catch {
|
|
2870
|
+
log.warn("outbound", "image-missing", { src: src.slice(0, 80) });
|
|
2871
|
+
return { cacheKey: `local:${abs}` };
|
|
2872
|
+
}
|
|
2873
|
+
if (size === 0 || size > MAX_BYTES) {
|
|
2874
|
+
log.warn("outbound", "image-size", { size, src: src.slice(0, 80) });
|
|
2875
|
+
return { cacheKey: `local:${abs}:${size}` };
|
|
2876
|
+
}
|
|
2877
|
+
const buffer = await readFile5(abs);
|
|
2878
|
+
return { buffer, cacheKey: `local:${abs}:${mtimeMs}:${size}` };
|
|
2879
|
+
}
|
|
2880
|
+
async function loadRemote(url) {
|
|
2881
|
+
const cacheKey = `url:${url}`;
|
|
2882
|
+
const ctrl = new AbortController();
|
|
2883
|
+
const timer = setTimeout(() => ctrl.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
2884
|
+
try {
|
|
2885
|
+
const res = await fetch(url, { signal: ctrl.signal, redirect: "follow" });
|
|
2886
|
+
if (!res.ok) {
|
|
2887
|
+
log.warn("outbound", "image-http", { url: url.slice(0, 80), status: res.status });
|
|
2888
|
+
return { cacheKey };
|
|
2889
|
+
}
|
|
2890
|
+
const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase();
|
|
2891
|
+
if (ct && !ct.startsWith("image/")) {
|
|
2892
|
+
log.warn("outbound", "image-ctype", { ct, url: url.slice(0, 80) });
|
|
2893
|
+
return { cacheKey };
|
|
2894
|
+
}
|
|
2895
|
+
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
2896
|
+
if (declared > MAX_BYTES) {
|
|
2897
|
+
log.warn("outbound", "image-size", { declared, url: url.slice(0, 80) });
|
|
2898
|
+
return { cacheKey };
|
|
2899
|
+
}
|
|
2900
|
+
const ab = await res.arrayBuffer();
|
|
2901
|
+
if (ab.byteLength === 0 || ab.byteLength > MAX_BYTES) {
|
|
2902
|
+
log.warn("outbound", "image-size", { size: ab.byteLength, url: url.slice(0, 80) });
|
|
2903
|
+
return { cacheKey };
|
|
2904
|
+
}
|
|
2905
|
+
return { buffer: Buffer.from(ab), cacheKey };
|
|
2906
|
+
} catch (err) {
|
|
2907
|
+
log.warn("outbound", "image-fetch", { url: url.slice(0, 80), err: String(err) });
|
|
2908
|
+
return { cacheKey };
|
|
2909
|
+
} finally {
|
|
2910
|
+
clearTimeout(timer);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
async function uploadBuffer(channel, buffer) {
|
|
2914
|
+
const res = await channel.rawClient.im.v1.image.create({
|
|
2915
|
+
data: { image_type: "message", image: buffer }
|
|
2916
|
+
});
|
|
2917
|
+
const key = res?.image_key ?? res?.data?.image_key;
|
|
2918
|
+
if (!key) {
|
|
2919
|
+
log.warn("outbound", "image-no-key", { res: JSON.stringify(res).slice(0, 120) });
|
|
2920
|
+
return void 0;
|
|
2921
|
+
}
|
|
2922
|
+
return key;
|
|
2923
|
+
}
|
|
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
|
+
|
|
2627
2990
|
// src/card/dm-cards.ts
|
|
2628
2991
|
function openChatUrl(chatId) {
|
|
2629
2992
|
return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
|
|
@@ -2633,6 +2996,7 @@ var DM = {
|
|
|
2633
2996
|
menu: "dm.menu",
|
|
2634
2997
|
newProject: "dm.newProject",
|
|
2635
2998
|
newProjectSubmit: "dm.newProject.submit",
|
|
2999
|
+
joinGroupSubmit: "dm.joinGroup.submit",
|
|
2636
3000
|
projects: "dm.projects",
|
|
2637
3001
|
settings: "dm.settings",
|
|
2638
3002
|
doctor: "dm.doctor",
|
|
@@ -2780,11 +3144,33 @@ function scopeDiagnosis(i) {
|
|
|
2780
3144
|
return [md("- \u98DE\u4E66\u6743\u9650\uFF1A\u2705 \u5FC5\u9700\u6743\u9650\u5DF2\u5168\u90E8\u5F00\u901A")];
|
|
2781
3145
|
}
|
|
2782
3146
|
return [
|
|
2783
|
-
md(`- \u98DE\u4E66\u6743\u9650\uFF1A\u274C \u7F3A ${i.missingScopes.length} \u9879 \u2014\u2014 \u5F00\u901A\u524D\u76F8\u5173\u529F\u80FD\uFF08\u6536\u53D1\u6D88\u606F / \u5361\u7247 / \u5EFA\u7FA4\u7B49\uFF09\u4E0D\u53EF\u7528`),
|
|
2784
|
-
note(`\u5F85\u5F00\u901A\uFF1A
|
|
3147
|
+
md(`- \u98DE\u4E66\u6743\u9650\uFF1A\u274C \u7F3A ${i.missingScopes.length} \u9879 \u2014\u2014 \u5F00\u901A\u524D\u76F8\u5173\u529F\u80FD\uFF08\u6536\u53D1\u6D88\u606F / \u5361\u7247 / \u56FE\u7247 / \u5EFA\u7FA4\u7B49\uFF09\u4E0D\u53EF\u7528`),
|
|
3148
|
+
note(`\u5F85\u5F00\u901A\uFF1A
|
|
3149
|
+
${i.missingScopes.map((s) => `\xB7 ${labelScope(s)}`).join("\n")}`),
|
|
2785
3150
|
actions([linkButton("\u{1F511} \u4E00\u952E\u53BB\u5F00\u901A\u8FD9\u4E9B\u6743\u9650", i.scopeGrantUrl)])
|
|
2786
3151
|
];
|
|
2787
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
|
+
}
|
|
2788
3174
|
function codexDiagnosePrompt(i) {
|
|
2789
3175
|
return [
|
|
2790
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",
|
|
@@ -2827,6 +3213,8 @@ function buildDoctorCard(i) {
|
|
|
2827
3213
|
...scopeDiagnosis(i),
|
|
2828
3214
|
note(`bridge v${i.bridgeVer}\u3000\xB7\u3000Node ${i.node}\u3000\xB7\u3000${i.platform}`),
|
|
2829
3215
|
hr(),
|
|
3216
|
+
...joinFeatureDiagnosis(i),
|
|
3217
|
+
hr(),
|
|
2830
3218
|
md("**\u65E5\u5FD7\u8DEF\u5F84**"),
|
|
2831
3219
|
note(`\u540E\u53F0\u5B88\u62A4\u8F93\u51FA\uFF1A\`${i.logStdout}\``),
|
|
2832
3220
|
note(`\u540E\u53F0\u5B88\u62A4\u9519\u8BEF\uFF1A\`${i.logStderr}\``),
|
|
@@ -2860,14 +3248,35 @@ function buildNewProjectFormCard(opts = {}) {
|
|
|
2860
3248
|
);
|
|
2861
3249
|
return card(elements, { header: { title: "\u2795 \u65B0\u5EFA\u9879\u76EE", template: "turquoise" } });
|
|
2862
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
|
+
}
|
|
2863
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";
|
|
2864
3273
|
const elements = [
|
|
2865
|
-
md(`\u2705
|
|
3274
|
+
md(`\u2705 ${verb} **${p.name}**${p.blank ? " _(\u7A7A\u767D\u9879\u76EE)_" : ""}`),
|
|
2866
3275
|
note(`\u{1F4C2} \`${p.cwd}\` \xB7 ${kindLabel(p.kind)}`),
|
|
2867
|
-
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")
|
|
2868
3277
|
];
|
|
2869
3278
|
if (p.chatId) elements.push(actions([linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId), "primary")]));
|
|
2870
|
-
return card(elements, { header: { title
|
|
3279
|
+
return card(elements, { header: { title, template: "green" } });
|
|
2871
3280
|
}
|
|
2872
3281
|
function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
|
|
2873
3282
|
if (projects.length === 0) {
|
|
@@ -2882,7 +3291,7 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
2882
3291
|
elements.push(note(`\u{1F4C2} \`${p.cwd}\`${p.branch && p.branch !== "\u2014" ? ` \u{1F33F} ${p.branch}` : ""}`));
|
|
2883
3292
|
elements.push(
|
|
2884
3293
|
note(
|
|
2885
|
-
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"
|
|
2886
3295
|
)
|
|
2887
3296
|
);
|
|
2888
3297
|
const sessions = (p.chatId ? sessionsByChat.get(p.chatId) : void 0) ?? [];
|
|
@@ -2905,11 +3314,12 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
2905
3314
|
elements.push(actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]));
|
|
2906
3315
|
return card(elements, { header: { title: "\u{1F4C1} \u9879\u76EE\u5217\u8868", template: "wathet" } });
|
|
2907
3316
|
}
|
|
2908
|
-
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";
|
|
2909
3319
|
return card(
|
|
2910
3320
|
[
|
|
2911
3321
|
md(`\u786E\u5B9A\u5220\u9664\u9879\u76EE **${name}**\uFF1F`),
|
|
2912
|
-
note(
|
|
3322
|
+
note(note_),
|
|
2913
3323
|
actions([
|
|
2914
3324
|
button("\u2705 \u786E\u8BA4\u5220\u9664", { a: DM.rmDo, n: name }, "danger"),
|
|
2915
3325
|
button("\u53D6\u6D88", { a: DM.rmCancel })
|
|
@@ -2957,7 +3367,7 @@ function buildSettingsCard(cfg) {
|
|
|
2957
3367
|
}
|
|
2958
3368
|
function buildGroupSettingsCard(project) {
|
|
2959
3369
|
const kind = project.kind ?? "multi";
|
|
2960
|
-
const noMention = project.noMention ??
|
|
3370
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
2961
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";
|
|
2962
3372
|
return card(
|
|
2963
3373
|
[
|
|
@@ -2977,16 +3387,16 @@ function buildGroupSettingsCard(project) {
|
|
|
2977
3387
|
// src/service/update.ts
|
|
2978
3388
|
import { execFile, spawn as spawn5 } from "child_process";
|
|
2979
3389
|
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
2980
|
-
import { dirname as
|
|
3390
|
+
import { dirname as dirname7, join as join8, resolve as resolve4 } from "path";
|
|
2981
3391
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2982
3392
|
import { promisify } from "util";
|
|
2983
3393
|
|
|
2984
3394
|
// src/service/launchd.ts
|
|
2985
3395
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
2986
3396
|
import { existsSync as existsSync4 } from "fs";
|
|
2987
|
-
import { appendFile, mkdir as
|
|
3397
|
+
import { appendFile, mkdir as mkdir5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
2988
3398
|
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
2989
|
-
import { dirname as
|
|
3399
|
+
import { dirname as dirname6, join as join7, resolve as resolve3 } from "path";
|
|
2990
3400
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2991
3401
|
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
2992
3402
|
function launchAgentPlistPath() {
|
|
@@ -2999,8 +3409,8 @@ function serviceStderrPath() {
|
|
|
2999
3409
|
return join7(paths.appDir, "service.err.log");
|
|
3000
3410
|
}
|
|
3001
3411
|
function resolveCliBinPath() {
|
|
3002
|
-
const distDir =
|
|
3003
|
-
return
|
|
3412
|
+
const distDir = dirname6(fileURLToPath2(import.meta.url));
|
|
3413
|
+
return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3004
3414
|
}
|
|
3005
3415
|
function escapeXml(value) {
|
|
3006
3416
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -3040,9 +3450,9 @@ function buildPlist() {
|
|
|
3040
3450
|
}
|
|
3041
3451
|
async function installLaunchd() {
|
|
3042
3452
|
const plistPath = launchAgentPlistPath();
|
|
3043
|
-
await
|
|
3453
|
+
await mkdir5(dirname6(plistPath), { recursive: true });
|
|
3044
3454
|
await ensureLogFiles();
|
|
3045
|
-
await
|
|
3455
|
+
await writeFile5(plistPath, buildPlist(), "utf8");
|
|
3046
3456
|
if (isLoaded()) {
|
|
3047
3457
|
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
3048
3458
|
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
@@ -3124,7 +3534,7 @@ async function waitUntilUnloaded(timeoutMs = 5e3) {
|
|
|
3124
3534
|
throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
|
|
3125
3535
|
}
|
|
3126
3536
|
async function ensureLogFiles() {
|
|
3127
|
-
await
|
|
3537
|
+
await mkdir5(paths.appDir, { recursive: true });
|
|
3128
3538
|
await appendFile(serviceStdoutPath(), "");
|
|
3129
3539
|
await appendFile(serviceStderrPath(), "");
|
|
3130
3540
|
}
|
|
@@ -3166,7 +3576,7 @@ function getServiceAdapter() {
|
|
|
3166
3576
|
var execFileP = promisify(execFile);
|
|
3167
3577
|
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3168
3578
|
function pkgRoot() {
|
|
3169
|
-
return
|
|
3579
|
+
return resolve4(dirname7(fileURLToPath3(import.meta.url)), "..");
|
|
3170
3580
|
}
|
|
3171
3581
|
function pkgJson() {
|
|
3172
3582
|
try {
|
|
@@ -3231,68 +3641,10 @@ async function restartDaemon() {
|
|
|
3231
3641
|
await getServiceAdapter().restart();
|
|
3232
3642
|
}
|
|
3233
3643
|
|
|
3234
|
-
// src/project/registry.ts
|
|
3235
|
-
import { mkdir as mkdir5, readFile as readFile5, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
3236
|
-
import { dirname as dirname7 } from "path";
|
|
3237
|
-
var FILE_VERSION2 = 1;
|
|
3238
|
-
async function read() {
|
|
3239
|
-
try {
|
|
3240
|
-
const text = await readFile5(paths.projectsFile, "utf8");
|
|
3241
|
-
const parsed = JSON.parse(text);
|
|
3242
|
-
return Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
3243
|
-
} catch (err) {
|
|
3244
|
-
if (err.code === "ENOENT") return [];
|
|
3245
|
-
throw err;
|
|
3246
|
-
}
|
|
3247
|
-
}
|
|
3248
|
-
async function write(projects) {
|
|
3249
|
-
await mkdir5(dirname7(paths.projectsFile), { recursive: true });
|
|
3250
|
-
const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
|
|
3251
|
-
const body = { version: FILE_VERSION2, projects };
|
|
3252
|
-
await writeFile5(tmp, `${JSON.stringify(body, null, 2)}
|
|
3253
|
-
`, "utf8");
|
|
3254
|
-
await rename4(tmp, paths.projectsFile);
|
|
3255
|
-
}
|
|
3256
|
-
async function listProjects() {
|
|
3257
|
-
return read();
|
|
3258
|
-
}
|
|
3259
|
-
async function getProjectByChatId(chatId) {
|
|
3260
|
-
return (await read()).find((p) => p.chatId === chatId);
|
|
3261
|
-
}
|
|
3262
|
-
async function getProjectByName(name) {
|
|
3263
|
-
return (await read()).find((p) => p.name === name);
|
|
3264
|
-
}
|
|
3265
|
-
async function addProject(p) {
|
|
3266
|
-
const projects = await read();
|
|
3267
|
-
if (projects.some((x) => x.name === p.name)) {
|
|
3268
|
-
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
3269
|
-
}
|
|
3270
|
-
projects.push(p);
|
|
3271
|
-
await write(projects);
|
|
3272
|
-
}
|
|
3273
|
-
async function updateProject(name, patch) {
|
|
3274
|
-
const projects = await read();
|
|
3275
|
-
const p = projects.find((x) => x.name === name);
|
|
3276
|
-
if (!p) return;
|
|
3277
|
-
const target = p;
|
|
3278
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
3279
|
-
if (v !== void 0) target[k] = v;
|
|
3280
|
-
}
|
|
3281
|
-
await write(projects);
|
|
3282
|
-
}
|
|
3283
|
-
async function removeProject(name) {
|
|
3284
|
-
const projects = await read();
|
|
3285
|
-
const idx = projects.findIndex((p) => p.name === name);
|
|
3286
|
-
if (idx === -1) return void 0;
|
|
3287
|
-
const [removed] = projects.splice(idx, 1);
|
|
3288
|
-
await write(projects);
|
|
3289
|
-
return removed;
|
|
3290
|
-
}
|
|
3291
|
-
|
|
3292
3644
|
// src/project/lifecycle.ts
|
|
3293
3645
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
3294
3646
|
import { existsSync as existsSync6 } from "fs";
|
|
3295
|
-
import { isAbsolute, join as join9, resolve as
|
|
3647
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
|
|
3296
3648
|
|
|
3297
3649
|
// src/project/git-info.ts
|
|
3298
3650
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -3393,21 +3745,23 @@ var HELP_DOC_URL = "https://my.feishu.cn/wiki/PZ23wGr7JiKK5RkIG4rcZXzGn5g";
|
|
|
3393
3745
|
async function onboardGroup(channel, project) {
|
|
3394
3746
|
const kind = project.kind ?? "multi";
|
|
3395
3747
|
const chatId = project.chatId;
|
|
3748
|
+
const decorate = (project.origin ?? "created") !== "joined";
|
|
3396
3749
|
try {
|
|
3397
|
-
const
|
|
3750
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3751
|
+
const content = JSON.stringify(buildWelcomeCard(kind, HELP_DOC_URL || void 0, noMention));
|
|
3398
3752
|
const sent = await channel.rawClient.im.v1.message.create({
|
|
3399
3753
|
params: { receive_id_type: "chat_id" },
|
|
3400
3754
|
data: { receive_id: chatId, msg_type: "interactive", content }
|
|
3401
3755
|
});
|
|
3402
3756
|
const messageId = sent.data?.message_id;
|
|
3403
|
-
if (messageId) {
|
|
3757
|
+
if (messageId && decorate) {
|
|
3404
3758
|
await channel.rawClient.im.v1.pin.create({ data: { message_id: messageId } });
|
|
3405
3759
|
log.info("project", "onboard-pin", { name: project.name });
|
|
3406
3760
|
}
|
|
3407
3761
|
} catch (err) {
|
|
3408
3762
|
log.fail("project", err, { phase: "onboard-welcome" });
|
|
3409
3763
|
}
|
|
3410
|
-
if (HELP_DOC_URL) {
|
|
3764
|
+
if (decorate && HELP_DOC_URL) {
|
|
3411
3765
|
try {
|
|
3412
3766
|
await channel.rawClient.im.v1.chatTab.create({
|
|
3413
3767
|
path: { chat_id: chatId },
|
|
@@ -3423,21 +3777,21 @@ async function onboardGroup(channel, project) {
|
|
|
3423
3777
|
}
|
|
3424
3778
|
|
|
3425
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
|
+
}
|
|
3426
3790
|
async function createProject(channel, input2) {
|
|
3427
3791
|
const name = input2.name.trim();
|
|
3428
3792
|
if (!name) throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3429
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`);
|
|
3430
|
-
|
|
3431
|
-
let blank;
|
|
3432
|
-
if (input2.existingPath) {
|
|
3433
|
-
cwd = isAbsolute(input2.existingPath) ? input2.existingPath : resolve4(input2.existingPath);
|
|
3434
|
-
if (!existsSync6(cwd)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd}`);
|
|
3435
|
-
blank = false;
|
|
3436
|
-
} else {
|
|
3437
|
-
cwd = join9(paths.projectsRootDir, name);
|
|
3438
|
-
await mkdir6(cwd, { recursive: true });
|
|
3439
|
-
blank = true;
|
|
3440
|
-
}
|
|
3794
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
3441
3795
|
const res = await channel.rawClient.im.v1.chat.create({
|
|
3442
3796
|
params: { user_id_type: "open_id" },
|
|
3443
3797
|
data: { name, user_id_list: [input2.ownerOpenId] }
|
|
@@ -3449,13 +3803,35 @@ async function createProject(channel, input2) {
|
|
|
3449
3803
|
params: { member_id_type: "open_id" },
|
|
3450
3804
|
data: { manager_ids: [input2.ownerOpenId] }
|
|
3451
3805
|
}).catch((err) => log.fail("project", err, { phase: "add-manager" }));
|
|
3452
|
-
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" };
|
|
3453
3807
|
await addProject(project);
|
|
3454
3808
|
log.info("project", "create", { name, chatId, cwd, blank });
|
|
3455
3809
|
await setAnnouncement(channel, project).catch((err) => log.fail("project", err, { phase: "announcement" }));
|
|
3456
3810
|
await onboardGroup(channel, project).catch((err) => log.fail("project", err, { phase: "onboard" }));
|
|
3457
3811
|
return project;
|
|
3458
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
|
+
}
|
|
3459
3835
|
|
|
3460
3836
|
// src/project/group-ops.ts
|
|
3461
3837
|
async function transferOwnership(channel, chatId, toOpenId) {
|
|
@@ -3466,14 +3842,21 @@ async function transferOwnership(channel, chatId, toOpenId) {
|
|
|
3466
3842
|
});
|
|
3467
3843
|
log.info("project", "owner-transfer", { chatId: chatId.slice(-6), to: toOpenId.slice(-6) });
|
|
3468
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
|
+
}
|
|
3469
3852
|
|
|
3470
3853
|
// src/bot/session-store.ts
|
|
3471
|
-
import { mkdir as mkdir7, readFile as
|
|
3854
|
+
import { mkdir as mkdir7, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "fs/promises";
|
|
3472
3855
|
import { dirname as dirname8 } from "path";
|
|
3473
3856
|
var FILE_VERSION3 = 1;
|
|
3474
3857
|
async function read2() {
|
|
3475
3858
|
try {
|
|
3476
|
-
const text = await
|
|
3859
|
+
const text = await readFile7(paths.sessionsFile, "utf8");
|
|
3477
3860
|
const parsed = JSON.parse(text);
|
|
3478
3861
|
return Array.isArray(parsed.sessions) ? parsed.sessions : [];
|
|
3479
3862
|
} catch (err) {
|
|
@@ -3529,9 +3912,9 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
3529
3912
|
}
|
|
3530
3913
|
|
|
3531
3914
|
// src/bot/media.ts
|
|
3532
|
-
import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as
|
|
3915
|
+
import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as stat3 } from "fs/promises";
|
|
3533
3916
|
import { join as join10 } from "path";
|
|
3534
|
-
var
|
|
3917
|
+
var MAX_IMAGES2 = 9;
|
|
3535
3918
|
var MEDIA_TTL_MS = 60 * 6e4;
|
|
3536
3919
|
var EXT_BY_CONTENT_TYPE = {
|
|
3537
3920
|
"image/png": "png",
|
|
@@ -3564,7 +3947,7 @@ async function collectInboundImages(channel, msg) {
|
|
|
3564
3947
|
}
|
|
3565
3948
|
const out = [];
|
|
3566
3949
|
let index = 0;
|
|
3567
|
-
for (const ref of refs.slice(0,
|
|
3950
|
+
for (const ref of refs.slice(0, MAX_IMAGES2)) {
|
|
3568
3951
|
const path = await downloadOne(channel, ref, index++);
|
|
3569
3952
|
if (path) out.push(path);
|
|
3570
3953
|
}
|
|
@@ -3671,7 +4054,7 @@ async function pruneOldMedia() {
|
|
|
3671
4054
|
for (const name of entries) {
|
|
3672
4055
|
const file = join10(paths.mediaDir, name);
|
|
3673
4056
|
try {
|
|
3674
|
-
const st = await
|
|
4057
|
+
const st = await stat3(file);
|
|
3675
4058
|
if (st.mtimeMs < cutoff) await rm3(file, { force: true });
|
|
3676
4059
|
} catch {
|
|
3677
4060
|
}
|
|
@@ -3996,11 +4379,22 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3996
4379
|
log.info("intake", "reject", { reason: "not_allowed", chatId: msg.chatId.slice(-6) });
|
|
3997
4380
|
return;
|
|
3998
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
|
+
}
|
|
3999
4393
|
const text = msg.content.trim();
|
|
4000
4394
|
const cmd = parseCommand(text);
|
|
4001
4395
|
if ((project?.kind ?? "multi") === "single") {
|
|
4002
4396
|
if (cmd === "help") {
|
|
4003
|
-
await postHelpCard(msg, "single");
|
|
4397
|
+
await postHelpCard(msg, "single", false, project);
|
|
4004
4398
|
return;
|
|
4005
4399
|
}
|
|
4006
4400
|
if (cmd === "settings") {
|
|
@@ -4016,7 +4410,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4016
4410
|
}
|
|
4017
4411
|
if (msg.threadId) {
|
|
4018
4412
|
if (cmd === "help") {
|
|
4019
|
-
await postHelpCard(msg, "topic", true);
|
|
4413
|
+
await postHelpCard(msg, "topic", true, project);
|
|
4020
4414
|
return;
|
|
4021
4415
|
}
|
|
4022
4416
|
if (cmd === "model") {
|
|
@@ -4027,7 +4421,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4027
4421
|
return;
|
|
4028
4422
|
}
|
|
4029
4423
|
if (cmd === "help") {
|
|
4030
|
-
await postHelpCard(msg, "main");
|
|
4424
|
+
await postHelpCard(msg, "main", false, project);
|
|
4031
4425
|
return;
|
|
4032
4426
|
}
|
|
4033
4427
|
if (cmd === "resume") {
|
|
@@ -4050,7 +4444,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4050
4444
|
return name === "resume" || name === "model" || name === "settings" || name === "help" ? name : null;
|
|
4051
4445
|
}
|
|
4052
4446
|
function shouldRespondWithoutMention(project, msg) {
|
|
4053
|
-
if (!(project.noMention ??
|
|
4447
|
+
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
4054
4448
|
if (msg.mentionAll || msg.mentions.some((m) => !m.isBot)) return false;
|
|
4055
4449
|
if ((project.kind ?? "multi") === "single") return true;
|
|
4056
4450
|
return Boolean(msg.threadId) || parseCommand(msg.content.trim()) !== null;
|
|
@@ -4247,9 +4641,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4247
4641
|
log.info("card", "model", { threadId: sessionKey, model: state.model, effort: state.effort });
|
|
4248
4642
|
});
|
|
4249
4643
|
}
|
|
4250
|
-
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;
|
|
4251
4646
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
4252
|
-
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope), msg.messageId, inThread).catch(
|
|
4647
|
+
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention), msg.messageId, inThread).catch(
|
|
4253
4648
|
(err) => log.fail("card", err, { cmd: "help", scope })
|
|
4254
4649
|
);
|
|
4255
4650
|
log.info("card", "help", { scope });
|
|
@@ -4387,6 +4782,32 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4387
4782
|
(e) => log.fail("console", e, { phase: "new-project-result" })
|
|
4388
4783
|
);
|
|
4389
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
|
+
})();
|
|
4390
4811
|
}).on(DM.projects, ({ evt }) => {
|
|
4391
4812
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4392
4813
|
patch(evt, renderProjectList);
|
|
@@ -4399,6 +4820,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4399
4820
|
const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
|
|
4400
4821
|
const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
|
|
4401
4822
|
const missingScopes = scopeCheck?.missingScopes;
|
|
4823
|
+
const missingJoinScopes = scopeCheck?.missingJoinScopes;
|
|
4402
4824
|
const info = {
|
|
4403
4825
|
codexOk: await backend.isAvailable().catch(() => false),
|
|
4404
4826
|
codexVer: codexBin ? codexVersion(codexBin) : null,
|
|
@@ -4415,7 +4837,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4415
4837
|
app.id,
|
|
4416
4838
|
app.tenant,
|
|
4417
4839
|
missingScopes && missingScopes.length ? missingScopes : void 0
|
|
4418
|
-
)
|
|
4840
|
+
),
|
|
4841
|
+
missingJoinScopes,
|
|
4842
|
+
// 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
|
|
4843
|
+
joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
|
|
4419
4844
|
};
|
|
4420
4845
|
await sendManagedCard(channel, evt.chatId, buildDoctorCard(info), evt.messageId).catch(
|
|
4421
4846
|
(err) => log.fail("console", err, { cmd: "doctor" })
|
|
@@ -4476,7 +4901,8 @@ SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8B
|
|
|
4476
4901
|
}).on(DM.rmConfirm, async ({ evt, value }) => {
|
|
4477
4902
|
const name = typeof value.n === "string" ? value.n : void 0;
|
|
4478
4903
|
if (!dmAdmin(evt.operator?.openId) || !name) return;
|
|
4479
|
-
await
|
|
4904
|
+
const proj = (await listProjects()).find((p) => p.name === name);
|
|
4905
|
+
await patch(evt, buildRmConfirmCard(name, proj?.origin));
|
|
4480
4906
|
}).on(DM.rmCancel, ({ evt }) => {
|
|
4481
4907
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4482
4908
|
patch(evt, renderProjectList);
|
|
@@ -4486,15 +4912,25 @@ SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8B
|
|
|
4486
4912
|
if (!dmAdmin(op) || !name) return;
|
|
4487
4913
|
patch(evt, async () => {
|
|
4488
4914
|
const removed = await removeProject(name);
|
|
4489
|
-
let
|
|
4490
|
-
if (removed
|
|
4491
|
-
|
|
4492
|
-
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" });
|
|
4493
4919
|
return false;
|
|
4494
|
-
});
|
|
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";
|
|
4495
4933
|
}
|
|
4496
|
-
log.info("console", "rm", { name, transferred });
|
|
4497
|
-
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";
|
|
4498
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
|
|
4499
4935
|
${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
4500
4936
|
return renderProjectList();
|
|
@@ -4686,9 +5122,22 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
4686
5122
|
const finalMsgId = cardMsgId;
|
|
4687
5123
|
await adoptThreadId(finalMsgId);
|
|
4688
5124
|
rc.cardKey = finalMsgId;
|
|
5125
|
+
const answerText = finalMessageText(rc.rs);
|
|
5126
|
+
const { fences } = extractCardFences(answerText);
|
|
5127
|
+
const imgSources = imageSources(answerText);
|
|
5128
|
+
if (imgSources.length > 0) {
|
|
5129
|
+
rc.images = await uploadOutboundImages(channel, imgSources, opts.cwd ?? fallbackCwd);
|
|
5130
|
+
}
|
|
4689
5131
|
await stream2.updateCard(channel, buildRunCard(rc));
|
|
4690
5132
|
runsByCard.delete(cardMsgId);
|
|
4691
5133
|
promoteCard(finalMsgId, rc);
|
|
5134
|
+
for (const fence of fences) {
|
|
5135
|
+
try {
|
|
5136
|
+
await sendManagedCard(channel, opts.chatId, buildCleanCard(fence, rc.images), finalMsgId, !opts.flat);
|
|
5137
|
+
} catch (err) {
|
|
5138
|
+
log.fail("card", err, { phase: "clean-card" });
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
4692
5141
|
if (topicThreadId) await patchSession(topicThreadId, { updatedAt: Date.now() });
|
|
4693
5142
|
replyTo = finalMsgId;
|
|
4694
5143
|
replyInThread = !opts.flat;
|
|
@@ -4817,13 +5266,57 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
4817
5266
|
});
|
|
4818
5267
|
return fresh;
|
|
4819
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
|
+
}
|
|
4820
5313
|
async function shutdown() {
|
|
4821
5314
|
const live = [...sessions.values()];
|
|
4822
5315
|
sessions.clear();
|
|
4823
5316
|
await Promise.allSettled(live.map((t) => t.close()));
|
|
4824
5317
|
log.info("bridge", "shutdown", { closed: live.length });
|
|
4825
5318
|
}
|
|
4826
|
-
return { onMessage, onComment, dispatcher, shutdown };
|
|
5319
|
+
return { onMessage, onComment, onBotAddedToChat, onBotRemovedFromChat, dispatcher, shutdown };
|
|
4827
5320
|
}
|
|
4828
5321
|
async function getThreadId(channel, messageId) {
|
|
4829
5322
|
try {
|
|
@@ -4861,6 +5354,24 @@ async function startBridge(opts) {
|
|
|
4861
5354
|
channel.on("message", orchestrator.onMessage);
|
|
4862
5355
|
channel.on("cardAction", orchestrator.dispatcher.handle);
|
|
4863
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
|
+
}
|
|
4864
5375
|
channel.on("reject", (evt) => log.info("intake", "reject", { reason: evt.reason, msgId: evt.messageId }));
|
|
4865
5376
|
channel.on("error", (err) => log.fail("ws", err));
|
|
4866
5377
|
channel.on("reconnecting", () => log.info("ws", "reconnecting"));
|
|
@@ -5072,6 +5583,7 @@ async function runBotInit(name) {
|
|
|
5072
5583
|
}
|
|
5073
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");
|
|
5074
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");
|
|
5075
5587
|
console.log(" 2) \u521B\u5EFA\u5E76\u53D1\u5E03\u5E94\u7528\u7248\u672C");
|
|
5076
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");
|
|
5077
5589
|
}
|
|
@@ -5171,15 +5683,15 @@ async function secretsRemove(id) {
|
|
|
5171
5683
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
5172
5684
|
}
|
|
5173
5685
|
function readStdin() {
|
|
5174
|
-
return new Promise((
|
|
5686
|
+
return new Promise((resolve6) => {
|
|
5175
5687
|
let data = "";
|
|
5176
5688
|
if (process.stdin.isTTY) {
|
|
5177
|
-
|
|
5689
|
+
resolve6("");
|
|
5178
5690
|
return;
|
|
5179
5691
|
}
|
|
5180
5692
|
process.stdin.setEncoding("utf8");
|
|
5181
5693
|
process.stdin.on("data", (c) => data += c);
|
|
5182
|
-
process.stdin.on("end", () =>
|
|
5694
|
+
process.stdin.on("end", () => resolve6(data));
|
|
5183
5695
|
});
|
|
5184
5696
|
}
|
|
5185
5697
|
|