@modesto_ma/dsh-channels 0.5.0-beta.5 → 0.5.0-beta.7
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 +31 -13
- package/docs/attachment-pipeline-v2.md +1 -1
- package/docs/feishu-onboarding.md +5 -4
- package/lib/client.js +605 -55
- package/lib/client.js.map +1 -1
- package/lib/index.js +379 -21
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/access/cards.ts +5 -1
- package/src/access/controller.ts +14 -6
- package/src/access/policy.ts +7 -2
- package/src/access/requests.ts +1 -0
- package/src/client/api.ts +45 -2
- package/src/client/avatar.tsx +36 -0
- package/src/client/icon.tsx +25 -0
- package/src/client/index.ts +78 -23
- package/src/client/origin-cache.ts +45 -0
- package/src/client/origin.tsx +201 -0
- package/src/client/panel/access-tab.tsx +41 -13
- package/src/client/panel/index.tsx +23 -9
- package/src/client/panel/profiles.ts +73 -0
- package/src/client/panel/whitelist.tsx +17 -11
- package/src/client/panel/wiki-tab.tsx +10 -2
- package/src/client/styles.ts +26 -3
- package/src/config.ts +9 -2
- package/src/domain.ts +44 -2
- package/src/feishu/scopes.ts +2 -1
- package/src/feishu-channel.ts +87 -0
- package/src/routing/inbound.ts +13 -5
- package/src/service.ts +147 -3
- package/src/session/routes.ts +45 -4
- package/src/settings/contract.ts +70 -1
- package/src/settings/routes.ts +47 -0
- package/src/settings/surface.ts +30 -1
package/lib/index.js
CHANGED
|
@@ -37,10 +37,16 @@ function decideAccess(subject, config, snapshot) {
|
|
|
37
37
|
kind: "allow",
|
|
38
38
|
reason: "admin"
|
|
39
39
|
};
|
|
40
|
-
if (subject.chatType === "p2p")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
if (subject.chatType === "p2p") {
|
|
41
|
+
if (config.dmAllowWhitelist && snapshot.users.has(subject.senderId)) return {
|
|
42
|
+
kind: "allow",
|
|
43
|
+
reason: "authorized-user"
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
kind: "deny",
|
|
47
|
+
reason: "dm-non-admin"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
44
50
|
if (!snapshot.groups.has(subject.chatId)) return {
|
|
45
51
|
kind: "deny",
|
|
46
52
|
reason: "group-not-authorized",
|
|
@@ -60,6 +66,8 @@ function decideAccess(subject, config, snapshot) {
|
|
|
60
66
|
//#region src/access/controller.ts
|
|
61
67
|
/** Fixed private-chat notice for non-administrators (no model call). */
|
|
62
68
|
const DM_DENY_TEXT = "非管理员禁用私聊功能哦~如需使用,请在管理员授权的群里 @ 我;或联系管理员把你的 user_id 加入白名单。";
|
|
69
|
+
/** Fixed private-chat notice for non-whitelisted users while `dmAllowWhitelist` is on. */
|
|
70
|
+
const DM_DENY_WHITELIST_TEXT = "你还不在白名单里,暂时不能私聊哦~发送 /userid 查看你的 user_id,请管理员加入白名单后即可使用。";
|
|
63
71
|
/** Fixed private-chat notice while no administrator is configured. */
|
|
64
72
|
const DM_UNCONFIGURED_TEXT = "机器人尚未配置管理员。发送 /userid 可查看你的 user_id,交给部署者配置后即可启用。";
|
|
65
73
|
/** Per-sender cooldown for the repeated private-chat notice (10 s). */
|
|
@@ -122,8 +130,9 @@ var AccessController = class {
|
|
|
122
130
|
/**
|
|
123
131
|
* Slash-command admission. Runs before any command card/topic/Session side
|
|
124
132
|
* effect. `open` mode keeps the legacy behavior; otherwise private chats
|
|
125
|
-
*
|
|
126
|
-
* group chats must pass the full admission
|
|
133
|
+
* admit whoever the admission matrix admits (with `/userid` as the
|
|
134
|
+
* bootstrap exception) and group chats must pass the full admission
|
|
135
|
+
* matrix; the administrator is
|
|
127
136
|
* never constrained by the whitelists, so all of their commands pass even
|
|
128
137
|
* inside an unauthorized group (`/auth` is the in-conversation grant
|
|
129
138
|
* entry).
|
|
@@ -133,7 +142,7 @@ var AccessController = class {
|
|
|
133
142
|
try {
|
|
134
143
|
if (msg.chatType === "p2p") {
|
|
135
144
|
if (commandName === "userid") return true;
|
|
136
|
-
if (this.
|
|
145
|
+
if (this.decide(msg).kind === "allow") return true;
|
|
137
146
|
await this.noticeDeniedDm(msg);
|
|
138
147
|
return false;
|
|
139
148
|
}
|
|
@@ -195,7 +204,8 @@ var AccessController = class {
|
|
|
195
204
|
const last = this.dmNoticeAt.get(msg.senderId);
|
|
196
205
|
if (last !== void 0 && now - last < DM_NOTICE_COOLDOWN_MS) return;
|
|
197
206
|
this.dmNoticeAt.set(msg.senderId, now);
|
|
198
|
-
const
|
|
207
|
+
const config = this.config();
|
|
208
|
+
const text = config.adminUserId === void 0 ? DM_UNCONFIGURED_TEXT : config.dmAllowWhitelist ? DM_DENY_WHITELIST_TEXT : DM_DENY_TEXT;
|
|
199
209
|
await this.sendText(msg.chatId, text, msg.messageId);
|
|
200
210
|
}
|
|
201
211
|
/** Best-effort fixed-text send; unavailable channel/failures only warn. */
|
|
@@ -400,7 +410,7 @@ function buildUserRequestCard(input) {
|
|
|
400
410
|
} else if (input.chatId !== void 0 && input.chatId !== "") elements.push(field("所在群", `\`${input.chatId}\``));
|
|
401
411
|
elements.push(field("申请时间", formatTime(input.now)));
|
|
402
412
|
elements.push(divider());
|
|
403
|
-
elements.push(note("授权后该用户可在所有已授权群里使用;私聊仍仅管理员可用。"));
|
|
413
|
+
elements.push(note(input.dmAllowWhitelist === true ? "授权后该用户可在所有已授权群里使用,也可以私聊机器人。" : "授权后该用户可在所有已授权群里使用;私聊仍仅管理员可用。"));
|
|
404
414
|
elements.push(decisionButtons(input.token, "授权用户", "忽略"));
|
|
405
415
|
return standaloneCard("**🙋 用户授权申请**", elements);
|
|
406
416
|
}
|
|
@@ -733,6 +743,7 @@ var AccessRequests = class {
|
|
|
733
743
|
...groupName === void 0 ? {} : { groupName },
|
|
734
744
|
...msg.senderName === void 0 ? {} : { requesterName: msg.senderName },
|
|
735
745
|
token: record.token,
|
|
746
|
+
dmAllowWhitelist: this.config().dmAllowWhitelist,
|
|
736
747
|
now
|
|
737
748
|
}), record);
|
|
738
749
|
return true;
|
|
@@ -1235,8 +1246,8 @@ const accessGroupRecordSchema = z.object({
|
|
|
1235
1246
|
});
|
|
1236
1247
|
/**
|
|
1237
1248
|
* One authorized user (`user_id`), global across every authorized group.
|
|
1238
|
-
* Private chats are administrator-only
|
|
1239
|
-
* conversations.
|
|
1249
|
+
* Private chats are administrator-only unless `dmAllowWhitelist` is on, so
|
|
1250
|
+
* by default this grant only affects group conversations.
|
|
1240
1251
|
*/
|
|
1241
1252
|
const accessUserRecordSchema = z.object({
|
|
1242
1253
|
/** Feishu `user_id` — also the table key. */
|
|
@@ -1337,6 +1348,31 @@ const analyzeRequestRecordSchema = z.object({
|
|
|
1337
1348
|
])
|
|
1338
1349
|
});
|
|
1339
1350
|
/**
|
|
1351
|
+
* Where one Feishu-routed Session came from, keyed by its DSH session id.
|
|
1352
|
+
* Written the first time a route record names the Session (new topic,
|
|
1353
|
+
* `/renew`, analyze API) and kept after the route retires, so the Web
|
|
1354
|
+
* client can still show the origin of an old Session. Names are resolved
|
|
1355
|
+
* live when read, never stored.
|
|
1356
|
+
*/
|
|
1357
|
+
const sessionOriginRecordSchema = z.object({
|
|
1358
|
+
/** DSH session id (also the table key). */
|
|
1359
|
+
key: z.string().min(1),
|
|
1360
|
+
/** Route key the Session was bound under (thread_id or chat:<chatId>). */
|
|
1361
|
+
routeKey: z.string().min(1),
|
|
1362
|
+
/** Topic-scoped (`thread`) or chat-scoped (`chat`) route. */
|
|
1363
|
+
kind: z.enum(["thread", "chat"]),
|
|
1364
|
+
/** Feishu chat the Session was started from. */
|
|
1365
|
+
chatId: z.string().min(1),
|
|
1366
|
+
/** `p2p` or `group`. */
|
|
1367
|
+
chatType: z.enum(["p2p", "group"]),
|
|
1368
|
+
/** `user_id` of the user who started the Session (the requester for analyze topics). */
|
|
1369
|
+
senderUserId: z.string().min(1),
|
|
1370
|
+
/** Epoch ms when the Session was bound to the route. */
|
|
1371
|
+
createdAt: z.number().int().nonnegative(),
|
|
1372
|
+
/** Group-shared route (log-platform analysis topic). */
|
|
1373
|
+
shared: z.literal(true).optional()
|
|
1374
|
+
});
|
|
1375
|
+
/**
|
|
1340
1376
|
* Domain declaration for the host's storage facility. Plain data plus zod
|
|
1341
1377
|
* schemas — deliberately not built with `defineDomain` so this module has no
|
|
1342
1378
|
* runtime import of `@deepseek-ai/dsh-storage-domain`.
|
|
@@ -1364,7 +1400,8 @@ const feishuDomain = {
|
|
|
1364
1400
|
access_users: { valueSchema: accessUserRecordSchema },
|
|
1365
1401
|
access_requests: { valueSchema: accessRequestRecordSchema },
|
|
1366
1402
|
access_wiki_users: { valueSchema: accessUserRecordSchema },
|
|
1367
|
-
analyze_requests: { valueSchema: analyzeRequestRecordSchema }
|
|
1403
|
+
analyze_requests: { valueSchema: analyzeRequestRecordSchema },
|
|
1404
|
+
session_origins: { valueSchema: sessionOriginRecordSchema }
|
|
1368
1405
|
}
|
|
1369
1406
|
};
|
|
1370
1407
|
/**
|
|
@@ -3367,6 +3404,7 @@ var RouteStore = class {
|
|
|
3367
3404
|
this.expired.set(key, record);
|
|
3368
3405
|
this.active.delete(key);
|
|
3369
3406
|
}
|
|
3407
|
+
for (const record of this.active.values()) this.recordOrigins(record);
|
|
3370
3408
|
}
|
|
3371
3409
|
/** One active route by key (cache-first, normalized). */
|
|
3372
3410
|
get(key) {
|
|
@@ -3390,7 +3428,13 @@ var RouteStore = class {
|
|
|
3390
3428
|
/** Persist one active route and refresh the cache. */
|
|
3391
3429
|
async put(key, record) {
|
|
3392
3430
|
await this.deps.domain()?.routes.put(key, record);
|
|
3393
|
-
|
|
3431
|
+
const normalized = normalizeRouteRecord(record);
|
|
3432
|
+
this.active.set(key, normalized);
|
|
3433
|
+
await this.recordOrigins(normalized);
|
|
3434
|
+
}
|
|
3435
|
+
/** Feishu origin of one Session any route ever named, retired routes included. */
|
|
3436
|
+
origin(sessionId) {
|
|
3437
|
+
return this.deps.domain()?.sessionOrigins.get(sessionId);
|
|
3394
3438
|
}
|
|
3395
3439
|
/** Delete one active route from the medium and the cache. */
|
|
3396
3440
|
async remove(key) {
|
|
@@ -3418,6 +3462,33 @@ var RouteStore = class {
|
|
|
3418
3462
|
this.active.clear();
|
|
3419
3463
|
this.expired.clear();
|
|
3420
3464
|
}
|
|
3465
|
+
/**
|
|
3466
|
+
* Write an origin for every Session in the route's history that has none.
|
|
3467
|
+
* Existing origins are never rewritten: the first route naming a Session
|
|
3468
|
+
* is where it started. Failures are logged and never fail the route write.
|
|
3469
|
+
*/
|
|
3470
|
+
async recordOrigins(record) {
|
|
3471
|
+
const table = this.deps.domain()?.sessionOrigins;
|
|
3472
|
+
if (table === void 0) return;
|
|
3473
|
+
for (const entry of normalizeSessionHistory(record)) {
|
|
3474
|
+
if (table.get(entry.sessionId) !== void 0) continue;
|
|
3475
|
+
const origin = {
|
|
3476
|
+
key: entry.sessionId,
|
|
3477
|
+
routeKey: record.key,
|
|
3478
|
+
kind: record.kind,
|
|
3479
|
+
chatId: record.chatId,
|
|
3480
|
+
chatType: record.chatType,
|
|
3481
|
+
senderUserId: record.senderUserId,
|
|
3482
|
+
createdAt: entry.createdAt,
|
|
3483
|
+
...record.shared === true ? { shared: true } : {}
|
|
3484
|
+
};
|
|
3485
|
+
try {
|
|
3486
|
+
await table.put(entry.sessionId, origin);
|
|
3487
|
+
} catch (error) {
|
|
3488
|
+
this.deps.warn("dsh-channels: recording the origin of session %s failed: %s", entry.sessionId, String(error));
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3421
3492
|
/** Whether one active route has crossed the configured inactivity deadline. */
|
|
3422
3493
|
hasExpired(record, now) {
|
|
3423
3494
|
const ttlMs = this.deps.ttlMs();
|
|
@@ -3504,7 +3575,12 @@ const BASE_REQUIREMENTS = [
|
|
|
3504
3575
|
{
|
|
3505
3576
|
anyOf: ["im:chat:readonly", "im:chat"],
|
|
3506
3577
|
level: "optional",
|
|
3507
|
-
purpose: "
|
|
3578
|
+
purpose: "机器人入群后读取群信息、发送群信息卡;Web 页面显示群名和群头像"
|
|
3579
|
+
},
|
|
3580
|
+
{
|
|
3581
|
+
anyOf: ["contact:user.base:readonly"],
|
|
3582
|
+
level: "optional",
|
|
3583
|
+
purpose: "Web 页面显示用户真实姓名和头像"
|
|
3508
3584
|
}
|
|
3509
3585
|
];
|
|
3510
3586
|
/** Panel sync needs, applicable while `slashCommandPanel` is on. */
|
|
@@ -4279,6 +4355,19 @@ function thrownErrorFields(error) {
|
|
|
4279
4355
|
function assertFeishuOk(response) {
|
|
4280
4356
|
if (response.code !== void 0 && response.code !== 0) throw new FeishuApiError(`code=${response.code}${response.msg === void 0 ? "" : ` ${response.msg}`}`, response.code);
|
|
4281
4357
|
}
|
|
4358
|
+
/** Member-list pages `memberName()` scans before giving up (100 members each). */
|
|
4359
|
+
const MEMBER_NAME_PAGES = 5;
|
|
4360
|
+
/** `user_ids` per `contact.v3.user.batch` call (the API maximum). */
|
|
4361
|
+
const USER_BATCH_SIZE = 50;
|
|
4362
|
+
/** Keep a non-blank name and an `https:` avatar URL from one Feishu response. */
|
|
4363
|
+
function feishuProfile(name, avatar) {
|
|
4364
|
+
const trimmed = name?.trim();
|
|
4365
|
+
const avatarUrl = avatar !== void 0 && /^https:\/\/[^\s"'<>]+$/u.test(avatar) ? avatar : void 0;
|
|
4366
|
+
return {
|
|
4367
|
+
...trimmed === void 0 || trimmed === "" ? {} : { name: trimmed },
|
|
4368
|
+
...avatarUrl === void 0 ? {} : { avatarUrl }
|
|
4369
|
+
};
|
|
4370
|
+
}
|
|
4282
4371
|
/** Run one raw request, mapping thrown and non-zero-code failures to {@link FeishuApiError}. */
|
|
4283
4372
|
async function feishuCall(call) {
|
|
4284
4373
|
let response;
|
|
@@ -4415,6 +4504,49 @@ var LarkChannelAdapter = class {
|
|
|
4415
4504
|
async getChatInfo(chatId) {
|
|
4416
4505
|
return this.channel.getChatInfo(chatId);
|
|
4417
4506
|
}
|
|
4507
|
+
async userProfiles(userIds) {
|
|
4508
|
+
const client = this.channel.rawClient;
|
|
4509
|
+
const profiles = /* @__PURE__ */ new Map();
|
|
4510
|
+
for (let offset = 0; offset < userIds.length; offset += USER_BATCH_SIZE) {
|
|
4511
|
+
const response = await feishuCall(() => client.contact.v3.user.batch({ params: {
|
|
4512
|
+
user_ids: userIds.slice(offset, offset + USER_BATCH_SIZE),
|
|
4513
|
+
user_id_type: "user_id"
|
|
4514
|
+
} }));
|
|
4515
|
+
for (const item of response.data?.items ?? []) {
|
|
4516
|
+
if (item.user_id === void 0) continue;
|
|
4517
|
+
profiles.set(item.user_id, feishuProfile(item.name, item.avatar?.avatar_72));
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
return profiles;
|
|
4521
|
+
}
|
|
4522
|
+
async chatProfile(chatId) {
|
|
4523
|
+
const client = this.channel.rawClient;
|
|
4524
|
+
const response = await feishuCall(() => client.im.v1.chat.get({ path: { chat_id: chatId } }));
|
|
4525
|
+
return feishuProfile(response.data?.name, response.data?.avatar);
|
|
4526
|
+
}
|
|
4527
|
+
async memberName(userId, chatId) {
|
|
4528
|
+
const client = this.channel.rawClient;
|
|
4529
|
+
let pageToken;
|
|
4530
|
+
for (let page = 0; page < MEMBER_NAME_PAGES; page += 1) {
|
|
4531
|
+
let response;
|
|
4532
|
+
try {
|
|
4533
|
+
response = await feishuCall(() => client.im.v1.chatMembers.get({
|
|
4534
|
+
path: { chat_id: chatId },
|
|
4535
|
+
params: {
|
|
4536
|
+
member_id_type: "user_id",
|
|
4537
|
+
page_size: 100,
|
|
4538
|
+
...pageToken === void 0 ? {} : { page_token: pageToken }
|
|
4539
|
+
}
|
|
4540
|
+
}));
|
|
4541
|
+
} catch (_membersUnavailable) {
|
|
4542
|
+
return;
|
|
4543
|
+
}
|
|
4544
|
+
const hit = response.data?.items?.find((item) => item.member_id === userId)?.name?.trim();
|
|
4545
|
+
if (hit !== void 0 && hit !== "") return hit;
|
|
4546
|
+
pageToken = response.data?.page_token;
|
|
4547
|
+
if (response.data?.has_more !== true || pageToken === void 0) return void 0;
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4418
4550
|
botOpenId() {
|
|
4419
4551
|
return this.channel.botIdentity?.openId;
|
|
4420
4552
|
}
|
|
@@ -6727,7 +6859,7 @@ var InboundRouter = class {
|
|
|
6727
6859
|
} catch (error) {
|
|
6728
6860
|
this.ctx.logger?.warn?.("dsh-channels: recording processed message %s failed: %s", msg.messageId, String(error));
|
|
6729
6861
|
}
|
|
6730
|
-
if (content.startsWith("/")) {
|
|
6862
|
+
if (content.startsWith("/") && this.deps.commands.owns(content)) {
|
|
6731
6863
|
await this.deps.commands.handleSlash(msg, content);
|
|
6732
6864
|
return;
|
|
6733
6865
|
}
|
|
@@ -16148,14 +16280,21 @@ const configSchema = z.object({
|
|
|
16148
16280
|
* Admission policy for inbound Feishu traffic.
|
|
16149
16281
|
*
|
|
16150
16282
|
* - `guarded` (default): only the configured administrator can talk in
|
|
16151
|
-
* private chats
|
|
16152
|
-
*
|
|
16283
|
+
* private chats (plus whitelisted users when `dmAllowWhitelist` is on),
|
|
16284
|
+
* and a group conversation additionally requires the group to be
|
|
16285
|
+
* authorized and the sender to be the administrator or an explicitly
|
|
16153
16286
|
* authorized user.
|
|
16154
16287
|
* - `open`: legacy behavior — any user inside the app's availability scope
|
|
16155
16288
|
* can start a conversation. Debug/compatibility only; unsafe.
|
|
16156
16289
|
*/
|
|
16157
16290
|
accessMode: z.enum(["open", "guarded"]).default("guarded"),
|
|
16158
16291
|
/**
|
|
16292
|
+
* In `guarded` mode, also admit users on the global user whitelist
|
|
16293
|
+
* (`/auth user`) in private chats. Off by default: private chats stay
|
|
16294
|
+
* administrator-only. Each private-chat user already has an own workspace.
|
|
16295
|
+
*/
|
|
16296
|
+
dmAllowWhitelist: z.boolean().default(false),
|
|
16297
|
+
/**
|
|
16159
16298
|
* The unique administrator's Feishu `user_id`. Set it in the Web
|
|
16160
16299
|
* settings panel (or the profile config). Get it by sending `/userid` to the
|
|
16161
16300
|
* bot in a private chat. When unset, the Feishu app owner (looked up via
|
|
@@ -16256,6 +16395,14 @@ const ACCESS_REQUESTS_ROUTE = "/dsh-channels/access/requests";
|
|
|
16256
16395
|
* [`EditableFieldWire.browse`],保留手写输入。
|
|
16257
16396
|
*/
|
|
16258
16397
|
const PICK_DIRECTORY_ROUTE = "/dsh-channels/pick-directory";
|
|
16398
|
+
/** 会话的飞书来源(GET/HEAD,`?sessionId=`):对话右侧栏「飞书来源」标签与会话头部入口使用。 */
|
|
16399
|
+
const SESSION_ORIGIN_ROUTE = "/dsh-channels/session-origin";
|
|
16400
|
+
/**
|
|
16401
|
+
* `GET PROFILES_ROUTE?users=<user_id,...>&chats=<chat_id,...>`: live Feishu
|
|
16402
|
+
* names and avatars for the users and chats the panel shows. At most
|
|
16403
|
+
* {@link PROFILES_MAX_IDS} ids per list.
|
|
16404
|
+
*/
|
|
16405
|
+
const PROFILES_ROUTE = "/dsh-channels/profiles";
|
|
16259
16406
|
/**
|
|
16260
16407
|
* 可由 Web 面板覆盖的配置项。顶层键对应 `src/config.ts` 的字段;`feishuTools.*`
|
|
16261
16408
|
* 是嵌套字段(覆盖层里仍写成 `{ "feishuTools": { ... } }` 对象)。覆盖值写进
|
|
@@ -16307,6 +16454,7 @@ const EDITABLE_KEYS = [
|
|
|
16307
16454
|
"feishuTools.topicMessagesTimeoutMs",
|
|
16308
16455
|
"promptContextTimeoutMs",
|
|
16309
16456
|
"accessMode",
|
|
16457
|
+
"dmAllowWhitelist",
|
|
16310
16458
|
"adminUserId",
|
|
16311
16459
|
"authRequestTtlDays",
|
|
16312
16460
|
"feishuTools.wikiRead",
|
|
@@ -16457,7 +16605,10 @@ const PANEL_COPY = {
|
|
|
16457
16605
|
cleared: "已恢复为 profile 配置",
|
|
16458
16606
|
debugModeWarning: "调试模式已打开:应用可用范围内的任何人都能和机器人对话。用完请在「访问授权」关闭。",
|
|
16459
16607
|
accessAdminMissingWarning: "没有可用的管理员,所有对话都会被拒绝。请在下方填写管理员 user_id(私聊机器人发送 /userid 获取)。",
|
|
16460
|
-
|
|
16608
|
+
accessAdminCurrent: "当前管理员",
|
|
16609
|
+
accessAdminSourceConfig: "已配置",
|
|
16610
|
+
accessAdminSourceOwner: "应用所有者",
|
|
16611
|
+
profilesWarning: "部分飞书名字和头像未能读取:{reason}",
|
|
16461
16612
|
accessNotLoaded: "飞书连接建立后加载授权列表",
|
|
16462
16613
|
accessRequestsTitle: "待处理申请",
|
|
16463
16614
|
accessGroupsTitle: "已授权群",
|
|
@@ -16766,6 +16917,18 @@ const APP_OWNER_LOOKUP_TIMEOUT_MS = 1e4;
|
|
|
16766
16917
|
const SCOPE_CHECK_TIMEOUT_MS = 1e4;
|
|
16767
16918
|
/** Upper bound on the whole post-connect slash-command panel sync (one list plus per-command writes). */
|
|
16768
16919
|
const SLASH_COMMAND_SYNC_TIMEOUT_MS = 3e4;
|
|
16920
|
+
/**
|
|
16921
|
+
* How long one resolved user or chat profile is reused. Short enough that
|
|
16922
|
+
* every page entry shows current Feishu data; long enough that the session
|
|
16923
|
+
* header and its sidebar tab opening together cost one lookup.
|
|
16924
|
+
*/
|
|
16925
|
+
const PROFILE_TTL_MS = 30 * 1e3;
|
|
16926
|
+
/** The Feishu `code=<n> <msg>` of a failed API call, else the error text. */
|
|
16927
|
+
function feishuReason(error) {
|
|
16928
|
+
return error instanceof FeishuApiError ? error.detail : messageOf$3(error);
|
|
16929
|
+
}
|
|
16930
|
+
/** Feishu `chat_id`s the profile lookup accepts. */
|
|
16931
|
+
const FEISHU_CHAT_ID_PATTERN = /^oc_[A-Za-z0-9_-]+$/u;
|
|
16769
16932
|
/** Wrap the host's storage facility open behind a stable local type. */
|
|
16770
16933
|
async function openFeishuDomain(ctx) {
|
|
16771
16934
|
const domain = await ctx.storageDomain.open(feishuDomain);
|
|
@@ -16778,6 +16941,7 @@ async function openFeishuDomain(ctx) {
|
|
|
16778
16941
|
const accessRequestsRaw = domain.table("access_requests");
|
|
16779
16942
|
const accessWikiUsersRaw = domain.table("access_wiki_users");
|
|
16780
16943
|
const analyzeRequestsRaw = domain.table("analyze_requests");
|
|
16944
|
+
const sessionOriginsRaw = domain.table("session_origins");
|
|
16781
16945
|
return {
|
|
16782
16946
|
routes: {
|
|
16783
16947
|
get: (key) => table.get(key),
|
|
@@ -16832,6 +16996,10 @@ async function openFeishuDomain(ctx) {
|
|
|
16832
16996
|
put: (key, value) => analyzeRequestsRaw.put(key, value),
|
|
16833
16997
|
entries: () => analyzeRequestsRaw.entries()
|
|
16834
16998
|
},
|
|
16999
|
+
sessionOrigins: {
|
|
17000
|
+
get: (key) => sessionOriginsRaw.get(key),
|
|
17001
|
+
put: (key, value) => sessionOriginsRaw.put(key, value)
|
|
17002
|
+
},
|
|
16835
17003
|
getGlobal: () => domain.global.get() ?? {},
|
|
16836
17004
|
setGlobal: (value) => domain.global.set(value),
|
|
16837
17005
|
close: () => domain.close()
|
|
@@ -16978,6 +17146,8 @@ var FeishuService = class {
|
|
|
16978
17146
|
* is unset. Cleared on shutdown so a reconnect looks it up again.
|
|
16979
17147
|
*/
|
|
16980
17148
|
appOwner;
|
|
17149
|
+
/** Live-resolved user and chat profiles, by `user:`/`chat:` key; misses are kept as `{}`. */
|
|
17150
|
+
profileCache = /* @__PURE__ */ new Map();
|
|
16981
17151
|
/** Post-connect scope self-check; cleared on shutdown. */
|
|
16982
17152
|
scopeCheckState;
|
|
16983
17153
|
/** Post-connect slash-command panel sync; cleared on shutdown. */
|
|
@@ -17156,7 +17326,13 @@ var FeishuService = class {
|
|
|
17156
17326
|
streamer: () => this.streamer,
|
|
17157
17327
|
questionAnswerer: () => this.questionAnswerer,
|
|
17158
17328
|
approvalAnswerer: () => this.approvalAnswerer,
|
|
17159
|
-
commands: {
|
|
17329
|
+
commands: {
|
|
17330
|
+
owns: (line) => {
|
|
17331
|
+
const parsed = parseSlashCommand(line);
|
|
17332
|
+
return parsed !== void 0 && commandSpec(parsed.name) !== void 0;
|
|
17333
|
+
},
|
|
17334
|
+
handleSlash: (msg, line) => this.handleSlashMessage(msg, line)
|
|
17335
|
+
}
|
|
17160
17336
|
});
|
|
17161
17337
|
this.analyzeLauncher = new AnalyzeLauncher({
|
|
17162
17338
|
ctx: this.ctx,
|
|
@@ -17645,6 +17821,118 @@ var FeishuService = class {
|
|
|
17645
17821
|
};
|
|
17646
17822
|
}
|
|
17647
17823
|
}
|
|
17824
|
+
/** Panel: Feishu origin of one Session, or null when Feishu never routed it. */
|
|
17825
|
+
async sessionOrigin(sessionId) {
|
|
17826
|
+
const origin = this.routes.origin(sessionId);
|
|
17827
|
+
if (origin === void 0) return null;
|
|
17828
|
+
const route = this.routes.findBySession(sessionId);
|
|
17829
|
+
const bound = route !== void 0;
|
|
17830
|
+
const group = origin.chatType === "group";
|
|
17831
|
+
const profiles = await this.feishuProfiles([origin.senderUserId], group ? [origin.chatId] : []);
|
|
17832
|
+
const chat = profiles.chats[origin.chatId] ?? {};
|
|
17833
|
+
const sender = profiles.users[origin.senderUserId] ?? {};
|
|
17834
|
+
const senderName = sender.name ?? (group ? await this.memberName(origin.senderUserId, origin.chatId) : void 0);
|
|
17835
|
+
return {
|
|
17836
|
+
sessionId,
|
|
17837
|
+
chatType: origin.chatType,
|
|
17838
|
+
kind: origin.kind,
|
|
17839
|
+
chatId: origin.chatId,
|
|
17840
|
+
...chat.name === void 0 ? {} : { chatName: chat.name },
|
|
17841
|
+
...chat.avatarUrl === void 0 ? {} : { chatAvatarUrl: chat.avatarUrl },
|
|
17842
|
+
senderUserId: origin.senderUserId,
|
|
17843
|
+
...senderName === void 0 ? {} : { senderName },
|
|
17844
|
+
...sender.avatarUrl === void 0 ? {} : { senderAvatarUrl: sender.avatarUrl },
|
|
17845
|
+
shared: origin.shared === true,
|
|
17846
|
+
createdAt: origin.createdAt,
|
|
17847
|
+
bound,
|
|
17848
|
+
...bound ? { lastActivityAt: route.lastActivityAt } : {},
|
|
17849
|
+
chatLink: `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(origin.chatId)}`
|
|
17850
|
+
};
|
|
17851
|
+
}
|
|
17852
|
+
/**
|
|
17853
|
+
* Live Feishu names and avatars for the panel and the session-origin view,
|
|
17854
|
+
* through a {@link PROFILE_TTL_MS} cache. Ids that are not Feishu
|
|
17855
|
+
* `user_id`s / `chat_id`s are skipped; a disconnected channel and failed
|
|
17856
|
+
* lookups leave ids absent, and failures are reported in `warning` and not
|
|
17857
|
+
* cached.
|
|
17858
|
+
* @param userIds - user `user_id`s to resolve.
|
|
17859
|
+
* @param chatIds - chat `chat_id`s to resolve.
|
|
17860
|
+
* @returns the resolved profiles by id.
|
|
17861
|
+
*/
|
|
17862
|
+
async feishuProfiles(userIds, chatIds) {
|
|
17863
|
+
const result = {
|
|
17864
|
+
users: {},
|
|
17865
|
+
chats: {}
|
|
17866
|
+
};
|
|
17867
|
+
const warnings = [];
|
|
17868
|
+
const now = Date.now();
|
|
17869
|
+
for (const [key, cached] of this.profileCache) if (now - cached.at >= PROFILE_TTL_MS) this.profileCache.delete(key);
|
|
17870
|
+
const fresh = (key) => this.profileCache.get(key)?.profile;
|
|
17871
|
+
const keep = (target, id, profile) => {
|
|
17872
|
+
if (profile.name !== void 0 || profile.avatarUrl !== void 0) target[id] = profile;
|
|
17873
|
+
};
|
|
17874
|
+
const users = [...new Set(userIds)].filter((id) => FEISHU_USER_ID_PATTERN.test(id));
|
|
17875
|
+
const chats = [...new Set(chatIds)].filter((id) => FEISHU_CHAT_ID_PATTERN.test(id));
|
|
17876
|
+
const missingUsers = [];
|
|
17877
|
+
for (const id of users) {
|
|
17878
|
+
const cached = fresh(`user:${id}`);
|
|
17879
|
+
if (cached === void 0) missingUsers.push(id);
|
|
17880
|
+
else keep(result.users, id, cached);
|
|
17881
|
+
}
|
|
17882
|
+
const missingChats = [];
|
|
17883
|
+
for (const id of chats) {
|
|
17884
|
+
const cached = fresh(`chat:${id}`);
|
|
17885
|
+
if (cached === void 0) missingChats.push(id);
|
|
17886
|
+
else keep(result.chats, id, cached);
|
|
17887
|
+
}
|
|
17888
|
+
const channel = this.channel;
|
|
17889
|
+
if (channel === void 0) return result;
|
|
17890
|
+
const lookups = [];
|
|
17891
|
+
if (missingUsers.length > 0 && channel.userProfiles !== void 0) lookups.push(channel.userProfiles(missingUsers).then((found) => {
|
|
17892
|
+
for (const id of missingUsers) {
|
|
17893
|
+
const profile = found.get(id) ?? {};
|
|
17894
|
+
this.profileCache.set(`user:${id}`, {
|
|
17895
|
+
profile,
|
|
17896
|
+
at: now
|
|
17897
|
+
});
|
|
17898
|
+
keep(result.users, id, profile);
|
|
17899
|
+
}
|
|
17900
|
+
}, (error) => {
|
|
17901
|
+
warnings.push(`读取用户资料失败:${feishuReason(error)}`);
|
|
17902
|
+
}));
|
|
17903
|
+
const chatProfile = channel.chatProfile?.bind(channel);
|
|
17904
|
+
if (chatProfile !== void 0) {
|
|
17905
|
+
let chatError;
|
|
17906
|
+
for (const id of missingChats) lookups.push(chatProfile(id).then((profile) => {
|
|
17907
|
+
this.profileCache.set(`chat:${id}`, {
|
|
17908
|
+
profile,
|
|
17909
|
+
at: now
|
|
17910
|
+
});
|
|
17911
|
+
keep(result.chats, id, profile);
|
|
17912
|
+
}, (error) => {
|
|
17913
|
+
if (chatError !== void 0) return;
|
|
17914
|
+
chatError = `读取群信息失败:${feishuReason(error)}`;
|
|
17915
|
+
warnings.push(chatError);
|
|
17916
|
+
}));
|
|
17917
|
+
}
|
|
17918
|
+
await Promise.all(lookups);
|
|
17919
|
+
if (warnings.length > 0) {
|
|
17920
|
+
result.warning = warnings.join(";");
|
|
17921
|
+
this.ctx.logger?.debug?.("dsh-channels: Feishu profile lookup: %s", result.warning);
|
|
17922
|
+
}
|
|
17923
|
+
return result;
|
|
17924
|
+
}
|
|
17925
|
+
/** Chat-member-list name for a user the contact profile cannot name. */
|
|
17926
|
+
async memberName(userId, chatId) {
|
|
17927
|
+
const cached = this.profileCache.get(`member:${chatId}:${userId}`);
|
|
17928
|
+
if (cached !== void 0 && Date.now() - cached.at < PROFILE_TTL_MS) return cached.profile.name;
|
|
17929
|
+
const name = await this.channel?.memberName?.(userId, chatId);
|
|
17930
|
+
this.profileCache.set(`member:${chatId}:${userId}`, {
|
|
17931
|
+
profile: name === void 0 ? {} : { name },
|
|
17932
|
+
at: Date.now()
|
|
17933
|
+
});
|
|
17934
|
+
return name;
|
|
17935
|
+
}
|
|
17648
17936
|
/** Panel: decide one pending authorization request (same path as the card). */
|
|
17649
17937
|
async resolveAccessRequest(token, decision) {
|
|
17650
17938
|
const requests = this.accessRequests;
|
|
@@ -18234,6 +18522,7 @@ var FeishuService = class {
|
|
|
18234
18522
|
await this.delivery.standaloneReply(msg, standaloneInfoCard("**🔐 访问授权状态**", [
|
|
18235
18523
|
`**准入模式**:\`${this.config.accessMode}\``,
|
|
18236
18524
|
`**管理员**:${this.adminStatusLine()}`,
|
|
18525
|
+
`**私聊**:${this.config.dmAllowWhitelist ? "管理员与白名单用户" : "仅管理员"}`,
|
|
18237
18526
|
...msg.chatType === "group" ? [`**本群**:${this.accessStore.hasGroup(msg.chatId) ? "已授权" : "未授权"}(\`${msg.chatId}\`)`] : [],
|
|
18238
18527
|
`**白名单**:群 ${groups.length} 个 · 用户 ${users.length} 个 · 知识库 ${this.accessStore.wikiUserRecords().length} 个 · 待处理申请 ${pending.length} 个`,
|
|
18239
18528
|
"群授权:`/auth allow` 或 `/auth revoke`(在目标群内发);用户授权:`/auth user add @用户` 或 `/auth user add <user_id>`;知识库授权:`/auth wiki add @用户`。"
|
|
@@ -19364,6 +19653,11 @@ async function readJsonBody(req) {
|
|
|
19364
19653
|
throw new HttpError(400, "请求体不是合法 JSON");
|
|
19365
19654
|
}
|
|
19366
19655
|
}
|
|
19656
|
+
/** Distinct non-empty entries of one comma-separated id query parameter. */
|
|
19657
|
+
function idList(raw) {
|
|
19658
|
+
if (raw === null) return [];
|
|
19659
|
+
return [...new Set(raw.split(",").map((id) => id.trim()).filter((id) => id !== ""))];
|
|
19660
|
+
}
|
|
19367
19661
|
/**
|
|
19368
19662
|
* Parse one JSON-object body for an access write. HttpErrors are answered
|
|
19369
19663
|
* here (415/413/400); `undefined` means "already responded, stop".
|
|
@@ -19469,6 +19763,50 @@ function registerSettingsRoutes(server, gate, surface) {
|
|
|
19469
19763
|
sendJson(res, 200, await surface.catalogFor(url.searchParams.get("provider") ?? void 0, url.searchParams.get("model") ?? void 0));
|
|
19470
19764
|
})
|
|
19471
19765
|
}),
|
|
19766
|
+
server.register({
|
|
19767
|
+
kind: "exact",
|
|
19768
|
+
path: SESSION_ORIGIN_ROUTE,
|
|
19769
|
+
handler: guarded(async (req, res) => {
|
|
19770
|
+
if (!allowOnly(req, res, ["GET", "HEAD"])) return;
|
|
19771
|
+
if (methodOf(req) === "HEAD") {
|
|
19772
|
+
res.writeHead(200, { "cache-control": "no-store" });
|
|
19773
|
+
res.end();
|
|
19774
|
+
return;
|
|
19775
|
+
}
|
|
19776
|
+
const sessionId = new URL(req.url ?? "/", "http://dsh.local").searchParams.get("sessionId")?.trim();
|
|
19777
|
+
if (sessionId === void 0 || sessionId === "") {
|
|
19778
|
+
sendJson(res, 400, {
|
|
19779
|
+
ok: false,
|
|
19780
|
+
error: "缺少 sessionId 参数"
|
|
19781
|
+
});
|
|
19782
|
+
return;
|
|
19783
|
+
}
|
|
19784
|
+
sendJson(res, 200, await surface.sessionOrigin(sessionId));
|
|
19785
|
+
})
|
|
19786
|
+
}),
|
|
19787
|
+
server.register({
|
|
19788
|
+
kind: "exact",
|
|
19789
|
+
path: PROFILES_ROUTE,
|
|
19790
|
+
handler: guarded(async (req, res) => {
|
|
19791
|
+
if (!allowOnly(req, res, ["GET", "HEAD"])) return;
|
|
19792
|
+
if (methodOf(req) === "HEAD") {
|
|
19793
|
+
res.writeHead(200, { "cache-control": "no-store" });
|
|
19794
|
+
res.end();
|
|
19795
|
+
return;
|
|
19796
|
+
}
|
|
19797
|
+
const params = new URL(req.url ?? "/", "http://dsh.local").searchParams;
|
|
19798
|
+
const userIds = idList(params.get("users"));
|
|
19799
|
+
const chatIds = idList(params.get("chats"));
|
|
19800
|
+
if (userIds.length > 200 || chatIds.length > 200) {
|
|
19801
|
+
sendJson(res, 400, {
|
|
19802
|
+
ok: false,
|
|
19803
|
+
error: `每次最多查询 200 个用户和 200 个群`
|
|
19804
|
+
});
|
|
19805
|
+
return;
|
|
19806
|
+
}
|
|
19807
|
+
sendJson(res, 200, await surface.feishuProfiles(userIds, chatIds));
|
|
19808
|
+
})
|
|
19809
|
+
}),
|
|
19472
19810
|
server.register({
|
|
19473
19811
|
kind: "exact",
|
|
19474
19812
|
path: CONFIG_ROUTE,
|
|
@@ -19742,6 +20080,7 @@ const BOOLEAN_KEYS = /* @__PURE__ */ new Set([
|
|
|
19742
20080
|
"welcomeOnboarding",
|
|
19743
20081
|
"slashCommandPanel",
|
|
19744
20082
|
"allowPermissionSwitch",
|
|
20083
|
+
"dmAllowWhitelist",
|
|
19745
20084
|
"acceptAttachments",
|
|
19746
20085
|
"prefetchImages",
|
|
19747
20086
|
"feishuTools.attachmentFetch",
|
|
@@ -20145,7 +20484,13 @@ const FIELD_META = {
|
|
|
20145
20484
|
label: "调试模式",
|
|
20146
20485
|
group: "access",
|
|
20147
20486
|
summary: "打开后所有人都能聊,不安全,只在调试时用",
|
|
20148
|
-
help: "
|
|
20487
|
+
help: "关闭时(默认):管理员随时可用;非管理员私聊会被拒绝(打开「白名单用户可私聊」后白名单用户也可以);群聊要同时满足群已授权、发言人在用户白名单里。授权入口有三个:群里 /auth 命令、授权卡、本页。撤销立即生效。"
|
|
20488
|
+
},
|
|
20489
|
+
dmAllowWhitelist: {
|
|
20490
|
+
label: "白名单用户可私聊",
|
|
20491
|
+
group: "access",
|
|
20492
|
+
summary: "关闭时私聊仅管理员可用",
|
|
20493
|
+
help: "打开后「已授权用户」里的人也可以私聊机器人,各自使用独立的私聊工作区,能力与群聊相同;不在白名单的人仍收到固定提示。撤销用户立即生效。"
|
|
20149
20494
|
},
|
|
20150
20495
|
adminUserId: {
|
|
20151
20496
|
label: "管理员 user_id",
|
|
@@ -20494,6 +20839,19 @@ var SettingsSurface = class {
|
|
|
20494
20839
|
* the draft provider/model changes). A broken lookup degrades to the
|
|
20495
20840
|
* free-text form instead of failing the panel.
|
|
20496
20841
|
*/
|
|
20842
|
+
/** Feishu origin of one Session for the conversation sidebar. */
|
|
20843
|
+
async sessionOrigin(sessionId) {
|
|
20844
|
+
return { origin: await this.target.sessionOrigin(sessionId) };
|
|
20845
|
+
}
|
|
20846
|
+
/**
|
|
20847
|
+
* Live Feishu names and avatars for the panel's users and chats.
|
|
20848
|
+
* @param userIds - user `user_id`s.
|
|
20849
|
+
* @param chatIds - chat `chat_id`s.
|
|
20850
|
+
* @returns the profiles Feishu resolved.
|
|
20851
|
+
*/
|
|
20852
|
+
async feishuProfiles(userIds, chatIds) {
|
|
20853
|
+
return this.target.feishuProfiles(userIds, chatIds);
|
|
20854
|
+
}
|
|
20497
20855
|
async catalogFor(provider, model) {
|
|
20498
20856
|
if (this.catalog === void 0) return emptyCatalog();
|
|
20499
20857
|
try {
|
|
@@ -21286,6 +21644,6 @@ function apply(ctx, config) {
|
|
|
21286
21644
|
}, "dsh-channels");
|
|
21287
21645
|
}
|
|
21288
21646
|
//#endregion
|
|
21289
|
-
export { AUTH_USER_TARGET_ERRORS, AccessController, AccessRequests, AccessStore, ApprovalAnswerer, CARD_MAX_BYTES, CARD_TARGET_BYTES, CATALOG_ROUTE, CHANNEL_COMMAND_NAMES, CONFIG_ROUTE, CREDENTIALS_ROUTE, Config, DM_DENY_TEXT, DM_UNCONFIGURED_TEXT, DSH_CHANNELS_VERSION, EDITABLE_KEYS, EMPTY_TOTALS, FIELD_DESCRIPTORS, FeishuService, HEALTH_DEPENDENCY_NAMES, INTERRUPTION_NOTICE, LOGICAL_EXPORT_FORMAT, LOGICAL_EXPORT_FORMAT_VERSION, MAX_QUESTION_OPTIONS, MAX_TABLES_PER_CARD, OverlayStore, PANEL_CHEVRON_TOKEN, PANEL_COPY, PANEL_ROUTE, PANEL_TITLES, PICK_DIRECTORY_ROUTE, QuestionAnswerer, REASONING_MAX_BYTES, RECONNECT_ROUTE, RESTART_KEYS, RouteOperationQueue, SESSION_HISTORY_DISPLAY_LIMIT, STATUS_ROUTE, SUPPORTED_COMMAND_NAMES, SettingsSurface, TRUNCATION_SUFFIX, TurnStreamer, accessGroupRecordSchema, accessRequestRecordSchema, accessUserRecordSchema, addUsage, appendSessionHistory, apply, applyCatalog, applyProjectionViews, armModelCommand, averageDecodeTokensPerSecond, averageTtftMs, buildApprovalCard, buildApprovalSettledCard, buildCard, buildCommandResultCard, buildCompactComparisonCard, buildGroupInfoCard, buildHealthCard, buildInterruptionCard, buildQuestionCard, buildQuestionSettledCard, buildRenewCard, buildRequestSettledCard, buildSessionHistoryCard, buildSessionStatusCard, buildUserRequestCard, buildWorkDirCard, cacheHitPct, cardJsonBytes, chatRouteKey, commandResultCardKind, compactFailureContext, countFiberStates, createHostCatalog, createLogicalSessionExport, createSettingsDirectoryPicker, decideAccess, defaultOverlayPath, emptyCatalog, ensureWorkspace, expiredRouteRecordSchema, feishuDomain, foldAll, foldEvent, foldHealthSnapshot, foldSessionStats, inflightSnapshotSchema, inject, isAdminUserId, isSameOriginRequest, isSupportedCommand, maskOpenIdTail, measureSessionContext, mergeOverlay, mountSettingsRoutes, name, normalizeCredentialValue, normalizeOverlay, normalizeRouteRecord, normalizeSessionHistory, parseOverlay, parseSlashCommand, pickEditable, processedRecordSchema, querySessionHistory, readProjectionViews, registerChannelCommands, registerModelCommand, registerSettingsRoutes, resolveAuthUserTarget, resolveDshHome, resolveWorkDir, routeRecordSchema, segmentCardContent, serializeLogicalSession, sessionDirOf, sessionHistoryEntrySchema, splitBodyForCard, stagingPathFor, stagingRoot, stripTables, supportedCommandHelp, supportedCommandList, tableBlocks, topicDirFor, topicTokenFor, usageFromBuckets, withDirectoryBrowse, workspacePathFor, workspaceTitleFor, worktreeBranchFor };
|
|
21647
|
+
export { AUTH_USER_TARGET_ERRORS, AccessController, AccessRequests, AccessStore, ApprovalAnswerer, CARD_MAX_BYTES, CARD_TARGET_BYTES, CATALOG_ROUTE, CHANNEL_COMMAND_NAMES, CONFIG_ROUTE, CREDENTIALS_ROUTE, Config, DM_DENY_TEXT, DM_DENY_WHITELIST_TEXT, DM_UNCONFIGURED_TEXT, DSH_CHANNELS_VERSION, EDITABLE_KEYS, EMPTY_TOTALS, FIELD_DESCRIPTORS, FeishuService, HEALTH_DEPENDENCY_NAMES, INTERRUPTION_NOTICE, LOGICAL_EXPORT_FORMAT, LOGICAL_EXPORT_FORMAT_VERSION, MAX_QUESTION_OPTIONS, MAX_TABLES_PER_CARD, OverlayStore, PANEL_CHEVRON_TOKEN, PANEL_COPY, PANEL_ROUTE, PANEL_TITLES, PICK_DIRECTORY_ROUTE, QuestionAnswerer, REASONING_MAX_BYTES, RECONNECT_ROUTE, RESTART_KEYS, RouteOperationQueue, SESSION_HISTORY_DISPLAY_LIMIT, STATUS_ROUTE, SUPPORTED_COMMAND_NAMES, SettingsSurface, TRUNCATION_SUFFIX, TurnStreamer, accessGroupRecordSchema, accessRequestRecordSchema, accessUserRecordSchema, addUsage, appendSessionHistory, apply, applyCatalog, applyProjectionViews, armModelCommand, averageDecodeTokensPerSecond, averageTtftMs, buildApprovalCard, buildApprovalSettledCard, buildCard, buildCommandResultCard, buildCompactComparisonCard, buildGroupInfoCard, buildHealthCard, buildInterruptionCard, buildQuestionCard, buildQuestionSettledCard, buildRenewCard, buildRequestSettledCard, buildSessionHistoryCard, buildSessionStatusCard, buildUserRequestCard, buildWorkDirCard, cacheHitPct, cardJsonBytes, chatRouteKey, commandResultCardKind, compactFailureContext, countFiberStates, createHostCatalog, createLogicalSessionExport, createSettingsDirectoryPicker, decideAccess, defaultOverlayPath, emptyCatalog, ensureWorkspace, expiredRouteRecordSchema, feishuDomain, foldAll, foldEvent, foldHealthSnapshot, foldSessionStats, inflightSnapshotSchema, inject, isAdminUserId, isSameOriginRequest, isSupportedCommand, maskOpenIdTail, measureSessionContext, mergeOverlay, mountSettingsRoutes, name, normalizeCredentialValue, normalizeOverlay, normalizeRouteRecord, normalizeSessionHistory, parseOverlay, parseSlashCommand, pickEditable, processedRecordSchema, querySessionHistory, readProjectionViews, registerChannelCommands, registerModelCommand, registerSettingsRoutes, resolveAuthUserTarget, resolveDshHome, resolveWorkDir, routeRecordSchema, segmentCardContent, serializeLogicalSession, sessionDirOf, sessionHistoryEntrySchema, splitBodyForCard, stagingPathFor, stagingRoot, stripTables, supportedCommandHelp, supportedCommandList, tableBlocks, topicDirFor, topicTokenFor, usageFromBuckets, withDirectoryBrowse, workspacePathFor, workspaceTitleFor, worktreeBranchFor };
|
|
21290
21648
|
|
|
21291
21649
|
//# sourceMappingURL=index.js.map
|