@modelzen/feishu-codex-bridge 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +735 -100
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -78,6 +78,7 @@ import { dirname as dirname3, join as join2 } from "path";
|
|
|
78
78
|
|
|
79
79
|
// src/config/store.ts
|
|
80
80
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
81
|
+
import { randomUUID } from "crypto";
|
|
81
82
|
import { dirname as dirname2 } from "path";
|
|
82
83
|
|
|
83
84
|
// src/config/schema.ts
|
|
@@ -114,18 +115,23 @@ function getRunIdleTimeoutMs(cfg) {
|
|
|
114
115
|
const clamped = Math.min(Math.max(Math.floor(raw), 10), 1800);
|
|
115
116
|
return clamped * 1e3;
|
|
116
117
|
}
|
|
117
|
-
function isUserAllowed(cfg, senderId) {
|
|
118
|
-
const list = cfg.preferences?.access?.allowedUsers;
|
|
119
|
-
if (!list || list.length === 0) return true;
|
|
120
|
-
return list.includes(senderId);
|
|
121
|
-
}
|
|
122
118
|
function isChatAllowed(cfg, chatId) {
|
|
123
119
|
const list = cfg.preferences?.access?.allowedChats;
|
|
124
120
|
if (!list || list.length === 0) return true;
|
|
125
121
|
return list.includes(chatId);
|
|
126
122
|
}
|
|
123
|
+
function resolveOwner(cfg) {
|
|
124
|
+
const access = cfg.preferences?.access;
|
|
125
|
+
return access?.ownerOpenId ?? access?.admins?.[0];
|
|
126
|
+
}
|
|
127
127
|
function isAdmin(cfg, senderId) {
|
|
128
|
-
|
|
128
|
+
if (!senderId) return false;
|
|
129
|
+
if (senderId === resolveOwner(cfg)) return true;
|
|
130
|
+
return Boolean(cfg.preferences?.access?.admins?.includes(senderId));
|
|
131
|
+
}
|
|
132
|
+
function isUserAllowedInProject(cfg, project, senderId) {
|
|
133
|
+
if (isAdmin(cfg, senderId)) return true;
|
|
134
|
+
const list = project?.allowedUsers;
|
|
129
135
|
if (!list || list.length === 0) return true;
|
|
130
136
|
return list.includes(senderId);
|
|
131
137
|
}
|
|
@@ -174,13 +180,21 @@ exec ${sq(node)} ${sq(bridgeEntry)} secrets get "$@"
|
|
|
174
180
|
await rename(tmp, wrapperPath);
|
|
175
181
|
return wrapperPath;
|
|
176
182
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
183
|
+
var saveChain = Promise.resolve();
|
|
184
|
+
function saveConfig(cfg, path = paths.configFile) {
|
|
185
|
+
const run = saveChain.then(async () => {
|
|
186
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
187
|
+
const tmp = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
188
|
+
await writeFile(tmp, `${JSON.stringify(cfg, null, 2)}
|
|
181
189
|
`, "utf8");
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
await chmod(tmp, 384);
|
|
191
|
+
await rename(tmp, path);
|
|
192
|
+
});
|
|
193
|
+
saveChain = run.then(
|
|
194
|
+
() => void 0,
|
|
195
|
+
() => void 0
|
|
196
|
+
);
|
|
197
|
+
return run;
|
|
184
198
|
}
|
|
185
199
|
|
|
186
200
|
// src/config/bots.ts
|
|
@@ -228,6 +242,21 @@ function findBot(reg, nameOrAppId) {
|
|
|
228
242
|
function currentBot(reg) {
|
|
229
243
|
return reg.current ? reg.bots.find((b) => b.appId === reg.current) : void 0;
|
|
230
244
|
}
|
|
245
|
+
function activeBots(reg) {
|
|
246
|
+
const configured = reg.bots.some((b) => b.active !== void 0);
|
|
247
|
+
if (configured) return reg.bots.filter((b) => b.active === true);
|
|
248
|
+
const cur = currentBot(reg);
|
|
249
|
+
return cur ? [cur] : [];
|
|
250
|
+
}
|
|
251
|
+
async function setActiveBots(appIds) {
|
|
252
|
+
const reg = await loadBots();
|
|
253
|
+
const want = new Set(appIds);
|
|
254
|
+
for (const b of reg.bots) b.active = want.has(b.appId);
|
|
255
|
+
const firstActive = reg.bots.find((b) => b.active);
|
|
256
|
+
if (firstActive) reg.current = firstActive.appId;
|
|
257
|
+
await saveBots(reg);
|
|
258
|
+
return reg;
|
|
259
|
+
}
|
|
231
260
|
async function addBot(entry) {
|
|
232
261
|
const reg = await loadBots();
|
|
233
262
|
reg.bots = reg.bots.filter((b) => b.appId !== entry.appId);
|
|
@@ -236,11 +265,6 @@ async function addBot(entry) {
|
|
|
236
265
|
await saveBots(reg);
|
|
237
266
|
return reg;
|
|
238
267
|
}
|
|
239
|
-
async function setCurrent(appId) {
|
|
240
|
-
const reg = await loadBots();
|
|
241
|
-
reg.current = appId;
|
|
242
|
-
await saveBots(reg);
|
|
243
|
-
}
|
|
244
268
|
async function removeBot(appId) {
|
|
245
269
|
const reg = await loadBots();
|
|
246
270
|
reg.bots = reg.bots.filter((b) => b.appId !== appId);
|
|
@@ -660,7 +684,7 @@ async function runRegistrationWizard() {
|
|
|
660
684
|
accounts: { app: { id: result.client_id, secret: result.client_secret, tenant } }
|
|
661
685
|
};
|
|
662
686
|
if (operatorOpenId) {
|
|
663
|
-
cfg.preferences = { access: { admins: [operatorOpenId] } };
|
|
687
|
+
cfg.preferences = { access: { ownerOpenId: operatorOpenId, admins: [operatorOpenId] } };
|
|
664
688
|
console.log(` Admin: ${operatorOpenId} (\u4F60\u81EA\u5DF1\uFF0C\u5DF2\u81EA\u52A8\u52A0\u5165\u7BA1\u7406\u5458\u540D\u5355)`);
|
|
665
689
|
} else {
|
|
666
690
|
console.log(
|
|
@@ -717,7 +741,13 @@ var JOIN_GROUP_SCOPES = [
|
|
|
717
741
|
"im:chat:readonly",
|
|
718
742
|
"im:chat.members:write_only"
|
|
719
743
|
];
|
|
720
|
-
var
|
|
744
|
+
var CONTACT_SCOPES = ["contact:user.base:readonly"];
|
|
745
|
+
var GRANT_SCOPES = [
|
|
746
|
+
...REQUIRED_SCOPES,
|
|
747
|
+
...COMMENT_SCOPES,
|
|
748
|
+
...JOIN_GROUP_SCOPES,
|
|
749
|
+
...CONTACT_SCOPES
|
|
750
|
+
];
|
|
721
751
|
var SCOPE_LABELS = {
|
|
722
752
|
"im:message.group_at_msg:readonly": "\u63A5\u6536\u7FA4\u91CC @\u673A\u5668\u4EBA \u7684\u6D88\u606F",
|
|
723
753
|
"im:message.group_msg": "\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\uFF08\u514D@\uFF09",
|
|
@@ -738,7 +768,8 @@ var SCOPE_LABELS = {
|
|
|
738
768
|
"cardkit:card:write": "\u4EA4\u4E92\u6309\u94AE\u5361\u7247",
|
|
739
769
|
"docs:document.comment:read": "\u8BFB\u53D6\u6587\u6863\u8BC4\u8BBA",
|
|
740
770
|
"docs:document.comment:create": "\u53D1\u8868\u6587\u6863\u8BC4\u8BBA\u56DE\u590D",
|
|
741
|
-
"wiki:wiki:readonly": "\u8BFB\u53D6\u77E5\u8BC6\u5E93\u8282\u70B9"
|
|
771
|
+
"wiki:wiki:readonly": "\u8BFB\u53D6\u77E5\u8BC6\u5E93\u8282\u70B9",
|
|
772
|
+
"contact:user.base:readonly": "\u8BFB\u53D6\u6210\u5458\u59D3\u540D\uFF08\u7BA1\u7406\u5458 / \u767D\u540D\u5355\u5C55\u793A\uFF09"
|
|
742
773
|
};
|
|
743
774
|
function labelScope(scope) {
|
|
744
775
|
const label = SCOPE_LABELS[scope];
|
|
@@ -1026,8 +1057,14 @@ function ensureCodex() {
|
|
|
1026
1057
|
async function ensureOnboarded(opts = {}) {
|
|
1027
1058
|
if (!ensureCodex()) return null;
|
|
1028
1059
|
const reg = await ensureRegistry();
|
|
1029
|
-
const entry = currentBot(reg);
|
|
1060
|
+
const entry = opts.bot ? findBot(reg, opts.bot) : currentBot(reg);
|
|
1030
1061
|
if (!entry) {
|
|
1062
|
+
if (opts.bot) {
|
|
1063
|
+
console.error(
|
|
1064
|
+
`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\u300C${opts.bot}\u300D\u3002\u7528 \`feishu-codex-bridge bot list\` \u67E5\u770B\u5DF2\u6CE8\u518C\u7684\u673A\u5668\u4EBA\u3002`
|
|
1065
|
+
);
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1031
1068
|
if (!opts.allowCreate) {
|
|
1032
1069
|
console.error("\u2717 \u5C1A\u672A\u914D\u7F6E\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u8BF7\u5148\u8FD0\u884C `feishu-codex-bridge bot init`\uFF08\u6216\u524D\u53F0 `run`\uFF09\u626B\u7801\u521B\u5EFA\u3002");
|
|
1033
1070
|
return null;
|
|
@@ -2152,6 +2189,14 @@ function selectStatic(opts) {
|
|
|
2152
2189
|
behaviors: [{ type: "callback", value: { a: opts.actionId } }]
|
|
2153
2190
|
};
|
|
2154
2191
|
}
|
|
2192
|
+
function selectMenu(opts) {
|
|
2193
|
+
return {
|
|
2194
|
+
tag: "select_static",
|
|
2195
|
+
name: opts.name,
|
|
2196
|
+
placeholder: { tag: "plain_text", content: opts.placeholder },
|
|
2197
|
+
options: opts.options.map((o) => ({ text: { tag: "plain_text", content: o.label }, value: o.value }))
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2155
2200
|
|
|
2156
2201
|
// src/card/command-cards.ts
|
|
2157
2202
|
var MC = {
|
|
@@ -2259,19 +2304,13 @@ function pickerTime(unixSeconds) {
|
|
|
2259
2304
|
function talkLine(noMention, tail) {
|
|
2260
2305
|
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`;
|
|
2261
2306
|
}
|
|
2262
|
-
function buildHelpCard(scope, noMention = true) {
|
|
2307
|
+
function buildHelpCard(scope, noMention = true, isAdmin2 = false) {
|
|
2263
2308
|
const elements = [];
|
|
2264
2309
|
if (scope === "single") {
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
`${talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406")}
|
|
2270
|
-
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2271
|
-
\xB7 \`/settings\` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09
|
|
2272
|
-
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2273
|
-
)
|
|
2274
|
-
);
|
|
2310
|
+
const lines = [talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"), "\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6"];
|
|
2311
|
+
if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
2312
|
+
lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
2313
|
+
elements.push(md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"), hr(), md(lines.join("\n")));
|
|
2275
2314
|
} else if (scope === "topic") {
|
|
2276
2315
|
elements.push(
|
|
2277
2316
|
md("\u{1F9F5} **\u8BDD\u9898\u5185** \u2014 \u6BCF\u4E2A\u8BDD\u9898\u662F\u4E00\u4E2A\u72EC\u7ACB\u4F1A\u8BDD\u3002"),
|
|
@@ -2284,13 +2323,10 @@ function buildHelpCard(scope, noMention = true) {
|
|
|
2284
2323
|
note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
|
|
2285
2324
|
);
|
|
2286
2325
|
} else {
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
"\xB7 **@\u6211 + \u5185\u5BB9** \u2192 \u5F00\u4E00\u4E2A\u65B0\u8BDD\u9898\u5E76\u5F00\u59CB\n\xB7 `/resume` \u2192 \u6062\u590D\u5386\u53F2\u4F1A\u8BDD\n\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09\n\xB7 `/model` \u2192 \u9700\u8981\u5728\u8BDD\u9898\u91CC\u7528\n\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361"
|
|
2292
|
-
)
|
|
2293
|
-
);
|
|
2326
|
+
const lines = ["\xB7 **@\u6211 + \u5185\u5BB9** \u2192 \u5F00\u4E00\u4E2A\u65B0\u8BDD\u9898\u5E76\u5F00\u59CB"];
|
|
2327
|
+
if (isAdmin2) lines.push("\xB7 `/resume` \u2192 \u6062\u590D\u5386\u53F2\u4F1A\u8BDD", "\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
2328
|
+
lines.push("\xB7 `/model` \u2192 \u9700\u8981\u5728\u8BDD\u9898\u91CC\u7528", "\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
2329
|
+
elements.push(md("\u{1F465} **\u4E3B\u7FA4\u533A** \u2014 @\u6211\u5F00\u8BDD\u9898\uFF0C\u6BCF\u4E2A\u8BDD\u9898\u662F\u72EC\u7ACB\u4F1A\u8BDD\u3002"), hr(), md(lines.join("\n")));
|
|
2294
2330
|
}
|
|
2295
2331
|
return card(elements, { header: { title: "\u{1F916} \u53EF\u7528\u547D\u4EE4", template: "blue" }, summary: "\u53EF\u7528\u547D\u4EE4" });
|
|
2296
2332
|
}
|
|
@@ -3097,6 +3133,7 @@ async function uploadBuffer(channel, buffer) {
|
|
|
3097
3133
|
|
|
3098
3134
|
// src/project/registry.ts
|
|
3099
3135
|
import { mkdir as mkdir4, readFile as readFile6, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
3136
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3100
3137
|
import { dirname as dirname5 } from "path";
|
|
3101
3138
|
function defaultNoMention(p) {
|
|
3102
3139
|
return !((p.origin ?? "created") === "joined" && (p.kind ?? "multi") === "single");
|
|
@@ -3112,9 +3149,18 @@ async function read() {
|
|
|
3112
3149
|
throw err;
|
|
3113
3150
|
}
|
|
3114
3151
|
}
|
|
3152
|
+
var opChain = Promise.resolve();
|
|
3153
|
+
function withLock(fn) {
|
|
3154
|
+
const run = opChain.then(fn, fn);
|
|
3155
|
+
opChain = run.then(
|
|
3156
|
+
() => void 0,
|
|
3157
|
+
() => void 0
|
|
3158
|
+
);
|
|
3159
|
+
return run;
|
|
3160
|
+
}
|
|
3115
3161
|
async function write(projects) {
|
|
3116
3162
|
await mkdir4(dirname5(paths.projectsFile), { recursive: true });
|
|
3117
|
-
const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
|
|
3163
|
+
const tmp = `${paths.projectsFile}.tmp-${process.pid}-${randomUUID2()}`;
|
|
3118
3164
|
const body = { version: FILE_VERSION2, projects };
|
|
3119
3165
|
await writeFile4(tmp, `${JSON.stringify(body, null, 2)}
|
|
3120
3166
|
`, "utf8");
|
|
@@ -3130,34 +3176,41 @@ async function getProjectByName(name) {
|
|
|
3130
3176
|
return (await read()).find((p) => p.name === name);
|
|
3131
3177
|
}
|
|
3132
3178
|
async function addProject(p) {
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3179
|
+
return withLock(async () => {
|
|
3180
|
+
const projects = await read();
|
|
3181
|
+
if (projects.some((x) => x.name === p.name)) {
|
|
3182
|
+
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
3183
|
+
}
|
|
3184
|
+
if (p.chatId) {
|
|
3185
|
+
const bound = projects.find((x) => x.chatId === p.chatId);
|
|
3186
|
+
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
3187
|
+
}
|
|
3188
|
+
projects.push(p);
|
|
3189
|
+
await write(projects);
|
|
3190
|
+
});
|
|
3143
3191
|
}
|
|
3144
3192
|
async function updateProject(name, patch) {
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3193
|
+
return withLock(async () => {
|
|
3194
|
+
const projects = await read();
|
|
3195
|
+
const p = projects.find((x) => x.name === name);
|
|
3196
|
+
if (!p) return;
|
|
3197
|
+
const actual = typeof patch === "function" ? patch(p) : patch;
|
|
3198
|
+
const target = p;
|
|
3199
|
+
for (const [k, v] of Object.entries(actual)) {
|
|
3200
|
+
if (v !== void 0) target[k] = v;
|
|
3201
|
+
}
|
|
3202
|
+
await write(projects);
|
|
3203
|
+
});
|
|
3153
3204
|
}
|
|
3154
3205
|
async function removeProject(name) {
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3206
|
+
return withLock(async () => {
|
|
3207
|
+
const projects = await read();
|
|
3208
|
+
const idx = projects.findIndex((p) => p.name === name);
|
|
3209
|
+
if (idx === -1) return void 0;
|
|
3210
|
+
const [removed] = projects.splice(idx, 1);
|
|
3211
|
+
await write(projects);
|
|
3212
|
+
return removed;
|
|
3213
|
+
});
|
|
3161
3214
|
}
|
|
3162
3215
|
|
|
3163
3216
|
// src/card/dm-cards.ts
|
|
@@ -3182,7 +3235,19 @@ var DM = {
|
|
|
3182
3235
|
setTools: "dm.set.tools",
|
|
3183
3236
|
setWatchdog: "dm.set.watchdog",
|
|
3184
3237
|
setPending: "dm.set.pending",
|
|
3185
|
-
setConcurrency: "dm.set.concurrency"
|
|
3238
|
+
setConcurrency: "dm.set.concurrency",
|
|
3239
|
+
// 权限管理:全局 admins(settings 卡进入)+ 项目响应白名单(项目列表 / 建项目完成卡进入)
|
|
3240
|
+
admins: "dm.admins",
|
|
3241
|
+
addAdminForm: "dm.admin.addForm",
|
|
3242
|
+
addAdminSubmit: "dm.admin.addSubmit",
|
|
3243
|
+
rmAdmin: "dm.admin.rm",
|
|
3244
|
+
allowlist: "dm.allowlist",
|
|
3245
|
+
addAllowedForm: "dm.allow.addForm",
|
|
3246
|
+
addAllowedSubmit: "dm.allow.addSubmit",
|
|
3247
|
+
rmAllowed: "dm.allow.rm",
|
|
3248
|
+
// 项目设置容器(项目列表 / 建项目完成卡 进入),以后的项目级设置项往这里加
|
|
3249
|
+
projectSettings: "dm.projectSettings",
|
|
3250
|
+
setNoMentionDm: "dm.proj.noMention"
|
|
3186
3251
|
};
|
|
3187
3252
|
var GS = {
|
|
3188
3253
|
setNoMention: "gs.noMention"
|
|
@@ -3448,7 +3513,13 @@ function buildNewProjectDoneCard(p) {
|
|
|
3448
3513
|
note(`\u{1F4C2} \`${p.cwd}\` \xB7 ${kindLabel(p.kind)}`),
|
|
3449
3514
|
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")
|
|
3450
3515
|
];
|
|
3451
|
-
if (p.chatId)
|
|
3516
|
+
if (p.chatId)
|
|
3517
|
+
elements.push(
|
|
3518
|
+
actions([
|
|
3519
|
+
linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId), "primary"),
|
|
3520
|
+
button("\u2699\uFE0F \u9879\u76EE\u8BBE\u7F6E", { a: DM.projectSettings, n: p.name })
|
|
3521
|
+
])
|
|
3522
|
+
);
|
|
3452
3523
|
return card(elements, { header: { title, template: "green" } });
|
|
3453
3524
|
}
|
|
3454
3525
|
function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
|
|
@@ -3479,6 +3550,7 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3479
3550
|
}
|
|
3480
3551
|
const row = [];
|
|
3481
3552
|
if (p.chatId) row.push(linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId)));
|
|
3553
|
+
row.push(button("\u2699\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: p.name }));
|
|
3482
3554
|
row.push(button("\u{1F5D1} \u5220\u9664", { a: DM.rmConfirm, n: p.name }, "danger"));
|
|
3483
3555
|
elements.push(actions(row));
|
|
3484
3556
|
elements.push(hr());
|
|
@@ -3533,7 +3605,8 @@ function buildSettingsCard(cfg) {
|
|
|
3533
3605
|
{ label: "20", value: "20" }
|
|
3534
3606
|
]),
|
|
3535
3607
|
note("\u26A0\uFE0F \u5047\u6B7B\u8D85\u65F6 / \u5E76\u53D1\u4E0A\u9650 \u6539\u540E\u9700**\u91CD\u542F**\u751F\u6548\uFF1B\u5DE5\u5177\u663E\u793A / \u8FD0\u884C\u4E2D\u65B0\u6D88\u606F \u5373\u65F6\u751F\u6548\u3002"),
|
|
3536
|
-
|
|
3608
|
+
hr(),
|
|
3609
|
+
actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
|
|
3537
3610
|
],
|
|
3538
3611
|
{ header: { title: "\u2699\uFE0F \u8BBE\u7F6E", template: "blue" } }
|
|
3539
3612
|
);
|
|
@@ -3556,6 +3629,146 @@ function buildGroupSettingsCard(project) {
|
|
|
3556
3629
|
{ header: { title: "\u2699\uFE0F \u7FA4\u8BBE\u7F6E", template: "blue" } }
|
|
3557
3630
|
);
|
|
3558
3631
|
}
|
|
3632
|
+
function memberName(names, id) {
|
|
3633
|
+
return names.get(id) ?? `\u2026${id.slice(-6)}`;
|
|
3634
|
+
}
|
|
3635
|
+
function buildAdminsCard(cfg, names) {
|
|
3636
|
+
const owner = resolveOwner(cfg);
|
|
3637
|
+
const admins = cfg.preferences?.access?.admins ?? [];
|
|
3638
|
+
const elements = [md("**\u7BA1\u7406\u5458\u540D\u5355** \xB7 \u672C bot \u5168\u5C40\uFF08\u53EF\u79C1\u804A\u7BA1\u7406 / \u5EFA\u9879\u76EE / \u9500\u6BC1\u64CD\u4F5C\uFF09"), hr()];
|
|
3639
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3640
|
+
if (owner) {
|
|
3641
|
+
seen.add(owner);
|
|
3642
|
+
elements.push(actions([md(`\u{1F451} **${memberName(names, owner)}** \xB7 Bot \u62E5\u6709\u8005\uFF08\u6CE8\u518C\u8005\uFF09`)]));
|
|
3643
|
+
}
|
|
3644
|
+
let extra = 0;
|
|
3645
|
+
for (const id of admins) {
|
|
3646
|
+
if (seen.has(id)) continue;
|
|
3647
|
+
seen.add(id);
|
|
3648
|
+
extra++;
|
|
3649
|
+
elements.push(actions([md(memberName(names, id)), button("\u{1F5D1} \u79FB\u9664", { a: DM.rmAdmin, u: id }, "danger")]));
|
|
3650
|
+
}
|
|
3651
|
+
if (extra === 0) elements.push(note("\u6682\u65E0\u989D\u5916\u7BA1\u7406\u5458\u3002"));
|
|
3652
|
+
elements.push(
|
|
3653
|
+
hr(),
|
|
3654
|
+
actions([button("\u2795 \u6DFB\u52A0\u7BA1\u7406\u5458", { a: DM.addAdminForm }, "primary"), button("\u2B05\uFE0F \u8BBE\u7F6E", { a: DM.settings })]),
|
|
3655
|
+
note("\u{1F451} Bot \u62E5\u6709\u8005\uFF08\u6CE8\u518C\u6B64 bot \u7684\u4EBA\uFF09\u6052\u4E3A\u7BA1\u7406\u5458\uFF0C\u4E0D\u53EF\u79FB\u9664\uFF1B\u540D\u5355\u4E3A\u7A7A\u65F6\u4EC5\u62E5\u6709\u8005\u53EF\u7BA1\u7406\u3002")
|
|
3656
|
+
);
|
|
3657
|
+
return card(elements, { header: { title: "\u{1F46E} \u7BA1\u7406\u5458", template: "blue" } });
|
|
3658
|
+
}
|
|
3659
|
+
function buildAddAdminCard(members) {
|
|
3660
|
+
const MAX = 50;
|
|
3661
|
+
const shown = members.slice(0, MAX);
|
|
3662
|
+
const formEls = [];
|
|
3663
|
+
if (shown.length > 0) {
|
|
3664
|
+
formEls.push(
|
|
3665
|
+
selectMenu({
|
|
3666
|
+
name: "pick",
|
|
3667
|
+
placeholder: "\u4ECE\u9879\u76EE\u7FA4\u6210\u5458\u9009\u62E9",
|
|
3668
|
+
options: shown.map((m) => ({ label: m.name, value: m.openId }))
|
|
3669
|
+
})
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
formEls.push(
|
|
3673
|
+
input({
|
|
3674
|
+
name: "open_id",
|
|
3675
|
+
label: shown.length ? "\u6216\u76F4\u63A5\u8F93\u5165 open_id" : "\u8F93\u5165 open_id\uFF08\u672A\u8BFB\u53D6\u5230\u9879\u76EE\u7FA4\u6210\u5458\uFF09",
|
|
3676
|
+
placeholder: "ou_xxx"
|
|
3677
|
+
}),
|
|
3678
|
+
actions([submitButton("\u2705 \u786E\u8BA4\u6DFB\u52A0", { a: DM.addAdminSubmit }, "primary", "submit_admin")])
|
|
3679
|
+
);
|
|
3680
|
+
const tail = [];
|
|
3681
|
+
if (members.length > MAX) tail.push(note(`\u5019\u9009\u8F83\u591A\uFF0C\u4EC5\u5217\u524D ${MAX} \u4E2A\uFF1B\u5176\u4F59\u8BF7\u76F4\u63A5\u8F93\u5165 open_id\u3002`));
|
|
3682
|
+
return card(
|
|
3683
|
+
[
|
|
3684
|
+
md("**\u6DFB\u52A0\u7BA1\u7406\u5458** \xB7 \u4ECE\u9879\u76EE\u7FA4\u6210\u5458\u9009\uFF0C\u6216\u8F93\u5165 open_id"),
|
|
3685
|
+
form("add_admin", formEls),
|
|
3686
|
+
...tail,
|
|
3687
|
+
actions([button("\u2B05\uFE0F \u53D6\u6D88", { a: DM.admins })])
|
|
3688
|
+
],
|
|
3689
|
+
{ header: { title: "\u2795 \u6DFB\u52A0\u7BA1\u7406\u5458", template: "blue" } }
|
|
3690
|
+
);
|
|
3691
|
+
}
|
|
3692
|
+
function buildProjectSettingsCard(project) {
|
|
3693
|
+
const kind = project.kind ?? "multi";
|
|
3694
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3695
|
+
return card(
|
|
3696
|
+
[
|
|
3697
|
+
md(`**\u9879\u76EE\u8BBE\u7F6E** \xB7 ${project.name}`),
|
|
3698
|
+
note(`${kindLabel(kind)}${project.cwd ? ` \xB7 \u{1F4C2} \`${project.cwd}\`` : ""}`),
|
|
3699
|
+
hr(),
|
|
3700
|
+
md("\u270B \u514D@\uFF08\u4E0D\u7528 @ \u4E5F\u56DE\u590D\uFF09"),
|
|
3701
|
+
actions([
|
|
3702
|
+
button("\u5F00", { a: DM.setNoMentionDm, v: "on", n: project.name }, noMention ? "primary" : "default"),
|
|
3703
|
+
button("\u5173", { a: DM.setNoMentionDm, v: "off", n: project.name }, noMention ? "default" : "primary")
|
|
3704
|
+
]),
|
|
3705
|
+
note(
|
|
3706
|
+
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\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u5904\u7406\uFF1B**\u5F00\u65B0\u8BDD\u9898\u4ECD\u9700 @\u6211**\u3002"
|
|
3707
|
+
),
|
|
3708
|
+
hr(),
|
|
3709
|
+
actions([button("\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", { a: DM.allowlist, n: project.name }, "primary")]),
|
|
3710
|
+
note("\u8BBE\u7F6E\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex\uFF08\u7A7A = \u6240\u6709\u4EBA\uFF09\u3002"),
|
|
3711
|
+
hr(),
|
|
3712
|
+
actions([button("\u2B05\uFE0F \u9879\u76EE\u5217\u8868", { a: DM.projects })])
|
|
3713
|
+
],
|
|
3714
|
+
{ header: { title: "\u2699\uFE0F \u9879\u76EE\u8BBE\u7F6E", template: "blue" } }
|
|
3715
|
+
);
|
|
3716
|
+
}
|
|
3717
|
+
function buildAllowlistCard(project, names) {
|
|
3718
|
+
const list = project.allowedUsers ?? [];
|
|
3719
|
+
const elements = [md(`**\u54CD\u5E94\u767D\u540D\u5355** \xB7 ${project.name}`), note("\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex"), hr()];
|
|
3720
|
+
if (list.length === 0) {
|
|
3721
|
+
elements.push(note("\u5F53\u524D**\u6240\u6709\u4EBA**\u53EF\u7528\uFF08\u7BA1\u7406\u5458\u59CB\u7EC8\u53EF\u7528\uFF09\u3002"));
|
|
3722
|
+
} else {
|
|
3723
|
+
for (const id of list) {
|
|
3724
|
+
elements.push(
|
|
3725
|
+
actions([md(memberName(names, id)), button("\u{1F5D1} \u79FB\u9664", { a: DM.rmAllowed, u: id, n: project.name }, "danger")])
|
|
3726
|
+
);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
elements.push(
|
|
3730
|
+
hr(),
|
|
3731
|
+
actions([
|
|
3732
|
+
button("\u2795 \u6DFB\u52A0", { a: DM.addAllowedForm, n: project.name }, "primary"),
|
|
3733
|
+
button("\u2B05\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: project.name })
|
|
3734
|
+
]),
|
|
3735
|
+
note("\u7BA1\u7406\u5458\u59CB\u7EC8\u53EF\u7528\uFF0C\u4E0D\u53D7\u6B64\u540D\u5355\u9650\u5236\uFF1B\u540D\u5355\u4E3A\u7A7A = \u6240\u6709\u4EBA\u53EF\u7528\u3002")
|
|
3736
|
+
);
|
|
3737
|
+
return card(elements, { header: { title: "\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", template: "blue" } });
|
|
3738
|
+
}
|
|
3739
|
+
function buildAddAllowedCard(projectName, members) {
|
|
3740
|
+
const MAX = 50;
|
|
3741
|
+
const shown = members.slice(0, MAX);
|
|
3742
|
+
const formEls = [];
|
|
3743
|
+
if (shown.length > 0) {
|
|
3744
|
+
formEls.push(
|
|
3745
|
+
selectMenu({
|
|
3746
|
+
name: "pick",
|
|
3747
|
+
placeholder: "\u4ECE\u7FA4\u6210\u5458\u9009\u62E9",
|
|
3748
|
+
options: shown.map((m) => ({ label: m.name, value: m.openId }))
|
|
3749
|
+
})
|
|
3750
|
+
);
|
|
3751
|
+
}
|
|
3752
|
+
formEls.push(
|
|
3753
|
+
input({
|
|
3754
|
+
name: "open_id",
|
|
3755
|
+
label: shown.length ? "\u6216\u76F4\u63A5\u8F93\u5165 open_id" : "\u8F93\u5165 open_id\uFF08\u672A\u8BFB\u53D6\u5230\u7FA4\u6210\u5458\uFF09",
|
|
3756
|
+
placeholder: "ou_xxx"
|
|
3757
|
+
}),
|
|
3758
|
+
actions([submitButton("\u2705 \u786E\u8BA4\u6DFB\u52A0", { a: DM.addAllowedSubmit, n: projectName }, "primary", "submit_allowed")])
|
|
3759
|
+
);
|
|
3760
|
+
const tail = [];
|
|
3761
|
+
if (members.length > MAX) tail.push(note(`\u7FA4\u6210\u5458\u8F83\u591A\uFF0C\u4EC5\u5217\u524D ${MAX} \u4E2A\uFF1B\u5176\u4F59\u8BF7\u76F4\u63A5\u8F93\u5165 open_id\u3002`));
|
|
3762
|
+
return card(
|
|
3763
|
+
[
|
|
3764
|
+
md(`**\u6DFB\u52A0\u53EF\u4F7F\u7528\u300C${projectName}\u300D\u7684\u4EBA**`),
|
|
3765
|
+
form("add_allowed", formEls),
|
|
3766
|
+
...tail,
|
|
3767
|
+
actions([button("\u2B05\uFE0F \u53D6\u6D88", { a: DM.allowlist, n: projectName })])
|
|
3768
|
+
],
|
|
3769
|
+
{ header: { title: "\u2795 \u6DFB\u52A0\u767D\u540D\u5355\u6210\u5458", template: "blue" } }
|
|
3770
|
+
);
|
|
3771
|
+
}
|
|
3559
3772
|
|
|
3560
3773
|
// src/service/update.ts
|
|
3561
3774
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
@@ -4829,6 +5042,59 @@ var Semaphore = class {
|
|
|
4829
5042
|
};
|
|
4830
5043
|
|
|
4831
5044
|
// src/bot/handle-message.ts
|
|
5045
|
+
async function resolveNames(channel, ids) {
|
|
5046
|
+
const uniq = [...new Set(ids.filter((x) => Boolean(x)))];
|
|
5047
|
+
const out = /* @__PURE__ */ new Map();
|
|
5048
|
+
if (uniq.length === 0) return out;
|
|
5049
|
+
try {
|
|
5050
|
+
const r = await channel.rawClient.contact.v3.user.batch({
|
|
5051
|
+
params: { user_ids: uniq, user_id_type: "open_id" }
|
|
5052
|
+
});
|
|
5053
|
+
for (const it of r.data?.items ?? []) {
|
|
5054
|
+
if (it.open_id && it.name) out.set(it.open_id, it.name);
|
|
5055
|
+
}
|
|
5056
|
+
} catch (err) {
|
|
5057
|
+
log.info("console", "resolve-names-fail", { n: uniq.length, err: String(err) });
|
|
5058
|
+
}
|
|
5059
|
+
return out;
|
|
5060
|
+
}
|
|
5061
|
+
async function fetchChatMembers(channel, chatId) {
|
|
5062
|
+
try {
|
|
5063
|
+
const r = await channel.rawClient.im.v1.chatMembers.get({
|
|
5064
|
+
path: { chat_id: chatId },
|
|
5065
|
+
params: { member_id_type: "open_id", page_size: 100 }
|
|
5066
|
+
});
|
|
5067
|
+
const out = [];
|
|
5068
|
+
for (const it of r.data?.items ?? []) {
|
|
5069
|
+
if (it.member_id) out.push({ openId: it.member_id, name: it.name || `\u2026${it.member_id.slice(-6)}` });
|
|
5070
|
+
}
|
|
5071
|
+
return out;
|
|
5072
|
+
} catch (err) {
|
|
5073
|
+
log.info("console", "fetch-members-fail", { chatId: chatId.slice(-6), err: String(err) });
|
|
5074
|
+
return [];
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
async function fetchAllProjectMembers(channel) {
|
|
5078
|
+
const projects = await listProjects();
|
|
5079
|
+
const lists = await Promise.all(projects.filter((p) => p.chatId).map((p) => fetchChatMembers(channel, p.chatId)));
|
|
5080
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5081
|
+
for (const members of lists) {
|
|
5082
|
+
for (const m of members) if (!seen.has(m.openId)) seen.set(m.openId, m.name);
|
|
5083
|
+
}
|
|
5084
|
+
return [...seen].map(([openId, name]) => ({ openId, name }));
|
|
5085
|
+
}
|
|
5086
|
+
function pickOpenId(formValue) {
|
|
5087
|
+
const raw = formValue?.pick;
|
|
5088
|
+
const cands = Array.isArray(raw) ? raw : [raw];
|
|
5089
|
+
for (const c of cands) {
|
|
5090
|
+
if (typeof c === "string" && c.startsWith("ou_")) return c;
|
|
5091
|
+
if (c && typeof c === "object") {
|
|
5092
|
+
const o = c;
|
|
5093
|
+
for (const v of [o.open_id, o.id, o.value]) if (typeof v === "string" && v.startsWith("ou_")) return v;
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
return void 0;
|
|
5097
|
+
}
|
|
4832
5098
|
function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
4833
5099
|
const backend = createBackend();
|
|
4834
5100
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -4906,7 +5172,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4906
5172
|
}
|
|
4907
5173
|
const project = await getProjectByChatId(msg.chatId);
|
|
4908
5174
|
if (!msg.mentionedBot && !(project && shouldRespondWithoutMention(project, msg))) return;
|
|
4909
|
-
if (!isChatAllowed(cfg, msg.chatId) || !
|
|
5175
|
+
if (!isChatAllowed(cfg, msg.chatId) || !isUserAllowedInProject(cfg, project, msg.senderId)) {
|
|
4910
5176
|
log.info("intake", "reject", { reason: "not_allowed", chatId: msg.chatId.slice(-6) });
|
|
4911
5177
|
return;
|
|
4912
5178
|
}
|
|
@@ -4980,9 +5246,13 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4980
5246
|
if ((project.kind ?? "multi") === "single") return true;
|
|
4981
5247
|
return Boolean(msg.threadId) || parseCommand(msg.content.trim()) !== null;
|
|
4982
5248
|
}
|
|
5249
|
+
async function denyAdminCommand(msg, cmd) {
|
|
5250
|
+
await channel.send(msg.chatId, { markdown: `\u26A0\uFE0F \`/${cmd}\` \u4EC5 bot \u7BA1\u7406\u5458\u53EF\u7528\u3002` }, { replyTo: msg.messageId }).catch(() => void 0);
|
|
5251
|
+
log.info("intake", "cmd-denied", { cmd });
|
|
5252
|
+
}
|
|
4983
5253
|
async function postGroupSettings(msg, project) {
|
|
4984
5254
|
if (!isAdmin(cfg, msg.senderId)) {
|
|
4985
|
-
await
|
|
5255
|
+
await denyAdminCommand(msg, "settings");
|
|
4986
5256
|
return;
|
|
4987
5257
|
}
|
|
4988
5258
|
if (!project) {
|
|
@@ -5134,6 +5404,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5134
5404
|
}).catch((err) => log.fail("intake", err));
|
|
5135
5405
|
}
|
|
5136
5406
|
async function postResumeCard(msg) {
|
|
5407
|
+
if (!isAdmin(cfg, msg.senderId)) {
|
|
5408
|
+
await denyAdminCommand(msg, "resume");
|
|
5409
|
+
return;
|
|
5410
|
+
}
|
|
5137
5411
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
5138
5412
|
const project = await getProjectByChatId(msg.chatId);
|
|
5139
5413
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
@@ -5175,7 +5449,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5175
5449
|
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
5176
5450
|
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
5177
5451
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
5178
|
-
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention), msg.messageId, inThread).catch(
|
|
5452
|
+
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention, isAdmin(cfg, msg.senderId)), msg.messageId, inThread).catch(
|
|
5179
5453
|
(err) => log.fail("card", err, { cmd: "help", scope })
|
|
5180
5454
|
);
|
|
5181
5455
|
log.info("card", "help", { scope });
|
|
@@ -5214,7 +5488,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5214
5488
|
return void 0;
|
|
5215
5489
|
}
|
|
5216
5490
|
const op = evt.operator?.openId ?? "";
|
|
5217
|
-
if (op !== state.requesterOpenId || !isChatAllowed(cfg, state.chatId)
|
|
5491
|
+
if (op !== state.requesterOpenId || !isChatAllowed(cfg, state.chatId)) {
|
|
5218
5492
|
log.info("card", "action-denied", { reason: "not-allowed" });
|
|
5219
5493
|
return void 0;
|
|
5220
5494
|
}
|
|
@@ -5250,7 +5524,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5250
5524
|
settleUpdate(evt.messageId, buildResumeLaunchingCard(state));
|
|
5251
5525
|
void resumeFromCard(evt, state, codexThreadId);
|
|
5252
5526
|
});
|
|
5253
|
-
const runAllowed = (evt) => isChatAllowed(cfg, evt.chatId)
|
|
5527
|
+
const runAllowed = (evt) => isChatAllowed(cfg, evt.chatId);
|
|
5254
5528
|
const runOwnerOrAdmin = (evt, ownerOpenId) => {
|
|
5255
5529
|
if (!runAllowed(evt)) return false;
|
|
5256
5530
|
const op = evt.operator?.openId ?? "";
|
|
@@ -5265,6 +5539,15 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5265
5539
|
});
|
|
5266
5540
|
const dmAdmin = (openId) => isAdmin(cfg, openId ?? "");
|
|
5267
5541
|
const patch = (evt, c) => settleUpdate(evt.messageId, c, evt.chatId);
|
|
5542
|
+
const namesWithOperator = async (evt, ids) => {
|
|
5543
|
+
const m = await resolveNames(channel, ids);
|
|
5544
|
+
if (ids.some((id) => id && !m.has(id))) {
|
|
5545
|
+
for (const mem of await fetchAllProjectMembers(channel)) if (!m.has(mem.openId)) m.set(mem.openId, mem.name);
|
|
5546
|
+
}
|
|
5547
|
+
const op = evt.operator;
|
|
5548
|
+
if (op?.openId && op.name && !m.has(op.openId)) m.set(op.openId, op.name);
|
|
5549
|
+
return m;
|
|
5550
|
+
};
|
|
5268
5551
|
function applyPref(evt, mut) {
|
|
5269
5552
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5270
5553
|
const prefs = { ...cfg.preferences ?? {} };
|
|
@@ -5488,6 +5771,107 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5488
5771
|
}
|
|
5489
5772
|
return buildGroupSettingsCard({ name: "\u672C\u7FA4", kind: "multi", noMention: on });
|
|
5490
5773
|
});
|
|
5774
|
+
}).on(DM.admins, ({ evt }) => {
|
|
5775
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5776
|
+
patch(
|
|
5777
|
+
evt,
|
|
5778
|
+
async () => buildAdminsCard(cfg, await namesWithOperator(evt, [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []]))
|
|
5779
|
+
);
|
|
5780
|
+
}).on(DM.addAdminForm, ({ evt }) => {
|
|
5781
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5782
|
+
patch(evt, async () => {
|
|
5783
|
+
const all = await fetchAllProjectMembers(channel);
|
|
5784
|
+
const members = all.filter((m) => !isAdmin(cfg, m.openId));
|
|
5785
|
+
return buildAddAdminCard(members);
|
|
5786
|
+
});
|
|
5787
|
+
}).on(DM.addAdminSubmit, ({ evt, formValue }) => {
|
|
5788
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5789
|
+
const manual = String(formValue?.open_id ?? "").trim();
|
|
5790
|
+
const id = manual.startsWith("ou_") ? manual : pickOpenId(formValue);
|
|
5791
|
+
log.info("console", "admin-add", { picked: id?.slice(-6) ?? null });
|
|
5792
|
+
void (async () => {
|
|
5793
|
+
if (id) {
|
|
5794
|
+
const access = { ...cfg.preferences?.access ?? {} };
|
|
5795
|
+
access.ownerOpenId ??= resolveOwner(cfg);
|
|
5796
|
+
access.admins = Array.from(/* @__PURE__ */ new Set([...access.admins ?? [], id]));
|
|
5797
|
+
cfg.preferences = { ...cfg.preferences ?? {}, access };
|
|
5798
|
+
await saveConfig(cfg).catch((e) => log.fail("console", e, { phase: "save-config" }));
|
|
5799
|
+
}
|
|
5800
|
+
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
5801
|
+
const next = buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
5802
|
+
await sendManagedCard(channel, evt.chatId, next).catch((e) => log.fail("console", e, { phase: "admin-add-result" }));
|
|
5803
|
+
})();
|
|
5804
|
+
}).on(DM.rmAdmin, ({ evt, value }) => {
|
|
5805
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5806
|
+
const id = typeof value.u === "string" ? value.u : "";
|
|
5807
|
+
patch(evt, async () => {
|
|
5808
|
+
if (id && id !== resolveOwner(cfg)) {
|
|
5809
|
+
const access = { ...cfg.preferences?.access ?? {} };
|
|
5810
|
+
access.ownerOpenId ??= resolveOwner(cfg);
|
|
5811
|
+
access.admins = (access.admins ?? []).filter((x) => x !== id);
|
|
5812
|
+
cfg.preferences = { ...cfg.preferences ?? {}, access };
|
|
5813
|
+
await saveConfig(cfg).catch((e) => log.fail("console", e, { phase: "save-config" }));
|
|
5814
|
+
}
|
|
5815
|
+
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
5816
|
+
return buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
5817
|
+
});
|
|
5818
|
+
}).on(DM.allowlist, ({ evt, value }) => {
|
|
5819
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5820
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5821
|
+
patch(evt, async () => {
|
|
5822
|
+
const p = await getProjectByName(name);
|
|
5823
|
+
if (!p) return buildDmMenuCard();
|
|
5824
|
+
return buildAllowlistCard(p, await namesWithOperator(evt, p.allowedUsers ?? []));
|
|
5825
|
+
});
|
|
5826
|
+
}).on(DM.addAllowedForm, ({ evt, value }) => {
|
|
5827
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5828
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5829
|
+
if (!name) return;
|
|
5830
|
+
patch(evt, async () => {
|
|
5831
|
+
const p = await getProjectByName(name);
|
|
5832
|
+
const members = p?.chatId ? await fetchChatMembers(channel, p.chatId) : [];
|
|
5833
|
+
return buildAddAllowedCard(name, members);
|
|
5834
|
+
});
|
|
5835
|
+
}).on(DM.addAllowedSubmit, ({ evt, value, formValue }) => {
|
|
5836
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5837
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5838
|
+
const manual = String(formValue?.open_id ?? "").trim();
|
|
5839
|
+
const id = manual.startsWith("ou_") ? manual : pickOpenId(formValue);
|
|
5840
|
+
log.info("console", "allow-add", { project: name, picked: id?.slice(-6) ?? null });
|
|
5841
|
+
void (async () => {
|
|
5842
|
+
if (id) await updateProject(name, (p) => ({ allowedUsers: Array.from(/* @__PURE__ */ new Set([...p.allowedUsers ?? [], id])) }));
|
|
5843
|
+
const fresh = await getProjectByName(name);
|
|
5844
|
+
if (!fresh) return;
|
|
5845
|
+
const card2 = buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
|
|
5846
|
+
await sendManagedCard(channel, evt.chatId, card2).catch((e) => log.fail("console", e, { phase: "allow-add-result" }));
|
|
5847
|
+
})();
|
|
5848
|
+
}).on(DM.rmAllowed, ({ evt, value }) => {
|
|
5849
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5850
|
+
const id = typeof value.u === "string" ? value.u : "";
|
|
5851
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5852
|
+
patch(evt, async () => {
|
|
5853
|
+
await updateProject(name, (p) => ({ allowedUsers: (p.allowedUsers ?? []).filter((x) => x !== id) }));
|
|
5854
|
+
const fresh = await getProjectByName(name);
|
|
5855
|
+
if (!fresh) return buildDmMenuCard();
|
|
5856
|
+
return buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
|
|
5857
|
+
});
|
|
5858
|
+
}).on(DM.projectSettings, ({ evt, value }) => {
|
|
5859
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5860
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5861
|
+
patch(evt, async () => {
|
|
5862
|
+
const p = await getProjectByName(name);
|
|
5863
|
+
return p ? buildProjectSettingsCard(p) : buildDmMenuCard();
|
|
5864
|
+
});
|
|
5865
|
+
}).on(DM.setNoMentionDm, ({ evt, value }) => {
|
|
5866
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5867
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5868
|
+
const on = value.v === "on";
|
|
5869
|
+
patch(evt, async () => {
|
|
5870
|
+
const p = await getProjectByName(name);
|
|
5871
|
+
if (!p) return buildDmMenuCard();
|
|
5872
|
+
await updateProject(name, { noMention: on });
|
|
5873
|
+
return buildProjectSettingsCard({ ...p, noMention: on });
|
|
5874
|
+
});
|
|
5491
5875
|
});
|
|
5492
5876
|
async function resumeFromCard(evt, state, codexThreadId) {
|
|
5493
5877
|
try {
|
|
@@ -5737,8 +6121,6 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5737
6121
|
if (!evt.mentionedBot) return log.info("comment", "skip", { reason: "not-mentioned" });
|
|
5738
6122
|
if (!SUPPORTED_FILE_TYPES.has(evt.fileType))
|
|
5739
6123
|
return log.info("comment", "skip", { reason: "unsupported-fileType", fileType: evt.fileType });
|
|
5740
|
-
if (!isUserAllowed(cfg, evt.operator.openId))
|
|
5741
|
-
return log.info("comment", "skip", { reason: "not-allowed" });
|
|
5742
6124
|
const resolved = await resolveComment(channel, evt);
|
|
5743
6125
|
if (!resolved) return log.info("comment", "skip", { reason: "no-target-or-empty" });
|
|
5744
6126
|
const { target, ctx } = resolved;
|
|
@@ -5956,6 +6338,94 @@ async function startBridge(opts) {
|
|
|
5956
6338
|
return { channel, shutdown };
|
|
5957
6339
|
}
|
|
5958
6340
|
|
|
6341
|
+
// src/bot/supervisor.ts
|
|
6342
|
+
var BACKOFF_MIN_MS = 1e3;
|
|
6343
|
+
var BACKOFF_MAX_MS = 3e4;
|
|
6344
|
+
var HEALTHY_UPTIME_MS = 6e4;
|
|
6345
|
+
var SHUTDOWN_GRACE_MS = 8e3;
|
|
6346
|
+
async function runSupervisor(bots) {
|
|
6347
|
+
const cliEntry = process.argv[1];
|
|
6348
|
+
if (!cliEntry) throw new Error("supervisor: \u65E0\u6CD5\u89E3\u6790 CLI \u5165\u53E3\uFF08process.argv[1] \u4E3A\u7A7A\uFF09");
|
|
6349
|
+
const childEnv = { ...process.env };
|
|
6350
|
+
delete childEnv[SERVICE_ENV_FLAG];
|
|
6351
|
+
let shuttingDown = false;
|
|
6352
|
+
const children = bots.map((bot2) => ({ bot: bot2, backoffMs: BACKOFF_MIN_MS, startedAt: 0 }));
|
|
6353
|
+
console.log(`
|
|
6354
|
+
\u6B63\u5728\u542F\u52A8 ${bots.length} \u4E2A\u673A\u5668\u4EBA\uFF08\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF09\uFF1A`);
|
|
6355
|
+
for (const b of bots) console.log(` \u2022 ${b.name} (${b.appId}) [${b.tenant}]`);
|
|
6356
|
+
console.log("Ctrl+C \u9000\u51FA\uFF08\u5173\u95ED\u5168\u90E8\uFF09\u3002\n");
|
|
6357
|
+
const prefixPipe = (name, src, dst) => {
|
|
6358
|
+
if (!src) return;
|
|
6359
|
+
let buf = "";
|
|
6360
|
+
src.setEncoding("utf8");
|
|
6361
|
+
src.on("data", (chunk) => {
|
|
6362
|
+
buf += chunk;
|
|
6363
|
+
let nl;
|
|
6364
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
6365
|
+
const line = buf.slice(0, nl);
|
|
6366
|
+
buf = buf.slice(nl + 1);
|
|
6367
|
+
dst.write(`\x1B[2m[${name}]\x1B[0m ${line}
|
|
6368
|
+
`);
|
|
6369
|
+
}
|
|
6370
|
+
});
|
|
6371
|
+
src.on("end", () => {
|
|
6372
|
+
if (buf) dst.write(`\x1B[2m[${name}]\x1B[0m ${buf}
|
|
6373
|
+
`);
|
|
6374
|
+
});
|
|
6375
|
+
};
|
|
6376
|
+
const spawnChild = (c) => {
|
|
6377
|
+
c.startedAt = Date.now();
|
|
6378
|
+
const proc = spawnProcess(process.execPath, [cliEntry, "run", "--bot", c.bot.appId], {
|
|
6379
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6380
|
+
env: childEnv
|
|
6381
|
+
});
|
|
6382
|
+
c.proc = proc;
|
|
6383
|
+
log.info("supervisor", "child-start", { bot: c.bot.name, appId: c.bot.appId, pid: proc.pid ?? null });
|
|
6384
|
+
prefixPipe(c.bot.name, proc.stdout, process.stdout);
|
|
6385
|
+
prefixPipe(c.bot.name, proc.stderr, process.stderr);
|
|
6386
|
+
proc.on("exit", (code, signal) => {
|
|
6387
|
+
c.proc = void 0;
|
|
6388
|
+
if (shuttingDown) return;
|
|
6389
|
+
const uptime = Date.now() - c.startedAt;
|
|
6390
|
+
if (uptime >= HEALTHY_UPTIME_MS) c.backoffMs = BACKOFF_MIN_MS;
|
|
6391
|
+
const wait = c.backoffMs;
|
|
6392
|
+
c.backoffMs = Math.min(c.backoffMs * 2, BACKOFF_MAX_MS);
|
|
6393
|
+
log.warn("supervisor", "child-exit", { bot: c.bot.name, code, signal, restartInMs: wait });
|
|
6394
|
+
console.error(
|
|
6395
|
+
`\x1B[2m[${c.bot.name}]\x1B[0m \u8FDB\u7A0B\u9000\u51FA\uFF08code=${code ?? signal ?? "?"}\uFF09\uFF0C${Math.round(wait / 1e3)}s \u540E\u91CD\u542F\u2026`
|
|
6396
|
+
);
|
|
6397
|
+
c.restartTimer = setTimeout(() => spawnChild(c), wait);
|
|
6398
|
+
});
|
|
6399
|
+
proc.on("error", (err) => {
|
|
6400
|
+
log.fail("supervisor", err, { bot: c.bot.name, phase: "spawn" });
|
|
6401
|
+
});
|
|
6402
|
+
};
|
|
6403
|
+
for (const c of children) spawnChild(c);
|
|
6404
|
+
await new Promise((resolve7) => {
|
|
6405
|
+
const stop = (sig) => {
|
|
6406
|
+
if (shuttingDown) return;
|
|
6407
|
+
shuttingDown = true;
|
|
6408
|
+
console.log(`
|
|
6409
|
+
\u6536\u5230 ${sig}\uFF0C\u6B63\u5728\u5173\u95ED\u5168\u90E8\u673A\u5668\u4EBA\u2026`);
|
|
6410
|
+
for (const c of children) {
|
|
6411
|
+
if (c.restartTimer) clearTimeout(c.restartTimer);
|
|
6412
|
+
c.proc?.kill("SIGTERM");
|
|
6413
|
+
}
|
|
6414
|
+
const deadline = Date.now() + SHUTDOWN_GRACE_MS;
|
|
6415
|
+
const poll = setInterval(() => {
|
|
6416
|
+
const alive = children.filter((c) => c.proc && !c.proc.killed);
|
|
6417
|
+
if (alive.length === 0 || Date.now() >= deadline) {
|
|
6418
|
+
clearInterval(poll);
|
|
6419
|
+
for (const c of alive) c.proc?.kill("SIGKILL");
|
|
6420
|
+
resolve7();
|
|
6421
|
+
}
|
|
6422
|
+
}, 200);
|
|
6423
|
+
};
|
|
6424
|
+
for (const sig of ["SIGINT", "SIGTERM"]) process.once(sig, () => stop(sig));
|
|
6425
|
+
});
|
|
6426
|
+
process.exit(0);
|
|
6427
|
+
}
|
|
6428
|
+
|
|
5959
6429
|
// src/core/single-instance.ts
|
|
5960
6430
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5961
6431
|
import { dirname as dirname11 } from "path";
|
|
@@ -6003,8 +6473,20 @@ function acquireSingleInstanceLock(appId) {
|
|
|
6003
6473
|
}
|
|
6004
6474
|
|
|
6005
6475
|
// src/cli/commands/run.ts
|
|
6006
|
-
async function runRun() {
|
|
6007
|
-
|
|
6476
|
+
async function runRun(botName) {
|
|
6477
|
+
if (botName) {
|
|
6478
|
+
await runSingle(botName);
|
|
6479
|
+
return;
|
|
6480
|
+
}
|
|
6481
|
+
const active = activeBots(await loadBots());
|
|
6482
|
+
if (active.length > 1) {
|
|
6483
|
+
await runSupervisor(active);
|
|
6484
|
+
return;
|
|
6485
|
+
}
|
|
6486
|
+
await runSingle(active[0]?.name);
|
|
6487
|
+
}
|
|
6488
|
+
async function runSingle(botName) {
|
|
6489
|
+
const ready = await ensureOnboarded({ allowCreate: !botName, bot: botName });
|
|
6008
6490
|
if (!ready) {
|
|
6009
6491
|
process.exitCode = 1;
|
|
6010
6492
|
return;
|
|
@@ -6047,14 +6529,37 @@ async function runRun() {
|
|
|
6047
6529
|
|
|
6048
6530
|
// src/cli/commands/daemon.ts
|
|
6049
6531
|
async function runStart() {
|
|
6050
|
-
const
|
|
6051
|
-
if (
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6532
|
+
const active = activeBots(await loadBots());
|
|
6533
|
+
if (active.length === 0) {
|
|
6534
|
+
const ready = await ensureOnboarded({ allowCreate: true });
|
|
6535
|
+
if (!ready) {
|
|
6536
|
+
process.exitCode = 1;
|
|
6537
|
+
return;
|
|
6538
|
+
}
|
|
6539
|
+
if (!await confirmReadyForDaemon(ready)) {
|
|
6540
|
+
process.exitCode = 1;
|
|
6541
|
+
return;
|
|
6542
|
+
}
|
|
6543
|
+
} else {
|
|
6544
|
+
if (active.length > 1) {
|
|
6545
|
+
console.log(`
|
|
6546
|
+
\u540E\u53F0\u670D\u52A1\u5C06\u6258\u7BA1 ${active.length} \u4E2A\u673A\u5668\u4EBA\uFF08supervisor \u591A\u8FDB\u7A0B\uFF0C\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF09\uFF1A`);
|
|
6547
|
+
for (const b of active) console.log(` \u2022 ${b.name} (${b.appId}) [${b.tenant}]`);
|
|
6548
|
+
console.log("");
|
|
6549
|
+
}
|
|
6550
|
+
for (const bot2 of active) {
|
|
6551
|
+
if (active.length > 1) console.log(`
|
|
6552
|
+
\u2500\u2500\u2500\u2500 \u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId}) \u2500\u2500\u2500\u2500`);
|
|
6553
|
+
const ready = await ensureOnboarded({ bot: bot2.appId });
|
|
6554
|
+
if (!ready) {
|
|
6555
|
+
process.exitCode = 1;
|
|
6556
|
+
return;
|
|
6557
|
+
}
|
|
6558
|
+
if (!await confirmReadyForDaemon(ready)) {
|
|
6559
|
+
process.exitCode = 1;
|
|
6560
|
+
return;
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6058
6563
|
}
|
|
6059
6564
|
const status = await getServiceAdapter().install();
|
|
6060
6565
|
console.log(installedNote());
|
|
@@ -6151,6 +6656,98 @@ async function runUpdate(opts = {}) {
|
|
|
6151
6656
|
|
|
6152
6657
|
// src/cli/commands/bot.ts
|
|
6153
6658
|
import { rm as rm5 } from "fs/promises";
|
|
6659
|
+
|
|
6660
|
+
// src/cli/checkbox.ts
|
|
6661
|
+
import { emitKeypressEvents } from "readline";
|
|
6662
|
+
var ESC = "\x1B";
|
|
6663
|
+
var HIDE_CURSOR = `${ESC}[?25l`;
|
|
6664
|
+
var SHOW_CURSOR = `${ESC}[?25h`;
|
|
6665
|
+
var ALT_SCREEN_ON = `${ESC}[?1049h`;
|
|
6666
|
+
var ALT_SCREEN_OFF = `${ESC}[?1049l`;
|
|
6667
|
+
var HOME_AND_CLEAR = `${ESC}[H${ESC}[2J`;
|
|
6668
|
+
async function checkboxSelect(title, items) {
|
|
6669
|
+
const input2 = process.stdin;
|
|
6670
|
+
const output = process.stdout;
|
|
6671
|
+
if (!input2.isTTY) throw new Error("checkboxSelect requires an interactive TTY");
|
|
6672
|
+
const checked = items.map((it) => Boolean(it.checked));
|
|
6673
|
+
let cursor = 0;
|
|
6674
|
+
const frame = () => {
|
|
6675
|
+
const lines = [title, ""];
|
|
6676
|
+
items.forEach((it, i) => {
|
|
6677
|
+
const box = checked[i] ? "\x1B[32m[x]\x1B[0m" : "[ ]";
|
|
6678
|
+
const pointer = i === cursor ? "\x1B[36m>\x1B[0m" : " ";
|
|
6679
|
+
const label = i === cursor ? `\x1B[1m${it.label}\x1B[0m` : it.label;
|
|
6680
|
+
const hint = it.hint ? ` \x1B[2m${it.hint}\x1B[0m` : "";
|
|
6681
|
+
lines.push(`${pointer} ${box} ${label}${hint}`);
|
|
6682
|
+
});
|
|
6683
|
+
const n = checked.filter(Boolean).length;
|
|
6684
|
+
lines.push("");
|
|
6685
|
+
lines.push(`\x1B[2m${n} \u4E2A\u5DF2\u52FE\u9009 \xB7 \u2191\u2193 \u79FB\u52A8 \xB7 \u7A7A\u683C\u52FE\u9009 \xB7 a \u5168\u9009 \xB7 \u56DE\u8F66\u786E\u8BA4 \xB7 q \u53D6\u6D88\x1B[0m`);
|
|
6686
|
+
return lines.join("\r\n");
|
|
6687
|
+
};
|
|
6688
|
+
const redraw = () => {
|
|
6689
|
+
output.write(HOME_AND_CLEAR + frame());
|
|
6690
|
+
};
|
|
6691
|
+
emitKeypressEvents(input2);
|
|
6692
|
+
const wasRaw = Boolean(input2.isRaw);
|
|
6693
|
+
let restored = false;
|
|
6694
|
+
const restore = () => {
|
|
6695
|
+
if (restored) return;
|
|
6696
|
+
restored = true;
|
|
6697
|
+
output.write(`${SHOW_CURSOR}${ALT_SCREEN_OFF}`);
|
|
6698
|
+
};
|
|
6699
|
+
input2.setRawMode?.(true);
|
|
6700
|
+
input2.resume();
|
|
6701
|
+
output.write(`${ALT_SCREEN_ON}${HIDE_CURSOR}`);
|
|
6702
|
+
process.once("exit", restore);
|
|
6703
|
+
redraw();
|
|
6704
|
+
return await new Promise((resolve7) => {
|
|
6705
|
+
const cleanup = () => {
|
|
6706
|
+
input2.off("keypress", onKey);
|
|
6707
|
+
input2.setRawMode?.(wasRaw);
|
|
6708
|
+
input2.pause();
|
|
6709
|
+
process.off("exit", restore);
|
|
6710
|
+
restore();
|
|
6711
|
+
};
|
|
6712
|
+
const onKey = (_str, key) => {
|
|
6713
|
+
const name = key?.name;
|
|
6714
|
+
if (key?.ctrl && name === "c" || name === "escape" || name === "q") {
|
|
6715
|
+
cleanup();
|
|
6716
|
+
resolve7(null);
|
|
6717
|
+
return;
|
|
6718
|
+
}
|
|
6719
|
+
if (name === "return" || name === "enter") {
|
|
6720
|
+
cleanup();
|
|
6721
|
+
resolve7(checked.flatMap((on, i) => on ? [i] : []));
|
|
6722
|
+
return;
|
|
6723
|
+
}
|
|
6724
|
+
if (name === "up" || name === "k") {
|
|
6725
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
6726
|
+
redraw();
|
|
6727
|
+
return;
|
|
6728
|
+
}
|
|
6729
|
+
if (name === "down" || name === "j") {
|
|
6730
|
+
cursor = (cursor + 1) % items.length;
|
|
6731
|
+
redraw();
|
|
6732
|
+
return;
|
|
6733
|
+
}
|
|
6734
|
+
if (name === "space") {
|
|
6735
|
+
checked[cursor] = !checked[cursor];
|
|
6736
|
+
redraw();
|
|
6737
|
+
return;
|
|
6738
|
+
}
|
|
6739
|
+
if (name === "a") {
|
|
6740
|
+
const allOn = checked.every(Boolean);
|
|
6741
|
+
for (let i = 0; i < checked.length; i++) checked[i] = !allOn;
|
|
6742
|
+
redraw();
|
|
6743
|
+
return;
|
|
6744
|
+
}
|
|
6745
|
+
};
|
|
6746
|
+
input2.on("keypress", onKey);
|
|
6747
|
+
});
|
|
6748
|
+
}
|
|
6749
|
+
|
|
6750
|
+
// src/cli/commands/bot.ts
|
|
6154
6751
|
async function runBotInit(name) {
|
|
6155
6752
|
if (!ensureCodex()) {
|
|
6156
6753
|
process.exitCode = 1;
|
|
@@ -6165,7 +6762,7 @@ async function runBotInit(name) {
|
|
|
6165
6762
|
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");
|
|
6166
6763
|
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");
|
|
6167
6764
|
console.log(" 2) \u521B\u5EFA\u5E76\u53D1\u5E03\u5E94\u7528\u7248\u672C");
|
|
6168
|
-
console.log("\n`bot list` \u67E5\u770B\u5168\u90E8\uFF1B`bot use
|
|
6765
|
+
console.log("\n`bot list` \u67E5\u770B\u5168\u90E8\uFF1B`bot use` \u52FE\u9009\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF1B`run` \u524D\u53F0\u8DD1 / `start` \u540E\u53F0\u5E38\u9A7B\u3002\n");
|
|
6169
6766
|
}
|
|
6170
6767
|
async function runBotList() {
|
|
6171
6768
|
const reg = await loadBots();
|
|
@@ -6173,27 +6770,65 @@ async function runBotList() {
|
|
|
6173
6770
|
console.log("\uFF08\u8FD8\u6CA1\u6709\u6CE8\u518C\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u8FD0\u884C `feishu-codex-bridge bot init` \u521B\u5EFA\u3002\uFF09");
|
|
6174
6771
|
return;
|
|
6175
6772
|
}
|
|
6773
|
+
const active = new Set(activeBots(reg).map((b) => b.appId));
|
|
6176
6774
|
console.log("\n\u5DF2\u6CE8\u518C\u7684\u98DE\u4E66\u673A\u5668\u4EBA\uFF1A\n");
|
|
6177
6775
|
for (const b of reg.bots) {
|
|
6178
|
-
const
|
|
6179
|
-
console.log(`${
|
|
6776
|
+
const mark = active.has(b.appId) ? "\u2705" : "\u2B1C";
|
|
6777
|
+
console.log(`${mark} ${b.name.padEnd(16)} ${b.appId} [${b.tenant}]${b.botName ? ` ${b.botName}` : ""}`);
|
|
6180
6778
|
}
|
|
6181
|
-
console.log("\n\
|
|
6779
|
+
console.log("\n\x1B[2m`bot use` \u52FE\u9009\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF0C\u6216 `bot use <\u540D> [\u540D\u2026]` \u76F4\u63A5\u6307\u5B9A\u3002\x1B[0m\n");
|
|
6182
6780
|
}
|
|
6183
|
-
async function runBotUse(
|
|
6781
|
+
async function runBotUse(names) {
|
|
6184
6782
|
const reg = await loadBots();
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
console.error(`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\u300C${name}\u300D\u3002\u5DF2\u6CE8\u518C\uFF1A${botNames(reg.bots)}`);
|
|
6783
|
+
if (reg.bots.length === 0) {
|
|
6784
|
+
console.error("\u2717 \u8FD8\u6CA1\u6709\u6CE8\u518C\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u5148 `feishu-codex-bridge bot init` \u521B\u5EFA\u3002");
|
|
6188
6785
|
process.exitCode = 1;
|
|
6189
6786
|
return;
|
|
6190
6787
|
}
|
|
6191
|
-
|
|
6192
|
-
|
|
6788
|
+
let appIds;
|
|
6789
|
+
if (names.length > 0) {
|
|
6790
|
+
const resolved = [];
|
|
6791
|
+
const unknown = [];
|
|
6792
|
+
for (const n of names) {
|
|
6793
|
+
const b = findBot(reg, n);
|
|
6794
|
+
if (!b) unknown.push(n);
|
|
6795
|
+
else if (!resolved.includes(b.appId)) resolved.push(b.appId);
|
|
6796
|
+
}
|
|
6797
|
+
if (unknown.length) {
|
|
6798
|
+
console.error(`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\uFF1A${unknown.join(", ")}\u3002\u5DF2\u6CE8\u518C\uFF1A${botNames(reg.bots)}`);
|
|
6799
|
+
process.exitCode = 1;
|
|
6800
|
+
return;
|
|
6801
|
+
}
|
|
6802
|
+
appIds = resolved;
|
|
6803
|
+
} else {
|
|
6804
|
+
if (!process.stdin.isTTY) {
|
|
6805
|
+
const cur = activeBots(reg).map((b) => b.name).join(", ") || "\uFF08\u7A7A\uFF09";
|
|
6806
|
+
console.log(`\u5F53\u524D\u6D3B\u8DC3\u673A\u5668\u4EBA\uFF1A${cur}`);
|
|
6807
|
+
console.log("\uFF08\u975E\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\uFF0C\u65E0\u6CD5\u5F39\u52FE\u9009\u6846\u3002\u7528 `bot use <\u540D> [\u540D\u2026]` \u76F4\u63A5\u6307\u5B9A\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\u3002\uFF09");
|
|
6808
|
+
return;
|
|
6809
|
+
}
|
|
6810
|
+
const activeSet = new Set(activeBots(reg).map((b) => b.appId));
|
|
6811
|
+
const items = reg.bots.map((b) => ({
|
|
6812
|
+
label: b.name,
|
|
6813
|
+
hint: `${b.appId} [${b.tenant}]${b.botName ? ` ${b.botName}` : ""}`,
|
|
6814
|
+
checked: activeSet.has(b.appId)
|
|
6815
|
+
}));
|
|
6816
|
+
const picked = await checkboxSelect("\u9009\u62E9\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF08\u7A7A\u683C\u52FE\u9009\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A", items);
|
|
6817
|
+
if (picked === null) {
|
|
6818
|
+
console.log("\u5DF2\u53D6\u6D88\uFF0C\u672A\u6539\u52A8\u3002");
|
|
6819
|
+
return;
|
|
6820
|
+
}
|
|
6821
|
+
appIds = picked.map((i) => reg.bots[i]?.appId).filter((id) => Boolean(id));
|
|
6822
|
+
}
|
|
6823
|
+
await setActiveBots(appIds);
|
|
6824
|
+
const chosen = appIds.map((id) => reg.bots.find((b) => b.appId === id)?.name ?? id);
|
|
6825
|
+
if (chosen.length === 0) {
|
|
6826
|
+
console.log("\u2713 \u5DF2\u6E05\u7A7A\u6D3B\u8DC3\u673A\u5668\u4EBA\u2014\u2014`run` / `start` \u6682\u4E0D\u8FDE\u63A5\u4EFB\u4F55 bot\u3002`bot use` \u91CD\u65B0\u52FE\u9009\u3002");
|
|
6193
6827
|
return;
|
|
6194
6828
|
}
|
|
6195
|
-
|
|
6196
|
-
|
|
6829
|
+
console.log(
|
|
6830
|
+
`\u2713 \u6D3B\u8DC3\u673A\u5668\u4EBA\uFF08${chosen.length} \u4E2A\uFF09\u2192 ${chosen.join(", ")}\u3002\u524D\u53F0\u91CD\u8DD1 \`run\` \u751F\u6548${chosen.length > 1 ? "\uFF08\u591A\u8FDB\u7A0B\u6258\u7BA1\uFF09" : ""}\uFF1B\u540E\u53F0\u8BF7 \`restart\`\u3002`
|
|
6831
|
+
);
|
|
6197
6832
|
}
|
|
6198
6833
|
async function runBotRm(name) {
|
|
6199
6834
|
const reg = await loadBots();
|
|
@@ -6278,8 +6913,8 @@ function readStdin() {
|
|
|
6278
6913
|
// src/cli/index.ts
|
|
6279
6914
|
var program = new Command();
|
|
6280
6915
|
program.name("feishu-codex-bridge").description("\u628A\u98DE\u4E66/Lark \u6865\u63A5\u5230\u672C\u673A Codex\uFF08\u9879\u76EE=\u7FA4, \u8BDD\u9898=\u4F1A\u8BDD\uFF09").version(bridgeVersion());
|
|
6281
|
-
program.command("run").description("\u524D\u53F0\u542F\u52A8
|
|
6282
|
-
await runRun();
|
|
6916
|
+
program.command("run").description("\u524D\u53F0\u542F\u52A8\u6D3B\u8DC3\u673A\u5668\u4EBA\uFF08\u591A\u4E2A\u5219\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF1B\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF1BCtrl+C \u4F18\u96C5\u9000\u51FA\uFF09").option("--bot <name>", "\u53EA\u542F\u52A8\u6307\u5B9A\u7684\u4E00\u4E2A\u673A\u5668\u4EBA\uFF08\u540D\u5B57\u6216 appId\uFF09").action(async (options) => {
|
|
6917
|
+
await runRun(options.bot);
|
|
6283
6918
|
});
|
|
6284
6919
|
program.command("start").description("\u540E\u53F0 daemon \u542F\u52A8\uFF08\u88C5 launchd \u5F00\u673A\u81EA\u542F\uFF1B\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF09").action(async () => {
|
|
6285
6920
|
await runStart();
|
|
@@ -6306,8 +6941,8 @@ bot.command("init [name]").description("\u6CE8\u518C\u4E00\u4E2A\u98DE\u4E66\u67
|
|
|
6306
6941
|
bot.command("list").description("\u5217\u51FA\u5DF2\u6CE8\u518C\u7684\u98DE\u4E66\u673A\u5668\u4EBA").action(async () => {
|
|
6307
6942
|
await runBotList();
|
|
6308
6943
|
});
|
|
6309
|
-
bot.command("use
|
|
6310
|
-
await runBotUse(
|
|
6944
|
+
bot.command("use [names...]").description("\u52FE\u9009/\u6307\u5B9A\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF08\u591A\u9009\uFF09\uFF1B\u65E0\u53C2\u6570\u5F39\u4EA4\u4E92\u5F0F\u52FE\u9009\u6846").action(async (names) => {
|
|
6945
|
+
await runBotUse(names ?? []);
|
|
6311
6946
|
});
|
|
6312
6947
|
bot.command("rm <name>").description("\u79FB\u9664\u4E00\u4E2A\u673A\u5668\u4EBA\u914D\u7F6E").action(async (name) => {
|
|
6313
6948
|
await runBotRm(name);
|