botmux 2.85.1 → 2.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +209 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/cost-calculator.d.ts.map +1 -1
- package/dist/core/cost-calculator.js +7 -106
- package/dist/core/cost-calculator.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +240 -2
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +1 -1
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/role-resolver.d.ts +1 -0
- package/dist/core/role-resolver.d.ts.map +1 -1
- package/dist/core/role-resolver.js +14 -0
- package/dist/core/role-resolver.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +15 -4
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +116 -0
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/groups.d.ts +2 -0
- package/dist/dashboard/web/groups.d.ts.map +1 -1
- package/dist/dashboard/web/groups.js +419 -3
- package/dist/dashboard/web/groups.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +617 -3
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/insights.d.ts +2 -0
- package/dist/dashboard/web/insights.d.ts.map +1 -0
- package/dist/dashboard/web/insights.js +1523 -0
- package/dist/dashboard/web/insights.js.map +1 -0
- package/dist/dashboard/web/role-profile-match.d.ts +31 -0
- package/dist/dashboard/web/role-profile-match.d.ts.map +1 -0
- package/dist/dashboard/web/role-profile-match.js +58 -0
- package/dist/dashboard/web/role-profile-match.js.map +1 -0
- package/dist/dashboard/web/roles.d.ts +1 -0
- package/dist/dashboard/web/roles.d.ts.map +1 -1
- package/dist/dashboard/web/roles.js +520 -27
- package/dist/dashboard/web/roles.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +84 -0
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard-web/app.js +1243 -831
- package/dist/dashboard-web/index.html +2 -1
- package/dist/dashboard-web/style.css +1085 -3
- package/dist/dashboard.js +215 -3
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +34 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +34 -1
- package/dist/i18n/zh.js.map +1 -1
- package/dist/services/group-creator.d.ts +6 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +54 -5
- package/dist/services/group-creator.js.map +1 -1
- package/dist/services/insight/antigravity-span-reader.d.ts +3 -0
- package/dist/services/insight/antigravity-span-reader.d.ts.map +1 -0
- package/dist/services/insight/antigravity-span-reader.js +249 -0
- package/dist/services/insight/antigravity-span-reader.js.map +1 -0
- package/dist/services/insight/classify.d.ts +7 -0
- package/dist/services/insight/classify.d.ts.map +1 -0
- package/dist/services/insight/classify.js +46 -0
- package/dist/services/insight/classify.js.map +1 -0
- package/dist/services/insight/claude-span-reader.d.ts +3 -0
- package/dist/services/insight/claude-span-reader.d.ts.map +1 -0
- package/dist/services/insight/claude-span-reader.js +257 -0
- package/dist/services/insight/claude-span-reader.js.map +1 -0
- package/dist/services/insight/codex-span-reader.d.ts +3 -0
- package/dist/services/insight/codex-span-reader.d.ts.map +1 -0
- package/dist/services/insight/codex-span-reader.js +290 -0
- package/dist/services/insight/codex-span-reader.js.map +1 -0
- package/dist/services/insight/intent.d.ts +5 -0
- package/dist/services/insight/intent.d.ts.map +1 -0
- package/dist/services/insight/intent.js +145 -0
- package/dist/services/insight/intent.js.map +1 -0
- package/dist/services/insight/jsonl.d.ts +10 -0
- package/dist/services/insight/jsonl.d.ts.map +1 -0
- package/dist/services/insight/jsonl.js +36 -0
- package/dist/services/insight/jsonl.js.map +1 -0
- package/dist/services/insight/prompt.d.ts +3 -0
- package/dist/services/insight/prompt.d.ts.map +1 -0
- package/dist/services/insight/prompt.js +99 -0
- package/dist/services/insight/prompt.js.map +1 -0
- package/dist/services/insight/redact.d.ts +4 -0
- package/dist/services/insight/redact.d.ts.map +1 -0
- package/dist/services/insight/redact.js +67 -0
- package/dist/services/insight/redact.js.map +1 -0
- package/dist/services/insight/report.d.ts +29 -0
- package/dist/services/insight/report.d.ts.map +1 -0
- package/dist/services/insight/report.js +1126 -0
- package/dist/services/insight/report.js.map +1 -0
- package/dist/services/insight/safe-detail.d.ts +5 -0
- package/dist/services/insight/safe-detail.d.ts.map +1 -0
- package/dist/services/insight/safe-detail.js +59 -0
- package/dist/services/insight/safe-detail.js.map +1 -0
- package/dist/services/insight/scrub.d.ts +22 -0
- package/dist/services/insight/scrub.d.ts.map +1 -0
- package/dist/services/insight/scrub.js +70 -0
- package/dist/services/insight/scrub.js.map +1 -0
- package/dist/services/insight/types.d.ts +394 -0
- package/dist/services/insight/types.d.ts.map +1 -0
- package/dist/services/insight/types.js +2 -0
- package/dist/services/insight/types.js.map +1 -0
- package/dist/services/role-profile-store.d.ts +25 -0
- package/dist/services/role-profile-store.d.ts.map +1 -0
- package/dist/services/role-profile-store.js +171 -0
- package/dist/services/role-profile-store.js.map +1 -0
- package/dist/services/transcript-resolver.d.ts +26 -0
- package/dist/services/transcript-resolver.d.ts.map +1 -0
- package/dist/services/transcript-resolver.js +111 -0
- package/dist/services/transcript-resolver.js.map +1 -0
- package/package.json +1 -1
package/dist/dashboard.js
CHANGED
|
@@ -37,8 +37,13 @@ import { loadBotConfigs } from './bot-registry.js';
|
|
|
37
37
|
import { analyzeSkillReferences } from './core/skills/references.js';
|
|
38
38
|
import { installDashboardSkill, parseDashboardSkillInstallRequest } from './dashboard/skill-install-request.js';
|
|
39
39
|
import { botDefaultsPayload, botSummaryPayload } from './dashboard/bot-payload.js';
|
|
40
|
+
import { isValidRoleProfileId } from './services/role-profile-store.js';
|
|
41
|
+
import { mergeSafeInsightOverviews } from './services/insight/report.js';
|
|
40
42
|
const SECRET_PATH = join(homedir(), '.botmux', '.dashboard-secret');
|
|
41
43
|
const TOKEN_PATH = join(homedir(), '.botmux', '.dashboard-token');
|
|
44
|
+
/** Per-daemon budget for the cross-daemon insight overview fan-out — bounds
|
|
45
|
+
* aggregate latency when one daemon's insight parse is slow or hung. */
|
|
46
|
+
const INSIGHT_FANOUT_TIMEOUT_MS = 10_000;
|
|
42
47
|
const BOTS_JSON_PATH = join(homedir(), '.botmux', 'bots.json');
|
|
43
48
|
const REGISTRY_DIR = join(homedir(), '.botmux', 'data', 'dashboard-daemons');
|
|
44
49
|
// The dashboard probes upward if its configured port is busy (e.g. a second
|
|
@@ -219,7 +224,7 @@ function serveFileAbs(res, fp) {
|
|
|
219
224
|
createReadStream(fp).pipe(res);
|
|
220
225
|
return true;
|
|
221
226
|
}
|
|
222
|
-
function serveStatic(
|
|
227
|
+
function serveStatic(req, res, pathname) {
|
|
223
228
|
const rel = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, '');
|
|
224
229
|
const fp = resolve(WEB_DIR, rel);
|
|
225
230
|
const webRoot = resolve(WEB_DIR);
|
|
@@ -231,7 +236,23 @@ function serveStatic(_req, res, pathname) {
|
|
|
231
236
|
const st = statSync(fp);
|
|
232
237
|
if (!st.isFile())
|
|
233
238
|
return false;
|
|
234
|
-
|
|
239
|
+
// Bundle filenames are fixed (app.js/style.css), so without revalidation
|
|
240
|
+
// browsers heuristic-cache them and serve a stale build after a deploy
|
|
241
|
+
// (new JS + old CSS → broken layout). `no-cache` + an mtime/size ETag makes
|
|
242
|
+
// the browser revalidate every load: 304 when unchanged (cheap), fresh 200
|
|
243
|
+
// when the build changed. No manual hard-refresh needed after deploy.
|
|
244
|
+
const etag = `W/"${st.size.toString(16)}-${Math.floor(st.mtimeMs).toString(16)}"`;
|
|
245
|
+
const headers = {
|
|
246
|
+
'content-type': MIME[extname(fp)] ?? 'application/octet-stream',
|
|
247
|
+
'cache-control': 'no-cache',
|
|
248
|
+
etag,
|
|
249
|
+
};
|
|
250
|
+
if (req.headers['if-none-match'] === etag) {
|
|
251
|
+
res.writeHead(304, headers);
|
|
252
|
+
res.end();
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
res.writeHead(200, headers);
|
|
235
256
|
res.end(readFileSync(fp));
|
|
236
257
|
return true;
|
|
237
258
|
}
|
|
@@ -302,7 +323,13 @@ async function createTeamGroup(args) {
|
|
|
302
323
|
const upstream = await proxyToDaemon(plan.creatorLarkAppId, '/api/groups/create', {
|
|
303
324
|
method: 'POST',
|
|
304
325
|
headers: { 'content-type': 'application/json' },
|
|
305
|
-
body: JSON.stringify({
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
name: args.name,
|
|
328
|
+
larkAppIds: selectedIds,
|
|
329
|
+
userOpenIds,
|
|
330
|
+
ownerUnionIds: args.ownerUnionIds ?? [],
|
|
331
|
+
...(args.roleProfileId ? { roleProfileId: args.roleProfileId } : {}),
|
|
332
|
+
}),
|
|
306
333
|
});
|
|
307
334
|
const text = await upstream.text();
|
|
308
335
|
let parsed = null;
|
|
@@ -673,6 +700,29 @@ const server = createServer(async (req, res) => {
|
|
|
673
700
|
});
|
|
674
701
|
return jsonRes(res, 200, { sessions });
|
|
675
702
|
}
|
|
703
|
+
if (req.method === 'GET' && url.pathname === '/api/insights/summary') {
|
|
704
|
+
const limit = Math.min(Math.max(parseInt(url.searchParams.get('limit') ?? '200', 10) || 200, 1), 500);
|
|
705
|
+
// Per-daemon timeout + isolate failures: an upstream insight parse can be
|
|
706
|
+
// heavy, so a slow/hung daemon must not stall the aggregated summary. A
|
|
707
|
+
// timed-out / errored chunk drops to null and is filtered out below.
|
|
708
|
+
const chunks = await Promise.all(registry.list().map(async (d) => {
|
|
709
|
+
try {
|
|
710
|
+
const upstream = await proxyToDaemon(d.larkAppId, `/api/insights/summary?limit=${limit}`, {
|
|
711
|
+
method: 'GET',
|
|
712
|
+
signal: AbortSignal.timeout(INSIGHT_FANOUT_TIMEOUT_MS),
|
|
713
|
+
});
|
|
714
|
+
if (!upstream.ok)
|
|
715
|
+
return null;
|
|
716
|
+
const body = await upstream.json().catch(() => null);
|
|
717
|
+
return body?.overview ?? null;
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
}));
|
|
723
|
+
const overview = mergeSafeInsightOverviews(chunks.filter((x) => !!x), { limit });
|
|
724
|
+
return jsonRes(res, 200, { ok: true, overview });
|
|
725
|
+
}
|
|
676
726
|
if (req.method === 'GET' && url.pathname === '/api/schedules') {
|
|
677
727
|
// Public-read carve-out: the row carries CONTENT (prompt = business
|
|
678
728
|
// instructions) and a bound `workingDir` (repo/customer path) — strip
|
|
@@ -1032,6 +1082,30 @@ const server = createServer(async (req, res) => {
|
|
|
1032
1082
|
res.end(await upstream.text());
|
|
1033
1083
|
return;
|
|
1034
1084
|
}
|
|
1085
|
+
// 会话 insight(只读 trace 分析:动作 span / 失败聚合 / 规则建议)。
|
|
1086
|
+
// owner-only:不在公开读白名单 → decideDashboardAuth 已对只读访客 401,
|
|
1087
|
+
// 公开/联邦访客看不到 tab 也拿不到 span。代理到 owner daemon 的同名 IPC。
|
|
1088
|
+
if (req.method === 'GET' && (m = url.pathname.match(/^\/api\/sessions\/([^/]+)\/insight$/))) {
|
|
1089
|
+
const sid = decodeURIComponent(m[1]);
|
|
1090
|
+
const owner = aggregator.ownerOf(sid);
|
|
1091
|
+
if (!owner)
|
|
1092
|
+
return jsonRes(res, 404, { ok: false, error: 'unknown_session' });
|
|
1093
|
+
const upstream = await proxyToDaemon(owner, `/api/sessions/${sid}/insight${url.search ?? ''}`, { method: 'GET' });
|
|
1094
|
+
res.writeHead(upstream.status, { 'content-type': 'application/json' });
|
|
1095
|
+
res.end(await upstream.text());
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (req.method === 'GET' && (m = url.pathname.match(/^\/api\/sessions\/([^/]+)\/insight\/turn\/([^/]+)$/))) {
|
|
1099
|
+
const sid = decodeURIComponent(m[1]);
|
|
1100
|
+
const turnIndex = decodeURIComponent(m[2]);
|
|
1101
|
+
const owner = aggregator.ownerOf(sid);
|
|
1102
|
+
if (!owner)
|
|
1103
|
+
return jsonRes(res, 404, { ok: false, error: 'unknown_session' });
|
|
1104
|
+
const upstream = await proxyToDaemon(owner, `/api/sessions/${sid}/insight/turn/${turnIndex}${url.search ?? ''}`, { method: 'GET' });
|
|
1105
|
+
res.writeHead(upstream.status, { 'content-type': 'application/json' });
|
|
1106
|
+
res.end(await upstream.text());
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1035
1109
|
// Writable web-terminal link (token-bearing). Not in any public allow-list,
|
|
1036
1110
|
// so decideDashboardAuth has already 401'd unauthenticated callers before we
|
|
1037
1111
|
// get here — the token only reaches authenticated dashboard sessions.
|
|
@@ -1202,6 +1276,135 @@ const server = createServer(async (req, res) => {
|
|
|
1202
1276
|
return;
|
|
1203
1277
|
}
|
|
1204
1278
|
}
|
|
1279
|
+
// ─── Profiles (aggregate/proxy to daemon) ─────────────────────────────
|
|
1280
|
+
if (req.method === 'GET' && url.pathname === '/api/role-profiles') {
|
|
1281
|
+
const merged = new Map();
|
|
1282
|
+
await Promise.all(registry.list().map(async (d) => {
|
|
1283
|
+
try {
|
|
1284
|
+
const r = await fetch(`http://127.0.0.1:${d.ipcPort}/api/role-profiles`);
|
|
1285
|
+
if (!r.ok)
|
|
1286
|
+
return;
|
|
1287
|
+
const j = await r.json();
|
|
1288
|
+
for (const p of j.profiles ?? []) {
|
|
1289
|
+
if (typeof p.profileId !== 'string')
|
|
1290
|
+
continue;
|
|
1291
|
+
const cur = merged.get(p.profileId) ?? { profileId: p.profileId, entryCount: 0, updatedAt: null, botEntries: [] };
|
|
1292
|
+
cur.entryCount = Math.max(cur.entryCount, typeof p.entryCount === 'number' ? p.entryCount : 0);
|
|
1293
|
+
if (typeof p.updatedAt === 'number')
|
|
1294
|
+
cur.updatedAt = cur.updatedAt === null ? p.updatedAt : Math.max(cur.updatedAt, p.updatedAt);
|
|
1295
|
+
const larkAppId = j.larkAppId ?? d.larkAppId;
|
|
1296
|
+
if (!cur.botEntries.some(entry => entry.larkAppId === larkAppId)) {
|
|
1297
|
+
cur.botEntries.push({ larkAppId, hasEntry: p.hasCurrentBotEntry === true });
|
|
1298
|
+
}
|
|
1299
|
+
merged.set(p.profileId, cur);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
catch { /* skip offline/bad daemon */ }
|
|
1303
|
+
}));
|
|
1304
|
+
return jsonRes(res, 200, {
|
|
1305
|
+
profiles: [...merged.values()]
|
|
1306
|
+
.map(p => ({
|
|
1307
|
+
...p,
|
|
1308
|
+
entryCount: Math.max(p.entryCount, p.botEntries.filter(entry => entry.hasEntry).length),
|
|
1309
|
+
}))
|
|
1310
|
+
.sort((a, b) => a.profileId.localeCompare(b.profileId)),
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
let mRoleProfileApply;
|
|
1314
|
+
if (req.method === 'POST' && (mRoleProfileApply = url.pathname.match(/^\/api\/role-profiles\/([^/]+)\/apply$/))) {
|
|
1315
|
+
const profileId = decodeURIComponent(mRoleProfileApply[1]);
|
|
1316
|
+
if (!isValidRoleProfileId(profileId))
|
|
1317
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_role_profile_id' });
|
|
1318
|
+
let raw = '{}';
|
|
1319
|
+
let parsed;
|
|
1320
|
+
try {
|
|
1321
|
+
const chunks = [];
|
|
1322
|
+
for await (const c of req)
|
|
1323
|
+
chunks.push(c);
|
|
1324
|
+
raw = Buffer.concat(chunks).toString('utf8') || '{}';
|
|
1325
|
+
parsed = JSON.parse(raw);
|
|
1326
|
+
}
|
|
1327
|
+
catch {
|
|
1328
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
1329
|
+
}
|
|
1330
|
+
const larkAppId = typeof parsed.larkAppId === 'string' ? parsed.larkAppId : '';
|
|
1331
|
+
if (!larkAppId)
|
|
1332
|
+
return jsonRes(res, 400, { ok: false, error: 'larkAppId_required' });
|
|
1333
|
+
const upstream = await proxyToDaemon(larkAppId, `/api/role-profiles/${encodeURIComponent(profileId)}/apply`, {
|
|
1334
|
+
method: 'POST',
|
|
1335
|
+
headers: { 'content-type': 'application/json' },
|
|
1336
|
+
body: raw,
|
|
1337
|
+
});
|
|
1338
|
+
res.writeHead(upstream.status, { 'content-type': 'application/json' });
|
|
1339
|
+
res.end(await upstream.text());
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
let mRoleProfileEntry;
|
|
1343
|
+
if ((mRoleProfileEntry = url.pathname.match(/^\/api\/role-profiles\/([^/]+)\/([^/]+)$/))) {
|
|
1344
|
+
const profileId = decodeURIComponent(mRoleProfileEntry[1]);
|
|
1345
|
+
const larkAppId = decodeURIComponent(mRoleProfileEntry[2]);
|
|
1346
|
+
if (!isValidRoleProfileId(profileId))
|
|
1347
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_role_profile_id' });
|
|
1348
|
+
if (req.method === 'GET') {
|
|
1349
|
+
const upstream = await proxyToDaemon(larkAppId, `/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(larkAppId)}`, { method: 'GET' });
|
|
1350
|
+
res.writeHead(upstream.status, { 'content-type': 'application/json' });
|
|
1351
|
+
res.end(await upstream.text());
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if (req.method === 'PUT') {
|
|
1355
|
+
const chunks = [];
|
|
1356
|
+
for await (const c of req)
|
|
1357
|
+
chunks.push(c);
|
|
1358
|
+
const raw = Buffer.concat(chunks).toString('utf8') || '{}';
|
|
1359
|
+
const upstream = await proxyToDaemon(larkAppId, `/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(larkAppId)}`, {
|
|
1360
|
+
method: 'PUT',
|
|
1361
|
+
headers: { 'content-type': 'application/json' },
|
|
1362
|
+
body: raw,
|
|
1363
|
+
});
|
|
1364
|
+
res.writeHead(upstream.status, { 'content-type': 'application/json' });
|
|
1365
|
+
res.end(await upstream.text());
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (req.method === 'DELETE') {
|
|
1369
|
+
const upstream = await proxyToDaemon(larkAppId, `/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(larkAppId)}`, { method: 'DELETE' });
|
|
1370
|
+
res.writeHead(upstream.status, { 'content-type': 'application/json' });
|
|
1371
|
+
res.end(await upstream.text());
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
let mRoleProfile;
|
|
1376
|
+
if (req.method === 'GET' && (mRoleProfile = url.pathname.match(/^\/api\/role-profiles\/([^/]+)$/))) {
|
|
1377
|
+
const profileId = decodeURIComponent(mRoleProfile[1]);
|
|
1378
|
+
if (!isValidRoleProfileId(profileId))
|
|
1379
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_role_profile_id' });
|
|
1380
|
+
const byBot = new Map();
|
|
1381
|
+
await Promise.all(registry.list().map(async (d) => {
|
|
1382
|
+
try {
|
|
1383
|
+
const r = await fetch(`http://127.0.0.1:${d.ipcPort}/api/role-profiles/${encodeURIComponent(profileId)}`);
|
|
1384
|
+
if (!r.ok)
|
|
1385
|
+
return;
|
|
1386
|
+
const j = await r.json();
|
|
1387
|
+
for (const entry of j.entries ?? []) {
|
|
1388
|
+
if (typeof entry.larkAppId !== 'string' || typeof entry.content !== 'string')
|
|
1389
|
+
continue;
|
|
1390
|
+
const updatedAt = typeof entry.updatedAt === 'number' ? entry.updatedAt : null;
|
|
1391
|
+
const current = byBot.get(entry.larkAppId);
|
|
1392
|
+
if (current && (current.updatedAt ?? 0) > (updatedAt ?? 0))
|
|
1393
|
+
continue;
|
|
1394
|
+
byBot.set(entry.larkAppId, {
|
|
1395
|
+
profileId,
|
|
1396
|
+
larkAppId: entry.larkAppId,
|
|
1397
|
+
content: entry.content,
|
|
1398
|
+
byteLength: typeof entry.byteLength === 'number' ? entry.byteLength : Buffer.byteLength(entry.content, 'utf-8'),
|
|
1399
|
+
updatedAt,
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
catch { /* skip */ }
|
|
1404
|
+
}));
|
|
1405
|
+
const entries = [...byBot.values()].sort((a, b) => a.larkAppId.localeCompare(b.larkAppId));
|
|
1406
|
+
return jsonRes(res, 200, { profileId, entries });
|
|
1407
|
+
}
|
|
1205
1408
|
let m2;
|
|
1206
1409
|
if (req.method === 'POST' && (m2 = url.pathname.match(/^\/api\/groups\/([^/]+)\/add-bots$/))) {
|
|
1207
1410
|
const chatId = decodeURIComponent(m2[1]);
|
|
@@ -1570,6 +1773,12 @@ const server = createServer(async (req, res) => {
|
|
|
1570
1773
|
if (selectedIds.length === 0) {
|
|
1571
1774
|
return jsonRes(res, 400, { ok: false, error: 'larkAppIds_required' });
|
|
1572
1775
|
}
|
|
1776
|
+
const roleProfileId = typeof parsed.roleProfileId === 'string' && parsed.roleProfileId.trim()
|
|
1777
|
+
? parsed.roleProfileId.trim()
|
|
1778
|
+
: null;
|
|
1779
|
+
if (roleProfileId && !isValidRoleProfileId(roleProfileId)) {
|
|
1780
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_role_profile_id' });
|
|
1781
|
+
}
|
|
1573
1782
|
const explicit = Array.isArray(parsed.userOpenIds)
|
|
1574
1783
|
? parsed.userOpenIds.filter((x) => typeof x === 'string')
|
|
1575
1784
|
: [];
|
|
@@ -1600,6 +1809,7 @@ const server = createServer(async (req, res) => {
|
|
|
1600
1809
|
bindWorkingDir: typeof parsed.bindWorkingDir === 'string' && parsed.bindWorkingDir.trim()
|
|
1601
1810
|
? parsed.bindWorkingDir.trim()
|
|
1602
1811
|
: undefined,
|
|
1812
|
+
roleProfileId: roleProfileId ?? undefined,
|
|
1603
1813
|
};
|
|
1604
1814
|
const upstream = await fetch(`http://127.0.0.1:${creator.ipcPort}/api/groups/create`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(forwardBody) });
|
|
1605
1815
|
const upstreamText = await upstream.text();
|
|
@@ -1609,6 +1819,8 @@ const server = createServer(async (req, res) => {
|
|
|
1609
1819
|
}
|
|
1610
1820
|
catch { /* leave null */ }
|
|
1611
1821
|
if (upstreamJson && typeof upstreamJson === 'object') {
|
|
1822
|
+
if (roleProfileId)
|
|
1823
|
+
upstreamJson.roleProfileId = roleProfileId;
|
|
1612
1824
|
// If Lark rejected the invite (open_id wrong scope, banned user, etc.)
|
|
1613
1825
|
// null out autoInvitedOpenId so the frontend doesn't falsely claim
|
|
1614
1826
|
// success — the user actually isn't a member of the new chat.
|