@modelzen/feishu-codex-bridge 0.3.1 → 0.3.3
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 +16 -9
- package/dist/cli.js +643 -96
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
- **私聊控制台**:私聊机器人弹交互菜单 —— 新建项目、项目列表、设置、诊断、重连。
|
|
34
34
|
- **稳定隔离**:每会话独立 app-server 进程;卡死有 watchdog(默认 120s)→ 终止 → 回收,异常不波及其他群。
|
|
35
35
|
- **本地加密密钥库**:飞书应用密钥用 AES-256-GCM 存在 `~/.feishu-codex-bridge/`,不入仓库、不进环境变量。
|
|
36
|
-
- **跨平台常驻**:macOS / Windows / Linux·WSL 均可注册成后台服务、开机或登录自启(分别走 launchd / 登录自启免管理员 / systemd)。
|
|
36
|
+
- **跨平台常驻**:macOS / Windows / Linux·WSL 均可注册成后台服务、开机或登录自启(分别走 launchd / 登录自启免管理员 / systemd)。注:跨平台指进程运行与后台自启;「项目内只读/读写」隐私沙箱仅 macOS / 原生 Windows 可强制(见[安全须知](#-安全须知))。
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
| 依赖 | 说明 | 获取方式 |
|
|
43
43
|
|------|------|----------|
|
|
44
|
-
| **操作系统** |
|
|
44
|
+
| **操作系统** | 运行/后台常驻:**macOS / Windows** 均支持,Linux·WSL 为 best-effort(已实现 systemd,未广泛实测)。注意:「项目内只读 / 读写」隐私档的沙箱强制仅 **macOS / 原生 Windows**,Linux·WSL 上这两档会 fail-closed 拒绝启动(见下方[安全须知](#-安全须知)) | — |
|
|
45
45
|
| **Node.js ≥ 20** | 运行时 | <https://nodejs.org> 或 `nvm install 20` |
|
|
46
46
|
| **Codex CLI** | 后端,bridge 会 spawn `codex app-server` | `npm i -g @openai/codex`,或装 Codex.app,或用 `CODEX_BIN` 指向已有二进制 |
|
|
47
47
|
| **Codex 已登录** | app-server 需要 `~/.codex/auth.json` | `codex login` |
|
|
@@ -241,17 +241,24 @@ src/
|
|
|
241
241
|
|
|
242
242
|
## ⚠️ 安全须知
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
机器人调用 Codex 始终是 **`approvalPolicy: "never"`**(无人工逐条审批),**沙箱就是唯一的安全闸门**。每个项目有一档**权限**,在私聊控制台「📁 项目列表 → ⚙️ 设置 → 🔐 权限」里用下拉框选择后提交:
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
| 档位 | 能读 | 能写 | 适用 |
|
|
247
|
+
|------|------|------|------|
|
|
248
|
+
| 🔒 **项目内只读** | 仅项目文件夹 | ✗ | 外部群 / 不可信场景的问答机器人 |
|
|
249
|
+
| ✏️ **项目内读写** | 仅项目文件夹 | 仅项目文件夹 | 自己的编码项目,但禁止它碰机器其余部分 |
|
|
250
|
+
| ⚠️ **完全访问** | 整台电脑 | 整台电脑 | 完全信任、你自己掌控的机器 |
|
|
247
251
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
-
|
|
251
|
-
|
|
252
|
-
-
|
|
252
|
+
- **管理员 / 普通用户可分设**:🔐 权限里有「管理员档」和「普通用户档」两个下拉。两档**不同**时,管理员与群里其他人**各用独立的 Codex 线程**(互不串沙箱、也互不串对话历史)——典型:外部群里管理员 `完全访问`、其他人 `项目内只读`。两档**相同** = 所有人一致(默认)。
|
|
253
|
+
- **默认值**:你自己新建的项目群 = `完全访问`(与历史行为一致);**别人把机器人拉进的存量/外部群** = `项目内只读`;普通用户档默认**同管理员档**(不分档)。**升级前没有 `mode` 字段的老项目按 `完全访问` 处理**,行为不变。
|
|
254
|
+
- 🔒/✏️ 靠 Codex 的自定义 permissions 档把读写都**锁死在项目文件夹内**(读不到 `~/.ssh`、`/etc` 等),由操作系统沙箱强制:**macOS(Seatbelt)与原生 Windows(restricted token)可强制**,其中 Windows 需 Codex 以 elevated 沙箱运行、否则它会**拒绝执行**(仍不泄漏)。**Linux / WSL 无法强制读限定**(沙箱只挡写、不限读,Landlock 读限制尚未实现,WSL 等同 Linux)——在这些平台选 🔒/✏️ 会被**直接拒绝启动(fail-closed),绝不静默降级为完全访问**;要在 Linux/WSL 用,请把 Codex 跑在容器/隔离环境里。
|
|
255
|
+
> Windows 上的强制是 Codex 自己做的,请先在真机自测一次(让机器人读项目文件夹外的文件,应被拒)再用于真实外部群。
|
|
256
|
+
- ⚠️ `完全访问` 档意味着:**任何能给机器人发消息的人,都能在你这台机器上、以你的身份执行任意命令(读写文件、联网、跑脚本)**。这一档只把**你信任的人**拉进群,在**你自己掌控的隔离机器**上跑,目录里别放不愿被读写的敏感数据。
|
|
257
|
+
- 「联网」是档位之外的独立开关,只影响它执行的 shell 命令能否上网,不影响模型本身和 Codex 自带的联网搜索。
|
|
253
258
|
- 它不是多租户托管服务,是给你(和你信任的小团队)自用的桥。
|
|
254
259
|
|
|
260
|
+
> 把机器人拉进**外部群**做只读问答前,先在飞书开发者后台开启应用的「可被添加到外部群 / 外部可用范围」,再由群里的真人手动把机器人加进群(机器人无法自行加入)。
|
|
261
|
+
|
|
255
262
|
---
|
|
256
263
|
|
|
257
264
|
## ❓ 故障排查
|
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
|
|
@@ -670,7 +684,7 @@ async function runRegistrationWizard() {
|
|
|
670
684
|
accounts: { app: { id: result.client_id, secret: result.client_secret, tenant } }
|
|
671
685
|
};
|
|
672
686
|
if (operatorOpenId) {
|
|
673
|
-
cfg.preferences = { access: { admins: [operatorOpenId] } };
|
|
687
|
+
cfg.preferences = { access: { ownerOpenId: operatorOpenId, admins: [operatorOpenId] } };
|
|
674
688
|
console.log(` Admin: ${operatorOpenId} (\u4F60\u81EA\u5DF1\uFF0C\u5DF2\u81EA\u52A8\u52A0\u5165\u7BA1\u7406\u5458\u540D\u5355)`);
|
|
675
689
|
} else {
|
|
676
690
|
console.log(
|
|
@@ -727,7 +741,13 @@ var JOIN_GROUP_SCOPES = [
|
|
|
727
741
|
"im:chat:readonly",
|
|
728
742
|
"im:chat.members:write_only"
|
|
729
743
|
];
|
|
730
|
-
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
|
+
];
|
|
731
751
|
var SCOPE_LABELS = {
|
|
732
752
|
"im:message.group_at_msg:readonly": "\u63A5\u6536\u7FA4\u91CC @\u673A\u5668\u4EBA \u7684\u6D88\u606F",
|
|
733
753
|
"im:message.group_msg": "\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\uFF08\u514D@\uFF09",
|
|
@@ -748,7 +768,8 @@ var SCOPE_LABELS = {
|
|
|
748
768
|
"cardkit:card:write": "\u4EA4\u4E92\u6309\u94AE\u5361\u7247",
|
|
749
769
|
"docs:document.comment:read": "\u8BFB\u53D6\u6587\u6863\u8BC4\u8BBA",
|
|
750
770
|
"docs:document.comment:create": "\u53D1\u8868\u6587\u6863\u8BC4\u8BBA\u56DE\u590D",
|
|
751
|
-
"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"
|
|
752
773
|
};
|
|
753
774
|
function labelScope(scope) {
|
|
754
775
|
const label = SCOPE_LABELS[scope];
|
|
@@ -1421,7 +1442,28 @@ function mapItemComplete(item) {
|
|
|
1421
1442
|
|
|
1422
1443
|
// src/agent/codex-appserver/backend.ts
|
|
1423
1444
|
var APPROVAL_POLICY = "never";
|
|
1424
|
-
|
|
1445
|
+
function sandboxParams(mode, network) {
|
|
1446
|
+
if ((mode ?? "full") === "full") return { sandbox: "danger-full-access" };
|
|
1447
|
+
if (process.platform !== "darwin" && process.platform !== "win32") {
|
|
1448
|
+
throw new Error(
|
|
1449
|
+
"\u300C\u9879\u76EE\u5185\u53EA\u8BFB / \u9879\u76EE\u5185\u8BFB\u5199\u300D\u9760\u64CD\u4F5C\u7CFB\u7EDF\u6C99\u7BB1\u628A\u8BFB\u5199\u9501\u8FDB\u9879\u76EE\u6587\u4EF6\u5939\uFF0C\u76EE\u524D\u53EA\u6709 macOS \u4E0E\u539F\u751F Windows \u80FD\u5F3A\u5236\u6267\u884C\u3002\u5F53\u524D\u5E73\u53F0\uFF08Linux / WSL \u53EA\u6321\u5199\u3001\u4E0D\u9650\u5236\u8BFB\u53D6\uFF0C\u65E0\u6CD5\u4FDD\u8BC1\u4E0D\u6CC4\u9732\u9690\u79C1\uFF09\u5DF2\u62D2\u7EDD\u542F\u52A8\uFF08\u7EDD\u4E0D\u964D\u7EA7\u4E3A\u5B8C\u5168\u8BBF\u95EE\uFF09\u3002\u8BF7\u6539\u7528\u300C\u5B8C\u5168\u8BBF\u95EE\u300D\u3001\u628A Codex \u8DD1\u8FDB\u5BB9\u5668/\u9694\u79BB\u73AF\u5883\uFF0C\u6216\u5728 macOS / Windows \u4E0A\u8FD0\u884C\u3002"
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
return {
|
|
1453
|
+
config: {
|
|
1454
|
+
default_permissions: "feishu",
|
|
1455
|
+
permissions: {
|
|
1456
|
+
feishu: {
|
|
1457
|
+
filesystem: {
|
|
1458
|
+
":minimal": "read",
|
|
1459
|
+
":workspace_roots": { ".": mode === "write" ? "write" : "read" }
|
|
1460
|
+
},
|
|
1461
|
+
network: { enabled: Boolean(network) }
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1425
1467
|
var BRIDGE_DEVELOPER_INSTRUCTIONS = [
|
|
1426
1468
|
"\u4F60\u73B0\u5728\u901A\u8FC7\u300C\u98DE\u4E66\u6865\u300D\u4E0E\u7528\u6237\u5BF9\u8BDD\uFF1A\u4F60\u7684\u56DE\u590D\u4F1A\u88AB\u6E32\u67D3\u6210\u98DE\u4E66\u6D88\u606F\u3002\u8BF7\u9075\u5B88\u4E24\u6761\u8F93\u51FA\u7EA6\u5B9A\u3002",
|
|
1427
1469
|
"",
|
|
@@ -1606,23 +1648,25 @@ var CodexAppServerBackend = class {
|
|
|
1606
1648
|
}
|
|
1607
1649
|
}
|
|
1608
1650
|
async startThread(opts) {
|
|
1651
|
+
const sandbox = sandboxParams(opts.mode, opts.network);
|
|
1609
1652
|
const client = await this.spawn(opts.cwd);
|
|
1610
1653
|
const res = await client.request("thread/start", {
|
|
1611
1654
|
cwd: opts.cwd,
|
|
1612
1655
|
approvalPolicy: APPROVAL_POLICY,
|
|
1613
|
-
sandbox
|
|
1656
|
+
...sandbox,
|
|
1614
1657
|
developerInstructions: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
1615
1658
|
...opts.model ? { model: opts.model } : {}
|
|
1616
1659
|
});
|
|
1617
1660
|
return new CodexThread(client, res.thread.id, opts.model, opts.effort);
|
|
1618
1661
|
}
|
|
1619
1662
|
async resumeThread(opts) {
|
|
1663
|
+
const sandbox = sandboxParams(opts.mode, opts.network);
|
|
1620
1664
|
const client = await this.spawn(opts.cwd);
|
|
1621
1665
|
const res = await client.request("thread/resume", {
|
|
1622
1666
|
threadId: opts.codexThreadId,
|
|
1623
1667
|
cwd: opts.cwd,
|
|
1624
1668
|
approvalPolicy: APPROVAL_POLICY,
|
|
1625
|
-
sandbox
|
|
1669
|
+
...sandbox,
|
|
1626
1670
|
developerInstructions: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
1627
1671
|
...opts.model ? { model: opts.model } : {}
|
|
1628
1672
|
});
|
|
@@ -2168,6 +2212,15 @@ function selectStatic(opts) {
|
|
|
2168
2212
|
behaviors: [{ type: "callback", value: { a: opts.actionId } }]
|
|
2169
2213
|
};
|
|
2170
2214
|
}
|
|
2215
|
+
function selectMenu(opts) {
|
|
2216
|
+
return {
|
|
2217
|
+
tag: "select_static",
|
|
2218
|
+
name: opts.name,
|
|
2219
|
+
placeholder: { tag: "plain_text", content: opts.placeholder },
|
|
2220
|
+
...opts.initial ? { initial_option: opts.initial } : {},
|
|
2221
|
+
options: opts.options.map((o) => ({ text: { tag: "plain_text", content: o.label }, value: o.value }))
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2171
2224
|
|
|
2172
2225
|
// src/card/command-cards.ts
|
|
2173
2226
|
var MC = {
|
|
@@ -2275,19 +2328,13 @@ function pickerTime(unixSeconds) {
|
|
|
2275
2328
|
function talkLine(noMention, tail) {
|
|
2276
2329
|
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`;
|
|
2277
2330
|
}
|
|
2278
|
-
function buildHelpCard(scope, noMention = true) {
|
|
2331
|
+
function buildHelpCard(scope, noMention = true, isAdmin2 = false) {
|
|
2279
2332
|
const elements = [];
|
|
2280
2333
|
if (scope === "single") {
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
`${talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406")}
|
|
2286
|
-
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2287
|
-
\xB7 \`/settings\` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09
|
|
2288
|
-
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2289
|
-
)
|
|
2290
|
-
);
|
|
2334
|
+
const lines = [talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"), "\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6"];
|
|
2335
|
+
if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
2336
|
+
lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
2337
|
+
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")));
|
|
2291
2338
|
} else if (scope === "topic") {
|
|
2292
2339
|
elements.push(
|
|
2293
2340
|
md("\u{1F9F5} **\u8BDD\u9898\u5185** \u2014 \u6BCF\u4E2A\u8BDD\u9898\u662F\u4E00\u4E2A\u72EC\u7ACB\u4F1A\u8BDD\u3002"),
|
|
@@ -2300,13 +2347,10 @@ function buildHelpCard(scope, noMention = true) {
|
|
|
2300
2347
|
note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
|
|
2301
2348
|
);
|
|
2302
2349
|
} else {
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
"\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"
|
|
2308
|
-
)
|
|
2309
|
-
);
|
|
2350
|
+
const lines = ["\xB7 **@\u6211 + \u5185\u5BB9** \u2192 \u5F00\u4E00\u4E2A\u65B0\u8BDD\u9898\u5E76\u5F00\u59CB"];
|
|
2351
|
+
if (isAdmin2) lines.push("\xB7 `/resume` \u2192 \u6062\u590D\u5386\u53F2\u4F1A\u8BDD", "\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
2352
|
+
lines.push("\xB7 `/model` \u2192 \u9700\u8981\u5728\u8BDD\u9898\u91CC\u7528", "\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
2353
|
+
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")));
|
|
2310
2354
|
}
|
|
2311
2355
|
return card(elements, { header: { title: "\u{1F916} \u53EF\u7528\u547D\u4EE4", template: "blue" }, summary: "\u53EF\u7528\u547D\u4EE4" });
|
|
2312
2356
|
}
|
|
@@ -3113,10 +3157,26 @@ async function uploadBuffer(channel, buffer) {
|
|
|
3113
3157
|
|
|
3114
3158
|
// src/project/registry.ts
|
|
3115
3159
|
import { mkdir as mkdir4, readFile as readFile6, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
3160
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3116
3161
|
import { dirname as dirname5 } from "path";
|
|
3117
3162
|
function defaultNoMention(p) {
|
|
3118
3163
|
return !((p.origin ?? "created") === "joined" && (p.kind ?? "multi") === "single");
|
|
3119
3164
|
}
|
|
3165
|
+
function effectiveMode(p) {
|
|
3166
|
+
return p.mode ?? "full";
|
|
3167
|
+
}
|
|
3168
|
+
function effectiveGuestMode(p) {
|
|
3169
|
+
return p.guestMode ?? effectiveMode(p);
|
|
3170
|
+
}
|
|
3171
|
+
function turnTier(p, isAdminSender) {
|
|
3172
|
+
const adminTier = effectiveMode(p);
|
|
3173
|
+
const guestTier = effectiveGuestMode(p);
|
|
3174
|
+
return {
|
|
3175
|
+
mode: isAdminSender ? adminTier : guestTier,
|
|
3176
|
+
role: isAdminSender ? "admin" : "guest",
|
|
3177
|
+
split: guestTier !== adminTier
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3120
3180
|
var FILE_VERSION2 = 1;
|
|
3121
3181
|
async function read() {
|
|
3122
3182
|
try {
|
|
@@ -3128,9 +3188,18 @@ async function read() {
|
|
|
3128
3188
|
throw err;
|
|
3129
3189
|
}
|
|
3130
3190
|
}
|
|
3191
|
+
var opChain = Promise.resolve();
|
|
3192
|
+
function withLock(fn) {
|
|
3193
|
+
const run = opChain.then(fn, fn);
|
|
3194
|
+
opChain = run.then(
|
|
3195
|
+
() => void 0,
|
|
3196
|
+
() => void 0
|
|
3197
|
+
);
|
|
3198
|
+
return run;
|
|
3199
|
+
}
|
|
3131
3200
|
async function write(projects) {
|
|
3132
3201
|
await mkdir4(dirname5(paths.projectsFile), { recursive: true });
|
|
3133
|
-
const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
|
|
3202
|
+
const tmp = `${paths.projectsFile}.tmp-${process.pid}-${randomUUID2()}`;
|
|
3134
3203
|
const body = { version: FILE_VERSION2, projects };
|
|
3135
3204
|
await writeFile4(tmp, `${JSON.stringify(body, null, 2)}
|
|
3136
3205
|
`, "utf8");
|
|
@@ -3146,34 +3215,41 @@ async function getProjectByName(name) {
|
|
|
3146
3215
|
return (await read()).find((p) => p.name === name);
|
|
3147
3216
|
}
|
|
3148
3217
|
async function addProject(p) {
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3218
|
+
return withLock(async () => {
|
|
3219
|
+
const projects = await read();
|
|
3220
|
+
if (projects.some((x) => x.name === p.name)) {
|
|
3221
|
+
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
3222
|
+
}
|
|
3223
|
+
if (p.chatId) {
|
|
3224
|
+
const bound = projects.find((x) => x.chatId === p.chatId);
|
|
3225
|
+
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
3226
|
+
}
|
|
3227
|
+
projects.push(p);
|
|
3228
|
+
await write(projects);
|
|
3229
|
+
});
|
|
3159
3230
|
}
|
|
3160
3231
|
async function updateProject(name, patch) {
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3232
|
+
return withLock(async () => {
|
|
3233
|
+
const projects = await read();
|
|
3234
|
+
const p = projects.find((x) => x.name === name);
|
|
3235
|
+
if (!p) return;
|
|
3236
|
+
const actual = typeof patch === "function" ? patch(p) : patch;
|
|
3237
|
+
const target = p;
|
|
3238
|
+
for (const [k, v] of Object.entries(actual)) {
|
|
3239
|
+
if (v !== void 0) target[k] = v;
|
|
3240
|
+
}
|
|
3241
|
+
await write(projects);
|
|
3242
|
+
});
|
|
3169
3243
|
}
|
|
3170
3244
|
async function removeProject(name) {
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3245
|
+
return withLock(async () => {
|
|
3246
|
+
const projects = await read();
|
|
3247
|
+
const idx = projects.findIndex((p) => p.name === name);
|
|
3248
|
+
if (idx === -1) return void 0;
|
|
3249
|
+
const [removed] = projects.splice(idx, 1);
|
|
3250
|
+
await write(projects);
|
|
3251
|
+
return removed;
|
|
3252
|
+
});
|
|
3177
3253
|
}
|
|
3178
3254
|
|
|
3179
3255
|
// src/card/dm-cards.ts
|
|
@@ -3198,7 +3274,22 @@ var DM = {
|
|
|
3198
3274
|
setTools: "dm.set.tools",
|
|
3199
3275
|
setWatchdog: "dm.set.watchdog",
|
|
3200
3276
|
setPending: "dm.set.pending",
|
|
3201
|
-
setConcurrency: "dm.set.concurrency"
|
|
3277
|
+
setConcurrency: "dm.set.concurrency",
|
|
3278
|
+
// 权限管理:全局 admins(settings 卡进入)+ 项目响应白名单(项目列表 / 建项目完成卡进入)
|
|
3279
|
+
admins: "dm.admins",
|
|
3280
|
+
addAdminForm: "dm.admin.addForm",
|
|
3281
|
+
addAdminSubmit: "dm.admin.addSubmit",
|
|
3282
|
+
rmAdmin: "dm.admin.rm",
|
|
3283
|
+
allowlist: "dm.allowlist",
|
|
3284
|
+
addAllowedForm: "dm.allow.addForm",
|
|
3285
|
+
addAllowedSubmit: "dm.allow.addSubmit",
|
|
3286
|
+
rmAllowed: "dm.allow.rm",
|
|
3287
|
+
// 项目设置容器(项目列表 / 建项目完成卡 进入),以后的项目级设置项往这里加
|
|
3288
|
+
projectSettings: "dm.projectSettings",
|
|
3289
|
+
setNoMentionDm: "dm.proj.noMention",
|
|
3290
|
+
// 🔐 权限:codex 沙箱档位(管理员档 + 普通用户档)+ 联网,做成下拉表单(选+提交)
|
|
3291
|
+
permission: "dm.proj.perm",
|
|
3292
|
+
permissionSubmit: "dm.proj.perm.submit"
|
|
3202
3293
|
};
|
|
3203
3294
|
var GS = {
|
|
3204
3295
|
setNoMention: "gs.noMention"
|
|
@@ -3464,7 +3555,13 @@ function buildNewProjectDoneCard(p) {
|
|
|
3464
3555
|
note(`\u{1F4C2} \`${p.cwd}\` \xB7 ${kindLabel(p.kind)}`),
|
|
3465
3556
|
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")
|
|
3466
3557
|
];
|
|
3467
|
-
if (p.chatId)
|
|
3558
|
+
if (p.chatId)
|
|
3559
|
+
elements.push(
|
|
3560
|
+
actions([
|
|
3561
|
+
linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId), "primary"),
|
|
3562
|
+
button("\u2699\uFE0F \u9879\u76EE\u8BBE\u7F6E", { a: DM.projectSettings, n: p.name })
|
|
3563
|
+
])
|
|
3564
|
+
);
|
|
3468
3565
|
return card(elements, { header: { title, template: "green" } });
|
|
3469
3566
|
}
|
|
3470
3567
|
function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
|
|
@@ -3495,6 +3592,7 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3495
3592
|
}
|
|
3496
3593
|
const row = [];
|
|
3497
3594
|
if (p.chatId) row.push(linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId)));
|
|
3595
|
+
row.push(button("\u2699\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: p.name }));
|
|
3498
3596
|
row.push(button("\u{1F5D1} \u5220\u9664", { a: DM.rmConfirm, n: p.name }, "danger"));
|
|
3499
3597
|
elements.push(actions(row));
|
|
3500
3598
|
elements.push(hr());
|
|
@@ -3549,7 +3647,8 @@ function buildSettingsCard(cfg) {
|
|
|
3549
3647
|
{ label: "20", value: "20" }
|
|
3550
3648
|
]),
|
|
3551
3649
|
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"),
|
|
3552
|
-
|
|
3650
|
+
hr(),
|
|
3651
|
+
actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
|
|
3553
3652
|
],
|
|
3554
3653
|
{ header: { title: "\u2699\uFE0F \u8BBE\u7F6E", template: "blue" } }
|
|
3555
3654
|
);
|
|
@@ -3572,6 +3671,199 @@ function buildGroupSettingsCard(project) {
|
|
|
3572
3671
|
{ header: { title: "\u2699\uFE0F \u7FA4\u8BBE\u7F6E", template: "blue" } }
|
|
3573
3672
|
);
|
|
3574
3673
|
}
|
|
3674
|
+
function memberName(names, id) {
|
|
3675
|
+
return names.get(id) ?? `\u2026${id.slice(-6)}`;
|
|
3676
|
+
}
|
|
3677
|
+
function buildAdminsCard(cfg, names) {
|
|
3678
|
+
const owner = resolveOwner(cfg);
|
|
3679
|
+
const admins = cfg.preferences?.access?.admins ?? [];
|
|
3680
|
+
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()];
|
|
3681
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3682
|
+
if (owner) {
|
|
3683
|
+
seen.add(owner);
|
|
3684
|
+
elements.push(actions([md(`\u{1F451} **${memberName(names, owner)}** \xB7 Bot \u62E5\u6709\u8005\uFF08\u6CE8\u518C\u8005\uFF09`)]));
|
|
3685
|
+
}
|
|
3686
|
+
let extra = 0;
|
|
3687
|
+
for (const id of admins) {
|
|
3688
|
+
if (seen.has(id)) continue;
|
|
3689
|
+
seen.add(id);
|
|
3690
|
+
extra++;
|
|
3691
|
+
elements.push(actions([md(memberName(names, id)), button("\u{1F5D1} \u79FB\u9664", { a: DM.rmAdmin, u: id }, "danger")]));
|
|
3692
|
+
}
|
|
3693
|
+
if (extra === 0) elements.push(note("\u6682\u65E0\u989D\u5916\u7BA1\u7406\u5458\u3002"));
|
|
3694
|
+
elements.push(
|
|
3695
|
+
hr(),
|
|
3696
|
+
actions([button("\u2795 \u6DFB\u52A0\u7BA1\u7406\u5458", { a: DM.addAdminForm }, "primary"), button("\u2B05\uFE0F \u8BBE\u7F6E", { a: DM.settings })]),
|
|
3697
|
+
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")
|
|
3698
|
+
);
|
|
3699
|
+
return card(elements, { header: { title: "\u{1F46E} \u7BA1\u7406\u5458", template: "blue" } });
|
|
3700
|
+
}
|
|
3701
|
+
function buildAddAdminCard(members) {
|
|
3702
|
+
const MAX = 50;
|
|
3703
|
+
const shown = members.slice(0, MAX);
|
|
3704
|
+
const formEls = [];
|
|
3705
|
+
if (shown.length > 0) {
|
|
3706
|
+
formEls.push(
|
|
3707
|
+
selectMenu({
|
|
3708
|
+
name: "pick",
|
|
3709
|
+
placeholder: "\u4ECE\u9879\u76EE\u7FA4\u6210\u5458\u9009\u62E9",
|
|
3710
|
+
options: shown.map((m) => ({ label: m.name, value: m.openId }))
|
|
3711
|
+
})
|
|
3712
|
+
);
|
|
3713
|
+
}
|
|
3714
|
+
formEls.push(
|
|
3715
|
+
input({
|
|
3716
|
+
name: "open_id",
|
|
3717
|
+
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",
|
|
3718
|
+
placeholder: "ou_xxx"
|
|
3719
|
+
}),
|
|
3720
|
+
actions([submitButton("\u2705 \u786E\u8BA4\u6DFB\u52A0", { a: DM.addAdminSubmit }, "primary", "submit_admin")])
|
|
3721
|
+
);
|
|
3722
|
+
const tail = [];
|
|
3723
|
+
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`));
|
|
3724
|
+
return card(
|
|
3725
|
+
[
|
|
3726
|
+
md("**\u6DFB\u52A0\u7BA1\u7406\u5458** \xB7 \u4ECE\u9879\u76EE\u7FA4\u6210\u5458\u9009\uFF0C\u6216\u8F93\u5165 open_id"),
|
|
3727
|
+
form("add_admin", formEls),
|
|
3728
|
+
...tail,
|
|
3729
|
+
actions([button("\u2B05\uFE0F \u53D6\u6D88", { a: DM.admins })])
|
|
3730
|
+
],
|
|
3731
|
+
{ header: { title: "\u2795 \u6DFB\u52A0\u7BA1\u7406\u5458", template: "blue" } }
|
|
3732
|
+
);
|
|
3733
|
+
}
|
|
3734
|
+
var MODE_OPTS = [
|
|
3735
|
+
{ value: "qa", label: "\u{1F512} \u9879\u76EE\u5185\u53EA\u8BFB", desc: "\u53EA\u80FD\u67E5\u770B\u9879\u76EE\u6587\u4EF6\u5939\u91CC\u7684\u5185\u5BB9\uFF0C\u4E0D\u4F1A\u6539\u4EFB\u4F55\u6587\u4EF6" },
|
|
3736
|
+
{ value: "write", label: "\u270F\uFE0F \u9879\u76EE\u5185\u8BFB\u5199", desc: "\u80FD\u67E5\u770B\u5E76\u4FEE\u6539\u9879\u76EE\u6587\u4EF6\u5939\u91CC\u7684\u6587\u4EF6\uFF0C\u4F46\u78B0\u4E0D\u5230\u6587\u4EF6\u5939\u5916" },
|
|
3737
|
+
{ value: "full", label: "\u26A0\uFE0F \u5B8C\u5168\u8BBF\u95EE", desc: "\u80FD\u8BFB\u5199\u6574\u53F0\u7535\u8111\u4E0A\u7684\u4EFB\u4F55\u6587\u4EF6" }
|
|
3738
|
+
];
|
|
3739
|
+
function tierLabel(m) {
|
|
3740
|
+
return MODE_OPTS.find((o) => o.value === m)?.label ?? m;
|
|
3741
|
+
}
|
|
3742
|
+
var TIER_SELECT_OPTS = MODE_OPTS.map((o) => ({ label: `${o.label} \u2014 ${o.desc}`, value: o.value }));
|
|
3743
|
+
function permissionSummary(p) {
|
|
3744
|
+
const admin = effectiveMode(p);
|
|
3745
|
+
const guest = effectiveGuestMode(p);
|
|
3746
|
+
return admin === guest ? `\u6240\u6709\u4EBA\uFF1A${tierLabel(admin)}` : `\u7BA1\u7406\u5458\uFF1A${tierLabel(admin)}\u3000\xB7\u3000\u5176\u4ED6\u4EBA\uFF1A${tierLabel(guest)}`;
|
|
3747
|
+
}
|
|
3748
|
+
function buildPermissionCard(p) {
|
|
3749
|
+
const network = p.network ?? false;
|
|
3750
|
+
return card(
|
|
3751
|
+
[
|
|
3752
|
+
md(`**\u{1F510} \u6743\u9650** \xB7 ${p.name}`),
|
|
3753
|
+
note(
|
|
3754
|
+
"codex \u6C99\u7BB1\u7684\u8BBF\u95EE\u8303\u56F4\u3002\u300C\u7BA1\u7406\u5458\u6863\u300D\u7ED9 owner / \u7BA1\u7406\u5458\uFF0C\u300C\u666E\u901A\u7528\u6237\u6863\u300D\u7ED9\u7FA4\u91CC\u5176\u4ED6\u4EBA\u3002\u4E24\u6863**\u4E0D\u540C**\u65F6\uFF0C\u4E24\u7C7B\u4EBA\u5404\u7528\u72EC\u7ACB\u7EBF\u7A0B\uFF08\u4E92\u4E0D\u4E32\u6C99\u7BB1\u4E0E\u5BF9\u8BDD\u5386\u53F2\uFF09\uFF1B**\u76F8\u540C**\u5219\u6240\u6709\u4EBA\u4E00\u81F4\u3002"
|
|
3755
|
+
),
|
|
3756
|
+
form("perm", [
|
|
3757
|
+
md("\u{1F451} **\u7BA1\u7406\u5458\u6863**"),
|
|
3758
|
+
selectMenu({ name: "mode", placeholder: "\u9009\u62E9\u7BA1\u7406\u5458\u6743\u9650\u6863", options: TIER_SELECT_OPTS, initial: effectiveMode(p) }),
|
|
3759
|
+
md("\u{1F465} **\u666E\u901A\u7528\u6237\u6863**"),
|
|
3760
|
+
selectMenu({
|
|
3761
|
+
name: "guestMode",
|
|
3762
|
+
placeholder: "\u9009\u62E9\u666E\u901A\u7528\u6237\u6743\u9650\u6863",
|
|
3763
|
+
options: TIER_SELECT_OPTS,
|
|
3764
|
+
initial: effectiveGuestMode(p)
|
|
3765
|
+
}),
|
|
3766
|
+
md("\u{1F310} **\u8054\u7F51**\uFF08\u53EA\u5BF9\u53EA\u8BFB / \u8BFB\u5199\u6863\u6709\u610F\u4E49\uFF1B\u5B8C\u5168\u8BBF\u95EE\u6052\u8054\u7F51\uFF09"),
|
|
3767
|
+
selectMenu({
|
|
3768
|
+
name: "network",
|
|
3769
|
+
placeholder: "\u8054\u7F51\u5F00\u5173",
|
|
3770
|
+
options: [
|
|
3771
|
+
{ label: "\u5173\uFF08\u9ED8\u8BA4\uFF0C\u66F4\u5B89\u5168\uFF09", value: "off" },
|
|
3772
|
+
{ label: "\u5F00", value: "on" }
|
|
3773
|
+
],
|
|
3774
|
+
initial: network ? "on" : "off"
|
|
3775
|
+
}),
|
|
3776
|
+
actions([submitButton("\u2705 \u4FDD\u5B58\u6743\u9650", { a: DM.permissionSubmit, n: p.name }, "primary", "submit_perm")])
|
|
3777
|
+
]),
|
|
3778
|
+
note("\u4FDD\u5B58\u4F1A\u65AD\u5F00\u672C\u9879\u76EE\u6B63\u5728\u8FDB\u884C\u7684\u4F1A\u8BDD\uFF0C\u8BA9\u65B0\u6863\u4F4D\u7ACB\u5373\u751F\u6548\u3002"),
|
|
3779
|
+
actions([button("\u2B05\uFE0F \u8FD4\u56DE\u8BBE\u7F6E", { a: DM.projectSettings, n: p.name })])
|
|
3780
|
+
],
|
|
3781
|
+
{ header: { title: "\u{1F510} \u6743\u9650", template: "blue" } }
|
|
3782
|
+
);
|
|
3783
|
+
}
|
|
3784
|
+
function buildProjectSettingsCard(project) {
|
|
3785
|
+
const kind = project.kind ?? "multi";
|
|
3786
|
+
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3787
|
+
return card(
|
|
3788
|
+
[
|
|
3789
|
+
md(`**\u9879\u76EE\u8BBE\u7F6E** \xB7 ${project.name}`),
|
|
3790
|
+
note(`${kindLabel(kind)}${project.cwd ? ` \xB7 \u{1F4C2} \`${project.cwd}\`` : ""}`),
|
|
3791
|
+
hr(),
|
|
3792
|
+
actions([button("\u{1F510} \u6743\u9650", { a: DM.permission, n: project.name }, "primary")]),
|
|
3793
|
+
note(`\u5F53\u524D ${permissionSummary(project)}\u3000\xB7\u3000codex \u6C99\u7BB1\u53EF\u8BBF\u95EE\u7684\u8303\u56F4\uFF08\u7BA1\u7406\u5458 / \u666E\u901A\u7528\u6237\u53EF\u5206\u8BBE\uFF09\u3002`),
|
|
3794
|
+
hr(),
|
|
3795
|
+
md("\u270B \u514D@\uFF08\u4E0D\u7528 @ \u4E5F\u56DE\u590D\uFF09"),
|
|
3796
|
+
actions([
|
|
3797
|
+
button("\u5F00", { a: DM.setNoMentionDm, v: "on", n: project.name }, noMention ? "primary" : "default"),
|
|
3798
|
+
button("\u5173", { a: DM.setNoMentionDm, v: "off", n: project.name }, noMention ? "default" : "primary")
|
|
3799
|
+
]),
|
|
3800
|
+
note(
|
|
3801
|
+
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"
|
|
3802
|
+
),
|
|
3803
|
+
hr(),
|
|
3804
|
+
actions([button("\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", { a: DM.allowlist, n: project.name }, "primary")]),
|
|
3805
|
+
note("\u8BBE\u7F6E\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex\uFF08\u7A7A = \u6240\u6709\u4EBA\uFF09\u3002"),
|
|
3806
|
+
hr(),
|
|
3807
|
+
actions([button("\u2B05\uFE0F \u9879\u76EE\u5217\u8868", { a: DM.projects })])
|
|
3808
|
+
],
|
|
3809
|
+
{ header: { title: "\u2699\uFE0F \u9879\u76EE\u8BBE\u7F6E", template: "blue" } }
|
|
3810
|
+
);
|
|
3811
|
+
}
|
|
3812
|
+
function buildAllowlistCard(project, names) {
|
|
3813
|
+
const list = project.allowedUsers ?? [];
|
|
3814
|
+
const elements = [md(`**\u54CD\u5E94\u767D\u540D\u5355** \xB7 ${project.name}`), note("\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex"), hr()];
|
|
3815
|
+
if (list.length === 0) {
|
|
3816
|
+
elements.push(note("\u5F53\u524D**\u6240\u6709\u4EBA**\u53EF\u7528\uFF08\u7BA1\u7406\u5458\u59CB\u7EC8\u53EF\u7528\uFF09\u3002"));
|
|
3817
|
+
} else {
|
|
3818
|
+
for (const id of list) {
|
|
3819
|
+
elements.push(
|
|
3820
|
+
actions([md(memberName(names, id)), button("\u{1F5D1} \u79FB\u9664", { a: DM.rmAllowed, u: id, n: project.name }, "danger")])
|
|
3821
|
+
);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
elements.push(
|
|
3825
|
+
hr(),
|
|
3826
|
+
actions([
|
|
3827
|
+
button("\u2795 \u6DFB\u52A0", { a: DM.addAllowedForm, n: project.name }, "primary"),
|
|
3828
|
+
button("\u2B05\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: project.name })
|
|
3829
|
+
]),
|
|
3830
|
+
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")
|
|
3831
|
+
);
|
|
3832
|
+
return card(elements, { header: { title: "\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", template: "blue" } });
|
|
3833
|
+
}
|
|
3834
|
+
function buildAddAllowedCard(projectName, members) {
|
|
3835
|
+
const MAX = 50;
|
|
3836
|
+
const shown = members.slice(0, MAX);
|
|
3837
|
+
const formEls = [];
|
|
3838
|
+
if (shown.length > 0) {
|
|
3839
|
+
formEls.push(
|
|
3840
|
+
selectMenu({
|
|
3841
|
+
name: "pick",
|
|
3842
|
+
placeholder: "\u4ECE\u7FA4\u6210\u5458\u9009\u62E9",
|
|
3843
|
+
options: shown.map((m) => ({ label: m.name, value: m.openId }))
|
|
3844
|
+
})
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
formEls.push(
|
|
3848
|
+
input({
|
|
3849
|
+
name: "open_id",
|
|
3850
|
+
label: shown.length ? "\u6216\u76F4\u63A5\u8F93\u5165 open_id" : "\u8F93\u5165 open_id\uFF08\u672A\u8BFB\u53D6\u5230\u7FA4\u6210\u5458\uFF09",
|
|
3851
|
+
placeholder: "ou_xxx"
|
|
3852
|
+
}),
|
|
3853
|
+
actions([submitButton("\u2705 \u786E\u8BA4\u6DFB\u52A0", { a: DM.addAllowedSubmit, n: projectName }, "primary", "submit_allowed")])
|
|
3854
|
+
);
|
|
3855
|
+
const tail = [];
|
|
3856
|
+
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`));
|
|
3857
|
+
return card(
|
|
3858
|
+
[
|
|
3859
|
+
md(`**\u6DFB\u52A0\u53EF\u4F7F\u7528\u300C${projectName}\u300D\u7684\u4EBA**`),
|
|
3860
|
+
form("add_allowed", formEls),
|
|
3861
|
+
...tail,
|
|
3862
|
+
actions([button("\u2B05\uFE0F \u53D6\u6D88", { a: DM.allowlist, n: projectName })])
|
|
3863
|
+
],
|
|
3864
|
+
{ header: { title: "\u2795 \u6DFB\u52A0\u767D\u540D\u5355\u6210\u5458", template: "blue" } }
|
|
3865
|
+
);
|
|
3866
|
+
}
|
|
3575
3867
|
|
|
3576
3868
|
// src/service/update.ts
|
|
3577
3869
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
@@ -4350,9 +4642,19 @@ async function createProject(channel, input2) {
|
|
|
4350
4642
|
params: { member_id_type: "open_id" },
|
|
4351
4643
|
data: { manager_ids: [input2.ownerOpenId] }
|
|
4352
4644
|
}).catch((err) => log.fail("project", err, { phase: "add-manager" }));
|
|
4353
|
-
const project = {
|
|
4645
|
+
const project = {
|
|
4646
|
+
name,
|
|
4647
|
+
chatId,
|
|
4648
|
+
cwd,
|
|
4649
|
+
blank,
|
|
4650
|
+
createdAt: Date.now(),
|
|
4651
|
+
kind: input2.kind ?? "multi",
|
|
4652
|
+
origin: "created",
|
|
4653
|
+
mode: input2.mode ?? "full",
|
|
4654
|
+
network: input2.network ?? false
|
|
4655
|
+
};
|
|
4354
4656
|
await addProject(project);
|
|
4355
|
-
log.info("project", "create", { name, chatId, cwd, blank });
|
|
4657
|
+
log.info("project", "create", { name, chatId, cwd, blank, mode: project.mode });
|
|
4356
4658
|
await setAnnouncement(channel, project).catch((err) => log.fail("project", err, { phase: "announcement" }));
|
|
4357
4659
|
await onboardGroup(channel, project).catch((err) => log.fail("project", err, { phase: "onboard" }));
|
|
4358
4660
|
return project;
|
|
@@ -4372,10 +4674,12 @@ async function joinExistingGroup(channel, input2) {
|
|
|
4372
4674
|
createdAt: Date.now(),
|
|
4373
4675
|
kind: input2.kind ?? "multi",
|
|
4374
4676
|
origin: "joined",
|
|
4375
|
-
addedBy: input2.addedBy
|
|
4677
|
+
addedBy: input2.addedBy,
|
|
4678
|
+
mode: input2.mode ?? "qa",
|
|
4679
|
+
network: input2.network ?? false
|
|
4376
4680
|
};
|
|
4377
4681
|
await addProject(project);
|
|
4378
|
-
log.info("project", "join", { name, chatId: input2.chatId, cwd, blank, kind: project.kind });
|
|
4682
|
+
log.info("project", "join", { name, chatId: input2.chatId, cwd, blank, kind: project.kind, mode: project.mode });
|
|
4379
4683
|
await onboardGroup(channel, project).catch((err) => log.fail("project", err, { phase: "onboard-join" }));
|
|
4380
4684
|
return project;
|
|
4381
4685
|
}
|
|
@@ -4845,6 +5149,74 @@ var Semaphore = class {
|
|
|
4845
5149
|
};
|
|
4846
5150
|
|
|
4847
5151
|
// src/bot/handle-message.ts
|
|
5152
|
+
async function resolveNames(channel, ids) {
|
|
5153
|
+
const uniq = [...new Set(ids.filter((x) => Boolean(x)))];
|
|
5154
|
+
const out = /* @__PURE__ */ new Map();
|
|
5155
|
+
if (uniq.length === 0) return out;
|
|
5156
|
+
try {
|
|
5157
|
+
const r = await channel.rawClient.contact.v3.user.batch({
|
|
5158
|
+
params: { user_ids: uniq, user_id_type: "open_id" }
|
|
5159
|
+
});
|
|
5160
|
+
for (const it of r.data?.items ?? []) {
|
|
5161
|
+
if (it.open_id && it.name) out.set(it.open_id, it.name);
|
|
5162
|
+
}
|
|
5163
|
+
} catch (err) {
|
|
5164
|
+
log.info("console", "resolve-names-fail", { n: uniq.length, err: String(err) });
|
|
5165
|
+
}
|
|
5166
|
+
return out;
|
|
5167
|
+
}
|
|
5168
|
+
async function fetchChatMembers(channel, chatId) {
|
|
5169
|
+
try {
|
|
5170
|
+
const r = await channel.rawClient.im.v1.chatMembers.get({
|
|
5171
|
+
path: { chat_id: chatId },
|
|
5172
|
+
params: { member_id_type: "open_id", page_size: 100 }
|
|
5173
|
+
});
|
|
5174
|
+
const out = [];
|
|
5175
|
+
for (const it of r.data?.items ?? []) {
|
|
5176
|
+
if (it.member_id) out.push({ openId: it.member_id, name: it.name || `\u2026${it.member_id.slice(-6)}` });
|
|
5177
|
+
}
|
|
5178
|
+
return out;
|
|
5179
|
+
} catch (err) {
|
|
5180
|
+
log.info("console", "fetch-members-fail", { chatId: chatId.slice(-6), err: String(err) });
|
|
5181
|
+
return [];
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
async function fetchAllProjectMembers(channel) {
|
|
5185
|
+
const projects = await listProjects();
|
|
5186
|
+
const lists = await Promise.all(projects.filter((p) => p.chatId).map((p) => fetchChatMembers(channel, p.chatId)));
|
|
5187
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5188
|
+
for (const members of lists) {
|
|
5189
|
+
for (const m of members) if (!seen.has(m.openId)) seen.set(m.openId, m.name);
|
|
5190
|
+
}
|
|
5191
|
+
return [...seen].map(([openId, name]) => ({ openId, name }));
|
|
5192
|
+
}
|
|
5193
|
+
function pickOpenId(formValue) {
|
|
5194
|
+
const raw = formValue?.pick;
|
|
5195
|
+
const cands = Array.isArray(raw) ? raw : [raw];
|
|
5196
|
+
for (const c of cands) {
|
|
5197
|
+
if (typeof c === "string" && c.startsWith("ou_")) return c;
|
|
5198
|
+
if (c && typeof c === "object") {
|
|
5199
|
+
const o = c;
|
|
5200
|
+
for (const v of [o.open_id, o.id, o.value]) if (typeof v === "string" && v.startsWith("ou_")) return v;
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5203
|
+
return void 0;
|
|
5204
|
+
}
|
|
5205
|
+
function selectValue(formValue, name) {
|
|
5206
|
+
const c = (() => {
|
|
5207
|
+
const raw = formValue?.[name];
|
|
5208
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
5209
|
+
})();
|
|
5210
|
+
if (typeof c === "string") return c;
|
|
5211
|
+
if (c && typeof c === "object") {
|
|
5212
|
+
const o = c;
|
|
5213
|
+
for (const v of [o.value, o.id]) if (typeof v === "string") return v;
|
|
5214
|
+
}
|
|
5215
|
+
return void 0;
|
|
5216
|
+
}
|
|
5217
|
+
function asTier(v) {
|
|
5218
|
+
return v === "qa" || v === "write" || v === "full" ? v : void 0;
|
|
5219
|
+
}
|
|
4848
5220
|
function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
4849
5221
|
const backend = createBackend();
|
|
4850
5222
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -4922,7 +5294,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4922
5294
|
}
|
|
4923
5295
|
const project = await getProjectByChatId(msg.chatId);
|
|
4924
5296
|
if (!msg.mentionedBot && !(project && shouldRespondWithoutMention(project, msg))) return;
|
|
4925
|
-
if (!isChatAllowed(cfg, msg.chatId) || !
|
|
5297
|
+
if (!isChatAllowed(cfg, msg.chatId) || !isUserAllowedInProject(cfg, project, msg.senderId)) {
|
|
4926
5298
|
log.info("intake", "reject", { reason: "not_allowed", chatId: msg.chatId.slice(-6) });
|
|
4927
5299
|
return;
|
|
4928
5300
|
}
|
|
@@ -4948,11 +5320,12 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4948
5320
|
await postGroupSettings(msg, project);
|
|
4949
5321
|
return;
|
|
4950
5322
|
}
|
|
5323
|
+
const ts = turnSession(msg.chatId, project, msg.senderId);
|
|
4951
5324
|
if (cmd === "model") {
|
|
4952
|
-
await postModelCard(msg,
|
|
5325
|
+
await postModelCard(msg, ts.sessionKey);
|
|
4953
5326
|
return;
|
|
4954
5327
|
}
|
|
4955
|
-
handleTurn(msg, text,
|
|
5328
|
+
handleTurn(msg, text, ts.sessionKey, true, project, ts);
|
|
4956
5329
|
return;
|
|
4957
5330
|
}
|
|
4958
5331
|
if (msg.threadId) {
|
|
@@ -4960,11 +5333,12 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4960
5333
|
await postHelpCard(msg, "topic", true, project);
|
|
4961
5334
|
return;
|
|
4962
5335
|
}
|
|
5336
|
+
const ts = turnSession(msg.threadId, project, msg.senderId);
|
|
4963
5337
|
if (cmd === "model") {
|
|
4964
|
-
await postModelCard(msg,
|
|
5338
|
+
await postModelCard(msg, ts.sessionKey);
|
|
4965
5339
|
return;
|
|
4966
5340
|
}
|
|
4967
|
-
handleTurn(msg, text,
|
|
5341
|
+
handleTurn(msg, text, ts.sessionKey, false, project, ts);
|
|
4968
5342
|
return;
|
|
4969
5343
|
}
|
|
4970
5344
|
if (cmd === "help") {
|
|
@@ -4996,9 +5370,13 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4996
5370
|
if ((project.kind ?? "multi") === "single") return true;
|
|
4997
5371
|
return Boolean(msg.threadId) || parseCommand(msg.content.trim()) !== null;
|
|
4998
5372
|
}
|
|
5373
|
+
async function denyAdminCommand(msg, cmd) {
|
|
5374
|
+
await channel.send(msg.chatId, { markdown: `\u26A0\uFE0F \`/${cmd}\` \u4EC5 bot \u7BA1\u7406\u5458\u53EF\u7528\u3002` }, { replyTo: msg.messageId }).catch(() => void 0);
|
|
5375
|
+
log.info("intake", "cmd-denied", { cmd });
|
|
5376
|
+
}
|
|
4999
5377
|
async function postGroupSettings(msg, project) {
|
|
5000
5378
|
if (!isAdmin(cfg, msg.senderId)) {
|
|
5001
|
-
await
|
|
5379
|
+
await denyAdminCommand(msg, "settings");
|
|
5002
5380
|
return;
|
|
5003
5381
|
}
|
|
5004
5382
|
if (!project) {
|
|
@@ -5010,13 +5388,22 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5010
5388
|
log.info("card", "group-settings", { project: project.name });
|
|
5011
5389
|
});
|
|
5012
5390
|
}
|
|
5013
|
-
|
|
5391
|
+
function turnPerm(project, senderId) {
|
|
5392
|
+
if (!project) return {};
|
|
5393
|
+
const t = turnTier(project, isAdmin(cfg, senderId));
|
|
5394
|
+
return { mode: t.mode, network: project.network, roleSuffix: t.split ? t.role : void 0 };
|
|
5395
|
+
}
|
|
5396
|
+
function turnSession(baseKey, project, senderId) {
|
|
5397
|
+
const perm = turnPerm(project, senderId);
|
|
5398
|
+
return { sessionKey: perm.roleSuffix ? `${baseKey}#${perm.roleSuffix}` : baseKey, ...perm };
|
|
5399
|
+
}
|
|
5400
|
+
async function handleTurn(msg, text, sessionKey, flat, project, perm) {
|
|
5014
5401
|
const existing = active.get(sessionKey);
|
|
5015
5402
|
if (existing) {
|
|
5016
5403
|
const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
|
|
5017
5404
|
const cur = active.get(sessionKey);
|
|
5018
5405
|
if (!cur) {
|
|
5019
|
-
startReservedRun(msg, text, sessionKey, flat, project, images);
|
|
5406
|
+
startReservedRun(msg, text, sessionKey, flat, project, perm, images);
|
|
5020
5407
|
return;
|
|
5021
5408
|
}
|
|
5022
5409
|
if (getPendingPolicy(cfg) === "steer" && cur.run && cur.thread) {
|
|
@@ -5035,9 +5422,9 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5035
5422
|
log.info("intake", "queued", { depth: cur.queue.length });
|
|
5036
5423
|
return;
|
|
5037
5424
|
}
|
|
5038
|
-
startReservedRun(msg, text, sessionKey, flat, project);
|
|
5425
|
+
startReservedRun(msg, text, sessionKey, flat, project, perm);
|
|
5039
5426
|
}
|
|
5040
|
-
function startReservedRun(msg, text, sessionKey, flat, project, preloadedImages) {
|
|
5427
|
+
function startReservedRun(msg, text, sessionKey, flat, project, perm, preloadedImages) {
|
|
5041
5428
|
const existing = active.get(sessionKey);
|
|
5042
5429
|
if (existing) {
|
|
5043
5430
|
existing.queue.push({ text, images: preloadedImages });
|
|
@@ -5050,10 +5437,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5050
5437
|
const reaction = runReaction(msg.messageId, !sema.hasFree());
|
|
5051
5438
|
try {
|
|
5052
5439
|
const images = preloadedImages ?? (messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0);
|
|
5053
|
-
let thread = await resolveThread(sessionKey, msg.chatId);
|
|
5440
|
+
let thread = await resolveThread(sessionKey, msg.chatId, { mode: perm.mode, network: perm.network });
|
|
5054
5441
|
if (!thread) {
|
|
5055
5442
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
5056
|
-
thread = await backend.startThread({ cwd });
|
|
5443
|
+
thread = await backend.startThread({ cwd, mode: perm.mode, network: perm.network });
|
|
5057
5444
|
sessions.set(sessionKey, thread);
|
|
5058
5445
|
await upsertSession({
|
|
5059
5446
|
threadId: sessionKey,
|
|
@@ -5088,7 +5475,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5088
5475
|
}
|
|
5089
5476
|
});
|
|
5090
5477
|
}
|
|
5091
|
-
async function resolveThread(threadId, chatId) {
|
|
5478
|
+
async function resolveThread(threadId, chatId, perm) {
|
|
5092
5479
|
const live = sessions.get(threadId);
|
|
5093
5480
|
if (live) return live;
|
|
5094
5481
|
const rec = await getSession(threadId);
|
|
@@ -5098,7 +5485,9 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5098
5485
|
cwd: rec.cwd,
|
|
5099
5486
|
codexThreadId: rec.codexThreadId,
|
|
5100
5487
|
model: rec.model,
|
|
5101
|
-
effort: rec.effort
|
|
5488
|
+
effort: rec.effort,
|
|
5489
|
+
mode: perm?.mode,
|
|
5490
|
+
network: perm?.network
|
|
5102
5491
|
});
|
|
5103
5492
|
sessions.set(threadId, resumed);
|
|
5104
5493
|
return resumed;
|
|
@@ -5106,20 +5495,39 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5106
5495
|
log.fail("agent", err, { phase: "resume-on-turn", threadId });
|
|
5107
5496
|
const project = await getProjectByChatId(chatId);
|
|
5108
5497
|
const cwd = project?.cwd ?? rec.cwd ?? fallbackCwd;
|
|
5109
|
-
const fresh = await backend.startThread({
|
|
5498
|
+
const fresh = await backend.startThread({
|
|
5499
|
+
cwd,
|
|
5500
|
+
model: rec.model,
|
|
5501
|
+
effort: rec.effort,
|
|
5502
|
+
mode: perm?.mode ?? project?.mode,
|
|
5503
|
+
network: perm?.network ?? project?.network
|
|
5504
|
+
});
|
|
5110
5505
|
sessions.set(threadId, fresh);
|
|
5111
5506
|
return fresh;
|
|
5112
5507
|
}
|
|
5113
5508
|
}
|
|
5509
|
+
async function evictLiveSessionsForChat(chatId) {
|
|
5510
|
+
let closed = 0;
|
|
5511
|
+
for (const rec of await listSessions()) {
|
|
5512
|
+
if (rec.chatId !== chatId) continue;
|
|
5513
|
+
const live = sessions.get(rec.threadId);
|
|
5514
|
+
if (!live) continue;
|
|
5515
|
+
sessions.delete(rec.threadId);
|
|
5516
|
+
void live.close().catch(() => void 0);
|
|
5517
|
+
closed++;
|
|
5518
|
+
}
|
|
5519
|
+
if (closed) log.info("console", "tier-evict", { chatId, closed });
|
|
5520
|
+
}
|
|
5114
5521
|
function startTopicDirectly(msg, text, project) {
|
|
5115
5522
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
5116
5523
|
const reaction = runReaction(msg.messageId, !sema.hasFree());
|
|
5117
5524
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
5525
|
+
const perm = turnPerm(project, msg.senderId);
|
|
5118
5526
|
if (project) void refreshBranch(channel, project).catch(() => void 0);
|
|
5119
5527
|
const { model, effort } = pickDefault(await listModels());
|
|
5120
5528
|
let thread;
|
|
5121
5529
|
try {
|
|
5122
|
-
thread = await backend.startThread({ cwd, model, effort });
|
|
5530
|
+
thread = await backend.startThread({ cwd, model, effort, mode: perm.mode, network: perm.network });
|
|
5123
5531
|
} catch (err) {
|
|
5124
5532
|
reaction.done();
|
|
5125
5533
|
log.fail("card", err, { phase: "start-topic" });
|
|
@@ -5141,7 +5549,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5141
5549
|
effort,
|
|
5142
5550
|
cwd,
|
|
5143
5551
|
summary: text.slice(0, 80) || "(\u7A7A)",
|
|
5144
|
-
requesterOpenId: msg.senderId
|
|
5552
|
+
requesterOpenId: msg.senderId,
|
|
5553
|
+
roleSuffix: perm.roleSuffix
|
|
5145
5554
|
},
|
|
5146
5555
|
reaction,
|
|
5147
5556
|
() => reaction.done()
|
|
@@ -5150,6 +5559,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5150
5559
|
}).catch((err) => log.fail("intake", err));
|
|
5151
5560
|
}
|
|
5152
5561
|
async function postResumeCard(msg) {
|
|
5562
|
+
if (!isAdmin(cfg, msg.senderId)) {
|
|
5563
|
+
await denyAdminCommand(msg, "resume");
|
|
5564
|
+
return;
|
|
5565
|
+
}
|
|
5153
5566
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
5154
5567
|
const project = await getProjectByChatId(msg.chatId);
|
|
5155
5568
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
@@ -5191,7 +5604,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5191
5604
|
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
5192
5605
|
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
5193
5606
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
5194
|
-
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention), msg.messageId, inThread).catch(
|
|
5607
|
+
await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention, isAdmin(cfg, msg.senderId)), msg.messageId, inThread).catch(
|
|
5195
5608
|
(err) => log.fail("card", err, { cmd: "help", scope })
|
|
5196
5609
|
);
|
|
5197
5610
|
log.info("card", "help", { scope });
|
|
@@ -5230,7 +5643,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5230
5643
|
return void 0;
|
|
5231
5644
|
}
|
|
5232
5645
|
const op = evt.operator?.openId ?? "";
|
|
5233
|
-
if (op !== state.requesterOpenId || !isChatAllowed(cfg, state.chatId)
|
|
5646
|
+
if (op !== state.requesterOpenId || !isChatAllowed(cfg, state.chatId)) {
|
|
5234
5647
|
log.info("card", "action-denied", { reason: "not-allowed" });
|
|
5235
5648
|
return void 0;
|
|
5236
5649
|
}
|
|
@@ -5266,7 +5679,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5266
5679
|
settleUpdate(evt.messageId, buildResumeLaunchingCard(state));
|
|
5267
5680
|
void resumeFromCard(evt, state, codexThreadId);
|
|
5268
5681
|
});
|
|
5269
|
-
const runAllowed = (evt) => isChatAllowed(cfg, evt.chatId)
|
|
5682
|
+
const runAllowed = (evt) => isChatAllowed(cfg, evt.chatId);
|
|
5270
5683
|
const runOwnerOrAdmin = (evt, ownerOpenId) => {
|
|
5271
5684
|
if (!runAllowed(evt)) return false;
|
|
5272
5685
|
const op = evt.operator?.openId ?? "";
|
|
@@ -5281,6 +5694,15 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
5281
5694
|
});
|
|
5282
5695
|
const dmAdmin = (openId) => isAdmin(cfg, openId ?? "");
|
|
5283
5696
|
const patch = (evt, c) => settleUpdate(evt.messageId, c, evt.chatId);
|
|
5697
|
+
const namesWithOperator = async (evt, ids) => {
|
|
5698
|
+
const m = await resolveNames(channel, ids);
|
|
5699
|
+
if (ids.some((id) => id && !m.has(id))) {
|
|
5700
|
+
for (const mem of await fetchAllProjectMembers(channel)) if (!m.has(mem.openId)) m.set(mem.openId, mem.name);
|
|
5701
|
+
}
|
|
5702
|
+
const op = evt.operator;
|
|
5703
|
+
if (op?.openId && op.name && !m.has(op.openId)) m.set(op.openId, op.name);
|
|
5704
|
+
return m;
|
|
5705
|
+
};
|
|
5284
5706
|
function applyPref(evt, mut) {
|
|
5285
5707
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5286
5708
|
const prefs = { ...cfg.preferences ?? {} };
|
|
@@ -5504,6 +5926,132 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5504
5926
|
}
|
|
5505
5927
|
return buildGroupSettingsCard({ name: "\u672C\u7FA4", kind: "multi", noMention: on });
|
|
5506
5928
|
});
|
|
5929
|
+
}).on(DM.admins, ({ evt }) => {
|
|
5930
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5931
|
+
patch(
|
|
5932
|
+
evt,
|
|
5933
|
+
async () => buildAdminsCard(cfg, await namesWithOperator(evt, [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []]))
|
|
5934
|
+
);
|
|
5935
|
+
}).on(DM.addAdminForm, ({ evt }) => {
|
|
5936
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5937
|
+
patch(evt, async () => {
|
|
5938
|
+
const all = await fetchAllProjectMembers(channel);
|
|
5939
|
+
const members = all.filter((m) => !isAdmin(cfg, m.openId));
|
|
5940
|
+
return buildAddAdminCard(members);
|
|
5941
|
+
});
|
|
5942
|
+
}).on(DM.addAdminSubmit, ({ evt, formValue }) => {
|
|
5943
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5944
|
+
const manual = String(formValue?.open_id ?? "").trim();
|
|
5945
|
+
const id = manual.startsWith("ou_") ? manual : pickOpenId(formValue);
|
|
5946
|
+
log.info("console", "admin-add", { picked: id?.slice(-6) ?? null });
|
|
5947
|
+
void (async () => {
|
|
5948
|
+
if (id) {
|
|
5949
|
+
const access = { ...cfg.preferences?.access ?? {} };
|
|
5950
|
+
access.ownerOpenId ??= resolveOwner(cfg);
|
|
5951
|
+
access.admins = Array.from(/* @__PURE__ */ new Set([...access.admins ?? [], id]));
|
|
5952
|
+
cfg.preferences = { ...cfg.preferences ?? {}, access };
|
|
5953
|
+
await saveConfig(cfg).catch((e) => log.fail("console", e, { phase: "save-config" }));
|
|
5954
|
+
}
|
|
5955
|
+
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
5956
|
+
const next = buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
5957
|
+
await sendManagedCard(channel, evt.chatId, next).catch((e) => log.fail("console", e, { phase: "admin-add-result" }));
|
|
5958
|
+
})();
|
|
5959
|
+
}).on(DM.rmAdmin, ({ evt, value }) => {
|
|
5960
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5961
|
+
const id = typeof value.u === "string" ? value.u : "";
|
|
5962
|
+
patch(evt, async () => {
|
|
5963
|
+
if (id && id !== resolveOwner(cfg)) {
|
|
5964
|
+
const access = { ...cfg.preferences?.access ?? {} };
|
|
5965
|
+
access.ownerOpenId ??= resolveOwner(cfg);
|
|
5966
|
+
access.admins = (access.admins ?? []).filter((x) => x !== id);
|
|
5967
|
+
cfg.preferences = { ...cfg.preferences ?? {}, access };
|
|
5968
|
+
await saveConfig(cfg).catch((e) => log.fail("console", e, { phase: "save-config" }));
|
|
5969
|
+
}
|
|
5970
|
+
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
5971
|
+
return buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
5972
|
+
});
|
|
5973
|
+
}).on(DM.allowlist, ({ evt, value }) => {
|
|
5974
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5975
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5976
|
+
patch(evt, async () => {
|
|
5977
|
+
const p = await getProjectByName(name);
|
|
5978
|
+
if (!p) return buildDmMenuCard();
|
|
5979
|
+
return buildAllowlistCard(p, await namesWithOperator(evt, p.allowedUsers ?? []));
|
|
5980
|
+
});
|
|
5981
|
+
}).on(DM.addAllowedForm, ({ evt, value }) => {
|
|
5982
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5983
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5984
|
+
if (!name) return;
|
|
5985
|
+
patch(evt, async () => {
|
|
5986
|
+
const p = await getProjectByName(name);
|
|
5987
|
+
const members = p?.chatId ? await fetchChatMembers(channel, p.chatId) : [];
|
|
5988
|
+
return buildAddAllowedCard(name, members);
|
|
5989
|
+
});
|
|
5990
|
+
}).on(DM.addAllowedSubmit, ({ evt, value, formValue }) => {
|
|
5991
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
5992
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
5993
|
+
const manual = String(formValue?.open_id ?? "").trim();
|
|
5994
|
+
const id = manual.startsWith("ou_") ? manual : pickOpenId(formValue);
|
|
5995
|
+
log.info("console", "allow-add", { project: name, picked: id?.slice(-6) ?? null });
|
|
5996
|
+
void (async () => {
|
|
5997
|
+
if (id) await updateProject(name, (p) => ({ allowedUsers: Array.from(/* @__PURE__ */ new Set([...p.allowedUsers ?? [], id])) }));
|
|
5998
|
+
const fresh = await getProjectByName(name);
|
|
5999
|
+
if (!fresh) return;
|
|
6000
|
+
const card2 = buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
|
|
6001
|
+
await sendManagedCard(channel, evt.chatId, card2).catch((e) => log.fail("console", e, { phase: "allow-add-result" }));
|
|
6002
|
+
})();
|
|
6003
|
+
}).on(DM.rmAllowed, ({ evt, value }) => {
|
|
6004
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6005
|
+
const id = typeof value.u === "string" ? value.u : "";
|
|
6006
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
6007
|
+
patch(evt, async () => {
|
|
6008
|
+
await updateProject(name, (p) => ({ allowedUsers: (p.allowedUsers ?? []).filter((x) => x !== id) }));
|
|
6009
|
+
const fresh = await getProjectByName(name);
|
|
6010
|
+
if (!fresh) return buildDmMenuCard();
|
|
6011
|
+
return buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
|
|
6012
|
+
});
|
|
6013
|
+
}).on(DM.projectSettings, ({ evt, value }) => {
|
|
6014
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6015
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
6016
|
+
patch(evt, async () => {
|
|
6017
|
+
const p = await getProjectByName(name);
|
|
6018
|
+
return p ? buildProjectSettingsCard(p) : buildDmMenuCard();
|
|
6019
|
+
});
|
|
6020
|
+
}).on(DM.setNoMentionDm, ({ evt, value }) => {
|
|
6021
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6022
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
6023
|
+
const on = value.v === "on";
|
|
6024
|
+
patch(evt, async () => {
|
|
6025
|
+
const p = await getProjectByName(name);
|
|
6026
|
+
if (!p) return buildDmMenuCard();
|
|
6027
|
+
await updateProject(name, { noMention: on });
|
|
6028
|
+
return buildProjectSettingsCard({ ...p, noMention: on });
|
|
6029
|
+
});
|
|
6030
|
+
}).on(DM.permission, ({ evt, value }) => {
|
|
6031
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6032
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
6033
|
+
patch(evt, async () => {
|
|
6034
|
+
const p = await getProjectByName(name);
|
|
6035
|
+
return p ? buildPermissionCard(p) : buildDmMenuCard();
|
|
6036
|
+
});
|
|
6037
|
+
}).on(DM.permissionSubmit, ({ evt, value, formValue }) => {
|
|
6038
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6039
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
6040
|
+
const mode = asTier(selectValue(formValue, "mode"));
|
|
6041
|
+
const guestMode = asTier(selectValue(formValue, "guestMode"));
|
|
6042
|
+
const network = selectValue(formValue, "network") === "on";
|
|
6043
|
+
void (async () => {
|
|
6044
|
+
const p = await getProjectByName(name);
|
|
6045
|
+
if (!p) return;
|
|
6046
|
+
await updateProject(name, { ...mode ? { mode } : {}, ...guestMode ? { guestMode } : {}, network });
|
|
6047
|
+
await evictLiveSessionsForChat(p.chatId);
|
|
6048
|
+
log.info("console", "permission", { project: name, mode, guestMode, network });
|
|
6049
|
+
const fresh = await getProjectByName(name);
|
|
6050
|
+
if (!fresh) return;
|
|
6051
|
+
await sendManagedCard(channel, evt.chatId, buildProjectSettingsCard(fresh)).catch(
|
|
6052
|
+
(e) => log.fail("console", e, { phase: "permission-result" })
|
|
6053
|
+
);
|
|
6054
|
+
})();
|
|
5507
6055
|
});
|
|
5508
6056
|
async function resumeFromCard(evt, state, codexThreadId) {
|
|
5509
6057
|
try {
|
|
@@ -5605,13 +6153,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5605
6153
|
if (activeKey.startsWith("pending:")) {
|
|
5606
6154
|
const tid = await getThreadId(channel, messageId);
|
|
5607
6155
|
if (tid) {
|
|
6156
|
+
const key = opts.roleSuffix ? `${tid}#${opts.roleSuffix}` : tid;
|
|
5608
6157
|
active.delete(activeKey);
|
|
5609
|
-
active.set(
|
|
5610
|
-
sessions.set(
|
|
5611
|
-
activeKey =
|
|
5612
|
-
topicThreadId =
|
|
5613
|
-
rc.threadId =
|
|
5614
|
-
await persist(
|
|
6158
|
+
active.set(key, state);
|
|
6159
|
+
sessions.set(key, opts.thread);
|
|
6160
|
+
activeKey = key;
|
|
6161
|
+
topicThreadId = key;
|
|
6162
|
+
rc.threadId = key;
|
|
6163
|
+
await persist(key);
|
|
5615
6164
|
}
|
|
5616
6165
|
} else {
|
|
5617
6166
|
topicThreadId = activeKey;
|
|
@@ -5753,8 +6302,6 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5753
6302
|
if (!evt.mentionedBot) return log.info("comment", "skip", { reason: "not-mentioned" });
|
|
5754
6303
|
if (!SUPPORTED_FILE_TYPES.has(evt.fileType))
|
|
5755
6304
|
return log.info("comment", "skip", { reason: "unsupported-fileType", fileType: evt.fileType });
|
|
5756
|
-
if (!isUserAllowed(cfg, evt.operator.openId))
|
|
5757
|
-
return log.info("comment", "skip", { reason: "not-allowed" });
|
|
5758
6305
|
const resolved = await resolveComment(channel, evt);
|
|
5759
6306
|
if (!resolved) return log.info("comment", "skip", { reason: "no-target-or-empty" });
|
|
5760
6307
|
const { target, ctx } = resolved;
|