@modelzen/feishu-codex-bridge 0.1.8 → 0.2.1-win
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 +677 -203
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,6 @@ function bridgeVersion() {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// src/cli/commands/doctor.ts
|
|
18
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
19
18
|
import { existsSync as existsSync3 } from "fs";
|
|
20
19
|
import { homedir as homedir2 } from "os";
|
|
21
20
|
import { join as join4 } from "path";
|
|
@@ -269,27 +268,56 @@ async function moveIfExists(src, dest) {
|
|
|
269
268
|
}
|
|
270
269
|
|
|
271
270
|
// src/agent/codex-appserver/locate.ts
|
|
272
|
-
import { execFileSync } from "child_process";
|
|
273
271
|
import { existsSync as existsSync2 } from "fs";
|
|
274
|
-
import { join as join3 } from "path";
|
|
272
|
+
import { extname, join as join3 } from "path";
|
|
273
|
+
|
|
274
|
+
// src/platform/spawn.ts
|
|
275
|
+
import crossSpawn from "cross-spawn";
|
|
276
|
+
function spawnProcess(command, args = [], options = {}) {
|
|
277
|
+
return crossSpawn(command, [...args], options);
|
|
278
|
+
}
|
|
279
|
+
function spawnProcessSync(command, args = [], options = {}) {
|
|
280
|
+
return crossSpawn.sync(command, [...args], options);
|
|
281
|
+
}
|
|
282
|
+
function mergeProcessEnv(base = process.env, overrides = {}) {
|
|
283
|
+
const out = { ...base };
|
|
284
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
285
|
+
for (const existing of Object.keys(out)) {
|
|
286
|
+
if (existing.toLowerCase() === key.toLowerCase()) delete out[existing];
|
|
287
|
+
}
|
|
288
|
+
if (value !== void 0) out[key] = value;
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/agent/codex-appserver/locate.ts
|
|
294
|
+
var IS_WIN = process.platform === "win32";
|
|
275
295
|
function resolveCodexBin() {
|
|
276
296
|
const env = process.env.CODEX_BIN;
|
|
277
297
|
if (env && existsSync2(env)) return env;
|
|
278
298
|
const onPath = which("codex");
|
|
279
299
|
if (onPath) return onPath;
|
|
280
|
-
const
|
|
281
|
-
|
|
300
|
+
for (const cand of execCandidates(paths.codexCliBinDir, "codex")) {
|
|
301
|
+
if (existsSync2(cand)) return cand;
|
|
302
|
+
}
|
|
282
303
|
const appBundle = "/Applications/Codex.app/Contents/Resources/codex";
|
|
283
304
|
if (process.platform === "darwin" && existsSync2(appBundle)) return appBundle;
|
|
284
305
|
return null;
|
|
285
306
|
}
|
|
307
|
+
function execCandidates(dir, base) {
|
|
308
|
+
const exact = join3(dir, base);
|
|
309
|
+
if (!IS_WIN || extname(base)) return [exact];
|
|
310
|
+
const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
311
|
+
return [exact, ...exts.map((e) => join3(dir, base + e.toLowerCase()))];
|
|
312
|
+
}
|
|
286
313
|
function which(cmd) {
|
|
287
314
|
try {
|
|
288
|
-
const
|
|
315
|
+
const res = spawnProcessSync(IS_WIN ? "where" : "which", [cmd], {
|
|
289
316
|
encoding: "utf8",
|
|
290
317
|
stdio: ["ignore", "pipe", "ignore"]
|
|
291
318
|
});
|
|
292
|
-
|
|
319
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
320
|
+
const first = res.stdout.split("\n").map((l) => l.trim()).find(Boolean);
|
|
293
321
|
return first && existsSync2(first) ? first : null;
|
|
294
322
|
} catch {
|
|
295
323
|
return null;
|
|
@@ -297,7 +325,9 @@ function which(cmd) {
|
|
|
297
325
|
}
|
|
298
326
|
function codexVersion(bin) {
|
|
299
327
|
try {
|
|
300
|
-
|
|
328
|
+
const res = spawnProcessSync(bin, ["--version"], { encoding: "utf8" });
|
|
329
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
330
|
+
return res.stdout.trim();
|
|
301
331
|
} catch {
|
|
302
332
|
return null;
|
|
303
333
|
}
|
|
@@ -355,7 +385,9 @@ ${failed === 0 ? "\u5168\u90E8\u901A\u8FC7 \u2713" : `${failed} \u9879\u9700\u59
|
|
|
355
385
|
}
|
|
356
386
|
function tryExec(cmd, args) {
|
|
357
387
|
try {
|
|
358
|
-
|
|
388
|
+
const res = spawnProcessSync(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
389
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
390
|
+
return res.stdout.trim();
|
|
359
391
|
} catch {
|
|
360
392
|
return null;
|
|
361
393
|
}
|
|
@@ -538,7 +570,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
538
570
|
const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
539
571
|
const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
|
|
540
572
|
const providerName = ref.provider ?? DEFAULT_PROVIDER;
|
|
541
|
-
return new Promise((
|
|
573
|
+
return new Promise((resolve7, reject) => {
|
|
542
574
|
const env = {};
|
|
543
575
|
if (pc.passEnv) for (const k of pc.passEnv) {
|
|
544
576
|
const v = process.env[k];
|
|
@@ -583,7 +615,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
583
615
|
try {
|
|
584
616
|
const parsed = JSON.parse(stdout);
|
|
585
617
|
const value = parsed.values?.[ref.id];
|
|
586
|
-
if (typeof value === "string") return
|
|
618
|
+
if (typeof value === "string") return resolve7(value);
|
|
587
619
|
const err = parsed.errors?.[ref.id]?.message;
|
|
588
620
|
reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
|
|
589
621
|
} catch (err) {
|
|
@@ -679,7 +711,11 @@ var COMMENT_SCOPES = [
|
|
|
679
711
|
"docs:document.comment:create",
|
|
680
712
|
"wiki:wiki:readonly"
|
|
681
713
|
];
|
|
682
|
-
var
|
|
714
|
+
var JOIN_GROUP_SCOPES = [
|
|
715
|
+
"im:chat:readonly",
|
|
716
|
+
"im:chat.members:write_only"
|
|
717
|
+
];
|
|
718
|
+
var GRANT_SCOPES = [...REQUIRED_SCOPES, ...COMMENT_SCOPES, ...JOIN_GROUP_SCOPES];
|
|
683
719
|
var SCOPE_LABELS = {
|
|
684
720
|
"im:message.group_at_msg:readonly": "\u63A5\u6536\u7FA4\u91CC @\u673A\u5668\u4EBA \u7684\u6D88\u606F",
|
|
685
721
|
"im:message.group_msg": "\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\uFF08\u514D@\uFF09",
|
|
@@ -695,6 +731,8 @@ var SCOPE_LABELS = {
|
|
|
695
731
|
"im:chat.announcement:write_only": "\u7F16\u8F91\u7FA4\u516C\u544A",
|
|
696
732
|
"im:chat.top_notice:write_only": "\u7F6E\u9876\u7FA4\u516C\u544A\u6A2A\u5E45",
|
|
697
733
|
"im:chat.tabs:write_only": "\u6DFB\u52A0\u7FA4\u6807\u7B7E\u9875",
|
|
734
|
+
"im:chat:readonly": "\u8BFB\u53D6\u7FA4\u4FE1\u606F\uFF08\u7FA4\u540D/\u7FA4\u4E3B\uFF0C\u52A0\u5165\u5B58\u91CF\u7FA4\u7528\uFF09",
|
|
735
|
+
"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
736
|
"cardkit:card:write": "\u4EA4\u4E92\u6309\u94AE\u5361\u7247",
|
|
699
737
|
"docs:document.comment:read": "\u8BFB\u53D6\u6587\u6863\u8BC4\u8BBA",
|
|
700
738
|
"docs:document.comment:create": "\u53D1\u8868\u6587\u6863\u8BC4\u8BBA\u56DE\u590D",
|
|
@@ -746,8 +784,15 @@ async function validateAppCredentials(appId, appSecret, tenant) {
|
|
|
746
784
|
}
|
|
747
785
|
const token = data.tenant_access_token;
|
|
748
786
|
const info = await fetchBotInfo(base, token).catch(() => void 0);
|
|
749
|
-
const
|
|
750
|
-
|
|
787
|
+
const granted = await fetchGrantedScopes(base, token).catch(() => void 0);
|
|
788
|
+
const missing = (list) => granted ? list.filter((s) => !granted.has(s)) : void 0;
|
|
789
|
+
return {
|
|
790
|
+
ok: true,
|
|
791
|
+
botName: info?.bot?.app_name,
|
|
792
|
+
botOpenId: info?.bot?.open_id,
|
|
793
|
+
missingScopes: missing(REQUIRED_SCOPES),
|
|
794
|
+
missingJoinScopes: missing(JOIN_GROUP_SCOPES)
|
|
795
|
+
};
|
|
751
796
|
}
|
|
752
797
|
async function fetchBotInfo(base, token) {
|
|
753
798
|
const resp = await fetch(`${base}/open-apis/bot/v3/info`, {
|
|
@@ -756,15 +801,14 @@ async function fetchBotInfo(base, token) {
|
|
|
756
801
|
if (!resp.ok) return void 0;
|
|
757
802
|
return await resp.json();
|
|
758
803
|
}
|
|
759
|
-
async function
|
|
804
|
+
async function fetchGrantedScopes(base, token) {
|
|
760
805
|
const resp = await fetch(`${base}/open-apis/application/v6/scopes`, {
|
|
761
806
|
headers: { Authorization: `Bearer ${token}` }
|
|
762
807
|
});
|
|
763
808
|
if (!resp.ok) return void 0;
|
|
764
809
|
const body = await resp.json();
|
|
765
810
|
if (!body.data?.scopes) return void 0;
|
|
766
|
-
|
|
767
|
-
return REQUIRED_SCOPES.filter((s) => !granted.has(s));
|
|
811
|
+
return new Set(body.data.scopes.filter((s) => s.grant_status === 1).map((s) => s.scope_name));
|
|
768
812
|
}
|
|
769
813
|
|
|
770
814
|
// src/utils/open-url.ts
|
|
@@ -1071,6 +1115,10 @@ async function confirmReadyForDaemon(result) {
|
|
|
1071
1115
|
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
1116
|
console.log(" drive.notice.comment_add_v1\uFF08\u4E91\u6587\u6863\u65B0\u589E\u8BC4\u8BBA\uFF09");
|
|
1073
1117
|
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");
|
|
1118
|
+
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");
|
|
1119
|
+
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");
|
|
1120
|
+
console.log(" im.chat.member.bot.deleted_v1\uFF08\u673A\u5668\u4EBA\u88AB\u79FB\u51FA\u7FA4 \u2192 \u81EA\u52A8\u89E3\u7ED1\u9879\u76EE\uFF09");
|
|
1121
|
+
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
1122
|
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
1123
|
console.log(" card.action.trigger\uFF08\u5361\u7247\u56DE\u4F20\u4EA4\u4E92\uFF09");
|
|
1076
1124
|
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");
|
|
@@ -1115,7 +1163,6 @@ ${rule}`);
|
|
|
1115
1163
|
import { createLarkChannel, Domain } from "@larksuiteoapi/node-sdk";
|
|
1116
1164
|
|
|
1117
1165
|
// src/agent/codex-appserver/app-server-client.ts
|
|
1118
|
-
import { spawn as spawn3 } from "child_process";
|
|
1119
1166
|
var AsyncQueue = class {
|
|
1120
1167
|
items = [];
|
|
1121
1168
|
waiters = [];
|
|
@@ -1136,7 +1183,7 @@ var AsyncQueue = class {
|
|
|
1136
1183
|
continue;
|
|
1137
1184
|
}
|
|
1138
1185
|
if (this.closed) return;
|
|
1139
|
-
const next = await new Promise((
|
|
1186
|
+
const next = await new Promise((resolve7) => this.waiters.push(resolve7));
|
|
1140
1187
|
if (next.done) return;
|
|
1141
1188
|
yield next.value;
|
|
1142
1189
|
}
|
|
@@ -1158,9 +1205,9 @@ var AppServerClient = class {
|
|
|
1158
1205
|
}
|
|
1159
1206
|
/** spawn + initialize handshake. Throws if spawn/handshake fails. */
|
|
1160
1207
|
async connect() {
|
|
1161
|
-
const child =
|
|
1208
|
+
const child = spawnProcess(this.opts.bin, ["app-server", "--listen", "stdio://"], {
|
|
1162
1209
|
cwd: this.opts.cwd,
|
|
1163
|
-
env:
|
|
1210
|
+
env: mergeProcessEnv(process.env, { ...this.opts.env, FEISHU_CODEX_BRIDGE: "1" }),
|
|
1164
1211
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1165
1212
|
});
|
|
1166
1213
|
this.child = child;
|
|
@@ -1187,8 +1234,8 @@ var AppServerClient = class {
|
|
|
1187
1234
|
const id = ++this.nextId;
|
|
1188
1235
|
const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
|
|
1189
1236
|
`;
|
|
1190
|
-
return new Promise((
|
|
1191
|
-
this.pending.set(id, { resolve:
|
|
1237
|
+
return new Promise((resolve7, reject) => {
|
|
1238
|
+
this.pending.set(id, { resolve: resolve7, reject });
|
|
1192
1239
|
this.child.stdin.write(payload, (err) => {
|
|
1193
1240
|
if (err) {
|
|
1194
1241
|
this.pending.delete(id);
|
|
@@ -1211,15 +1258,36 @@ var AppServerClient = class {
|
|
|
1211
1258
|
this.closed = true;
|
|
1212
1259
|
const child = this.child;
|
|
1213
1260
|
if (!child || child.exitCode !== null) return;
|
|
1261
|
+
if (process.platform === "win32" && child.pid) {
|
|
1262
|
+
await new Promise((resolve7) => {
|
|
1263
|
+
let settled = false;
|
|
1264
|
+
const done = () => {
|
|
1265
|
+
if (settled) return;
|
|
1266
|
+
settled = true;
|
|
1267
|
+
clearTimeout(t);
|
|
1268
|
+
resolve7();
|
|
1269
|
+
};
|
|
1270
|
+
const t = setTimeout(done, graceMs);
|
|
1271
|
+
child.once("exit", done);
|
|
1272
|
+
spawnProcess("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
1273
|
+
"error",
|
|
1274
|
+
() => {
|
|
1275
|
+
child.kill();
|
|
1276
|
+
done();
|
|
1277
|
+
}
|
|
1278
|
+
);
|
|
1279
|
+
});
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1214
1282
|
child.kill("SIGTERM");
|
|
1215
|
-
await new Promise((
|
|
1283
|
+
await new Promise((resolve7) => {
|
|
1216
1284
|
const t = setTimeout(() => {
|
|
1217
1285
|
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1218
|
-
|
|
1286
|
+
resolve7();
|
|
1219
1287
|
}, graceMs);
|
|
1220
1288
|
child.once("exit", () => {
|
|
1221
1289
|
clearTimeout(t);
|
|
1222
|
-
|
|
1290
|
+
resolve7();
|
|
1223
1291
|
});
|
|
1224
1292
|
});
|
|
1225
1293
|
}
|
|
@@ -1352,12 +1420,12 @@ var BRIDGE_DEVELOPER_INSTRUCTIONS = [
|
|
|
1352
1420
|
].join("\n");
|
|
1353
1421
|
var READ_HISTORY_TIMEOUT_MS = 2e4;
|
|
1354
1422
|
function withDeadline(p, ms, label) {
|
|
1355
|
-
return new Promise((
|
|
1423
|
+
return new Promise((resolve7, reject) => {
|
|
1356
1424
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
1357
1425
|
p.then(
|
|
1358
1426
|
(v) => {
|
|
1359
1427
|
clearTimeout(t);
|
|
1360
|
-
|
|
1428
|
+
resolve7(v);
|
|
1361
1429
|
},
|
|
1362
1430
|
(e) => {
|
|
1363
1431
|
clearTimeout(t);
|
|
@@ -1397,11 +1465,11 @@ var CodexThread = class {
|
|
|
1397
1465
|
if (self.model) params.model = self.model;
|
|
1398
1466
|
if (self.effort) params.effort = self.effort;
|
|
1399
1467
|
let startError;
|
|
1400
|
-
const startFailed = new Promise((
|
|
1468
|
+
const startFailed = new Promise((resolve7) => {
|
|
1401
1469
|
self.client.request("turn/start", params).then(void 0, (err) => {
|
|
1402
1470
|
startError = err instanceof Error ? err : new Error(String(err));
|
|
1403
1471
|
log.fail("agent", startError, { phase: "turn/start" });
|
|
1404
|
-
|
|
1472
|
+
resolve7("start-failed");
|
|
1405
1473
|
});
|
|
1406
1474
|
});
|
|
1407
1475
|
const stream2 = self.client.stream()[Symbol.asyncIterator]();
|
|
@@ -1707,7 +1775,7 @@ function stampRenderToken(card2) {
|
|
|
1707
1775
|
};
|
|
1708
1776
|
visit(card2);
|
|
1709
1777
|
}
|
|
1710
|
-
async function sendManagedCard(channel,
|
|
1778
|
+
async function sendManagedCard(channel, to, card2, replyTo, replyInThread = false, receiveIdType = "chat_id") {
|
|
1711
1779
|
stampRenderToken(card2);
|
|
1712
1780
|
const created = await channel.rawClient.cardkit.v1.card.create({
|
|
1713
1781
|
data: { type: "card_json", data: JSON.stringify(card2) }
|
|
@@ -1726,8 +1794,8 @@ async function sendManagedCard(channel, chatId, card2, replyTo, replyInThread =
|
|
|
1726
1794
|
messageId = sent.data?.message_id;
|
|
1727
1795
|
} else {
|
|
1728
1796
|
const sent = await channel.rawClient.im.v1.message.create({
|
|
1729
|
-
params: { receive_id_type:
|
|
1730
|
-
data: { receive_id:
|
|
1797
|
+
params: { receive_id_type: receiveIdType },
|
|
1798
|
+
data: { receive_id: to, msg_type: "interactive", content }
|
|
1731
1799
|
});
|
|
1732
1800
|
messageId = sent.data?.message_id;
|
|
1733
1801
|
}
|
|
@@ -2183,14 +2251,20 @@ function pickerTime(unixSeconds) {
|
|
|
2183
2251
|
const md2 = `${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
|
|
2184
2252
|
return d.getFullYear() === now.getFullYear() ? `${md2} ${hm}` : `${d.getFullYear()}-${md2} ${hm}`;
|
|
2185
2253
|
}
|
|
2186
|
-
function
|
|
2254
|
+
function talkLine(noMention, tail) {
|
|
2255
|
+
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`;
|
|
2256
|
+
}
|
|
2257
|
+
function buildHelpCard(scope, noMention = true) {
|
|
2187
2258
|
const elements = [];
|
|
2188
2259
|
if (scope === "single") {
|
|
2189
2260
|
elements.push(
|
|
2190
2261
|
md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"),
|
|
2191
2262
|
hr(),
|
|
2192
2263
|
md(
|
|
2193
|
-
"\
|
|
2264
|
+
`${talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406")}
|
|
2265
|
+
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2266
|
+
\xB7 \`/settings\` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09
|
|
2267
|
+
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2194
2268
|
)
|
|
2195
2269
|
);
|
|
2196
2270
|
} else if (scope === "topic") {
|
|
@@ -2198,7 +2272,9 @@ function buildHelpCard(scope) {
|
|
|
2198
2272
|
md("\u{1F9F5} **\u8BDD\u9898\u5185** \u2014 \u6BCF\u4E2A\u8BDD\u9898\u662F\u4E00\u4E2A\u72EC\u7ACB\u4F1A\u8BDD\u3002"),
|
|
2199
2273
|
hr(),
|
|
2200
2274
|
md(
|
|
2201
|
-
"\
|
|
2275
|
+
`${talkLine(noMention, "\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD")}
|
|
2276
|
+
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2277
|
+
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2202
2278
|
),
|
|
2203
2279
|
note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
|
|
2204
2280
|
);
|
|
@@ -2213,7 +2289,7 @@ function buildHelpCard(scope) {
|
|
|
2213
2289
|
}
|
|
2214
2290
|
return card(elements, { header: { title: "\u{1F916} \u53EF\u7528\u547D\u4EE4", template: "blue" }, summary: "\u53EF\u7528\u547D\u4EE4" });
|
|
2215
2291
|
}
|
|
2216
|
-
function buildWelcomeCard(kind, docUrl) {
|
|
2292
|
+
function buildWelcomeCard(kind, docUrl, noMention = true) {
|
|
2217
2293
|
const elements = [
|
|
2218
2294
|
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
2295
|
hr()
|
|
@@ -2222,7 +2298,10 @@ function buildWelcomeCard(kind, docUrl) {
|
|
|
2222
2298
|
elements.push(
|
|
2223
2299
|
md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4**\uFF08\u6574\u7FA4\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\uFF09"),
|
|
2224
2300
|
md(
|
|
2225
|
-
"\
|
|
2301
|
+
`${talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406")}
|
|
2302
|
+
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2303
|
+
\xB7 \`/settings\` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09
|
|
2304
|
+
\xB7 \`/help\` \u2192 \u547D\u4EE4\u901F\u67E5\u5361`
|
|
2226
2305
|
)
|
|
2227
2306
|
);
|
|
2228
2307
|
} else {
|
|
@@ -2761,7 +2840,7 @@ var RunCardStream = class {
|
|
|
2761
2840
|
|
|
2762
2841
|
// src/card/outbound-images.ts
|
|
2763
2842
|
import { readFile as readFile5, stat as stat2 } from "fs/promises";
|
|
2764
|
-
import { extname, isAbsolute, resolve as resolve2, sep } from "path";
|
|
2843
|
+
import { extname as extname2, isAbsolute, resolve as resolve2, sep } from "path";
|
|
2765
2844
|
var MAX_IMAGES = 9;
|
|
2766
2845
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
2767
2846
|
var DOWNLOAD_TIMEOUT_MS = 1e4;
|
|
@@ -2827,7 +2906,7 @@ async function loadLocal(src, cwd) {
|
|
|
2827
2906
|
log.warn("outbound", "image-outside-cwd", { src: src.slice(0, 80) });
|
|
2828
2907
|
return { cacheKey: `local:${abs}` };
|
|
2829
2908
|
}
|
|
2830
|
-
const ext =
|
|
2909
|
+
const ext = extname2(abs).slice(1).toLowerCase();
|
|
2831
2910
|
if (!ALLOWED_EXT.has(ext)) {
|
|
2832
2911
|
log.warn("outbound", "image-ext", { ext, src: src.slice(0, 80) });
|
|
2833
2912
|
return { cacheKey: `local:${abs}` };
|
|
@@ -2895,6 +2974,71 @@ async function uploadBuffer(channel, buffer) {
|
|
|
2895
2974
|
return key;
|
|
2896
2975
|
}
|
|
2897
2976
|
|
|
2977
|
+
// src/project/registry.ts
|
|
2978
|
+
import { mkdir as mkdir4, readFile as readFile6, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
2979
|
+
import { dirname as dirname5 } from "path";
|
|
2980
|
+
function defaultNoMention(p) {
|
|
2981
|
+
return !((p.origin ?? "created") === "joined" && (p.kind ?? "multi") === "single");
|
|
2982
|
+
}
|
|
2983
|
+
var FILE_VERSION2 = 1;
|
|
2984
|
+
async function read() {
|
|
2985
|
+
try {
|
|
2986
|
+
const text = await readFile6(paths.projectsFile, "utf8");
|
|
2987
|
+
const parsed = JSON.parse(text);
|
|
2988
|
+
return Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
2989
|
+
} catch (err) {
|
|
2990
|
+
if (err.code === "ENOENT") return [];
|
|
2991
|
+
throw err;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
async function write(projects) {
|
|
2995
|
+
await mkdir4(dirname5(paths.projectsFile), { recursive: true });
|
|
2996
|
+
const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
|
|
2997
|
+
const body = { version: FILE_VERSION2, projects };
|
|
2998
|
+
await writeFile4(tmp, `${JSON.stringify(body, null, 2)}
|
|
2999
|
+
`, "utf8");
|
|
3000
|
+
await rename4(tmp, paths.projectsFile);
|
|
3001
|
+
}
|
|
3002
|
+
async function listProjects() {
|
|
3003
|
+
return read();
|
|
3004
|
+
}
|
|
3005
|
+
async function getProjectByChatId(chatId) {
|
|
3006
|
+
return (await read()).find((p) => p.chatId === chatId);
|
|
3007
|
+
}
|
|
3008
|
+
async function getProjectByName(name) {
|
|
3009
|
+
return (await read()).find((p) => p.name === name);
|
|
3010
|
+
}
|
|
3011
|
+
async function addProject(p) {
|
|
3012
|
+
const projects = await read();
|
|
3013
|
+
if (projects.some((x) => x.name === p.name)) {
|
|
3014
|
+
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
3015
|
+
}
|
|
3016
|
+
if (p.chatId) {
|
|
3017
|
+
const bound = projects.find((x) => x.chatId === p.chatId);
|
|
3018
|
+
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
3019
|
+
}
|
|
3020
|
+
projects.push(p);
|
|
3021
|
+
await write(projects);
|
|
3022
|
+
}
|
|
3023
|
+
async function updateProject(name, patch) {
|
|
3024
|
+
const projects = await read();
|
|
3025
|
+
const p = projects.find((x) => x.name === name);
|
|
3026
|
+
if (!p) return;
|
|
3027
|
+
const target = p;
|
|
3028
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
3029
|
+
if (v !== void 0) target[k] = v;
|
|
3030
|
+
}
|
|
3031
|
+
await write(projects);
|
|
3032
|
+
}
|
|
3033
|
+
async function removeProject(name) {
|
|
3034
|
+
const projects = await read();
|
|
3035
|
+
const idx = projects.findIndex((p) => p.name === name);
|
|
3036
|
+
if (idx === -1) return void 0;
|
|
3037
|
+
const [removed] = projects.splice(idx, 1);
|
|
3038
|
+
await write(projects);
|
|
3039
|
+
return removed;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
2898
3042
|
// src/card/dm-cards.ts
|
|
2899
3043
|
function openChatUrl(chatId) {
|
|
2900
3044
|
return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
|
|
@@ -2904,6 +3048,7 @@ var DM = {
|
|
|
2904
3048
|
menu: "dm.menu",
|
|
2905
3049
|
newProject: "dm.newProject",
|
|
2906
3050
|
newProjectSubmit: "dm.newProject.submit",
|
|
3051
|
+
joinGroupSubmit: "dm.joinGroup.submit",
|
|
2907
3052
|
projects: "dm.projects",
|
|
2908
3053
|
settings: "dm.settings",
|
|
2909
3054
|
doctor: "dm.doctor",
|
|
@@ -3057,6 +3202,27 @@ ${i.missingScopes.map((s) => `\xB7 ${labelScope(s)}`).join("\n")}`),
|
|
|
3057
3202
|
actions([linkButton("\u{1F511} \u4E00\u952E\u53BB\u5F00\u901A\u8FD9\u4E9B\u6743\u9650", i.scopeGrantUrl)])
|
|
3058
3203
|
];
|
|
3059
3204
|
}
|
|
3205
|
+
function joinFeatureDiagnosis(i) {
|
|
3206
|
+
const out = [md("**\u52A0\u5165\u5B58\u91CF\u7FA4\uFF08\u53EF\u9009\uFF09**")];
|
|
3207
|
+
if (i.missingJoinScopes === void 0) {
|
|
3208
|
+
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)]));
|
|
3209
|
+
} else if (i.missingJoinScopes.length === 0) {
|
|
3210
|
+
out.push(md("- \u6743\u9650\uFF1A\u2705 \u5DF2\u5F00\u901A\uFF08`im:chat:readonly` / `im:chat.members:write_only`\uFF09"));
|
|
3211
|
+
} else {
|
|
3212
|
+
out.push(
|
|
3213
|
+
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`),
|
|
3214
|
+
note(`\u5F85\u5F00\u901A\uFF1A
|
|
3215
|
+
${i.missingJoinScopes.map((s) => `\xB7 ${labelScope(s)}`).join("\n")}`),
|
|
3216
|
+
actions([linkButton("\u{1F511} \u4E00\u952E\u5F00\u901A\u8FD9\u4E24\u9879\u6743\u9650", i.joinScopeGrantUrl)])
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
out.push(
|
|
3220
|
+
note(
|
|
3221
|
+
"\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"
|
|
3222
|
+
)
|
|
3223
|
+
);
|
|
3224
|
+
return out;
|
|
3225
|
+
}
|
|
3060
3226
|
function codexDiagnosePrompt(i) {
|
|
3061
3227
|
return [
|
|
3062
3228
|
"\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 +3265,8 @@ function buildDoctorCard(i) {
|
|
|
3099
3265
|
...scopeDiagnosis(i),
|
|
3100
3266
|
note(`bridge v${i.bridgeVer}\u3000\xB7\u3000Node ${i.node}\u3000\xB7\u3000${i.platform}`),
|
|
3101
3267
|
hr(),
|
|
3268
|
+
...joinFeatureDiagnosis(i),
|
|
3269
|
+
hr(),
|
|
3102
3270
|
md("**\u65E5\u5FD7\u8DEF\u5F84**"),
|
|
3103
3271
|
note(`\u540E\u53F0\u5B88\u62A4\u8F93\u51FA\uFF1A\`${i.logStdout}\``),
|
|
3104
3272
|
note(`\u540E\u53F0\u5B88\u62A4\u9519\u8BEF\uFF1A\`${i.logStderr}\``),
|
|
@@ -3132,14 +3300,35 @@ function buildNewProjectFormCard(opts = {}) {
|
|
|
3132
3300
|
);
|
|
3133
3301
|
return card(elements, { header: { title: "\u2795 \u65B0\u5EFA\u9879\u76EE", template: "turquoise" } });
|
|
3134
3302
|
}
|
|
3303
|
+
function buildJoinGroupFormCard(opts) {
|
|
3304
|
+
const elements = [];
|
|
3305
|
+
if (opts.error) elements.push(md(`\u274C **\u7ED1\u5B9A\u5931\u8D25**\uFF1A${opts.error}`));
|
|
3306
|
+
elements.push(
|
|
3307
|
+
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"),
|
|
3308
|
+
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"),
|
|
3309
|
+
form("join_group", [
|
|
3310
|
+
input({ name: "name", label: "\u9879\u76EE\u540D", placeholder: "my-app", value: opts.name, required: true }),
|
|
3311
|
+
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 }),
|
|
3312
|
+
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"),
|
|
3313
|
+
actions([
|
|
3314
|
+
submitButton("\u{1F465} \u7ED1\u5B9A\xB7\u591A\u8BDD\u9898\u7FA4", { a: DM.joinGroupSubmit, kind: "multi", chatId: opts.chatId }, "primary", "submit_multi"),
|
|
3315
|
+
submitButton("\u{1F4AC} \u7ED1\u5B9A\xB7\u5355\u4F1A\u8BDD\u7FA4", { a: DM.joinGroupSubmit, kind: "single", chatId: opts.chatId }, "primary", "submit_single")
|
|
3316
|
+
])
|
|
3317
|
+
])
|
|
3318
|
+
);
|
|
3319
|
+
return card(elements, { header: { title: "\u{1F517} \u7ED1\u5B9A\u5DF2\u6709\u7FA4", template: "turquoise" } });
|
|
3320
|
+
}
|
|
3135
3321
|
function buildNewProjectDoneCard(p) {
|
|
3322
|
+
const joined = (p.origin ?? "created") === "joined";
|
|
3323
|
+
const verb = joined ? "\u5DF2\u7ED1\u5B9A\u7FA4" : "\u5DF2\u521B\u5EFA\u9879\u76EE";
|
|
3324
|
+
const title = joined ? "\u{1F517} \u7ED1\u5B9A\u5DF2\u6709\u7FA4" : "\u2795 \u65B0\u5EFA\u9879\u76EE";
|
|
3136
3325
|
const elements = [
|
|
3137
|
-
md(`\u2705
|
|
3326
|
+
md(`\u2705 ${verb} **${p.name}**${p.blank ? " _(\u7A7A\u767D\u9879\u76EE)_" : ""}`),
|
|
3138
3327
|
note(`\u{1F4C2} \`${p.cwd}\` \xB7 ${kindLabel(p.kind)}`),
|
|
3139
|
-
md(p.chatId ? "\
|
|
3328
|
+
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
3329
|
];
|
|
3141
3330
|
if (p.chatId) elements.push(actions([linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId), "primary")]));
|
|
3142
|
-
return card(elements, { header: { title
|
|
3331
|
+
return card(elements, { header: { title, template: "green" } });
|
|
3143
3332
|
}
|
|
3144
3333
|
function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
|
|
3145
3334
|
if (projects.length === 0) {
|
|
@@ -3154,7 +3343,7 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3154
3343
|
elements.push(note(`\u{1F4C2} \`${p.cwd}\`${p.branch && p.branch !== "\u2014" ? ` \u{1F33F} ${p.branch}` : ""}`));
|
|
3155
3344
|
elements.push(
|
|
3156
3345
|
note(
|
|
3157
|
-
p.chatId ? `\u{1F4AC} \u7FA4\uFF1A**${p.name}** \xB7 ${kindLabel(p.kind)} \xB7 \u514D@\uFF1A${p.noMention ??
|
|
3346
|
+
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
3347
|
)
|
|
3159
3348
|
);
|
|
3160
3349
|
const sessions = (p.chatId ? sessionsByChat.get(p.chatId) : void 0) ?? [];
|
|
@@ -3177,11 +3366,12 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3177
3366
|
elements.push(actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]));
|
|
3178
3367
|
return card(elements, { header: { title: "\u{1F4C1} \u9879\u76EE\u5217\u8868", template: "wathet" } });
|
|
3179
3368
|
}
|
|
3180
|
-
function buildRmConfirmCard(name) {
|
|
3369
|
+
function buildRmConfirmCard(name, origin) {
|
|
3370
|
+
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
3371
|
return card(
|
|
3182
3372
|
[
|
|
3183
3373
|
md(`\u786E\u5B9A\u5220\u9664\u9879\u76EE **${name}**\uFF1F`),
|
|
3184
|
-
note(
|
|
3374
|
+
note(note_),
|
|
3185
3375
|
actions([
|
|
3186
3376
|
button("\u2705 \u786E\u8BA4\u5220\u9664", { a: DM.rmDo, n: name }, "danger"),
|
|
3187
3377
|
button("\u53D6\u6D88", { a: DM.rmCancel })
|
|
@@ -3229,7 +3419,7 @@ function buildSettingsCard(cfg) {
|
|
|
3229
3419
|
}
|
|
3230
3420
|
function buildGroupSettingsCard(project) {
|
|
3231
3421
|
const kind = project.kind ?? "multi";
|
|
3232
|
-
const noMention = project.noMention ??
|
|
3422
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3233
3423
|
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
3424
|
return card(
|
|
3235
3425
|
[
|
|
@@ -3247,31 +3437,42 @@ function buildGroupSettingsCard(project) {
|
|
|
3247
3437
|
}
|
|
3248
3438
|
|
|
3249
3439
|
// src/service/update.ts
|
|
3250
|
-
import { execFile, spawn as
|
|
3251
|
-
import { existsSync as
|
|
3252
|
-
import { dirname as
|
|
3253
|
-
import { fileURLToPath as
|
|
3440
|
+
import { execFile, spawn as spawn4 } from "child_process";
|
|
3441
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
|
|
3442
|
+
import { dirname as dirname8, join as join10, resolve as resolve5 } from "path";
|
|
3443
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3254
3444
|
import { promisify } from "util";
|
|
3255
3445
|
|
|
3256
3446
|
// src/service/launchd.ts
|
|
3257
|
-
import { spawn as
|
|
3447
|
+
import { spawn as spawn3, spawnSync } from "child_process";
|
|
3258
3448
|
import { existsSync as existsSync4 } from "fs";
|
|
3259
|
-
import {
|
|
3449
|
+
import { mkdir as mkdir6, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
3260
3450
|
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
3261
|
-
import { dirname as
|
|
3451
|
+
import { dirname as dirname6, join as join8, resolve as resolve3 } from "path";
|
|
3262
3452
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
}
|
|
3453
|
+
|
|
3454
|
+
// src/service/common.ts
|
|
3455
|
+
import { appendFile, mkdir as mkdir5 } from "fs/promises";
|
|
3456
|
+
import { join as join7 } from "path";
|
|
3267
3457
|
function serviceStdoutPath() {
|
|
3268
3458
|
return join7(paths.appDir, "service.log");
|
|
3269
3459
|
}
|
|
3270
3460
|
function serviceStderrPath() {
|
|
3271
3461
|
return join7(paths.appDir, "service.err.log");
|
|
3272
3462
|
}
|
|
3463
|
+
async function ensureLogFiles() {
|
|
3464
|
+
await mkdir5(paths.appDir, { recursive: true });
|
|
3465
|
+
await appendFile(serviceStdoutPath(), "");
|
|
3466
|
+
await appendFile(serviceStderrPath(), "");
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
// src/service/launchd.ts
|
|
3470
|
+
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
3471
|
+
function launchAgentPlistPath() {
|
|
3472
|
+
return join8(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
3473
|
+
}
|
|
3273
3474
|
function resolveCliBinPath() {
|
|
3274
|
-
const distDir =
|
|
3475
|
+
const distDir = dirname6(fileURLToPath2(import.meta.url));
|
|
3275
3476
|
return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3276
3477
|
}
|
|
3277
3478
|
function escapeXml(value) {
|
|
@@ -3312,9 +3513,9 @@ function buildPlist() {
|
|
|
3312
3513
|
}
|
|
3313
3514
|
async function installLaunchd() {
|
|
3314
3515
|
const plistPath = launchAgentPlistPath();
|
|
3315
|
-
await
|
|
3516
|
+
await mkdir6(dirname6(plistPath), { recursive: true });
|
|
3316
3517
|
await ensureLogFiles();
|
|
3317
|
-
await
|
|
3518
|
+
await writeFile5(plistPath, buildPlist(), "utf8");
|
|
3318
3519
|
if (isLoaded()) {
|
|
3319
3520
|
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
3320
3521
|
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
@@ -3350,9 +3551,10 @@ function statusLaunchd() {
|
|
|
3350
3551
|
const raw = result.stdout || result.stderr;
|
|
3351
3552
|
const parsed = parseLaunchdStatus(raw);
|
|
3352
3553
|
return {
|
|
3554
|
+
platformName: "launchd (macOS)",
|
|
3353
3555
|
installed: existsSync4(launchAgentPlistPath()),
|
|
3354
|
-
|
|
3355
|
-
|
|
3556
|
+
running: result.ok,
|
|
3557
|
+
servicePath: launchAgentPlistPath(),
|
|
3356
3558
|
stdoutPath: serviceStdoutPath(),
|
|
3357
3559
|
stderrPath: serviceStderrPath(),
|
|
3358
3560
|
pid: parsed.pid,
|
|
@@ -3364,7 +3566,7 @@ async function tailLaunchdLogs(follow) {
|
|
|
3364
3566
|
await ensureLogFiles();
|
|
3365
3567
|
const args = follow ? ["-f", serviceStdoutPath(), serviceStderrPath()] : ["-n", "100", serviceStdoutPath(), serviceStderrPath()];
|
|
3366
3568
|
await new Promise((resolvePromise, reject) => {
|
|
3367
|
-
const child =
|
|
3569
|
+
const child = spawn3("tail", args, { stdio: "inherit" });
|
|
3368
3570
|
child.on("error", reject);
|
|
3369
3571
|
child.on("close", (code) => {
|
|
3370
3572
|
if (code === 0 || follow && code === null) {
|
|
@@ -3395,11 +3597,6 @@ async function waitUntilUnloaded(timeoutMs = 5e3) {
|
|
|
3395
3597
|
}
|
|
3396
3598
|
throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
|
|
3397
3599
|
}
|
|
3398
|
-
async function ensureLogFiles() {
|
|
3399
|
-
await mkdir4(paths.appDir, { recursive: true });
|
|
3400
|
-
await appendFile(serviceStdoutPath(), "");
|
|
3401
|
-
await appendFile(serviceStderrPath(), "");
|
|
3402
|
-
}
|
|
3403
3600
|
function userTarget() {
|
|
3404
3601
|
return `gui/${userInfo2().uid}`;
|
|
3405
3602
|
}
|
|
@@ -3420,29 +3617,218 @@ function launchctlError(command, result) {
|
|
|
3420
3617
|
return new Error(`${command} \u5931\u8D25\uFF08exit ${result.status ?? "unknown"}\uFF09${output ? `\uFF1A${output}` : ""}`);
|
|
3421
3618
|
}
|
|
3422
3619
|
|
|
3620
|
+
// src/service/schtasks.ts
|
|
3621
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3622
|
+
import { createReadStream, existsSync as existsSync5, statSync } from "fs";
|
|
3623
|
+
import { mkdir as mkdir7, readFile as readFile7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
3624
|
+
import { dirname as dirname7, join as join9, resolve as resolve4 } from "path";
|
|
3625
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3626
|
+
var WINDOWS_TASK_NAME = "feishu-codex-bridge";
|
|
3627
|
+
function launcherCmdPath() {
|
|
3628
|
+
return join9(paths.appDir, "service-launcher.cmd");
|
|
3629
|
+
}
|
|
3630
|
+
function resolveCliBinPath2() {
|
|
3631
|
+
const distDir = dirname7(fileURLToPath3(import.meta.url));
|
|
3632
|
+
return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3633
|
+
}
|
|
3634
|
+
function buildLauncherCmd() {
|
|
3635
|
+
const nodePath = process.execPath;
|
|
3636
|
+
const cliBinPath = resolveCliBinPath2();
|
|
3637
|
+
const pathEnv = process.env.PATH ?? "";
|
|
3638
|
+
return [
|
|
3639
|
+
"@echo off",
|
|
3640
|
+
`set "PATH=${pathEnv}"`,
|
|
3641
|
+
`"${nodePath}" "${cliBinPath}" run >> "${serviceStdoutPath()}" 2>> "${serviceStderrPath()}"`,
|
|
3642
|
+
""
|
|
3643
|
+
].join("\r\n");
|
|
3644
|
+
}
|
|
3645
|
+
function runSchtasks(args) {
|
|
3646
|
+
const r = spawnSync2("schtasks", args, { encoding: "utf8" });
|
|
3647
|
+
return {
|
|
3648
|
+
ok: r.status === 0,
|
|
3649
|
+
status: r.status,
|
|
3650
|
+
stdout: r.stdout ?? "",
|
|
3651
|
+
stderr: r.stderr ?? ""
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
function schtasksError(command, r) {
|
|
3655
|
+
const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
|
|
3656
|
+
return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
|
|
3657
|
+
}
|
|
3658
|
+
async function writeLauncherCmd() {
|
|
3659
|
+
const cmdPath = launcherCmdPath();
|
|
3660
|
+
await mkdir7(dirname7(cmdPath), { recursive: true });
|
|
3661
|
+
await ensureLogFiles();
|
|
3662
|
+
await writeFile6(cmdPath, buildLauncherCmd(), "utf8");
|
|
3663
|
+
}
|
|
3664
|
+
async function installSchtask() {
|
|
3665
|
+
await writeLauncherCmd();
|
|
3666
|
+
const create = runSchtasks([
|
|
3667
|
+
"/Create",
|
|
3668
|
+
"/F",
|
|
3669
|
+
"/SC",
|
|
3670
|
+
"ONLOGON",
|
|
3671
|
+
"/RL",
|
|
3672
|
+
"LIMITED",
|
|
3673
|
+
"/TN",
|
|
3674
|
+
WINDOWS_TASK_NAME,
|
|
3675
|
+
"/TR",
|
|
3676
|
+
`"${launcherCmdPath()}"`
|
|
3677
|
+
]);
|
|
3678
|
+
if (!create.ok) throw schtasksError("schtasks /Create", create);
|
|
3679
|
+
const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
|
|
3680
|
+
if (!run.ok) throw schtasksError("schtasks /Run", run);
|
|
3681
|
+
return statusSchtask();
|
|
3682
|
+
}
|
|
3683
|
+
async function uninstallSchtask() {
|
|
3684
|
+
runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
|
|
3685
|
+
const del = runSchtasks(["/Delete", "/F", "/TN", WINDOWS_TASK_NAME]);
|
|
3686
|
+
if (!del.ok && isTaskRegistered()) throw schtasksError("schtasks /Delete", del);
|
|
3687
|
+
if (existsSync5(launcherCmdPath())) await rm3(launcherCmdPath(), { force: true });
|
|
3688
|
+
}
|
|
3689
|
+
async function restartSchtask() {
|
|
3690
|
+
if (!isTaskRegistered()) {
|
|
3691
|
+
throw new Error(`\u8BA1\u5212\u4EFB\u52A1\u672A\u5B89\u88C5\uFF1A${WINDOWS_TASK_NAME}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
|
|
3692
|
+
}
|
|
3693
|
+
runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
|
|
3694
|
+
await waitUntilStopped();
|
|
3695
|
+
const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
|
|
3696
|
+
if (!run.ok) throw schtasksError("schtasks /Run", run);
|
|
3697
|
+
return statusSchtask();
|
|
3698
|
+
}
|
|
3699
|
+
function statusSchtask() {
|
|
3700
|
+
const installed = isTaskRegistered();
|
|
3701
|
+
const raw = installed ? describeTask() : "";
|
|
3702
|
+
return {
|
|
3703
|
+
platformName: "Task Scheduler (Windows)",
|
|
3704
|
+
installed,
|
|
3705
|
+
running: installed && /Status:\s+Running/i.test(raw),
|
|
3706
|
+
servicePath: WINDOWS_TASK_NAME,
|
|
3707
|
+
stdoutPath: serviceStdoutPath(),
|
|
3708
|
+
stderrPath: serviceStderrPath(),
|
|
3709
|
+
// `Process ID:` only appears in verbose output while the task is running.
|
|
3710
|
+
pid: raw.match(/Process ID:\s*(\d+)/i)?.[1],
|
|
3711
|
+
// `Last Result: 0` ⇒ last run succeeded. Surface it as the exit code.
|
|
3712
|
+
lastExit: raw.match(/Last Result:\s*(-?\d+)/i)?.[1],
|
|
3713
|
+
raw
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
function isTaskRegistered() {
|
|
3717
|
+
const r = spawnSync2("schtasks", ["/Query", "/TN", WINDOWS_TASK_NAME], {
|
|
3718
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3719
|
+
});
|
|
3720
|
+
return r.status === 0;
|
|
3721
|
+
}
|
|
3722
|
+
function schtaskRunning() {
|
|
3723
|
+
if (!isTaskRegistered()) return false;
|
|
3724
|
+
return /Status:\s+Running/i.test(describeTask());
|
|
3725
|
+
}
|
|
3726
|
+
function describeTask() {
|
|
3727
|
+
const r = runSchtasks(["/Query", "/V", "/FO", "LIST", "/TN", WINDOWS_TASK_NAME]);
|
|
3728
|
+
return r.stdout || r.stderr || "";
|
|
3729
|
+
}
|
|
3730
|
+
async function waitUntilStopped(timeoutMs = 5e3) {
|
|
3731
|
+
const deadline = Date.now() + timeoutMs;
|
|
3732
|
+
while (Date.now() < deadline) {
|
|
3733
|
+
if (!schtaskRunning()) return true;
|
|
3734
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3735
|
+
}
|
|
3736
|
+
return false;
|
|
3737
|
+
}
|
|
3738
|
+
async function tailSchtaskLogs(follow) {
|
|
3739
|
+
await ensureLogFiles();
|
|
3740
|
+
const files = [serviceStdoutPath(), serviceStderrPath()];
|
|
3741
|
+
for (const f of files) {
|
|
3742
|
+
const tail = await lastLines(f, 100);
|
|
3743
|
+
if (tail) process.stdout.write(`
|
|
3744
|
+
===== ${f} =====
|
|
3745
|
+
${tail}
|
|
3746
|
+
`);
|
|
3747
|
+
}
|
|
3748
|
+
if (!follow) return;
|
|
3749
|
+
const offsets = new Map(files.map((f) => [f, fileSize(f)]));
|
|
3750
|
+
await new Promise((resolvePromise) => {
|
|
3751
|
+
const onSigint = () => {
|
|
3752
|
+
clearInterval(timer);
|
|
3753
|
+
process.off("SIGINT", onSigint);
|
|
3754
|
+
resolvePromise();
|
|
3755
|
+
};
|
|
3756
|
+
process.on("SIGINT", onSigint);
|
|
3757
|
+
const timer = setInterval(() => {
|
|
3758
|
+
for (const f of files) {
|
|
3759
|
+
const size = fileSize(f);
|
|
3760
|
+
const from = offsets.get(f) ?? 0;
|
|
3761
|
+
if (size > from) {
|
|
3762
|
+
offsets.set(f, size);
|
|
3763
|
+
createReadStream(f, { start: from, end: size - 1, encoding: "utf8" }).pipe(process.stdout, {
|
|
3764
|
+
end: false
|
|
3765
|
+
});
|
|
3766
|
+
} else if (size < from) {
|
|
3767
|
+
offsets.set(f, size);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
}, 700);
|
|
3771
|
+
});
|
|
3772
|
+
}
|
|
3773
|
+
function fileSize(file) {
|
|
3774
|
+
try {
|
|
3775
|
+
return statSync(file).size;
|
|
3776
|
+
} catch {
|
|
3777
|
+
return 0;
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
async function lastLines(file, n) {
|
|
3781
|
+
try {
|
|
3782
|
+
const text = await readFile7(file, "utf8");
|
|
3783
|
+
const lines = text.split("\n");
|
|
3784
|
+
return lines.slice(-n - 1).join("\n").trimEnd();
|
|
3785
|
+
} catch {
|
|
3786
|
+
return "";
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3423
3790
|
// src/service/adapter.ts
|
|
3424
3791
|
function getServiceAdapter() {
|
|
3425
|
-
if (process.platform
|
|
3426
|
-
|
|
3792
|
+
if (process.platform === "darwin") {
|
|
3793
|
+
return {
|
|
3794
|
+
install: installLaunchd,
|
|
3795
|
+
uninstall: uninstallLaunchd,
|
|
3796
|
+
status: async () => statusLaunchd(),
|
|
3797
|
+
restart: restartLaunchd,
|
|
3798
|
+
logs: tailLaunchdLogs
|
|
3799
|
+
};
|
|
3427
3800
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3801
|
+
if (process.platform === "win32") {
|
|
3802
|
+
return {
|
|
3803
|
+
install: installSchtask,
|
|
3804
|
+
uninstall: uninstallSchtask,
|
|
3805
|
+
status: async () => statusSchtask(),
|
|
3806
|
+
restart: restartSchtask,
|
|
3807
|
+
logs: tailSchtaskLogs
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
throw new Error(
|
|
3811
|
+
"service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u540E\u53F0\u670D\u52A1\uFF08\u4EC5 macOS launchd / Windows \u8BA1\u5212\u4EFB\u52A1\uFF09\u3002\u8BF7\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C" + (process.platform === "linux" ? "\uFF1BLinux systemd \u652F\u6301\u540E\u7EED\u63D0\u4F9B\u3002" : "\u3002")
|
|
3812
|
+
);
|
|
3813
|
+
}
|
|
3814
|
+
function isServiceRunning() {
|
|
3815
|
+
try {
|
|
3816
|
+
if (process.platform === "darwin") return isLoaded();
|
|
3817
|
+
if (process.platform === "win32") return schtaskRunning();
|
|
3818
|
+
} catch {
|
|
3819
|
+
}
|
|
3820
|
+
return false;
|
|
3435
3821
|
}
|
|
3436
3822
|
|
|
3437
3823
|
// src/service/update.ts
|
|
3438
3824
|
var execFileP = promisify(execFile);
|
|
3439
3825
|
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3440
3826
|
function pkgRoot() {
|
|
3441
|
-
return
|
|
3827
|
+
return resolve5(dirname8(fileURLToPath4(import.meta.url)), "..");
|
|
3442
3828
|
}
|
|
3443
3829
|
function pkgJson() {
|
|
3444
3830
|
try {
|
|
3445
|
-
return JSON.parse(readFileSync2(
|
|
3831
|
+
return JSON.parse(readFileSync2(join10(pkgRoot(), "package.json"), "utf8"));
|
|
3446
3832
|
} catch {
|
|
3447
3833
|
return {};
|
|
3448
3834
|
}
|
|
@@ -3454,7 +3840,7 @@ function packageName() {
|
|
|
3454
3840
|
return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
|
|
3455
3841
|
}
|
|
3456
3842
|
function isDevSource() {
|
|
3457
|
-
return
|
|
3843
|
+
return existsSync6(join10(pkgRoot(), ".git"));
|
|
3458
3844
|
}
|
|
3459
3845
|
function isNewer(a, b) {
|
|
3460
3846
|
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -3477,7 +3863,7 @@ async function latestVersion() {
|
|
|
3477
3863
|
async function installLatest(opts = {}) {
|
|
3478
3864
|
const target = `${packageName()}@latest`;
|
|
3479
3865
|
return await new Promise((resolveP) => {
|
|
3480
|
-
const child =
|
|
3866
|
+
const child = spawn4(NPM, ["install", "-g", target], {
|
|
3481
3867
|
stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
3482
3868
|
});
|
|
3483
3869
|
let out = "";
|
|
@@ -3493,78 +3879,16 @@ async function installLatest(opts = {}) {
|
|
|
3493
3879
|
});
|
|
3494
3880
|
}
|
|
3495
3881
|
function daemonRunning() {
|
|
3496
|
-
|
|
3497
|
-
return statusLaunchd().loaded;
|
|
3498
|
-
} catch {
|
|
3499
|
-
return false;
|
|
3500
|
-
}
|
|
3882
|
+
return isServiceRunning();
|
|
3501
3883
|
}
|
|
3502
3884
|
async function restartDaemon() {
|
|
3503
3885
|
await getServiceAdapter().restart();
|
|
3504
3886
|
}
|
|
3505
3887
|
|
|
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
3888
|
// src/project/lifecycle.ts
|
|
3565
|
-
import { mkdir as
|
|
3566
|
-
import { existsSync as
|
|
3567
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3889
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
3890
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3891
|
+
import { isAbsolute as isAbsolute2, join as join11, resolve as resolve6 } from "path";
|
|
3568
3892
|
|
|
3569
3893
|
// src/project/git-info.ts
|
|
3570
3894
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -3665,21 +3989,23 @@ var HELP_DOC_URL = "https://my.feishu.cn/wiki/PZ23wGr7JiKK5RkIG4rcZXzGn5g";
|
|
|
3665
3989
|
async function onboardGroup(channel, project) {
|
|
3666
3990
|
const kind = project.kind ?? "multi";
|
|
3667
3991
|
const chatId = project.chatId;
|
|
3992
|
+
const decorate = (project.origin ?? "created") !== "joined";
|
|
3668
3993
|
try {
|
|
3669
|
-
const
|
|
3994
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3995
|
+
const content = JSON.stringify(buildWelcomeCard(kind, HELP_DOC_URL || void 0, noMention));
|
|
3670
3996
|
const sent = await channel.rawClient.im.v1.message.create({
|
|
3671
3997
|
params: { receive_id_type: "chat_id" },
|
|
3672
3998
|
data: { receive_id: chatId, msg_type: "interactive", content }
|
|
3673
3999
|
});
|
|
3674
4000
|
const messageId = sent.data?.message_id;
|
|
3675
|
-
if (messageId) {
|
|
4001
|
+
if (messageId && decorate) {
|
|
3676
4002
|
await channel.rawClient.im.v1.pin.create({ data: { message_id: messageId } });
|
|
3677
4003
|
log.info("project", "onboard-pin", { name: project.name });
|
|
3678
4004
|
}
|
|
3679
4005
|
} catch (err) {
|
|
3680
4006
|
log.fail("project", err, { phase: "onboard-welcome" });
|
|
3681
4007
|
}
|
|
3682
|
-
if (HELP_DOC_URL) {
|
|
4008
|
+
if (decorate && HELP_DOC_URL) {
|
|
3683
4009
|
try {
|
|
3684
4010
|
await channel.rawClient.im.v1.chatTab.create({
|
|
3685
4011
|
path: { chat_id: chatId },
|
|
@@ -3695,21 +4021,21 @@ async function onboardGroup(channel, project) {
|
|
|
3695
4021
|
}
|
|
3696
4022
|
|
|
3697
4023
|
// src/project/lifecycle.ts
|
|
4024
|
+
async function resolveCwd(name, existingPath) {
|
|
4025
|
+
if (existingPath) {
|
|
4026
|
+
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
|
|
4027
|
+
if (!existsSync7(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
4028
|
+
return { cwd: cwd2, blank: false };
|
|
4029
|
+
}
|
|
4030
|
+
const cwd = join11(paths.projectsRootDir, name);
|
|
4031
|
+
await mkdir8(cwd, { recursive: true });
|
|
4032
|
+
return { cwd, blank: true };
|
|
4033
|
+
}
|
|
3698
4034
|
async function createProject(channel, input2) {
|
|
3699
4035
|
const name = input2.name.trim();
|
|
3700
4036
|
if (!name) throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3701
4037
|
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
|
-
}
|
|
4038
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
3713
4039
|
const res = await channel.rawClient.im.v1.chat.create({
|
|
3714
4040
|
params: { user_id_type: "open_id" },
|
|
3715
4041
|
data: { name, user_id_list: [input2.ownerOpenId] }
|
|
@@ -3721,13 +4047,35 @@ async function createProject(channel, input2) {
|
|
|
3721
4047
|
params: { member_id_type: "open_id" },
|
|
3722
4048
|
data: { manager_ids: [input2.ownerOpenId] }
|
|
3723
4049
|
}).catch((err) => log.fail("project", err, { phase: "add-manager" }));
|
|
3724
|
-
const project = { name, chatId, cwd, blank, createdAt: Date.now(), kind: input2.kind ?? "multi" };
|
|
4050
|
+
const project = { name, chatId, cwd, blank, createdAt: Date.now(), kind: input2.kind ?? "multi", origin: "created" };
|
|
3725
4051
|
await addProject(project);
|
|
3726
4052
|
log.info("project", "create", { name, chatId, cwd, blank });
|
|
3727
4053
|
await setAnnouncement(channel, project).catch((err) => log.fail("project", err, { phase: "announcement" }));
|
|
3728
4054
|
await onboardGroup(channel, project).catch((err) => log.fail("project", err, { phase: "onboard" }));
|
|
3729
4055
|
return project;
|
|
3730
4056
|
}
|
|
4057
|
+
async function joinExistingGroup(channel, input2) {
|
|
4058
|
+
const name = input2.name.trim();
|
|
4059
|
+
if (!name) throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4060
|
+
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`);
|
|
4061
|
+
const bound = await getProjectByChatId(input2.chatId);
|
|
4062
|
+
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
4063
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
4064
|
+
const project = {
|
|
4065
|
+
name,
|
|
4066
|
+
chatId: input2.chatId,
|
|
4067
|
+
cwd,
|
|
4068
|
+
blank,
|
|
4069
|
+
createdAt: Date.now(),
|
|
4070
|
+
kind: input2.kind ?? "multi",
|
|
4071
|
+
origin: "joined",
|
|
4072
|
+
addedBy: input2.addedBy
|
|
4073
|
+
};
|
|
4074
|
+
await addProject(project);
|
|
4075
|
+
log.info("project", "join", { name, chatId: input2.chatId, cwd, blank, kind: project.kind });
|
|
4076
|
+
await onboardGroup(channel, project).catch((err) => log.fail("project", err, { phase: "onboard-join" }));
|
|
4077
|
+
return project;
|
|
4078
|
+
}
|
|
3731
4079
|
|
|
3732
4080
|
// src/project/group-ops.ts
|
|
3733
4081
|
async function transferOwnership(channel, chatId, toOpenId) {
|
|
@@ -3738,14 +4086,21 @@ async function transferOwnership(channel, chatId, toOpenId) {
|
|
|
3738
4086
|
});
|
|
3739
4087
|
log.info("project", "owner-transfer", { chatId: chatId.slice(-6), to: toOpenId.slice(-6) });
|
|
3740
4088
|
}
|
|
4089
|
+
async function leaveChat(channel, chatId) {
|
|
4090
|
+
await channel.rawClient.request({
|
|
4091
|
+
method: "PATCH",
|
|
4092
|
+
url: `/open-apis/im/v1/chats/${encodeURIComponent(chatId)}/members/me_leave`
|
|
4093
|
+
});
|
|
4094
|
+
log.info("project", "leave-chat", { chatId: chatId.slice(-6) });
|
|
4095
|
+
}
|
|
3741
4096
|
|
|
3742
4097
|
// src/bot/session-store.ts
|
|
3743
|
-
import { mkdir as
|
|
3744
|
-
import { dirname as
|
|
4098
|
+
import { mkdir as mkdir9, readFile as readFile8, rename as rename5, writeFile as writeFile7 } from "fs/promises";
|
|
4099
|
+
import { dirname as dirname9 } from "path";
|
|
3745
4100
|
var FILE_VERSION3 = 1;
|
|
3746
4101
|
async function read2() {
|
|
3747
4102
|
try {
|
|
3748
|
-
const text = await
|
|
4103
|
+
const text = await readFile8(paths.sessionsFile, "utf8");
|
|
3749
4104
|
const parsed = JSON.parse(text);
|
|
3750
4105
|
return Array.isArray(parsed.sessions) ? parsed.sessions : [];
|
|
3751
4106
|
} catch (err) {
|
|
@@ -3754,10 +4109,10 @@ async function read2() {
|
|
|
3754
4109
|
}
|
|
3755
4110
|
}
|
|
3756
4111
|
async function write2(sessions) {
|
|
3757
|
-
await
|
|
4112
|
+
await mkdir9(dirname9(paths.sessionsFile), { recursive: true });
|
|
3758
4113
|
const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
|
|
3759
4114
|
const body = { version: FILE_VERSION3, sessions };
|
|
3760
|
-
await
|
|
4115
|
+
await writeFile7(tmp, `${JSON.stringify(body, null, 2)}
|
|
3761
4116
|
`, "utf8");
|
|
3762
4117
|
await rename5(tmp, paths.sessionsFile);
|
|
3763
4118
|
}
|
|
@@ -3801,8 +4156,8 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
3801
4156
|
}
|
|
3802
4157
|
|
|
3803
4158
|
// src/bot/media.ts
|
|
3804
|
-
import { mkdir as
|
|
3805
|
-
import { join as
|
|
4159
|
+
import { mkdir as mkdir10, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
|
|
4160
|
+
import { join as join12 } from "path";
|
|
3806
4161
|
var MAX_IMAGES2 = 9;
|
|
3807
4162
|
var MEDIA_TTL_MS = 60 * 6e4;
|
|
3808
4163
|
var EXT_BY_CONTENT_TYPE = {
|
|
@@ -3831,7 +4186,7 @@ async function collectInboundImages(channel, msg) {
|
|
|
3831
4186
|
if (refs.length === 0) return [];
|
|
3832
4187
|
await pruneOldMedia();
|
|
3833
4188
|
try {
|
|
3834
|
-
await
|
|
4189
|
+
await mkdir10(paths.mediaDir, { recursive: true });
|
|
3835
4190
|
} catch {
|
|
3836
4191
|
}
|
|
3837
4192
|
const out = [];
|
|
@@ -3907,7 +4262,7 @@ async function downloadOne(channel, ref, index) {
|
|
|
3907
4262
|
params: { type: "image" }
|
|
3908
4263
|
});
|
|
3909
4264
|
const ext = extFromHeaders(res.headers);
|
|
3910
|
-
const file =
|
|
4265
|
+
const file = join12(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
|
|
3911
4266
|
await res.writeFile(file);
|
|
3912
4267
|
return file;
|
|
3913
4268
|
} catch (err) {
|
|
@@ -3941,10 +4296,10 @@ async function pruneOldMedia() {
|
|
|
3941
4296
|
}
|
|
3942
4297
|
const cutoff = Date.now() - MEDIA_TTL_MS;
|
|
3943
4298
|
for (const name of entries) {
|
|
3944
|
-
const file =
|
|
4299
|
+
const file = join12(paths.mediaDir, name);
|
|
3945
4300
|
try {
|
|
3946
4301
|
const st = await stat3(file);
|
|
3947
|
-
if (st.mtimeMs < cutoff) await
|
|
4302
|
+
if (st.mtimeMs < cutoff) await rm4(file, { force: true });
|
|
3948
4303
|
} catch {
|
|
3949
4304
|
}
|
|
3950
4305
|
}
|
|
@@ -4268,11 +4623,22 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4268
4623
|
log.info("intake", "reject", { reason: "not_allowed", chatId: msg.chatId.slice(-6) });
|
|
4269
4624
|
return;
|
|
4270
4625
|
}
|
|
4626
|
+
if (!project) {
|
|
4627
|
+
log.info("intake", "unbound-group", { chatId: msg.chatId.slice(-6), atBot: msg.mentionedBot });
|
|
4628
|
+
if (msg.mentionedBot) {
|
|
4629
|
+
await channel.send(
|
|
4630
|
+
msg.chatId,
|
|
4631
|
+
{ 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" },
|
|
4632
|
+
{ replyTo: msg.messageId }
|
|
4633
|
+
).catch(() => void 0);
|
|
4634
|
+
}
|
|
4635
|
+
return;
|
|
4636
|
+
}
|
|
4271
4637
|
const text = msg.content.trim();
|
|
4272
4638
|
const cmd = parseCommand(text);
|
|
4273
4639
|
if ((project?.kind ?? "multi") === "single") {
|
|
4274
4640
|
if (cmd === "help") {
|
|
4275
|
-
await postHelpCard(msg, "single");
|
|
4641
|
+
await postHelpCard(msg, "single", false, project);
|
|
4276
4642
|
return;
|
|
4277
4643
|
}
|
|
4278
4644
|
if (cmd === "settings") {
|
|
@@ -4288,7 +4654,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4288
4654
|
}
|
|
4289
4655
|
if (msg.threadId) {
|
|
4290
4656
|
if (cmd === "help") {
|
|
4291
|
-
await postHelpCard(msg, "topic", true);
|
|
4657
|
+
await postHelpCard(msg, "topic", true, project);
|
|
4292
4658
|
return;
|
|
4293
4659
|
}
|
|
4294
4660
|
if (cmd === "model") {
|
|
@@ -4299,7 +4665,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4299
4665
|
return;
|
|
4300
4666
|
}
|
|
4301
4667
|
if (cmd === "help") {
|
|
4302
|
-
await postHelpCard(msg, "main");
|
|
4668
|
+
await postHelpCard(msg, "main", false, project);
|
|
4303
4669
|
return;
|
|
4304
4670
|
}
|
|
4305
4671
|
if (cmd === "resume") {
|
|
@@ -4322,7 +4688,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4322
4688
|
return name === "resume" || name === "model" || name === "settings" || name === "help" ? name : null;
|
|
4323
4689
|
}
|
|
4324
4690
|
function shouldRespondWithoutMention(project, msg) {
|
|
4325
|
-
if (!(project.noMention ??
|
|
4691
|
+
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
4326
4692
|
if (msg.mentionAll || msg.mentions.some((m) => !m.isBot)) return false;
|
|
4327
4693
|
if ((project.kind ?? "multi") === "single") return true;
|
|
4328
4694
|
return Boolean(msg.threadId) || parseCommand(msg.content.trim()) !== null;
|
|
@@ -4519,9 +4885,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4519
4885
|
log.info("card", "model", { threadId: sessionKey, model: state.model, effort: state.effort });
|
|
4520
4886
|
});
|
|
4521
4887
|
}
|
|
4522
|
-
async function postHelpCard(msg, scope, inThread = false) {
|
|
4888
|
+
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
4889
|
+
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
4523
4890
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
4524
|
-
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope), msg.messageId, inThread).catch(
|
|
4891
|
+
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention), msg.messageId, inThread).catch(
|
|
4525
4892
|
(err) => log.fail("card", err, { cmd: "help", scope })
|
|
4526
4893
|
);
|
|
4527
4894
|
log.info("card", "help", { scope });
|
|
@@ -4659,6 +5026,32 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4659
5026
|
(e) => log.fail("console", e, { phase: "new-project-result" })
|
|
4660
5027
|
);
|
|
4661
5028
|
})();
|
|
5029
|
+
}).on(DM.joinGroupSubmit, ({ evt, formValue, value }) => {
|
|
5030
|
+
const op = evt.operator?.openId;
|
|
5031
|
+
if (!dmAdmin(op)) return;
|
|
5032
|
+
const name = String(formValue?.name ?? "").trim();
|
|
5033
|
+
const cwdIn = String(formValue?.cwd ?? "").trim();
|
|
5034
|
+
const chatId = typeof value.chatId === "string" ? value.chatId : "";
|
|
5035
|
+
const kind = value.kind === "single" ? "single" : "multi";
|
|
5036
|
+
void (async () => {
|
|
5037
|
+
let result;
|
|
5038
|
+
if (!chatId)
|
|
5039
|
+
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" });
|
|
5040
|
+
else if (!name) result = buildJoinGroupFormCard({ chatId, cwd: cwdIn, error: "\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A" });
|
|
5041
|
+
else if (!op) result = buildJoinGroupFormCard({ chatId, name, cwd: cwdIn, error: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u8005\u8EAB\u4EFD" });
|
|
5042
|
+
else {
|
|
5043
|
+
try {
|
|
5044
|
+
const p = await joinExistingGroup(channel, { name, chatId, addedBy: op, existingPath: cwdIn || void 0, kind });
|
|
5045
|
+
log.info("console", "join-group", { name: p.name, blank: p.blank });
|
|
5046
|
+
result = buildNewProjectDoneCard(p);
|
|
5047
|
+
} catch (err) {
|
|
5048
|
+
result = buildJoinGroupFormCard({ chatId, name, cwd: cwdIn, error: err instanceof Error ? err.message : String(err) });
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
await sendManagedCard(channel, evt.chatId, result).catch(
|
|
5052
|
+
(e) => log.fail("console", e, { phase: "join-group-result" })
|
|
5053
|
+
);
|
|
5054
|
+
})();
|
|
4662
5055
|
}).on(DM.projects, ({ evt }) => {
|
|
4663
5056
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4664
5057
|
patch(evt, renderProjectList);
|
|
@@ -4671,6 +5064,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4671
5064
|
const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
|
|
4672
5065
|
const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
|
|
4673
5066
|
const missingScopes = scopeCheck?.missingScopes;
|
|
5067
|
+
const missingJoinScopes = scopeCheck?.missingJoinScopes;
|
|
4674
5068
|
const info = {
|
|
4675
5069
|
codexOk: await backend.isAvailable().catch(() => false),
|
|
4676
5070
|
codexVer: codexBin ? codexVersion(codexBin) : null,
|
|
@@ -4687,7 +5081,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4687
5081
|
app.id,
|
|
4688
5082
|
app.tenant,
|
|
4689
5083
|
missingScopes && missingScopes.length ? missingScopes : void 0
|
|
4690
|
-
)
|
|
5084
|
+
),
|
|
5085
|
+
missingJoinScopes,
|
|
5086
|
+
// 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
|
|
5087
|
+
joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
|
|
4691
5088
|
};
|
|
4692
5089
|
await sendManagedCard(channel, evt.chatId, buildDoctorCard(info), evt.messageId).catch(
|
|
4693
5090
|
(err) => log.fail("console", err, { cmd: "doctor" })
|
|
@@ -4748,7 +5145,8 @@ SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8B
|
|
|
4748
5145
|
}).on(DM.rmConfirm, async ({ evt, value }) => {
|
|
4749
5146
|
const name = typeof value.n === "string" ? value.n : void 0;
|
|
4750
5147
|
if (!dmAdmin(evt.operator?.openId) || !name) return;
|
|
4751
|
-
await
|
|
5148
|
+
const proj = (await listProjects()).find((p) => p.name === name);
|
|
5149
|
+
await patch(evt, buildRmConfirmCard(name, proj?.origin));
|
|
4752
5150
|
}).on(DM.rmCancel, ({ evt }) => {
|
|
4753
5151
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4754
5152
|
patch(evt, renderProjectList);
|
|
@@ -4758,15 +5156,25 @@ SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8B
|
|
|
4758
5156
|
if (!dmAdmin(op) || !name) return;
|
|
4759
5157
|
patch(evt, async () => {
|
|
4760
5158
|
const removed = await removeProject(name);
|
|
4761
|
-
let
|
|
4762
|
-
if (removed
|
|
4763
|
-
|
|
4764
|
-
log.fail("console", err, { phase: "
|
|
5159
|
+
let tail;
|
|
5160
|
+
if (removed && (removed.origin ?? "created") === "joined") {
|
|
5161
|
+
const left = removed.chatId ? await leaveChat(channel, removed.chatId).then(() => true).catch((err) => {
|
|
5162
|
+
log.fail("console", err, { phase: "leave-chat" });
|
|
4765
5163
|
return false;
|
|
4766
|
-
});
|
|
5164
|
+
}) : false;
|
|
5165
|
+
log.info("console", "rm", { name, origin: "joined", left });
|
|
5166
|
+
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";
|
|
5167
|
+
} else {
|
|
5168
|
+
let transferred = false;
|
|
5169
|
+
if (removed?.chatId && op) {
|
|
5170
|
+
transferred = await transferOwnership(channel, removed.chatId, op).then(() => true).catch((err) => {
|
|
5171
|
+
log.fail("console", err, { phase: "owner-transfer" });
|
|
5172
|
+
return false;
|
|
5173
|
+
});
|
|
5174
|
+
}
|
|
5175
|
+
log.info("console", "rm", { name, origin: "created", transferred });
|
|
5176
|
+
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
5177
|
}
|
|
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
5178
|
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
5179
|
${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
4772
5180
|
return renderProjectList();
|
|
@@ -5102,13 +5510,57 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5102
5510
|
});
|
|
5103
5511
|
return fresh;
|
|
5104
5512
|
}
|
|
5513
|
+
async function onBotAddedToChat(evt) {
|
|
5514
|
+
await withTrace({ chatId: evt.chatId }, async () => {
|
|
5515
|
+
const op = evt.operator?.openId;
|
|
5516
|
+
if (await getProjectByChatId(evt.chatId)) {
|
|
5517
|
+
log.info("intake", "bot-added-bound", { chatId: evt.chatId.slice(-6) });
|
|
5518
|
+
return;
|
|
5519
|
+
}
|
|
5520
|
+
if (!op || !isAdmin(cfg, op)) {
|
|
5521
|
+
log.info("intake", "bot-added-nonadmin", { chatId: evt.chatId.slice(-6), op: op?.slice(-6) });
|
|
5522
|
+
return;
|
|
5523
|
+
}
|
|
5524
|
+
const info = await channel.getChatInfo(evt.chatId).catch((err) => {
|
|
5525
|
+
log.fail("intake", err, { phase: "bot-added-chatinfo" });
|
|
5526
|
+
return void 0;
|
|
5527
|
+
});
|
|
5528
|
+
const name = (info?.name ?? "").trim();
|
|
5529
|
+
await sendManagedCard(
|
|
5530
|
+
channel,
|
|
5531
|
+
op,
|
|
5532
|
+
buildJoinGroupFormCard({ chatId: evt.chatId, name }),
|
|
5533
|
+
void 0,
|
|
5534
|
+
false,
|
|
5535
|
+
"open_id"
|
|
5536
|
+
).catch((err) => log.fail("intake", err, { phase: "bot-added-bindcard" }));
|
|
5537
|
+
log.info("intake", "bot-added", { chatId: evt.chatId.slice(-6), op: op.slice(-6), named: Boolean(name) });
|
|
5538
|
+
}).catch((err) => log.fail("intake", err, { phase: "bot-added" }));
|
|
5539
|
+
}
|
|
5540
|
+
async function onBotRemovedFromChat(chatId) {
|
|
5541
|
+
const project = await getProjectByChatId(chatId);
|
|
5542
|
+
if (!project) return;
|
|
5543
|
+
const removed = await removeProject(project.name);
|
|
5544
|
+
if (!removed) return;
|
|
5545
|
+
log.info("intake", "bot-removed-unbind", { name: removed.name, chatId: chatId.slice(-6) });
|
|
5546
|
+
if (removed.addedBy) {
|
|
5547
|
+
await channel.rawClient.im.v1.message.create({
|
|
5548
|
+
params: { receive_id_type: "open_id" },
|
|
5549
|
+
data: {
|
|
5550
|
+
receive_id: removed.addedBy,
|
|
5551
|
+
msg_type: "text",
|
|
5552
|
+
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` })
|
|
5553
|
+
}
|
|
5554
|
+
}).catch(() => void 0);
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5105
5557
|
async function shutdown() {
|
|
5106
5558
|
const live = [...sessions.values()];
|
|
5107
5559
|
sessions.clear();
|
|
5108
5560
|
await Promise.allSettled(live.map((t) => t.close()));
|
|
5109
5561
|
log.info("bridge", "shutdown", { closed: live.length });
|
|
5110
5562
|
}
|
|
5111
|
-
return { onMessage, onComment, dispatcher, shutdown };
|
|
5563
|
+
return { onMessage, onComment, onBotAddedToChat, onBotRemovedFromChat, dispatcher, shutdown };
|
|
5112
5564
|
}
|
|
5113
5565
|
async function getThreadId(channel, messageId) {
|
|
5114
5566
|
try {
|
|
@@ -5146,6 +5598,24 @@ async function startBridge(opts) {
|
|
|
5146
5598
|
channel.on("message", orchestrator.onMessage);
|
|
5147
5599
|
channel.on("cardAction", orchestrator.dispatcher.handle);
|
|
5148
5600
|
channel.on("comment", orchestrator.onComment);
|
|
5601
|
+
channel.on("botAdded", orchestrator.onBotAddedToChat);
|
|
5602
|
+
try {
|
|
5603
|
+
const tap = channel.dispatcher;
|
|
5604
|
+
if (tap?.register) {
|
|
5605
|
+
tap.register({
|
|
5606
|
+
"im.chat.member.bot.deleted_v1": (raw) => {
|
|
5607
|
+
const ev = raw;
|
|
5608
|
+
const chatId = ev?.chat_id ?? ev?.event?.chat_id;
|
|
5609
|
+
if (chatId) void orchestrator.onBotRemovedFromChat(chatId);
|
|
5610
|
+
}
|
|
5611
|
+
});
|
|
5612
|
+
log.info("ws", "bot-removed-tap");
|
|
5613
|
+
} else {
|
|
5614
|
+
log.info("ws", "bot-removed-tap-unavailable");
|
|
5615
|
+
}
|
|
5616
|
+
} catch (err) {
|
|
5617
|
+
log.fail("ws", err, { phase: "bot-removed-tap" });
|
|
5618
|
+
}
|
|
5149
5619
|
channel.on("reject", (evt) => log.info("intake", "reject", { reason: evt.reason, msgId: evt.messageId }));
|
|
5150
5620
|
channel.on("error", (err) => log.fail("ws", err));
|
|
5151
5621
|
channel.on("reconnecting", () => log.info("ws", "reconnecting"));
|
|
@@ -5164,7 +5634,7 @@ async function startBridge(opts) {
|
|
|
5164
5634
|
|
|
5165
5635
|
// src/core/single-instance.ts
|
|
5166
5636
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "fs";
|
|
5167
|
-
import { dirname as
|
|
5637
|
+
import { dirname as dirname10 } from "path";
|
|
5168
5638
|
var BridgeAlreadyRunningError = class extends Error {
|
|
5169
5639
|
constructor(pid) {
|
|
5170
5640
|
super(
|
|
@@ -5193,7 +5663,7 @@ function acquireSingleInstanceLock(appId) {
|
|
|
5193
5663
|
} catch (err) {
|
|
5194
5664
|
if (err instanceof BridgeAlreadyRunningError) throw err;
|
|
5195
5665
|
}
|
|
5196
|
-
mkdirSync2(
|
|
5666
|
+
mkdirSync2(dirname10(file), { recursive: true });
|
|
5197
5667
|
const record = { pid: process.pid, appId, startedAt: Date.now() };
|
|
5198
5668
|
writeFileSync(file, `${JSON.stringify(record)}
|
|
5199
5669
|
`, "utf8");
|
|
@@ -5262,12 +5732,14 @@ async function runStart() {
|
|
|
5262
5732
|
return;
|
|
5263
5733
|
}
|
|
5264
5734
|
const status = await getServiceAdapter().install();
|
|
5265
|
-
console.log(
|
|
5735
|
+
console.log(
|
|
5736
|
+
process.platform === "win32" ? "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF1B\u6CE8\u610F\uFF1AWindows \u8BA1\u5212\u4EFB\u52A1\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002" : "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002"
|
|
5737
|
+
);
|
|
5266
5738
|
printStatus(status);
|
|
5267
5739
|
}
|
|
5268
5740
|
async function runStop() {
|
|
5269
5741
|
await getServiceAdapter().uninstall();
|
|
5270
|
-
console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\
|
|
5742
|
+
console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\u81EA\u542F\uFF08\u5DF2\u79FB\u9664\u670D\u52A1\u5B9A\u4E49\uFF09\u3002");
|
|
5271
5743
|
}
|
|
5272
5744
|
async function runRestart() {
|
|
5273
5745
|
const status = await getServiceAdapter().restart();
|
|
@@ -5281,17 +5753,18 @@ async function runLogs(follow) {
|
|
|
5281
5753
|
await getServiceAdapter().logs(follow);
|
|
5282
5754
|
}
|
|
5283
5755
|
function printStatus(status) {
|
|
5284
|
-
console.log(`
|
|
5756
|
+
console.log(`service: ${status.platformName}`);
|
|
5757
|
+
console.log(`path: ${status.servicePath}`);
|
|
5285
5758
|
console.log(`installed: ${status.installed ? "yes" : "no"}`);
|
|
5286
|
-
console.log(`
|
|
5759
|
+
console.log(`running: ${status.running ? "yes" : "no"}`);
|
|
5287
5760
|
console.log(`pid: ${status.pid ?? "-"}`);
|
|
5288
5761
|
console.log(`last exit: ${status.lastExit ?? "-"}`);
|
|
5289
5762
|
console.log(`stdout: ${status.stdoutPath}`);
|
|
5290
5763
|
console.log(`stderr: ${status.stderrPath}`);
|
|
5291
5764
|
if (!status.installed) {
|
|
5292
5765
|
console.log("\u63D0\u793A\uFF1A\u540E\u53F0\u670D\u52A1\u5C1A\u672A\u5B89\u88C5\uFF0C\u8FD0\u884C `feishu-codex-bridge start`\u3002");
|
|
5293
|
-
} else if (!status.
|
|
5294
|
-
console.log("\u63D0\u793A\
|
|
5766
|
+
} else if (!status.running) {
|
|
5767
|
+
console.log("\u63D0\u793A\uFF1A\u670D\u52A1\u5DF2\u6CE8\u518C\u4F46\u5F53\u524D\u672A\u8FD0\u884C\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
|
|
5295
5768
|
}
|
|
5296
5769
|
}
|
|
5297
5770
|
|
|
@@ -5344,7 +5817,7 @@ async function runUpdate(opts = {}) {
|
|
|
5344
5817
|
}
|
|
5345
5818
|
|
|
5346
5819
|
// src/cli/commands/bot.ts
|
|
5347
|
-
import { rm as
|
|
5820
|
+
import { rm as rm5 } from "fs/promises";
|
|
5348
5821
|
async function runBotInit(name) {
|
|
5349
5822
|
if (!ensureCodex()) {
|
|
5350
5823
|
process.exitCode = 1;
|
|
@@ -5357,6 +5830,7 @@ async function runBotInit(name) {
|
|
|
5357
5830
|
}
|
|
5358
5831
|
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
5832
|
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");
|
|
5833
|
+
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
5834
|
console.log(" 2) \u521B\u5EFA\u5E76\u53D1\u5E03\u5E94\u7528\u7248\u672C");
|
|
5361
5835
|
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
5836
|
}
|
|
@@ -5398,7 +5872,7 @@ async function runBotRm(name) {
|
|
|
5398
5872
|
}
|
|
5399
5873
|
const after = await removeBot(bot2.appId);
|
|
5400
5874
|
await removeSecret(secretKeyForApp(bot2.appId));
|
|
5401
|
-
await
|
|
5875
|
+
await rm5(botDir(bot2.appId), { recursive: true, force: true });
|
|
5402
5876
|
console.log(`\u2713 \u5DF2\u79FB\u9664\u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId})\uFF1A\u6CE8\u518C\u8868 + \u5BC6\u94A5 + \u72B6\u6001\u76EE\u5F55(projects/sessions)\u3002`);
|
|
5403
5877
|
if (after.bots.length === 0) {
|
|
5404
5878
|
console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
|
|
@@ -5456,15 +5930,15 @@ async function secretsRemove(id) {
|
|
|
5456
5930
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
5457
5931
|
}
|
|
5458
5932
|
function readStdin() {
|
|
5459
|
-
return new Promise((
|
|
5933
|
+
return new Promise((resolve7) => {
|
|
5460
5934
|
let data = "";
|
|
5461
5935
|
if (process.stdin.isTTY) {
|
|
5462
|
-
|
|
5936
|
+
resolve7("");
|
|
5463
5937
|
return;
|
|
5464
5938
|
}
|
|
5465
5939
|
process.stdin.setEncoding("utf8");
|
|
5466
5940
|
process.stdin.on("data", (c) => data += c);
|
|
5467
|
-
process.stdin.on("end", () =>
|
|
5941
|
+
process.stdin.on("end", () => resolve7(data));
|
|
5468
5942
|
});
|
|
5469
5943
|
}
|
|
5470
5944
|
|