@modesto_ma/dsh-channels 0.1.0 → 0.2.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/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["addressedToBot","ALL_COMMANDS","messageOf","OPTIONAL_TEXT_KEYS","fs","messageOf","messageOf"],"sources":["../src/access/policy.ts","../src/access/controller.ts","../src/cards/primitives.ts","../src/access/cards.ts","../src/access/requests.ts","../src/access/store.ts","../src/cards/approval.ts","../src/interactions/pending.ts","../src/interactions/approvals.ts","../src/cards/question.ts","../src/interactions/questions.ts","../src/cards/streaming.ts","../src/commands/registry.ts","../src/commands.ts","../src/domain.ts","../src/cleanup.ts","../src/session/routes.ts","../src/session/repair.ts","../src/feishu/agent-context.ts","../src/feishu-channel.ts","../src/feishu/inbound-attachments.ts","../src/session/request-policy.ts","../src/workspaces.ts","../src/session/manager.ts","../src/feishu/delivery.ts","../src/routing/inbound.ts","../src/host/services.ts","../src/health.ts","../src/stats.ts","../src/session-commands.ts","../src/session-export.ts","../src/streamer.ts","../src/service.ts","../src/config.ts","../src/settings/contract.ts","../src/settings/overlay.ts","../src/settings/routes.ts","../src/settings/surface.ts","../src/settings/catalog.ts","../src/index.ts"],"sourcesContent":["/**\n * Pure admission policy for inbound Feishu traffic.\n *\n * The policy deliberately has no I/O: it takes one normalized subject, the\n * relevant configuration slice, and a snapshot of the durable authorization\n * tables, and returns an allow/deny decision. The controller turns a denial\n * into the user-facing behavior (fixed private-chat notice, silent drop,\n * authorization request card), which keeps the security-critical matrix\n * cheap to unit test.\n *\n * Access model:\n * - the administrator may start/continue a conversation in any private chat\n * or any group, regardless of the whitelists;\n * - private chats: non-administrators are refused;\n * - group chats: non-administrators need the group to be authorized AND\n * their open_id to be on the global user whitelist.\n *\n * @module dsh-channels/access/policy\n */\n\nimport type { FeishuConfig } from '../config.ts'\n\n/** Chat kinds this policy decides about (matches the channel normalizer). */\nexport type AccessChatType = 'p2p' | 'group'\n\n/** The security-relevant facts of one inbound message. */\nexport interface AccessSubject {\n chatType: AccessChatType\n chatId: string\n senderId: string\n}\n\n/** Read-only view of the durable authorization tables. */\nexport interface AccessSnapshot {\n /** Authorized group `chat_id`s. */\n groups: ReadonlySet<string>\n /** Authorized user `open_id`s (global, across authorized groups). */\n users: ReadonlySet<string>\n}\n\n/** Why access was denied; drives the controller's response. */\nexport type AccessDenyReason =\n | 'admin-missing'\n | 'dm-non-admin'\n | 'group-not-authorized'\n | 'user-not-authorized'\n\n/** Result of evaluating one inbound message. */\nexport type AccessDecision =\n | { readonly kind: 'allow'; readonly reason: 'open' | 'admin' | 'authorized-user' }\n | {\n readonly kind: 'deny'\n readonly reason: AccessDenyReason\n /** Which authorization request a denial may trigger, when applicable. */\n readonly request?: 'group' | 'user'\n }\n\n/** The configuration slice admission reads (allows easy fakes in tests). */\nexport type AccessPolicyConfig = Pick<FeishuConfig, 'accessMode' | 'adminOpenId'>\n\n/** True when `openId` is exactly the configured administrator. */\nexport function isAdminOpenId(\n config: Pick<FeishuConfig, 'adminOpenId'>,\n openId: string | undefined,\n): boolean {\n const admin = config.adminOpenId?.trim()\n if (admin === undefined || admin === '') return false\n return openId !== undefined && openId.trim() === admin\n}\n\n/**\n * Evaluate the admission matrix for one message.\n *\n * `open` mode reproduces the legacy behavior (the SDK's app-availability scope\n * is then the only boundary) and exists for debugging/compatibility only.\n */\nexport function decideAccess(\n subject: AccessSubject,\n config: AccessPolicyConfig,\n snapshot: AccessSnapshot,\n): AccessDecision {\n if (config.accessMode === 'open') return { kind: 'allow', reason: 'open' }\n\n const admin = config.adminOpenId?.trim()\n if (admin === undefined || admin === '') return { kind: 'deny', reason: 'admin-missing' }\n\n // The administrator is never constrained by the whitelists: they may start\n // a conversation in any private chat or any group, even one that has not\n // been authorized yet. The group/user gates only restrict non-admins.\n if (subject.senderId.trim() === admin) return { kind: 'allow', reason: 'admin' }\n\n if (subject.chatType === 'p2p') {\n return { kind: 'deny', reason: 'dm-non-admin' }\n }\n\n // Non-admin group access needs both gates: the group must be authorized and\n // the sender must be on the global user whitelist.\n if (!snapshot.groups.has(subject.chatId)) {\n return { kind: 'deny', reason: 'group-not-authorized', request: 'group' }\n }\n if (snapshot.users.has(subject.senderId)) return { kind: 'allow', reason: 'authorized-user' }\n return { kind: 'deny', reason: 'user-not-authorized', request: 'user' }\n}\n","/**\n * Inbound admission controller.\n *\n * Every inbound Feishu message passes this gate *before* any side effect\n * (Session creation, workspace, attachment archive, model call). The pure\n * matrix lives in `policy.ts`; this module owns the user-facing behavior on\n * denial:\n *\n * - private chats: non-administrators get one fixed notice (10 s per-sender\n * cooldown); with no administrator configured they get the bootstrap\n * notice instead;\n * - group chats: fully silent, except that addressed denials call back into\n * the authorization-request lifecycle so the administrator can be asked;\n * - slash commands: `/userid` stays available to every private sender, the\n * administrator's commands always pass (including inside an unauthorized\n * group), everything else must pass admission.\n *\n * The controller never creates a Session or a workspace.\n *\n * @module dsh-channels/access/controller\n */\n\nimport type { FeishuConfig } from '../config.ts'\nimport type { RouteRecord } from '../domain.ts'\nimport type { FeishuChannel, NormalizedMessage } from '../feishu-channel.ts'\nimport { decideAccess, isAdminOpenId } from './policy.ts'\nimport type { AccessDecision } from './policy.ts'\nimport type { AccessStore, AccessStoreSnapshot } from './store.ts'\n\n/** Fixed private-chat notice for non-administrators (no model call). */\nexport const DM_DENY_TEXT = '非管理员禁用私聊功能哦~如需使用,请在管理员授权的群里 @ 我;或联系管理员把你的 open_id 加入白名单。'\n\n/** Fixed private-chat notice while no administrator is configured. */\nexport const DM_UNCONFIGURED_TEXT = '机器人尚未配置管理员。发送 /userid 可查看你的 open_id,交给部署者配置后即可启用。'\n\n/** Per-sender cooldown for the repeated private-chat notice (10 s). */\nconst DM_NOTICE_COOLDOWN_MS = 10_000\n\n/** Collaborators of the admission gate. */\nexport interface AccessControllerDeps {\n /** Current plugin configuration (administrator id + access mode). */\n config(): FeishuConfig\n /** Durable whitelists (read through the in-memory snapshot). */\n store: AccessStore\n /** Live channel, undefined before startup / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Best-effort diagnostics; never rethrown. */\n warn(message: string, ...args: unknown[]): void\n /** Addressed message in an unauthorized group (request lifecycle hook). */\n onDeniedGroup?(msg: NormalizedMessage): void\n /** Addressed message from an unauthorized user in an authorized group. */\n onDeniedUser?(msg: NormalizedMessage): void\n /** Clock seam for tests. */\n now?(): number\n}\n\nexport class AccessController {\n /** sender open_id → last fixed-notice delivery instant (DM spam guard). */\n private readonly dmNoticeAt = new Map<string, number>()\n\n constructor(private readonly deps: AccessControllerDeps) {}\n\n private config(): FeishuConfig {\n return this.deps.config()\n }\n\n private now(): number {\n return this.deps.now?.() ?? Date.now()\n }\n\n /** True when `openId` is exactly the configured administrator. */\n isAdmin(openId?: string): boolean {\n return isAdminOpenId(this.config(), openId)\n }\n\n /** Current whitelist snapshot used by the pure policy. */\n snapshot(): AccessStoreSnapshot {\n return this.deps.store.snapshot()\n }\n\n /** Evaluate the admission matrix for one message (pure; no side effects). */\n decide(msg: NormalizedMessage): AccessDecision {\n return decideAccess(\n { chatType: msg.chatType, chatId: msg.chatId, senderId: msg.senderId },\n this.config(),\n this.deps.store.snapshot(),\n )\n }\n\n /**\n * Full channel-plane admission for one non-slash message. `false` means the\n * message has been handled (denied silently or with the fixed notice) and\n * the caller must stop before any side effect.\n */\n async admit(msg: NormalizedMessage): Promise<boolean> {\n const decision = this.decide(msg)\n if (decision.kind === 'allow') return true\n try {\n if (decision.reason === 'dm-non-admin') {\n if (msg.chatType === 'p2p') await this.noticeDeniedDm(msg)\n return false\n }\n if (decision.reason === 'admin-missing') {\n // Private chats get the bootstrap instructions; groups stay silent.\n // Goes through the same per-sender cooldown as the regular notice so a\n // single sender cannot make the bot answer every message while the\n // deployment is unconfigured.\n if (msg.chatType === 'p2p') await this.noticeDeniedDm(msg)\n return false\n }\n // Group denials are silent. Only messages actually addressed to the bot\n // may trigger the (administrator-facing) authorization request.\n if (msg.chatType === 'group' && addressedToBot(msg)) {\n if (decision.reason === 'group-not-authorized') this.deps.onDeniedGroup?.(msg)\n else if (decision.reason === 'user-not-authorized') this.deps.onDeniedUser?.(msg)\n }\n } catch (error) {\n this.deps.warn('dsh-channels: admission side effect failed: %s', String(error))\n }\n return false\n }\n\n /**\n * Slash-command admission. Runs before any command card/topic/Session side\n * effect. `open` mode keeps the legacy behavior; otherwise private chats\n * stay administrator-only (with `/userid` as the bootstrap exception) and\n * group chats must pass the full admission matrix; the administrator is\n * never constrained by the whitelists, so all of their commands pass even\n * inside an unauthorized group (`/auth` is the in-conversation grant\n * entry).\n */\n async admitCommand(msg: NormalizedMessage, commandName: string | undefined): Promise<boolean> {\n if (this.config().accessMode === 'open') return true\n try {\n if (msg.chatType === 'p2p') {\n // `/userid` is the bootstrap entry for everyone: it only echoes the\n // sender's own open_id and has no storage/model side effect.\n if (commandName === 'userid') return true\n if (this.isAdmin(msg.senderId)) return true\n await this.noticeDeniedDm(msg)\n return false\n }\n if (this.isAdmin(msg.senderId) && (commandName === 'auth' || commandName === 'groupid')) {\n return true\n }\n return this.decide(msg).kind === 'allow'\n } catch (error) {\n this.deps.warn('dsh-channels: command admission failed: %s', String(error))\n return false\n }\n }\n\n /**\n * Topic/route ownership: a route belongs to the sender (and chat) that\n * created it. Used to reject foreign replies, attachments, and command\n * execution inside someone else's topic.\n */\n ownsRoute(msg: NormalizedMessage, record: RouteRecord): boolean {\n return record.senderOpenId === msg.senderId && record.chatId === msg.chatId\n }\n\n /** Drop in-memory notices (shutdown; durable state lives in the store). */\n clear(): void {\n this.dmNoticeAt.clear()\n }\n\n // ---- internals -------------------------------------------------------\n\n /**\n * Deliver the fixed private-chat notice (cooled down). While no\n * administrator is configured the bootstrap text is used instead, so users\n * are never pointed at an administrator that does not exist yet.\n */\n private async noticeDeniedDm(msg: NormalizedMessage): Promise<void> {\n const now = this.now()\n const last = this.dmNoticeAt.get(msg.senderId)\n if (last !== undefined && now - last < DM_NOTICE_COOLDOWN_MS) return\n this.dmNoticeAt.set(msg.senderId, now)\n const text = this.config().adminOpenId === undefined ? DM_UNCONFIGURED_TEXT : DM_DENY_TEXT\n await this.sendText(msg.chatId, text, msg.messageId)\n }\n\n /** Best-effort fixed-text send; unavailable channel/failures only warn. */\n private async sendText(chatId: string, text: string, replyTo?: string): Promise<void> {\n const channel = this.deps.channel()\n if (channel === undefined) {\n this.deps.warn('dsh-channels: channel unavailable; admission notice not delivered')\n return\n }\n try {\n await channel.send(chatId, { text }, replyTo === undefined ? undefined : { replyTo })\n } catch (error) {\n this.deps.warn('dsh-channels: delivering the admission notice failed: %s', String(error))\n }\n }\n}\n\n/** Late-trigger condition: only messages addressed to the bot count. */\nfunction addressedToBot(msg: NormalizedMessage): boolean {\n return msg.mentionedBot === true || msg.threadId !== undefined\n}\n","/**\n * Card JSON 2.0 primitives shared by every card this plugin renders.\n *\n * One envelope + one element vocabulary keeps the byte budget, the width\n * mode and the callback shape in a single place; card modules only describe\n * their content.\n *\n * @module dsh-channels/cards/primitives\n */\n\n/** Feishu card JSON schema version this plugin targets. */\nexport const CARD_SCHEMA = '2.0'\n\n/** Default card config: full-width cards in the chat pane. */\nexport const CARD_CONFIG: Record<string, unknown> = { width_mode: 'fill' }\n\n/** Wrap elements in a card 2.0 envelope. */\nexport function card(elements: object[], config: Record<string, unknown> = CARD_CONFIG): object {\n return { schema: CARD_SCHEMA, config, body: { elements } }\n}\n\n/** Markdown element with optional presentation fields (`text_size`, …). */\nexport function markdown(content: string, extra: Record<string, unknown> = {}): object {\n return { tag: 'markdown', content, ...extra }\n}\n\n/** Small notation-size markdown, used for hints and footers. */\nexport function note(content: string): object {\n return markdown(content, { text_size: 'notation' })\n}\n\n/** Horizontal divider. */\nexport function divider(): object {\n return { tag: 'hr' }\n}\n\nexport interface TextButtonOptions {\n label: string\n /** Callback payload carried back by `card.action.trigger`. */\n value: unknown\n type?: 'default' | 'primary' | 'danger'\n disabled?: boolean\n width?: 'fill' | 'default'\n margin?: string\n}\n\n/** One callback button (`behaviors[0].value` is the plugin's action object). */\nexport function textButton(options: TextButtonOptions): object {\n return {\n tag: 'button',\n type: options.type ?? 'default',\n width: options.width ?? 'fill',\n margin: options.margin ?? '4px 0 0 0',\n ...(options.disabled === true ? { disabled: true } : {}),\n text: { tag: 'plain_text', content: options.label },\n behaviors: [{ type: 'callback', value: options.value }],\n }\n}\n\n/** A row of equal-width columns, each holding one element. */\nexport function columnSet(\n columns: object[],\n options: { flexMode?: string; spacing?: string; margin?: string } = {},\n): object {\n return {\n tag: 'column_set',\n flex_mode: options.flexMode ?? 'bisect',\n horizontal_spacing: options.spacing ?? 'medium',\n margin: options.margin ?? '4px 0 0 0',\n columns: columns.map(element => ({ tag: 'column', width: 'weighted', weight: 1, elements: [element] })),\n }\n}\n\n/** Titled standalone card: bold title element followed by the body elements. */\nexport function standaloneCard(title: string, elements: object[], config?: Record<string, unknown>): object {\n return card([markdown(title), ...elements], config)\n}\n\n/** Titled card whose body is one markdown element per supplied line. */\nexport function standaloneInfoCard(title: string, lines: readonly string[]): object {\n return standaloneCard(title, lines.map(line => markdown(line)))\n}\n","/**\n * Access-control cards: the bot-added group information card, the\n * administrator's authorization request cards, and their terminal state.\n *\n * The request cards carry an opaque token in the button callback value; the\n * token is resolved server-side against the durable request table, so a card\n * can never grant anything by itself (see `docs/access-control-design.md`\n * §5.7/§5.8).\n *\n * @module dsh-channels/access/cards\n */\n\nimport { columnSet, divider, markdown, note, standaloneCard, textButton } from '../cards/primitives.ts'\n\n/** Terminal status of one settled authorization request. */\nexport type SettledRequestStatus = 'approved' | 'denied' | 'expired'\n\n/** Inputs of the group-information card sent to the administrator. */\nexport interface GroupInfoCardInput {\n /** Feishu `chat_id` of the group the bot was added to (or was seen in). */\n chatId: string\n /** Group name, when `getChatInfo()` could read it. */\n name?: string\n /** Member count, when available. */\n memberCount?: number\n /** Group owner `open_id`, when available. */\n ownerId?: string\n /** `open_id` of whoever invited/triggered the bot. */\n inviterOpenId?: string\n /** Whether the group already passed the first admission gate. */\n authorized: boolean\n /** Opaque request token; only present for a *pending* group request. */\n token?: string\n /** Render timestamp (defaults to now). */\n now?: number\n}\n\n/** Inputs of the user-authorization request card sent to the administrator. */\nexport interface UserRequestCardInput {\n /** `open_id` of the user asking for access. */\n openId: string\n /** Display name of that user, when known. */\n name?: string\n /** Chat the request originated from. */\n chatId?: string\n /** Name of the authorized group the user was seen in. */\n groupName?: string\n /** Display name of the requester (the inbound sender). */\n requesterName?: string\n /** Opaque request token. */\n token: string\n /** Render timestamp (defaults to now). */\n now?: number\n}\n\n/** Inputs of the terminal replacement card. */\nexport interface RequestSettledCardInput {\n /** What kind of grant the request was about. */\n kind: 'group' | 'user'\n /** `chat_id` (group) or `open_id` (user) the request targeted. */\n targetId: string\n /** Human-readable target name, when known. */\n targetName?: string\n /** Settled state; pending requests never use this card. */\n status: SettledRequestStatus\n /** Render timestamp (defaults to now). */\n now?: number\n}\n\n/** Truncate one display string so the card stays far below the size budget. */\nfunction clip(value: string, max = 120): string {\n const trimmed = value.trim()\n return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max)}…`\n}\n\n/** Render one epoch-ms instant as a stable UTC `YYYY-MM-DD HH:mm` string. */\nfunction formatTime(timestamp?: number): string {\n const value = timestamp ?? Date.now()\n if (!Number.isFinite(value)) return '—'\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return '—'\n return `${date.toISOString().replace('T', ' ').slice(0, 16)} UTC`\n}\n\n/** Two side-by-side decision buttons carrying the opaque request token. */\nfunction decisionButtons(token: string, allowLabel: string, denyLabel: string): object {\n return columnSet([\n textButton({\n label: allowLabel,\n type: 'primary',\n width: 'fill',\n margin: '0px',\n value: { token, action: 'allow' },\n }),\n textButton({\n label: denyLabel,\n type: 'danger',\n width: 'fill',\n margin: '0px',\n value: { token, action: 'deny' },\n }),\n ])\n}\n\n/** `**字段**:值` line used across both request cards. */\nfunction field(label: string, value: string): object {\n return markdown(`**${label}**:${value}`)\n}\n\n/**\n * The group-information card the administrator receives when the bot joins a\n * group (and, as a fallback, on the first denied access in an unauthorized\n * group). Unauthorized groups with a pending request carry「授权本群 / 忽略」.\n */\nexport function buildGroupInfoCard(input: GroupInfoCardInput): object {\n const name = input.name === undefined ? undefined : clip(input.name)\n const elements: object[] = []\n elements.push(field('群名', name === undefined || name === '' ? '(未知)' : name))\n elements.push(field('群 ID', `\\`${input.chatId}\\``))\n if (input.memberCount !== undefined) elements.push(field('成员数', String(input.memberCount)))\n if (input.ownerId !== undefined && input.ownerId !== '') {\n elements.push(field('群主', `\\`${input.ownerId}\\``))\n }\n if (input.inviterOpenId !== undefined && input.inviterOpenId !== '') {\n elements.push(field('邀请人', `\\`${input.inviterOpenId}\\``))\n }\n elements.push(field('加入时间', formatTime(input.now)))\n elements.push(divider())\n\n if (!input.authorized && input.token !== undefined && input.token !== '') {\n elements.push(note('该群尚未授权:群内目前只有管理员可以与我对话;授权后群内白名单用户也可使用。'))\n elements.push(decisionButtons(input.token, '授权本群', '忽略'))\n } else if (input.authorized) {\n elements.push(note('✅ 该群已授权,群内白名单用户可正常使用(管理员不受白名单限制)。'))\n } else {\n elements.push(note('该群当前未授权(仅管理员可用),且没有可用的授权令牌;可在 Web 设置面板的「访问与授权」中授权。'))\n }\n return standaloneCard('**📋 群信息**', elements)\n}\n\n/**\n * The user-authorization request card: an unauthorized user mentioned the bot\n * inside an authorized group, so the administrator can grant that `open_id`\n * from the DM card.\n */\nexport function buildUserRequestCard(input: UserRequestCardInput): object {\n const display = clip(input.name ?? input.requesterName ?? '')\n const groupName = input.groupName === undefined ? undefined : clip(input.groupName)\n const elements: object[] = [\n field('用户', display === '' ? '(未知昵称)' : display),\n field('open_id', `\\`${input.openId}\\``),\n ]\n if (groupName !== undefined && groupName !== '') {\n const group = input.chatId === undefined ? groupName : `${groupName}(\\`${input.chatId}\\`)`\n elements.push(field('所在群', group))\n } else if (input.chatId !== undefined && input.chatId !== '') {\n elements.push(field('所在群', `\\`${input.chatId}\\``))\n }\n elements.push(field('申请时间', formatTime(input.now)))\n elements.push(divider())\n elements.push(note('授权后该用户可在所有已授权群里使用;私聊仍仅管理员可用。'))\n elements.push(decisionButtons(input.token, '授权用户', '忽略'))\n return standaloneCard('**🙋 用户授权申请**', elements)\n}\n\n/** Title/description pairs for the three settled states. */\nconst SETTLED_PRESENTATION: Record<SettledRequestStatus, { title: string; line: string }> = {\n approved: { title: '✅ 已授权', line: '授权已生效。' },\n denied: { title: '❌ 已忽略', line: '本次申请已被忽略。' },\n expired: { title: '⏱ 已过期', line: '申请超过有效期,已自动失效。' },\n}\n\n/**\n * The terminal replacement for a request card (decision or expiry). Carries\n * no buttons: the request token can settle exactly once.\n */\nexport function buildRequestSettledCard(input: RequestSettledCardInput): object {\n const presentation = SETTLED_PRESENTATION[input.status]\n const kindLabel = input.kind === 'group' ? '群' : '用户'\n const name = input.targetName === undefined ? undefined : clip(input.targetName)\n const target = name === undefined || name === '' ? `\\`${input.targetId}\\`` : `${name}(\\`${input.targetId}\\`)`\n const elements: object[] = [\n field(kindLabel, target),\n field('处理时间', formatTime(input.now)),\n note(presentation.line),\n ]\n return standaloneCard(`**${presentation.title}**`, elements)\n}\n","/**\n * Authorization request lifecycle.\n *\n * Two fixed events can ask the administrator to grant access: the bot being\n * added to a group, and an unauthorized user mentioning the bot inside an\n * already-authorized group. Both create one durable\n * `AccessRequestRecord` (opaque token, TTL from `authRequestTtlDays`), deliver\n * a DM card carrying that token, and settle exactly once from the card or the\n * Web panel through the same `resolve()` path. Every network/persistence\n * failure degrades to a warning: admission must never throw because a card\n * could not be delivered.\n *\n * @module dsh-channels/access/requests\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { FeishuConfig } from '../config.ts'\nimport type { AccessRequestRecord } from '../domain.ts'\nimport type {\n BotAddedEvent,\n CardActionEvent,\n ChatInfo,\n FeishuChannel,\n NormalizedMessage,\n} from '../feishu-channel.ts'\nimport { buildGroupInfoCard, buildRequestSettledCard, buildUserRequestCard } from './cards.ts'\nimport type { SettledRequestStatus } from './cards.ts'\nimport { isAdminOpenId } from './policy.ts'\nimport type { AccessStore } from './store.ts'\n\n/** Same-target re-notification cooldown after a request was denied (24 h). */\nconst DENIED_COOLDOWN_MS = 24 * 60 * 60 * 1000\n\nconst MS_PER_DAY = 86_400_000\n\n/** Collaborators the request lifecycle needs, injected so tests stay offline. */\nexport interface AccessRequestsDeps {\n /** Current plugin configuration (administrator id + request TTL). */\n config(): FeishuConfig\n /** Durable whitelist/request store. */\n store: AccessStore\n /** Live channel, undefined before startup / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Best-effort diagnostics; never rethrown. */\n warn(message: string, ...args: unknown[]): void\n /** Clock seam for tests. */\n now?(): number\n}\n\n/** One decoded card callback value (the token is the only trusted input). */\ninterface CardDecisionValue {\n token: string\n action: 'allow' | 'deny'\n}\n\n/** Decode one button callback value; anything malformed is silently ignored. */\nfunction decodeCardValue(value: unknown): CardDecisionValue | undefined {\n if (typeof value !== 'object' || value === null) return undefined\n const record = value as Record<string, unknown>\n const token = record.token\n const action = record.action\n if (typeof token !== 'string' || token === '') return undefined\n if (action !== 'allow' && action !== 'deny') return undefined\n return { token, action }\n}\n\n/** Human label for a terminal request status (error messages and cards). */\nfunction statusLabel(status: AccessRequestRecord['status']): string {\n switch (status) {\n case 'approved':\n return '已授权'\n case 'denied':\n return '已忽略'\n case 'expired':\n return '已过期'\n default:\n return '处理中'\n }\n}\n\n/** Map a settled record status to the card vocabulary (`pending` has no card). */\nfunction settledStatus(status: AccessRequestRecord['status']): SettledRequestStatus | undefined {\n return status === 'pending' ? undefined : status\n}\n\n/**\n * Late-trigger condition: only messages addressed to the bot may create a\n * request. A plain group message in an unauthorized group stays silent.\n */\nfunction addressedToBot(msg: NormalizedMessage): boolean {\n return msg.mentionedBot === true || msg.threadId !== undefined\n}\n\nexport class AccessRequests {\n /** Targets with an in-flight create/deliver pass (avoids duplicate cards). */\n private readonly creating = new Set<string>()\n /** `${messageId}:${openId}` of non-administrator card clicks already answered. */\n private readonly unauthorizedNotified = new Set<string>()\n\n constructor(private readonly deps: AccessRequestsDeps) {}\n\n private now(): number {\n return this.deps.now?.() ?? Date.now()\n }\n\n private config(): FeishuConfig {\n return this.deps.config()\n }\n\n private adminOpenId(): string | undefined {\n const admin = this.config().adminOpenId?.trim()\n return admin === undefined || admin === '' ? undefined : admin\n }\n\n private ttlMs(): number {\n return this.config().authRequestTtlDays * MS_PER_DAY\n }\n\n /**\n * Bot added to a group (`im.chat.member.bot.added_v1`): DM the group\n * information card to the administrator. An authorized group only gets the\n * informational card; an unauthorized one gets an「授权本群 / 忽略」card\n * backed by a fresh pending request (deduplicated, 24 h deny cooldown).\n */\n async handleBotAdded(event: BotAddedEvent): Promise<void> {\n try {\n const admin = this.adminOpenId()\n if (admin === undefined) {\n this.deps.warn(\n 'dsh-channels: bot added to %s but no administrator open_id is configured; group card skipped',\n event.chatId,\n )\n return\n }\n const now = this.now()\n if (this.deps.store.hasGroup(event.chatId)) {\n const info = await this.chatInfo(event.chatId)\n await this.sendCard(admin, buildGroupInfoCard({\n chatId: event.chatId,\n ...(info?.name === undefined ? {} : { name: info.name }),\n ...(info?.memberCount === undefined ? {} : { memberCount: info.memberCount }),\n ...(info?.ownerId === undefined ? {} : { ownerId: info.ownerId }),\n inviterOpenId: event.operator.openId,\n authorized: true,\n now,\n }))\n return\n }\n if (!(await this.beginGroupRequest(event.chatId, event.operator.openId, now))) return\n } catch (error) {\n this.deps.warn('dsh-channels: bot-added group notice failed: %s', String(error))\n }\n }\n\n /**\n * Fallback trigger for an unauthorized group: the first *addressed* message\n * in it re-sends the group information/authorization card to the\n * administrator (used when the bot-added event is not subscribed).\n */\n async ensureGroupRequest(msg: NormalizedMessage): Promise<void> {\n if (!addressedToBot(msg) || msg.chatType !== 'group') return\n try {\n const admin = this.adminOpenId()\n if (admin === undefined) {\n this.deps.warn(\n 'dsh-channels: group %s needs authorization but no administrator open_id is configured',\n msg.chatId,\n )\n return\n }\n if (this.deps.store.hasGroup(msg.chatId)) return\n if (!(await this.beginGroupRequest(msg.chatId, msg.senderId, this.now()))) return\n } catch (error) {\n this.deps.warn('dsh-channels: group authorization request failed: %s', String(error))\n }\n }\n\n /**\n * Lazy trigger for an unauthorized user inside an authorized group: DM the\n * administrator a user-authorization request card (deduplicated, 24 h deny\n * cooldown). Never runs for unauthorized groups (the group gate is first).\n */\n async ensureUserRequest(msg: NormalizedMessage): Promise<void> {\n if (!addressedToBot(msg) || msg.chatType !== 'group') return\n try {\n const admin = this.adminOpenId()\n if (admin === undefined) {\n this.deps.warn(\n 'dsh-channels: user %s needs authorization but no administrator open_id is configured',\n msg.senderId,\n )\n return\n }\n const group = this.deps.store.getGroup(msg.chatId)\n if (group === undefined) return\n if (this.deps.store.hasUser(msg.senderId)) return\n await this.beginUserRequest(msg, group.name, this.now())\n } catch (error) {\n this.deps.warn('dsh-channels: user authorization request failed: %s', String(error))\n }\n }\n\n /**\n * Card button callback: administrator-only settlement. A stale card (already\n * settled or expired) is patched to its terminal state; a request that is\n * still pending goes through the same `resolve()` as the Web panel.\n */\n async onCardAction(event: CardActionEvent): Promise<void> {\n try {\n const decision = decodeCardValue(event.action?.value)\n if (decision === undefined) return\n const record = this.deps.store.getRequest(decision.token)\n if (record === undefined) return\n if (!isAdminOpenId(this.config(), event.operator.openId)) {\n await this.notifyUnauthorized(event)\n return\n }\n if (record.status !== 'pending') {\n await this.patchCard(record)\n return\n }\n if (record.expiresAt <= this.now()) {\n await this.settleExpired(record)\n return\n }\n const result = await this.resolve(decision.token, decision.action, event.operator.openId)\n if (!result.ok) {\n this.deps.warn('dsh-channels: settling access request %s failed: %s', decision.token, result.error)\n }\n } catch (error) {\n this.deps.warn('dsh-channels: access card action failed: %s', String(error))\n }\n }\n\n /**\n * Settle one pending request: `allow` writes the group/user grant (source\n * `card`), `deny` only records the decision. Shared by the DM card and the\n * Web panel; every failure is reported in the return value, never thrown.\n */\n async resolve(\n token: string,\n decision: 'allow' | 'deny',\n actorOpenId: string,\n ): Promise<{ ok: true } | { ok: false; error: string }> {\n if (!isAdminOpenId(this.config(), actorOpenId)) return { ok: false, error: '仅管理员可处理授权申请' }\n const record = this.deps.store.getRequest(token)\n if (record === undefined) return { ok: false, error: '授权申请不存在' }\n if (record.status !== 'pending') {\n return { ok: false, error: `该申请已处理(${statusLabel(record.status)})` }\n }\n const now = this.now()\n if (record.expiresAt <= now) {\n await this.settleExpired(record)\n return { ok: false, error: '授权申请已过期' }\n }\n const status = decision === 'allow' ? 'approved' : 'denied'\n try {\n if (decision === 'allow') await this.grant(record, actorOpenId, now)\n await this.deps.store.putRequest({ ...record, status })\n } catch (error) {\n this.deps.warn('dsh-channels: persisting access request %s settlement failed: %s', token, String(error))\n return { ok: false, error: '保存授权结果失败' }\n }\n await this.patchCard({ ...record, status })\n return { ok: true }\n }\n\n /** Mark every pending request past its deadline as expired and patch its card. */\n async expirePending(): Promise<void> {\n try {\n const expired = await this.deps.store.expireRequests(this.now())\n for (const record of expired) await this.patchCard(record)\n } catch (error) {\n this.deps.warn('dsh-channels: expiring access requests failed: %s', String(error))\n }\n }\n\n /** Pending, not-yet-expired requests (the administrator's queue). */\n pending(): AccessRequestRecord[] {\n return this.deps.store.pendingRequests(this.now())\n }\n\n /** Drop in-memory dedupe guards (shutdown). Durable state lives in the store. */\n clear(): void {\n this.creating.clear()\n this.unauthorizedNotified.clear()\n }\n\n // ---- internals -------------------------------------------------------\n\n /** Create + DM one group request unless deduplicated/cooling down. */\n private async beginGroupRequest(chatId: string, inviterOpenId: string, now: number): Promise<boolean> {\n const admin = this.adminOpenId()\n if (admin === undefined) return false\n if (await this.hasLivePending('group', chatId, now)) return false\n if (this.deps.store.recentlyDenied('group', chatId, DENIED_COOLDOWN_MS, now)) return false\n const key = `group:${chatId}`\n if (this.creating.has(key)) return false\n this.creating.add(key)\n try {\n const info = await this.chatInfo(chatId)\n const record = this.newRecord({\n kind: 'group',\n targetId: chatId,\n chatId,\n ...(info?.name === undefined ? {} : { groupName: info.name }),\n requesterOpenId: inviterOpenId,\n })\n if (!(await this.persist(record))) return false\n await this.sendCard(admin, buildGroupInfoCard({\n chatId,\n ...(info?.name === undefined ? {} : { name: info.name }),\n ...(info?.memberCount === undefined ? {} : { memberCount: info.memberCount }),\n ...(info?.ownerId === undefined ? {} : { ownerId: info.ownerId }),\n inviterOpenId,\n authorized: false,\n token: record.token,\n now,\n }), record)\n return true\n } finally {\n this.creating.delete(key)\n }\n }\n\n /** Create + DM one user request unless deduplicated/cooling down. */\n private async beginUserRequest(msg: NormalizedMessage, groupName: string | undefined, now: number): Promise<boolean> {\n const admin = this.adminOpenId()\n if (admin === undefined) return false\n if (await this.hasLivePending('user', msg.senderId, now)) return false\n if (this.deps.store.recentlyDenied('user', msg.senderId, DENIED_COOLDOWN_MS, now)) return false\n const key = `user:${msg.senderId}`\n if (this.creating.has(key)) return false\n this.creating.add(key)\n try {\n const record = this.newRecord({\n kind: 'user',\n targetId: msg.senderId,\n chatId: msg.chatId,\n ...(groupName === undefined ? {} : { groupName }),\n requesterOpenId: msg.senderId,\n ...(msg.senderName === undefined ? {} : { requesterName: msg.senderName }),\n })\n if (!(await this.persist(record))) return false\n await this.sendCard(admin, buildUserRequestCard({\n openId: msg.senderId,\n ...(msg.senderName === undefined ? {} : { name: msg.senderName }),\n chatId: msg.chatId,\n ...(groupName === undefined ? {} : { groupName }),\n ...(msg.senderName === undefined ? {} : { requesterName: msg.senderName }),\n token: record.token,\n now,\n }), record)\n return true\n } finally {\n this.creating.delete(key)\n }\n }\n\n /**\n * True when an unexpired request for this target is pending. An expired but\n * not-yet-swept record is settled as expired first so the new request is not\n * silently blocked by the stale one.\n */\n private async hasLivePending(\n kind: AccessRequestRecord['kind'],\n targetId: string,\n now: number,\n ): Promise<boolean> {\n const pending = this.deps.store.pendingRequestFor(kind, targetId)\n if (pending === undefined) return false\n if (pending.expiresAt > now) return true\n await this.settleExpired(pending)\n return false\n }\n\n /** Build one pending request record (token + TTL, status `pending`). */\n private newRecord(input: {\n kind: AccessRequestRecord['kind']\n targetId: string\n chatId?: string\n groupName?: string\n requesterOpenId?: string\n requesterName?: string\n }): AccessRequestRecord {\n const now = this.now()\n return {\n token: randomUUID(),\n kind: input.kind,\n targetId: input.targetId,\n ...(input.chatId === undefined ? {} : { chatId: input.chatId }),\n ...(input.groupName === undefined || input.groupName === '' ? {} : { groupName: input.groupName }),\n ...(input.requesterOpenId === undefined ? {} : { requesterOpenId: input.requesterOpenId }),\n ...(input.requesterName === undefined || input.requesterName === '' ? {} : { requesterName: input.requesterName }),\n status: 'pending',\n createdAt: now,\n expiresAt: now + this.ttlMs(),\n }\n }\n\n /** Persist a request; returns false (and warns) on failure. */\n private async persist(record: AccessRequestRecord): Promise<boolean> {\n try {\n await this.deps.store.putRequest(record)\n return true\n } catch (error) {\n this.deps.warn('dsh-channels: persisting access request %s failed: %s', record.token, String(error))\n return false\n }\n }\n\n /** DM one card to the administrator and remember its message id. */\n private async sendCard(to: string, card: object, record?: AccessRequestRecord): Promise<void> {\n if (to === '') return\n const channel = this.deps.channel()\n if (channel === undefined) {\n this.deps.warn('dsh-channels: channel unavailable; access card %s not delivered', record?.token ?? '(group info)')\n return\n }\n try {\n const result = await channel.send(to, { card })\n if (record === undefined) return\n const messageId = result?.messageId\n if (typeof messageId !== 'string' || messageId === '') return\n try {\n await this.deps.store.putRequest({ ...record, messageId })\n } catch (error) {\n this.deps.warn('dsh-channels: persisting card message id for %s failed: %s', record.token, String(error))\n }\n } catch (error) {\n this.deps.warn('dsh-channels: delivering access card %s failed: %s', record?.token ?? '(group info)', String(error))\n }\n }\n\n /** Group name/owner/member count, best-effort (missing permission → warn). */\n private async chatInfo(chatId: string): Promise<ChatInfo | undefined> {\n const channel = this.deps.channel()\n if (channel === undefined) return undefined\n try {\n return await channel.getChatInfo(chatId)\n } catch (error) {\n this.deps.warn('dsh-channels: reading chat info for %s failed: %s', chatId, String(error))\n return undefined\n }\n }\n\n /** Write the actual grant; throws so `resolve()` can report the failure. */\n private async grant(record: AccessRequestRecord, actorOpenId: string, now: number): Promise<void> {\n if (record.kind === 'group') {\n await this.deps.store.addGroup({\n chatId: record.targetId,\n ...(record.groupName === undefined ? {} : { name: record.groupName }),\n source: 'card',\n addedBy: actorOpenId,\n addedAt: now,\n })\n return\n }\n await this.deps.store.addUser({\n openId: record.targetId,\n ...(record.requesterName === undefined ? {} : { name: record.requesterName }),\n source: 'card',\n addedBy: actorOpenId,\n addedAt: now,\n })\n }\n\n /** Persist the expired status (best-effort) then patch the DM card. */\n private async settleExpired(record: AccessRequestRecord): Promise<void> {\n const expired: AccessRequestRecord = { ...record, status: 'expired' }\n try {\n await this.deps.store.putRequest(expired)\n } catch (error) {\n this.deps.warn('dsh-channels: persisting expired access request %s failed: %s', record.token, String(error))\n }\n await this.patchCard(expired)\n }\n\n /** Replace the DM card with its terminal state; delivery failures only warn. */\n private async patchCard(record: AccessRequestRecord): Promise<void> {\n const status = settledStatus(record.status)\n if (status === undefined || record.messageId === undefined) return\n const channel = this.deps.channel()\n if (channel === undefined) return\n const targetName = record.kind === 'group' ? record.groupName : record.requesterName\n try {\n await channel.updateCard(record.messageId, buildRequestSettledCard({\n kind: record.kind,\n targetId: record.targetId,\n ...(targetName === undefined ? {} : { targetName }),\n status,\n now: this.now(),\n }))\n } catch (error) {\n this.deps.warn('dsh-channels: patching access card %s failed: %s', record.messageId, String(error))\n }\n }\n\n /** One control card is delivered once; non-administrator clicks get one notice. */\n private async notifyUnauthorized(event: CardActionEvent): Promise<void> {\n const key = `${event.messageId}:${event.operator.openId}`\n if (this.unauthorizedNotified.has(key)) return\n this.unauthorizedNotified.add(key)\n const channel = this.deps.channel()\n if (channel === undefined) return\n try {\n await channel.send(event.chatId, { text: '仅管理员可操作该授权卡片。' }, { replyTo: event.messageId })\n } catch (error) {\n this.deps.warn('dsh-channels: notifying a non-administrator card click failed: %s', String(error))\n }\n }\n}\n","/**\n * Durable access-control store.\n *\n * Reads come from in-memory maps (seeded once from the plugin's\n * `storageDomain` tables) so admission decisions stay synchronous and cheap;\n * every mutation writes through to the domain first and then updates memory,\n * matching the store patterns used for routes and inflight snapshots.\n *\n * The tables (`access_groups`, `access_users`, `access_requests`) were added\n * without bumping the domain version on purpose: the JSON backend's `single`\n * layout refuses a stored-version mismatch, while unknown/missing tables are\n * simply empty on load (see `docs/access-control-design.md` §4.2).\n *\n * @module dsh-channels/access/store\n */\n\nimport type {\n AccessGroupRecord,\n AccessRequestRecord,\n AccessUserRecord,\n FeishuDomainHandle,\n} from '../domain.ts'\n\nexport interface AccessStoreDeps {\n /** The open domain handle, or undefined before start / after shutdown. */\n domain(): FeishuDomainHandle | undefined\n warn(message: string, ...args: unknown[]): void\n}\n\n/** Read-only whitespace snapshot handed to the pure policy. */\nexport interface AccessStoreSnapshot {\n groups: ReadonlySet<string>\n users: ReadonlySet<string>\n}\n\n/** One settled authorization request (audit trail). */\nexport type AccessRequestStatus = AccessRequestRecord['status']\n\nexport class AccessStore {\n private readonly groups = new Map<string, AccessGroupRecord>()\n private readonly users = new Map<string, AccessUserRecord>()\n private readonly requests = new Map<string, AccessRequestRecord>()\n /** Cached key sets so `snapshot()` can stay allocation-free per message. */\n private readonly groupKeys = new Set<string>()\n private readonly userKeys = new Set<string>()\n\n constructor(private readonly deps: AccessStoreDeps) {}\n\n /** Seed memory from the durable tables (called once after the domain opens). */\n load(): void {\n this.clear()\n const domain = this.deps.domain()\n if (domain === undefined) {\n this.deps.warn('dsh-channels: access tables unavailable; starting with an empty whitelist')\n return\n }\n for (const [key, record] of domain.accessGroups.entries()) {\n this.groups.set(key, record)\n this.groupKeys.add(key)\n }\n for (const [key, record] of domain.accessUsers.entries()) {\n this.users.set(key, record)\n this.userKeys.add(key)\n }\n for (const [key, record] of domain.accessRequests.entries()) {\n this.requests.set(key, record)\n }\n }\n\n /** Drop every in-memory record (shutdown). The domain owns durability. */\n clear(): void {\n this.groups.clear()\n this.users.clear()\n this.requests.clear()\n this.groupKeys.clear()\n this.userKeys.clear()\n }\n\n snapshot(): AccessStoreSnapshot {\n return { groups: this.groupKeys, users: this.userKeys }\n }\n\n hasGroup(chatId: string): boolean {\n return this.groupKeys.has(chatId)\n }\n\n hasUser(openId: string): boolean {\n return this.userKeys.has(openId)\n }\n\n getGroup(chatId: string): AccessGroupRecord | undefined {\n return this.groups.get(chatId)\n }\n\n getUser(openId: string): AccessUserRecord | undefined {\n return this.users.get(openId)\n }\n\n /** Every authorized group, oldest first. */\n groupRecords(): AccessGroupRecord[] {\n return [...this.groups.values()].sort((left, right) => left.addedAt - right.addedAt)\n }\n\n /** Every authorized user, oldest first. */\n userRecords(): AccessUserRecord[] {\n return [...this.users.values()].sort((left, right) => left.addedAt - right.addedAt)\n }\n\n async addGroup(record: AccessGroupRecord): Promise<AccessGroupRecord> {\n await this.requireDomain('add access group').accessGroups.put(record.chatId, record)\n this.groups.set(record.chatId, record)\n this.groupKeys.add(record.chatId)\n return record\n }\n\n async removeGroup(chatId: string): Promise<boolean> {\n if (!this.groups.has(chatId)) return false\n await this.requireDomain('remove access group').accessGroups.delete(chatId)\n this.groups.delete(chatId)\n this.groupKeys.delete(chatId)\n return true\n }\n\n async addUser(record: AccessUserRecord): Promise<AccessUserRecord> {\n await this.requireDomain('add access user').accessUsers.put(record.openId, record)\n this.users.set(record.openId, record)\n this.userKeys.add(record.openId)\n return record\n }\n\n async removeUser(openId: string): Promise<boolean> {\n if (!this.users.has(openId)) return false\n await this.requireDomain('remove access user').accessUsers.delete(openId)\n this.users.delete(openId)\n this.userKeys.delete(openId)\n return true\n }\n\n /** Every authorization request, including settled ones (audit). */\n requestRecords(): AccessRequestRecord[] {\n return [...this.requests.values()].sort((left, right) => left.createdAt - right.createdAt)\n }\n\n /**\n * Pending, not-yet-expired requests. Expired-but-unsettled records are\n * excluded here; `expireRequests()` is the sweep that persists the terminal\n * state (and lets the card surface patch the DM card).\n */\n pendingRequests(now = Date.now()): AccessRequestRecord[] {\n return this.requestRecords().filter(\n record => record.status === 'pending' && record.expiresAt > now,\n )\n }\n\n getRequest(token: string): AccessRequestRecord | undefined {\n return this.requests.get(token)\n }\n\n /** The current pending request for one target, if any. */\n pendingRequestFor(kind: AccessRequestRecord['kind'], targetId: string): AccessRequestRecord | undefined {\n for (const record of this.requests.values()) {\n if (record.kind === kind && record.targetId === targetId && record.status === 'pending') {\n return record\n }\n }\n return undefined\n }\n\n /**\n * True when the same target was denied recently — used to keep a rejected\n * request from immediately re-notifying the administrator.\n */\n recentlyDenied(\n kind: AccessRequestRecord['kind'],\n targetId: string,\n windowMs: number,\n now = Date.now(),\n ): boolean {\n const cutoff = now - windowMs\n for (const record of this.requests.values()) {\n if (record.kind === kind && record.targetId === targetId\n && record.status === 'denied' && record.createdAt >= cutoff) {\n return true\n }\n }\n return false\n }\n\n async putRequest(record: AccessRequestRecord): Promise<AccessRequestRecord> {\n await this.requireDomain('put access request').accessRequests.put(record.token, record)\n this.requests.set(record.token, record)\n return record\n }\n\n /**\n * Mark every pending request past its deadline as expired. Returns the\n * settled records so the caller can patch stale DM cards.\n */\n async expireRequests(now = Date.now()): Promise<AccessRequestRecord[]> {\n const expired: AccessRequestRecord[] = []\n for (const record of [...this.requests.values()]) {\n if (record.status !== 'pending' || record.expiresAt > now) continue\n const next: AccessRequestRecord = { ...record, status: 'expired' }\n try {\n await this.requireDomain('expire access request').accessRequests.put(next.token, next)\n } catch (error) {\n this.deps.warn('dsh-channels: persisting expired access request %s failed: %s', next.token, String(error))\n }\n this.requests.set(next.token, next)\n expired.push(next)\n }\n return expired\n }\n\n private requireDomain(action: string): FeishuDomainHandle {\n const domain = this.deps.domain()\n if (domain === undefined) throw new Error(`dsh-channels: access domain not open (${action})`)\n return domain\n }\n}\n","/**\n * HITL approval decision cards (`approval/request`).\n *\n * @module dsh-channels/cards/approval\n */\n\nimport { columnSet, divider, markdown, note, standaloneCard, textButton } from './primitives.ts'\n\n// ---- HITL approval decision cards ----------------------------------------\n\n/** Terminal states an approval decision card can settle into. */\nexport type ApprovalCardState = 'allowed' | 'denied' | 'expired' | 'cancelled'\n\n/** The question one approval decision card presents. */\nexport interface ApprovalQuestion {\n /** Opaque token binding the card to one pending approval (button value). */\n token: string\n /** Model-facing tool name asking for approval (e.g. `bash`). */\n toolName: string\n /** The asker's human-readable justification; clipped to a safe length. */\n reason?: string\n /** Timeout window shown on the card (milliseconds). */\n timeoutMs: number\n /** Session id trace line (omitted when absent). */\n sessionId?: string\n}\n\n/** Approval card rendering options. */\nexport interface ApprovalCardOptions {\n /** Add the owner-only notice (a non-owner clicked and was ignored). */\n unauthorizedNote?: boolean\n}\n\n/** Cap on the rendered reason so the card can never approach the size budget. */\nconst APPROVAL_REASON_MAX_CHARS = 1_000\n\n/** `⏱ 5 分钟内有效…` — human timeout label (whole minutes, min 1). */\nfunction approvalTimeoutLabel(timeoutMs: number): string {\n return `${Math.max(1, Math.round(timeoutMs / 60_000))} 分钟`\n}\n\n/** One approval decision button with its callback payload. */\nfunction approvalButton(question: ApprovalQuestion, decision: 'allow' | 'deny'): object {\n const allow = decision === 'allow'\n return textButton({\n label: allow ? '✅ 允许' : '❌ 拒绝',\n type: allow ? 'primary' : 'danger',\n width: 'fill',\n margin: '0px',\n value: { decision, token: question.token },\n })\n}\n\n/** Two-column row that keeps both decisions side by side. */\nfunction approvalActions(question: ApprovalQuestion): object {\n return columnSet((['allow', 'deny'] as const).map(decision => approvalButton(question, decision)))\n}\n\n/** The question summary shared by the pending and settled variants. */\nfunction approvalQuestionElements(question: ApprovalQuestion): object[] {\n const elements: object[] = [\n markdown(`工具 \\`${question.toolName}\\` 请求执行一个需要授权的操作。`),\n ]\n if (question.reason !== undefined && question.reason !== '') {\n const clipped = question.reason.length > APPROVAL_REASON_MAX_CHARS\n ? `${question.reason.slice(0, APPROVAL_REASON_MAX_CHARS)}…`\n : question.reason\n elements.push(markdown(`> ${clipped.replace(/\\s+/gu, ' ')}`))\n }\n return elements\n}\n\n/**\n * The pending approval decision card: question summary, timeout note, and the\n * enabled allow/deny buttons. Sent as a NEW standalone message anchored to\n * the session's latest streaming card (never embedded into the streamed\n * card, whose full-replacement patches would clobber the buttons).\n */\nexport function buildApprovalCard(question: ApprovalQuestion, options: ApprovalCardOptions = {}): object {\n const elements: object[] = [\n ...approvalQuestionElements(question),\n divider(),\n note(`⏱ ${approvalTimeoutLabel(question.timeoutMs)}内有效,超时自动拒绝 · 允许仅对本次动作生效`),\n ]\n if (options.unauthorizedNote === true) {\n elements.push(note('ℹ️ 仅发起该会话的用户可操作此卡片'))\n }\n if (question.sessionId !== undefined) {\n elements.push(note(`🔑 ${question.sessionId}`))\n }\n elements.push(approvalActions(question))\n return standaloneCard('**⚠️ 需要你的审批**', elements)\n}\n\n/** Settled-card status lines, one per terminal state. */\nconst APPROVAL_SETTLED_LINES: Record<ApprovalCardState, string> = {\n allowed: '✅ 已允许执行',\n denied: '❌ 已拒绝',\n expired: '⏱ 已超时未处理,自动拒绝',\n cancelled: '↩️ 请求已取消',\n}\n\n/**\n * The terminal replacement for a decision card after the question settles\n * (decision, timeout, or withdrawal). Keeps the question summary for context\n * and removes the decision controls entirely.\n */\nexport function buildApprovalSettledCard(question: ApprovalQuestion, state: ApprovalCardState): object {\n return standaloneCard(`**${APPROVAL_SETTLED_LINES[state]}**`, [\n ...approvalQuestionElements(question),\n divider(),\n ])\n}\n\n","/**\n * Shared lifecycle of a pending interaction card.\n *\n * Both HITL surfaces (approvals and `ask_user_question`) need the same\n * mechanics: a token-keyed registry, owner-gated card-action lookup, delivery\n * with a withdrawn-race guard, a local timeout, settle-once cleanup, and\n * shutdown withdrawal. Only the rendered card and the resolution semantics\n * differ, so those stay in the surface modules and everything else lives here.\n *\n * @module dsh-channels/interactions/pending\n */\n\nimport type { CardActionEvent, FeishuChannel } from '../feishu-channel.ts'\n\n/** Where one session's interaction cards are placed on Feishu. */\nexport interface InteractionRoute {\n /** Feishu chat the cards live in. */\n chatId: string\n /** Reply anchor: the session's latest streaming card message id. */\n cardMessageId: string\n /** Whether the anchor lives in a real topic (replyInThread). */\n inThread: boolean\n /** The session owner (open_id) — the only operator allowed to decide. */\n senderOpenId: string\n}\n\n/** Fields every pending interaction card carries. */\nexport interface PendingCardEntry {\n /** Opaque token binding the card to one pending interaction. */\n readonly token: string\n /** DSH session the interaction belongs to (routing + error logs). */\n readonly sessionId: string\n /** Feishu placement of this card. */\n readonly route: InteractionRoute\n /** Card message id once it lands; undefined while sending. */\n messageId: string | undefined\n /** Settle-once guard: a late click/signal/timeout is discarded. */\n settled: boolean\n /** Local timeout handle (absent for host-owned deadlines). */\n timer: ReturnType<typeof setTimeout> | undefined\n /** The ask's own abort signal, when it has one. */\n readonly signal: AbortSignal | undefined\n /** Listener registered for that signal, removed on close. */\n onAbort: (() => void) | undefined\n /** The owner-only notice was already patched onto the card. */\n unauthorizedNoted: boolean\n}\n\nexport interface DeliverHandlers<E extends PendingCardEntry> {\n /** The pending card to send as a topic/thread reply. */\n card: object\n /** The card landed and its message id is recorded (arm timeouts here). */\n onDelivered?(entry: E): void\n /** The request withdrew while the card was in flight. */\n onWithdrawn(entry: E): void\n /** Delivery failed: fail the request closed. */\n onFailure(entry: E, error: unknown): void\n}\n\n/**\n * The pending-card table: registry, delivery, owner gating, and cleanup.\n * Concrete surfaces supply rendering and the terminal action.\n */\nexport class PendingCardTable<E extends PendingCardEntry> {\n private readonly entries = new Map<string, E>()\n\n constructor(\n private readonly channel: FeishuChannel,\n private readonly onError: (sessionId: string, error: unknown) => void,\n ) {}\n\n add(entry: E): void {\n this.entries.set(entry.token, entry)\n }\n\n count(): number {\n return this.entries.size\n }\n\n /** Every pending entry (diagnostics and inbound text routing). */\n values(): IterableIterator<E> {\n return this.entries.values()\n }\n\n /**\n * Owner-gated lookup for one card action, containing the owner-only notice.\n * Returns `undefined` for unknown cards, settled entries, and non-owner\n * clicks (which call `onUnauthorized` once).\n */\n resolveAction(event: CardActionEvent, onUnauthorized: (entry: E) => void): E | undefined {\n let entry: E | undefined\n for (const candidate of this.entries.values()) {\n if (candidate.messageId === event.messageId) {\n entry = candidate\n break\n }\n }\n if (entry === undefined || entry.settled) return undefined\n if (event.operator.openId !== entry.route.senderOpenId) {\n onUnauthorized(entry)\n return undefined\n }\n return entry\n }\n\n /** Send the pending card and contain every outcome. */\n async deliver(entry: E, handlers: DeliverHandlers<E>): Promise<void> {\n try {\n const res = await this.channel.send(\n entry.route.chatId,\n { card: handlers.card },\n { replyTo: entry.route.cardMessageId, replyInThread: entry.route.inThread },\n )\n // Record the id before the withdrawn check: a request that settled\n // while the card was in flight still needs its card patched terminal.\n entry.messageId = res.messageId\n if (entry.settled) {\n handlers.onWithdrawn(entry)\n return\n }\n handlers.onDelivered?.(entry)\n } catch (error) {\n this.onError(entry.sessionId, error)\n handlers.onFailure(entry, error)\n }\n }\n\n /** Full-replacement patch of one delivered card; failures stay contained. */\n patch(entry: E, card: object): void {\n if (entry.messageId === undefined) return\n void this.channel.updateCard(entry.messageId, card)\n .catch((error: unknown) => { this.onError(entry.sessionId, error) })\n }\n\n /** Arm the local deadline for a request the host does not time itself. */\n armTimeout(entry: E, ms: number, onTimeout: () => void): void {\n entry.timer = setTimeout(onTimeout, ms)\n entry.timer.unref?.()\n }\n\n /**\n * Close one entry exactly once: clear the timer and abort listener, drop it\n * from the registry, then run the terminal action (usually a card patch).\n * @returns false when it was already settled.\n */\n close(entry: E, terminal: () => void): boolean {\n if (entry.settled) return false\n entry.settled = true\n if (entry.timer !== undefined) clearTimeout(entry.timer)\n if (entry.signal !== undefined && entry.onAbort !== undefined) {\n entry.signal.removeEventListener('abort', entry.onAbort)\n }\n this.entries.delete(entry.token)\n terminal()\n return true\n }\n\n /** Withdraw every pending entry (plugin shutdown). */\n withdrawAll(withdraw: (entry: E) => void): void {\n for (const entry of [...this.entries.values()]) withdraw(entry)\n }\n}\n","/**\n * HITL approval answerer: turns the host's `approval/request` waterfall\n * events into standalone Feishu decision cards.\n *\n * The answerer is registered with `prepend: true` on the plugin context so it\n * runs BEFORE generic answerers: a request for a session this channel routes\n * is claimed here, and the decision card becomes the only way to answer.\n * Requests for sessions the channel does NOT route delegate through `next()`.\n *\n * Rendering lives in `cards/approval.ts`; the shared pending-card lifecycle\n * (delivery, owner gating, timeout, settle-once, withdrawal) lives in\n * `interactions/pending.ts`.\n *\n * @module dsh-channels/interactions/approvals\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { buildApprovalCard, buildApprovalSettledCard } from '../cards/approval.ts'\nimport type { ApprovalCardState, ApprovalQuestion } from '../cards/approval.ts'\nimport type { ApprovalOutcome, ApprovalRequest } from '../dsh-types.ts'\nimport type { CardActionEvent, FeishuChannel } from '../feishu-channel.ts'\nimport { PendingCardTable } from './pending.ts'\nimport type { InteractionRoute, PendingCardEntry } from './pending.ts'\n\n/** Feishu placement of one session's approval cards. */\nexport type ApprovalRoute = InteractionRoute\n\nexport interface ApprovalHooks {\n /**\n * Resolve the Feishu placement for a session. `undefined` means the session\n * is not owned by this channel — the answerer delegates through `next()`.\n */\n routeFor(sessionId: string): ApprovalRoute | undefined\n /** Log a non-fatal error (delivery/update failures). */\n onError(sessionId: string, error: unknown): void\n}\n\n/** One in-flight approval question: its card content and its resolver. */\ninterface Pending extends PendingCardEntry {\n question: ApprovalQuestion\n resolve(outcome: ApprovalOutcome): void\n}\n\nexport class ApprovalAnswerer {\n private readonly table: PendingCardTable<Pending>\n\n constructor(\n channel: FeishuChannel,\n private readonly hooks: ApprovalHooks,\n private readonly timeoutMs: number,\n ) {\n this.table = new PendingCardTable(channel, hooks.onError)\n }\n\n /** In-flight question count (diagnostics/tests). */\n pendingCount(): number {\n return this.table.count()\n }\n\n /**\n * The `approval/request` waterfall listener. Never rejects: the host service\n * already contains answerer failures to the fail-closed `'unavailable'`.\n */\n onRequest(req: ApprovalRequest, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome> {\n const route = this.hooks.routeFor(req.agent.session.id)\n if (route === undefined) return next()\n // The abort window between the service's own signal check and this\n // listener is closed by settling synchronously: an aborted ask never\n // opens a question.\n if (req.signal?.aborted === true) return Promise.resolve('cancelled')\n\n const token = randomUUID()\n // `displayReason` (DSH 0.2.0) is presentation-only and locale-aware;\n // `reason` remains the audit-trail fallback.\n const displayReason = req.displayReason?.zh ?? req.displayReason?.en ?? req.reason\n const question: ApprovalQuestion = {\n token,\n toolName: req.toolName,\n ...(displayReason === undefined ? {} : { reason: displayReason }),\n timeoutMs: this.timeoutMs,\n sessionId: req.agent.session.id,\n }\n return new Promise<ApprovalOutcome>((resolve) => {\n const entry: Pending = {\n token,\n sessionId: req.agent.session.id,\n route,\n messageId: undefined,\n settled: false,\n timer: undefined,\n signal: req.signal,\n onAbort: undefined,\n unauthorizedNoted: false,\n question,\n resolve,\n }\n const onAbort = (): void => {\n this.settle(entry, 'cancelled', 'cancelled')\n }\n entry.onAbort = onAbort\n // Registered synchronously at promise creation so no abort can slip\n // between the ownership check and the card delivery; a residual abort\n // in the tiny window before addEventListener is still covered because\n // the host's decide() races the signal against this promise and\n // discards a late resolution (worst case: the card times out).\n req.signal?.addEventListener('abort', onAbort, { once: true })\n this.table.add(entry)\n void this.table.deliver(entry, {\n card: buildApprovalCard(question),\n onWithdrawn: e => this.table.patch(e, buildApprovalSettledCard(e.question, 'cancelled')),\n onDelivered: e => this.table.armTimeout(e, this.timeoutMs, () => {\n this.settle(e, 'rejected', 'expired')\n }),\n // No card → no human answerer → fail closed.\n onFailure: e => {\n this.table.close(e, () => {})\n e.resolve('unavailable')\n },\n })\n })\n }\n\n /**\n * Route one card button-callback event to its pending question. Unknown\n * cards and non-owner operators are ignored (a non-owner click adds the\n * owner-only notice to the card once); owner decisions settle the question.\n */\n onCardAction(event: CardActionEvent): void {\n const entry = this.table.resolveAction(event, candidate => {\n if (candidate.unauthorizedNoted) return\n candidate.unauthorizedNoted = true\n this.table.patch(candidate, buildApprovalCard(candidate.question, { unauthorizedNote: true }))\n })\n if (entry === undefined) return\n const decision = (event.action.value as { decision?: unknown } | undefined)?.decision\n if (decision === 'allow') this.settle(entry, 'allowed-once', 'allowed')\n else if (decision === 'deny') this.settle(entry, 'rejected', 'denied')\n }\n\n /** Withdraw every in-flight question (plugin shutdown). */\n dispose(): void {\n this.table.withdrawAll(entry => this.settle(entry, 'cancelled'))\n }\n\n /** Settle once, then patch the card to its terminal state. */\n private settle(entry: Pending, outcome: ApprovalOutcome, cardState?: ApprovalCardState): void {\n const closed = this.table.close(entry, () => {\n if (cardState !== undefined) {\n this.table.patch(entry, buildApprovalSettledCard(entry.question, cardState))\n }\n })\n if (closed) entry.resolve(outcome)\n }\n}\n","/**\n * Structured user-question cards (`user-questions/request`).\n *\n * @module dsh-channels/cards/question\n */\n\nimport { card, divider, markdown, note, standaloneCard, textButton } from './primitives.ts'\n\n// ---- structured user-question cards --------------------------------------\n\n/** Terminal states a user-question card can settle into. */\nexport type QuestionCardState = 'answered' | 'expired' | 'cancelled'\n\n/** One question as rendered (options are always present after normalization). */\nexport interface QuestionCardItem {\n id: string\n question: string\n detail?: string\n header?: string\n options: Array<{ label: string; description?: string }>\n multiSelect: boolean\n /** `plan-review` intent's approve label (presentation only). */\n intentApproved?: string\n}\n\n/** One pending question card's render input. */\nexport interface QuestionCardInput {\n /** Opaque token binding the card to one pending question set. */\n token: string\n questions: QuestionCardItem[]\n timeoutMs: number\n sessionId?: string\n /** Multi-select toggles accumulated so far, by question id. */\n selected?: Record<string, string[]>\n /** Question ids already answered (single-select picks). */\n answered?: readonly string[]\n /** Add the owner-only notice (a non-owner clicked and was ignored). */\n unauthorizedNote?: boolean\n}\n\n/** Cap on rendered strings so one question cannot approach the card budget. */\nconst QUESTION_TEXT_MAX_CHARS = 500\n/** Cap on rendered options per question (excess is reported, not dropped silently). */\nexport const MAX_QUESTION_OPTIONS = 8\n\nfunction clipQuestionText(text: string, max = QUESTION_TEXT_MAX_CHARS): string {\n return text.length <= max ? text : `${text.slice(0, max)}…`\n}\n\nfunction questionAnswered(input: QuestionCardInput, id: string): boolean {\n return (input.answered ?? []).includes(id)\n}\n\nfunction questionOptionButton(\n input: QuestionCardInput,\n question: QuestionCardItem,\n option: { label: string; description?: string },\n): object {\n const isMulti = question.multiSelect\n const selected = (input.selected?.[question.id] ?? []).includes(option.label)\n const approved = question.intentApproved === option.label\n const answered = questionAnswered(input, question.id)\n const type = isMulti\n ? (selected ? 'primary' : 'default')\n : (approved ? 'primary' : 'default')\n const label = isMulti && selected ? `✅ ${option.label}` : option.label\n return textButton({\n label: clipQuestionText(label, 80),\n type: answered ? 'default' : type,\n width: 'fill',\n disabled: answered,\n value: {\n decision: isMulti ? 'question-toggle' : 'question-answer',\n token: input.token,\n id: question.id,\n value: option.label,\n },\n })\n}\n\nfunction questionElements(input: QuestionCardInput): object[] {\n const elements: object[] = [markdown('**❓ 需要你的回答**')]\n input.questions.forEach((question, index) => {\n const heading = question.header === undefined || question.header === ''\n ? `**问题 ${index + 1}**`\n : `**问题 ${index + 1} · ${clipQuestionText(question.header, 80)}**`\n elements.push(markdown(`${heading}\\n${clipQuestionText(question.question)}`))\n if (question.detail !== undefined && question.detail !== '') {\n elements.push(markdown(`> ${clipQuestionText(question.detail, 1_200).replace(/\\s+/gu, ' ')}`))\n }\n const options = question.options.slice(0, MAX_QUESTION_OPTIONS)\n for (const option of options) {\n elements.push(questionOptionButton(input, question, option))\n }\n if (question.options.length === 0) {\n elements.push(note('本题没有预设选项,请直接回复文字作答。'))\n } else if (question.options.length > options.length) {\n elements.push(note(`另有 ${question.options.length - options.length} 个选项未展示,请在 Web 端回答。`))\n }\n if (question.multiSelect) {\n const selected = input.selected?.[question.id] ?? []\n if (selected.length > 0 && !questionAnswered(input, question.id)) {\n elements.push(textButton({\n label: '提交本题选择',\n type: 'primary',\n value: { decision: 'question-submit', token: input.token, id: question.id },\n }))\n }\n elements.push(note(`本题为多选:点选后请点“提交本题选择”(已选 ${selected.length} 项)。`))\n }\n })\n elements.push(divider())\n elements.push(note(`⏱ ${Math.max(1, Math.round(input.timeoutMs / 60_000))} 分钟内有效,超时后本轮提问失败 · 仅发起该会话的用户可操作 · 也可直接回复文字作答`))\n if (input.sessionId !== undefined) elements.push(note(`🔑 ${input.sessionId}`))\n if (input.unauthorizedNote === true) elements.push(note('ℹ️ 仅发起该会话的用户可操作此卡片'))\n return elements\n}\n\n/** The pending structured-question card (one card carries every question). */\nexport function buildQuestionCard(input: QuestionCardInput): object {\n return card(questionElements(input))\n}\n\nconst QUESTION_SETTLED_LINES: Record<QuestionCardState, string> = {\n answered: '✅ 已回答',\n expired: '⏱ 已超时未处理,本轮提问已失败',\n cancelled: '↩️ 提问已取消',\n}\n\n/** The terminal replacement after the question set settles; controls removed. */\nexport function buildQuestionSettledCard(input: QuestionCardInput, state: QuestionCardState): object {\n const elements: object[] = [\n ...input.questions.slice(0, 4).map((question, index) =>\n markdown(`**问题 ${index + 1}**:${clipQuestionText(question.question, 200)}`)),\n divider(),\n ]\n if (state === 'expired') {\n elements.push(note('提问窗口已关闭。可在下一条消息中要求模型重新提问。'))\n }\n return standaloneCard(`**${QUESTION_SETTLED_LINES[state]}**`, elements)\n}\n","/**\n * HITL structured-question answerer: turns the host's\n * `user-questions/request` waterfall events (the model's `ask_user_question`\n * tool) into standalone Feishu cards.\n *\n * The host's only shipped answerer is the Web client, so a Feishu-only\n * session otherwise fails every `ask_user_question` call with `NO_PROVIDER`.\n * This answerer is registered with `prepend: true` on the plugin context:\n * requests for a session this channel routes are claimed here, everything else\n * delegates through `next()`.\n *\n * Rendering lives in `cards/question.ts`; the shared pending-card lifecycle\n * lives in `interactions/pending.ts`.\n *\n * Deliberately unsupported in v1: questions that declare no options (a card\n * cannot ask for them); such a request fails with an explicit error instead of\n * hanging.\n *\n * @module dsh-channels/interactions/questions\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { buildQuestionCard, buildQuestionSettledCard } from '../cards/question.ts'\nimport type { QuestionCardInput, QuestionCardItem, QuestionCardState } from '../cards/question.ts'\nimport type { AskUserQuestionAnswer, AskUserQuestionItem, AskUserQuestionRequestEvent } from '../dsh-types.ts'\nimport type { CardActionEvent, FeishuChannel } from '../feishu-channel.ts'\nimport { PendingCardTable } from './pending.ts'\nimport type { InteractionRoute, PendingCardEntry } from './pending.ts'\n\nexport interface QuestionHooks {\n /**\n * Resolve the Feishu placement for a session. `undefined` means the session\n * is not owned by this channel — the answerer delegates through `next()`.\n */\n routeFor(sessionId: string): InteractionRoute | undefined\n /** Log a non-fatal error (delivery/update failures). */\n onError(sessionId: string, error: unknown): void\n}\n\n/** One in-flight question set: card content, partial answers, resolver. */\ninterface PendingQuestions extends PendingCardEntry {\n questions: QuestionCardItem[]\n /** Multi-select toggles not yet submitted, by question id. */\n selected: Map<string, string[]>\n /** Committed answers, by question id. */\n answered: Map<string, string[]>\n /** Free-text answers supplied by a plain reply, by question id. */\n custom: Map<string, string>\n /** Host owns the deadline (timed wait): no local timer is armed. */\n hostTimed: boolean\n resolve(answer: AskUserQuestionAnswer): void\n reject(error: unknown): void\n}\n\n/** Normalize one host question into its renderable form. */\nfunction normalizeQuestion(item: AskUserQuestionItem): QuestionCardItem {\n const intent = item.intent\n return {\n id: item.id,\n question: item.question,\n ...(item.detail === undefined ? {} : { detail: item.detail }),\n ...(item.header === undefined ? {} : { header: item.header }),\n options: (item.options ?? []).map(option => ({\n label: option.label,\n ...(option.description === undefined ? {} : { description: option.description }),\n })),\n multiSelect: item.multiSelect === true,\n ...(intent?.kind === 'plan-review' ? { intentApproved: intent.approve } : {}),\n }\n}\n\nexport class QuestionAnswerer {\n private readonly table: PendingCardTable<PendingQuestions>\n\n constructor(\n channel: FeishuChannel,\n private readonly hooks: QuestionHooks,\n private readonly timeoutMs: number,\n ) {\n this.table = new PendingCardTable(channel, hooks.onError)\n }\n\n /** In-flight question-set count (diagnostics/tests). */\n pendingCount(): number {\n return this.table.count()\n }\n\n /**\n * The `user-questions/request` waterfall listener. Non-owned sessions and\n * questions without options delegate through `next()`.\n */\n onRequest(\n request: AskUserQuestionRequestEvent,\n next: () => Promise<AskUserQuestionAnswer>,\n ): Promise<AskUserQuestionAnswer> {\n const agent = request.agent\n if (agent === undefined) return next()\n const route = this.hooks.routeFor(agent.session.id)\n if (route === undefined) return next()\n\n const questions = request.questions.map(normalizeQuestion)\n if (questions.length === 0) return next()\n\n const token = randomUUID()\n return new Promise<AskUserQuestionAnswer>((resolve, reject) => {\n const entry: PendingQuestions = {\n token,\n sessionId: agent.session.id,\n route,\n messageId: undefined,\n settled: false,\n timer: undefined,\n signal: request.signal,\n onAbort: undefined,\n unauthorizedNoted: false,\n questions,\n selected: new Map(),\n answered: new Map(),\n custom: new Map(),\n hostTimed: request.wait?.timed === true,\n resolve,\n reject,\n }\n const onAbort = (): void => {\n const reason = request.signal?.reason\n entry.reject(reason instanceof Error ? reason : new Error('dsh-channels: 提问已被取消。'))\n this.close(entry, 'cancelled')\n }\n entry.onAbort = onAbort\n if (request.signal?.aborted === true) {\n reject(new Error('dsh-channels: 提问已被取消。'))\n return\n }\n request.signal?.addEventListener('abort', onAbort, { once: true })\n this.table.add(entry)\n void this.table.deliver(entry, {\n card: buildQuestionCard(this.cardInput(entry)),\n onWithdrawn: e => this.table.patch(e, buildQuestionSettledCard(this.cardInput(e), 'cancelled')),\n // A timed wait is owned by the host: its signal settles the request at\n // the deadline (rejection with ASK_TIMED_OUT becomes a continued\n // question). Only blocking asks need a local timer.\n onDelivered: e => {\n if (e.hostTimed) return\n this.table.armTimeout(e, this.timeoutMs, () => {\n e.reject(new Error('dsh-channels: ask_user_question 等待超时,未收到回答。'))\n this.close(e, 'expired')\n })\n },\n onFailure: (e, error) => {\n this.table.close(e, () => {})\n e.reject(error instanceof Error ? error : new Error(String(error)))\n },\n })\n })\n }\n\n /** Snapshot the current selections for rendering. */\n private cardInput(entry: PendingQuestions): QuestionCardInput {\n const selected: Record<string, string[]> = {}\n for (const [id, labels] of entry.selected) selected[id] = [...labels]\n return {\n token: entry.token,\n questions: entry.questions,\n timeoutMs: this.timeoutMs,\n sessionId: entry.sessionId,\n selected,\n answered: [...entry.answered.keys()],\n ...(entry.unauthorizedNoted ? { unauthorizedNote: true } : {}),\n }\n }\n\n /**\n * Route one card button-callback event to its pending question set.\n * Unknown cards and non-owner operators are ignored (a non-owner click adds\n * the owner-only notice once); owner decisions settle the request.\n */\n onCardAction(event: CardActionEvent): void {\n const entry = this.table.resolveAction(event, candidate => {\n if (candidate.unauthorizedNoted) return\n candidate.unauthorizedNoted = true\n this.table.patch(candidate, buildQuestionCard(this.cardInput(candidate)))\n })\n if (entry === undefined) return\n const value = event.action.value as\n | { decision?: unknown; id?: unknown; value?: unknown }\n | undefined\n const decision = value?.decision\n const id = typeof value?.id === 'string' ? value.id : undefined\n const option = typeof value?.value === 'string' ? value.value : undefined\n if (id === undefined) return\n const question = entry.questions.find(item => item.id === id)\n if (question === undefined || entry.answered.has(id)) return\n\n if (decision === 'question-answer' && option !== undefined) {\n entry.answered.set(id, [option])\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n this.resolveIfComplete(entry)\n return\n }\n if (decision === 'question-toggle' && option !== undefined) {\n const current = new Set(entry.selected.get(id) ?? [])\n if (current.has(option)) current.delete(option)\n else current.add(option)\n entry.selected.set(id, [...current])\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n return\n }\n if (decision === 'question-submit') {\n const chosen = entry.selected.get(id) ?? []\n if (chosen.length === 0) return\n entry.answered.set(id, [...chosen])\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n this.resolveIfComplete(entry)\n }\n }\n\n /** Complete the waterfall once every question has a committed answer. */\n private resolveIfComplete(entry: PendingQuestions): void {\n if (entry.questions.some(question => !entry.answered.has(question.id))) return\n const answer: AskUserQuestionAnswer = {\n answers: entry.questions.map(question => ({\n id: question.id,\n selected: entry.answered.get(question.id) ?? [],\n ...(entry.custom.has(question.id) ? { custom: entry.custom.get(question.id) } : {}),\n })),\n }\n const closed = this.table.close(entry, () => {\n this.table.patch(entry, buildQuestionSettledCard(this.cardInput(entry), 'answered'))\n })\n if (closed) entry.resolve(answer)\n }\n\n /**\n * Feed one plain reply as a free-text answer.\n *\n * A session blocked on `ask_user_question` has no other use for an inbound\n * text message, so the next unanswered question consumes it as its \"Other\"\n * answer. Returns false when the session has no pending question.\n */\n submitText(sessionId: string, text: string): boolean {\n const trimmed = text.trim()\n if (trimmed === '') return false\n const entry = [...this.table.values()].find(candidate =>\n candidate.sessionId === sessionId && !candidate.settled)\n if (entry === undefined) return false\n const question = entry.questions.find(candidate => !entry.answered.has(candidate.id))\n if (question === undefined) return false\n entry.answered.set(question.id, [])\n entry.custom.set(question.id, trimmed)\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n this.resolveIfComplete(entry)\n return true\n }\n\n /** Whether a session currently has an unanswered question set. */\n hasPendingFor(sessionId: string): boolean {\n return [...this.table.values()].some(candidate => candidate.sessionId === sessionId && !candidate.settled)\n }\n\n /** Withdraw every in-flight question (plugin shutdown). */\n dispose(): void {\n this.table.withdrawAll(entry => {\n entry.reject(new Error('dsh-channels: 插件已关闭,提问未完成。'))\n this.close(entry, 'cancelled')\n })\n }\n\n /** Settle once and patch the card to its terminal state. */\n private close(entry: PendingQuestions, state: QuestionCardState): void {\n this.table.close(entry, () => {\n this.table.patch(entry, buildQuestionSettledCard(this.cardInput(entry), state))\n })\n }\n}\n","/**\n * Streaming reply card: reasoning panel, activity lines, body segmentation\n * and the byte/table budgets that keep one card inside Feishu's limits.\n *\n * Two independent budgets: the reply body segments across cards at\n * `CARD_TARGET_BYTES` (never splitting reasoning), while the reasoning fold\n * panel is a fixed `REASONING_MAX_BYTES` (5 KB) window — clipped from the tail\n * with a truncation marker and never traded against the body in either\n * direction.\n *\n * @module dsh-channels/cards/streaming\n */\n\nexport type PanelPhase = 'thinking' | 'replying' | 'done' | 'error'\n\nexport const PANEL_TITLES: Record<PanelPhase, string> = {\n thinking: '推理中',\n replying: '回复中',\n done: '回复结束',\n error: '回复失败',\n}\n\n/** Built-in icon token for the fold-panel chevron (from the official component docs). */\nexport const PANEL_CHEVRON_TOKEN = 'down-small-ccm_outlined'\n\n/** Feishu hard cap for one card message body. */\nexport const CARD_MAX_BYTES = 30_000\n/** Target budget per card — 2 KB below the hard cap as headroom. */\nexport const CARD_TARGET_BYTES = 28_000\n/**\n * Hard byte cap for the reasoning fold panel's markdown (5 KB, JSON-escaped\n * UTF-8, including the truncation marker). The panel is clipped against this\n * alone — never against the card — so it neither competes with nor is squeezed\n * by the reply body: the visible reasoning window is identical whether the\n * answer is one line or a full 28 KB segment. Measured at the cap: ≈4.96 K\n * ASCII chars, ≈1.65 K CJK chars, ≈2.5 K emoji. The panel element's own\n * wrapper (border, header, chevron) is charged separately — see\n * `CARD_PANEL_RESERVE_BYTES`.\n */\nexport const REASONING_MAX_BYTES = 5_000\n/**\n * Bytes held back from the card target for the fold panel as a whole, so a\n * segmented card's body plus its panel stay inside `CARD_TARGET_BYTES`.\n * `REASONING_MAX_BYTES` caps the panel's markdown; the element wrapper around\n * it (border, header, chevron icon, `elements` array) measures a further 315 B\n * at the worst phase, and this reserve covers that with headroom.\n */\nexport const CARD_PANEL_RESERVE_BYTES = 5_500\n/**\n * Feishu hard-limits the number of markdown tables a single card may contain\n * (API error 11310 \"card table number over limit\"; verified on device: the\n * platform allows 5). Segmentation therefore keeps at most 5 tables per card.\n */\nexport const MAX_TABLES_PER_CARD = 5\n\n/** Appended to clipped reasoning (and, as an absolute last resort, clipped body). */\nexport const TRUNCATION_SUFFIX = '\\n\\n…(内容过长,已截断)'\n\n/** Session-scoped figures rendered on the info lines. */\nexport interface CardStats {\n /** DSH session id. */\n sessionId: string\n /** Cumulative uncached input tokens of the whole session. */\n inputTokens: number\n /** Cumulative output tokens of the whole session. */\n outputTokens: number\n /** Cache hit ratio of the current turn's latest provider sample, 0–1; undefined → `—`. */\n cacheHitPct?: number\n /** Latest provider-reported prompt pressure in tokens; undefined → `—`. */\n contextTokens?: number\n /** Model context window in tokens; undefined → `—`. */\n contextWindow?: number\n /** Model id (current turn's latest request header). */\n model?: string\n /** Reasoning effort (current turn's latest request header). */\n effort?: string\n}\n\nexport interface CardState {\n phase: PanelPhase\n /** Raw reasoning text accumulated so far (may be empty). */\n reasoning: string\n /** Reply body accumulated so far (may be empty). */\n body: string\n /**\n * Transient delegation status (subagent/workflow progress, settled workflow\n * outcome, heartbeat elapsed) rendered as a notation line between the panel\n * and the body — the FIRST of the two activity lines. Empty/undefined →\n * omitted. Callers cap its length so it stays inside the budget headroom.\n */\n status?: string\n /**\n * The parent agent's in-flight tool call, rendered as a notation line below\n * the status line — the SECOND activity line. Independent of `status`:\n * neither ever supersedes the other. Empty/undefined → omitted.\n */\n tool?: string\n stats: CardStats\n}\n\n/** Rendering variants. */\nexport interface CardOptions {\n /** Render the reasoning fold panel (default `true`). Continuation segment\n * cards pass `false` and get a segment header instead. */\n panel?: boolean\n /** 1-based segment number shown in the continuation header. */\n segment?: number\n}\n\n/** The result of splitting a body for one card. `head` + `tail` re-join to the\n * original body except for blank-line separators at the cut. */\nexport interface BodySplit {\n /** Body prefix that fits one card (cut at a clean breakpoint). */\n head: string\n /** Remainder to flow into the next segment card ('' when everything fits). */\n tail: string\n}\n\n/** UTF-8 byte size of a card's JSON — the metric behind Feishu's 30 KB cap. */\nexport function cardJsonBytes(card: object): number {\n return new TextEncoder().encode(JSON.stringify(card)).length\n}\n\n/** A contiguous run of markdown table lines; `end` is exclusive. */\nexport interface TableBlock {\n start: number\n end: number\n}\n\n/**\n * Locate markdown table blocks: runs of ≥ 2 consecutive lines that start with\n * `|` and contain a second `|` (outside fenced code blocks). Feishu renders\n * each such run as a `table` element and hard-limits their count per card\n * (error 11310), so tables are the atomic unit for segmentation.\n */\nexport function tableBlocks(body: string): TableBlock[] {\n const blocks: TableBlock[] = []\n let inFence = false\n let runStart = -1\n let runEnd = -1\n let runLines = 0\n let offset = 0\n for (const line of body.split('\\n')) {\n const fence = /^[ \\t]*```/.test(line)\n const pipeLine = !inFence && /^[ \\t]*\\|[^\\n]*\\|/.test(line)\n if (fence) inFence = !inFence\n if (pipeLine) {\n if (runLines === 0) runStart = offset\n runEnd = offset + line.length\n runLines++\n } else if (runLines > 0) {\n if (runLines >= 2) blocks.push({ start: runStart, end: runEnd })\n runStart = -1\n runLines = 0\n }\n offset += line.length + 1\n }\n if (runLines >= 2) blocks.push({ start: runStart, end: runEnd })\n return blocks\n}\n\n/**\n * Replace markdown table blocks with a placeholder. Used for the reasoning\n * panel, whose content is auxiliary (truncation has no impact) and is never\n * segmented — this keeps panel tables from tripping the per-card table cap.\n */\nexport function stripTables(text: string): string {\n const blocks = tableBlocks(text)\n if (blocks.length === 0) return text\n let out = ''\n let position = 0\n for (const block of blocks) {\n out += text.slice(position, block.start)\n if (out !== '' && !out.endsWith('\\n')) out += '\\n'\n out += '*(表格已省略)*'\n position = block.end\n }\n out += text.slice(position)\n return out\n}\n\nfunction fmt(n: number): string {\n return n.toLocaleString('en-US')\n}\n\nfunction fmtContext(tokens?: number, window?: number): string {\n if (tokens === undefined) return '—'\n if (window === undefined) return `${fmt(tokens)} tokens`\n return `${fmt(tokens)} / ${fmt(window)}`\n}\n\nfunction fmtCacheHit(pct?: number): string {\n return pct === undefined ? '—' : `${(pct * 100).toFixed(1)}%`\n}\n\n/** Info line 2: `icon - 模型 {model}({effort}) · icon - 缓存命中 {cache} · icon - 上下文 {context}` */\nfunction buildModelLine(stats: CardStats): string {\n const model = stats.model ?? '—'\n const effort = stats.effort !== undefined && stats.effort !== '' ? `(${stats.effort})` : ''\n return `🧠 模型 ${model}${effort} · ⚡ 缓存命中 ${fmtCacheHit(stats.cacheHitPct)} · 📊 上下文 ${fmtContext(stats.contextTokens, stats.contextWindow)}`\n}\n\n/** Info line 1: `icon - {session id} · icon - input token {input_token} · icon - output token {output_token}` */\nfunction buildSessionLine(stats: CardStats): string {\n return `🔑 ${stats.sessionId} · ⬆ input token ${fmt(stats.inputTokens)} · ⬇ output token ${fmt(stats.outputTokens)}`\n}\n\nfunction reasoningPlaceholder(phase: PanelPhase): string {\n return phase === 'thinking' ? '*等待模型输出…*' : '*(本次无推理内容)*'\n}\n\nfunction bodyPlaceholder(phase: PanelPhase): string {\n return phase === 'done' || phase === 'error' ? '*(本次无正文输出)*' : '*正在生成…*'\n}\n\n/** Continuation header: `📄 回复分段 {n} · {phase}` (or 续上一条卡片 without a number). */\nfunction segmentHeader(options: CardOptions, phase: PanelPhase): string {\n const seg = options.segment\n const title = seg === undefined ? '续上一条卡片' : `回复分段 ${seg}`\n return `📄 ${title} · ${PANEL_TITLES[phase]}`\n}\n\n/** Raw card assembly — no clipping of any kind (the budget machinery wraps this). */\nfunction assemble(\n phase: PanelPhase,\n reasoning: string,\n body: string,\n stats: CardStats,\n options: CardOptions,\n status?: string,\n tool?: string,\n): object {\n const elements: object[] = []\n\n if (options.panel ?? true) {\n const panelReasoning = stripTables(reasoning)\n elements.push({\n tag: 'collapsible_panel',\n expanded: false,\n border: { color: 'grey', corner_radius: '8px' },\n header: {\n title: { tag: 'plain_text', content: PANEL_TITLES[phase] },\n icon: { tag: 'standard_icon', token: PANEL_CHEVRON_TOKEN },\n icon_position: 'right',\n icon_expanded_angle: 180,\n },\n elements: [\n { tag: 'markdown', content: panelReasoning.trim() === '' ? reasoningPlaceholder(phase) : panelReasoning },\n ],\n })\n } else {\n elements.push({ tag: 'markdown', content: segmentHeader(options, phase), text_size: 'notation' })\n }\n\n if (status !== undefined && status !== '') {\n elements.push({ tag: 'markdown', content: status, text_size: 'notation' })\n }\n if (tool !== undefined && tool !== '') {\n elements.push({ tag: 'markdown', content: tool, text_size: 'notation' })\n }\n\n elements.push({ tag: 'hr' })\n elements.push({ tag: 'markdown', content: body.trim() === '' ? bodyPlaceholder(phase) : body })\n elements.push({ tag: 'hr' })\n elements.push({ tag: 'markdown', content: buildSessionLine(stats), text_size: 'notation' })\n elements.push({ tag: 'markdown', content: buildModelLine(stats), text_size: 'notation' })\n\n return {\n schema: '2.0',\n config: { width_mode: 'fill' },\n body: { elements },\n }\n}\n\n/**\n * Fit `text` into a byte budget measured by `render`: return `text` unchanged\n * when it already fits; otherwise binary-search the longest prefix that still\n * fits once `suffix` is appended. The result is guaranteed to satisfy the\n * same measurement within `budget`.\n */\nfunction clipText(text: string, suffix: string, render: (content: string) => number, budget: number): string {\n if (render(text) <= budget) return text\n let lo = 0\n let hi = text.length\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n if (render(text.slice(0, mid) + suffix) <= budget) {\n lo = mid\n } else {\n hi = mid - 1\n }\n }\n return text.slice(0, lo) + suffix\n}\n\n/** JSON-escaped UTF-8 size of a string — the metric Feishu's card cap counts. */\nfunction jsonBytes(text: string): number {\n return new TextEncoder().encode(JSON.stringify(text)).length\n}\n\n/**\n * JSON-escaped UTF-8 size of the fold panel's markdown, as rendered by one\n * assembled card. Reasoning is budgeted against THIS rather than against the\n * whole card: a card-wide budget makes the panel collapse to the bare\n * truncation marker as soon as the reply body grows, so the visible reasoning\n * window would depend on how long the answer is.\n */\nfunction panelMarkdownBytes(render: (reasoning: string) => object): (content: string) => number {\n return content => {\n const elements = (render(content) as { body?: { elements?: Array<{ elements?: Array<{ content?: string }> }> } })\n .body?.elements\n return jsonBytes(elements?.[0]?.elements?.[0]?.content ?? '')\n }\n}\n\n/** JSON-escaped UTF-8 size of the truncation marker — reserved so clips can\n * never push a card past its budget. */\nconst SUFFIX_JSON_BYTES = jsonBytes(TRUNCATION_SUFFIX)\n\n/**\n * Build one card.\n *\n * - Reasoning is clipped so its markdown stays within `REASONING_MAX_BYTES`\n * (including the truncation marker): a fixed, self-contained budget that\n * neither grants space to nor borrows space from the body, so the visible\n * reasoning window is the same whether the reply is one line or a 28 KB\n * segment.\n * - The body gets whatever is left of `CARD_MAX_BYTES` once the clipped\n * reasoning is accounted for, which keeps the total under Feishu's hard cap.\n * The streamer pre-splits with `segmentCardContent` (probed at the\n * worst-case phase), so a live stream never reaches this clip: it is a pure\n * guard for direct callers.\n *\n * Guarantee: the returned card is always ≤ `CARD_MAX_BYTES`, and its panel is\n * always ≤ `REASONING_MAX_BYTES`.\n */\nexport function buildCard(state: CardState, options: CardOptions = {}): object {\n const render = (reasoning: string, body: string) =>\n assemble(state.phase, reasoning, body, state.stats, options, state.status, state.tool)\n\n // 1. The panel, against its own budget. The panel markdown (and therefore the\n // visible reasoning window) does not depend on the body, the phase title,\n // or the info lines — only on the reasoning text and this cap.\n const panelBytes = panelMarkdownBytes(content => render(content, ''))\n const reasoning = clipText(state.reasoning, TRUNCATION_SUFFIX, panelBytes, REASONING_MAX_BYTES)\n\n // 2. The body, against what is left of the hard cap once the panel's real\n // bytes are counted. The suffix reserve keeps a clipped body sendable.\n const body = clipText(state.body, TRUNCATION_SUFFIX, content => cardJsonBytes(render('', content)),\n CARD_MAX_BYTES - panelBytes(reasoning) - SUFFIX_JSON_BYTES)\n return render(reasoning, body)\n}\n\n/** Interruption notice body for cards repaired after a restart mid-turn. */\nexport const INTERRUPTION_NOTICE = '⚠️ 服务已重启,本次回复中断,请重新发送消息。'\n\n/**\n * Terminal \"interrupted\" card used by the restart repair pass (`repairInflight`).\n *\n * Deliberately NOT built through `buildCard`: the local process has no copy of\n * the partial body/reasoning the card previously streamed (patch is full\n * replacement), and fabricating zero-valued token stats would be misleading.\n * The card is therefore minimal — a plain notice (+ segment header on\n * continuation cards) — and always far inside the size budget.\n */\nexport function buildInterruptionCard(options: CardOptions = {}): object {\n const elements: object[] = []\n if (options.panel === false) {\n elements.push({\n tag: 'markdown',\n content: `📄 ${options.segment === undefined ? '续上一条卡片' : `回复分段 ${options.segment}`} · 回复中断`,\n text_size: 'notation',\n })\n }\n elements.push({ tag: 'markdown', content: INTERRUPTION_NOTICE })\n return {\n schema: '2.0',\n config: { width_mode: 'fill' },\n body: { elements },\n }\n}\n\n/**\n * Largest body prefix (char count) whose card renders within `CARD_TARGET_BYTES`\n * minus `CARD_PANEL_RESERVE_BYTES`, reserving the panel's whole footprint for\n * the reasoning that travels with the segment. `segmentCardContent` only ever\n * segments bodies, so reasoning is never a segment budget input by construction\n * — but the panel still occupies the card, so its cap is held back. The probe renders\n * at phase `error`, the worst case (its segment header title is the longest),\n * so a frozen segment can never outgrow the budget merely by the phase flipping\n * 回复中 → 回复失败; stats growth between the split and the final patch consumes\n * the 2 KB redundancy (`CARD_MAX_BYTES` − `CARD_TARGET_BYTES`).\n */\nfunction largestBodyPrefix(state: CardState, options: CardOptions): number {\n const text = state.body\n const budget = CARD_TARGET_BYTES - CARD_PANEL_RESERVE_BYTES\n // `panel: false` keeps the reserved panel cap out of the measurement itself.\n const render = (content: string) =>\n assemble('error', '', content, state.stats, { ...options, panel: false }, state.status, state.tool)\n if (cardJsonBytes(render(text)) <= budget) return text.length\n let lo = 0\n let hi = text.length\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n if (cardJsonBytes(render(text.slice(0, mid))) <= budget) {\n lo = mid\n } else {\n hi = mid - 1\n }\n }\n return lo\n}\n\n/**\n * Split a body at a clean breakpoint so `head` fits one card:\n * 1. the head holds at most `maxTables` whole markdown tables — the boundary\n * is capped at the first excess table (Feishu rejects cards with more);\n * 2. never inside a fenced code block (pull the boundary back before the fence);\n * 3. never inside a table block (a breakpoint that would land inside one is\n * moved to its start);\n * 4. then the clean-breakpoint tiers: paragraph blank line → `#` heading line\n * → line break → hard cut as the escape hatch.\n */\nexport function splitBodyForCard(body: string, maxChars: number, maxTables: number = MAX_TABLES_PER_CARD): BodySplit {\n const tables = tableBlocks(body)\n if (body.length <= maxChars && tables.length <= maxTables) {\n return { head: body, tail: '' }\n }\n\n let limit = maxChars\n\n // 1. Table-count ceiling: the head may hold at most `maxTables` tables, so\n // never extend past the boundary right before the first excess table.\n const firstExcess = tables[maxTables]\n if (firstExcess !== undefined) {\n limit = Math.min(limit, firstExcess.start)\n }\n\n // 2. Never cut inside a fenced code block straddling the ceiling.\n const fencePattern = /^[ \\t]*```[^\\n]*$/gm\n let fenceStart = -1\n let inside = false\n for (const match of body.matchAll(fencePattern)) {\n const index = match.index ?? 0\n if (!inside) {\n if (index >= limit) break\n inside = true\n fenceStart = index\n } else if (index >= limit) {\n // The block opened before the ceiling and closes after it: move the\n // boundary to just before the opening fence (unless it starts at 0).\n if (fenceStart > 0) limit = fenceStart\n break\n } else {\n inside = false\n }\n }\n if (inside && fenceStart > 0 && fenceStart < limit) limit = fenceStart\n\n // 3. Never cut inside a table block straddling the ceiling (pull the\n // boundary back to its start; a table beginning the body is left to the\n // hard-cut escape hatch).\n for (const block of tables) {\n if (block.start >= limit) break\n if (block.end > limit) {\n if (block.start > 0) limit = block.start\n break\n }\n }\n\n /** Move a cut position out of any table block (to just before it). */\n const avoidTables = (at: number): number => {\n for (const block of tables) {\n if (block.start >= at) break\n if (at <= block.end && block.start > 0) return block.start\n }\n return at\n }\n\n const window = body.slice(0, limit)\n const cut = (at: number): BodySplit => ({\n head: body.slice(0, at).replace(/\\s+$/u, ''),\n tail: body.slice(at).replace(/^\\n+/u, ''),\n })\n\n // Preferred breakpoints scanning back from the ceiling; only accept one that\n // keeps the card reasonably full, else fall through to the next tier.\n const minFill = Math.floor(limit * 0.7)\n\n const paragraph = window.lastIndexOf('\\n\\n')\n if (paragraph >= minFill) return cut(avoidTables(paragraph + 2))\n\n let heading = -1\n for (const match of window.matchAll(/^#{1,6} .*$/gm)) {\n const start = match.index ?? 0\n heading = start === 0 ? 0 : start - 1\n }\n if (heading >= minFill) return cut(avoidTables(heading))\n\n const line = window.lastIndexOf('\\n')\n if (line >= minFill) return cut(avoidTables(line + 1))\n\n return { head: body.slice(0, avoidTables(limit)), tail: body.slice(avoidTables(limit)) }\n}\n\n/**\n * How the accumulated body should be laid out for one card: everything that\n * fits (clean breakpoint) plus the remainder for the next segment card.\n * `head` re-renders within `CARD_TARGET_BYTES`; the body is never dropped.\n */\nexport function segmentCardContent(state: CardState, options: CardOptions = {}): BodySplit {\n return splitBodyForCard(state.body, largestBodyPrefix(state, options))\n}\n\n/**\n * The initial (empty) streaming card for one session: thinking phase, empty\n * body and reasoning, zeroed stats. The service sends it as the topic-creating\n * first reply and then streams into the same message.\n */\nexport function initialStreamingCard(sessionId: string): object {\n const state: CardState = {\n phase: 'thinking',\n reasoning: '',\n body: '',\n stats: { sessionId, inputTokens: 0, outputTokens: 0 },\n }\n return buildCard(state)\n}\n\n/**\n * One-time onboarding card: what the bot does, how workspaces are scoped, and\n * the command cheat sheet. Sent on the first private message of a chat.\n */\nexport function buildOnboardingCard(): object {\n return {\n schema: '2.0',\n config: { width_mode: 'fill' },\n body: {\n elements: [\n { tag: 'markdown', content: '**👋 欢迎使用飞书 DSH**' },\n {\n tag: 'markdown',\n content: '直接发消息即可开始一个会话,我会在话题里流式回复;话题内继续发消息无需再 @ 我。',\n },\n {\n tag: 'markdown',\n content: '**工作区**:私聊一人一个;群里每位成员各自一个,互不影响。',\n },\n {\n tag: 'markdown',\n content: '**常用命令**:`/help` 命令列表 · `/status` 会话状态 · `/workspace` 工作目录\\n'\n + '`/effort` 推理强度 · `/model` 模型 · `/permissions` 权限预设\\n'\n + '`/new` 新建会话 · `/stop` 中断当前回合 · `/history` 历史 · `/export` 导出日志',\n },\n {\n tag: 'markdown',\n content: '需要授权或补充信息时,我会发一张卡片,点按钮或直接回复即可。',\n text_size: 'notation',\n },\n ],\n },\n }\n}\n","/**\n * The channel's slash-command registry: one declarative source for the\n * command vocabulary, admission rules, `/help` rendering, and dispatch.\n *\n * Adding a command means adding one entry here (plus its handler): the\n * supported-name list, the help text, and the admission gate all derive from\n * this table.\n *\n * @module dsh-channels/commands/registry\n */\n\n/** Help grouping, in render order. */\nexport const COMMAND_GROUPS = ['基础', '会话', '模型与上下文', '导出'] as const\nexport type CommandGroup = typeof COMMAND_GROUPS[number]\n\nexport interface ChannelCommandSpec {\n /** Canonical command name (without the leading slash). */\n readonly name: string\n readonly group: CommandGroup\n readonly description: string\n /** Argument hint shown in help, e.g. `[id|reset]`. */\n readonly usage?: string\n /** Answerable without any Session work (and inside a retired topic). */\n readonly routeFree?: boolean\n /** Answerable with or without a bound Session: bound runs on the command plane. */\n readonly sessionOptional?: boolean\n /** Unthreaded group messages require a bot mention before the command answers. */\n readonly mentionGate?: boolean\n /** Only usable in this chat type. */\n readonly chatType?: 'p2p' | 'group'\n /** Answer with a direct reply instead of creating a topic. */\n readonly directReply?: boolean\n /**\n * Answered by the channel itself (`/stop`), never through the agent command\n * plane, and never cold-resuming a Session just to answer.\n */\n readonly channelPlane?: boolean\n /**\n * Registered inside each Agent scope for the agent command plane. Defaults\n * to true; `false` for route-free commands, channel-plane commands, and the\n * native host `/compact`.\n */\n readonly agentScoped?: boolean\n}\n\n/** The complete command vocabulary. */\nexport const CHANNEL_COMMANDS = [\n {\n name: 'help', group: '基础', description: '显示当前支持的斜杠命令',\n routeFree: true, directReply: false,\n },\n {\n name: 'userid', group: '基础', description: '在私聊中显示当前用户 ID',\n routeFree: true, mentionGate: true, chatType: 'p2p', directReply: true, agentScoped: false,\n },\n {\n name: 'groupid', group: '基础', description: '在群聊中显示当前群组 ID',\n routeFree: true, mentionGate: true, chatType: 'group', directReply: true, agentScoped: false,\n },\n {\n name: 'health', group: '基础', description: '显示 Harness 与飞书通道健康状态',\n sessionOptional: true,\n },\n {\n name: 'status', group: '会话', description: '显示当前 Session 状态与性能指标',\n },\n {\n name: 'workspace', group: '会话', description: '显示当前 Session 工作目录',\n },\n {\n name: 'permissions', group: '会话', description: '查看(或按配置切换)当前 Session 权限预设',\n usage: '[name]', channelPlane: true, agentScoped: false,\n },\n {\n name: 'auth', group: '会话', description: '查看/管理访问授权(仅管理员)',\n usage: '[status|allow|revoke|list|user add <open_id>|user remove <open_id>]',\n routeFree: true, channelPlane: true, agentScoped: false,\n },\n {\n name: 'renew', group: '会话', description: '在当前话题创建并绑定新 Session',\n },\n {\n name: 'stop', group: '会话', description: '停止当前正在运行的回合',\n channelPlane: true, agentScoped: false,\n },\n {\n name: 'effort', group: '模型与上下文', description: '查看或切换当前 Session 推理强度',\n usage: '[id|reset]',\n },\n {\n name: 'model', group: '模型与上下文', description: '查看或切换当前 Session 模型',\n usage: '[id|provider/model|reset]',\n },\n {\n name: 'compact', group: '模型与上下文', description: '压缩当前 Session 上下文', agentScoped: false,\n },\n {\n name: 'history', group: '导出', description: '显示当前话题的 Session 历史',\n },\n {\n name: 'export', group: '导出', description: '导出当前 Session 的逻辑日志',\n },\n] as const satisfies readonly ChannelCommandSpec[]\n\n/** Canonical command name literal union. */\nexport type ChannelCommandName = typeof CHANNEL_COMMANDS[number]['name']\n\n/** User-facing aliases, resolved to their canonical command. */\nexport const COMMAND_ALIASES = { new: 'renew' } as const\nexport type ChannelCommandAlias = keyof typeof COMMAND_ALIASES\n\n/** Any command name a user may type. */\nexport type SupportedCommandName = ChannelCommandName | ChannelCommandAlias\n\nconst BY_NAME = new Map<string, ChannelCommandSpec>(\n CHANNEL_COMMANDS.map(spec => [spec.name, spec as ChannelCommandSpec]),\n)\n\n/** Resolve one typed name (canonical or alias) to its command spec. */\nexport function commandSpec(name: string): ChannelCommandSpec | undefined {\n const canonical = (COMMAND_ALIASES as Record<string, string>)[name] ?? name\n return BY_NAME.get(canonical)\n}\n\n/** Canonical command names, in registry order. */\nexport function commandNames(): readonly ChannelCommandName[] {\n return CHANNEL_COMMANDS.map(spec => spec.name)\n}\n\n/** Every typeable name (canonical + aliases), for the supported-name list. */\nexport function supportedCommandNames(): readonly SupportedCommandName[] {\n return [...commandNames(), ...Object.keys(COMMAND_ALIASES)] as SupportedCommandName[]\n}\n\n/** `用法:/effort [id|reset]` line, or undefined without a usage hint. */\nexport function commandUsage(spec: ChannelCommandSpec): string | undefined {\n if (spec.usage === undefined) return undefined\n return `/${spec.name} ${spec.usage}`\n}\n\n/** Grouped `/help` markdown, derived from the registry. */\nexport function groupedCommandHelp(): string {\n const sections: string[] = []\n const specs: readonly ChannelCommandSpec[] = CHANNEL_COMMANDS\n for (const group of COMMAND_GROUPS) {\n const lines = specs\n .filter(spec => spec.group === group)\n .map(spec => {\n const usage = commandUsage(spec)\n return `**/${spec.name}**${usage === undefined ? '' : ` ${spec.usage}`} — ${spec.description}`\n })\n if (lines.length > 0) sections.push(`**${group}**\\n${lines.join('\\n')}`)\n }\n return sections.join('\\n\\n')\n}\n","/**\n * Feishu slash-command admission primitives.\n *\n * This module deliberately contains no channel or Host I/O. It owns the\n * product command vocabulary, exact parsing, agent-scoped registrations,\n * per-route admission serialization, and standalone result-card rendering.\n * Command-specific folds and side effects are added by the later SC tasks.\n *\n * @module dsh-channels/commands\n */\n\nimport type {\n AgentContext, CommandDefinition, CommandDescriptor, CommandResult, CommandRuntime,\n} from './dsh-types.ts'\nimport { card } from './cards/primitives.ts'\n\nimport {\n CHANNEL_COMMANDS, commandNames, groupedCommandHelp, commandSpec, supportedCommandNames,\n} from './commands/registry.ts'\nimport type { ChannelCommandSpec, SupportedCommandName } from './commands/registry.ts'\n\n/** Canonical channel command names, in registry order (agent-scoped ones included). */\nexport const CHANNEL_COMMAND_NAMES = commandNames()\n\n/** Every typeable command name (canonical + aliases). */\nexport const SUPPORTED_COMMAND_NAMES = supportedCommandNames()\n\nexport type { SupportedCommandName }\n\nexport interface ParsedSlashCommand {\n readonly name: string\n /** Exact text following the command name, including separator whitespace. */\n readonly rawInput: string\n}\n\n/** Match Harness' lowercase command grammar without normalizing arguments. */\nexport function parseSlashCommand(line: string): ParsedSlashCommand | undefined {\n const match = /^\\/([a-z][a-z0-9_-]*)(?=$|[\\t\\n\\r ])/u.exec(line)\n if (match === null) return undefined\n const name = match[1]\n if (name === undefined) return undefined\n return { name, rawInput: line.slice(match[0].length) }\n}\n\nexport function isSupportedCommand(name: string): name is SupportedCommandName {\n return commandSpec(name) !== undefined\n}\n\n/** Stable display used by unknown-command and deployment diagnostics. */\nexport function supportedCommandList(): string {\n return SUPPORTED_COMMAND_NAMES.map(name => `/${name}`).join('、')\n}\n\n/** Markdown help, grouped and generated from the command registry. */\nexport function supportedCommandHelp(): string {\n return groupedCommandHelp()\n}\n\n/** Commands registered inside each Agent scope (route-free ones are excluded). */\nconst ALL_COMMANDS: readonly ChannelCommandSpec[] = CHANNEL_COMMANDS\nconst AGENT_SCOPED_COMMANDS = ALL_COMMANDS.filter(spec => spec.agentScoped !== false)\n\nconst DESCRIPTORS: ReadonlyArray<CommandDescriptor> = AGENT_SCOPED_COMMANDS.map(spec => ({\n name: spec.name,\n description: spec.description,\n ...(spec.usage === undefined ? {} : { input: { hint: spec.usage } }),\n}))\n\nfunction pendingHandler(name: string): CommandDefinition['handler'] {\n return ({ rawInput }): CommandResult => {\n if (rawInput.trim() !== '') {\n return { kind: 'error', text: `用法错误:/${name} 不接受参数。` }\n }\n return { kind: 'error', text: `命令 /${name} 的功能将在后续实施任务中启用。` }\n }\n}\n\nfunction readyHandler(name: string, text: string): CommandDefinition['handler'] {\n return ({ rawInput }): CommandResult => {\n if (rawInput.trim() !== '') {\n return { kind: 'error', text: `用法错误:/${name} 不接受参数。` }\n }\n return { kind: 'success', text }\n }\n}\n\nconst healthHandler: CommandDefinition['handler'] = ({ rawInput }): CommandResult => {\n if (rawInput.trim() !== '') {\n return { kind: 'error', text: '用法错误:/health 不接受参数。' }\n }\n return { kind: 'success', text: '健康检查已完成。' }\n}\n\ntype RenewOperation = (invocation: Parameters<CommandDefinition['handler']>[0]) =>\n ReturnType<CommandDefinition['handler']>\n\nexport type EffortCommandAction =\n | { readonly kind: 'show' }\n | { readonly kind: 'set'; readonly value: string }\n | { readonly kind: 'reset' }\n\ntype EffortOperation = (\n invocation: Parameters<CommandDefinition['handler']>[0],\n action: EffortCommandAction,\n) => ReturnType<CommandDefinition['handler']>\n\n/** One armed `/model` execution: lists, validates, and switches models. */\nexport type ModelOperation = (\n invocation: Parameters<CommandDefinition['handler']>[0],\n rawInput: string,\n) => ReturnType<CommandDefinition['handler']>\n\n/** Per-execution authority: handlers stay instance-neutral when an Agent is borrowed. */\nconst RENEW_OPERATIONS = new WeakMap<AbortSignal, RenewOperation>()\nconst EFFORT_OPERATIONS = new WeakMap<AbortSignal, EffortOperation>()\nconst MODEL_OPERATIONS = new WeakMap<AbortSignal, ModelOperation>()\n\nfunction parseEffortAction(rawInput: string): EffortCommandAction | undefined {\n const value = rawInput.trim()\n if (value === '') return { kind: 'show' }\n if (/\\s/u.test(value)) return undefined\n return value === 'reset' ? { kind: 'reset' } : { kind: 'set', value }\n}\n\nconst effortHandler: CommandDefinition['handler'] = invocation => {\n const action = parseEffortAction(invocation.rawInput)\n if (action === undefined) {\n return { kind: 'error', text: '用法错误:/effort [档位-id|reset]。' }\n }\n if (action.kind !== 'show' && (\n invocation.agent.status !== 'idle'\n || invocation.agent.inbox.nextTurn.length > 0\n || invocation.agent.inbox.nextStep.length > 0\n )) {\n return { kind: 'error', text: '当前 Session 正忙或仍有排队消息,暂时不能切换推理强度。' }\n }\n const operation = EFFORT_OPERATIONS.get(invocation.signal)\n if (operation === undefined) {\n return { kind: 'error', text: '/effort 只能通过已授权的飞书话题执行。' }\n }\n return operation(invocation, action)\n}\n\nconst modelHandler: CommandDefinition['handler'] = invocation => {\n const rawInput = invocation.rawInput.trim()\n if (/\\s/u.test(rawInput)) {\n return { kind: 'error', text: '用法错误:/model [模型-id|provider/model|reset]。' }\n }\n if (invocation.agent.status !== 'idle'\n || invocation.agent.inbox.nextTurn.length > 0\n || invocation.agent.inbox.nextStep.length > 0) {\n return { kind: 'error', text: '当前 Session 正忙或仍有排队消息,暂时不能切换模型。' }\n }\n const operation = MODEL_OPERATIONS.get(invocation.signal)\n if (operation === undefined) {\n return { kind: 'error', text: '/model 只能通过已授权的飞书话题执行。' }\n }\n return operation(invocation, rawInput)\n}\n\nconst renewHandler: CommandDefinition['handler'] = invocation => {\n if (invocation.rawInput.trim() !== '') {\n return { kind: 'error', text: '用法错误:/renew 不接受参数。' }\n }\n if (invocation.agent.status !== 'idle'\n || invocation.agent.inbox.nextTurn.length > 0\n || invocation.agent.inbox.nextStep.length > 0) {\n return { kind: 'error', text: '当前 Session 正忙或仍有排队消息,暂时不能续建。' }\n }\n const operation = RENEW_OPERATIONS.get(invocation.signal)\n if (operation === undefined) {\n return { kind: 'error', text: '/renew 只能通过已授权的飞书话题执行。' }\n }\n return operation(invocation)\n}\n\nconst READY_HANDLERS: Readonly<Record<string, CommandDefinition['handler']>> = {\n help: readyHandler('help', supportedCommandHelp()),\n health: healthHandler,\n status: readyHandler('status', 'Session 状态已读取。'),\n workspace: readyHandler('workspace', '工作目录已读取。'),\n effort: effortHandler,\n model: modelHandler,\n renew: renewHandler,\n history: readyHandler('history', 'Session 历史已读取。'),\n export: readyHandler('export', '导出已请求。'),\n}\n\n/** Arm exactly one Feishu-issued `/renew` execution, then return its disarmer. */\nexport function armRenewCommand(signal: AbortSignal, operation: RenewOperation): () => void {\n RENEW_OPERATIONS.set(signal, operation)\n return () => { RENEW_OPERATIONS.delete(signal) }\n}\n\n/** Arm exactly one Feishu-issued `/effort` execution, then return its disarmer. */\nexport function armEffortCommand(signal: AbortSignal, operation: EffortOperation): () => void {\n EFFORT_OPERATIONS.set(signal, operation)\n return () => { EFFORT_OPERATIONS.delete(signal) }\n}\n\n/** Arm exactly one Feishu-issued `/model` execution, then return its disarmer. */\nexport function armModelCommand(signal: AbortSignal, operation: ModelOperation): () => void {\n MODEL_OPERATIONS.set(signal, operation)\n return () => { MODEL_OPERATIONS.delete(signal) }\n}\n\n/** Install only `/model` into a borrowed Agent scope that lacks channel commands. */\nexport function registerModelCommand(commands: CommandRuntime): () => void {\n const descriptor = DESCRIPTORS.find(entry => entry.name === 'model')!\n return commands.register({ ...descriptor, handler: modelHandler })\n}\n\n/** Install only `/renew` into a borrowed Agent scope that lacks channel commands. */\nexport function registerRenewCommand(commands: CommandRuntime): () => void {\n const descriptor = DESCRIPTORS.find(entry => entry.name === 'renew')!\n return commands.register({ ...descriptor, handler: renewHandler })\n}\n\n/** Install only `/effort` into a borrowed Agent scope that lacks channel commands. */\nexport function registerEffortCommand(commands: CommandRuntime): () => void {\n const descriptor = DESCRIPTORS.find(entry => entry.name === 'effort')!\n return commands.register({ ...descriptor, handler: effortHandler })\n}\n\n/**\n * Register this channel's commands inside one agent scope. `/compact` stays\n * global and is intentionally not registered here.\n */\nexport function registerChannelCommands(commands: CommandRuntime): void {\n for (const descriptor of DESCRIPTORS) {\n commands.register({\n ...descriptor,\n handler: READY_HANDLERS[descriptor.name] ?? pendingHandler(descriptor.name),\n })\n }\n}\n\nfunction registerScopedChannelCommands(ctx: AgentContext): void {\n registerChannelCommands(ctx.commands)\n}\n\n/**\n * Mount channel commands through an Agent child plugin that explicitly\n * injects the command service. The Agent scope is minted by `agent-loop`, so\n * it does not inherit this package's top-level dependency declaration.\n */\nexport async function mountChannelCommands(agentCtx: AgentContext): Promise<void> {\n await agentCtx.inject(['commands'], registerScopedChannelCommands)\n}\n\n/**\n * FIFO admission queue keyed by the durable route key. Failures never poison\n * the tail, and an idle key is retired so the map remains bounded.\n */\nexport class RouteOperationQueue {\n private readonly tails = new Map<string, Promise<void>>()\n\n async run<T>(key: string, operation: () => Promise<T> | T): Promise<T> {\n const previous = this.tails.get(key) ?? Promise.resolve()\n const result = previous.catch(() => {}).then(operation)\n const tail = result.then(() => {}, () => {})\n this.tails.set(key, tail)\n try {\n return await result\n } finally {\n if (this.tails.get(key) === tail) this.tails.delete(key)\n }\n }\n\n clear(): void {\n this.tails.clear()\n }\n}\n\nexport type CommandCardKind =\n | 'success'\n | 'help'\n | 'context-error'\n | 'input-error'\n | 'unknown'\n | 'unbound'\n | 'expired'\n | 'forbidden'\n | 'busy'\n | 'unavailable'\n | 'internal-error'\n\nexport interface CommandCardState {\n readonly kind: CommandCardKind\n readonly command?: string\n readonly text: string\n readonly supported?: string\n readonly sessionId?: string\n}\n\n/** Map stable command outcomes onto the shared result-card vocabulary. */\nexport function commandResultCardKind(result: CommandResult): CommandCardKind {\n if (result.kind === 'success') return 'success'\n if (/^(?:用法错误:|Usage:)/u.test(result.text)) return 'input-error'\n if (/\\b(?:active compaction|not idle|busy)\\b|正忙|正在运行/iu.test(result.text)) return 'busy'\n return 'unavailable'\n}\n\nconst CARD_TITLES: Record<CommandCardKind, string> = {\n success: '✅ 命令执行完成',\n help: '📖 斜杠命令帮助',\n 'context-error': '⚠️ 使用场景不匹配',\n 'input-error': '⚠️ 命令格式不正确',\n unknown: '❓ 未知命令',\n unbound: '🔗 尚未绑定会话',\n expired: '⌛ 话题已过期',\n forbidden: '🔒 无权操作此会话',\n busy: '⏳ 会话正忙',\n unavailable: '🧩 命令暂不可用',\n 'internal-error': '❌ 命令执行失败',\n}\n\n/** Render a small, non-streaming Feishu card that never contains raw errors. */\nexport function buildCommandResultCard(state: CommandCardState): object {\n const elements: object[] = [\n { tag: 'markdown', content: `**${CARD_TITLES[state.kind]}**` },\n { tag: 'markdown', content: state.text },\n ]\n if (state.supported !== undefined) {\n elements.push({ tag: 'markdown', content: `可用命令:${state.supported}`, text_size: 'notation' })\n }\n if (state.command !== undefined || state.sessionId !== undefined) {\n const trace = [\n state.command === undefined ? undefined : `/${state.command}`,\n state.sessionId,\n ].filter((value): value is string => value !== undefined).join(' · ')\n elements.push({ tag: 'markdown', content: trace, text_size: 'notation' })\n }\n return card(elements)\n}\n","/**\n * Durable routing domain: thread/chat keys → bound DSH session records.\n *\n * Opened through the host's `ctx.storageDomain` facility; the spec is plain\n * data (no runtime import of DSH packages), so the plugin carries zero\n * runtime dependency on host internals — everything flows through `ctx`.\n *\n * @module dsh-channels/domain\n */\n\nimport { z } from 'zod'\n\n/** Route key kinds. `thread` = real Feishu topic; `chat` = degraded chat-scoped fallback. */\nexport const ROUTE_RECORD_VERSION = 1\n\nexport const sessionHistoryEntrySchema = z.object({\n /** One Session that has been bound to this route. */\n sessionId: z.string().min(1),\n /** Epoch ms when the Session was created. */\n createdAt: z.number().int().nonnegative(),\n})\n\nexport type SessionHistoryEntry = z.infer<typeof sessionHistoryEntrySchema>\n\nexport const routeRecordSchema = z.object({\n /** Route key the record is stored under (thread_id or chat:<chatId>). */\n key: z.string().min(1),\n /** Kind of the key. */\n kind: z.enum(['thread', 'chat']),\n /** Bound DSH session id. */\n sessionId: z.string().min(1),\n /** Feishu chat the session lives in. */\n chatId: z.string().min(1),\n /** `p2p` or `group`. */\n chatType: z.enum(['p2p', 'group']),\n /** Workspace owner (sender open_id). */\n senderOpenId: z.string().min(1),\n /** Workspace directory for this (chat, user) pair. */\n workspacePath: z.string().min(1),\n /** Message id of the latest streaming card (updated per turn). */\n cardMessageId: z.string().min(1),\n /** Epoch ms when the record was created. */\n createdAt: z.number().int().nonnegative(),\n /** Epoch ms of the last activity routed through this record. */\n lastActivityAt: z.number().int().nonnegative(),\n /** Sessions this topic has pointed at, oldest first. Optional for v1 compatibility. */\n sessionHistory: z.array(sessionHistoryEntrySchema).optional(),\n})\n\nexport type RouteRecord = z.infer<typeof routeRecordSchema>\n\n/**\n * Minimal persistent tombstone for a thread route retired by route TTL.\n *\n * Deleting the active route alone would make a later message look like a new\n * conversation and silently create another Session in the same Feishu topic.\n * The tombstone preserves only enough identity to reject that message across\n * process restarts; the old Session and workspace remain host-owned.\n */\nexport const expiredRouteRecordSchema = z.object({\n /** Expired Feishu thread id (also the table key). */\n key: z.string().min(1),\n /** Feishu chat containing the expired topic. */\n chatId: z.string().min(1),\n /** Original chat type, retained for diagnostics and future policy. */\n chatType: z.enum(['p2p', 'group']),\n /** Original route owner; never used to grant access after expiry. */\n senderOpenId: z.string().min(1),\n /** Epoch ms when the route was retired. */\n expiredAt: z.number().int().nonnegative(),\n /** Workspace the retired route belonged to; absent in tombstones written by\n * older plugin versions, so the field must stay optional. */\n workspacePath: z.string().min(1).optional(),\n /** Epoch ms of the last activity through the route before retirement.\n * Optional for the same legacy-compatibility reason; consumers fall back to\n * `expiredAt` when it is absent. */\n lastActivityAt: z.number().int().nonnegative().optional(),\n})\n\nexport type ExpiredRouteRecord = z.infer<typeof expiredRouteRecordSchema>\n\n/**\n * Normalize legacy and partially duplicated histories without mutating the\n * durable record. The current Session is always represented, duplicate ids\n * collapse to their earliest timestamp, and output order is deterministic.\n */\nexport function normalizeSessionHistory(record: RouteRecord): SessionHistoryEntry[] {\n const bySession = new Map<string, SessionHistoryEntry>()\n for (const entry of record.sessionHistory ?? []) {\n const existing = bySession.get(entry.sessionId)\n if (existing === undefined || entry.createdAt < existing.createdAt) {\n bySession.set(entry.sessionId, { ...entry })\n }\n }\n if (!bySession.has(record.sessionId)) {\n bySession.set(record.sessionId, { sessionId: record.sessionId, createdAt: record.createdAt })\n }\n return [...bySession.values()].sort((left, right) => left.createdAt - right.createdAt)\n}\n\n/** Materialize the optional legacy history field for in-memory consumers. */\nexport function normalizeRouteRecord(record: RouteRecord): RouteRecord {\n return { ...record, sessionHistory: normalizeSessionHistory(record) }\n}\n\n/** Append a newly created Session to one normalized route history. */\nexport function appendSessionHistory(\n record: RouteRecord,\n sessionId: string,\n createdAt: number,\n): SessionHistoryEntry[] {\n return normalizeSessionHistory({\n ...record,\n sessionId,\n sessionHistory: [...normalizeSessionHistory(record), { sessionId, createdAt }],\n })\n}\n\n/**\n * In-flight turn snapshot: everything needed to repair a streaming card that a\n * restart left mid-flight (stuck panel, leftover Typing reaction, never-patched\n * segment cards). Keyed by the turn's MAIN card message id — unique per turn\n * and known at attach time — so overlapping turns of one session never\n * overwrite each other. Written before the route record, updated as segment\n * cards resolve, deleted when the turn finalizes (or the creation fails).\n */\nexport const INFLIGHT_VERSION = 1\n\nexport const inflightCardSchema = z.object({\n /** Feishu message id of one segment card. */\n messageId: z.string().min(1),\n /** Delivery mode; `text` messages are complete and never patched by repair. */\n mode: z.enum(['card', 'text']),\n})\n\nexport type InflightCard = z.infer<typeof inflightCardSchema>\n\nexport const inflightSnapshotSchema = z.object({\n /** Key the snapshot is stored under (= `cards[0].messageId`, the main card). */\n key: z.string().min(1),\n /** DSH session the turn belongs to. */\n sessionId: z.string().min(1),\n /** Feishu chat the cards live in (reaction targeting). */\n chatId: z.string().min(1),\n /** User message that triggered the turn (Typing/DONE reaction anchor). */\n triggerMessageId: z.string().min(1),\n /** Reaction id of the Typing reaction (absent when the add failed). */\n reactionId: z.string().optional(),\n /** Segment cards in order; `[0]` is the main card. */\n cards: z.array(inflightCardSchema).min(1),\n /** Epoch ms when the turn attached. */\n startedAt: z.number().int().nonnegative(),\n /** Repair attempts spent (cap: 2 — see `repairInflight`). */\n attempts: z.number().int().nonnegative(),\n})\n\nexport type InflightSnapshot = z.infer<typeof inflightSnapshotSchema>\n\n/**\n * Access-control grants (v1 format, added without a domain version bump —\n * see the notes on `feishuDomain` below).\n *\n * One record per authorized group: the group gate is the first of the two\n * independent admission gates. Inside an authorized group only the\n * administrator and explicitly authorized users may start conversations.\n */\nexport const accessGroupRecordSchema = z.object({\n /** Feishu chat id (`oc_...`) — also the table key. */\n chatId: z.string().min(1),\n /** Human-readable group name captured at authorization time (optional). */\n name: z.string().optional(),\n /** Which authorization entry produced this grant. */\n source: z.enum(['command', 'card', 'panel']),\n /** Administrator open_id that authorized it. */\n addedBy: z.string().min(1),\n /** Epoch ms when it was authorized. */\n addedAt: z.number().int().nonnegative(),\n})\n\nexport type AccessGroupRecord = z.infer<typeof accessGroupRecordSchema>\n\n/**\n * One authorized user (`open_id`), global across every authorized group.\n * Private chats are administrator-only, so this grant only affects group\n * conversations.\n */\nexport const accessUserRecordSchema = z.object({\n /** Feishu open id (`ou_...`) — also the table key. */\n openId: z.string().min(1),\n /** Display name captured when the user was authorized (optional). */\n name: z.string().optional(),\n /** Which authorization entry produced this grant. */\n source: z.enum(['command', 'card', 'panel']),\n /** Administrator open_id that authorized it. */\n addedBy: z.string().min(1),\n /** Epoch ms when it was authorized. */\n addedAt: z.number().int().nonnegative(),\n})\n\nexport type AccessUserRecord = z.infer<typeof accessUserRecordSchema>\n\n/**\n * One authorization request the administrator has been (or should be) asked\n * about. Requests are persisted so the DM card stays decidable across plugin\n * restarts; settled records are kept as an audit trail and for request\n * de-duplication/cooldown.\n */\nexport const accessRequestRecordSchema = z.object({\n /** Opaque token carried by the card buttons — also the table key. */\n token: z.string().min(1),\n /** What is being requested. */\n kind: z.enum(['group', 'user']),\n /** `chat_id` for a group request; `open_id` for a user request. */\n targetId: z.string().min(1),\n /** Chat the request originated from, when applicable. */\n chatId: z.string().optional(),\n /** Group name captured for the card (group requests, or the group the user was seen in). */\n groupName: z.string().optional(),\n /** User who triggered a user request. */\n requesterOpenId: z.string().optional(),\n /** Display name of that user, when the event carried one. */\n requesterName: z.string().optional(),\n /** Lifecycle state; only `pending` requests accept a card action. */\n status: z.enum(['pending', 'approved', 'denied', 'expired']),\n /** Epoch ms when the request was created. */\n createdAt: z.number().int().nonnegative(),\n /** Epoch ms after which a pending request can no longer be decided. */\n expiresAt: z.number().int().nonnegative(),\n /** DM message id of the administrator card, once delivery succeeded. */\n messageId: z.string().optional(),\n})\n\nexport type AccessRequestRecord = z.infer<typeof accessRequestRecordSchema>\n\n/**\n * One processed inbound message, keyed by its Feishu message id. Feishu\n * redelivers un-acked events on WS reconnect, so every accepted message is\n * recorded here BEFORE its side effects begin; redeliveries then hit the\n * table (or the in-process guard) and are dropped. Records are pruned by the\n * GC sweep after a fixed retention window (24 h — redelivery windows are\n * minutes).\n */\nexport const PROCESSED_VERSION = 1\n\nexport const processedRecordSchema = z.object({\n /** Key the record is stored under (the Feishu message id). */\n key: z.string().min(1),\n /** Epoch ms when the message was accepted for processing. */\n processedAt: z.number().int().nonnegative(),\n})\n\nexport type ProcessedRecord = z.infer<typeof processedRecordSchema>\n\nexport interface FeishuDomainSpecShape {\n name: 'channels_routes'\n version: 1\n global: {\n schema: z.ZodType<FeishuDomainGlobal>\n initial: FeishuDomainGlobal\n }\n tables: {\n routes: {\n valueSchema: typeof routeRecordSchema\n __key?: string\n }\n inflight: {\n valueSchema: typeof inflightSnapshotSchema\n __key?: string\n }\n processed: {\n valueSchema: typeof processedRecordSchema\n __key?: string\n }\n expired_routes: {\n valueSchema: typeof expiredRouteRecordSchema\n __key?: string\n }\n access_groups: {\n valueSchema: typeof accessGroupRecordSchema\n __key?: string\n }\n access_users: {\n valueSchema: typeof accessUserRecordSchema\n __key?: string\n }\n access_requests: {\n valueSchema: typeof accessRequestRecordSchema\n __key?: string\n }\n }\n}\n\nexport const feishuDomainGlobalSchema = z.object({\n /** Legacy: img_key of a previously uploaded chevron icon. Kept optional so\n * storage written by older plugin versions still validates; the card now\n * uses the built-in `standard_icon` chevron and never writes this. */\n chevronImgKey: z.string().optional(),\n /** p2p chats already shown the one-time onboarding card (bounded list). */\n welcomedChats: z.array(z.string()).optional(),\n})\n\nexport type FeishuDomainGlobal = z.infer<typeof feishuDomainGlobalSchema>\n\n/**\n * Domain declaration for the host's storage facility. Plain data plus zod\n * schemas — deliberately not built with `defineDomain` so this module has no\n * runtime import of `@deepseek-ai/dsh-storage-domain`.\n */\nexport const feishuDomain: FeishuDomainSpecShape = {\n name: 'channels_routes',\n version: 1,\n global: {\n schema: feishuDomainGlobalSchema,\n initial: {} satisfies FeishuDomainGlobal,\n },\n tables: {\n routes: { valueSchema: routeRecordSchema },\n inflight: { valueSchema: inflightSnapshotSchema },\n processed: { valueSchema: processedRecordSchema },\n expired_routes: { valueSchema: expiredRouteRecordSchema },\n access_groups: { valueSchema: accessGroupRecordSchema },\n access_users: { valueSchema: accessUserRecordSchema },\n access_requests: { valueSchema: accessRequestRecordSchema },\n },\n}\n\nexport type FeishuDomainSpec = typeof feishuDomain\n\n/** Normalized table handle for the routes table. */\nexport interface RoutesTable {\n get(key: string): RouteRecord | undefined\n put(key: string, value: RouteRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, RouteRecord]>\n}\n\n/** Normalized table handle for the inflight table (repair snapshots). */\nexport interface InflightTable {\n get(key: string): InflightSnapshot | undefined\n put(key: string, value: InflightSnapshot): Promise<unknown> | void\n /**\n * Atomic read-modify-write on the host's write chain (mirrors `KvTable`).\n * Rejects with `missing-key` when the key is absent (e.g. the turn finalize\n * deleted the snapshot while a late segment delivery was still landing).\n */\n update(key: string, fn: (current: InflightSnapshot) => InflightSnapshot): Promise<unknown>\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, InflightSnapshot]>\n}\n\n/** Normalized table handle for the processed-message dedup table. */\nexport interface ProcessedTable {\n get(key: string): ProcessedRecord | undefined\n put(key: string, value: ProcessedRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, ProcessedRecord]>\n}\n\n/** Persistent thread tombstones used to reject post-TTL messages. */\nexport interface ExpiredRoutesTable {\n get(key: string): ExpiredRouteRecord | undefined\n put(key: string, value: ExpiredRouteRecord): Promise<unknown> | void\n entries(): IterableIterator<[string, ExpiredRouteRecord]>\n}\n\n/** Normalized table handle for authorized groups (key = `chat_id`). */\nexport interface AccessGroupsTable {\n get(key: string): AccessGroupRecord | undefined\n put(key: string, value: AccessGroupRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, AccessGroupRecord]>\n}\n\n/** Normalized table handle for authorized users (key = `open_id`). */\nexport interface AccessUsersTable {\n get(key: string): AccessUserRecord | undefined\n put(key: string, value: AccessUserRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, AccessUserRecord]>\n}\n\n/** Normalized table handle for authorization requests (key = opaque token). */\nexport interface AccessRequestsTable {\n get(key: string): AccessRequestRecord | undefined\n put(key: string, value: AccessRequestRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, AccessRequestRecord]>\n}\n\n/** Normalized domain handle. */\nexport interface FeishuDomainHandle {\n routes: RoutesTable\n inflight: InflightTable\n processed: ProcessedTable\n expiredRoutes: ExpiredRoutesTable\n accessGroups: AccessGroupsTable\n accessUsers: AccessUsersTable\n accessRequests: AccessRequestsTable\n getGlobal(): FeishuDomainGlobal\n setGlobal(value: FeishuDomainGlobal): Promise<unknown> | void\n close(): Promise<void>\n}\n\n/** The raw shape `ctx.storageDomain.open()` resolves to (cast at the seam).\n * Mirrors `KvTable` from `@deepseek-ai/dsh-storage-domain`: reads are sync,\n * writes go through `put`/`delete` (NOT `set`), and `update` provides the\n * atomic read-modify-write used by the inflight merge. */\nexport interface RawDomainHandle {\n table(name: string): {\n get(key: string): unknown\n put(key: string, value: unknown): Promise<unknown>\n update(key: string, fn: (current: unknown) => unknown): Promise<unknown>\n delete(key: string): Promise<unknown>\n entries(): IterableIterator<[string, unknown]>\n }\n global: {\n get(): unknown\n set(value: unknown): Promise<unknown>\n }\n close(): Promise<void>\n}\n\n/** Build a chat-scoped fallback key for degraded (topic-less) routing. */\nexport function chatRouteKey(chatType: 'p2p' | 'group', chatId: string, senderOpenId: string): string {\n return chatType === 'p2p'\n ? `chat:p2p:${chatId}:${senderOpenId}`\n : `chat:group:${chatId}:${senderOpenId}`\n}\n","/**\n * Stale workspace cleanup: pure planning and application policy.\n *\n * The plugin creates one host workspace per (user × chat) pair below its\n * configured `baseDir` (see `workspaces.ts`). The host never removes those\n * directories on its own, so a long-running deployment slowly accumulates\n * attachments and agent output for conversations nobody uses anymore. This\n * module decides which plugin-created workspaces are stale and applies the\n * removal while deliberately owning no I/O: every filesystem and registry\n * effect is injected through `CleanupDeps`, which keeps the policy small and\n * testable and keeps the dangerous half (recursive deletion + registry\n * unregistration) in the caller.\n *\n * Activity precedence for one workspace:\n *\n * 1. the newest `lastActivityAt` among its active routes; a workspace with a\n * live session is never offered for deletion,\n * 2. otherwise the newest tombstone that names the workspace\n * (`lastActivityAt`, falling back to `expiredAt` for tombstones written\n * before routes recorded their workspace path),\n * 3. otherwise the directory's own `mtime` (an orphan workspace).\n *\n * Tombstones written before this feature carry neither `workspacePath` nor\n * `lastActivityAt`, so they cannot be attributed to a workspace; such a\n * workspace falls through to the `mtime` rule instead. A workspace with no\n * usable activity evidence at all (missing or unreadable directory) is never\n * offered for deletion: irreversible removal requires positive evidence.\n *\n * @module dsh-channels/cleanup\n */\n\nimport path from 'node:path'\n\n/** Values the `workspaceRetention` config accepts. `off` disables cleanup. */\nexport const WORKSPACE_RETENTION_VALUES = ['off', '7d', '30d', '90d', '180d'] as const\n\n/** One workspace-retention setting. */\nexport type WorkspaceRetention = (typeof WORKSPACE_RETENTION_VALUES)[number]\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/** Days represented by every non-`off` retention value. */\nconst RETENTION_DAYS: Readonly<Record<Exclude<WorkspaceRetention, 'off'>, number>> = {\n '7d': 7,\n '30d': 30,\n '90d': 90,\n '180d': 180,\n}\n\n/**\n * Convert a retention setting to its window in milliseconds.\n *\n * @param value - configured retention value.\n * @returns the window in milliseconds, or `undefined` when cleanup is off.\n */\nexport function retentionMs(value: WorkspaceRetention): number | undefined {\n return value === 'off' ? undefined : RETENTION_DAYS[value] * DAY_MS\n}\n\n/** Title prefixes `workspaces.ts` gives plugin-created workspaces. */\nconst PLUGIN_TITLE_PREFIXES = ['飞书私聊 ·', '飞书群聊 ·'] as const\n\n/**\n * Whether `target` is strictly below `baseDir`.\n *\n * Both sides are normalized lexically (`path.resolve`), so a `..` segment or a\n * relative value cannot escape `baseDir`; the base directory itself and\n * siblings sharing a name prefix (e.g. `base-other`) are not \"inside\". The\n * check is lexical on purpose: this module owns no I/O. The caller's\n * `removeWorkspace` should still refuse symlinked escapes with a realpath\n * check before deleting.\n *\n * @param baseDir - directory the target must stay below.\n * @param target - candidate path.\n * @returns `true` when `target` is strictly inside `baseDir`.\n */\nexport function isPathInside(baseDir: string, target: string): boolean {\n const base = path.resolve(baseDir)\n const resolved = path.resolve(target)\n if (base === resolved) return false\n const relative = path.relative(base, resolved)\n if (relative === '' || path.isAbsolute(relative)) return false\n return relative !== '..' && !relative.startsWith(`..${path.sep}`)\n}\n\n/** Filesystem facts the caller measures for one workspace candidate. */\nexport interface WorkspaceStats {\n /** Directory mtime in epoch ms — the activity fallback for orphan workspaces. */\n mtimeMs: number\n /** Total regular-file bytes below the directory. */\n bytes: number\n /** Regular-file count below the directory. */\n files: number\n}\n\n/** Minimal workspace shape passed around by plan/apply (structurally `Workspace`). */\nexport interface CleanupWorkspace {\n /** Host workspace-registry id (needed to unregister the record). */\n readonly id: string\n /** Workspace directory. */\n readonly path: string\n /** Host display title; plugin-created workspaces use the `飞书…` prefixes. */\n readonly title: string\n}\n\n/** One active route record, reduced to the facts cleanup needs. */\nexport interface CleanupRoute {\n /** Route key (thread_id or `chat:<chatId>`). */\n readonly key: string\n /** `thread` for a real Feishu topic, `chat` for a degraded chat route. */\n readonly kind: 'thread' | 'chat'\n /** Workspace directory this route's session works in. */\n readonly workspacePath: string\n /** DSH session the route points at. */\n readonly sessionId: string\n /** Epoch ms of the last activity routed through the record. */\n readonly lastActivityAt: number\n}\n\n/**\n * One retired-route tombstone.\n *\n * Older plugin versions stored only route identity, so `workspacePath` and\n * `lastActivityAt` stay optional and legacy tombstones remain parseable.\n */\nexport interface CleanupTombstone {\n /** Route key the tombstone replaced. */\n readonly key: string\n /** Workspace the retired route belonged to; absent in legacy tombstones. */\n readonly workspacePath?: string\n /** Epoch ms when the route was retired. */\n readonly expiredAt: number\n /** Epoch ms of the last activity before retirement; absent in legacy tombstones. */\n readonly lastActivityAt?: number\n}\n\n/** A dependency seam that may be sync (registry tables) or async (filesystem). */\nexport type MaybePromise<T> = T | Promise<T>\n\n/**\n * Every effect cleanup needs, injected so this module stays I/O-free.\n *\n * The service supplies the real implementations: the workspace registry for\n * `listWorkspaces`/`removeWorkspace`, the routing tables for\n * `activeRoutes`/`tombstones`, the live-agent set for `isLive`, and a\n * filesystem walk for `statWorkspace`.\n */\nexport interface CleanupDeps {\n /** Workspace root the plugin owns; candidates must be strictly inside it. */\n readonly baseDir: string\n /** Retention value from config (`off` disables planning). */\n readonly retention: WorkspaceRetention\n /** Every workspace currently registered with the host. */\n listWorkspaces(): MaybePromise<readonly CleanupWorkspace[]>\n /** Every active route record. */\n activeRoutes(): MaybePromise<readonly CleanupRoute[]>\n /** Every retired-route tombstone. */\n tombstones(): MaybePromise<readonly CleanupTombstone[]>\n /** Whether a DSH session is still live; live workspaces are never offered. */\n isLive(sessionId: string): boolean\n /** Measure one directory; `undefined` when it cannot be read (missing, permission). */\n statWorkspace(workspacePath: string): MaybePromise<WorkspaceStats | undefined>\n /** Delete one workspace directory and unregister its registry record. */\n removeWorkspace(workspace: CleanupWorkspace): MaybePromise<void>\n /** Retire one active route into a tombstone (same bookkeeping as route TTL). */\n retireRoute(route: CleanupRoute): MaybePromise<void>\n /** Clock seam; defaults to `Date.now()`. */\n now?(): number\n /** Warning seam for best-effort failures; defaults to silence. */\n warn?(message: string): void\n}\n\n/** One workspace old enough to delete, with its measured footprint. */\nexport interface CleanupCandidate {\n /** Workspace to delete. */\n readonly workspace: CleanupWorkspace\n /** Newest activity observed for the workspace (epoch ms). */\n readonly activityAt: number\n /** Regular-file bytes below the directory (0 when unreadable). */\n readonly bytes: number\n /** Regular-file count below the directory (0 when unreadable). */\n readonly files: number\n /** Active routes still pointing at the workspace; retired before removal. */\n readonly routes: readonly CleanupRoute[]\n}\n\n/** Result of one dry-run planning pass. */\nexport interface CleanupPlan {\n /** Retention window used for this plan, in milliseconds. */\n readonly retentionMs: number\n /** Activity cutoff (epoch ms); workspaces at or after it were kept. */\n readonly cutoff: number\n /** Eligible workspaces, oldest activity first. */\n readonly candidates: readonly CleanupCandidate[]\n /** Plugin workspaces kept because a live session still uses them. */\n readonly skippedLive: number\n}\n\n/** One failed removal; the pass continues with the remaining candidates. */\nexport interface CleanupFailure {\n /** Workspace directory the failure belongs to. */\n readonly path: string\n /** Human-readable reason. */\n readonly message: string\n}\n\n/** Result of one apply pass. */\nexport interface CleanupOutcome {\n /** Workspaces whose directory was removed and registry record deleted. */\n readonly deletedWorkspaces: number\n /** Active routes retired before their workspace was removed. */\n readonly retiredRoutes: number\n /** Measured bytes of the deleted workspaces. */\n readonly freedBytes: number\n /** Per-workspace failures; other candidates were still processed. */\n readonly failures: readonly CleanupFailure[]\n}\n\n/** Whether a host workspace title marks the workspace as plugin-created. */\nfunction pluginWorkspaceTitle(title: string): boolean {\n return PLUGIN_TITLE_PREFIXES.some(prefix => title.startsWith(prefix))\n}\n\n/** Lexical path equality (both sides resolved the same way as `isPathInside`). */\nfunction samePath(left: string, right: string): boolean {\n return path.resolve(left) === path.resolve(right)\n}\n\n/** Newest finite timestamp of a list, or `undefined` when none is usable. */\nfunction newestActivity(values: readonly number[]): number | undefined {\n let newest: number | undefined\n for (const value of values) {\n if (!Number.isFinite(value)) continue\n if (newest === undefined || value > newest) newest = value\n }\n return newest\n}\n\n/** Count/byte values never go negative or non-finite on the outcome path. */\nfunction nonNegative(value: number): number {\n return Number.isFinite(value) && value > 0 ? value : 0\n}\n\n/** Best-effort error text (never a stack trace). */\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** A filesystem root must never become the cleanup base directory. */\nfunction isFilesystemRoot(dir: string): boolean {\n return path.parse(dir).root === dir\n}\n\n/** Liveness probe that fails safe: an unknowable session counts as live. */\nfunction sessionIsLive(deps: CleanupDeps, sessionId: string): boolean {\n try {\n return deps.isLive(sessionId)\n } catch (error) {\n deps.warn?.(`dsh-channels: workspace cleanup liveness check failed for ${sessionId}: ${errorMessage(error)}`)\n return true\n }\n}\n\n/** Measure one directory, tolerating a throwing or absent implementation. */\nasync function readWorkspaceStats(\n deps: CleanupDeps,\n workspacePath: string,\n): Promise<WorkspaceStats | undefined> {\n try {\n return await deps.statWorkspace(workspacePath)\n } catch (error) {\n deps.warn?.(`dsh-channels: workspace stats unavailable for ${workspacePath}: ${errorMessage(error)}`)\n return undefined\n }\n}\n\n/**\n * Build the dry-run cleanup plan for the configured retention window.\n *\n * Only plugin-created workspaces (`飞书私聊 ·` / `飞书群聊 ·` titles) strictly\n * below `baseDir` are considered. A workspace is a candidate when its newest\n * activity is older than the cutoff; its footprint is measured for the panel\n * preview and for `freedBytes` reporting.\n *\n * @param deps - injected effect seams (nothing is deleted here).\n * @returns the plan, or `undefined` when retention is off or the base\n * directory is unusable (filesystem root).\n */\nexport async function planWorkspaceCleanup(deps: CleanupDeps): Promise<CleanupPlan | undefined> {\n const window = retentionMs(deps.retention)\n if (window === undefined) return undefined\n\n const baseDir = path.resolve(deps.baseDir)\n if (isFilesystemRoot(baseDir)) {\n deps.warn?.('dsh-channels: workspace cleanup disabled: baseDir is a filesystem root')\n return undefined\n }\n\n const now = deps.now?.() ?? Date.now()\n const cutoff = now - window\n const activeRoutes = await deps.activeRoutes()\n const tombstones = await deps.tombstones()\n const candidates: CleanupCandidate[] = []\n let skippedLive = 0\n\n for (const workspace of await deps.listWorkspaces()) {\n if (!pluginWorkspaceTitle(workspace.title)) continue\n if (!isPathInside(baseDir, workspace.path)) continue\n\n const routes = activeRoutes.filter(route => samePath(route.workspacePath, workspace.path))\n if (routes.some(route => sessionIsLive(deps, route.sessionId))) {\n skippedLive++\n continue\n }\n\n let stats: WorkspaceStats | undefined\n let activityAt = newestActivity(routes.map(route => route.lastActivityAt))\n if (activityAt === undefined) {\n activityAt = newestActivity(\n tombstones\n .filter(tombstone => tombstone.workspacePath !== undefined\n && samePath(tombstone.workspacePath, workspace.path))\n .map(tombstone => tombstone.lastActivityAt ?? tombstone.expiredAt),\n )\n }\n if (activityAt === undefined) {\n // Orphan workspace: the directory's own mtime is the only age evidence.\n stats = await readWorkspaceStats(deps, workspace.path)\n activityAt = stats !== undefined && Number.isFinite(stats.mtimeMs) ? stats.mtimeMs : undefined\n }\n if (activityAt === undefined || activityAt >= cutoff) continue\n\n stats ??= await readWorkspaceStats(deps, workspace.path)\n candidates.push({\n workspace,\n activityAt,\n bytes: stats === undefined ? 0 : nonNegative(stats.bytes),\n files: stats === undefined ? 0 : nonNegative(stats.files),\n routes,\n })\n }\n\n candidates.sort((left, right) =>\n left.activityAt - right.activityAt || left.workspace.path.localeCompare(right.workspace.path))\n return { retentionMs: window, cutoff, candidates, skippedLive }\n}\n\n/**\n * Apply a plan: retire the candidate's active routes, then remove the\n * workspace directory and its registry record.\n *\n * A candidate is the unit of failure: if retiring any of its routes fails, the\n * workspace is left untouched (route bookkeeping must not point at a deleted\n * directory), the failure is collected, and the remaining candidates are still\n * processed. Paths are revalidated immediately before the destructive step, so\n * a stale or hand-built plan can never delete something outside `baseDir` (or\n * `baseDir` itself).\n *\n * @param deps - injected effect seams.\n * @param plan - plan produced by `planWorkspaceCleanup` (or an equivalent preview).\n * @returns aggregate counts plus per-workspace failures.\n */\nexport async function applyWorkspaceCleanup(\n deps: CleanupDeps,\n plan: CleanupPlan,\n): Promise<CleanupOutcome> {\n const baseDir = path.resolve(deps.baseDir)\n const baseDirIsRoot = isFilesystemRoot(baseDir)\n const failures: CleanupFailure[] = []\n let deletedWorkspaces = 0\n let retiredRoutes = 0\n let freedBytes = 0\n\n for (const candidate of plan.candidates) {\n const target = candidate.workspace.path\n // Defense in depth: never trust a plan object for the destructive step.\n if (baseDirIsRoot || !isPathInside(baseDir, target)) {\n failures.push({ path: target, message: 'refused: workspace path is outside the cleanup base directory' })\n continue\n }\n try {\n for (const route of candidate.routes) {\n if (!samePath(route.workspacePath, target)) continue\n await deps.retireRoute(route)\n retiredRoutes++\n }\n await deps.removeWorkspace(candidate.workspace)\n deletedWorkspaces++\n freedBytes += nonNegative(candidate.bytes)\n } catch (error) {\n const message = errorMessage(error)\n failures.push({ path: target, message })\n deps.warn?.(`dsh-channels: workspace cleanup failed for ${target}: ${message}`)\n }\n }\n\n return { deletedWorkspaces, retiredRoutes, freedBytes, failures }\n}\n","/**\n * Durable route store: thread/chat keys → bound DSH session records.\n *\n * Owns the hot cache over the `channels_routes` domain tables, the TTL\n * retirement that leaves a persistent tombstone, and session → route lookup.\n * The domain handle is late-bound (`domain()`), because the service opens it\n * during `start()` while the store is constructed earlier.\n *\n * @module dsh-channels/session/routes\n */\n\nimport type { ExpiredRouteRecord, FeishuDomainHandle, RouteRecord } from '../domain.ts'\nimport { normalizeRouteRecord } from '../domain.ts'\n\nexport interface RouteStoreDeps {\n /** The opened domain handle, or undefined before `start()` / after shutdown. */\n domain(): FeishuDomainHandle | undefined\n /** Route retirement TTL in milliseconds; `0` disables retirement. */\n ttlMs(): number\n /** Structured warning sink (never throws). */\n warn(message: string, ...args: unknown[]): void\n}\n\nexport class RouteStore {\n /** Active route records, hydrated once and kept in sync with writes. */\n private readonly active = new Map<string, RouteRecord>()\n /** Thread ids retired by TTL; retained so later messages cannot fork context. */\n private readonly expired = new Map<string, ExpiredRouteRecord>()\n\n constructor(private readonly deps: RouteStoreDeps) {}\n\n /**\n * Hydrate the caches from the opened domain. A tombstone wins over an active\n * row left behind by an interrupted or failed delete: expired Sessions must\n * not remain approval-routable.\n */\n load(): void {\n const domain = this.deps.domain()\n if (domain === undefined) return\n for (const [key, record] of domain.routes.entries()) {\n this.active.set(key, normalizeRouteRecord(record))\n }\n for (const [key, record] of domain.expiredRoutes.entries()) {\n this.expired.set(key, record)\n this.active.delete(key)\n }\n }\n\n /** One active route by key (cache-first, normalized). */\n get(key: string): RouteRecord | undefined {\n const cached = this.active.get(key)\n if (cached !== undefined) return cached\n const stored = this.deps.domain()?.routes.get(key)\n if (stored === undefined) return undefined\n const normalized = normalizeRouteRecord(stored)\n this.active.set(key, normalized)\n return normalized\n }\n\n /** One durable tombstone by expired thread id. */\n expiredRoute(key: string): ExpiredRouteRecord | undefined {\n const cached = this.expired.get(key)\n if (cached !== undefined) return cached\n const stored = this.deps.domain()?.expiredRoutes.get(key)\n if (stored === undefined) return undefined\n this.expired.set(key, stored)\n return stored\n }\n\n /** Persist one active route and refresh the cache. */\n async put(key: string, record: RouteRecord): Promise<void> {\n await this.deps.domain()?.routes.put(key, record)\n this.active.set(key, normalizeRouteRecord(record))\n }\n\n /** Delete one active route from the medium and the cache. */\n async remove(key: string): Promise<void> {\n await this.deps.domain()?.routes.delete(key)\n this.active.delete(key)\n }\n\n /** Drop one key from the cache only (used when a tombstone takes over). */\n forget(key: string): void {\n this.active.delete(key)\n }\n\n /** Find the route record owning a session (a thread route wins). */\n findBySession(sessionId: string): RouteRecord | undefined {\n let fallback: RouteRecord | undefined\n for (const record of this.active.values()) {\n if (record.sessionId !== sessionId) continue\n if (record.kind === 'thread') return record\n fallback ??= record\n }\n return fallback\n }\n\n size(): number {\n return this.active.size\n }\n\n clear(): void {\n this.active.clear()\n this.expired.clear()\n }\n\n /** Whether one active route has crossed the configured inactivity deadline. */\n private hasExpired(record: RouteRecord, now: number): boolean {\n const ttlMs = this.deps.ttlMs()\n return ttlMs > 0 && record.lastActivityAt < now - ttlMs\n }\n\n /**\n * Resolve or create the persistent tombstone for a thread whose active route\n * has crossed TTL. Tombstone publication comes first and wins even if the\n * best-effort active-route delete fails.\n */\n async expireThread(key: string, now = Date.now()): Promise<ExpiredRouteRecord | undefined> {\n const existing = this.expiredRoute(key)\n if (existing !== undefined) {\n this.active.delete(key)\n if (this.deps.domain()?.routes.get(key) !== undefined) {\n try {\n await this.deps.domain()?.routes.delete(key)\n } catch (error) {\n this.deps.warn('dsh-channels: deleting tombstoned route %s failed: %s', key, String(error))\n }\n }\n return existing\n }\n const record = this.get(key)\n if (record === undefined || record.kind !== 'thread' || !this.hasExpired(record, now)) return undefined\n return await this.retire(key, now)\n }\n\n /**\n * Retire one active thread route into a persistent tombstone regardless of\n * the configured TTL. Used by the workspace-retention cleanup, which may\n * close topics earlier than the route TTL.\n */\n async retire(key: string, now = Date.now()): Promise<ExpiredRouteRecord | undefined> {\n const record = this.get(key)\n if (record === undefined) return this.expiredRoute(key)\n const tombstone: ExpiredRouteRecord = {\n key,\n chatId: record.chatId,\n chatType: record.chatType,\n senderOpenId: record.senderOpenId,\n expiredAt: now,\n workspacePath: record.workspacePath,\n lastActivityAt: record.lastActivityAt,\n }\n await this.deps.domain()?.expiredRoutes.put(key, tombstone)\n this.expired.set(key, tombstone)\n this.active.delete(key)\n try {\n await this.deps.domain()?.routes.delete(key)\n } catch (error) {\n // The durable tombstone already makes expiry fail-closed. Keep serving\n // the explicit rejection and let a later sweep retry the stale delete.\n this.deps.warn('dsh-channels: deleting retired route %s failed: %s', key, String(error))\n }\n return tombstone\n }\n}\n","/**\n * In-flight turn snapshots and the startup repair pass.\n *\n * Every streaming turn records the credentials needed to settle its cards if\n * the process dies mid-turn (trigger message, Typing reaction, segment card\n * ids). The snapshot is keyed by the turn's MAIN card message id — unique per\n * turn — so overlapping turns never overwrite each other. On the next boot\n * `repairAll()` patches each interrupted card to its terminal state and drops\n * the snapshot.\n *\n * @module dsh-channels/session/repair\n */\n\nimport { buildInterruptionCard } from '../cards/streaming.ts'\nimport type { InflightSnapshot } from '../domain.ts'\nimport type { FeishuChannel } from '../feishu-channel.ts'\nimport type { FeishuDomainHandle } from '../domain.ts'\n\nexport interface InflightRepairDeps {\n /** The opened domain handle, or undefined before `start()` / after shutdown. */\n domain(): FeishuDomainHandle | undefined\n /** The live channel, or undefined before `start()` / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Route retirement TTL; snapshots older than it are the GC pass's job. */\n ttlMs(): number\n info(message: string, ...args: unknown[]): void\n warn(message: string, ...args: unknown[]): void\n}\n\nexport class InflightRepair {\n constructor(private readonly deps: InflightRepairDeps) {}\n\n /** Write the turn's repair snapshot. Keyed by the main card message id. */\n async write(snapshot: InflightSnapshot): Promise<void> {\n await this.deps.domain()?.inflight.put(snapshot.key, snapshot)\n }\n\n /** Retire a snapshot (turn finalized / creation failed). Never throws. */\n async clear(key: string): Promise<void> {\n if (key === '') return\n try {\n await this.deps.domain()?.inflight.delete(key)\n } catch (error) {\n this.deps.warn('dsh-channels: clearing inflight snapshot %s failed: %s', key, String(error))\n }\n }\n\n /** Outstanding repair snapshots (health diagnostics). */\n count(): number {\n let total = 0\n try {\n const table = this.deps.domain()?.inflight\n if (table !== undefined) for (const _entry of table.entries()) total++\n } catch {\n // The storage dependency is reported separately; never expose details.\n }\n return total\n }\n\n /**\n * Fold a resolved segment card id into the turn's snapshot (atomic\n * read-modify-write). A missing snapshot means the turn already finalized\n * (the finalize delete won the race) — silently skipped.\n */\n async recordSegment(\n mainCardMessageId: string,\n segmentIndex: number,\n messageId: string,\n mode: 'card' | 'text',\n ): Promise<void> {\n if (mainCardMessageId === '') return\n try {\n await this.deps.domain()?.inflight.update(mainCardMessageId, snapshot => {\n // The snapshot starts with only the main card; segment sends are\n // serialized in order, so a delivery for index `cards.length` is the\n // next segment (append), anything below replaces, and a gap beyond\n // the tail cannot happen under the serialized chain (ignored).\n const cards = [...snapshot.cards]\n if (segmentIndex === cards.length) {\n cards.push({ messageId, mode })\n } else if (segmentIndex >= 0 && segmentIndex < cards.length) {\n cards[segmentIndex] = { messageId, mode }\n } else {\n return snapshot\n }\n return { ...snapshot, cards }\n })\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: updating inflight snapshot %s failed: %s', mainCardMessageId, String(error),\n )\n }\n }\n\n /**\n * Startup repair pass: settle every card a previous instance left\n * mid-flight. Runs in the background right after the long connection is\n * established; each snapshot is independent and best-effort.\n *\n * Per snapshot: patch every `card`-mode card to the terminal interrupted\n * state (the partial body is gone with the old process; patch is full\n * replacement), best-effort swap Typing → DONE, then delete. A failed card\n * patch keeps the snapshot for one more attempt (capped at 2) so a\n * transient failure at boot does not lose the repair, while a medium that\n * permanently rejects the patch cannot trigger an endless retry loop.\n */\n async repairAll(): Promise<void> {\n const table = this.deps.domain()?.inflight\n if (table === undefined) return\n const snapshots = [...table.entries()]\n if (snapshots.length === 0) return\n // With GC enabled, snapshots older than the TTL are the GC pass's job:\n // repairing an ancient card would surprise the user, so repair skips\n // them and lets the GC drop them silently (both passes run at boot).\n const ttlMs = this.deps.ttlMs()\n const cutoff = ttlMs === 0 ? undefined : Date.now() - ttlMs\n const due = snapshots.filter(([, snapshot]) => cutoff === undefined || snapshot.startedAt >= cutoff)\n if (due.length === 0) return\n this.deps.info('dsh-channels: repairing %d interrupted turn(s)', due.length)\n for (const [key, snapshot] of due) {\n try {\n await this.repairSnapshot(snapshot)\n await table.delete(key)\n this.deps.info('dsh-channels: repaired interrupted turn card %s', key)\n } catch (error) {\n this.deps.warn('dsh-channels: repair of turn card %s failed: %s', key, String(error))\n const attempts = snapshot.attempts + 1\n try {\n if (attempts >= 2) {\n await table.delete(key)\n } else {\n await table.put(key, { ...snapshot, attempts })\n }\n } catch {\n // The domain may already be closing (shutdown raced the repair).\n }\n }\n }\n }\n\n /** Patch the cards of one interrupted turn to their terminal state. */\n private async repairSnapshot(snapshot: InflightSnapshot): Promise<void> {\n const channel = this.deps.channel()\n if (channel === undefined) throw new Error('dsh-channels: channel not started')\n for (const [index, card] of snapshot.cards.entries()) {\n if (card.mode !== 'card') continue\n // Full replacement: the interrupted card's streamed content lived only\n // in the previous process. Continuation cards keep their segment header.\n const options = index === 0 ? {} : { panel: false, segment: index + 1 }\n await channel.updateCard(card.messageId, buildInterruptionCard(options))\n }\n if (snapshot.reactionId !== undefined) {\n await channel.removeReaction(snapshot.triggerMessageId, snapshot.reactionId).catch(() => {})\n }\n await channel.addReaction(snapshot.triggerMessageId, 'DONE').catch(() => {})\n }\n}\n","/**\n * Agent-scoped system-prompt guidance for Feishu attachments.\n *\n * The channel archives inbound images/files/forwarded records below\n * `<workspace>/attachments/` without any model turn. This section is what\n * makes that convention discoverable: it tells the Agent where attachments\n * live, how to read them, and that reading is opt-in per user request.\n *\n * Registered inside the Agent scope (through `inject(['systemPrompt'])`), so\n * it is disposed with the Agent and does not leak into other compositions.\n *\n * @module dsh-channels/feishu/agent-context\n */\n\nimport type { AgentContext } from '../host/agent.ts'\n\n/** Stable prompt-section name; one registration per Agent scope. */\nexport const FEISHU_ATTACHMENTS_SECTION = 'dsh-channels:feishu-attachments'\n\n/**\n * Placement between the harness file-reference section (900) and the\n * filesystem tool descriptions (1000): the convention is read before the\n * tool that implements it.\n */\nconst FEISHU_ATTACHMENTS_ORDER = 950\n\n/** Model-facing text. Kept stable so unchanged sessions keep their KV cache. */\nexport const FEISHU_ATTACHMENTS_GUIDANCE = `## Feishu attachments\nEvery Feishu conversation (topic) has its own workspace and its own attachments directory. Images, files, and forwarded chat records sent through Feishu are archived verbatim in this conversation's \\`attachments/\\` directory. Each file name starts with its receipt time (\\`YYYYMMDD-HHmmss-\\`), so newer attachments sort later and \\`ls -lt <dir>\\` lists the newest first.\n\nAttachments are never inserted into the conversation automatically. Read them only when the user's request needs them, and do not read attachments the user did not mention:\n- images: use \\`read_image\\` with the file path;\n- text and code: use \\`read\\` when the session mounts that tool, otherwise \\`bash\\` (for example \\`sed -n '1,200p' <file>\\`);\n- other formats (PDF, Office, archives, ...): inspect or convert them with \\`bash\\`.\nIf the user refers to \"the file/image I just sent\" without a name, list this conversation's attachments directory and pick the most recent matching attachment. Other Feishu conversations have separate workspaces: never read outside this workspace, and never mention files from another conversation.`\n\n/**\n * Full guidance for one Session. The absolute per-session directory is\n * appended when known so the convention still resolves after the Agent changes\n * its working directory with the `working_directory` tool.\n */\nexport function attachmentsGuidance(attachmentsDir?: string): string {\n if (attachmentsDir === undefined || attachmentsDir === '') return FEISHU_ATTACHMENTS_GUIDANCE\n return `${FEISHU_ATTACHMENTS_GUIDANCE}\\n\\nThis conversation's attachment directory is \\`${attachmentsDir.replace(/`/gu, \"'\")}\\` (absolute).`\n}\n\n/**\n * Mount the attachment guidance into one Agent scope. Call from agent setup;\n * a missing `systemPrompt` service degrades to a no-op (headless hosts).\n */\nexport async function mountChannelGuidance(\n agentCtx: AgentContext,\n attachmentsDir?: string,\n): Promise<void> {\n await agentCtx.inject(['systemPrompt'], (ctx) => {\n ctx.systemPrompt?.section({\n name: FEISHU_ATTACHMENTS_SECTION,\n order: FEISHU_ATTACHMENTS_ORDER,\n text: attachmentsGuidance(attachmentsDir),\n })\n })\n}\n","/**\n * Thin wrapper over the official SDK's `createLarkChannel` high-level module.\n *\n * The wrapper exists to pin the exact surface the plugin uses behind one\n * interface so the unit tests can stub the official SDK wholesale (no\n * network, no creds).\n *\n * @module dsh-channels/feishu-channel\n */\n\nimport { createLarkChannel, LoggerLevel } from '@larksuiteoapi/node-sdk'\nimport type {\n BotAddedEvent,\n ChatInfo,\n LarkChannel,\n NormalizedMessage,\n SendResult,\n WSConnectionStatus,\n} from '@larksuiteoapi/node-sdk'\n\nexport type { BotAddedEvent, ChatInfo, NormalizedMessage, SendResult } from '@larksuiteoapi/node-sdk'\nexport type FeishuConnectionStatus = WSConnectionStatus\n\n/** Outbound payloads used by this plugin, including Buffer-backed file upload. */\nexport type FeishuSendInput =\n | { card: object }\n | { text: string }\n | { file: { source: string | Buffer; fileName: string } }\n\n/**\n * One normalized card action (button click) event, as delivered by the\n * official SDK's `card.action.trigger` handler (card JSON 2.0 callback\n * behaviors). `messageId` is the card's own message id; `action.value` is the\n * callback value object the button carried (SDK contract: object values).\n */\nexport interface CardActionEvent {\n /** Message id of the card the action belongs to. */\n messageId: string\n chatId: string\n operator: { openId: string; userId?: string; name?: string }\n action: { value: unknown; tag: string; name?: string; option?: string }\n raw?: unknown\n}\n\n/** The minimal channel surface the plugin consumes. */\nexport interface FeishuChannel {\n /** Bound handler for normalized inbound messages. */\n onMessage(handler: (message: NormalizedMessage) => void | Promise<void>): () => void\n /** Bound handler for card button-callback events. */\n onCardAction(handler: (event: CardActionEvent) => void | Promise<void>): () => void\n /**\n * Bound handler for bot-added-to-chat events (`im.chat.member.bot.added_v1`).\n * Requires the app to subscribe that event; without it the plugin falls\n * back to the first denied @-mention for group information cards.\n */\n onBotAdded(handler: (event: BotAddedEvent) => void | Promise<void>): () => void\n /** Chat name/owner/member count (needs chat-info read permission). */\n getChatInfo(chatId: string): Promise<ChatInfo>\n /** Bot open_id, available after `connect()` resolves. */\n botOpenId(): string | undefined\n /** Current official-SDK WebSocket lifecycle snapshot, when initialized. */\n getConnectionStatus(): FeishuConnectionStatus | undefined\n /** Start the WebSocket long connection (resolves after the handshake). */\n connect(): Promise<void>\n /** Stop the connection. */\n disconnect(): Promise<void>\n /** Send one message (text or card) with optional reply targeting. */\n send(\n chatId: string,\n input: FeishuSendInput,\n options?: { replyTo?: string; replyInThread?: boolean },\n ): Promise<SendResult>\n /** Full-card replacement of a previously sent card message. */\n updateCard(messageId: string, card: object): Promise<void>\n /** Add an emoji reaction; resolves with the reaction id needed for removal. */\n addReaction(messageId: string, emojiType: string): Promise<string>\n /** Remove a reaction previously added by the bot. */\n removeReaction(messageId: string, reactionId: string): Promise<void>\n /** Fetch the thread id of a sent message (undefined when not a topic message). */\n threadIdOfMessage(messageId: string): Promise<string | undefined>\n /**\n * Stream one inbound message resource. Files and images share the same\n * download endpoint; only the `type` query differs. The stream is bounded\n * chunk-by-chunk so arbitrarily large files never have to fit in memory.\n */\n downloadResource(\n messageId: string,\n fileKey: string,\n type: 'image' | 'file',\n ): Promise<ResourceChunkStream>\n}\n\n/** Ordered byte chunks of one inbound resource. */\nexport type ResourceChunkStream = AsyncIterable<Uint8Array>\n\n/**\n * Chunk size for ranged file downloads. Feishu's message-resource endpoint\n * rejects a single response larger than 100 MB (error 234037), but honors\n * bounded Range requests, so large files are fetched in independent chunks.\n */\nexport const RANGE_DOWNLOAD_CHUNK_BYTES = 32 * 1024 * 1024\n\n/** Total object size from a `Content-Range: bytes start-end/total` header. */\nexport function contentRangeTotal(headers: unknown): number | undefined {\n const value = headerValue(headers, 'content-range')\n if (value === undefined) return undefined\n const match = /\\/(\\d+)\\s*$/u.exec(value)\n if (match === null) return undefined\n const total = Number(match[1])\n return Number.isSafeInteger(total) && total >= 0 ? total : undefined\n}\n\n/** Case-insensitive header lookup over a plain object or an AxiosHeaders-like getter. */\nfunction headerValue(headers: unknown, name: string): string | undefined {\n if (headers === null || typeof headers !== 'object') return undefined\n const record = headers as Record<string, unknown>\n const lower = name.toLowerCase()\n for (const key of Object.keys(record)) {\n if (key.toLowerCase() !== lower) continue\n const value = record[key]\n if (typeof value === 'string') return value\n }\n const get = (headers as { get?: (key: string) => unknown }).get\n if (typeof get === 'function') {\n const value = get.call(headers, name)\n return typeof value === 'string' ? value : undefined\n }\n return undefined\n}\n\n/** One bounded range request returning the response headers and byte stream. */\nexport type ResourceRangeFetcher = (range: string) => Promise<{\n headers?: unknown\n stream: AsyncIterable<Uint8Array>\n}>\n\n/**\n * Download one object through sequential bounded Range requests. The caller\n * supplies the first response's total size and a fetcher for the remaining\n * ranges; bytes are yielded as they arrive so the archive writes them\n * straight to disk.\n */\nexport async function* downloadResourceInRanges(\n fetchRange: ResourceRangeFetcher,\n totalBytes: number,\n chunkBytes = RANGE_DOWNLOAD_CHUNK_BYTES,\n): AsyncGenerator<Uint8Array> {\n let offset = 0\n while (offset < totalBytes) {\n const end = Math.min(offset + chunkBytes, totalBytes) - 1\n const response = await fetchRange(`bytes=${offset}-${end}`)\n let received = 0\n for await (const chunk of response.stream) {\n received += chunk.length\n yield chunk\n }\n if (received === 0) {\n throw new Error(`dsh-channels: ranged download stalled at byte ${offset}/${totalBytes}`)\n }\n offset += received\n }\n}\n\nexport interface ChannelConnectOptions {\n appId: string\n appSecret: string\n loggerLevel: 'debug' | 'info' | 'warn' | 'error'\n requireMention: boolean\n dmMode: 'open' | 'disabled'\n}\n\nclass LarkChannelAdapter implements FeishuChannel {\n private readonly channel: LarkChannel\n private readonly requireMention: boolean\n private readonly dmMode: 'open' | 'disabled'\n\n constructor(options: ChannelConnectOptions) {\n this.channel = createLarkChannel({\n appId: options.appId,\n appSecret: options.appSecret,\n loggerLevel: LOGGER_LEVELS[options.loggerLevel],\n // Keep the raw event body: rich-text messages attach files in a\n // top-level `files` array the SDK normalizer drops, so the attachment\n // intake recovers them from `msg.raw` instead.\n includeRawEvent: true,\n policy: {\n // The plugin routes mentions itself (thread routing must work without\n // mention), so the SDK policy layer stays permissive.\n requireMention: false,\n dmMode: options.dmMode === 'open' ? 'open' : 'disabled',\n respondToMentionAll: false,\n },\n })\n this.requireMention = options.requireMention\n this.dmMode = options.dmMode\n }\n\n onMessage(handler: (message: NormalizedMessage) => void | Promise<void>): () => void {\n return this.channel.on('message', handler)\n }\n\n onCardAction(handler: (event: CardActionEvent) => void | Promise<void>): () => void {\n return this.channel.on('cardAction', handler)\n }\n\n onBotAdded(handler: (event: BotAddedEvent) => void | Promise<void>): () => void {\n // The SDK event handler is synchronous; the plugin's handler is\n // fire-and-forget (the service wraps it with its own catch/log).\n return this.channel.on('botAdded', event => {\n void handler(event)\n })\n }\n\n async getChatInfo(chatId: string): Promise<ChatInfo> {\n return this.channel.getChatInfo(chatId)\n }\n\n botOpenId(): string | undefined {\n return this.channel.botIdentity?.openId\n }\n\n getConnectionStatus(): FeishuConnectionStatus | undefined {\n return this.channel.getConnectionStatus()\n }\n\n async connect(): Promise<void> {\n await this.channel.connect()\n }\n\n async disconnect(): Promise<void> {\n await this.channel.disconnect()\n }\n\n async send(\n chatId: string,\n input: FeishuSendInput,\n options?: { replyTo?: string; replyInThread?: boolean },\n ): Promise<SendResult> {\n return this.channel.send(chatId, input, options)\n }\n\n async updateCard(messageId: string, card: object): Promise<void> {\n await this.channel.updateCard(messageId, card)\n }\n\n async addReaction(messageId: string, emojiType: string): Promise<string> {\n return this.channel.addReaction(messageId, emojiType)\n }\n\n async removeReaction(messageId: string, reactionId: string): Promise<void> {\n await this.channel.removeReaction(messageId, reactionId)\n }\n\n async threadIdOfMessage(messageId: string): Promise<string | undefined> {\n const res = await this.channel.rawClient.im.v1.message.get({ path: { message_id: messageId } })\n return res?.data?.items?.[0]?.thread_id\n }\n\n async downloadResource(\n messageId: string,\n fileKey: string,\n type: 'image' | 'file',\n ): Promise<ResourceChunkStream> {\n if (type === 'image') {\n // Images are platform-capped far below the download limit; one request.\n const response = await this.rawResource(messageId, fileKey, type)\n return chunkStream(response.stream)\n }\n // Probe one byte to learn the total size without triggering the whole-file\n // size check. Feishu honors a bounded Range request even when the object\n // is larger than the API's single-response limit.\n const probe = await this.rawResource(messageId, fileKey, type, 'bytes=0-0')\n const total = contentRangeTotal(probe.headers)\n if (total === undefined) {\n // The server ignored Range and returned the whole object; use it as is.\n return chunkStream(probe.stream)\n }\n ;(probe.stream as { destroy?: () => void }).destroy?.()\n if (total === 0) {\n return {\n async *[Symbol.asyncIterator]() { /* empty resource */ },\n }\n }\n return downloadResourceInRanges(async range => {\n const response = await this.rawResource(messageId, fileKey, type, range)\n return { headers: response.headers, stream: chunkStream(response.stream) }\n }, total)\n }\n\n /** One message-resource GET, with an optional Range header. */\n private async rawResource(\n messageId: string,\n fileKey: string,\n type: 'image' | 'file',\n range?: string,\n ): Promise<{ headers: unknown; stream: AsyncIterable<Uint8Array | Buffer> }> {\n const response = await this.channel.rawClient.im.v1.messageResource.get(\n { params: { type }, path: { message_id: messageId, file_key: fileKey } },\n range === undefined ? undefined : { headers: { Range: range } },\n ) as unknown as { getReadableStream(): AsyncIterable<Uint8Array | Buffer>; headers?: unknown }\n return { headers: response.headers, stream: response.getReadableStream() }\n }\n}\n\n/** Wrap one Node stream as a plain byte-chunk iterable. */\nfunction chunkStream(source: AsyncIterable<Uint8Array | Buffer>): ResourceChunkStream {\n return {\n async *[Symbol.asyncIterator]() {\n for await (const chunk of source) {\n yield Buffer.isBuffer(chunk) ? new Uint8Array(chunk) : chunk\n }\n },\n }\n}\n\n/**\n * Detect the raster format from magic bytes. Feishu's resource download does\n * not hand back a media type, and DSH validates the declared type against the\n * decoded bytes, so the plugin sniffs instead of trusting the file name.\n */\nexport function sniffImageMediaType(data: Uint8Array): 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif' | undefined {\n if (data.length >= 8 && data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47) return 'image/png'\n if (data.length >= 3 && data[0] === 0xFF && data[1] === 0xD8 && data[2] === 0xFF) return 'image/jpeg'\n if (data.length >= 12 && data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50) return 'image/webp'\n if (data.length >= 6 && data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) return 'image/gif'\n return undefined\n}\n\nconst LOGGER_LEVELS: Record<ChannelConnectOptions['loggerLevel'], LoggerLevel> = {\n debug: LoggerLevel.debug,\n info: LoggerLevel.info,\n warn: LoggerLevel.warn,\n error: LoggerLevel.error,\n}\n\n/** Factory seam: tests substitute a stub adapter. */\nexport type ChannelFactory = (options: ChannelConnectOptions) => FeishuChannel\n\nexport const defaultChannelFactory: ChannelFactory = options => new LarkChannelAdapter(options)\n","/**\n * Inbound attachment archival: Feishu image/file resources and merged-forward\n * chat records are written verbatim below `<workspace>/attachments/`.\n *\n * Nothing is forwarded to the model. The Agent's own filesystem tools read\n * whichever attachment the conversation needs, so an attachment message costs\n * no model request at all and unreferenced files never enter the context.\n * Files are streamed straight to disk: there is no plugin-side size cap, so a\n * large upload must not be collected in memory.\n *\n * Two payload shapes need care because the SDK normalizer drops them:\n * rich-text (`post`) messages keep their attached files in a top-level\n * `files` array that `convertPost` ignores, and merged-forward records are\n * plain text rather than resources. Both are recovered here; the post files\n * come from the raw event, which the channel adapter asks the SDK to attach.\n *\n * @module dsh-channels/feishu/inbound-attachments\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { createWriteStream } from 'node:fs'\nimport { mkdir, open, readdir, rename, rm, stat, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport type { FeishuChannel, NormalizedMessage } from '../feishu-channel.ts'\nimport { sniffImageMediaType } from '../feishu-channel.ts'\n\n/** Workspace subdirectory that holds every archived attachment. */\nexport const ATTACHMENTS_DIR = 'attachments'\n\n/** `<workspace>/attachments` directory of one topic workspace. */\nexport function attachmentsDir(workspacePath: string): string {\n return path.join(workspacePath, ATTACHMENTS_DIR)\n}\n\n/**\n * Move every staged attachment into one topic workspace. Staging holds files\n * received before their topic (and therefore its workspace) existed; they\n * belong to the first Session created for that topic. Best-effort per file.\n *\n * @returns how many files were adopted.\n */\nexport async function adoptStagedAttachments(stagingDir: string, workspacePath: string): Promise<number> {\n let entries: Array<{ name: string; isFile(): boolean }>\n try {\n entries = await readdir(stagingDir, { withFileTypes: true })\n } catch {\n return 0\n }\n const files = entries.filter(entry => entry.isFile() && !entry.name.startsWith('.'))\n if (files.length === 0) return 0\n const target = attachmentsDir(workspacePath)\n try {\n await mkdir(target, { recursive: true })\n } catch {\n return 0\n }\n let adopted = 0\n for (const entry of files) {\n const destination = await uniqueName(target, entry.name)\n try {\n await rename(path.join(stagingDir, entry.name), path.join(target, destination))\n adopted += 1\n } catch {\n // Leave the staged file for the next attempt; never block the turn.\n }\n }\n return adopted\n}\n\n/** The SDK placeholder for a rich-text message that carried no visible text. */\nconst EMPTY_POST_PLACEHOLDER = '[rich text message]'\n\n/** Placeholders the SDK leaves in normalized content for attachment payloads. */\nconst IMAGE_PLACEHOLDER = /!\\[image\\]\\([^)]*\\)/gu\nconst FILE_PLACEHOLDER = /<file[^>]*\\/>/gu\nconst FORWARDED_PLACEHOLDER = /<forwarded_messages(?:\\/>|>[\\s\\S]*?<\\/forwarded_messages>)/gu\n/** An expanded record always starts with this wrapper; a bare empty one means the fetch failed. */\nconst EMPTY_FORWARDED = '<forwarded_messages/>'\n\nexport interface AttachmentIntakeDeps {\n /** Whether this deployment archives inbound attachments at all. */\n enabled(): boolean\n /** Live channel; undefined after shutdown. */\n channel(): FeishuChannel | undefined\n /** Structured warning sink (never throws). */\n warn(message: string, ...args: unknown[]): void\n /** Deterministic clock seam for file-name prefixes. */\n now?(): number\n}\n\n/** One decoded attachment recovered from the normalized message or its raw post payload. */\nexport interface AttachmentResource {\n readonly type: 'image' | 'file'\n readonly fileKey: string\n readonly fileName?: string\n /** Feishu folder messages cannot be downloaded through the resource API. */\n readonly folder?: boolean\n}\n\n/** One attachment that could not be archived, with a user-facing reason. */\nexport interface AttachmentFailure {\n readonly name: string\n readonly reason: string\n}\n\nexport interface AttachmentArchiveResult {\n /** Resources durably written to the workspace. */\n saved: number\n /** Resources that failed to download, write, or are unsupported. */\n failed: number\n /** User-facing per-attachment failure notes (only when `failed > 0`). */\n failures: readonly AttachmentFailure[]\n /** True when the message carried no prompt text after removing placeholders. */\n attachmentOnly: boolean\n /** Remaining text after attachment placeholders are removed. */\n text: string\n}\n\n/** One file attached to a rich-text (`post`) message. */\nexport interface PostAttachedFile {\n readonly fileKey: string\n readonly fileName?: string\n readonly folder?: boolean\n}\n\n/**\n * Every attachment payload of one message, merging SDK-normalized resources\n * with files the `post` converter dropped.\n */\nexport function attachmentResources(msg: NormalizedMessage): AttachmentResource[] {\n const resources: AttachmentResource[] = (msg.resources ?? [])\n .filter(resource => resource.type === 'image' || resource.type === 'file')\n .map(resource => ({\n type: resource.type === 'file' ? 'file' : 'image',\n fileKey: resource.fileKey,\n ...(resource.fileName === undefined ? {} : { fileName: resource.fileName }),\n }))\n const seen = new Set(resources.map(resource => resource.fileKey))\n for (const file of postAttachedFiles(msg.rawContentType, msg.raw)) {\n if (seen.has(file.fileKey)) continue\n seen.add(file.fileKey)\n resources.push({ type: 'file', ...file })\n }\n return resources\n}\n\n/** Whether one message may carry an attachment payload worth archiving. */\nexport function hasAttachmentPayload(msg: NormalizedMessage): boolean {\n if (msg.rawContentType === 'merge_forward') return true\n if (msg.rawContentType === 'post') return true\n return attachmentResources(msg).length > 0\n}\n\n/**\n * Recover the files a rich-text message attached.\n *\n * Feishu serializes a post with text and files as `{ title, content, files }`.\n * The SDK's `convertPost` walks `content` elements only, so every entry of\n * `files` is lost from `NormalizedMessage.resources`; the raw event (enabled by\n * `includeRawEvent`) still carries it. Locale-wrapped bodies are searched one\n * level deep for the same shape.\n */\nexport function postAttachedFiles(rawContentType: string, raw: unknown): PostAttachedFile[] {\n if (rawContentType !== 'post' || raw === null || raw === undefined) return []\n const message = (raw as { message?: { content?: unknown } }).message\n if (message === undefined) return []\n const content = typeof message.content === 'string' ? safeJson(message.content) : message.content\n if (content === undefined) return []\n const entries = collectFileEntries(content, 0)\n const files: PostAttachedFile[] = []\n for (const entry of entries) {\n const fileKey = typeof entry.file_key === 'string' && entry.file_key !== '' ? entry.file_key : undefined\n if (fileKey === undefined) continue\n files.push({\n fileKey,\n ...(typeof entry.file_name === 'string' && entry.file_name !== '' ? { fileName: entry.file_name } : {}),\n ...(entry.is_folder === true ? { folder: true } : {}),\n })\n }\n return files\n}\n\nfunction safeJson(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return undefined\n }\n}\n\n/** Find every `files` array in a post body (flat or locale-wrapped), depth-bounded. */\nfunction collectFileEntries(node: unknown, depth: number): Array<Record<string, unknown>> {\n if (depth > 3 || node === null || typeof node !== 'object' || Array.isArray(node)) return []\n const record = node as Record<string, unknown>\n const files = record.files\n if (Array.isArray(files)) {\n return files.filter(\n (entry): entry is Record<string, unknown> => entry !== null && typeof entry === 'object' && !Array.isArray(entry),\n )\n }\n const out: Array<Record<string, unknown>> = []\n for (const value of Object.values(record)) out.push(...collectFileEntries(value, depth + 1))\n return out\n}\n\nexport class AttachmentIntake {\n constructor(private readonly deps: AttachmentIntakeDeps) {}\n\n /**\n * Archive every image/file resource and merged-forward record of one\n * inbound message. Failures are contained per attachment; the method never\n * throws and always reports the remaining prompt text.\n */\n async archive(msg: NormalizedMessage, directory: string): Promise<AttachmentArchiveResult> {\n const resources = attachmentResources(msg)\n const forwarded = msg.rawContentType === 'merge_forward'\n if (resources.length === 0 && !forwarded) {\n return { saved: 0, failed: 0, failures: [], attachmentOnly: false, text: msg.content ?? '' }\n }\n\n const stripped = stripAttachmentPlaceholders(msg.content ?? '', forwarded)\n const remainingText = stripped === EMPTY_POST_PLACEHOLDER ? '' : stripped\n const result = (\n saved: number,\n failures: readonly AttachmentFailure[],\n ): AttachmentArchiveResult => ({\n saved,\n failed: failures.length,\n failures,\n attachmentOnly: remainingText === '',\n text: remainingText,\n })\n\n if (!this.deps.enabled()) return result(0, [])\n\n const channel = this.deps.channel()\n if (channel === undefined) {\n return result(0, resources.map(resource => ({\n name: resourceName(resource),\n reason: '飞书连接未运行,附件未保存',\n })))\n }\n\n const dir = directory\n let saved = 0\n const failures: AttachmentFailure[] = []\n try {\n await mkdir(dir, { recursive: true })\n } catch (error) {\n this.deps.warn('dsh-channels: creating attachments directory %s failed: %s', dir, String(error))\n return result(0, resources.map(resource => ({\n name: resourceName(resource),\n reason: `无法创建工作区附件目录:${describeFailure(error)}`,\n })))\n }\n\n for (const resource of resources) {\n const name = resourceName(resource)\n if (resource.folder === true) {\n failures.push({ name, reason: '飞书文件夹消息暂不支持下载' })\n continue\n }\n try {\n await this.saveResource(msg, resource, dir)\n saved += 1\n } catch (error) {\n failures.push({ name, reason: describeFailure(error) })\n this.deps.warn(\n 'dsh-channels: archiving resource %s of message %s failed: %s',\n resource.fileKey, msg.messageId, String(error),\n )\n }\n }\n\n if (forwarded) {\n try {\n await this.saveForwardedRecord(msg, dir)\n saved += 1\n } catch (error) {\n failures.push({ name: '会话记录', reason: describeFailure(error) })\n this.deps.warn(\n 'dsh-channels: archiving forwarded record of message %s failed: %s',\n msg.messageId, String(error),\n )\n }\n }\n\n if (saved > 0) {\n await channel.addReaction(msg.messageId, 'DONE').catch(() => undefined)\n } else if (failures.length > 0) {\n await channel.addReaction(msg.messageId, 'No').catch(() => undefined)\n }\n\n return result(saved, failures)\n }\n\n /** Stream one image/file resource to disk, then publish it under a stable name. */\n private async saveResource(\n msg: NormalizedMessage,\n resource: AttachmentResource,\n dir: string,\n ): Promise<void> {\n const channel = this.deps.channel()\n if (channel === undefined) throw new Error('channel is not running')\n const stream = await channel.downloadResource(\n msg.messageId,\n resource.fileKey,\n resource.type === 'file' ? 'file' : 'image',\n )\n const temporary = path.join(dir, `.tmp-${randomUUID()}`)\n try {\n await pipeline(Readable.from(stream), createWriteStream(temporary, { mode: 0o600 }))\n const original = sanitizeFileName(resource.fileName)\n const base = original ?? (resource.type === 'image'\n ? `image${await sniffExtension(temporary)}`\n : 'file.bin')\n const finalName = await uniqueName(dir, `${this.stamp()}-${base}`)\n await rename(temporary, path.join(dir, finalName))\n } catch (error) {\n await rm(temporary, { force: true }).catch(() => undefined)\n throw error\n }\n }\n\n /**\n * Persist one expanded merged-forward record as UTF-8 text. The content is\n * already plain text (`<forwarded_messages>…`), so no parsing or conversion\n * happens here — the Agent reads the file when the conversation needs it.\n */\n private async saveForwardedRecord(msg: NormalizedMessage, dir: string): Promise<void> {\n const content = msg.content ?? ''\n if (content.trim() === '' || content.trim() === EMPTY_FORWARDED) {\n throw new Error('会话记录为空(机器人可能缺少源消息的读取权限)')\n }\n const name = await uniqueName(dir, `${this.stamp()}-forwarded-record.txt`)\n await writeFile(path.join(dir, name), `${content}\\n`, { encoding: 'utf8', mode: 0o600 })\n }\n\n private stamp(): string {\n return compactTimestamp(this.deps.now?.() ?? Date.now())\n }\n}\n\n/** Remove attachment placeholders while keeping ordinary prompt text. */\nexport function stripAttachmentPlaceholders(content: string, forwarded: boolean): string {\n let text = content.replace(IMAGE_PLACEHOLDER, ' ').replace(FILE_PLACEHOLDER, ' ')\n if (forwarded) text = text.replace(FORWARDED_PLACEHOLDER, ' ')\n return text.replace(/[ \\t]+/gu, ' ').replace(/\\s*\\n\\s*/gu, '\\n').trim()\n}\n\n/** `YYYYMMDD-HHmmss`, UTC, filesystem- and sort-friendly. */\nexport function compactTimestamp(epochMs: number): string {\n return new Date(epochMs).toISOString().replace(/[-:]/gu, '').replace(/\\..+$/u, '').replace('T', '-')\n}\n\n/** Strip directories, control characters, and reserved path characters from a display name. */\nexport function sanitizeFileName(raw: string | undefined): string | undefined {\n if (raw === undefined) return undefined\n const base = raw.split(/[\\\\/]/u).pop() ?? raw\n const cleaned = base\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f\\u007f<>:\"\\\\|?*]/gu, '_')\n .replace(/^[.\\s]+/u, '')\n .replace(/[.\\s]+$/u, '')\n if (cleaned === '') return undefined\n return truncateUtf8(cleaned, 120)\n}\n\n/** User-facing name for one attachment. */\nfunction resourceName(resource: AttachmentResource): string {\n if (resource.fileName !== undefined && resource.fileName !== '') return resource.fileName\n return resource.type === 'image' ? '图片' : '文件'\n}\n\n/**\n * Turn a download/write failure into a short user-facing reason. Feishu's\n * message-resource API refuses files above 100 MB, which is the common case\n * for a 400 on an otherwise valid file.\n */\nexport function describeFailure(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error)\n if (/status code 400|bad request/iu.test(message)) {\n return '飞书下载接口拒绝请求(文件可能已过期、已被删除或暂不可下载)'\n }\n if (/status code 403|forbidden/iu.test(message)) return '飞书下载接口无权限'\n return message\n}\n\n/** Truncate a UTF-8 string on a character boundary. */\nfunction truncateUtf8(value: string, maxBytes: number): string {\n if (Buffer.byteLength(value) <= maxBytes) return value\n let out = ''\n for (const char of value) {\n if (Buffer.byteLength(out + char) > maxBytes) break\n out += char\n }\n return out\n}\n\n/** Pick a non-colliding destination name inside one directory. */\nasync function uniqueName(dir: string, candidate: string): Promise<string> {\n const parsed = path.parse(candidate)\n for (let attempt = 1; attempt <= 100; attempt += 1) {\n const name = attempt === 1 ? candidate : `${parsed.name}-${attempt}${parsed.ext}`\n try {\n await stat(path.join(dir, name))\n } catch {\n return name\n }\n }\n throw new Error('no free attachment file name after 100 attempts')\n}\n\n/** Detect an image extension from the first bytes of a staged file. */\nasync function sniffExtension(file: string): Promise<string> {\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(file, 'r')\n const buffer = Buffer.alloc(16)\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0)\n const type = sniffImageMediaType(new Uint8Array(buffer.subarray(0, bytesRead)))\n switch (type) {\n case 'image/png': return '.png'\n case 'image/jpeg': return '.jpg'\n case 'image/webp': return '.webp'\n case 'image/gif': return '.gif'\n default: return '.bin'\n }\n } catch {\n return '.bin'\n } finally {\n await handle?.close().catch(() => undefined)\n }\n}\n","/**\n * Per-session request policy: provider / model / reasoning-effort overrides.\n *\n * Cordis waterfalls run the earliest listener as the outermost wrapper, so the\n * channel policy registers with `prepend: true`: preset/model-selection\n * listeners may be standing registrations that predate this Agent, and they\n * rewrite the request after `next()` resolves. The outer channel wrapper\n * therefore writes last.\n *\n * Session overrides are recovered from successful command lifecycles\n * (`/effort`, `/model`). Reusing Harness' durable `command/run` /\n * `command/done` pair avoids inventing an out-of-repo Session event that\n * persistence could not safely recognize on restore.\n *\n * @module dsh-channels/session/request-policy\n */\n\nimport type { AgentContext, CommandId, SessionEvent } from '../dsh-types.ts'\n\n/** A model selection override: a bare model keeps the composed provider. */\nexport interface ModelOverride {\n /** Provider route; absent keeps the route composed by earlier listeners. */\n readonly provider?: string\n readonly model: string\n}\n\n/** Channel-configured defaults for sessions this plugin creates. */\nexport interface RequestPolicyConfig {\n /** Configured reasoning effort (optional → adapter/provider default). */\n effort?: string\n /** Configured provider pin (optional → deployment default). */\n provider?: string\n /** Configured model pin (optional → deployment default). */\n model?: string\n}\n\nexport interface RequestPolicyState {\n readonly configuredEffort: string | undefined\n readonly configuredModel: ModelOverride | undefined\n /** Session-level `/effort` override. */\n readonly effortOverride: string | undefined\n /** Session-level `/model` override. */\n readonly modelOverride: ModelOverride | undefined\n /** Effort this channel writes into the next request, when defined. */\n readonly effectiveEffort: string | undefined\n /** Model this channel writes into the next request, when defined. */\n readonly effectiveModel: ModelOverride | undefined\n readonly effortSource: 'session' | 'config' | 'model-default'\n readonly modelSource: 'session' | 'config' | 'deployment-default'\n}\n\nexport interface RequestPolicyController {\n readonly state: RequestPolicyState\n /** Re-fold the durable log after resume or a settled command lifecycle. */\n sync(events: readonly SessionEvent[]): void\n /** Remove only this controller's Agent-scoped request wrapper. */\n dispose(): void\n}\n\ntype PendingSelection =\n | { readonly kind: 'effort'; readonly value: string | undefined }\n | { readonly kind: 'model'; readonly value: ModelOverride | undefined }\n\n/** Parse `/effort` input; only values the handler can accept. */\nfunction effortSelection(args: string | undefined): string | undefined | 'invalid' {\n if (args === undefined) return 'invalid'\n const value = args.trim()\n if (value === '' || /\\s/u.test(value)) return 'invalid'\n return value === 'reset' ? undefined : value\n}\n\n/**\n * Parse `/model` input: `reset`, `<model>` (keeps the composed provider), or\n * `<provider>/<model>`.\n */\nfunction modelSelection(args: string | undefined): ModelOverride | undefined | 'invalid' {\n if (args === undefined) return 'invalid'\n const value = args.trim()\n if (value === '' || /\\s/u.test(value)) return 'invalid'\n if (value === 'reset') return undefined\n const slash = value.indexOf('/')\n if (slash === -1) return { model: value }\n const provider = value.slice(0, slash)\n const model = value.slice(slash + 1)\n if (provider === '' || model === '' || model.includes('/')) return 'invalid'\n return { provider, model }\n}\n\n/** One folded policy: the last successful Session-level selections. */\nexport interface FoldedRequestPolicy {\n effort: string | undefined\n model: ModelOverride | undefined\n}\n\n/**\n * Fold the durable log into the Session-level policy.\n *\n * A run changes nothing until its matching `command/done` succeeds. Failed,\n * incomplete, query-only, and malformed commands are therefore inert.\n */\nexport function foldRequestPolicy(events: readonly SessionEvent[]): FoldedRequestPolicy {\n const pending = new Map<CommandId, PendingSelection>()\n let effort: string | undefined\n let model: ModelOverride | undefined\n for (const event of events) {\n if (event.type === 'command/run') {\n if (event.data.name === 'effort') {\n const selection = effortSelection(event.data.args)\n if (selection !== 'invalid') pending.set(event.data.commandId, { kind: 'effort', value: selection })\n } else if (event.data.name === 'model') {\n const selection = modelSelection(event.data.args)\n if (selection !== 'invalid') pending.set(event.data.commandId, { kind: 'model', value: selection })\n }\n continue\n }\n if (event.type !== 'command/done') continue\n const selection = pending.get(event.data.commandId)\n if (selection === undefined) continue\n pending.delete(event.data.commandId)\n if (event.data.kind !== 'success') continue\n if (selection.kind === 'effort') effort = selection.value\n else model = selection.value\n }\n return { effort, model }\n}\n\n/** Fold only the last successful Session-level `/effort` selection. */\nexport function foldEffortOverride(events: readonly SessionEvent[]): string | undefined {\n return foldRequestPolicy(events).effort\n}\n\n/** Fold only the last successful Session-level `/model` selection. */\nexport function foldModelOverride(events: readonly SessionEvent[]): ModelOverride | undefined {\n return foldRequestPolicy(events).model\n}\n\n/** Install the mutable request policy outside every other request wrapper. */\nexport function installRequestPolicy(\n agentCtx: AgentContext,\n config: RequestPolicyConfig = {},\n): RequestPolicyController {\n let effortOverride: string | undefined\n let modelOverride: ModelOverride | undefined\n let disposed = false\n const configuredModel: ModelOverride | undefined = config.provider !== undefined && config.model !== undefined\n ? { provider: config.provider, model: config.model }\n : undefined\n\n const disposeListener = agentCtx.on('agent/request', async (_payload, next) => {\n const request = await next()\n const effort = effortOverride ?? config.effort\n const model = modelOverride ?? configuredModel\n if (effort === undefined && model === undefined) return request\n return {\n ...request,\n ...(effort === undefined ? {} : { reasoningEffort: effort }),\n ...(model === undefined ? {} : { model: model.model }),\n ...(model?.provider === undefined ? {} : { provider: model.provider }),\n }\n }, { prepend: true })\n\n return {\n get state(): RequestPolicyState {\n const effectiveEffort = effortOverride ?? config.effort\n const effectiveModel = modelOverride ?? configuredModel\n return {\n configuredEffort: config.effort,\n configuredModel,\n effortOverride,\n modelOverride,\n effectiveEffort,\n effectiveModel,\n effortSource: effortOverride !== undefined\n ? 'session'\n : config.effort !== undefined ? 'config' : 'model-default',\n modelSource: modelOverride !== undefined\n ? 'session'\n : configuredModel !== undefined ? 'config' : 'deployment-default',\n }\n },\n sync(events) {\n const folded = foldRequestPolicy(events)\n effortOverride = folded.effort\n modelOverride = folded.model\n },\n dispose() {\n if (disposed) return\n disposed = true\n disposeListener()\n },\n }\n}\n","/**\n * Workspace directory resolution: one DSH workspace per (user × chat).\n *\n * Every Feishu topic gets its own workspace, grouped by the (chat, user) pair:\n * - p2p: `<baseDir>/p2p/<open_id>/<topic>/`\n * - group: `<baseDir>/groups/<chat_id>/<open_id>/<topic>/`\n *\n * Attachments received before a topic exists are staged below\n * `<baseDir>/_staging/<chatType>/<chat_id>/<open_id>/` and adopted into the\n * topic workspace once its first Session is created.\n *\n * Feishu ids (`ou_…`, `oc_…`) are safe path segments, but they are validated\n * anyway so a hostile id can never escape `baseDir`.\n *\n * @module dsh-channels/workspaces\n */\n\nimport { constants } from 'node:fs'\nimport { access, mkdir, stat } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport path from 'node:path'\nimport type { HostContext, Workspace } from './dsh-types.ts'\n\nconst SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/\n\n/** Upper bound for a panel-supplied workspace root (filesystem path limit headroom). */\nconst MAX_BASE_DIR_LENGTH = 4096\n\n/** Lexical validation result for a workspace root. */\nexport type BaseDirCheck =\n | { ok: true; value: string }\n | { ok: false; message: string }\n\n/**\n * Validate and normalize a workspace root supplied by the settings panel.\n *\n * The profile layer keeps accepting whatever the deployment wrote (including\n * the historical relative default), but a value *typed into the Web panel*\n * must name an unambiguous location: a relative path would resolve against\n * the host process's unpredictable cwd. `~` is expanded for convenience and\n * the result is normalized; the root itself is rejected because workspace\n * directories are appended to it.\n *\n * @param raw - untrusted panel value.\n * @param home - home directory seam (tests pin it; production uses `homedir()`).\n * @returns the normalized absolute path, or a user-facing message.\n */\nexport function normalizeWorkspaceBaseDir(raw: unknown, home: string = homedir()): BaseDirCheck {\n if (typeof raw !== 'string') return { ok: false, message: '必须是字符串' }\n const trimmed = raw.trim()\n if (trimmed === '') return { ok: false, message: '不能为空' }\n if (trimmed.length > MAX_BASE_DIR_LENGTH) {\n return { ok: false, message: `路径长度不得超过 ${MAX_BASE_DIR_LENGTH} 个字符` }\n }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(trimmed)) return { ok: false, message: '包含非法控制字符' }\n let expanded = trimmed\n if (expanded === '~') {\n expanded = home\n } else if (expanded.startsWith('~/') || expanded.startsWith('~\\\\')) {\n expanded = path.join(home, expanded.slice(2))\n }\n // Check absoluteness before resolving: `path.resolve` would silently turn a\n // relative panel value into a cwd-dependent absolute path.\n if (!path.isAbsolute(expanded)) {\n return { ok: false, message: '必须是绝对路径(例如 /Users/you/.dsh/dsh-channels/workspaces)' }\n }\n const normalized = path.resolve(expanded)\n if (path.parse(normalized).root === normalized) {\n return { ok: false, message: '不能使用文件系统根目录' }\n }\n return { ok: true, value: normalized }\n}\n\n/**\n * Materialize one panel-supplied workspace root and prove it is usable.\n *\n * Called when the panel saves `baseDir` so an unusable location fails at save\n * time instead of surfacing later as \"会话创建失败\" inside a Feishu chat.\n * The directory is created here (the same `mkdir -p` the first session would\n * perform); an existing non-directory is refused.\n *\n * @returns an error message, or `undefined` when the directory is writable.\n */\nexport async function prepareWorkspaceBaseDir(dir: string): Promise<string | undefined> {\n try {\n await mkdir(dir, { recursive: true })\n } catch (error) {\n return `无法创建工作区目录:${String(error)}`\n }\n try {\n const info = await stat(dir)\n if (!info.isDirectory()) return '该路径已存在但不是目录'\n } catch (error) {\n return `无法读取工作区目录:${String(error)}`\n }\n try {\n await access(dir, constants.W_OK)\n } catch {\n return '工作区目录不可写'\n }\n return undefined\n}\n\nexport function safeSegment(raw: string, what: string): string {\n const segment = raw.replace(/^\\/+|\\/+$/g, '')\n if (!SAFE_SEGMENT.test(segment) || segment === '.' || segment === '..') {\n throw new Error(`dsh-channels: unsafe ${what} for workspace path: ${JSON.stringify(raw)}`)\n }\n return segment\n}\n\n/**\n * Stable, filesystem-safe token for one Feishu conversation: a real topic uses\n * its thread id, while a degraded chat-scoped conversation uses the Session id\n * that owns it (there is no topic boundary to key on).\n */\nexport function topicTokenFor(\n routeKey: string,\n kind: 'thread' | 'chat',\n sessionId: string,\n): string {\n return kind === 'thread' ? safeSegment(routeKey, 'route key') : safeSegment(sessionId, 'session id')\n}\n\n/**\n * Deterministic workspace directory for one Feishu topic: every route gets its\n * own workspace below its (user × chat) grouping directory.\n *\n * - p2p: `<baseDir>/topics/p2p/<open_id>/<topic>/`\n * - group: `<baseDir>/topics/groups/<chat_id>/<open_id>/<topic>/`\n *\n * A separate `topics/` root keeps new workspaces out of the legacy (user ×\n * chat) workspace directories left by older versions, so cleaning one can\n * never delete the other.\n */\nexport function workspacePathFor(\n baseDir: string,\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n topicToken: string,\n): string {\n const chat = safeSegment(chatId, 'chatId')\n const user = safeSegment(senderOpenId, 'senderOpenId')\n const topic = safeSegment(topicToken, 'topic token')\n return chatType === 'p2p'\n ? path.join(baseDir, 'topics', 'p2p', user, topic)\n : path.join(baseDir, 'topics', 'groups', chat, user, topic)\n}\n\n/**\n * Staging directory for attachments that arrive before their topic (and so\n * its workspace) exists. Not a registered workspace: files move into the real\n * workspace as soon as the first Session of that topic is created.\n */\nexport function stagingPathFor(\n baseDir: string,\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n): string {\n const chat = safeSegment(chatId, 'chatId')\n const user = safeSegment(senderOpenId, 'senderOpenId')\n return path.join(baseDir, '_staging', chatType, chat, user)\n}\n\n/** Human-friendly workspace title shown in the DSH workspace list. */\nexport function workspaceTitleFor(\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n topicToken?: string,\n): string {\n const base = chatType === 'p2p'\n ? `飞书私聊 · ${senderOpenId}`\n : `飞书群聊 · ${chatId} · ${senderOpenId}`\n return topicToken === undefined ? base : `${base} · ${topicToken}`\n}\n\n/**\n * Ensure one topic's workspace directory exists and is registered with the\n * host's workspace registry. Idempotent: repeated calls return the same record.\n */\nexport async function ensureWorkspace(\n ctx: HostContext,\n baseDir: string,\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n topicToken: string,\n): Promise<Workspace> {\n const dir = workspacePathFor(baseDir, chatType, chatId, senderOpenId, topicToken)\n await mkdir(dir, { recursive: true })\n return ctx.workspaceRegistry.create(\n dir,\n workspaceTitleFor(chatType, chatId, senderOpenId, topicToken),\n )\n}\n","/**\n * Agent/session lifecycle for this channel.\n *\n * Owns the live-agent registry (including agents BORROWED from another owner),\n * cold resume serialization, agent-preset composition, the per-session\n * reasoning-effort policy, and the `/renew` transaction that atomically binds\n * a new Session to an existing Feishu route.\n *\n * `agents.create()` never applies a preset on its own — the caller composes\n * the agent's world through the `setup` callback, which the factory awaits\n * before publishing the session. In web-profile deployments the model-facing\n * tools exist only inside agent presets (the host disables the tool rows at\n * the host layer), so sessions created without mounting one reach the model\n * with an empty tool list: the agent writes `<tool_calls>` as plain text and\n * stops. Web-UI sessions mount the default preset via `session.create`;\n * channels sessions must do the same through `setup`, and record the preset id\n * on the header meta exactly like the host's own `composeAgent` does.\n *\n * @module dsh-channels/session/manager\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type {\n Agent, AgentContext, AgentHandle, AgentPresets, AgentSetup, HostContext, Session, SessionId,\n} from '../dsh-types.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport { mountChannelCommands } from '../commands.ts'\nimport { appendSessionHistory } from '../domain.ts'\nimport type { RouteRecord } from '../domain.ts'\nimport { mountChannelGuidance } from '../feishu/agent-context.ts'\nimport { attachmentsDir } from '../feishu/inbound-attachments.ts'\nimport { installRequestPolicy } from './request-policy.ts'\nimport type { RequestPolicyController } from './request-policy.ts'\nimport { workspaceTitleFor } from '../workspaces.ts'\n\n/** One live agent plus this plugin's ownership handle. */\nexport interface LiveAgent {\n agent: Agent\n handle: AgentHandle\n}\n\n/** A committed `/renew`: the old and new route records plus the old handle. */\nexport interface RenewCommit {\n previous: RouteRecord\n current: RouteRecord\n oldLive: LiveAgent\n}\n\n/** What `agents.create`/`resume` need to compose one agent from a preset. */\nexport interface PresetComposition {\n /** Preset id recorded on the session header meta (absent without a roster). */\n agentPreset?: string\n /** Mounts the resolved preset inside the agent factory's setup. */\n setup?: AgentSetup\n}\n\nexport interface SessionManagerDeps {\n ctx: HostContext\n config: FeishuConfig\n /** Persist one route record (the `/renew` commit writes the new binding). */\n persistRoute(key: string, record: RouteRecord): Promise<void>\n /** Whether the service still accepts work (shutdown race guard). */\n started(): boolean\n /** Drop streamer state for a session whose owner handle is being released. */\n detachStream(sessionId: string): void\n warn(message: string, ...args: unknown[]): void\n}\n\n/**\n * Wrap a live agent owned by someone else (web UI api-remote resolver, a\n * previous plugin instance, a won cold-resume race). The borrow is fully\n * deliverable through `followup`; disposal releases only the channel-owned\n * scoped policy and never the foreign Agent itself.\n */\nfunction borrowLiveAgent(agent: Agent, release: () => void = () => {}): LiveAgent {\n return { agent, handle: { agent, dispose: async () => { release() } } }\n}\n\nexport class SessionManager {\n private readonly live = new Map<string, LiveAgent>()\n private readonly pendingResume = new Map<string, Promise<LiveAgent>>()\n private readonly policies = new WeakMap<AgentContext, RequestPolicyController>()\n\n constructor(private readonly deps: SessionManagerDeps) {}\n\n /** Live agent entry for a session, when this plugin holds one. */\n get(sessionId: string): LiveAgent | undefined {\n return this.live.get(sessionId)\n }\n\n /** Number of live agent entries this plugin owns or borrows. */\n liveCount(): number {\n return this.live.size\n }\n\n /** Provider/model/token caps sessions this plugin creates. */\n agentOptions(): Record<string, unknown> {\n const options: Record<string, unknown> = {}\n const { provider, model, maxTokens } = this.deps.config\n if (provider !== undefined) options.provider = provider\n if (model !== undefined) options.model = model\n if (maxTokens !== undefined) options.maxTokens = maxTokens\n return options\n }\n\n /**\n * Create the root agent for one freshly routed session and register it live.\n * The caller owns the workspace attachment and the failure card.\n */\n async createSession(\n sessionId: SessionId,\n cwd: string,\n agentOptions: Record<string, unknown> = this.agentOptions(),\n ): Promise<AgentHandle> {\n const composition = await this.presetComposition(undefined, true, attachmentsDir(cwd))\n const handle = await this.deps.ctx.agents.create({\n sessionId,\n meta: {\n cwd,\n ...(composition.agentPreset === undefined ? {} : { agentPreset: composition.agentPreset }),\n },\n agentOptions,\n setup: composition.setup,\n })\n this.requestPolicy(handle.agent)\n this.live.set(sessionId, { agent: handle.agent, handle })\n return handle\n }\n\n /**\n * Live agent for a route: this plugin's own → a foreign live agent (borrow)\n * → a serialized cold resume.\n *\n * The session may already be live under another owner — the web UI's\n * api-remote resolver, a previous plugin instance whose shutdown is still\n * draining, or a concurrent cold resume that won the race. `agents.resume`\n * refuses a live session (\"cannot prepare … while it is live\"), so BORROW\n * the live agent instead: `followup` delivery works identically and the\n * owning side keeps the dispose responsibility.\n */\n async ensureLive(record: RouteRecord): Promise<LiveAgent> {\n const sessionId = record.sessionId\n const own = this.live.get(sessionId)\n if (own !== undefined) return own\n\n const foreign = this.deps.ctx.agents.get(sessionId as SessionId)\n if (foreign !== undefined) return this.borrow(sessionId, foreign)\n\n let pending = this.pendingResume.get(sessionId)\n if (pending === undefined) {\n pending = this.resume(record).finally(() => {\n this.pendingResume.delete(sessionId)\n })\n this.pendingResume.set(sessionId, pending)\n }\n return await pending\n }\n\n /** Borrow one foreign live agent and mount this channel's scoped policy. */\n private borrow(sessionId: string, agent: Agent): LiveAgent {\n const borrowed = borrowLiveAgent(agent, this.mountRequestPolicy(agent.ctx, agent.session))\n this.live.set(sessionId, borrowed)\n return borrowed\n }\n\n /** Cold-resume one persisted session, losing a resume race gracefully. */\n private async resume(record: RouteRecord): Promise<LiveAgent> {\n const sessionId = record.sessionId as SessionId\n try {\n // Compose the resumed world from the same preset resolution as creation\n // (config override or roster default). A session persisted before the\n // preset fix has no preset recorded on its header; re-resolving here is\n // what repairs it on the first resume after a restart.\n const composition = await this.presetComposition(\n undefined, true, attachmentsDir(record.workspacePath),\n )\n const handle = await this.deps.ctx.agents.resume({\n resumeSessionId: sessionId,\n agentOptions: this.agentOptions(),\n setup: composition.setup,\n })\n if (!this.deps.started()) {\n // Shutdown raced this in-flight resume: dispose the just-published\n // agent so it cannot leak into the live registry of the next instance\n // (whose every resume would then fail as \"while it is live\").\n void handle.dispose().catch(() => {})\n throw new Error('dsh-channels: session resume aborted by shutdown')\n }\n this.requestPolicy(handle.agent)\n const liveAgent: LiveAgent = { agent: handle.agent, handle }\n this.live.set(sessionId, liveAgent)\n return liveAgent\n } catch (error) {\n // Lost a cold-resume race: another owner published the session while our\n // resume was in flight. Borrow the winner instead of failing the turn.\n const winner = this.deps.ctx.agents.get(sessionId)\n if (winner !== undefined) return this.borrow(sessionId, winner)\n throw error\n }\n }\n\n /** Mount one mutable Session-aware request policy outside every other wrapper. */\n mountRequestPolicy(agentCtx: AgentContext, session?: Session): () => void {\n const controller = installRequestPolicy(agentCtx, {\n effort: this.deps.config.reasoningEffort,\n provider: this.deps.config.provider,\n model: this.deps.config.model,\n })\n if (session !== undefined) controller.sync(session.snapshotEvents())\n this.policies.set(agentCtx, controller)\n return () => {\n controller.dispose()\n if (this.policies.get(agentCtx) === controller) this.policies.delete(agentCtx)\n }\n }\n\n /** Resolve and synchronize the policy composed for this exact Agent scope. */\n requestPolicy(agent: Agent): RequestPolicyController {\n const controller = this.policies.get(agent.ctx)\n if (controller === undefined) {\n throw new Error('dsh-channels: request policy is not mounted for this Agent')\n }\n controller.sync(agent.session.snapshotEvents())\n return controller\n }\n\n /** Create, workspace-attach, and atomically publish one renewed route. */\n async renew(record: RouteRecord, oldLive: LiveAgent): Promise<RenewCommit> {\n const sessionId = randomUUID() as SessionId\n const preset = this.effectivePreset(oldLive.agent.session)\n const composition = await this.presetComposition(preset, false, attachmentsDir(record.workspacePath))\n const workspace = await this.deps.ctx.workspaceRegistry.create(\n record.workspacePath,\n workspaceTitleFor(\n record.chatType, record.chatId, record.senderOpenId,\n record.kind === 'thread' ? record.key : record.sessionId,\n ),\n )\n\n let newHandle: AgentHandle | undefined\n let attached = false\n try {\n newHandle = await this.deps.ctx.agents.create({\n sessionId,\n meta: {\n cwd: record.workspacePath,\n ...(composition.agentPreset === undefined ? {} : { agentPreset: composition.agentPreset }),\n },\n agentOptions: { ...oldLive.agent.options },\n setup: composition.setup,\n })\n this.requestPolicy(newHandle.agent)\n await workspace.attachSession(sessionId)\n attached = true\n const createdAt = newHandle.agent.session.header.createdAt\n\n const current: RouteRecord = {\n ...record,\n sessionId,\n sessionHistory: appendSessionHistory(record, sessionId, createdAt),\n lastActivityAt: Date.now(),\n }\n await this.deps.persistRoute(record.key, current)\n this.live.set(sessionId, { agent: newHandle.agent, handle: newHandle })\n return { previous: record, current, oldLive }\n } catch (error) {\n if (attached) {\n await Promise.resolve(workspace.detachSession(sessionId)).catch((detachError: unknown) => {\n this.deps.warn(\n 'dsh-channels: rolling back workspace attachment for session %s failed: %s',\n sessionId, String(detachError),\n )\n })\n }\n await newHandle?.dispose().catch(() => {})\n throw error\n }\n }\n\n /** Release only this plugin's old handle, after `command/done` has landed. */\n async releaseRenewed(commit: RenewCommit): Promise<void> {\n const oldSessionId = commit.previous.sessionId\n if (this.live.get(oldSessionId) === commit.oldLive) {\n this.live.delete(oldSessionId)\n this.deps.detachStream(oldSessionId)\n }\n try {\n await commit.oldLive.handle.dispose()\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: releasing renewed session %s handle failed: %s',\n oldSessionId, String(error),\n )\n }\n }\n\n /** Stop and dispose every live agent this plugin owns. */\n async disposeAll(): Promise<void> {\n for (const { handle } of this.live.values()) {\n await handle.dispose().catch(() => {})\n }\n this.live.clear()\n this.pendingResume.clear()\n }\n\n /**\n * Resolve the agent-preset composition for sessions this plugin creates or\n * resumes. The id resolves BEFORE the session exists because the session\n * boundary snapshots `meta` before asynchronous setup begins; mounting stays\n * inside `setup`, where a failure rolls the whole creation back.\n */\n private async presetComposition(\n requestedPreset: string | undefined = this.deps.config.agentPreset,\n useRosterDefault = true,\n attachmentsDir?: string,\n ): Promise<PresetComposition> {\n // Read through ctx.get (safe lookup): only the web profile ships a roster,\n // and direct property access on a cordis context throws for services the\n // plugin did not inject.\n const presets = this.deps.ctx.get?.('agentPresets') as AgentPresets | undefined\n const channelCommandsOnly = async (agentCtx: AgentContext): Promise<void> => {\n // `prepend` is essential: standing preset listeners may have been\n // mounted before this Agent existed, so setup registration order alone\n // cannot guarantee that the explicit channel value wins.\n this.mountRequestPolicy(agentCtx)\n await mountChannelCommands(agentCtx)\n await mountChannelGuidance(agentCtx, attachmentsDir)\n }\n if (presets === undefined || (requestedPreset === undefined && !useRosterDefault)) {\n return { setup: channelCommandsOnly }\n }\n const resolved = await presets.resolve(requestedPreset)\n return {\n agentPreset: resolved.id,\n setup: async (agentCtx) => {\n this.mountRequestPolicy(agentCtx)\n await presets.mount(agentCtx, resolved.id)\n await mountChannelCommands(agentCtx)\n await mountChannelGuidance(agentCtx, attachmentsDir)\n },\n }\n }\n\n /** Effective preset after folding any durable runtime preset switch. */\n private effectivePreset(session: Session): string | undefined {\n let selected = session.header.agentPreset\n for (const event of session.snapshotEvents()) {\n if (event.type === 'agent-preset/selected') selected = event.data.agentPreset\n }\n return selected\n }\n}\n","/**\n * Outbound Feishu delivery: card sends, the topic-creating first reply, body\n * segment fallbacks, and the command reaction lifecycle.\n *\n * Every send is gated on the service being started, and commands are the only\n * plane that adds/deletes reactions (`Typing` → `DONE`); ordinary turns get\n * their reaction lifecycle from the streamer's finalize path.\n *\n * @module dsh-channels/feishu/delivery\n */\n\nimport { buildCommandResultCard } from '../commands.ts'\nimport type { CommandCardState } from '../commands.ts'\nimport type { RouteRecord } from '../domain.ts'\nimport type { FeishuChannel, NormalizedMessage, SendResult } from '../feishu-channel.ts'\n\n/** Rejection text for a message in a topic retired by route TTL. */\nexport const EXPIRED_ROUTE_TEXT = '该话题已超过路由保留时间,并已与原 Session 解绑。为避免误接续上下文,插件不会在此话题创建新 Session;请从私聊或群聊主会话发起新消息(群聊请 @ 机器人)创建新话题。'\n\nexport interface CardDeliveryDeps {\n /** The live channel, or undefined before `start()` / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Whether the service is accepting sends. */\n started(): boolean\n /** Route owning a session (segment-card anchoring). */\n routeForSession(sessionId: string): RouteRecord | undefined\n warn(message: string, ...args: unknown[]): void\n}\n\nexport class CardDelivery {\n constructor(private readonly deps: CardDeliveryDeps) {}\n\n /** The live channel; throws instead of silently dropping a delivery. */\n requireChannel(): FeishuChannel {\n const channel = this.deps.channel()\n if (channel === undefined) throw new Error('dsh-channels: channel not started')\n return channel\n }\n\n /** Apply the command-only Typing → DONE reaction lifecycle. */\n async withCommandReaction(msg: NormalizedMessage, operation: () => Promise<void>): Promise<void> {\n if (!this.deps.started()) return\n const channel = this.requireChannel()\n const reactionId = await channel.addReaction(msg.messageId, 'Typing').catch(() => undefined)\n try {\n await operation()\n } finally {\n if (reactionId !== undefined) {\n await channel.removeReaction(msg.messageId, reactionId).catch(() => {})\n }\n await channel.addReaction(msg.messageId, 'DONE').catch(() => {})\n }\n }\n\n /** Standalone, non-streaming command result card with a non-threaded fallback. */\n async commandCard(\n msg: NormalizedMessage,\n state: CommandCardState,\n options: { createThread?: boolean } = {},\n ): Promise<void> {\n await this.standaloneReply(msg, buildCommandResultCard(state), options)\n }\n\n /** Reject a post-TTL topic message without creating or resuming an Agent. */\n async expiredRouteCard(msg: NormalizedMessage, command?: string): Promise<void> {\n await this.commandCard(msg, {\n kind: 'expired',\n ...(command === undefined ? {} : { command }),\n text: EXPIRED_ROUTE_TEXT,\n })\n }\n\n /**\n * Shared delivery policy for command-plane cards: reply in a new topic by\n * default, then degrade to a plain reply when Feishu refuses the topic.\n */\n async standaloneReply(\n msg: NormalizedMessage,\n card: object,\n options: { createThread?: boolean } = {},\n ): Promise<void> {\n const channel = this.deps.channel()\n if (!this.deps.started() || channel === undefined) return\n const createThread = options.createThread ?? true\n try {\n await channel.send(msg.chatId, { card }, createThread\n ? { replyTo: msg.messageId, replyInThread: true }\n : { replyTo: msg.messageId })\n } catch (error) {\n if (!createThread) {\n this.deps.warn(\n 'dsh-channels: direct command result delivery failed for message %s: %s',\n msg.messageId, String(error),\n )\n return\n }\n try {\n await channel.send(msg.chatId, { card }, { replyTo: msg.messageId })\n } catch (fallbackError) {\n this.deps.warn(\n 'dsh-channels: command result delivery failed for message %s: %s / %s',\n msg.messageId, String(error), String(fallbackError),\n )\n }\n }\n }\n\n /**\n * Send one short plain-text notice anchored to an inbound message. Replies\n * inside an existing topic when the message belongs to one, and never\n * creates a topic: an attachment-only message must stay a notice, not a new\n * conversation. Best-effort — a failed notice only warns.\n */\n async noticeText(msg: NormalizedMessage, text: string): Promise<void> {\n const channel = this.deps.channel()\n if (!this.deps.started() || channel === undefined) return\n try {\n await channel.send(msg.chatId, { text }, {\n replyTo: msg.messageId,\n ...(msg.threadId === undefined ? {} : { replyInThread: true }),\n })\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: attachment notice delivery failed for message %s: %s',\n msg.messageId, String(error),\n )\n }\n }\n\n /**\n * Reply with the first card frame. Prefers topic replies (`reply_in_thread`)\n * — the documented way a bot creates a topic — and falls back to a plain\n * reply when Feishu refuses (degraded chat-scoped routing).\n */\n async replyFirstCard(\n msg: NormalizedMessage,\n card: object,\n ): Promise<{ messageId: string; threadId?: string; degraded: boolean }> {\n const channel = this.requireChannel()\n try {\n const res = await channel.send(\n msg.chatId,\n { card },\n { replyTo: msg.messageId, replyInThread: true },\n )\n const threadId = await channel.threadIdOfMessage(res.messageId).catch(() => undefined)\n return { messageId: res.messageId, threadId, degraded: false }\n } catch (error) {\n if (!this.deps.started()) throw error\n const res = await channel.send(msg.chatId, { card }, { replyTo: msg.messageId })\n return { messageId: res.messageId, degraded: true }\n }\n }\n\n /**\n * Send one body-segment continuation card into the session's topic (or, for\n * degraded chat-scoped routes, as a plain reply anchored to the main card).\n * When Feishu rejects the card (e.g. per-card table limit, error 11310) the\n * segment is re-delivered as a plain-text message so the content is never\n * lost — text messages carry no card constraints.\n */\n async segmentCard(\n sessionId: string,\n segment: number,\n card: object,\n fallbackText: string,\n ): Promise<{ messageId: string; mode: 'card' | 'text' }> {\n const record = this.deps.routeForSession(sessionId)\n if (record === undefined) {\n throw new Error(`dsh-channels: no route record for session ${sessionId}; segment card not sent`)\n }\n const channel = this.requireChannel()\n const options = { replyTo: record.cardMessageId, replyInThread: record.kind === 'thread' }\n try {\n const res: SendResult = await channel.send(record.chatId, { card }, options)\n return { messageId: res.messageId, mode: 'card' }\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: segment %d card for session %s rejected, falling back to plain text: %s',\n segment, sessionId, String(error),\n )\n const res = await channel.send(record.chatId, {\n text: `📄 回复分段 ${segment}(卡片发送失败,已降级为纯文本)\\n\\n${fallbackText}`,\n }, options)\n return { messageId: res.messageId, mode: 'text' }\n }\n }\n}\n","/**\n * Inbound routing: dedup, admission, the route decision tree, and the\n * session-establishment / continuation flow.\n *\n * Everything here is channel-plane policy; the command plane (slash commands)\n * and the answerers stay in the service and are reached through narrow ports.\n * Dependency reads go through accessors so the moved logic stays readable.\n *\n * @module dsh-channels/routing/inbound\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { AgentHandle, HostContext, SessionId, UserMessage, Workspace } from '../dsh-types.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport { buildCard, buildOnboardingCard, initialStreamingCard } from '../cards.ts'\nimport { RouteOperationQueue } from '../commands.ts'\nimport { chatRouteKey } from '../domain.ts'\nimport type { FeishuDomainHandle, RouteRecord } from '../domain.ts'\nimport type { CardDelivery } from '../feishu/delivery.ts'\nimport {\n adoptStagedAttachments, attachmentsDir, hasAttachmentPayload,\n} from '../feishu/inbound-attachments.ts'\nimport { AttachmentIntake } from '../feishu/inbound-attachments.ts'\nimport type { AttachmentArchiveResult, AttachmentFailure } from '../feishu/inbound-attachments.ts'\nimport type { NormalizedMessage } from '../feishu-channel.ts'\nimport type { QuestionAnswerer } from '../interactions/questions.ts'\nimport type { InflightRepair } from '../session/repair.ts'\nimport type { SessionManager } from '../session/manager.ts'\nimport type { RouteStore } from '../session/routes.ts'\nimport type { TurnStreamer } from '../streamer.ts'\nimport { ensureWorkspace, stagingPathFor, topicTokenFor } from '../workspaces.ts'\n\n/** The command plane the inbound router delegates admission-time work to. */\nexport interface CommandPlanePort {\n /** Slash-command admission + dispatch. */\n handleSlash(msg: NormalizedMessage, line: string): Promise<void>\n}\n\n/**\n * Channel-plane admission port (structurally satisfied by the access\n * controller). Kept as a local interface so routing has no dependency on the\n * access module's implementation details.\n */\nexport interface InboundAccessPort {\n /** Full admission for a non-slash message; false = handled (deny/notice). */\n admit(msg: NormalizedMessage): Promise<boolean>\n /**\n * Route ownership: only the sender who created a topic may continue it.\n * Also guards against cross-chat id confusion.\n */\n ownsRoute(msg: NormalizedMessage, record: RouteRecord): boolean\n}\n\nexport interface InboundDeps {\n ctx: HostContext\n config: FeishuConfig\n started(): boolean\n domain(): FeishuDomainHandle | undefined\n routes: RouteStore\n repair: InflightRepair\n delivery: CardDelivery\n sessions: SessionManager\n attachments: AttachmentIntake\n /** Channel-plane access control (administrator + whitelist gates). */\n access: InboundAccessPort\n /** Shared per-route FIFO admission (commands and turns must not interleave). */\n queue: RouteOperationQueue\n streamer(): TurnStreamer | undefined\n questionAnswerer(): QuestionAnswerer | undefined\n commands: CommandPlanePort\n}\n\n/**\n * User-facing text for attachments that could not be archived. Delivered as a\n * plain reply (never a card) so the user learns why the Agent cannot read a\n * file instead of silently getting an answer about older attachments.\n */\nexport function buildAttachmentFailureNotice(failures: readonly AttachmentFailure[]): string {\n const lines = failures.map(failure => `• ${failure.name}:${failure.reason}`)\n return [\n '⚠️ 附件保存失败,Agent 无法读取:',\n ...lines,\n '可尝试压缩/拆分后重发,或把文件直接放到工作区目录。',\n ].join('\\n')\n}\n\n/** Build one durable user message from inbound Feishu text. */\nexport function feishuUserMessage(text: string): UserMessage {\n return {\n id: randomUUID(),\n role: 'user',\n content: [{ type: 'text', text }],\n source: { kind: 'user' },\n } as unknown as UserMessage\n}\n\nexport class InboundRouter {\n /**\n * In-process dedup guard: message id → accept timestamp. Set synchronously\n * at acceptance so a same-process redelivery can never slip through the\n * window between the persisted dedup check and its `put` landing.\n */\n private readonly accepted = new Map<string, number>()\n /**\n * chat-scoped key → in-flight session establishment. Concurrent first\n * messages of one (chat, user) pair all observe the same promise: they wait\n * for the establishment to settle, then either continue the just-created\n * session (same topic, or degraded chat route) or fall through to start\n * their own — never racing each other into duplicate sessions, route\n * overwrites, or the \"no route, no mention\" drop window.\n */\n private readonly establishing = new Map<string, Promise<RouteRecord>>()\n\n constructor(private readonly deps: InboundDeps) {}\n\n private get ctx(): HostContext { return this.deps.ctx }\n private get config(): FeishuConfig { return this.deps.config }\n private get started(): boolean { return this.deps.started() }\n private get routes(): RouteStore { return this.deps.routes }\n private get repair(): InflightRepair { return this.deps.repair }\n private get delivery(): CardDelivery { return this.deps.delivery }\n private get sessions(): SessionManager { return this.deps.sessions }\n private get attachments(): AttachmentIntake { return this.deps.attachments }\n private get routeOperations(): RouteOperationQueue { return this.deps.queue }\n private get streamer(): TurnStreamer | undefined { return this.deps.streamer() }\n private get questionAnswerer(): QuestionAnswerer | undefined { return this.deps.questionAnswerer() }\n private get domain(): FeishuDomainHandle | undefined { return this.deps.domain() }\n private get access(): InboundAccessPort { return this.deps.access }\n\n /** Retire dedup entries older than the retention cutoff (GC sweep). */\n pruneAccepted(cutoff: number): void {\n for (const [messageId, acceptedAt] of this.accepted) {\n if (acceptedAt < cutoff) this.accepted.delete(messageId)\n }\n }\n\n/**\n * Send the one-time onboarding card for a private chat, then remember the\n * chat in the domain global. Best-effort: a storage failure only risks a\n * repeated card, never a blocked session.\n */\n private async sendOnboardingCard(msg: NormalizedMessage): Promise<void> {\n const domain = this.domain\n if (domain === undefined) return\n try {\n const global = domain.getGlobal()\n const welcomed = global.welcomedChats ?? []\n if (welcomed.includes(msg.chatId)) return\n await this.delivery.standaloneReply(msg, buildOnboardingCard(), { createThread: false })\n // Bounded: keep the most recent 500 chats.\n const next = [...welcomed, msg.chatId].slice(-500)\n await domain.setGlobal({ ...global, welcomedChats: next })\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: onboarding card failed: %s', String(error))\n }\n }\n\n /** Drop all in-memory routing state (shutdown). */\n clear(): void {\n this.accepted.clear()\n this.establishing.clear()\n }\n\n /** Entry point: one normalized inbound message. */\n async handle(msg: NormalizedMessage): Promise<void> {\n const botId = this.delivery.requireChannel().botOpenId()\n if (botId !== undefined && msg.senderId === botId) return\n if (msg.chatType !== 'p2p' && msg.chatType !== 'group') return\n let content = (msg.content ?? '').trim()\n if (content === '') return\n\n if (msg.chatType === 'p2p' && this.config.dmMode === 'disabled') return\n\n // 0. Idempotency gate: Feishu redelivers un-acked events on WS reconnect.\n // The in-process guard closes the window between the persisted check\n // and its `put` landing; the durable record covers restarts. Recording\n // happens BEFORE any side effect so a redelivery is dropped, never\n // double-processed (at-most-once per message id).\n if (this.accepted.has(msg.messageId)) return\n if (this.domain?.processed.get(msg.messageId) !== undefined) return\n this.accepted.set(msg.messageId, Date.now())\n try {\n await this.domain?.processed.put(msg.messageId, {\n key: msg.messageId,\n processedAt: Date.now(),\n })\n this.accepted.delete(msg.messageId)\n } catch (error) {\n // Best-effort: the memory guard still covers this process; the failed\n // record only widens the crash window (pruned by the GC sweep later).\n this.ctx.logger?.warn?.('dsh-channels: recording processed message %s failed: %s', msg.messageId, String(error))\n }\n\n // Slash admission stays behind message-id dedup but ahead of every route,\n // mention, workspace, Session, card-stream, and model side effect. The\n // command plane applies its own administrator/bootstrap rules.\n if (content.startsWith('/')) {\n await this.deps.commands.handleSlash(msg, content)\n return\n }\n\n // Channel-plane admission (administrator / group whitelist / user\n // whitelist). Nothing below this point may run for a denied sender: no\n // TTL tombstone, attachment archive, workspace, Session, or model call.\n if (!(await this.access.admit(msg))) return\n\n // Enforce TTL at admission as well as in the periodic sweep. A message that\n // arrives after the deadline must not get one chance to revive the route\n // merely because the background GC has not run yet.\n const expiredThread = msg.threadId === undefined\n ? undefined\n : await this.routes.expireThread(msg.threadId)\n\n // A TTL-retired topic is permanently closed on the channel plane. Keeping\n // a durable tombstone avoids silently forking a fresh Session in a topic\n // whose visible history still belongs to the old Session.\n if (expiredThread !== undefined) {\n await this.delivery.expiredRouteCard(msg)\n return\n }\n\n // Attachments are archived before session work: an attachment-only message\n // produces no model turn, and a caption mixed with attachments keeps only\n // the caption as the prompt. The Agent reads the files on demand later.\n const archived = await this.archiveAttachments(msg)\n if (archived !== undefined) {\n if (archived.failures.length > 0) {\n await this.delivery.noticeText(msg, buildAttachmentFailureNotice(archived.failures))\n }\n if (archived.attachmentOnly) return\n content = archived.text\n if (content === '') return\n }\n\n // A session blocked on `ask_user_question` has no other use for an inbound\n // text message: the next reply becomes the free-text answer.\n if (await this.tryAnswerPendingQuestion(msg, content)) return\n\n // 1. Real topic route first (works without a mention, per product spec).\n if (msg.threadId !== undefined) {\n const record = this.routes.get(msg.threadId)\n if (record !== undefined) {\n // Only the topic creator may continue its Session (admission already\n // passed, but another authorized user could be in the same group).\n if (!this.access.ownsRoute(msg, record)) return\n await this.routeOperations.run(record.key, () => {\n if (!this.started) return\n return this.continueSession(msg, this.routes.get(record.key) ?? record, content)\n })\n return\n }\n }\n\n // 2. Degraded chat-scoped route (topic-less fallback sessions).\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const chatRecord = this.routes.get(chatKey)\n if (chatRecord !== undefined) {\n if (!this.access.ownsRoute(msg, chatRecord)) return\n await this.routeOperations.run(chatRecord.key, () => {\n if (!this.started) return\n return this.continueSession(msg, this.routes.get(chatRecord.key) ?? chatRecord, content)\n })\n return\n }\n\n // 3. In-flight establishment for this (chat, user) pair: wait for it and\n // re-route with serial semantics (see the `establishing` field docs).\n const pending = this.establishing.get(chatKey)\n if (pending !== undefined) {\n const record = await pending.catch(() => undefined)\n if (record !== undefined\n && (record.kind === 'chat' || (msg.threadId !== undefined && msg.threadId === record.key))) {\n await this.routeOperations.run(record.key, () => {\n if (!this.started) return\n return this.continueSession(msg, this.routes.get(record.key) ?? record, content)\n })\n return\n }\n // Thread-scoped record but this message is not in that topic (or the\n // establishment failed): fall through — a fresh session is what the\n // serial order would have produced too.\n }\n\n // 4. New session: groups require a mention (unless disabled).\n if (msg.chatType === 'group' && this.config.requireMention && !msg.mentionedBot) return\n\n await this.startSession(msg, content)\n }\n\n /**\n * Archive the attachment payload of one message, if any. Returns `undefined`\n * when the message carries no archivable payload; otherwise the archive\n * result whose `text` is the remaining prompt (possibly empty).\n *\n * Runs inside the same per-route FIFO as turns so a caption sent right after\n * a file is queued behind the file's download instead of racing it.\n */\n private async archiveAttachments(msg: NormalizedMessage): Promise<AttachmentArchiveResult | undefined> {\n if (!hasAttachmentPayload(msg)) return undefined\n\n const threadRecord = msg.threadId === undefined ? undefined : this.routes.get(msg.threadId)\n // A foreign topic must never receive this sender's attachments: the\n // archive path resolves the workspace from the route record.\n if (threadRecord !== undefined && !this.access.ownsRoute(msg, threadRecord)) return undefined\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const record = threadRecord ?? this.routes.get(chatKey)\n // A brand-new group conversation still needs a mention; a topic the bot\n // already owns does not.\n if (record === undefined && msg.chatType === 'group'\n && this.config.requireMention && !msg.mentionedBot) {\n return { saved: 0, failed: 0, failures: [], attachmentOnly: true, text: '' }\n }\n\n return await this.routeOperations.run(record?.key ?? chatKey, async () => {\n if (!this.started) return { saved: 0, failed: 0, failures: [], attachmentOnly: true, text: '' }\n // An existing topic has a workspace already; a conversation without one\n // stages files below baseDir until its first Session creates the topic\n // workspace and adopts them.\n const directory = record === undefined\n ? stagingPathFor(this.config.baseDir, msg.chatType, msg.chatId, msg.senderId)\n : attachmentsDir(record.workspacePath)\n return await this.attachments.archive(msg, directory)\n })\n }\n /**\n * Consume one plain reply as an `ask_user_question` free-text answer.\n *\n * Only sessions with a pending question are intercepted, so ordinary\n * conversation and commands are untouched.\n */\n private async tryAnswerPendingQuestion(msg: NormalizedMessage, content: string): Promise<boolean> {\n const answerer = this.questionAnswerer\n if (answerer === undefined || answerer.pendingCount() === 0) return false\n const route = await this.commandRoute(msg)\n if (route === undefined || !this.access.ownsRoute(msg, route)) return false\n if (!answerer.hasPendingFor(route.sessionId)) return false\n if (!answerer.submitText(route.sessionId, content)) return false\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'ask_user_question', sessionId: route.sessionId,\n text: '已记录你的文字回答。',\n }, { createThread: false })\n return true\n }\n /** Resolve the route visible to one command without creating a Session. */\n /** Public read-only route resolution (used by the command plane too). */\n async resolveRoute(msg: NormalizedMessage): Promise<RouteRecord | undefined> {\n return await this.commandRoute(msg)\n }\n\n private async commandRoute(msg: NormalizedMessage): Promise<RouteRecord | undefined> {\n if (msg.threadId !== undefined) {\n const threaded = this.routes.get(msg.threadId)\n if (threaded !== undefined) return threaded\n }\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const degraded = this.routes.get(chatKey)\n if (degraded !== undefined) return degraded\n const pending = this.establishing.get(chatKey)\n if (pending === undefined) return undefined\n const record = await pending.catch(() => undefined)\n if (record?.kind === 'chat') return record\n if (record !== undefined && msg.threadId === record.key) return record\n return undefined\n }\n /**\n * Serialize session establishment per (chat, user) pair. The promise is\n * registered under the chat-scoped key SYNCHRONOUSLY (the key is known\n * before any network op), so concurrent first messages all observe the same\n * in-flight establishment instead of racing each other. The entry lives\n * exactly as long as the establishment; waiters holding the promise still\n * see the settled record after the entry is gone.\n */\n private startSession(msg: NormalizedMessage, content: string): Promise<RouteRecord> {\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const existing = this.establishing.get(chatKey)\n if (existing !== undefined) return existing\n const promise = this.establishSession(msg, content, chatKey)\n this.establishing.set(chatKey, promise)\n void promise.then(\n () => { this.establishing.delete(chatKey) },\n () => { this.establishing.delete(chatKey) },\n )\n return promise\n }\n private async establishSession(msg: NormalizedMessage, content: string, chatKey: string): Promise<RouteRecord> {\n // 0. One-time onboarding for a private chat (persisted, never repeats).\n if (this.config.welcomeOnboarding && msg.chatType === 'p2p') {\n await this.sendOnboardingCard(msg)\n }\n\n // 1. \"Typing\" reaction immediately (id captured for the swap).\n const typingPromise = this.delivery.requireChannel().addReaction(msg.messageId, 'Typing').catch(() => undefined)\n\n const sessionId = randomUUID() as SessionId\n const initialStats = { sessionId, inputTokens: 0, outputTokens: 0 }\n\n // 2. First card frame — also creates the topic (reply_in_thread). The\n // topic identity is only known after Feishu answers, and it decides the\n // workspace: every new topic gets its own directory.\n const sent = await this.delivery.replyFirstCard(msg, initialStreamingCard(sessionId))\n const typingId = await typingPromise\n const key = sent.degraded || sent.threadId === undefined ? chatKey : sent.threadId\n const kind = sent.degraded || sent.threadId === undefined ? 'chat' : 'thread'\n\n // 3. Workspace for this topic, then adopt attachments staged before it.\n let workspace: Workspace\n try {\n workspace = await ensureWorkspace(\n this.ctx, this.config.baseDir, msg.chatType, msg.chatId, msg.senderId,\n topicTokenFor(key, kind, sessionId),\n )\n await adoptStagedAttachments(\n stagingPathFor(this.config.baseDir, msg.chatType, msg.chatId, msg.senderId),\n workspace.path,\n )\n } catch (error) {\n void this.repair.clear(sent.messageId)\n await this.delivery.requireChannel().updateCard(sent.messageId, buildCard({\n phase: 'done',\n reasoning: '',\n body: `会话创建失败:${String(error)}`,\n stats: { ...initialStats },\n })).catch(() => {})\n throw error\n }\n\n // 3.5 Repair snapshot FIRST (before the route record and the agent):\n // the crash window between the two writes then degrades to an orphan\n // snapshot — which startup repair handles safely — instead of a zombie\n // streaming card with no snapshot at all.\n await this.repair.write({\n key: sent.messageId,\n sessionId,\n chatId: msg.chatId,\n triggerMessageId: msg.messageId,\n reactionId: typingId,\n cards: [{ messageId: sent.messageId, mode: 'card' }],\n startedAt: Date.now(),\n attempts: 0,\n })\n\n // 4. Create the agent/session bound to the workspace directory.\n let handle: AgentHandle\n try {\n handle = await this.sessions.createSession(sessionId, workspace.path)\n } catch (error) {\n // The card lands in a final failure state — no repair needed later.\n void this.repair.clear(sent.messageId)\n await this.delivery.requireChannel().updateCard(sent.messageId, buildCard({\n phase: 'done',\n reasoning: '',\n body: `会话创建失败:${String(error)}`,\n stats: { ...initialStats },\n })).catch(() => {})\n throw error\n }\n void Promise.resolve(workspace.attachSession(sessionId)).catch(error => {\n this.ctx.logger?.warn?.(\n 'dsh-channels: attaching initial session %s to workspace failed: %s',\n sessionId, String(error),\n )\n })\n\n // 5. Persist the route (thread-scoped, or degraded chat-scoped).\n const createdAt = handle.agent.session.header.createdAt\n const record: RouteRecord = {\n key,\n kind,\n sessionId,\n chatId: msg.chatId,\n chatType: msg.chatType,\n senderOpenId: msg.senderId,\n workspacePath: workspace.path,\n cardMessageId: sent.messageId,\n createdAt,\n lastActivityAt: createdAt,\n sessionHistory: [{ sessionId, createdAt }],\n }\n await this.routes.put(key, record)\n\n // 6. Stream into the card and hand the message to the driver.\n this.streamer?.attach(handle.agent.session, sent.messageId, msg.messageId, typingId)\n handle.agent.followup(feishuUserMessage(content))\n return record\n }\n private async continueSession(msg: NormalizedMessage, record: RouteRecord, content: string): Promise<void> {\n // Defence in depth: every current caller checks this first, but a future\n // call path must not be able to continue another user's Session.\n if (!this.access.ownsRoute(msg, record)) return\n // Files staged before this conversation's topic existed follow its next\n // turn (for example a file sent in the chat before the first prompt).\n await adoptStagedAttachments(\n stagingPathFor(this.config.baseDir, msg.chatType, msg.chatId, msg.senderId),\n record.workspacePath,\n ).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: adopting staged attachments failed: %s', String(error))\n })\n const typingPromise = this.delivery.requireChannel().addReaction(msg.messageId, 'Typing').catch(() => undefined)\n const live = await this.sessions.ensureLive(record)\n\n const sent = await this.delivery.replyFirstCard(msg, initialStreamingCard(record.sessionId))\n const typingId = await typingPromise\n\n // Repair snapshot first — same crash-window reasoning as `startSession`.\n await this.repair.write({\n key: sent.messageId,\n sessionId: record.sessionId,\n chatId: msg.chatId,\n triggerMessageId: msg.messageId,\n reactionId: typingId,\n cards: [{ messageId: sent.messageId, mode: 'card' }],\n startedAt: Date.now(),\n attempts: 0,\n })\n\n await this.routes.put(record.key, {\n ...record,\n cardMessageId: sent.messageId,\n lastActivityAt: Date.now(),\n })\n this.streamer?.attach(live.agent.session, sent.messageId, msg.messageId, typingId)\n live.agent.followup(feishuUserMessage(content))\n }\n}\n","/**\n * Host bridge, service surface: workspaces, storage domains, credentials,\n * token meter, LLM catalog, session projections, Cordis diagnostics, and the\n * `HostContext` slice this plugin consumes.\n *\n * @module dsh-channels/host/services\n */\n\nimport type { AgentRegistry } from './agent.ts'\nimport type { CommandRuntime } from './commands.ts'\nimport type { AssistantStreamPayload, Session, SessionEvent, SessionId } from './core.ts'\nimport type { ApprovalAnswererListener, UserQuestionAnswererListener } from './interaction.ts'\n\n// ---- Workspace surface --------------------------------------------------\n\nexport interface Workspace {\n readonly id: string\n readonly path: string\n readonly title: string\n attachSession(id: SessionId): Promise<void> | void\n detachSession(id: SessionId): Promise<void> | void\n}\n\nexport interface WorkspaceRegistry {\n create(path: string, title?: string): Promise<Workspace>\n /** Every workspace currently registered with the host. */\n list(): Workspace[]\n /** Unregister one workspace record (the directory itself is the caller's concern). */\n delete(id: string): Promise<boolean>\n}\n\n// ---- Storage / token-meter / llm / credentials surface ------------------\n\n/** The host's storage-domain facility (narrowly typed at the call site). */\nexport interface StorageDomainFacility {\n open(spec: unknown): Promise<unknown>\n}\n\n/** One resolved credential value and the source layer that supplied it. */\nexport interface ResolvedCredential {\n value: string\n source: string\n}\n\n/** Presence/writability facts for one credential reference (never the value). */\nexport interface CredentialInfo {\n configured: boolean\n writable?: boolean\n source?: string\n}\n\n/**\n * The host credentials seam (`@deepseek-ai/dsh-credentials`): resolves a\n * reference per call with the provider's layer precedence\n * (env > $DSH_HOME/.credentials.yaml > .env fallbacks), and can store a value\n * for a reference through the same seam (the Web panel's credential editor).\n * Every member beyond the read path is optional so embedded/test hosts can\n * supply only what they support.\n */\nexport interface CredentialProvider {\n resolve(ref: string): Promise<ResolvedCredential | undefined>\n /** Presence check without revealing the value (the host's `credentials.describe`). */\n describe?(ref: string): Promise<CredentialInfo | undefined>\n /** Store a value for the reference; stored values outrank environment fallbacks. */\n set?(ref: string, value: string): Promise<void>\n /** Remove the stored value (environment fallbacks then apply again). */\n unset?(ref: string): Promise<void>\n}\n\nexport interface TokenMeasurement {\n readonly totalTokens: number\n}\n\nexport interface TokenMeter {\n measure(session: Session, requestHeader?: unknown): TokenMeasurement\n}\n\n/** One registered provider route, as advertised by the LLM service. */\nexport interface LlmProviderInfo {\n id: string\n name: string\n}\n\n/** One adapter-owned reasoning effort advertised for an exact model route. */\nexport interface LlmReasoningEffortInfo {\n /** Opaque stable id accepted by model requests. */\n id: string\n /** Human-readable effort name for selectors. */\n name: string\n description?: string\n}\n\n/** Selectable reasoning efforts for one exact provider/model route. */\nexport interface LlmModelReasoningInfo {\n /** Supported efforts from least to greatest. */\n efforts: readonly LlmReasoningEffortInfo[]\n /** Adapter-configured default effort; absent means the provider default. */\n defaultEffort?: string\n}\n\nexport interface LlmModelInfo {\n /** Provider route that owns this entry. */\n provider?: string\n /** Model id passed to request generation. */\n id?: string\n /** Human-readable model name for selectors. */\n name?: string\n description?: string\n context?: { contextWindow: number }\n /** Adapter-owned reasoning levels, when `resolveModelInfo` reports them. */\n reasoning?: LlmModelReasoningInfo\n}\n\nexport interface LlmService {\n resolveModelInfo(provider: string, model: string): Promise<LlmModelInfo>\n /** Registered provider routes, when the deployment exposes them. */\n listProviders?(): readonly LlmProviderInfo[]\n /** Models advertised for one provider route. */\n listModels?(provider: string): Promise<readonly LlmModelInfo[]>\n}\n\n/**\n * Deployment default model selection (`@deepseek-ai/dsh-agent-default-model`).\n * Web UI and headless entry points read it before creating an Agent; the\n * settings panel only reads it to label the \"inherit\" choice.\n */\nexport interface AgentDefaultModel {\n currentSelection(): { provider: string; model: string; reasoningEffort?: string }\n}\n\n// ---- Session projections -------------------------------------------------\n\n/**\n * Whole-log figures maintained by the host's `sessionStats` projection\n * (`@deepseek-ai/dsh-session-stats`, mounted by the web-app bundle). Reading\n * these replaces re-folding the event log for wall times and TTFT.\n */\nexport interface SessionStatsView {\n turns: number\n steps: number\n llmMs: number\n toolMs: number\n ttftMs: number\n ttftSteps: number\n decodeMs: number\n decodeTokens: number\n}\n\n/** Provider-reported token buckets maintained by the `tokenUsage` projection. */\nexport interface TokenUsageBuckets {\n uncachedInputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n}\n\nexport interface TokenUsageView {\n totals: TokenUsageBuckets\n last: { turn: number; step: number; buckets: TokenUsageBuckets } | null\n}\n\n/** Route capacity and prompt pressure maintained by the `contextPressure` projection. */\nexport interface ContextPressureView {\n pressureTokens?: number\n projectedTokens?: number\n contextWindow?: number\n}\n\n/**\n * The host projection registry (`ctx.sessionProjections`). Read through\n * `ctx.get` so deployments without the registry keep working: `stateOf`\n * returns `undefined` for an unregistered key.\n */\nexport interface SessionProjections {\n stateOf(session: Session, key: string): unknown\n}\n\n\n// ---- Session query -------------------------------------------------------\n\n/** Latest log-backed title of one session. */\nexport interface SessionTitleSnapshot {\n title: string\n updatedAt: number\n}\n\nexport interface SessionTitleObservation {\n /** Absent when the observed log has no title event. */\n title?: SessionTitleSnapshot\n}\n\n/** One ordered result from a batch title observation. */\nexport type SessionTitleObservationResult =\n | { sessionId: SessionId; status: 'fulfilled'; value: SessionTitleObservation }\n | { sessionId: SessionId; status: 'rejected'; reason: unknown }\n\n/**\n * The narrow read side of `ctx.sessionQuery` this plugin consumes: live-first\n * title folding for the sessions a Feishu topic has bound.\n */\nexport interface SessionQuery {\n readTitleSnapshots(sessionIds: readonly SessionId[]): Promise<readonly SessionTitleObservationResult[]>\n}\n\n// ---- Permission presets --------------------------------------------------\n\n/** One selectable sandbox+approval preset offered to clients. */\nexport interface PermissionPresetOption {\n value: string\n name: string\n description?: string\n}\n\n/** The process-level preset catalog exposed to current-session controls. */\nexport interface PermissionCatalog {\n options: PermissionPresetOption[]\n defaultOptions: PermissionPresetOption[]\n defaultPreset: string\n}\n\n/**\n * The host permission-preset service (`ctx.permissionPresets`): reads the\n * current Session's effective preset and records a switched one.\n */\nexport interface PermissionPresets {\n catalog(): PermissionCatalog\n current(session: Session): string\n optionOf(name: string): PermissionPresetOption\n set(session: Session, name: string): void\n}\n\n// ---- Cordis diagnostic surface -----------------------------------------\n\n/** Numeric values mirror Cordis 4's public `FiberState` enum. */\nexport const CordisFiberState = {\n PENDING: 0,\n LOADING: 1,\n ACTIVE: 2,\n FAILED: 3,\n DISPOSED: 4,\n UNLOADING: 5,\n} as const\n\nexport type CordisFiberState = typeof CordisFiberState[keyof typeof CordisFiberState]\n\nexport interface CordisFiber {\n readonly state: CordisFiberState\n /** Best-effort plugin display name (`fiber.name`), used only for health diagnostics. */\n readonly name?: string\n}\n\nexport interface CordisPluginRuntime {\n readonly fibers: Iterable<CordisFiber>\n}\n\nexport interface CordisRegistry {\n readonly size: number\n values(): IterableIterator<CordisPluginRuntime>\n}\n\n// ---- Host context surface ----------------------------------------------\n\n/**\n * The host (Cordis) context slice the plugin consumes. Declared locally and\n * structurally: the host injects every capability through `ctx`, and the\n * plugin never imports cordis at runtime, so there is no dependency on the\n * npm-published cordis typings (whose `.ts`-specifier augmentations do not\n * resolve under bundler module resolution).\n */\nexport interface HostLogger {\n debug?(...args: unknown[]): void\n info?(...args: unknown[]): void\n warn?(...args: unknown[]): void\n error?(...args: unknown[]): void\n}\n\nexport interface HostContext {\n agents: AgentRegistry\n commands: CommandRuntime\n workspaceRegistry: WorkspaceRegistry\n storageDomain: StorageDomainFacility\n /** Resolved through the injected `credentials` seam (env > .credentials.yaml > .env). */\n credentials: CredentialProvider\n tokenMeter: TokenMeter\n llm: LlmService\n /** Built-in Cordis plugin registry used for aggregate health diagnostics. */\n registry: CordisRegistry\n logger?: HostLogger\n /** Safe service lookup (returns undefined for absent services). */\n get?(name: string): unknown\n /** Host events this plugin subscribes to (overloads per event). */\n on(name: 'session/event', listener: (session: Session, event: SessionEvent) => void): () => boolean\n /** Live (transient) model stream for every agent; replaces the removed `assistant/chunk` events. */\n on(name: 'agent/assistant-stream', listener: (payload: AssistantStreamPayload) => void): () => boolean\n /**\n * The host's structured-question waterfall (`ask_user_question`). Same\n * shape as `approval/request`: return an answer to claim, or delegate with\n * `next()`. Untagged plugin contexts receive requests for every agent, so\n * ownership filtering is mandatory.\n */\n on(\n name: 'user-questions/request',\n listener: UserQuestionAnswererListener,\n options?: { prepend?: boolean },\n ): () => boolean\n /**\n * The host's HITL answerer waterfall. Registered with `prepend: true` so\n * this channel claims its own sessions before generic answerers (the web\n * gateway); non-owned requests delegate through `next()`. Untagged plugin\n * contexts receive requests for EVERY agent, so ownership filtering is\n * mandatory. Safe to register without the approval service mounted — the\n * event simply never fires.\n */\n on(\n name: 'approval/request',\n listener: ApprovalAnswererListener,\n options?: { prepend?: boolean },\n ): () => boolean\n effect(callback: () => (() => void | Promise<void>) | void, label?: string): void\n}\n","/**\n * Privacy-preserving health snapshot and Feishu card rendering.\n *\n * The fold consumes only aggregate lifecycle and service-presence signals.\n * It never accepts configuration values, credentials, fiber errors, or stack\n * traces, which keeps accidental disclosure out of the rendering boundary.\n *\n * @module dsh-channels/health\n */\n\nimport { CordisFiberState } from './dsh-types.ts'\nimport type { CordisFiber } from './dsh-types.ts'\nimport type { FeishuConnectionStatus } from './feishu-channel.ts'\nimport { standaloneCard } from './cards/primitives.ts'\n\nexport const HEALTH_DEPENDENCY_NAMES = [\n 'agents',\n 'commands',\n 'workspace',\n 'storage',\n 'credentials',\n 'llm',\n 'token-meter',\n] as const\n\nexport type HealthDependencyName = typeof HEALTH_DEPENDENCY_NAMES[number]\nexport type HealthLevel = 'healthy' | 'degraded' | 'unhealthy'\n\nexport interface FiberHealthCounts {\n active: number\n pending: number\n loading: number\n failed: number\n unloading: number\n}\n\n/** States that make the Fold report something other than healthy. */\nexport type FiberIssueState = 'pending' | 'loading' | 'unloading' | 'failed'\n\n/** One non-active fiber with a best-effort plugin name (no errors/stacks). */\nexport interface FiberHealthIssue {\n state: FiberIssueState\n label: string\n}\n\n/** Upper bound on the diagnostic list; counts still include every fiber. */\nconst MAX_INACTIVE_FIBERS = 12\n\nexport interface HealthSnapshotInput {\n fibers: Iterable<CordisFiber>\n dependencies: Readonly<Record<HealthDependencyName, boolean>>\n connection?: FeishuConnectionStatus\n botOpenId?: string\n started: boolean\n routeCount: number\n liveAgentCount: number\n inflightTurnCount: number\n}\n\nexport interface HealthSnapshot extends Omit<HealthSnapshotInput, 'fibers' | 'botOpenId'> {\n level: HealthLevel\n fibers: FiberHealthCounts\n /** Masked display only; the complete open_id never crosses this boundary. */\n botOpenIdTail?: string\n /** Non-active plugin fibers (capped at MAX_INACTIVE_FIBERS), diagnostics only. */\n inactiveFibers: FiberHealthIssue[]\n /** Total non-active fibers, including those beyond the capped list. */\n inactiveTotal: number\n /** Human-readable explanation of `level`; empty when healthy. */\n reasons: string[]\n}\n\n/** Best-effort label for a fiber: Cordis exposes the plugin display name on the fiber. */\nfunction fiberLabel(fiber: CordisFiber): string {\n const name = fiber.name?.trim()\n return name !== undefined && name !== '' ? name : '(未命名插件)'\n}\n\n/** One pass over the fiber table: state counts plus the non-active diagnostics. */\nfunction foldFiberStates(fibers: Iterable<CordisFiber>): {\n counts: FiberHealthCounts\n inactive: FiberHealthIssue[]\n inactiveTotal: number\n} {\n const counts: FiberHealthCounts = { active: 0, pending: 0, loading: 0, failed: 0, unloading: 0 }\n const inactive: FiberHealthIssue[] = []\n let inactiveTotal = 0\n for (const fiber of fibers) {\n const issueState: FiberIssueState | undefined =\n fiber.state === CordisFiberState.PENDING ? 'pending'\n : fiber.state === CordisFiberState.LOADING ? 'loading'\n : fiber.state === CordisFiberState.UNLOADING ? 'unloading'\n : fiber.state === CordisFiberState.FAILED ? 'failed'\n : undefined\n if (fiber.state === CordisFiberState.ACTIVE) {\n counts.active++\n continue\n }\n if (issueState === undefined) continue // disposed fibers are not health signals\n counts[issueState === 'pending' ? 'pending' : issueState === 'loading' ? 'loading' : issueState === 'unloading' ? 'unloading' : 'failed']++\n inactiveTotal++\n if (inactive.length < MAX_INACTIVE_FIBERS) inactive.push({ state: issueState, label: fiberLabel(fiber) })\n }\n return { counts, inactive, inactiveTotal }\n}\n\n/** Fold every non-disposed Cordis fiber into the five product-facing states. */\nexport function countFiberStates(fibers: Iterable<CordisFiber>): FiberHealthCounts {\n return foldFiberStates(fibers).counts\n}\n\n/** Human-readable connection state for diagnostics (keeps raw states visible). */\nfunction connectionReason(state: string | undefined): string {\n switch (state) {\n case 'connected': return '已连接'\n case 'connecting': return '连接中'\n case 'reconnecting': return '重连中'\n case 'failed': return '连接失败'\n case 'idle': return '空闲(未连接)'\n default: return state ?? '未知'\n }\n}\n\nfunction labelList(labels: readonly string[], total: number, max = 6): string {\n if (labels.length === 0) return ''\n const shown = labels.slice(0, max).join('、')\n return total > Math.min(labels.length, max) ? `${shown} 等 ${total} 个` : shown\n}\n\n/** Reveal only the final four characters, and nothing for unusually short ids. */\nexport function maskOpenIdTail(openId?: string): string | undefined {\n if (openId === undefined || openId.length <= 4) return undefined\n return `…${openId.slice(-4)}`\n}\n\n/** One deterministic fold owns both the snapshot and its overall conclusion. */\nexport function foldHealthSnapshot(input: HealthSnapshotInput): HealthSnapshot {\n const { counts: fibers, inactive, inactiveTotal } = foldFiberStates(input.fibers)\n const missingDependencies = HEALTH_DEPENDENCY_NAMES.filter(name => !input.dependencies[name])\n const dependencyMissing = missingDependencies.length > 0\n const connectionState = input.connection?.state\n\n let level: HealthLevel\n if (!input.started || dependencyMissing || connectionState === 'failed' || fibers.failed > 0) {\n level = 'unhealthy'\n } else if (\n connectionState !== 'connected'\n || fibers.pending > 0\n || fibers.loading > 0\n || fibers.unloading > 0\n ) {\n level = 'degraded'\n } else {\n level = 'healthy'\n }\n\n const reasons: string[] = []\n if (!input.started) reasons.push('飞书通道尚未启动')\n if (dependencyMissing) reasons.push(`主机服务未就绪:${missingDependencies.join('、')}`)\n if (input.connection === undefined) reasons.push('飞书长连接尚未建立')\n else if (connectionState === 'failed') reasons.push('飞书长连接失败')\n else if (connectionState !== 'connected') reasons.push(`飞书长连接状态:${connectionReason(connectionState)}`)\n\n const failedLabels = inactive.filter(issue => issue.state === 'failed').map(issue => issue.label)\n if (fibers.failed > 0) {\n reasons.push(`插件加载失败 ${fibers.failed} 个${failedLabels.length === 0 ? '' : `:${labelList(failedLabels, fibers.failed)}`}`)\n }\n const stalled = fibers.pending + fibers.loading + fibers.unloading\n if (stalled > 0) {\n const stalledLabels = inactive\n .filter(issue => issue.state === 'pending' || issue.state === 'loading' || issue.state === 'unloading')\n .map(issue => issue.label)\n reasons.push(\n `宿主其它插件仍有 ${stalled} 个入口在等待依赖或加载/卸载(与飞书通道无关)`\n + (stalledLabels.length === 0 ? '' : `:${labelList(stalledLabels, stalled)}`),\n )\n }\n\n return {\n level,\n fibers,\n dependencies: input.dependencies,\n connection: input.connection,\n botOpenIdTail: maskOpenIdTail(input.botOpenId),\n started: input.started,\n routeCount: input.routeCount,\n liveAgentCount: input.liveAgentCount,\n inflightTurnCount: input.inflightTurnCount,\n inactiveFibers: inactive,\n inactiveTotal,\n reasons,\n }\n}\n\nconst LEVEL_PRESENTATION: Record<HealthLevel, { icon: string; title: string }> = {\n healthy: { icon: '✅', title: '运行健康' },\n degraded: { icon: '⚠️', title: '运行降级' },\n unhealthy: { icon: '❌', title: '运行异常' },\n}\n\nfunction formatTimestamp(timestamp?: number): string {\n if (timestamp === undefined || !Number.isFinite(timestamp)) return '—'\n const date = new Date(timestamp)\n return Number.isNaN(date.getTime()) ? '—' : date.toISOString()\n}\n\n/** Render a standalone non-streaming health card from the sanitized snapshot. */\nexport function buildHealthCard(snapshot: HealthSnapshot): object {\n const presentation = LEVEL_PRESENTATION[snapshot.level]\n const dependencyLine = HEALTH_DEPENDENCY_NAMES\n .map(name => `${snapshot.dependencies[name] ? '✅' : '❌'} ${name}`)\n .join(' · ')\n const connection = snapshot.connection\n const connectionState = connection?.state ?? 'unknown'\n\n return standaloneCard(`**${presentation.icon} 健康检查 · ${presentation.title}**`, [\n { tag: 'markdown', content: `整体结论:**${snapshot.level}**` },\n { tag: 'hr' },\n {\n tag: 'markdown',\n content: `**Harness fibers**\\nactive ${snapshot.fibers.active} · pending ${snapshot.fibers.pending} · loading ${snapshot.fibers.loading} · failed ${snapshot.fibers.failed} · unloading ${snapshot.fibers.unloading}`,\n },\n { tag: 'markdown', content: `**关键依赖**\\n${dependencyLine}` },\n {\n tag: 'markdown',\n content: `**飞书 WebSocket**\\n${connectionState} · 最近连接 ${formatTimestamp(connection?.lastConnectTime)} · 下次重连 ${formatTimestamp(connection?.nextConnectTime)} · 重连次数 ${connection?.reconnectAttempts ?? 0} · bot open_id ${snapshot.botOpenIdTail ?? '—'}`,\n },\n {\n tag: 'markdown',\n content: `**dsh-channels**\\n${snapshot.started ? 'started' : 'stopped'} · 持久化路由 ${snapshot.routeCount} · live Agent ${snapshot.liveAgentCount} · in-flight turn ${snapshot.inflightTurnCount}`,\n },\n ])\n}\n","/**\n * Whole-session diagnostic folds and context measurement.\n *\n * All historical figures come from the durable event log, so a live Agent\n * and the same Session resumed after restart produce identical output.\n * Command lifecycle records are intentionally ignored.\n *\n * @module dsh-channels/stats\n */\n\nimport type {\n AssistantStreamRecord, ContextPressureView, LlmService, Session, SessionEvent, SessionProjections, SessionStatsView,\n TokenMeter, TokenUsage, TokenUsageView,\n} from './dsh-types.ts'\n\nexport interface SessionTotals {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n reasoningTokens: number\n}\n\nexport const EMPTY_TOTALS: SessionTotals = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n reasoningTokens: 0,\n}\n\nfunction usableCount(value: number | undefined): number {\n return value !== undefined && Number.isFinite(value) && value >= 0 ? value : 0\n}\n\nexport function addUsage(totals: SessionTotals, usage: TokenUsage): SessionTotals {\n return {\n inputTokens: totals.inputTokens + usableCount(usage.inputTokens),\n outputTokens: totals.outputTokens + usableCount(usage.outputTokens),\n cacheReadTokens: totals.cacheReadTokens + usableCount(usage.cacheReadTokens),\n cacheWriteTokens: totals.cacheWriteTokens + usableCount(usage.cacheWriteTokens),\n reasoningTokens: totals.reasoningTokens + usableCount(usage.reasoningTokens),\n }\n}\n\n/** Latest provider usage sample carried by an `assistant/message` event. */\nexport interface UsageSample {\n usage: TokenUsage\n turn: number\n step: number\n}\n\nexport interface HeaderSample {\n provider: string\n model: string\n effort?: string\n}\n\nexport type ToolEventStatus = 'success' | 'error' | 'running'\n\nexport interface ToolEventSample {\n callId: string\n name: string\n status: ToolEventStatus\n durationMs?: number\n startedAt?: number\n}\n\ninterface OpenStep {\n turn: number\n step: number\n startTime?: number\n}\n\ninterface PendingTool {\n callId: string\n name: string\n startedAt?: number\n}\n\n/** Complete fold state and public whole-log status figures. */\nexport interface StatsFold {\n totals: SessionTotals\n usageSamples: number\n lastUsage: UsageSample | undefined\n lastHeader: HeaderSample | undefined\n llmMs: number\n llmSteps: number\n toolMs: number\n toolTimedCalls: number\n ttftMs: number\n ttftSteps: number\n decodeMs: number\n decodeTokens: number\n toolCalls: number\n pendingTools: Map<string, PendingTool>\n recentTools: ToolEventSample[]\n openStep: OpenStep | undefined\n}\n\nfunction eventTime(event: SessionEvent): number | undefined {\n return event.time !== undefined && Number.isFinite(event.time) ? event.time : undefined\n}\n\n/**\n * First non-whitespace delta time inside one durable embedded assistant\n * stream (`assistant/message.stream`). Local fallback for deployments where\n * the host `sessionStats` projection is not mounted; the projection remains\n * authoritative when present.\n */\nfunction firstTokenTime(stream: readonly AssistantStreamRecord[] | undefined): number | undefined {\n let best: number | undefined\n for (const record of stream ?? []) {\n const time0 = record.time0\n if (typeof time0 !== 'number' || !Number.isFinite(time0)) continue\n if (record.type !== 'text-chunks' && record.type !== 'reasoning-chunks') continue\n const texts = record.texts\n const hasVisible = Array.isArray(texts)\n && texts.some(text => typeof text === 'string' && /\\S/u.test(text))\n if (hasVisible && (best === undefined || time0 < best)) best = time0\n }\n return best\n}\n\n/**\n * Authoritative call correlation and failure flag of one `tool/result`.\n *\n * DSH's `ToolResultMessage` is `{ role:'tool', source:{kind:'tool', callId},\n * content, isError? }` and has no `tool-result` content-block type, so the\n * message-level `source.callId` / `isError` decide; the first content block is\n * only a legacy fallback.\n */\nfunction toolResultCallId(event: Extract<SessionEvent, { type: 'tool/result' }>): string | undefined {\n const sourceId = event.data.message.source?.callId\n if (typeof sourceId === 'string' && sourceId !== '') return sourceId\n const blockId = event.data.message.content?.find(block => typeof block.toolCallId === 'string')?.toolCallId\n return typeof blockId === 'string' && blockId !== '' ? blockId : undefined\n}\n\nfunction toolResultFailed(event: Extract<SessionEvent, { type: 'tool/result' }>): boolean {\n if (event.data.error !== undefined) return true\n if (event.data.message.isError === true) return true\n return event.data.message.content?.some(block => block.isError === true) ?? false\n}\n\nfunction elapsed(start: number | undefined, end: number | undefined): number | undefined {\n if (start === undefined || end === undefined) return undefined\n return Math.max(0, end - start)\n}\n\nfunction pushRecentTool(fold: StatsFold, sample: ToolEventSample): void {\n fold.recentTools.push(sample)\n if (fold.recentTools.length > 5) fold.recentTools.shift()\n}\n\n/** Fold one durable event into the running Session figures. */\nexport function foldEvent(fold: StatsFold, event: SessionEvent): void {\n switch (event.type) {\n case 'step/start':\n fold.openStep = {\n turn: event.data.turn,\n step: event.data.step,\n startTime: eventTime(event),\n }\n break\n case 'assistant/message': {\n const usage = event.data.usage\n if (usage !== undefined) {\n fold.totals = addUsage(fold.totals, usage)\n fold.usageSamples++\n fold.lastUsage = { usage, turn: event.data.turn, step: event.data.step }\n }\n const open = fold.openStep\n if (open === undefined || open.turn !== event.data.turn || open.step !== event.data.step) break\n const completedAt = eventTime(event)\n const llmDuration = elapsed(open.startTime, completedAt)\n if (llmDuration !== undefined) {\n fold.llmMs += llmDuration\n fold.llmSteps++\n }\n // Live `assistant/chunk` events no longer exist; the first-token time is\n // recovered from the durable embedded stream. The host `sessionStats`\n // projection (overlaid by `applyProjectionViews`) is authoritative when\n // mounted.\n const firstToken = firstTokenTime(event.data.stream)\n if (firstToken !== undefined) {\n const ttft = elapsed(open.startTime, firstToken)\n if (ttft !== undefined) {\n fold.ttftMs += ttft\n fold.ttftSteps++\n }\n const outputTokens = usage?.outputTokens\n const decodeDuration = elapsed(firstToken, completedAt)\n if (decodeDuration !== undefined && outputTokens !== undefined\n && Number.isFinite(outputTokens) && outputTokens >= 0) {\n fold.decodeMs += decodeDuration\n fold.decodeTokens += outputTokens\n }\n }\n fold.openStep = undefined\n break\n }\n case 'step/end':\n fold.openStep = undefined\n break\n case 'request/header': {\n const config = event.data.header?.config\n if (config !== undefined && typeof config === 'object') {\n const cfg = config as { provider?: unknown; model?: unknown; reasoningEffort?: unknown }\n fold.lastHeader = {\n provider: typeof cfg.provider === 'string' ? cfg.provider : '',\n model: typeof cfg.model === 'string' ? cfg.model : '',\n effort: typeof cfg.reasoningEffort === 'string' ? cfg.reasoningEffort : undefined,\n }\n }\n break\n }\n case 'tool/call': {\n fold.toolCalls++\n const pending: PendingTool = {\n callId: event.data.callId,\n name: event.data.name,\n startedAt: eventTime(event),\n }\n fold.pendingTools.set(event.data.callId, pending)\n pushRecentTool(fold, { ...pending, status: 'running' })\n break\n }\n case 'tool/result': {\n const callId = toolResultCallId(event)\n if (callId === undefined) break\n const pending = fold.pendingTools.get(callId)\n if (pending === undefined) break\n fold.pendingTools.delete(callId)\n const durationMs = elapsed(pending.startedAt, eventTime(event))\n const settled: ToolEventSample = {\n callId,\n name: pending.name,\n status: toolResultFailed(event) ? 'error' : 'success',\n startedAt: pending.startedAt,\n ...(durationMs === undefined ? {} : { durationMs }),\n }\n const recentIndex = fold.recentTools.findIndex(sample => sample.callId === callId)\n if (recentIndex !== -1) fold.recentTools.splice(recentIndex, 1)\n pushRecentTool(fold, settled)\n if (durationMs !== undefined) {\n fold.toolMs += durationMs\n fold.toolTimedCalls++\n }\n break\n }\n default:\n break\n }\n}\n\n/** Fold one complete Session log. */\nexport function foldAll(events: readonly SessionEvent[]): StatsFold {\n const fold: StatsFold = {\n totals: { ...EMPTY_TOTALS },\n usageSamples: 0,\n lastUsage: undefined,\n lastHeader: undefined,\n llmMs: 0,\n llmSteps: 0,\n toolMs: 0,\n toolTimedCalls: 0,\n ttftMs: 0,\n ttftSteps: 0,\n decodeMs: 0,\n decodeTokens: 0,\n toolCalls: 0,\n pendingTools: new Map(),\n recentTools: [],\n openStep: undefined,\n }\n for (const event of events) foldEvent(fold, event)\n return fold\n}\n\n/** Cache hit ratio of the latest usage sample: cacheRead / (cacheRead + uncached input). */\nexport function cacheHitPct(lastUsage: UsageSample | undefined): number | undefined {\n if (lastUsage === undefined) return undefined\n const hit = lastUsage.usage.cacheReadTokens\n if (hit === undefined || !Number.isFinite(hit) || hit < 0) return undefined\n const input = lastUsage.usage.inputTokens\n if (!Number.isFinite(input) || input < 0) return undefined\n const total = hit + input\n return total > 0 ? hit / total : undefined\n}\n\nexport interface ContextMeasurement {\n contextTokens?: number\n contextWindow?: number\n}\n\nexport interface ContextServices {\n tokenMeter?: TokenMeter\n llm?: LlmService\n /** Optional projection registry; supplies the authoritative context window. */\n projections?: SessionProjections\n}\n\n/** Projection readings for one session; every key is optional (unmounted unit). */\nexport interface ProjectionViews {\n sessionStats?: SessionStatsView\n tokenUsage?: TokenUsageView\n contextPressure?: ContextPressureView\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null ? value as Record<string, unknown> : undefined\n}\n\nfunction finiteNonNegative(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined\n}\n\nfunction positive(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined\n}\n\n/** Read the host projection units this plugin consumes, tolerating absence. */\nexport function readProjectionViews(\n projections: SessionProjections | undefined,\n session: Session,\n): ProjectionViews {\n if (projections === undefined) return {}\n const views: ProjectionViews = {}\n try {\n const stats = asRecord(projections.stateOf(session, 'sessionStats'))\n if (stats !== undefined) {\n const parsed: SessionStatsView = {\n turns: finiteNonNegative(stats.turns) ?? 0,\n steps: finiteNonNegative(stats.steps) ?? 0,\n llmMs: finiteNonNegative(stats.llmMs) ?? 0,\n toolMs: finiteNonNegative(stats.toolMs) ?? 0,\n ttftMs: finiteNonNegative(stats.ttftMs) ?? 0,\n ttftSteps: finiteNonNegative(stats.ttftSteps) ?? 0,\n decodeMs: finiteNonNegative(stats.decodeMs) ?? 0,\n decodeTokens: finiteNonNegative(stats.decodeTokens) ?? 0,\n }\n views.sessionStats = parsed\n }\n } catch { /* a failing projection must never break the fold */ }\n try {\n const usage = asRecord(projections.stateOf(session, 'tokenUsage'))\n const totals = asRecord(usage?.totals)\n if (usage !== undefined && totals !== undefined) {\n const buckets = (input: Record<string, unknown> | undefined) => ({\n uncachedInputTokens: finiteNonNegative(input?.uncachedInputTokens) ?? 0,\n outputTokens: finiteNonNegative(input?.outputTokens) ?? 0,\n cacheReadTokens: finiteNonNegative(input?.cacheReadTokens) ?? 0,\n cacheWriteTokens: finiteNonNegative(input?.cacheWriteTokens) ?? 0,\n })\n const last = asRecord(usage.last)\n views.tokenUsage = {\n totals: buckets(totals),\n last: last === undefined\n ? null\n : {\n turn: finiteNonNegative(last.turn) ?? 0,\n step: finiteNonNegative(last.step) ?? 0,\n buckets: buckets(asRecord(last.buckets)),\n },\n }\n }\n } catch { /* ignore malformed projection state */ }\n try {\n const pressure = asRecord(projections.stateOf(session, 'contextPressure'))\n if (pressure !== undefined) {\n views.contextPressure = {\n ...(finiteNonNegative(pressure.pressureTokens) === undefined\n ? {} : { pressureTokens: finiteNonNegative(pressure.pressureTokens) }),\n ...(finiteNonNegative(pressure.projectedTokens) === undefined\n ? {} : { projectedTokens: finiteNonNegative(pressure.projectedTokens) }),\n ...(positive(pressure.contextWindow) === undefined\n ? {} : { contextWindow: positive(pressure.contextWindow) }),\n }\n }\n } catch { /* ignore malformed projection state */ }\n return views\n}\n\n/** Map a projection token bucket into the plugin's usage vocabulary. */\nexport function usageFromBuckets(buckets: TokenUsageView['totals']): TokenUsage {\n return {\n inputTokens: buckets.uncachedInputTokens,\n outputTokens: buckets.outputTokens,\n cacheReadTokens: buckets.cacheReadTokens,\n cacheWriteTokens: buckets.cacheWriteTokens,\n }\n}\n\n/**\n * Overlay host projection figures onto a log fold. The host units are\n * authoritative where mounted (they fold the same log, including the\n * embedded streams the plugin no longer sees as live events); the log fold\n * stays as the fallback and still owns tool-detail fields.\n */\nexport function applyProjectionViews(fold: StatsFold, views: ProjectionViews): StatsFold {\n const stats = views.sessionStats\n if (stats !== undefined) {\n // Times are host-authoritative; counts (llmSteps / toolTimedCalls) stay\n // with the fold, which sees the same boundaries.\n fold.llmMs = stats.llmMs\n fold.toolMs = stats.toolMs\n fold.ttftMs = stats.ttftMs\n fold.ttftSteps = stats.ttftSteps\n fold.decodeMs = stats.decodeMs\n fold.decodeTokens = stats.decodeTokens\n }\n const usage = views.tokenUsage\n if (usage !== undefined) {\n fold.totals = {\n inputTokens: usage.totals.uncachedInputTokens,\n outputTokens: usage.totals.outputTokens,\n cacheReadTokens: usage.totals.cacheReadTokens,\n cacheWriteTokens: usage.totals.cacheWriteTokens,\n reasoningTokens: fold.totals.reasoningTokens,\n }\n if (usage.last !== null) {\n fold.lastUsage = {\n usage: usageFromBuckets(usage.last.buckets),\n turn: usage.last.turn,\n step: usage.last.step,\n }\n fold.usageSamples = Math.max(fold.usageSamples, 1)\n }\n }\n return fold\n}\n\n/** Read one session's figures from the projection registry, falling back to the log fold. */\nexport function foldSessionStats(\n session: Session,\n projections: SessionProjections | undefined,\n): StatsFold {\n const fold = foldAll(session.snapshotEvents())\n return applyProjectionViews(fold, readProjectionViews(projections, session))\n}\n\n/** Measure current surface pressure and resolve the latest model's context capacity. */\nexport async function measureSessionContext(\n session: Session,\n services: ContextServices,\n header = session.requestHeader?.()?.config ?? foldAll(session.snapshotEvents()).lastHeader,\n): Promise<ContextMeasurement> {\n let contextTokens: number | undefined\n try {\n const measured = services.tokenMeter?.measure(session).totalTokens\n if (measured !== undefined && Number.isFinite(measured) && measured >= 0) contextTokens = measured\n } catch {\n contextTokens = undefined\n }\n\n let contextWindow: number | undefined\n if (services.projections !== undefined) {\n try {\n contextWindow = readProjectionViews(services.projections, session).contextPressure?.contextWindow\n } catch {\n contextWindow = undefined\n }\n }\n if (contextWindow === undefined && header !== undefined\n && typeof header.provider === 'string' && header.provider !== ''\n && typeof header.model === 'string' && header.model !== '') {\n try {\n const resolved = await services.llm?.resolveModelInfo(header.provider, header.model)\n const window = resolved?.context?.contextWindow\n if (window !== undefined && Number.isFinite(window) && window > 0) contextWindow = window\n } catch {\n contextWindow = undefined\n }\n }\n return { contextTokens, contextWindow }\n}\n\nexport function averageTtftMs(stats: StatsFold): number | undefined {\n return stats.ttftSteps > 0 ? stats.ttftMs / stats.ttftSteps : undefined\n}\n\nexport function averageDecodeTokensPerSecond(stats: StatsFold): number | undefined {\n return stats.decodeMs > 0 ? stats.decodeTokens / stats.decodeMs * 1000 : undefined\n}\n","/**\n * Pure result rendering and validation for Session-scoped slash commands.\n *\n * @module dsh-channels/session-commands\n */\n\nimport path from 'node:path'\nimport { standaloneCard } from './cards/primitives.ts'\nimport type { RouteRecord, SessionHistoryEntry } from './domain.ts'\nimport { normalizeSessionHistory } from './domain.ts'\nimport type { ContextMeasurement, StatsFold, ToolEventSample } from './stats.ts'\nimport {\n averageDecodeTokensPerSecond, averageTtftMs, cacheHitPct,\n} from './stats.ts'\n\nfunction fmtCount(value: number): string {\n return value.toLocaleString('en-US')\n}\n\nfunction fmtDuration(value: number | undefined): string {\n if (value === undefined || !Number.isFinite(value)) return '暂无数据'\n if (value < 1000) return `${Math.round(value)} ms`\n return `${(value / 1000).toFixed(2)} s`\n}\n\nfunction fmtPercent(value: number | undefined): string {\n return value === undefined || !Number.isFinite(value) ? '暂无数据' : `${(value * 100).toFixed(1)}%`\n}\n\nfunction fmtRate(value: number | undefined): string {\n return value === undefined || !Number.isFinite(value) ? '暂无数据' : `${value.toFixed(1)} tokens/s`\n}\n\nfunction contextLine(measurement: ContextMeasurement): string {\n const tokens = measurement.contextTokens\n const window = measurement.contextWindow\n if (tokens === undefined) return '暂无数据'\n if (window === undefined) return `${fmtCount(tokens)} tokens`\n return `${fmtCount(tokens)} / ${fmtCount(window)} (${(tokens / window * 100).toFixed(1)}%)`\n}\n\nfunction escapeInlineCode(value: string): string {\n return value.replaceAll('`', '\\\\`')\n}\n\nfunction toolLine(sample: ToolEventSample): string {\n const status = sample.status === 'running' ? '运行中' : sample.status === 'success' ? '成功' : '失败'\n const duration = sample.status === 'running' ? '尚未完成' : fmtDuration(sample.durationMs)\n return `- \\`${escapeInlineCode(sample.name)}\\` · ${status} · ${duration}`\n}\n\nexport interface SessionStatusCardInput {\n sessionId: string\n stats: StatsFold\n context: ContextMeasurement\n /** Optional operational extras (permission preset, workspace, uptime, …). */\n extras?: SessionStatusExtras\n}\n\n/** Operational extras appended to the `/status` card when available. */\nexport interface SessionStatusExtras {\n /** Effective permission preset of this Session (`ctx.permissionPresets`). */\n permissionPreset?: string\n /** Workspace directory bound to this route. */\n workspacePath?: string\n /** Plugin uptime in milliseconds. */\n uptimeMs?: number\n /** Live agent entries this channel owns/borrows. */\n liveAgents?: number\n /** Persisted route records. */\n routeCount?: number\n}\n\n/** Render the complete `/status` snapshot without exposing tool arguments or results. */\nexport function buildSessionStatusCard(input: SessionStatusCardInput): object {\n const { stats } = input\n const model = stats.lastHeader === undefined\n ? '尚未产生模型请求'\n : `${stats.lastHeader.provider}/${stats.lastHeader.model}`\n const effort = stats.lastHeader?.effort ?? '暂无数据'\n const tools = stats.recentTools.length === 0\n ? '暂无工具事件'\n : [...stats.recentTools].reverse().map(toolLine).join('\\n')\n const pendingNames = [...stats.pendingTools.values()].slice(-5).map(tool => `\\`${escapeInlineCode(tool.name)}\\``)\n const hiddenPending = stats.pendingTools.size - pendingNames.length\n const pending = pendingNames.length === 0\n ? '无'\n : `${pendingNames.join('、')}${hiddenPending > 0 ? `,另有 ${hiddenPending} 个` : ''}`\n const tokenSummary = stats.usageSamples === 0\n ? '暂无数据'\n : `输入 ${fmtCount(stats.totals.inputTokens)} · 输出 ${fmtCount(stats.totals.outputTokens)} · 推理 ${fmtCount(stats.totals.reasoningTokens)}`\n const cacheSummary = stats.usageSamples === 0\n ? '暂无数据'\n : `读取 ${fmtCount(stats.totals.cacheReadTokens)} · 写入 ${fmtCount(stats.totals.cacheWriteTokens)} · 最近命中率 ${fmtPercent(cacheHitPct(stats.lastUsage))}`\n\n return standaloneCard('**📊 Session 状态**', [\n { tag: 'markdown', content: `**模型**:${model}\\n**推理强度**:${effort}` },\n {\n tag: 'markdown',\n content: `**Token 累计**:${tokenSummary}\\n`\n + `**缓存**:${cacheSummary}\\n`\n + `**上下文**:${contextLine(input.context)}`,\n },\n {\n tag: 'markdown',\n content: `**LLM 时间**:${fmtDuration(stats.llmSteps === 0 ? undefined : stats.llmMs)}\\n`\n + `**工具时间**:${fmtDuration(stats.toolTimedCalls === 0 ? undefined : stats.toolMs)}\\n`\n + `**平均首 token**:${fmtDuration(averageTtftMs(stats))}\\n`\n + `**平均生成速度**:${fmtRate(averageDecodeTokensPerSecond(stats))}`,\n },\n {\n tag: 'markdown',\n content: `**工具调用**:累计 ${fmtCount(stats.toolCalls)} · 未完成 ${fmtCount(stats.pendingTools.size)}(${pending})\\n`\n + `**最近工具事件(最多 5 条)**\\n${tools}`,\n },\n ...statusExtrasElements(input.extras),\n { tag: 'markdown', content: input.sessionId, text_size: 'notation' },\n ])\n}\n\n/** Render the optional operational lines, omitting unavailable ones. */\nfunction statusExtrasElements(extras: SessionStatusExtras | undefined): object[] {\n if (extras === undefined) return []\n const lines: string[] = []\n if (extras.permissionPreset !== undefined) lines.push(`**权限预设**:\\`${escapeInlineCode(extras.permissionPreset)}\\``)\n if (extras.workspacePath !== undefined) lines.push(`**工作区**:\\`${escapeInlineCode(extras.workspacePath)}\\``)\n if (extras.uptimeMs !== undefined) lines.push(`**插件运行时长**:${fmtUptime(extras.uptimeMs)}`)\n if (extras.liveAgents !== undefined || extras.routeCount !== undefined) {\n const parts: string[] = []\n if (extras.liveAgents !== undefined) parts.push(`live Agent ${fmtCount(extras.liveAgents)}`)\n if (extras.routeCount !== undefined) parts.push(`持久化路由 ${fmtCount(extras.routeCount)}`)\n lines.push(`**通道**:${parts.join(' · ')}`)\n }\n return lines.length === 0 ? [] : [{ tag: 'markdown', content: lines.join('\\n') }]\n}\n\n/** `2 小时 5 分` / `45 秒` — coarse human uptime. */\nfunction fmtUptime(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000))\n if (totalSeconds < 60) return `${totalSeconds} 秒`\n const minutes = Math.floor(totalSeconds / 60) % 60\n const hours = Math.floor(totalSeconds / 3600)\n return hours === 0 ? `${minutes} 分` : `${hours} 小时 ${minutes} 分`\n}\n\nexport type WorkDirResolution =\n | { kind: 'success'; path: string }\n | { kind: 'missing'; text: string }\n | { kind: 'mismatch'; text: string }\n\n/** Validate the Session header cwd against the plugin's durable route. */\nexport function resolveWorkDir(headerCwd: string | undefined, routeWorkspacePath: string): WorkDirResolution {\n if (headerCwd === undefined || headerCwd === '' || !path.isAbsolute(headerCwd)) {\n return {\n kind: 'missing',\n text: '当前 Session header 未记录可用的绝对工作目录;请检查 Session 创建或恢复配置。',\n }\n }\n if (!path.isAbsolute(routeWorkspacePath)) {\n return {\n kind: 'missing',\n text: '当前飞书路由未记录可用的绝对工作目录;请检查路由持久化状态。',\n }\n }\n if (headerCwd !== routeWorkspacePath) {\n return {\n kind: 'mismatch',\n text: 'Session header 与飞书路由记录的工作目录不一致;为避免返回错误目录,已拒绝显示。',\n }\n }\n return { kind: 'success', path: headerCwd }\n}\n\n/** Render one verified absolute workspace path. */\nexport function buildWorkDirCard(sessionId: string, workspacePath: string): object {\n return standaloneCard('**📁 当前工作目录**', [\n { tag: 'markdown', content: `\\`${escapeInlineCode(workspacePath)}\\`` },\n { tag: 'markdown', content: sessionId, text_size: 'notation' },\n ])\n}\n\nexport interface RenewCardInput {\n previousSessionId: string\n currentSessionId: string\n workspacePath: string\n}\n\n/** Render the committed route switch without implying that the old log was deleted. */\nexport function buildRenewCard(input: RenewCardInput): object {\n return standaloneCard('**🔄 Session 已续建**', [\n {\n tag: 'markdown',\n content: `**旧 Session**:${input.previousSessionId}\\n`\n + `**新 Session**:${input.currentSessionId}\\n`\n + `**复用工作区**:\\`${escapeInlineCode(input.workspacePath)}\\``,\n },\n { tag: 'markdown', content: '旧 Session 及其日志仍由 Harness 保留。', text_size: 'notation' },\n ])\n}\n\nexport const SESSION_HISTORY_DISPLAY_LIMIT = 20\n\nexport interface SessionHistoryItem extends SessionHistoryEntry {\n current: boolean\n}\n\nexport interface SessionHistoryView {\n total: number\n items: SessionHistoryItem[]\n hidden: number\n}\n\n/** Read one route's normalized history without consulting the Session repository. */\nexport function querySessionHistory(record: RouteRecord): SessionHistoryView {\n const history = normalizeSessionHistory(record)\n const items = history\n .slice(-SESSION_HISTORY_DISPLAY_LIMIT)\n .reverse()\n .map(entry => ({ ...entry, current: entry.sessionId === record.sessionId }))\n return {\n total: history.length,\n items,\n hidden: Math.max(0, history.length - items.length),\n }\n}\n\n/** Optional per-session facts folded from the host session query. */\nexport interface SessionHistoryAnnotation {\n /** Latest log-backed session title, when one exists. */\n title?: string\n /** Whether the session is currently live (held by any owner). */\n live?: boolean\n}\n\nfunction historyLine(\n item: SessionHistoryItem,\n index: number,\n annotation: SessionHistoryAnnotation | undefined,\n): string {\n const markers = [\n ...(item.current ? ['**当前**'] : []),\n ...(annotation?.live === true && !item.current ? ['live'] : []),\n ]\n const marker = markers.length === 0 ? '' : ` · ${markers.join(' · ')}`\n const title = annotation?.title === undefined || annotation.title === ''\n ? ''\n : `\\n 标题:${escapeInlineCode(annotation.title)}`\n return `${index + 1}. \\`${escapeInlineCode(item.sessionId)}\\`${marker}${title}\\n`\n + ` 创建时间:${new Date(item.createdAt).toISOString()}`\n}\n\n/** Render the newest route-local Session history with host-query annotations. */\nexport function buildSessionHistoryCard(\n record: RouteRecord,\n annotations: Readonly<Record<string, SessionHistoryAnnotation>> = {},\n): object {\n const view = querySessionHistory(record)\n const elements: object[] = [\n { tag: 'markdown', content: `**总数**:${fmtCount(view.total)}\\n**当前 Session**:\\`${escapeInlineCode(record.sessionId)}\\`` },\n { tag: 'markdown', content: view.items.map((item, index) => historyLine(item, index, annotations[item.sessionId])).join('\\n') },\n ]\n if (view.hidden > 0) {\n elements.push({ tag: 'markdown', content: `另有 ${fmtCount(view.hidden)} 条未展示。`, text_size: 'notation' })\n }\n return standaloneCard('**🕘 Session 历史**', elements)\n}\n\nfunction comparisonLine(label: string, tokens: number | undefined, window: number | undefined): string {\n if (tokens === undefined) return `${label} 暂无数据`\n if (window === undefined) return `${label} ${fmtCount(tokens)} tokens`\n const ratio = tokens / window\n const filled = Math.min(10, Math.max(0, Math.round(ratio * 10)))\n return `${label} ${'█'.repeat(filled)}${'░'.repeat(10 - filled)} ${(ratio * 100).toFixed(0)}% ${fmtCount(tokens)} / ${fmtCount(window)}`\n}\n\nexport interface CompactComparisonInput {\n sessionId: string\n resultText: string\n before: ContextMeasurement\n after: ContextMeasurement\n}\n\n/** Render successful and no-op compaction outcomes with one shared 10-cell scale. */\nexport function buildCompactComparisonCard(input: CompactComparisonInput): object {\n const window = input.after.contextWindow ?? input.before.contextWindow\n const beforeTokens = input.before.contextTokens\n const afterTokens = input.after.contextTokens\n const released = beforeTokens === undefined || afterTokens === undefined\n ? undefined\n : Math.max(0, beforeTokens - afterTokens)\n return standaloneCard('**🗜️ 上下文压缩完成**', [\n { tag: 'markdown', content: input.resultText },\n {\n tag: 'markdown',\n content: `${comparisonLine('压缩前', beforeTokens, window)}\\n`\n + `${comparisonLine('压缩后', afterTokens, window)}\\n`\n + `释放 ${released === undefined ? '暂无数据' : `${fmtCount(released)} tokens`}`,\n },\n { tag: 'markdown', content: input.sessionId, text_size: 'notation' },\n ])\n}\n\n/** Describe whether a failed compaction left the measured surface unchanged. */\nexport function compactFailureContext(before: ContextMeasurement, after: ContextMeasurement): string {\n if (before.contextTokens === undefined || after.contextTokens === undefined) return '上下文状态:暂无可用测量数据。'\n if (before.contextTokens === after.contextTokens) {\n return `上下文保持不变:${fmtCount(before.contextTokens)} tokens。`\n }\n return `上下文状态可能已变化:执行前 ${fmtCount(before.contextTokens)} tokens,当前 ${fmtCount(after.contextTokens)} tokens;请检查 Session 后再重试。`\n}\n","/**\n * Logical Session export format and in-memory gzip packaging.\n *\n * This is deliberately a projection of the live logical Session surface. It\n * does not walk descendant Sessions, fetch attachments, or copy persistence\n * backend artifacts.\n *\n * @module dsh-channels/session-export\n */\n\nimport { readFileSync } from 'node:fs'\nimport { promisify } from 'node:util'\nimport { gzip } from 'node:zlib'\nimport type { Session } from './dsh-types.ts'\n\ninterface PackageMetadata {\n version?: unknown\n}\n\nfunction packageVersion(): string {\n const metadata = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n ) as PackageMetadata\n if (typeof metadata.version !== 'string' || metadata.version === '') {\n throw new Error('dsh-channels: package version is unavailable')\n }\n return metadata.version\n}\n\nexport const LOGICAL_EXPORT_FORMAT = 'dsh-logical-session-jsonl'\nexport const LOGICAL_EXPORT_FORMAT_VERSION = 1\nexport const DSH_CHANNELS_VERSION = packageVersion()\n\nexport interface LogicalExportManifest {\n format: typeof LOGICAL_EXPORT_FORMAT\n formatVersion: typeof LOGICAL_EXPORT_FORMAT_VERSION\n logical: true\n exportedAt: string\n plugin: {\n name: 'dsh-channels'\n version: string\n }\n includes: {\n descendants: false\n attachments: false\n rawArtifacts: false\n }\n session: {\n id: string\n header: Session['header']\n }\n}\n\nexport interface LogicalSessionExportFile {\n fileName: string\n source: Buffer\n}\n\nexport type SessionExportBuilder = (session: Session) => Promise<LogicalSessionExportFile>\n\nexport interface LogicalSessionExportOptions {\n /** Deterministic clock seam for format tests. */\n exportedAt?: Date\n /** Explicit package-version seam for format tests. */\n pluginVersion?: string\n /** Compression seam used to verify gzip failures without filesystem I/O. */\n compress?: (source: Buffer) => Promise<Buffer>\n}\n\nconst gzipBuffer = promisify(gzip)\n\n/** Build one immutable UTF-8 JSONL snapshot before any asynchronous work. */\nexport function serializeLogicalSession(\n session: Session,\n options: Pick<LogicalSessionExportOptions, 'exportedAt' | 'pluginVersion'> = {},\n): Buffer {\n const manifest: LogicalExportManifest = {\n format: LOGICAL_EXPORT_FORMAT,\n formatVersion: LOGICAL_EXPORT_FORMAT_VERSION,\n logical: true,\n exportedAt: (options.exportedAt ?? new Date()).toISOString(),\n plugin: {\n name: 'dsh-channels',\n version: options.pluginVersion ?? DSH_CHANNELS_VERSION,\n },\n includes: {\n descendants: false,\n attachments: false,\n rawArtifacts: false,\n },\n session: {\n id: session.id,\n header: session.header,\n },\n }\n const lines = [JSON.stringify(manifest)]\n for (const event of session.snapshotEvents()) lines.push(JSON.stringify(event))\n return Buffer.from(`${lines.join('\\n')}\\n`, 'utf8')\n}\n\n/** Serialize the logical snapshot, then gzip it entirely in memory. */\nexport async function createLogicalSessionExport(\n session: Session,\n options: LogicalSessionExportOptions = {},\n): Promise<LogicalSessionExportFile> {\n const snapshot = serializeLogicalSession(session, options)\n const source = await (options.compress ?? gzipBuffer)(snapshot)\n return {\n fileName: `dsh-session-${session.id}.logical.jsonl.gz`,\n source,\n }\n}\n","/**\n * Per-turn streaming card state: folds live session events into card snapshots\n * and patches the Feishu card on a fixed refresh interval (default 500 ms).\n *\n * Lifecycle per (session, user message):\n * 1. `attach()` — the reply card was just sent; bind it to the session as\n * segment 1.\n * 2. `session/event` consumption — text deltas fill the body, reasoning\n * deltas fill the fold panel, usage/header events advance the stats fold,\n * and tool-call / workflow-record events feed the two transient activity\n * lines: the delegation status line (子代理 / 工作流 progress, settled\n * workflow outcome, heartbeat) and the tool line (the parent's in-flight\n * tool call). The lines are INDEPENDENT — a later tool call never\n * supersedes the settled workflow status. Subagent children live in their\n * own sessions: their `subagent/descriptor` / `tool/call` / `turn/end`\n * events are folded into the parent turn's status line through\n * `session.header.parentSession` (status only — the child's text output\n * never reaches the card).\n * 3. When the body outgrows one card's budget, the overflow is split at a\n * clean breakpoint and sent as a continuation segment card (segment 2, 3,\n * …) into the same topic; the stream then keeps flowing into the newest\n * card. Reasoning is never segmented — it stays on card 1 and is clipped\n * to the leftover budget (body has priority).\n * 4. `turnEnded(turn)` — finalize every segment card (`回复结束` / `回复失败`)\n * and request the Typing → DONE/No reaction swap on the triggering message.\n *\n * The first visible text delta flips the panel title 推理中 → 回复中; the\n * final patch flips it to 回复结束 or 回复失败. Patching is throttled to `refreshMs` and\n * only runs when the content actually changed. Segment sends are serialized\n * per session so cards arrive in order.\n *\n * Keep-alive: turns that produce no text deltas for a while (tool calls,\n * subagent/workflow runs, provider-side work) are kept visibly alive by the\n * activity lines, whose elapsed counters are re-rendered on a slow heartbeat\n * (`heartbeatMs`, default 5 s) — the card never sits silently on a live turn.\n *\n * @module dsh-channels/streamer\n */\n\nimport type { AssistantStreamFrame, Session, SessionEvent, TurnEndReason } from './dsh-types.ts'\nimport { buildCard, segmentCardContent } from './cards.ts'\nimport type { CardState, PanelPhase } from './cards.ts'\nimport type { FeishuChannel } from './feishu-channel.ts'\nimport { cacheHitPct, foldAll, foldEvent } from './stats.ts'\nimport type { HeaderSample, StatsFold, UsageSample } from './stats.ts'\n\n/**\n * Compact build marker printed by the service at startup so a deployment's\n * console can confirm which build of the activity status line is loaded.\n * Bump on every behavior change to this feature.\n */\nexport const ACTIVITY_BUILD_MARKER = 'activity-status-v6'\n\n/** How a segment card reached Feishu. */\nexport interface SegmentDelivery {\n messageId: string\n /** `card` segments stream via `updateCard`; `text` messages are final\n * (delivered as a plain-text fallback and never patched afterwards). */\n mode: 'card' | 'text'\n}\n\n/** Card figures read from host projections (all optional: unit may be unmounted). */\nexport interface ProjectedStats {\n inputTokens?: number\n outputTokens?: number\n cacheHitPct?: number\n contextWindow?: number\n}\n\nexport interface StreamerHooks {\n /** Fetch current context pressure for one session (may be expensive → cached by caller). */\n measureContext(session: Session): { contextTokens?: number; contextWindow?: number }\n /**\n * Host-projection-backed figures for one session. When present these win\n * over the local incremental fold (the host units fold the durable embedded\n * assistant streams the plugin cannot see as live events).\n */\n projectStats?(session: Session): ProjectedStats | undefined\n /** Resolve the session id label shown on the card. */\n sessionLabel(sessionId: string): string\n /** Log a non-fatal streaming error. */\n onError(sessionId: string, error: unknown): void\n /**\n * Invoked once when a turn's card reaches its final state. Carries the main\n * card's message id so the caller can retire the turn's in-flight repair\n * snapshot (the id is the snapshot's storage key).\n */\n onTurnFinalized(sessionId: string, turn: number, mainCardMessageId: string): void\n /**\n * Invoked when a continuation segment's message id resolves (delivery\n * succeeded). Lets the caller fold the resolved id into the turn's repair\n * snapshot. `segmentIndex` is the card index within the turn (`0` is the\n * main card; never reported).\n */\n onSegmentDelivered?(\n mainCardMessageId: string,\n segmentIndex: number,\n messageId: string,\n mode: SegmentDelivery['mode'],\n ): void\n /**\n * Send one continuation segment card for a turn (calls arrive in segment\n * order per session). Implementations may fall back to a plain-text message\n * (reporting `mode: 'text'`) when the card is rejected — e.g. Feishu's\n * per-card table limit (error 11310) — so the content always reaches the\n * user. `fallbackText` carries the raw segment body for that fallback.\n */\n sendSegmentCard(\n sessionId: string,\n segment: number,\n card: object,\n fallbackText: string,\n ): Promise<SegmentDelivery>\n}\n\n/** One segment card of a turn. Card 1 is the attached reply card; further\n * cards are created lazily when the body overflows the previous card. */\ninterface SegmentCard {\n /** Feishu message id; undefined until the segment send resolves. */\n messageId: string | undefined\n /** Body accumulated for this segment (frozen once a later segment exists). */\n body: string\n /** True when the card has unsent content for its message id. */\n dirty: boolean\n /** True once this card stopped accepting body growth. */\n frozen: boolean\n /** Delivery mode once known; `text` segments are never patched. */\n mode: SegmentDelivery['mode'] | undefined\n /** True when delivery failed permanently (logged; never retried again). */\n failed: boolean\n /** True while a segment send is enqueued/in flight (prevents duplicate sends). */\n sending: boolean\n /** Consecutive update failures (drives the retry backoff). */\n failures: number\n /** Earliest timestamp for the next update attempt. */\n nextRetryAt: number\n}\n\ninterface TurnState {\n /** Turn number once known; undefined until the first `step/start`. */\n turn: number | undefined\n /** Message whose reaction carries Typing → DONE/No. */\n triggerMessageId: string\n /** Reaction id of the Typing reaction (undefined when the add failed). */\n reactionId: string | undefined\n phase: PanelPhase\n reasoning: string\n /** Request header produced during THIS turn; never inherited from history. */\n header: HeaderSample | undefined\n /** Provider usage produced during THIS turn; never inherited from history. */\n lastUsage: UsageSample | undefined\n /** Structured terminal failure for this turn, when `turn/end.kind=error`. */\n failure: { message: string; code?: string } | undefined\n /** Segment cards in order; `cards[0]` is the attached reply card. */\n cards: SegmentCard[]\n dirty: boolean\n lastPatchAt: number\n timer: ReturnType<typeof setTimeout> | undefined\n finalized: boolean\n /** Set by `detach()`; pending sends then stop scheduling further patches. */\n disposed: boolean\n /** Unix ms when the turn was attached (heartbeat elapsed base). */\n turnStartedAt: number\n /** The in-flight tool call, if any (cleared by its `tool/result`). */\n toolActivity: { name: string; label: string; since: number } | undefined\n /** Live workflow-run folds projected by the workflow tool into this session. */\n workflowRuns: Map<string, WorkflowRun>\n /** The last settled workflow run (kept visible until the next activity). */\n workflowEndStatus: { name: string; stopReason: string } | undefined\n /**\n * Live subagent child sessions folded into this turn (child session id →\n * status). Children are discovered through `session.header.parentSession`\n * and removed when their own turn ends.\n */\n children: Map<string, ChildActivity>\n /**\n * Labels of subagent children that FINISHED during this turn (capped). They\n * leave the live `children` fold at their turn/end but are summarized on\n * the settled card: `🤖 子代理完成:a、b`.\n */\n settled: string[]\n /** Currently rendered delegation status line (recomputed on every snapshot). */\n status: string\n /** Kind of the status line currently logged (change-detection only). */\n statusKind: string\n /** Currently rendered tool-call line (recomputed on every snapshot). */\n tool: string\n /** Kind of the tool line currently logged (change-detection only). */\n toolKind: string\n /** Slow keep-alive timer re-rendering the activity elapsed counter. */\n heartbeatTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Rollback marks for live stream attempts (`attemptId` → lengths at the\n * attempt's `start` frame). An `abandoned` attempt's deltas never reach the\n * durable model history, so the card must drop them again.\n */\n attempts: Map<string, AttemptMark>\n}\n\n/** Card/body lengths captured at one stream attempt's `start` frame. */\ninterface AttemptMark {\n cardCount: number\n bodyLengths: number[]\n reasoningLength: number\n}\n\n/** One live `tool-workflow/run-*` fold: run identity and member progress. */\ninterface WorkflowRun {\n name: string\n startedAt: number\n /** Members started so far. */\n started: number\n /** Members settled so far. */\n ended: number\n /** Still-running members (started minus ended), in start order. */\n active: Array<{ seq: number; label: string }>\n /** Latest phase declared by a member (`phase()` in the workflow script). */\n phase?: string\n /**\n * Child session ids of this run's members — their own events are already\n * aggregated by the workflow line and must not be folded again as generic\n * children.\n */\n memberIds: Set<string>\n}\n\n/** One running subagent child's displayable status. */\ninterface ChildActivity {\n /** Delegation description (subagent/descriptor label), default 子代理. */\n label: string\n /** The child's latest tool call, if any. */\n tool?: string\n /** When the child was first observed (elapsed base). */\n since: number\n}\n\n/** Hard cap on the activity line so it can never threaten the card budget. */\nconst ACTIVITY_MAX_CHARS = 64\n\n/** Trim an activity line to `ACTIVITY_MAX_CHARS` (ellipsis counts as one char). */\nfunction clipActivity(line: string): string {\n return line.length <= ACTIVITY_MAX_CHARS ? line : `${line.slice(0, ACTIVITY_MAX_CHARS - 1)}…`\n}\n\n/** `已运行 45s` / `已运行 2分5s` — the activity line's elapsed counter. */\nfunction fmtElapsed(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000))\n if (total < 60) return `已运行 ${total}s`\n return `已运行 ${Math.floor(total / 60)}分${total % 60}s`\n}\n\n/**\n * Human label for one tool call: the subagent tool contributes its\n * `description`, the workflow tool its `meta.name`; anything else uses the\n * bare tool name. Arguments are model-produced JSON — parsing failures fall\n * back to the tool name instead of failing the fold.\n */\nfunction toolActivityLabel(name: string, rawArguments: string): string {\n if (name !== 'subagent' && name !== 'workflow') return name\n try {\n const args = JSON.parse(rawArguments) as Record<string, unknown>\n if (name === 'subagent' && typeof args.description === 'string' && args.description !== '') {\n return args.description\n }\n if (name === 'workflow') {\n const meta = args.meta as Record<string, unknown> | undefined\n if (meta !== undefined && typeof meta.name === 'string' && meta.name !== '') return meta.name\n }\n } catch {\n // Unparseable arguments: fall back to the bare tool name.\n }\n return name\n}\n\n/** `completed`/`failed`/`cancelled` → the Chinese line fragment. */\nconst WORKFLOW_OUTCOMES: Record<string, string> = {\n completed: '完成',\n failed: '失败',\n cancelled: '已取消',\n error: '失败',\n}\n\nexport class TurnStreamer {\n private readonly channel: FeishuChannel\n private readonly refreshMs: number\n private readonly hooks: StreamerHooks\n /** Slow keep-alive interval for the activity line's elapsed counter. */\n private readonly heartbeatMs: number\n /** sessionId → queue of turn states (FIFO consumption of the driver). */\n private readonly bySession = new Map<string, TurnState[]>()\n /** sessionId → session-log stats fold (session cumulative). */\n private readonly folds = new Map<string, StatsFold>()\n /** sessionId → the Session object for context measurement. */\n private readonly sessions = new Map<string, Session>()\n /** sessionId → context pressure cache (refreshed every N patches). */\n private readonly contextCache = new Map<string, { contextTokens?: number; contextWindow?: number }>()\n /** sessionId → serialized segment-send chain (keeps card order). */\n private readonly segmentChains = new Map<string, Promise<void>>()\n\n constructor(channel: FeishuChannel, refreshMs: number, hooks: StreamerHooks, heartbeatMs: number = 5_000) {\n this.channel = channel\n this.refreshMs = refreshMs\n this.hooks = hooks\n this.heartbeatMs = heartbeatMs\n }\n\n /** Bind a freshly sent reply card to a session; seeds the stats fold from the log. */\n attach(session: Session, cardMessageId: string, triggerMessageId: string, reactionId: string | undefined): void {\n this.sessions.set(session.id, session)\n let fold = this.folds.get(session.id)\n if (fold === undefined) {\n fold = foldAll(session.snapshotEvents())\n this.folds.set(session.id, fold)\n }\n const queue = this.bySession.get(session.id) ?? []\n queue.push({\n turn: undefined,\n triggerMessageId,\n reactionId,\n phase: 'thinking',\n reasoning: '',\n header: undefined,\n lastUsage: undefined,\n failure: undefined,\n cards: [{\n messageId: cardMessageId,\n body: '',\n dirty: true,\n frozen: false,\n mode: 'card',\n failed: false,\n sending: false,\n failures: 0,\n nextRetryAt: 0,\n }],\n dirty: true,\n lastPatchAt: 0,\n timer: undefined,\n finalized: false,\n disposed: false,\n turnStartedAt: Date.now(),\n toolActivity: undefined,\n workflowRuns: new Map(),\n workflowEndStatus: undefined,\n children: new Map(),\n settled: [],\n status: '',\n statusKind: '',\n tool: '',\n toolKind: '',\n heartbeatTimer: undefined,\n attempts: new Map(),\n })\n this.bySession.set(session.id, queue)\n const state = queue[queue.length - 1]!\n this.scheduleHeartbeat(session.id, state)\n }\n\n /** Drop all state for a session (agent disposed / route removed). */\n detach(sessionId: string): void {\n const queue = this.bySession.get(sessionId) ?? []\n for (const state of queue) {\n state.disposed = true\n if (state.timer !== undefined) clearTimeout(state.timer)\n if (state.heartbeatTimer !== undefined) clearTimeout(state.heartbeatTimer)\n }\n this.bySession.delete(sessionId)\n this.folds.delete(sessionId)\n this.sessions.delete(sessionId)\n this.contextCache.delete(sessionId)\n this.segmentChains.delete(sessionId)\n }\n\n hasSession(sessionId: string): boolean {\n return this.bySession.has(sessionId)\n }\n\n /** The front (oldest unfinished) turn state of a session. */\n private front(sessionId: string): TurnState | undefined {\n const queue = this.bySession.get(sessionId)\n if (queue === undefined || queue.length === 0) return undefined\n for (const state of queue) {\n if (!state.finalized) return state\n }\n return undefined\n }\n\n /** Consume one appended session event. */\n onSessionEvent(session: Session, event: SessionEvent): void {\n const fold = this.folds.get(session.id)\n if (fold !== undefined) foldEvent(fold, event)\n this.sessions.set(session.id, session)\n\n // Subagent children live in their OWN sessions (header.parentSession →\n // caller). Their events never touch the parent's log, so fold their\n // status into the parent turn's activity line here.\n const parentId = session.header.parentSession\n if (parentId !== undefined) {\n const parentState = this.front(parentId)\n if (parentState !== undefined) {\n this.foldChildEvent(parentId, parentState, session, event)\n return\n }\n }\n\n const state = this.front(session.id)\n if (state === undefined) return\n\n switch (event.type) {\n case 'step/start': {\n if (state.turn === undefined) {\n state.turn = event.data.turn\n this.markDirty(session.id, state)\n }\n break\n }\n case 'assistant/message': {\n if (state.turn === undefined) state.turn = event.data.turn\n if (event.data.usage !== undefined) {\n state.lastUsage = {\n usage: event.data.usage,\n turn: event.data.turn,\n step: event.data.step,\n }\n this.markDirty(session.id, state)\n }\n break\n }\n case 'request/header': {\n const config = event.data.header.config\n // Parse this exact event instead of copying the session fold's latest\n // sample, so malformed/current events can never expose prior history.\n state.header = {\n provider: typeof config.provider === 'string' ? config.provider : '',\n model: typeof config.model === 'string' ? config.model : '',\n effort: typeof config.reasoningEffort === 'string' ? config.reasoningEffort : undefined,\n }\n this.markDirty(session.id, state)\n break\n }\n case 'tool/call': {\n if (state.turn === undefined) state.turn = event.data.turn\n state.toolActivity = {\n name: event.data.name,\n label: toolActivityLabel(event.data.name, event.data.arguments),\n since: Date.now(),\n }\n // The tool line is independent: it never supersedes the settled\n // workflow end status on the delegation line (both render together).\n this.markDirty(session.id, state)\n break\n }\n case 'tool/result': {\n if (state.turn === undefined) state.turn = event.data.turn\n state.toolActivity = undefined\n this.markDirty(session.id, state)\n break\n }\n case 'tool-workflow/run-start': {\n // Workflow record events carry no turn/step — they belong to the\n // front (oldest unfinished) turn by construction.\n state.workflowRuns.set(event.data.runId, {\n name: event.data.name,\n startedAt: Date.now(),\n started: 0,\n ended: 0,\n active: [],\n memberIds: new Set(),\n })\n state.workflowEndStatus = undefined\n this.markDirty(session.id, state)\n break\n }\n case 'tool-workflow/agent-start': {\n const run = state.workflowRuns.get(event.data.runId)\n if (run !== undefined) {\n run.started++\n run.active.push({ seq: event.data.seq, label: event.data.label })\n run.memberIds.add(event.data.childId)\n if (event.data.phase !== undefined) run.phase = event.data.phase\n this.markDirty(session.id, state)\n }\n break\n }\n case 'tool-workflow/agent-end': {\n const run = state.workflowRuns.get(event.data.runId)\n if (run !== undefined) {\n run.ended++\n const index = run.active.findIndex(member => member.seq === event.data.seq)\n if (index >= 0) run.active.splice(index, 1)\n this.markDirty(session.id, state)\n }\n break\n }\n case 'tool-workflow/run-end': {\n const run = state.workflowRuns.get(event.data.runId)\n if (run !== undefined) {\n state.workflowRuns.delete(event.data.runId)\n state.workflowEndStatus = {\n name: run.name,\n stopReason: WORKFLOW_OUTCOMES[event.data.stopReason] ?? event.data.stopReason,\n }\n this.markDirty(session.id, state)\n }\n break\n }\n case 'turn/end': {\n // The ended turn belongs to the front state: assign it if a turn was\n // rejected/emptied before any step started.\n if (state.turn === undefined) state.turn = event.data.turn\n this.finalize(session.id, state, turnFailure(event.data.reason))\n break\n }\n default:\n break\n }\n }\n\n /**\n * Consume one live assistant-stream frame (`agent/assistant-stream`).\n *\n * DSH stopped emitting durable `assistant/chunk` events (commit\n * `f99b06eaed`), so this transient agent-scoped publication is the only\n * live delta source. `start` records rollback marks; `end` keeps the deltas\n * only when the attempt committed to the durable log — an `abandoned`\n * attempt's text is not model-visible history and must leave the card again.\n */\n onAssistantFrame(session: Session, frame: AssistantStreamFrame): void {\n this.sessions.set(session.id, session)\n const state = this.front(session.id)\n if (state === undefined || state.disposed) return\n switch (frame.type) {\n case 'start': {\n state.attempts.set(frame.attemptId, this.attemptMark(state))\n if (state.turn === undefined) state.turn = frame.turn\n break\n }\n case 'chunk': {\n const chunk = frame.chunk\n if (chunk.type === 'text-delta') {\n const last = state.cards[state.cards.length - 1]\n if (last !== undefined && !last.frozen && chunk.text !== '') {\n last.body += chunk.text\n if (state.phase === 'thinking') state.phase = 'replying'\n this.markDirty(session.id, state)\n }\n } else if (chunk.type === 'reasoning-delta' && chunk.text !== '') {\n state.reasoning += chunk.text\n this.markDirty(session.id, state)\n }\n break\n }\n case 'end': {\n const mark = state.attempts.get(frame.attemptId)\n state.attempts.delete(frame.attemptId)\n if (frame.outcome.kind === 'abandoned' && mark !== undefined) {\n this.rollbackAttempt(state, mark)\n this.markDirty(session.id, state)\n }\n break\n }\n }\n }\n\n /** Capture the card/body lengths an attempt's deltas start from. */\n private attemptMark(state: TurnState): AttemptMark {\n return {\n cardCount: state.cards.length,\n bodyLengths: state.cards.map(card => card.body.length),\n reasoningLength: state.reasoning.length,\n }\n }\n\n /**\n * Drop one abandoned attempt's deltas. The attempt's text is a contiguous\n * suffix of the logical body: segments created during the attempt are\n * removed, the segment that was last at `start` is truncated, and the\n * reasoning tail is cut. Already-delivered plain-text segments cannot be\n * retracted and stop the removal.\n */\n private rollbackAttempt(state: TurnState, mark: AttemptMark): void {\n const lastIndex = mark.cardCount - 1\n for (let index = state.cards.length - 1; index > lastIndex; index -= 1) {\n const card = state.cards[index]\n if (card === undefined) continue\n if (card.mode === 'text' && card.messageId !== undefined) break\n state.cards.splice(index, 1)\n }\n const tail = state.cards[lastIndex]\n const length = mark.bodyLengths[lastIndex]\n if (tail !== undefined && length !== undefined\n && !(tail.mode === 'text' && tail.messageId !== undefined)) {\n tail.body = tail.body.slice(0, length)\n tail.dirty = true\n }\n state.reasoning = state.reasoning.slice(0, mark.reasoningLength)\n }\n\n private markDirty(sessionId: string, state: TurnState): void {\n if (state.disposed) return\n state.dirty = true\n if (state.timer !== undefined) return\n const wait = state.lastPatchAt === 0\n ? this.refreshMs\n : Math.max(0, this.refreshMs - (Date.now() - state.lastPatchAt))\n state.timer = setTimeout(() => {\n state.timer = undefined\n this.flush(sessionId, state)\n }, wait)\n }\n\n private statsFor(sessionId: string, state: TurnState): CardState['stats'] {\n const fold = this.folds.get(sessionId)\n const lastUsage = state.lastUsage\n const header = state.header\n const cached = this.contextCache.get(sessionId)\n const session = this.sessions.get(sessionId)\n let context = cached\n if (session !== undefined) {\n try {\n context = this.hooks.measureContext(session)\n this.contextCache.set(sessionId, context)\n } catch {\n context = cached\n }\n }\n // Host projections are authoritative when mounted: they fold the durable\n // embedded assistant streams the plugin can no longer see as live events.\n const projected = session === undefined ? undefined : this.hooks.projectStats?.(session)\n return {\n sessionId: this.hooks.sessionLabel(sessionId),\n inputTokens: projected?.inputTokens ?? fold?.totals.inputTokens ?? 0,\n outputTokens: projected?.outputTokens ?? fold?.totals.outputTokens ?? 0,\n cacheHitPct: projected?.cacheHitPct ?? cacheHitPct(lastUsage),\n contextTokens: context?.contextTokens,\n // The context-token count is session-wide, but the model window belongs\n // to the current request. Suppress it when this turn failed before a\n // request header was emitted, just like model/effort/cache.\n contextWindow: header === undefined\n ? undefined\n : context?.contextWindow ?? projected?.contextWindow,\n model: header?.model,\n effort: header?.effort,\n }\n }\n\n /**\n * Fold one event from a subagent child session into its parent turn's\n * activity state. Only identity/progress signals are kept — the child's\n * text output stays out of the user-facing card (the parent model receives\n * it as the tool result and composes the final reply itself).\n *\n * Workflow members are skipped here: their childId is recorded on the run\n * fold and the workflow line already aggregates them.\n */\n private foldChildEvent(parentId: string, state: TurnState, child: Session, event: SessionEvent): void {\n for (const run of state.workflowRuns.values()) {\n if (run.memberIds.has(child.id)) return\n }\n switch (event.type) {\n case 'subagent/descriptor': {\n const label = typeof event.data.label === 'string' && event.data.label !== ''\n ? event.data.label\n : '子代理'\n const existing = state.children.get(child.id)\n if (existing === undefined) {\n state.children.set(child.id, { label, tool: undefined, since: Date.now() })\n } else if (existing.label === '子代理') {\n existing.label = label\n }\n this.markDirty(parentId, state)\n break\n }\n case 'tool/call': {\n const existing = state.children.get(child.id)\n if (existing === undefined) {\n state.children.set(child.id, { label: '子代理', tool: event.data.name, since: Date.now() })\n } else {\n existing.tool = event.data.name\n }\n this.markDirty(parentId, state)\n break\n }\n case 'tool/result': {\n const existing = state.children.get(child.id)\n if (existing !== undefined && existing.tool !== undefined) {\n existing.tool = undefined\n this.markDirty(parentId, state)\n }\n break\n }\n case 'turn/end': {\n const entry = state.children.get(child.id)\n if (entry === undefined) break\n state.children.delete(child.id)\n // A finished child leaves the live fold but is summarized on the\n // settled card (capped; placeholder labels are skipped).\n if (entry.label !== '子代理' && state.settled.length < 3) {\n state.settled.push(entry.label)\n }\n this.markDirty(parentId, state)\n break\n }\n default:\n break\n }\n }\n\n /**\n * The delegation status line, by priority:\n * 1. the most recently started live workflow run (member progress + phase);\n * 2. running subagent children (label + current tool + elapsed);\n * 3. the last settled workflow run's outcome — it persists across later\n * tool calls and text streaming (only a new run-start replaces it), so\n * the workflow conclusion stays visible;\n * 4. heartbeat fallback — a thinking turn with no output yet still shows\n * an elapsed counter so the card never sits silently.\n * Once the turn is finalized, the line switches to the SETTLED form\n * (workflow conclusion + finished-subagent summary) — the final card keeps\n * the delegation status; only the tool line disappears.\n */\n private delegationLine(state: TurnState, now: number): string {\n if (state.failure !== undefined) {\n const suffix = state.failure.code === undefined ? '' : ` · ${state.failure.code}`\n return clipActivity(`🔴 本轮运行失败${suffix}`)\n }\n if (state.finalized) return this.settledLine(state)\n\n let latest: WorkflowRun | undefined\n for (const run of state.workflowRuns.values()) {\n if (latest === undefined || run.startedAt > latest.startedAt) latest = run\n }\n if (latest !== undefined) {\n let line = `🧵 工作流 ${latest.name} · ${latest.ended}/${latest.started} 子代理完成`\n if (latest.phase !== undefined) line += ` · 阶段 ${latest.phase}`\n const labels = latest.active.slice(0, 3).map(member => member.label)\n if (labels.length > 0) line += ` · ${labels.join('、')}`\n line += ` · ${fmtElapsed(now - latest.startedAt)}`\n return clipActivity(line)\n }\n\n const children = [...state.children.values()]\n if (children.length > 0) {\n const listed = children.slice(0, 2).map(child => {\n const tool = child.tool === undefined ? '' : ` (${child.tool})`\n return `${child.label}${tool}`\n })\n const head = children.length === 1\n ? `🤖 子代理执行中:${listed[0]}`\n : `🤖 ${children.length} 个子代理执行中:${listed.join('、')}${children.length > 2 ? '、…' : ''}`\n const oldest = Math.min(...children.map(child => child.since))\n return clipActivity(`${head} · ${fmtElapsed(now - oldest)}`)\n }\n\n if (state.workflowEndStatus !== undefined) {\n const { name, stopReason } = state.workflowEndStatus\n return clipActivity(`🧵 工作流 ${name} 结束(${stopReason})`)\n }\n\n const empty = state.cards.every(card => card.body === '')\n // The tool line already shows liveness while a call is in flight, so the\n // heartbeat fallback stays out of the way.\n if (state.phase === 'thinking' && empty && state.toolActivity === undefined) {\n return clipActivity(`⏳ 处理中 · ${fmtElapsed(now - state.turnStartedAt)}`)\n }\n return ''\n }\n\n /**\n * The settled-card form of the delegation status line: the workflow\n * conclusion and/or the finished-subagent summary, without elapsed counters.\n * Still-running (background) children are noted factually. Empty when the\n * turn delegated nothing.\n */\n private settledLine(state: TurnState): string {\n const parts: string[] = []\n if (state.workflowEndStatus !== undefined) {\n const { name, stopReason } = state.workflowEndStatus\n parts.push(`🧵 工作流 ${name} 结束(${stopReason})`)\n }\n if (state.settled.length > 0) {\n parts.push(`🤖 子代理完成:${state.settled.join('、')}`)\n }\n if (parts.length > 0) return clipActivity(parts.join(' · '))\n\n const children = [...state.children.values()]\n if (children.length > 0) {\n const listed = children.slice(0, 2).map(child =>\n `${child.label}${child.tool === undefined ? '' : ` (${child.tool})`}`)\n const head = children.length === 1\n ? `🤖 子代理执行中:${listed[0]}`\n : `🤖 ${children.length} 个子代理执行中:${listed.join('、')}${children.length > 2 ? '、…' : ''}`\n return clipActivity(head)\n }\n return ''\n }\n\n /**\n * The tool-call line: the parent agent's in-flight tool call, independent\n * of the delegation status line — workflow/subagent status never hides it\n * and it never hides them. Cleared by the tool's `tool/result`; always\n * empty on the settled card.\n */\n private toolLine(state: TurnState, now: number): string {\n if (state.finalized) return ''\n const tool = state.toolActivity\n if (tool === undefined) return ''\n const prefix = tool.name === 'subagent'\n ? '🤖 调用子代理:'\n : tool.name === 'workflow'\n ? '🧵 调用工作流:'\n : '🔧 调用工具:'\n return clipActivity(`${prefix}${tool.label} · ${fmtElapsed(now - tool.since)}`)\n }\n\n /**\n * Slow keep-alive: recompute both activity lines and patch only when their\n * rendered text actually changed (the elapsed counter ticking). Reschedules\n * itself while the turn is pending; `finalize`/`detach` cancel it.\n */\n private scheduleHeartbeat(sessionId: string, state: TurnState): void {\n if (state.disposed || state.finalized || state.heartbeatTimer !== undefined) return\n state.heartbeatTimer = setTimeout(() => {\n state.heartbeatTimer = undefined\n if (state.disposed || state.finalized) return\n const now = Date.now()\n if (this.delegationLine(state, now) !== state.status\n || this.toolLine(state, now) !== state.tool) {\n this.markDirty(sessionId, state)\n }\n this.scheduleHeartbeat(sessionId, state)\n }, this.heartbeatMs)\n }\n\n private cardState(sessionId: string, state: TurnState, body: string): CardState {\n // The snapshot point recomputes (and remembers) both activity lines so\n // every patch — including heartbeat-driven ones — carries the current\n // status and tool call.\n const now = Date.now()\n const status = this.delegationLine(state, now)\n if (status !== state.status) {\n state.status = status\n const kind = this.noteKind('status', status, state.statusKind)\n if (kind !== undefined) state.statusKind = kind\n }\n const tool = this.toolLine(state, now)\n if (tool !== state.tool) {\n state.tool = tool\n const kind = this.noteKind('tool', tool, state.toolKind)\n if (kind !== undefined) state.toolKind = kind\n }\n return {\n phase: state.phase,\n reasoning: state.reasoning,\n body,\n status: state.status,\n tool: state.tool,\n stats: this.statsFor(sessionId, state),\n }\n }\n\n /**\n * Diagnostic: log activity-type transitions only (first segment of the\n * line, stable across elapsed/count ticks) so a live test can confirm\n * exactly which status the card is being patched with.\n */\n private noteKind(slot: 'status' | 'tool', line: string, current: string): string | undefined {\n const kind = line === '' ? '' : (line.split('·')[0] ?? '').trim()\n if (kind === current) return undefined\n console.info(`[dsh-channels] activity ${slot} →`, kind === '' ? '(收起)' : kind)\n return kind\n }\n\n /**\n * Freeze the newest card once its body stops fitting its budget (bytes or\n * the per-card table cap) and move the overflow into fresh segment cards\n * (their sends are chained in order).\n */\n private splitOverflow(sessionId: string, state: TurnState): void {\n for (;;) {\n const index = state.cards.length - 1\n const card = state.cards[index]\n if (card === undefined || card.frozen || card.mode === 'text') return\n\n const options = index === 0 ? {} : { panel: false, segment: index + 1 }\n const split = segmentCardContent(this.cardState(sessionId, state, card.body), options)\n if (split.head === '') {\n // Degenerate budget (cannot happen with the real fixed overhead):\n // keep streaming into this card; buildCard's absolute clip still\n // guards the card size.\n return\n }\n if (split.tail === '') {\n card.body = split.head\n card.dirty = true\n // A card with content but no message id yet (e.g. the continuation of\n // a text-fallback segment) needs its own send, not just a patch.\n if (card.messageId === undefined && !card.sending && !card.failed && card.body !== '') {\n this.sendSegment(sessionId, state, index)\n }\n return\n }\n\n card.body = split.head\n card.frozen = true\n card.dirty = true\n const next: SegmentCard = this.freshSegment(split.tail)\n state.cards.push(next)\n this.sendSegment(sessionId, state, state.cards.length - 1)\n }\n }\n\n private freshSegment(body: string): SegmentCard {\n return {\n messageId: undefined,\n body,\n dirty: true,\n frozen: false,\n mode: undefined,\n failed: false,\n sending: false,\n failures: 0,\n nextRetryAt: 0,\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n }\n\n /**\n * Serialize one continuation-card send per session (order on Feishu), with\n * up to two retries (1s / 2s backoff) for transient failures. When delivery\n * resolves as a text fallback the segment is frozen and further growth\n * continues in a new segment — text messages cannot be patched afterwards.\n */\n private sendSegment(sessionId: string, state: TurnState, index: number): void {\n const card = state.cards[index]\n if (card === undefined || card.failed || card.sending || state.disposed) return\n card.sending = true\n\n const previous = this.segmentChains.get(sessionId) ?? Promise.resolve()\n const next = previous.then(async () => {\n let lastError: unknown\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n const snapshot = buildCard(this.cardState(sessionId, state, card.body), {\n panel: false,\n segment: index + 1,\n })\n const delivery = await this.hooks.sendSegmentCard(sessionId, index + 1, snapshot, card.body)\n card.sending = false\n card.messageId = delivery.messageId\n card.mode = delivery.mode\n this.hooks.onSegmentDelivered?.(\n state.cards[0]?.messageId ?? '',\n index,\n delivery.messageId,\n delivery.mode,\n )\n if (delivery.mode === 'card') {\n card.dirty = true\n this.markDirty(sessionId, state)\n } else if (!card.frozen && !state.disposed) {\n // Text fallback: this card is final; keep streaming into a new one.\n card.frozen = true\n if (state.cards[state.cards.length - 1] === card) {\n state.cards.push(this.freshSegment(''))\n }\n this.markDirty(sessionId, state)\n }\n return\n } catch (error) {\n lastError = error\n if (attempt >= 2 || state.disposed) break\n await this.delay(1_000 * 2 ** attempt)\n }\n }\n card.sending = false\n card.failed = true\n this.hooks.onError(sessionId, lastError)\n })\n this.segmentChains.set(sessionId, next.then(() => {}, () => {}))\n }\n\n /** Patch one card when due; failed updates retry with exponential backoff\n * (logged on the first failure and every 10th — no per-flush spam).\n *\n * The patch gate covers both per-card body growth (`card.dirty`) and\n * turn-scoped snapshot changes (`turnDirty`: phase, reasoning, activity\n * line, session stats), so activity-only events still reach the card. */\n private patchCard(sessionId: string, state: TurnState, index: number, force: boolean, turnDirty: boolean): void {\n const card = state.cards[index]\n if (card === undefined || card.messageId === undefined || card.mode === 'text' || card.failed) return\n const now = Date.now()\n if (!force && !card.dirty && !turnDirty) return\n if (!force && card.nextRetryAt > now) return\n card.dirty = false\n const options = index === 0 ? {} : { panel: false, segment: index + 1 }\n const snapshot = buildCard(this.cardState(sessionId, state, card.body), options)\n this.channel.updateCard(card.messageId, snapshot).catch((error: unknown) => {\n card.dirty = true\n card.failures++\n card.nextRetryAt = Date.now() + Math.min(1_000 * 2 ** Math.min(card.failures, 6), 60_000)\n if (card.failures === 1 || card.failures % 10 === 0) {\n this.hooks.onError(sessionId, error)\n }\n })\n }\n\n private flush(sessionId: string, state: TurnState): void {\n state.lastPatchAt = Date.now()\n const turnDirty = state.dirty\n if (state.dirty) {\n state.dirty = false\n this.splitOverflow(sessionId, state)\n }\n for (const [index] of state.cards.entries()) {\n this.patchCard(sessionId, state, index, false, turnDirty)\n }\n }\n\n private finalize(\n sessionId: string,\n state: TurnState,\n failure: { message: string; code?: string } | undefined,\n ): void {\n if (state.finalized) return\n state.finalized = true\n state.failure = failure\n state.phase = failure === undefined ? 'done' : 'error'\n if (failure !== undefined) {\n const code = failure.code === undefined ? '' : `\\n\\n错误码:\\`${failure.code}\\``\n const notice = `❌ **本轮运行失败**\\n\\n${failure.message}${code}`\n const tail = state.cards[state.cards.length - 1]\n if (tail !== undefined) {\n if (tail.mode === 'text' || tail.failed) {\n // An already-delivered plain-text fallback cannot be patched, and a\n // permanently failed card cannot surface new content. Put the\n // terminal diagnostic in its own continuation card instead.\n state.cards.push(this.freshSegment(notice))\n } else {\n tail.body = tail.body.trim() === '' ? notice : `${tail.body}\\n\\n---\\n\\n${notice}`\n tail.dirty = true\n }\n }\n }\n if (state.timer !== undefined) {\n clearTimeout(state.timer)\n state.timer = undefined\n }\n // The settled card keeps the delegation status (workflow conclusion /\n // subagent summary — `delegationLine` renders its settled form once\n // `finalized` is set); only the transient tool line disappears. The\n // heartbeat stops.\n if (state.heartbeatTimer !== undefined) {\n clearTimeout(state.heartbeatTimer)\n state.heartbeatTimer = undefined\n }\n state.tool = ''\n // Freeze the tail and spawn any remaining segment cards.\n this.splitOverflow(sessionId, state)\n\n // Final patches with the settled content: every patchable card flips to\n // 回复结束 / 回复失败 (backoff is bypassed once so the final state always lands;\n // late-arriving sends are patched when they resolve).\n for (const [index] of state.cards.entries()) {\n this.patchCard(sessionId, state, index, true, false)\n }\n\n // Reaction swap: Typing → DONE on success, Typing → No on failure.\n void this.swapReaction(state, failure !== undefined)\n\n // Prune the finalized state lazily (kept until the next attach so the\n // front-scan skips it, then removed).\n void Promise.resolve().then(() => {\n const queue = this.bySession.get(sessionId)\n if (queue === undefined) return\n const index = queue.indexOf(state)\n if (index >= 0) queue.splice(index, 1)\n })\n\n this.hooks.onTurnFinalized(sessionId, state.turn ?? 0, state.cards[0]?.messageId ?? '')\n }\n\n private async swapReaction(state: TurnState, failed: boolean): Promise<void> {\n try {\n if (state.reactionId !== undefined) {\n await this.channel.removeReaction(state.triggerMessageId, state.reactionId)\n }\n await this.channel.addReaction(state.triggerMessageId, failed ? 'No' : 'DONE')\n } catch (error) {\n // Reaction failures are cosmetic: never surface them to the user.\n this.hooks.onError(state.triggerMessageId, error)\n }\n }\n}\n\n/** Extract the user-visible failure from the current Harness turn-end union. */\nfunction turnFailure(reason: TurnEndReason): { message: string; code?: string } | undefined {\n if (typeof reason !== 'object' || reason === null || reason.kind !== 'error') return undefined\n const error = reason.error\n const message = typeof error?.message === 'string' && error.message !== ''\n ? error.message\n : '模型请求失败'\n const code = typeof error?.code === 'string' && error.code !== '' ? error.code : undefined\n return { message, code }\n}\n","/**\n * The Feishu bridge service: inbound routing, workspace/session ownership,\n * topic creation, and streaming-card orchestration.\n *\n * Ownership model (one workspace per user × chat):\n * - p2p: one workspace per (bot, user), all of that user's sessions inside;\n * - group: one workspace per (chat, user), sessions grouped per user.\n *\n * Routing model:\n * - `thread_id` → bound session (no @ needed inside an owned topic);\n * - degraded chat-scoped keys when Feishu refuses topic replies;\n * - new topic + new session for an @-triggered message without a route.\n *\n * @module dsh-channels/service\n */\n\nimport { lstat, readdir, realpath, rm } from 'node:fs/promises'\nimport path from 'node:path'\nimport { AccessController } from './access/controller.ts'\nimport { AccessRequests } from './access/requests.ts'\nimport { AccessStore } from './access/store.ts'\nimport { ApprovalAnswerer } from './approvals.ts'\nimport type { ApprovalRoute } from './approvals.ts'\nimport { QuestionAnswerer } from './questions.ts'\nimport type {\n CommandInvocation,\n CommandResult,\n CredentialInfo,\n CredentialProvider,\n CordisFiber,\n HostContext,\n LlmService,\n Session,\n SessionId,\n SessionProjections,\n TokenMeter,\n Workspace,\n} from './dsh-types.ts'\nimport { standaloneInfoCard } from './cards.ts'\nimport {\n armEffortCommand, armModelCommand, armRenewCommand, registerModelCommand, commandResultCardKind, parseSlashCommand,\n registerEffortCommand, registerRenewCommand, RouteOperationQueue, supportedCommandHelp, supportedCommandList,\n} from './commands.ts'\nimport type { EffortCommandAction, ParsedSlashCommand } from './commands.ts'\nimport { commandSpec } from './commands/registry.ts'\nimport type { ChannelCommandSpec } from './commands/registry.ts'\nimport type { PermissionPresets, SessionQuery } from './dsh-types.ts'\nimport { feishuDomain } from './domain.ts'\nimport {\n applyWorkspaceCleanup, isPathInside, planWorkspaceCleanup, retentionMs,\n} from './cleanup.ts'\nimport type { CleanupDeps, WorkspaceStats } from './cleanup.ts'\nimport type {\n AccessGroupRecord, AccessRequestRecord, AccessUserRecord,\n ExpiredRouteRecord, FeishuDomainGlobal, FeishuDomainHandle, InflightSnapshot, ProcessedRecord, RawDomainHandle,\n RouteRecord,\n} from './domain.ts'\nimport { RouteStore } from './session/routes.ts'\nimport { InflightRepair } from './session/repair.ts'\nimport { SessionManager } from './session/manager.ts'\nimport type { LiveAgent, RenewCommit } from './session/manager.ts'\nimport { CardDelivery } from './feishu/delivery.ts'\nimport { AttachmentIntake } from './feishu/inbound-attachments.ts'\nimport { InboundRouter } from './routing/inbound.ts'\nimport type { RequestPolicyController } from './session/request-policy.ts'\nimport type { ChannelFactory, FeishuChannel, NormalizedMessage } from './feishu-channel.ts'\nimport { buildHealthCard, foldHealthSnapshot, HEALTH_DEPENDENCY_NAMES } from './health.ts'\nimport type { HealthDependencyName, HealthSnapshot } from './health.ts'\nimport {\n buildCompactComparisonCard, buildRenewCard, buildSessionHistoryCard, buildSessionStatusCard, buildWorkDirCard,\n compactFailureContext, resolveWorkDir, SESSION_HISTORY_DISPLAY_LIMIT,\n} from './session-commands.ts'\nimport type { SessionHistoryAnnotation } from './session-commands.ts'\nimport { normalizeSessionHistory } from './domain.ts'\nimport { foldSessionStats, measureSessionContext, readProjectionViews } from './stats.ts'\nimport type { ContextMeasurement, ContextServices } from './stats.ts'\nimport { createLogicalSessionExport } from './session-export.ts'\nimport type { SessionExportBuilder } from './session-export.ts'\nimport { ACTIVITY_BUILD_MARKER, TurnStreamer } from './streamer.ts'\nimport type { ProjectedStats, StreamerHooks } from './streamer.ts'\nimport type { FeishuConfig } from './config.ts'\nimport type {\n AccessMutationResponse, AccessRequestWire, AccessWire,\n} from './settings/contract.ts'\nimport type { OverlayStore } from './settings/overlay.ts'\n\n/** Periodic route-GC sweep cadence (a long-running instance also accumulates\n * dead records; TTL semantics make 6 h granularity more than sufficient). */\nconst GC_SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1000\n\n/** Dedup-record retention: Feishu redelivery windows are minutes-scale, so a\n * day is 100×+ headroom while keeping the table bounded. */\nconst PROCESSED_RETENTION_MS = 24 * 60 * 60 * 1000\n\n/** Workspace-retention sweep cadence when automatic cleanup is enabled. */\nconst RETENTION_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000\n\n\ninterface RenewAdmission {\n route: RouteRecord\n live: LiveAgent\n commit?: RenewCommit\n}\n\ninterface EffortAdmission {\n live: LiveAgent\n controller: RequestPolicyController\n}\n\ninterface ModelAdmission {\n live: LiveAgent\n controller: RequestPolicyController\n}\n\n/** Wrap the host's storage facility open behind a stable local type. */\nasync function openFeishuDomain(ctx: HostContext): Promise<FeishuDomainHandle> {\n const domain = await ctx.storageDomain.open(feishuDomain) as unknown as RawDomainHandle\n const table = domain.table('routes')\n const inflightRaw = domain.table('inflight')\n const processedRaw = domain.table('processed')\n const expiredRoutesRaw = domain.table('expired_routes')\n const accessGroupsRaw = domain.table('access_groups')\n const accessUsersRaw = domain.table('access_users')\n const accessRequestsRaw = domain.table('access_requests')\n return {\n routes: {\n get: key => table.get(key) as RouteRecord | undefined,\n put: (key, value) => table.put(key, value),\n delete: key => table.delete(key),\n entries: () => table.entries() as IterableIterator<[string, RouteRecord]>,\n },\n inflight: {\n get: key => inflightRaw.get(key) as InflightSnapshot | undefined,\n put: (key, value) => inflightRaw.put(key, value),\n update: (key, fn) => inflightRaw.update(key, current => fn(current as InflightSnapshot)),\n delete: key => inflightRaw.delete(key),\n entries: () => inflightRaw.entries() as IterableIterator<[string, InflightSnapshot]>,\n },\n processed: {\n get: key => processedRaw.get(key) as ProcessedRecord | undefined,\n put: (key, value) => processedRaw.put(key, value),\n delete: key => processedRaw.delete(key),\n entries: () => processedRaw.entries() as IterableIterator<[string, ProcessedRecord]>,\n },\n expiredRoutes: {\n get: key => expiredRoutesRaw.get(key) as ExpiredRouteRecord | undefined,\n put: (key, value) => expiredRoutesRaw.put(key, value),\n entries: () => expiredRoutesRaw.entries() as IterableIterator<[string, ExpiredRouteRecord]>,\n },\n accessGroups: {\n get: key => accessGroupsRaw.get(key) as AccessGroupRecord | undefined,\n put: (key, value) => accessGroupsRaw.put(key, value),\n delete: key => accessGroupsRaw.delete(key),\n entries: () => accessGroupsRaw.entries() as IterableIterator<[string, AccessGroupRecord]>,\n },\n accessUsers: {\n get: key => accessUsersRaw.get(key) as AccessUserRecord | undefined,\n put: (key, value) => accessUsersRaw.put(key, value),\n delete: key => accessUsersRaw.delete(key),\n entries: () => accessUsersRaw.entries() as IterableIterator<[string, AccessUserRecord]>,\n },\n accessRequests: {\n get: key => accessRequestsRaw.get(key) as AccessRequestRecord | undefined,\n put: (key, value) => accessRequestsRaw.put(key, value),\n delete: key => accessRequestsRaw.delete(key),\n entries: () => accessRequestsRaw.entries() as IterableIterator<[string, AccessRequestRecord]>,\n },\n getGlobal: () => (domain.global.get() ?? {}) as FeishuDomainGlobal,\n setGlobal: value => domain.global.set(value),\n close: () => domain.close(),\n }\n}\n\n/**\n * Resolve one credential reference through the host credentials seam.\n *\n * Precedence is owned by the provider (env > `$DSH_HOME/.credentials.yaml`\n * > `.env` fallbacks). Without a mounted seam the process environment is the\n * whole credential plane.\n */\nasync function resolveCredentialRef(ctx: HostContext, ref: string): Promise<string> {\n const provider = ctx.get?.('credentials') as CredentialProvider | undefined\n if (provider !== undefined) {\n const hit = await provider.resolve(ref)\n if (hit !== undefined) return hit.value\n } else {\n const ambient = process.env[ref]\n if (ambient !== undefined && ambient !== '') return ambient\n }\n throw new Error(\n `dsh-channels: credential \"${ref}\" is not configured; add it to $DSH_HOME/.credentials.yaml `\n + `(e.g. \"${ref}: <value>\") or export it in the launching environment`,\n )\n}\n\n/** Best-effort human-readable error text for the settings panel. */\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/**\n * Measure one workspace directory for the cleanup preview: total regular-file\n * bytes and file count. Symlinks are never followed; unreadable entries are\n * skipped so a permission problem cannot fail the whole pass.\n */\nasync function measureWorkspace(dir: string): Promise<WorkspaceStats | undefined> {\n let info\n try {\n info = await lstat(dir)\n } catch {\n return undefined\n }\n if (!info.isDirectory()) return undefined\n let bytes = 0\n let files = 0\n const pending = [dir]\n while (pending.length > 0) {\n const current = pending.pop()!\n let entries\n try {\n entries = await readdir(current, { withFileTypes: true })\n } catch {\n continue\n }\n for (const entry of entries) {\n const child = path.join(current, entry.name)\n if (entry.isSymbolicLink()) continue\n if (entry.isDirectory()) {\n pending.push(child)\n continue\n }\n if (!entry.isFile()) continue\n try {\n const stat = await lstat(child)\n if (stat.isFile()) {\n bytes += stat.size\n files += 1\n }\n } catch {\n // The directory changed while walking it; the byte total is advisory.\n }\n }\n }\n return { mtimeMs: info.mtimeMs, bytes, files }\n}\n\nexport class FeishuService {\n private readonly ctx: HostContext\n /** Live config object. Mutated in place — collaborators captured this reference. */\n private readonly config: FeishuConfig\n /** Loader/profile-provided config, the layer below the Web overlay. */\n private readonly baseConfig: FeishuConfig\n /** Web overlay store (`$DSH_HOME/dsh-channels/settings.json`), when mounted. */\n private readonly overlay: OverlayStore | undefined\n private readonly channelFactory: ChannelFactory\n private readonly sessionExportBuilder: SessionExportBuilder\n private channel: FeishuChannel | undefined\n private streamer: TurnStreamer | undefined\n private domain: FeishuDomainHandle | undefined\n /** Durable route store (cache + TTL retirement). */\n private readonly routes: RouteStore\n /** In-flight turn snapshots + startup repair. */\n private readonly repair: InflightRepair\n /** Outbound card delivery + reaction lifecycle. */\n private readonly delivery: CardDelivery\n /** Inbound attachment archival (Feishu resources → workspace files). */\n private readonly attachments: AttachmentIntake\n /** Inbound routing: dedup, admission, decision tree, session establishment. */\n private readonly inbound: InboundRouter\n /** Agent/session lifecycle: live registry, resume, presets, renew. */\n private readonly sessions: SessionManager\n /** FIFO admission for ordinary turns and commands targeting one route. */\n private readonly routeOperations = new RouteOperationQueue()\n /** Active Host command executions, aborted before shutdown tears peers down. */\n private readonly commandControllers = new Set<AbortController>()\n /** Whole command admissions, drained before channel/storage teardown. */\n private readonly commandOperations = new Set<Promise<void>>()\n /** `<provider>/<model>` → context window (lazy async fill; sync reads). */\n private readonly contextWindows = new Map<string, number>()\n private readonly windowRequests = new Set<string>()\n private started = false\n /** Epoch ms when `start()` completed (uptime for `/status`). */\n private startedAt: number | undefined\n /** Last failed `boot()` message, surfaced in the Web settings panel. */\n private lastStartError: string | undefined\n /** Effective config captured at the last successful connect (restart-pending diff). */\n private runningConfig: FeishuConfig | undefined\n /** Guards overlapping panel-triggered reconnects. */\n private restarting = false\n private unlisten: (() => void) | undefined\n /** Periodic route-GC timer (unref'd; only armed when `routeTtlMs > 0`). */\n private gcTimer: ReturnType<typeof setInterval> | undefined\n /** Periodic workspace-retention timer (only armed when auto cleanup is on). */\n private retentionTimer: ReturnType<typeof setInterval> | undefined\n /** Guards overlapping cleanup passes (panel button / daily timer). */\n private cleanupRunning = false\n /** HITL approval answerer (created in `start()`). */\n private approvalAnswerer: ApprovalAnswerer | undefined\n /** HITL structured-question answerer (`ask_user_question`). */\n private questionAnswerer: QuestionAnswerer | undefined\n /** Disposers for the host approval waterfall listener + card actions. */\n private unlistenApproval: (() => boolean) | undefined\n /** Disposer for the host user-questions waterfall listener. */\n private unlistenQuestions: (() => boolean) | undefined\n private unlistenCardAction: (() => void) | undefined\n /** Durable access-control whitelists (groups + users + authorization requests). */\n private readonly accessStore: AccessStore\n /** Channel-plane admission gate (administrator / group / user). */\n private readonly accessController: AccessController\n /** Administrator DM authorization cards + bot-added group info cards. */\n private accessRequests: AccessRequests | undefined\n private unlistenBotAdded: (() => void) | undefined\n\n constructor(\n ctx: HostContext,\n config: FeishuConfig,\n channelFactory: ChannelFactory,\n sessionExportBuilder: SessionExportBuilder = createLogicalSessionExport,\n overlay?: OverlayStore,\n ) {\n this.ctx = ctx\n this.config = config\n this.baseConfig = { ...config }\n this.overlay = overlay\n this.channelFactory = channelFactory\n this.sessionExportBuilder = sessionExportBuilder\n this.routes = new RouteStore({\n domain: () => this.domain,\n ttlMs: () => this.config.routeTtlMs,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.repair = new InflightRepair({\n domain: () => this.domain,\n channel: () => this.channel,\n ttlMs: () => this.config.routeTtlMs,\n info: (message, ...args) => this.ctx.logger?.info?.(message, ...args),\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.sessions = new SessionManager({\n ctx: this.ctx,\n config: this.config,\n persistRoute: (key, record) => this.routes.put(key, record),\n started: () => this.started,\n detachStream: sessionId => this.streamer?.detach(sessionId),\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.attachments = new AttachmentIntake({\n enabled: () => this.config.acceptAttachments,\n channel: () => this.channel,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.delivery = new CardDelivery({\n channel: () => this.channel,\n started: () => this.started,\n routeForSession: sessionId => this.routes.findBySession(sessionId),\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.accessStore = new AccessStore({\n domain: () => this.domain,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.accessController = new AccessController({\n config: () => this.config,\n store: this.accessStore,\n channel: () => this.channel,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n onDeniedGroup: msg => {\n void this.accessRequests?.ensureGroupRequest(msg).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: group authorization request failed: %s', String(error))\n })\n },\n onDeniedUser: msg => {\n void this.accessRequests?.ensureUserRequest(msg).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: user authorization request failed: %s', String(error))\n })\n },\n })\n this.inbound = new InboundRouter({\n ctx: this.ctx,\n config: this.config,\n started: () => this.started,\n domain: () => this.domain,\n routes: this.routes,\n repair: this.repair,\n delivery: this.delivery,\n sessions: this.sessions,\n attachments: this.attachments,\n access: this.accessController,\n queue: this.routeOperations,\n streamer: () => this.streamer,\n questionAnswerer: () => this.questionAnswerer,\n commands: { handleSlash: (msg, line) => this.handleSlashMessage(msg, line) },\n })\n }\n\n // ---- lifecycle / Web settings panel ----------------------------------\n\n /**\n * Load the Web overlay, then open the channel. Startup errors are recorded\n * for the settings panel and the partial service is torn down before the\n * error is rethrown (the plugin fiber still fails the same way).\n */\n async boot(): Promise<void> {\n try {\n await this.loadOverlay()\n await this.start()\n this.lastStartError = undefined\n this.runningConfig = { ...this.config }\n } catch (error) {\n this.lastStartError = messageOf(error)\n try {\n await this.shutdown()\n } catch {\n // The original startup error is the useful one.\n }\n throw error\n }\n }\n\n /** Re-read the Web overlay over the profile config (no channel touch). */\n private async loadOverlay(): Promise<void> {\n const overlay = this.overlay === undefined ? {} : await this.overlay.load()\n this.replaceConfig({ ...this.baseConfig, ...overlay })\n }\n\n /**\n * Replace every own key of the live config object. `Object.assign` alone\n * would keep stale overlay-only keys (e.g. `provider`) when the next layer\n * no longer defines them; collaborators hold this object identity, so the\n * mutation must happen in place.\n */\n private replaceConfig(next: FeishuConfig): void {\n const live = this.config as unknown as Record<string, unknown>\n for (const key of Object.keys(live)) delete live[key]\n Object.assign(live, next)\n }\n\n /**\n * Apply an already-validated overlay over the profile config. Fields read\n * per event/turn take effect immediately; `RESTART_KEYS` need `restart()`.\n */\n applyOverlay(overlay: Partial<FeishuConfig>): void {\n this.replaceConfig({ ...this.baseConfig, ...overlay })\n this.refreshRetentionTimer()\n }\n\n /** Tear the connection down and bring it back with the latest config. */\n async restart(): Promise<void> {\n if (this.restarting) throw new Error('飞书连接正在重启,请稍候再试')\n this.restarting = true\n try {\n await this.shutdown()\n await this.loadOverlay()\n await this.start()\n this.lastStartError = undefined\n this.runningConfig = { ...this.config }\n } catch (error) {\n this.lastStartError = messageOf(error)\n // `start()` flips `started` before wiring; a failed reconnect must not\n // leave the panel claiming a live connection.\n try {\n await this.shutdown()\n } catch {\n // The restart error is the useful one.\n }\n throw error\n } finally {\n this.restarting = false\n }\n }\n\n isRunning(): boolean { return this.started }\n isRestarting(): boolean { return this.restarting }\n startedAtMs(): number | undefined { return this.startedAt }\n uptimeMs(): number | undefined {\n return this.startedAt === undefined || !this.started ? undefined : Math.max(0, Date.now() - this.startedAt)\n }\n startError(): string | undefined { return this.lastStartError }\n configSnapshot(): FeishuConfig { return { ...this.config } }\n baseConfigSnapshot(): FeishuConfig { return { ...this.baseConfig } }\n runningConfigSnapshot(): FeishuConfig | undefined {\n return this.runningConfig === undefined ? undefined : { ...this.runningConfig }\n }\n\n // ---- access-control panel surface ------------------------------------\n\n /** Whitelists + pending authorization requests (read-only Web projection). */\n accessSnapshot(): AccessWire {\n const admin = this.config.adminOpenId\n return {\n mode: this.config.accessMode,\n admin: {\n ...(admin === undefined ? {} : { openId: admin }),\n configured: admin !== undefined,\n },\n groups: this.accessStore.groupRecords().map(record => ({\n chatId: record.chatId,\n ...(record.name === undefined ? {} : { name: record.name }),\n source: record.source,\n addedAt: record.addedAt,\n })),\n users: this.accessStore.userRecords().map(record => ({\n openId: record.openId,\n ...(record.name === undefined ? {} : { name: record.name }),\n source: record.source,\n addedAt: record.addedAt,\n })),\n requests: this.accessStore.pendingRequests().map(record => {\n const wire: AccessRequestWire = {\n token: record.token,\n kind: record.kind,\n targetId: record.targetId,\n createdAt: record.createdAt,\n expiresAt: record.expiresAt,\n }\n const name = record.kind === 'group' ? record.groupName : record.requesterName\n if (name !== undefined) wire.name = name\n if (record.chatId !== undefined) wire.chatId = record.chatId\n if (record.groupName !== undefined) wire.groupName = record.groupName\n const requester = record.requesterName ?? record.requesterOpenId\n if (requester !== undefined) wire.requester = requester\n return wire\n }),\n }\n }\n\n /** Panel: authorize one group by id. */\n async addAccessGroup(chatId: string, name?: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessTargetId(chatId)\n if (id === undefined) return { ok: false, error: 'chat_id 不合法(应为 oc_ 开头的群 ID)' }\n const admin = this.config.adminOpenId\n if (admin === undefined) return { ok: false, error: '请先在常用配置中设置管理员 open_id' }\n try {\n if (!this.accessStore.hasGroup(id)) {\n await this.accessStore.addGroup({\n chatId: id,\n ...(name === undefined || name.trim() === '' ? {} : { name: name.trim() }),\n source: 'panel',\n addedBy: admin,\n addedAt: Date.now(),\n })\n }\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `写入授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: revoke one group. */\n async removeAccessGroup(chatId: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessTargetId(chatId)\n if (id === undefined) return { ok: false, error: 'chat_id 不合法' }\n try {\n await this.accessStore.removeGroup(id)\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `撤销授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: authorize one user (global across authorized groups). */\n async addAccessUser(openId: string, name?: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessUserId(openId)\n if (id === undefined) return { ok: false, error: 'open_id 不合法(应以 ou_ 开头)' }\n const admin = this.config.adminOpenId\n if (admin === undefined) return { ok: false, error: '请先在常用配置中设置管理员 open_id' }\n try {\n if (!this.accessStore.hasUser(id)) {\n await this.accessStore.addUser({\n openId: id,\n ...(name === undefined || name.trim() === '' ? {} : { name: name.trim() }),\n source: 'panel',\n addedBy: admin,\n addedAt: Date.now(),\n })\n }\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `写入授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: revoke one user. */\n async removeAccessUser(openId: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessUserId(openId)\n if (id === undefined) return { ok: false, error: 'open_id 不合法' }\n try {\n await this.accessStore.removeUser(id)\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `撤销授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: decide one pending authorization request (same path as the card). */\n async resolveAccessRequest(token: string, decision: 'allow' | 'deny'): Promise<AccessMutationResponse> {\n const requests = this.accessRequests\n if (requests === undefined) return { ok: false, error: '授权服务尚未启动' }\n const actor = this.config.adminOpenId\n if (actor === undefined) return { ok: false, error: '请先配置管理员 open_id' }\n try {\n const result = await requests.resolve(token, decision, actor)\n if (!result.ok) return { ok: false, error: result.error }\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `处理授权申请失败:${messageOf(error)}` }\n }\n }\n\n /** Trim + bound one panel-supplied identifier; rejects control characters. */\n private cleanAccessTargetId(raw: string): string | undefined {\n const value = raw.trim()\n if (value === '' || value.length > 128) return undefined\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/u.test(value)) return undefined\n return value\n }\n\n /** Same normalization for user open_ids, which must look like ou_xxx. */\n private cleanAccessUserId(raw: string): string | undefined {\n const value = this.cleanAccessTargetId(raw)\n if (value === undefined || !/^ou_[A-Za-z0-9_-]+$/u.test(value)) return undefined\n return value\n }\n\n // ---- workspace retention cleanup -------------------------------------\n\n /**\n * Workspace-retention preview for the Web panel: how many plugin-created\n * workspaces exist, how many are past the configured window, and their\n * measured footprint. Never deletes anything.\n */\n async cleanupPreview(): Promise<{\n retention: string\n thresholdMs?: number\n auto: boolean\n workspaces: number\n candidates: number\n candidateBytes: number\n error?: string\n }> {\n const retention = this.config.workspaceRetention\n const window = retentionMs(retention)\n const base = {\n retention,\n ...(window === undefined ? {} : { thresholdMs: window }),\n auto: this.config.workspaceRetentionAuto,\n }\n try {\n const workspaces = this.ctx.workspaceRegistry.list().filter(workspace => this.isPluginWorkspace(workspace))\n const plan = await planWorkspaceCleanup(this.cleanupDeps())\n return {\n ...base,\n workspaces: workspaces.length,\n candidates: plan?.candidates.length ?? 0,\n candidateBytes: plan?.candidates.reduce((total, candidate) => total + candidate.bytes, 0) ?? 0,\n }\n } catch (error) {\n return { ...base, workspaces: 0, candidates: 0, candidateBytes: 0, error: messageOf(error) }\n }\n }\n\n /**\n * Delete every stale plugin workspace the configured retention allows.\n * Routes bound to a removed workspace are retired first (thread routes leave\n * a tombstone), so an old topic fails with the explicit expired card instead\n * of resuming into a missing directory. DSH Session logs are not touched.\n */\n async runWorkspaceCleanup(): Promise<{\n ok: boolean\n deletedWorkspaces: number\n retiredRoutes: number\n freedBytes: number\n failures: string[]\n error?: string\n }> {\n const empty = { ok: false, deletedWorkspaces: 0, retiredRoutes: 0, freedBytes: 0, failures: [] as string[] }\n if (retentionMs(this.config.workspaceRetention) === undefined) {\n return { ...empty, error: '未启用工作区清理阈值(workspaceRetention 为 off)' }\n }\n if (this.cleanupRunning) return { ...empty, error: '工作区清理正在进行中,请稍候' }\n this.cleanupRunning = true\n try {\n const deps = this.cleanupDeps()\n const plan = await planWorkspaceCleanup(deps)\n if (plan === undefined) return { ...empty, error: '工作区清理不可用:baseDir 配置无效' }\n const outcome = await applyWorkspaceCleanup(deps, plan)\n this.ctx.logger?.info?.(\n 'dsh-channels: workspace cleanup removed %d workspace(s), retired %d route(s), freed %d bytes',\n outcome.deletedWorkspaces, outcome.retiredRoutes, outcome.freedBytes,\n )\n return {\n ok: true,\n deletedWorkspaces: outcome.deletedWorkspaces,\n retiredRoutes: outcome.retiredRoutes,\n freedBytes: outcome.freedBytes,\n failures: outcome.failures.map(failure => `${failure.path}: ${failure.message}`),\n }\n } catch (error) {\n return { ...empty, error: messageOf(error) }\n } finally {\n this.cleanupRunning = false\n }\n }\n\n /** Whether one registry workspace was created by this plugin inside `baseDir`. */\n private isPluginWorkspace(workspace: Workspace): boolean {\n const pluginTitle = workspace.title.startsWith('飞书私聊 ·') || workspace.title.startsWith('飞书群聊 ·')\n return pluginTitle && isPathInside(path.resolve(this.config.baseDir), workspace.path)\n }\n\n /** Effect seams for the cleanup policy module (all I/O stays here). */\n private cleanupDeps(): CleanupDeps {\n const baseDir = path.resolve(this.config.baseDir)\n return {\n baseDir,\n retention: this.config.workspaceRetention,\n listWorkspaces: () => this.ctx.workspaceRegistry.list(),\n activeRoutes: () => [...(this.domain?.routes.entries() ?? [])].map(([key, record]) => ({\n key,\n kind: record.kind,\n workspacePath: record.workspacePath,\n sessionId: record.sessionId,\n lastActivityAt: record.lastActivityAt,\n })),\n tombstones: () => [...(this.domain?.expiredRoutes.entries() ?? [])].map(([key, record]) => ({\n key,\n ...(record.workspacePath === undefined ? {} : { workspacePath: record.workspacePath }),\n expiredAt: record.expiredAt,\n ...(record.lastActivityAt === undefined ? {} : { lastActivityAt: record.lastActivityAt }),\n })),\n isLive: sessionId => this.sessions.get(sessionId) !== undefined\n || this.ctx.agents.get(sessionId as SessionId) !== undefined,\n statWorkspace: workspacePath => measureWorkspace(workspacePath),\n removeWorkspace: async workspace => {\n const target = path.resolve(workspace.path)\n if (!isPathInside(baseDir, target)) {\n throw new Error(`refusing to delete a path outside baseDir (${target})`)\n }\n // Re-verify the physical path: a symlinked ancestor inside baseDir\n // must not let the recursive delete escape it.\n try {\n const [physicalBase, physicalTarget] = await Promise.all([realpath(baseDir), realpath(target)])\n if (!isPathInside(physicalBase, physicalTarget)) {\n throw new Error(`refusing to delete a symlink escape outside baseDir (${physicalTarget})`)\n }\n } catch (error) {\n if (error instanceof Error && error.message.startsWith('refusing to delete a symlink escape')) throw error\n // A missing workspace is already effectively removed (rm is idempotent).\n }\n await rm(target, { recursive: true, force: true })\n try {\n await this.ctx.workspaceRegistry.delete(workspace.id)\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: unregistering cleaned workspace %s failed: %s', workspace.path, String(error),\n )\n }\n },\n retireRoute: async route => {\n if (route.kind === 'thread') await this.routes.retire(route.key)\n else await this.routes.remove(route.key)\n },\n warn: message => this.ctx.logger?.warn?.(message),\n }\n }\n\n /**\n * (Re)arm the daily retention timer to match the live config. `runNow`\n * performs one pass immediately; it is only used at startup so saving any\n * unrelated setting can never trigger a surprise deletion pass.\n */\n private refreshRetentionTimer(runNow = false): void {\n if (this.retentionTimer !== undefined) {\n clearInterval(this.retentionTimer)\n this.retentionTimer = undefined\n }\n if (!this.started\n || !this.config.workspaceRetentionAuto\n || retentionMs(this.config.workspaceRetention) === undefined) return\n const sweep = (): void => {\n void this.runWorkspaceCleanup().then(result => {\n if (!result.ok && result.error !== '工作区清理正在进行中,请稍候') {\n this.ctx.logger?.warn?.('dsh-channels: automatic workspace cleanup failed: %s', result.error)\n }\n })\n }\n if (runNow) sweep()\n const timer = setInterval(sweep, RETENTION_SWEEP_INTERVAL_MS)\n timer.unref?.()\n this.retentionTimer = timer\n }\n\n /** Aggregate health used by `/health` and the Web settings panel. */\n health(): HealthSnapshot { return this.healthSnapshot() }\n\n /**\n * Credential facts for the two Feishu refs. Values are never returned:\n * `describe()` reports presence/source/writability only.\n */\n async credentialStates(): Promise<{ appId: CredentialInfo; appSecret: CredentialInfo }> {\n const provider = this.credentialProvider()\n const read = async (ref: string): Promise<CredentialInfo> => {\n try {\n if (provider?.describe !== undefined) {\n const info = await provider.describe(ref)\n return {\n configured: info?.configured === true,\n writable: info?.writable !== false && provider.set !== undefined,\n ...(info?.source === undefined ? {} : { source: info.source }),\n }\n }\n if (provider === undefined) {\n const ambient = process.env[ref]\n return { configured: ambient !== undefined && ambient !== '', writable: false }\n }\n return { configured: (await provider.resolve(ref)) !== undefined, writable: provider.set !== undefined }\n } catch {\n return { configured: false, writable: false }\n }\n }\n const [appId, appSecret] = await Promise.all([read(this.config.appId), read(this.config.appSecret)])\n return { appId, appSecret }\n }\n\n /** The host credentials seam, when mounted. */\n private credentialProvider(): CredentialProvider | undefined {\n return this.ctx.get?.('credentials') as CredentialProvider | undefined\n }\n\n /** Store one Feishu credential value through the host seam; the value is never logged. */\n async setCredential(field: 'appId' | 'appSecret', value: string): Promise<void> {\n const provider = this.credentialProvider()\n if (provider?.set === undefined) {\n throw new Error('主机的凭据服务不可写(未挂载 credentials.set,或当前提供方只读)')\n }\n const ref = this.credentialRef(field)\n try {\n await provider.set(ref, value)\n } catch (error) {\n throw new Error(`写入凭据引用 ${ref} 失败:${messageOf(error)}`)\n }\n }\n\n /** Remove the stored value of one Feishu credential reference. */\n async clearCredential(field: 'appId' | 'appSecret'): Promise<void> {\n const provider = this.credentialProvider()\n if (provider?.unset === undefined) {\n throw new Error('主机的凭据服务不支持移除凭据')\n }\n const ref = this.credentialRef(field)\n try {\n await provider.unset(ref)\n } catch (error) {\n throw new Error(`移除凭据引用 ${ref} 失败:${messageOf(error)}`)\n }\n }\n\n /** The overlay-independent ref name backing one editable credential slot. */\n private credentialRef(field: 'appId' | 'appSecret'): string {\n return field === 'appId' ? this.config.appId : this.config.appSecret\n }\n\n /** Wire events and open the long connection. */\n async start(): Promise<void> {\n if (this.started) return\n this.started = true\n\n // 1. Resolve credentials through the host seam (env > .credentials.yaml > .env).\n const appId = await resolveCredentialRef(this.ctx, this.config.appId)\n const appSecret = await resolveCredentialRef(this.ctx, this.config.appSecret)\n\n // 2. Build the channel with the resolved secrets.\n const channel = this.channelFactory({\n appId,\n appSecret,\n loggerLevel: this.config.loggerLevel,\n requireMention: this.config.requireMention,\n dmMode: this.config.dmMode,\n })\n this.channel = channel\n const hooks: StreamerHooks = {\n measureContext: session => this.measureContext(session),\n projectStats: session => this.projectCardStats(session),\n sessionLabel: sessionId => sessionId,\n onError: (_key, error) => {\n this.ctx.logger?.warn?.('dsh-channels stream error: %s', String(error))\n },\n onTurnFinalized: (_sessionId, _turn, mainCardMessageId) => {\n // The turn settled (final patches fired, reaction swap requested):\n // retire its repair snapshot. Kept deliberately non-awaited — repair\n // state is a safety net, never a dependency of the turn lifecycle.\n void this.repair.clear(mainCardMessageId)\n },\n onSegmentDelivered: (mainCardMessageId, segmentIndex, messageId, mode) => {\n void this.repair.recordSegment(mainCardMessageId, segmentIndex, messageId, mode)\n },\n sendSegmentCard: (sessionId, segment, card, fallbackText) =>\n this.delivery.segmentCard(sessionId, segment, card, fallbackText),\n }\n this.streamer = new TurnStreamer(\n channel,\n this.config.refreshMs,\n hooks,\n this.config.statusRefreshMs,\n )\n\n this.domain = await openFeishuDomain(this.ctx)\n this.routes.load()\n this.accessStore.load()\n\n // HITL: answer the host's approval/request waterfall for sessions this\n // channel routes. `prepend` claims such requests BEFORE the web gateway's\n // generic answerer (its panel would be invisible to a Feishu user), while\n // non-owned sessions delegate through `next()`.\n const answerer = new ApprovalAnswerer(channel, {\n routeFor: sessionId => this.approvalRouteFor(sessionId),\n onError: (sessionId, error) => {\n this.ctx.logger?.warn?.('dsh-channels approval error (session %s): %s', sessionId, String(error))\n },\n }, this.config.approvalTimeoutMs)\n this.approvalAnswerer = answerer\n this.unlistenApproval = this.ctx.on('approval/request', (req, next) => answerer.onRequest(req, next), { prepend: true })\n // Structured questions (`ask_user_question`): the host's only shipped\n // answerer is the Web client, so a Feishu-only session would otherwise\n // fail every ask with NO_PROVIDER.\n const questions = new QuestionAnswerer(channel, {\n routeFor: sessionId => this.approvalRouteFor(sessionId),\n onError: (sessionId, error) => {\n this.ctx.logger?.warn?.('dsh-channels question error (session %s): %s', sessionId, String(error))\n },\n }, this.config.questionTimeoutMs)\n this.questionAnswerer = questions\n this.unlistenQuestions = this.ctx.on(\n 'user-questions/request',\n (request, next) => questions.onRequest(request, next),\n { prepend: true },\n )\n const accessRequests = new AccessRequests({\n config: () => this.config,\n store: this.accessStore,\n channel: () => this.channel,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.accessRequests = accessRequests\n this.unlistenBotAdded = channel.onBotAdded(event => {\n void accessRequests.handleBotAdded(event).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: bot-added group info card failed: %s', String(error))\n })\n })\n void accessRequests.expirePending().catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: access request expiry sweep failed: %s', String(error))\n })\n this.unlistenCardAction = channel.onCardAction(event => {\n void accessRequests.onCardAction(event)\n answerer.onCardAction(event)\n questions.onCardAction(event)\n })\n\n this.ctx.on('session/event', (session, event) => {\n this.streamer?.onSessionEvent(session, event)\n })\n // DSH removed the durable `assistant/chunk` event (format v2 embeds the\n // stream in the settlement); the live deltas arrive here instead.\n this.ctx.on('agent/assistant-stream', ({ agent, frame }) => {\n this.streamer?.onAssistantFrame(agent.session, frame)\n })\n this.unlisten = channel.onMessage(message => {\n void this.inbound.handle(message).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels inbound error: %s', String(error))\n // User-facing safety net: never leave the user without a reply.\n void channel\n .send(message.chatId, { text: `dsh-channels 处理失败:${String(error)}` }, { replyTo: message.messageId })\n .catch(() => {})\n })\n })\n await channel.connect()\n // Repair cards a previous instance left mid-flight (best-effort, background).\n void this.repair.repairAll().catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: inflight repair failed: %s', String(error))\n })\n // Record GC: one sweep right after boot, then a slow periodic sweep.\n // Dedup records are pruned unconditionally (fixed retention); route\n // records of deleted topics (their thread id never reappears) are pruned\n // when `routeTtlMs` is enabled.\n void this.gcExpiredEntries().catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: record GC failed: %s', String(error))\n })\n const timer = setInterval(() => {\n void this.gcExpiredEntries().catch(() => {})\n }, GC_SWEEP_INTERVAL_MS)\n timer.unref?.()\n this.gcTimer = timer\n // Workspace retention: opt-in automatic cleanup (default off).\n this.refreshRetentionTimer(true)\n this.ctx.logger?.info?.(\n 'dsh-channels: feishu long connection established (bot open_id %s, %d route(s) loaded)',\n channel.botOpenId() ?? 'unknown',\n this.routes.size(),\n )\n console.info('[dsh-channels] feishu long connection established, bot open_id:', channel.botOpenId())\n console.info('[dsh-channels] activity build:', ACTIVITY_BUILD_MARKER)\n this.startedAt = Date.now()\n console.info('[dsh-channels] model request overrides:', {\n provider: this.config.provider ?? 'deployment-default',\n model: this.config.model ?? 'deployment-default',\n reasoningEffort: this.config.reasoningEffort ?? 'adapter-default',\n })\n if (this.config.accessMode === 'open') {\n console.warn('[dsh-channels] 准入=open:任何应用可用范围内的用户都可以对话(不安全,仅调试)')\n } else if (this.config.adminOpenId === undefined) {\n console.warn(\n '[dsh-channels] 准入=guarded 但未配置管理员 open_id:所有对话都会被拒绝;'\n + '私聊机器人发送 /userid 取得 open_id 后填入 Web 设置面板',\n )\n } else {\n console.info('[dsh-channels] 准入=guarded,管理员已配置')\n }\n const approvalMounted = this.ctx.get?.('approval') !== undefined\n console.info(\n approvalMounted\n ? `[dsh-channels] HITL 审批应答已就绪(决策卡片超时 ${this.config.approvalTimeoutMs} ms)`\n : '[dsh-channels] 主机未挂载 approval 服务,HITL 审批应答关闭',\n )\n const questionsMounted = this.ctx.get?.('userQuestions') !== undefined\n console.info(\n questionsMounted\n ? `[dsh-channels] ask_user_question 应答已就绪(提问卡片超时 ${this.config.questionTimeoutMs} ms)`\n : '[dsh-channels] 主机未挂载 userQuestions 服务,结构化提问应答关闭',\n )\n }\n\n /** Tear down: close the connection and dispose all live agents. */\n async shutdown(): Promise<void> {\n // Close admission first. In-flight Host commands receive cancellation\n // before their Agent/channel/storage dependencies are disposed, while\n // already queued route operations observe `started === false` and retire.\n this.started = false\n for (const controller of this.commandControllers) controller.abort()\n await Promise.allSettled([...this.commandOperations])\n this.commandControllers.clear()\n this.commandOperations.clear()\n this.unlisten?.()\n this.unlisten = undefined\n this.unlistenCardAction?.()\n this.unlistenCardAction = undefined\n this.unlistenBotAdded?.()\n this.unlistenBotAdded = undefined\n this.accessRequests?.clear()\n this.accessRequests = undefined\n this.accessController.clear()\n this.unlistenApproval?.()\n this.unlistenApproval = undefined\n this.unlistenQuestions?.()\n this.unlistenQuestions = undefined\n // Withdraw in-flight approval questions (the host settles them cancelled).\n this.approvalAnswerer?.dispose()\n this.approvalAnswerer = undefined\n this.questionAnswerer?.dispose()\n this.questionAnswerer = undefined\n if (this.gcTimer !== undefined) {\n clearInterval(this.gcTimer)\n this.gcTimer = undefined\n }\n if (this.retentionTimer !== undefined) {\n clearInterval(this.retentionTimer)\n this.retentionTimer = undefined\n }\n await this.sessions.disposeAll()\n this.routeOperations.clear()\n this.inbound.clear()\n this.routes.clear()\n this.accessStore.clear()\n try {\n await this.channel?.disconnect()\n } catch {\n // Already disconnected is fine.\n }\n this.channel = undefined\n this.streamer = undefined\n await this.domain?.close()\n this.domain = undefined\n this.runningConfig = undefined\n }\n\n // ---- inbound routing -------------------------------------------------\n\n \n /** Slash-command plane. Nothing in this method calls `agent.followup()`. */\n /**\n * Slash-command admission. The command vocabulary and its admission rules\n * come from the command registry; nothing here calls `agent.followup()`.\n */\n private async handleSlashMessage(msg: NormalizedMessage, line: string): Promise<void> {\n if (!this.started) return\n const parsed = parseSlashCommand(line)\n // Channel-plane command admission runs before ANY card/topic/side effect:\n // denied senders get at most a fixed private-chat notice (or silence in\n // groups), never an unknown-command card or a command thread.\n if (!(await this.accessController.admitCommand(msg, parsed?.name))) return\n if (parsed === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'input-error',\n text: '命令名必须以小写字母开头,且只能包含小写字母、数字、下划线或连字符。',\n supported: supportedCommandList(),\n })\n return\n }\n const spec = commandSpec(parsed.name)\n if (spec === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'unknown', command: parsed.name,\n text: `未找到命令 /${parsed.name}。`, supported: supportedCommandList(),\n })\n return\n }\n\n // `/auth` is administrator-only, route-free, and handled right here so it\n // works even inside an unauthorized group (that is the \"authorize from\n // the conversation\" entry point).\n if (spec.name === 'auth') {\n await this.runAuthCommand(msg, parsed)\n return\n }\n\n // Route-free commands answer without any Session work.\n if (spec.routeFree === true) {\n await this.runRouteFreeCommand(spec, msg, parsed)\n return\n }\n\n // A TTL-retired topic is permanently closed except for session-optional\n // diagnostics (`/health`).\n const expiredRoute = msg.threadId === undefined ? undefined : this.routes.expiredRoute(msg.threadId)\n if (expiredRoute !== undefined) {\n if (spec.sessionOptional === true) {\n await this.runRouteFreeCommand(spec, msg, parsed)\n return\n }\n await this.delivery.expiredRouteCard(msg, parsed.name)\n return\n }\n\n const route = await this.inbound.resolveRoute(msg)\n if (route === undefined) {\n if (spec.sessionOptional === true) {\n await this.runRouteFreeCommand(spec, msg, parsed)\n return\n }\n await this.delivery.commandCard(msg, {\n kind: 'unbound', command: parsed.name,\n text: '当前话题尚未绑定 Session。请先发送普通消息创建会话,或使用 /health 检查通道。',\n })\n return\n }\n\n if (msg.senderId !== route.senderOpenId) {\n await this.delivery.commandCard(msg, {\n kind: 'forbidden', command: parsed.name, text: '无权操作此会话。',\n })\n return\n }\n\n const operation = this.routeOperations.run(route.key, async () => {\n if (!this.started) return\n const current = this.routes.get(route.key) ?? route\n if (msg.senderId !== current.senderOpenId) {\n await this.delivery.commandCard(msg, {\n kind: 'forbidden', command: parsed.name, text: '无权操作此会话。',\n })\n return\n }\n await this.runRouteCommand(spec, msg, parsed, current)\n })\n this.commandOperations.add(operation)\n try {\n await operation\n } finally {\n this.commandOperations.delete(operation)\n }\n }\n\n /**\n * `/auth`: administrator-only access management on the channel plane. It\n * never creates a Session or resumes an Agent; every reply is a fixed card.\n */\n private async runAuthCommand(msg: NormalizedMessage, parsed: ParsedSlashCommand): Promise<void> {\n if (!this.accessController.isAdmin(msg.senderId)) return\n const direct = { createThread: false }\n const words = parsed.rawInput.trim().split(/\\s+/u).filter(word => word !== '')\n const action = words[0] ?? 'status'\n const now = Date.now()\n try {\n if (action === 'status') {\n const groups = this.accessStore.groupRecords()\n const users = this.accessStore.userRecords()\n const pending = this.accessStore.pendingRequests()\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 访问授权状态**', [\n `**准入模式**:\\`${this.config.accessMode}\\``,\n `**管理员**:${this.config.adminOpenId === undefined ? '未配置' : `\\`${this.config.adminOpenId}\\``}`,\n ...(msg.chatType === 'group'\n ? [`**本群**:${this.accessStore.hasGroup(msg.chatId) ? '已授权' : '未授权'}(\\`${msg.chatId}\\`)`]\n : []),\n `**白名单**:群 ${groups.length} 个 · 用户 ${users.length} 个 · 待处理申请 ${pending.length} 个`,\n '群授权:`/auth allow` 或 `/auth revoke`(在目标群内发);用户授权:`/auth user add <open_id>`。',\n ]), direct)\n return\n }\n if (action === 'allow' || action === 'revoke') {\n if (msg.chatType !== 'group') {\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'auth',\n text: '群授权命令只能在群聊中使用:在目标群里发送 /auth allow 或 /auth revoke。',\n }, direct)\n return\n }\n if (action === 'allow') {\n if (this.accessStore.hasGroup(msg.chatId)) {\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth', text: `本群已经授权(\\`${msg.chatId}\\`)。`,\n }, direct)\n return\n }\n let name: string | undefined\n try {\n name = (await this.channel?.getChatInfo(msg.chatId))?.name\n } catch {\n name = undefined\n }\n await this.accessStore.addGroup({\n chatId: msg.chatId,\n ...(name === undefined || name === '' ? {} : { name }),\n source: 'command',\n addedBy: msg.senderId,\n addedAt: now,\n })\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth',\n text: '✅ 已授权本群。群内只有管理员与已授权用户能与我对话。',\n }, direct)\n return\n }\n const removed = await this.accessStore.removeGroup(msg.chatId)\n await this.delivery.commandCard(msg, {\n kind: removed ? 'success' : 'unavailable', command: 'auth',\n text: removed ? '已撤销本群授权,立即生效。' : '本群当前未被授权。',\n }, direct)\n return\n }\n if (action === 'user') {\n const sub = words[1]\n const openId = words[2]\n if ((sub !== 'add' && sub !== 'remove') || openId === undefined\n || !/^ou_[A-Za-z0-9_-]+$/u.test(openId)) {\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'auth',\n text: '用法:/auth user add <open_id> 或 /auth user remove <open_id>(open_id 以 ou_ 开头,可用 /userid 查看)。',\n }, direct)\n return\n }\n if (sub === 'add') {\n if (this.accessStore.hasUser(openId)) {\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth', text: `该用户已在白名单中(\\`${openId}\\`)。`,\n }, direct)\n return\n }\n await this.accessStore.addUser({\n openId, source: 'command', addedBy: msg.senderId, addedAt: now,\n })\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth',\n text: `✅ 已授权用户 \\`${openId}\\`(在所有已授权群里生效)。`,\n }, direct)\n return\n }\n const removed = await this.accessStore.removeUser(openId)\n await this.delivery.commandCard(msg, {\n kind: removed ? 'success' : 'unavailable', command: 'auth',\n text: removed ? `已移除用户 \\`${openId}\\`,立即生效。` : `用户 \\`${openId}\\` 不在白名单中。`,\n }, direct)\n return\n }\n if (action === 'list') {\n const groups = this.accessStore.groupRecords()\n const users = this.accessStore.userRecords()\n const pending = this.accessStore.pendingRequests()\n const groupLines = groups.length === 0\n ? ['(暂无)']\n : groups.slice(0, 30).map(group => `- ${group.name ?? group.chatId} \\`${group.chatId}\\``)\n const userLines = users.length === 0\n ? ['(暂无)']\n : users.slice(0, 30).map(user => `- ${user.name ?? user.openId} \\`${user.openId}\\``)\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 访问授权清单**', [\n `**已授权群(${groups.length})**`,\n ...groupLines,\n `**已授权用户(${users.length})**`,\n ...userLines,\n `**待处理申请**:${pending.length} 个(可在 Web 设置面板处理)`,\n ]), direct)\n return\n }\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'auth',\n text: '用法:/auth [status|allow|revoke|list|user add <open_id>|user remove <open_id>]',\n }, direct)\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: /auth failed: %s', String(error))\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: 'auth', text: `授权操作失败:${messageOf(error)}`,\n }, direct).catch(() => {})\n }\n }\n\n /** Answer one command that needs no Session (help / userid / groupid / health). */\n private async runRouteFreeCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n ): Promise<void> {\n const createThread = spec.directReply !== true\n if (parsed.rawInput.trim() !== '') {\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: spec.name, text: `用法错误:/${spec.name} 不接受参数。`,\n }, { createThread })\n return\n }\n if (spec.mentionGate === true\n && msg.chatType === 'group'\n && msg.threadId === undefined\n && this.config.requireMention\n && !msg.mentionedBot) {\n return\n }\n if (spec.chatType !== undefined && msg.chatType !== spec.chatType) {\n await this.delivery.commandCard(msg, {\n kind: 'context-error', command: spec.name,\n text: spec.chatType === 'p2p'\n ? `命令 /${spec.name} 仅支持在私聊中使用。`\n : `命令 /${spec.name} 仅支持在群聊中使用。`,\n }, { createThread })\n return\n }\n await this.delivery.withCommandReaction(msg, async () => {\n switch (spec.name) {\n case 'help':\n await this.delivery.commandCard(msg, { kind: 'help', text: supportedCommandHelp() }, { createThread })\n return\n case 'userid':\n await this.delivery.commandCard(msg, {\n kind: 'success', command: spec.name, text: `当前用户 ID:\\`${msg.senderId}\\``,\n }, { createThread })\n return\n case 'groupid':\n await this.delivery.commandCard(msg, {\n kind: 'success', command: spec.name, text: `当前群组 ID:\\`${msg.chatId}\\``,\n }, { createThread })\n return\n case 'health':\n await this.delivery.standaloneReply(msg, buildHealthCard(this.healthSnapshot()), { createThread })\n return\n default:\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name, text: `命令 /${spec.name} 暂不可用。`,\n }, { createThread })\n }\n })\n }\n\n /** Answer one route-bound command inside the per-route admission queue. */\n private async runRouteCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n await this.delivery.withCommandReaction(msg, async () => {\n try {\n if (spec.channelPlane === true) {\n await this.runChannelCommand(spec, msg, parsed, current)\n return\n }\n await this.runAgentCommand(spec, msg, parsed, current)\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: command /%s failed for session %s: %s',\n spec.name, current.sessionId, String(error),\n )\n await this.delivery.commandCard(msg, {\n kind: 'internal-error', command: spec.name,\n text: '命令执行失败,请稍后重试;若持续发生,请查看 Harness 日志。',\n sessionId: current.sessionId,\n })\n }\n })\n }\n\n /**\n * Commands the channel answers itself. `/stop` aborts the live turn\n * directly and deliberately never cold-resumes a Session just to cancel\n * nothing.\n */\n private async runChannelCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n if (spec.name === 'permissions') {\n await this.runPermissionsCommand(msg, parsed, current)\n return\n }\n if (spec.name === 'stop') {\n const agent = this.sessions.get(current.sessionId)?.agent\n ?? this.ctx.agents.get(current.sessionId as SessionId)\n const running = agent?.status === 'running'\n agent?.cancel?.({ kind: 'user' })\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'stop', sessionId: current.sessionId,\n text: running ? '已请求停止当前回合。' : '当前没有正在运行的回合。',\n })\n return\n }\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name, text: `命令 /${spec.name} 暂不可用。`,\n sessionId: current.sessionId,\n })\n }\n\n /**\n * `/permissions`: show this Session's effective sandbox+approval preset and\n * the selectable catalog; switch only when the deployment opted in.\n */\n private async runPermissionsCommand(\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n const presets = this.ctx.get?.('permissionPresets') as PermissionPresets | undefined\n if (presets === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: 'permissions', sessionId: current.sessionId,\n text: '当前部署未挂载 permissionPresets 服务,无法查看权限预设。',\n })\n return\n }\n const live = await this.sessions.ensureLive(current)\n let active: string | undefined\n let catalogLine = ''\n try {\n active = presets.current(live.agent.session)\n const catalog = presets.catalog()\n catalogLine = catalog.options.map(option => `\\`${option.value}\\``).join('、')\n } catch {\n active = undefined\n }\n const requested = parsed.rawInput.trim()\n if (requested === '') {\n const lines = [\n `**当前权限预设**:${active === undefined ? '暂无数据' : `\\`${active}\\``}`,\n ...(catalogLine === '' ? [] : [`**可选预设**:${catalogLine}`]),\n ...(this.config.allowPermissionSwitch\n ? ['使用 `/permissions <name>` 切换;切换会同时调整沙箱与审批策略。']\n : ['当前部署未开启飞书侧切换(`allowPermissionSwitch: false`),仅支持查看。']),\n ]\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 权限预设**', lines))\n return\n }\n if (!this.config.allowPermissionSwitch) {\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: 'permissions', sessionId: current.sessionId,\n text: '当前部署未开启飞书侧权限切换,仅支持 /permissions 查看。',\n })\n return\n }\n try {\n presets.set(live.agent.session, requested)\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 权限预设已切换**', [\n `**当前权限预设**:\\`${requested}\\``,\n '沙箱模式与审批策略已按该预设更新。',\n ]))\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: permission switch for session %s failed: %s', current.sessionId, String(error),\n )\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'permissions', sessionId: current.sessionId,\n text: `未知或不可用的权限预设:\\`${requested}\\`。可用:${catalogLine === '' ? '暂无数据' : catalogLine}`,\n })\n }\n }\n\n /**\n * Execute one command through the agent command plane: resolve the live\n * agent, mount borrowed channel commands when needed, arm the channel-scoped\n * `/renew` and `/effort` handlers, then render the settled result.\n */\n private async runAgentCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n // An alias (`/new`) resolves to this canonical spec; execute the canonical\n // line so the agent command plane finds the registered definition.\n const line = `/${spec.name}${parsed.rawInput}`\n const live = await this.sessions.ensureLive(current)\n let definition = this.ctx.commands.find(live.agent, spec.name)\n let unregisterBorrowedCommand: (() => void) | undefined\n if (definition === undefined && spec.name === 'renew') {\n unregisterBorrowedCommand = registerRenewCommand(live.agent.ctx.commands)\n definition = this.ctx.commands.find(live.agent, spec.name)\n }\n if (definition === undefined && spec.name === 'effort') {\n unregisterBorrowedCommand = registerEffortCommand(live.agent.ctx.commands)\n definition = this.ctx.commands.find(live.agent, spec.name)\n }\n if (definition === undefined && spec.name === 'model') {\n unregisterBorrowedCommand = registerModelCommand(live.agent.ctx.commands)\n definition = this.ctx.commands.find(live.agent, spec.name)\n }\n if (definition === undefined) {\n unregisterBorrowedCommand?.()\n const text = spec.name === 'compact'\n ? '当前部署未挂载 Harness 原生 /compact 命令。'\n : `当前 Session 未挂载 /${spec.name} 命令。`\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name, text, sessionId: current.sessionId,\n })\n return\n }\n const compactBefore = spec.name === 'compact'\n ? await this.measureCommandContext(live.agent.session)\n : undefined\n const controller = new AbortController()\n this.commandControllers.add(controller)\n const renewAdmission: RenewAdmission | undefined = spec.name === 'renew'\n ? { route: current, live }\n : undefined\n const effortAdmission: EffortAdmission | undefined = spec.name === 'effort'\n ? { live, controller: this.sessions.requestPolicy(live.agent) }\n : undefined\n const modelAdmission: ModelAdmission | undefined = spec.name === 'model'\n ? { live, controller: this.sessions.requestPolicy(live.agent) }\n : undefined\n const disarmRenew = renewAdmission === undefined\n ? undefined\n : armRenewCommand(controller.signal, invocation => this.runRenewCommand(invocation, renewAdmission))\n const disarmEffort = effortAdmission === undefined\n ? undefined\n : armEffortCommand(\n controller.signal,\n (invocation, action) => this.runEffortCommand(invocation, action, effortAdmission),\n )\n const disarmModel = modelAdmission === undefined\n ? undefined\n : armModelCommand(\n controller.signal,\n (invocation, rawInput) => this.runModelCommand(invocation, rawInput, modelAdmission),\n )\n let execution\n try {\n execution = await this.ctx.commands.execute(live.agent, line, [], controller.signal)\n } finally {\n this.commandControllers.delete(controller)\n disarmRenew?.()\n disarmEffort?.()\n disarmModel?.()\n unregisterBorrowedCommand?.()\n if (renewAdmission?.commit !== undefined) {\n await this.sessions.releaseRenewed(renewAdmission.commit)\n }\n }\n if (controller.signal.aborted || !this.started) return\n if (execution === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name,\n text: `当前 Session 无法执行 /${spec.name}。`, sessionId: current.sessionId,\n })\n return\n }\n await this.renderCommandResult({\n spec, msg, current, live,\n result: execution.result,\n compactBefore,\n renewAdmission,\n effortAdmission,\n modelAdmission,\n })\n }\n\n /** Render one settled agent-plane command result. */\n private async renderCommandResult(input: {\n spec: ChannelCommandSpec\n msg: NormalizedMessage\n current: RouteRecord\n live: LiveAgent\n result: CommandResult\n compactBefore: ContextMeasurement | undefined\n renewAdmission: RenewAdmission | undefined\n effortAdmission: EffortAdmission | undefined\n modelAdmission: ModelAdmission | undefined\n }): Promise<void> {\n const { spec, msg, current, live, result, compactBefore, renewAdmission, effortAdmission, modelAdmission } = input\n if (spec.name === 'effort') effortAdmission?.controller.sync(live.agent.session.snapshotEvents())\n if (spec.name === 'model') modelAdmission?.controller.sync(live.agent.session.snapshotEvents())\n if (spec.name === 'renew' && result.kind === 'success' && renewAdmission?.commit !== undefined) {\n await this.delivery.standaloneReply(msg, buildRenewCard({\n previousSessionId: renewAdmission.commit.previous.sessionId,\n currentSessionId: renewAdmission.commit.current.sessionId,\n workspacePath: renewAdmission.commit.current.workspacePath,\n }))\n return\n }\n if (spec.name === 'health' && result.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildHealthCard(this.healthSnapshot()))\n return\n }\n if (spec.name === 'status' && result.kind === 'success') {\n const session = live.agent.session\n const stats = foldSessionStats(session, this.projectionRegistry())\n const context = await measureSessionContext(\n session, this.commandContextServices(), stats.lastHeader,\n )\n await this.delivery.standaloneReply(msg, buildSessionStatusCard({\n sessionId: current.sessionId,\n stats,\n context,\n extras: {\n permissionPreset: this.permissionPresetOf(session),\n workspacePath: current.workspacePath,\n uptimeMs: this.startedAt === undefined ? undefined : Date.now() - this.startedAt,\n liveAgents: this.sessions.liveCount(),\n routeCount: this.routes.size(),\n },\n }))\n return\n }\n if (spec.name === 'workspace' && result.kind === 'success') {\n const resolved = resolveWorkDir(live.agent.session.header.cwd, current.workspacePath)\n if (resolved.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildWorkDirCard(current.sessionId, resolved.path))\n return\n }\n if (resolved.kind === 'mismatch') {\n this.ctx.logger?.warn?.(\n 'dsh-channels: workspace mismatch for session %s (paths redacted)', current.sessionId,\n )\n }\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name,\n text: resolved.text, sessionId: current.sessionId,\n })\n return\n }\n if (spec.name === 'history' && result.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildSessionHistoryCard(\n current, await this.sessionHistoryAnnotations(current),\n ))\n return\n }\n if (spec.name === 'export' && result.kind === 'success') {\n try {\n const file = await this.sessionExportBuilder(live.agent.session)\n await this.delivery.requireChannel().send(msg.chatId, { file }, {\n replyTo: msg.messageId,\n replyInThread: true,\n })\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: logical export delivery failed for session %s: %s',\n current.sessionId, String(error),\n )\n await this.delivery.commandCard(msg, {\n kind: 'internal-error', command: spec.name,\n text: 'Session 逻辑日志导出失败,未交付文件;请稍后重试并查看 Harness 日志。',\n sessionId: current.sessionId,\n })\n }\n return\n }\n if (spec.name === 'compact' && compactBefore !== undefined) {\n const compactAfter = await this.measureCommandContext(live.agent.session)\n if (result.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildCompactComparisonCard({\n sessionId: current.sessionId,\n resultText: result.text ?? '命令已执行。',\n before: compactBefore,\n after: compactAfter,\n }))\n return\n }\n await this.delivery.commandCard(msg, {\n kind: commandResultCardKind(result), command: spec.name,\n text: `${result.text}\\n\\n${compactFailureContext(compactBefore, compactAfter)}`,\n sessionId: current.sessionId,\n })\n return\n }\n await this.delivery.commandCard(msg, {\n kind: commandResultCardKind(result), command: spec.name,\n text: result.text ?? '命令已执行。', sessionId: current.sessionId,\n })\n }\n\n /**\n * Annotate a route's session history with host-query facts: the latest\n * log-backed title per session and whether it is currently live. Both are\n * best-effort — a missing or failing `sessionQuery` only drops the titles.\n */\n private async sessionHistoryAnnotations(record: RouteRecord): Promise<Record<string, SessionHistoryAnnotation>> {\n const ids = normalizeSessionHistory(record)\n .slice(-SESSION_HISTORY_DISPLAY_LIMIT)\n .map(entry => entry.sessionId)\n const annotations: Record<string, SessionHistoryAnnotation> = {}\n for (const id of ids) {\n annotations[id] = { live: this.sessions.get(id) !== undefined || this.ctx.agents.get(id as SessionId) !== undefined }\n }\n const query = this.ctx.get?.('sessionQuery') as SessionQuery | undefined\n if (query === undefined) return annotations\n try {\n const results = await query.readTitleSnapshots(ids as SessionId[])\n for (const item of results) {\n if (item.status !== 'fulfilled' || item.value.title === undefined) continue\n annotations[item.sessionId] = { ...annotations[item.sessionId], title: item.value.title.title }\n }\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: session title lookup failed: %s', String(error))\n }\n return annotations\n }\n\n /** Effective permission preset of one Session, when the service is mounted. */\n private permissionPresetOf(session: Session): string | undefined {\n const presets = this.ctx.get?.('permissionPresets') as PermissionPresets | undefined\n try {\n return presets?.current(session)\n } catch {\n return undefined\n }\n }\n\n /** Optional diagnostic services resolved without topology-sensitive property access. */\n private commandContextServices(): ContextServices {\n return {\n tokenMeter: this.ctx.get?.('tokenMeter') as TokenMeter | undefined,\n llm: this.ctx.get?.('llm') as LlmService | undefined,\n }\n }\n\n /** Exact command-time measurement shared by `/status` and `/compact`. */\n private measureCommandContext(session: Session): Promise<ContextMeasurement> {\n return measureSessionContext(session, this.commandContextServices())\n }\n\n /** Capture aggregate health only; raw configuration and errors are excluded. */\n private healthSnapshot(): HealthSnapshot {\n const serviceKeys: Record<HealthDependencyName, string> = {\n agents: 'agents',\n commands: 'commands',\n workspace: 'workspaceRegistry',\n storage: 'storageDomain',\n credentials: 'credentials',\n llm: 'llm',\n 'token-meter': 'tokenMeter',\n }\n const directServices: Record<HealthDependencyName, () => unknown> = {\n agents: () => this.ctx.agents,\n commands: () => this.ctx.commands,\n workspace: () => this.ctx.workspaceRegistry,\n storage: () => this.ctx.storageDomain,\n credentials: () => this.ctx.credentials,\n llm: () => this.ctx.llm,\n 'token-meter': () => this.ctx.tokenMeter,\n }\n const dependencies = Object.fromEntries(HEALTH_DEPENDENCY_NAMES.map(name => {\n try {\n const service = this.ctx.get === undefined\n ? directServices[name]()\n : this.ctx.get(serviceKeys[name])\n return [name, service !== undefined]\n } catch {\n return [name, false]\n }\n })) as Record<HealthDependencyName, boolean>\n\n const fibers: CordisFiber[] = []\n try {\n for (const runtime of this.ctx.registry.values()) {\n for (const fiber of runtime.fibers) fibers.push(fiber)\n }\n } catch {\n // Registry inspection is diagnostic only. An empty fold is safer than\n // exposing or propagating an internal registry error to the user.\n }\n\n const inflightTurnCount = this.repair.count()\n\n let connection\n let botOpenId: string | undefined\n try { connection = this.channel?.getConnectionStatus() } catch {}\n try { botOpenId = this.channel?.botOpenId() } catch {}\n\n return foldHealthSnapshot({\n fibers,\n dependencies,\n connection,\n botOpenId,\n started: this.started,\n routeCount: this.routes.size(),\n liveAgentCount: this.sessions.liveCount(),\n inflightTurnCount,\n })\n }\n\n\n\n\n\n\n\n\n\n\n\n /**\n * GC pass over the routing domain. Two independent halves:\n *\n * 1. Dedup records (unconditional): `processed` records and in-process\n * guard entries older than `PROCESSED_RETENTION_MS` are dropped —\n * redelivery windows are minutes, so a day of retention is ample and\n * keeps the table bounded regardless of the route TTL setting.\n * 2. Route/inflight GC (`routeTtlMs > 0`): thread routes idle past the TTL\n * are retired into minimal persistent tombstones before their active\n * records are deleted. A later message in that visible topic is then\n * rejected instead of silently creating a context-free Session. Degraded\n * chat-scoped routes have no topic boundary and are deleted normally.\n * Stale in-flight snapshots are deleted without surprising the user with\n * a repair of an ancient card. Sessions and workspaces remain host-owned.\n *\n * Per-key failures are logged and skipped; concurrent writes are safe\n * because `entries()` snapshots.\n */\n private async gcExpiredEntries(): Promise<void> {\n const now = Date.now()\n const processedCutoff = now - PROCESSED_RETENTION_MS\n let removedRoutes = 0\n let retiredThreadRoutes = 0\n let removedInflight = 0\n let removedProcessed = 0\n\n const processed = this.domain?.processed\n if (processed !== undefined) {\n for (const [key, record] of [...processed.entries()]) {\n if (record.processedAt >= processedCutoff) continue\n try {\n await processed.delete(key)\n removedProcessed++\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: GC of processed record %s failed: %s', key, String(error))\n }\n }\n }\n this.inbound.pruneAccepted(processedCutoff)\n\n const ttlMs = this.config.routeTtlMs\n if (ttlMs === 0) {\n if (removedProcessed > 0) {\n this.ctx.logger?.info?.('dsh-channels: record GC removed %d dedup record(s)', removedProcessed)\n }\n return\n }\n const cutoff = now - ttlMs\n\n const routes = this.domain?.routes\n if (routes !== undefined) {\n for (const [key, record] of [...routes.entries()]) {\n if (record.lastActivityAt >= cutoff) continue\n try {\n if (record.kind === 'thread') {\n await this.routes.expireThread(key, now)\n retiredThreadRoutes++\n } else {\n await this.routes.remove(key)\n }\n removedRoutes++\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: GC of route %s failed: %s', key, String(error))\n }\n }\n }\n\n const inflight = this.domain?.inflight\n if (inflight !== undefined) {\n for (const [key, snapshot] of [...inflight.entries()]) {\n if (snapshot.startedAt >= cutoff) continue\n try {\n await inflight.delete(key)\n removedInflight++\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: GC of inflight snapshot %s failed: %s', key, String(error))\n }\n }\n }\n\n if (removedRoutes > 0 || removedInflight > 0 || removedProcessed > 0) {\n this.ctx.logger?.info?.(\n 'dsh-channels: record GC removed %d route(s) (%d thread tombstone(s)), %d stale snapshot(s), %d dedup record(s)',\n removedRoutes, retiredThreadRoutes, removedInflight, removedProcessed,\n )\n }\n }\n\n\n /**\n * HITL answerer hook: the Feishu placement of one session's approval cards,\n * or `undefined` when this channel does not route the session (the answerer\n * then delegates to other answerers).\n */\n private approvalRouteFor(sessionId: string): ApprovalRoute | undefined {\n const record = this.routes.findBySession(sessionId)\n if (record === undefined) return undefined\n return {\n chatId: record.chatId,\n cardMessageId: record.cardMessageId,\n inThread: record.kind === 'thread',\n senderOpenId: record.senderOpenId,\n }\n }\n\n /**\n * Send one body-segment continuation card into the session's topic (or, for\n * degraded chat-scoped routes, as a plain reply anchored to the main card).\n * When Feishu rejects the card (e.g. per-card table limit, error 11310) the\n * segment is re-delivered as a plain-text message so the content is never\n * lost — text messages carry no card constraints.\n */\n /** Authorized Session-level `/model` view/switch operation. */\n private async runModelCommand(\n invocation: CommandInvocation,\n rawInput: string,\n admission: ModelAdmission,\n ): Promise<CommandResult> {\n if (admission.live.agent !== invocation.agent) {\n return { kind: 'error', text: '/model 只能通过已授权的飞书话题执行。' }\n }\n const session = invocation.agent.session\n const state = admission.controller.state\n const header = session.requestHeader?.()?.config\n const currentProvider = state.effectiveModel?.provider\n ?? (typeof header?.provider === 'string' && header.provider !== '' ? header.provider : undefined)\n const currentModel = state.effectiveModel?.model\n ?? (typeof header?.model === 'string' && header.model !== '' ? header.model : undefined)\n const source = state.modelSource === 'session'\n ? 'Session 覆盖'\n : state.modelSource === 'config' ? '配置默认' : '部署默认'\n\n if (rawInput === '') {\n const lines = [\n `**当前模型**:${currentModel === undefined ? '尚无模型请求记录' : `\\`${currentModel}\\``}`\n + `${currentProvider === undefined ? '' : `(provider \\`${currentProvider}\\`)`} · ${source}`,\n ]\n const models = await this.listModels(currentProvider)\n if (models.length > 0) {\n lines.push(`**可选模型(${models.length})**:${models.slice(0, 20).map(id => `\\`${id}\\``).join('、')}`\n + (models.length > 20 ? ' …' : ''))\n } else if (currentProvider === undefined) {\n lines.push('当前还没有模型请求记录;可先用 `/model <provider>/<model>` 指定,或先发一条普通消息。')\n }\n lines.push('用法:`/model <模型-id>`(沿用当前 provider)· `/model <provider>/<model>` · `/model reset`')\n return { kind: 'success', text: lines.join('\\n') }\n }\n\n if (rawInput === 'reset') {\n return { kind: 'success', text: '已恢复部署默认模型(下一条消息生效)。' }\n }\n\n const slash = rawInput.indexOf('/')\n const provider = slash === -1 ? currentProvider : rawInput.slice(0, slash)\n const model = slash === -1 ? rawInput : rawInput.slice(slash + 1)\n if (model === '' || (slash !== -1 && (provider === undefined || provider === ''))) {\n return { kind: 'error', text: '用法错误:/model [模型-id|provider/model|reset]。' }\n }\n // Validate against the advertised catalog when the deployment exposes one;\n // an unsupported value would otherwise only fail at the next request.\n if (provider !== undefined) {\n const models = await this.listModels(provider)\n if (models.length > 0 && !models.includes(model)) {\n return {\n kind: 'error',\n text: `provider \\`${provider}\\` 未提供模型 \\`${model}\\`。可选:${models.slice(0, 20).map(id => `\\`${id}\\``).join('、')}`,\n }\n }\n }\n const label = provider === undefined ? model : `${provider}/${model}`\n return { kind: 'success', text: `当前 Session 模型已切换为 \\`${label}\\`(下一条消息生效)。` }\n }\n\n /** Model ids advertised for one provider route (empty when unavailable). */\n private async listModels(provider: string | undefined): Promise<string[]> {\n if (provider === undefined) return []\n try {\n const llm = this.ctx.get?.('llm') as LlmService | undefined\n const models = await llm?.listModels?.(provider)\n return (models ?? []).map(entry => entry.id ?? entry.name ?? '').filter(id => id !== '')\n } catch {\n return []\n }\n }\n\n /**\n * Agent-scoped `/renew` handler. Only the Feishu admission path arms its\n * AbortSignal, preventing another Host client from switching a Feishu route\n * by invoking the scoped command out of band.\n */\n private async runRenewCommand(\n invocation: CommandInvocation,\n admission: RenewAdmission,\n ): Promise<CommandResult> {\n if (admission.live.agent !== invocation.agent) {\n return { kind: 'error', text: '/renew 只能通过已授权的飞书话题执行。' }\n }\n const current = this.routes.get(admission.route.key)\n if (current === undefined || current.sessionId !== admission.route.sessionId) {\n return { kind: 'error', text: '当前话题绑定已变化,请重试 /renew。' }\n }\n try {\n admission.commit = await this.sessions.renew(current, admission.live)\n return { kind: 'success', text: '新 Session 已创建并绑定。' }\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: renew transaction failed for session %s: %s',\n current.sessionId, String(error),\n )\n return { kind: 'error', text: 'Session 续建失败,原 Session 仍保持绑定;请稍后重试。' }\n }\n }\n\n /** Authorized Session-level `/effort` view/switch operation. */\n private runEffortCommand(\n invocation: CommandInvocation,\n action: EffortCommandAction,\n admission: EffortAdmission,\n ): CommandResult {\n if (admission.live.agent !== invocation.agent) {\n return { kind: 'error', text: '/effort 只能通过已授权的飞书话题执行。' }\n }\n const state = admission.controller.state\n if (action.kind === 'show') {\n if (state.effortSource === 'session') {\n return { kind: 'success', text: `当前 Session 推理强度:\\`${state.effectiveEffort}\\`(Session 覆盖)。` }\n }\n if (state.effortSource === 'config') {\n return { kind: 'success', text: `当前 Session 推理强度:\\`${state.effectiveEffort}\\`(配置默认)。` }\n }\n return { kind: 'success', text: '当前 Session 未设置推理强度覆盖,使用模型适配器/provider 默认。' }\n }\n if (action.kind === 'reset') {\n return state.configuredEffort === undefined\n ? { kind: 'success', text: '已恢复模型适配器/provider 默认推理强度。' }\n : { kind: 'success', text: `已恢复配置默认推理强度:\\`${state.configuredEffort}\\`。` }\n }\n return { kind: 'success', text: `当前 Session 推理强度已切换为:\\`${action.value}\\`。` }\n }\n\n // ---- outbound helpers ------------------------------------------------\n\n /** Context pressure for the card's second info line (synchronous reads only). */\n private measureContext(session: Session): { contextTokens?: number; contextWindow?: number } {\n let contextTokens: number | undefined\n try {\n // Optional peers are read through ctx.get (safe lookup); direct property\n // access on a cordis context throws for services the plugin did not inject.\n const meter = this.ctx.get?.('tokenMeter') as TokenMeter | undefined\n contextTokens = meter?.measure(session).totalTokens\n } catch {\n contextTokens = undefined\n }\n\n // The route-capacity window is published by the host's `contextPressure`\n // projection; the old request/header scan + resolveModelInfo call stays as\n // a fallback for deployments without the projection registry.\n let contextWindow = readProjectionViews(this.projectionRegistry(), session).contextPressure?.contextWindow\n if (contextWindow === undefined) {\n try {\n const header = session.requestHeader?.()?.config\n const provider = typeof header?.provider === 'string' ? header.provider : undefined\n const model = typeof header?.model === 'string' ? header.model : undefined\n if (provider !== undefined && provider !== '' && model !== undefined && model !== '') {\n const routeKey = `${provider}/${model}`\n contextWindow = this.contextWindows.get(routeKey)\n if (contextWindow === undefined && !this.windowRequests.has(routeKey)) {\n this.windowRequests.add(routeKey)\n const llm = this.ctx.get?.('llm') as LlmService | undefined\n void llm?.resolveModelInfo(provider, model)\n .then(info => {\n if (info?.context?.contextWindow !== undefined) {\n this.contextWindows.set(routeKey, info.context.contextWindow)\n }\n })\n .catch(() => {})\n }\n }\n } catch {\n contextWindow = undefined\n }\n }\n\n return { contextTokens, contextWindow }\n }\n\n /** Host projection registry, when this deployment mounts one. */\n private projectionRegistry(): SessionProjections | undefined {\n return this.ctx.get?.('sessionProjections') as SessionProjections | undefined\n }\n\n /** Card figures from host projections (token totals / cache hit / window). */\n private projectCardStats(session: Session): ProjectedStats | undefined {\n const views = readProjectionViews(this.projectionRegistry(), session)\n const usage = views.tokenUsage\n const pressure = views.contextPressure\n if (usage === undefined && pressure === undefined) return undefined\n const last = usage?.last?.buckets\n const hit = last === undefined ? undefined : last.cacheReadTokens\n const uncached = last === undefined ? undefined : last.uncachedInputTokens\n const cacheHit = hit !== undefined && uncached !== undefined && hit + uncached > 0\n ? hit / (hit + uncached)\n : undefined\n return {\n ...(usage === undefined ? {} : {\n inputTokens: usage.totals.uncachedInputTokens,\n outputTokens: usage.totals.outputTokens,\n }),\n ...(cacheHit === undefined ? {} : { cacheHitPct: cacheHit }),\n ...(pressure?.contextWindow === undefined ? {} : { contextWindow: pressure.contextWindow }),\n }\n }\n}\n","/**\n * Plugin configuration: schema, defaults, and credential-reference validation.\n *\n * `appId` / `appSecret` are NOT secrets themselves — they are credential\n * *references* (POSIX shell identifiers such as `FEISHU_APP_ID`), resolved at\n * startup through the host's `ctx.credentials` seam, which layers\n * `$DSH_HOME/.credentials.yaml` (the managed, writable store) between the\n * process environment and `.env` fallbacks — the same seam the DeepSeek\n * adapter uses for `DEEPSEEK_API_KEY`. Secrets therefore never sit in the\n * composition file.\n *\n * @module dsh-channels/config\n */\n\nimport { z } from 'zod'\nimport { WORKSPACE_RETENTION_VALUES } from './cleanup.ts'\n\nexport const FEISHU_LOGGER_LEVELS = ['debug', 'info', 'warn', 'error'] as const\nexport type FeishuLoggerLevel = (typeof FEISHU_LOGGER_LEVELS)[number]\n\n/** Same shape as the host credentials seam's `CredentialRef` (shell identifier). */\nexport const CREDENTIAL_REF_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/\n\n/** Default route-table GC threshold: 30 days without activity. */\nexport const DEFAULT_ROUTE_TTL_MS = 30 * 24 * 60 * 60 * 1000\n\n/** Default HITL approval decision window: 5 minutes per question. */\nexport const DEFAULT_APPROVAL_TIMEOUT_MS = 5 * 60 * 1000\n\n/** Default structured-question window: 5 minutes per `ask_user_question` call. */\nexport const DEFAULT_QUESTION_TIMEOUT_MS = 5 * 60 * 1000\n\n/** A credential reference: `FEISHU_APP_ID`-style name resolved through ctx.credentials. */\nconst credentialRefSchema = z.string().regex(\n CREDENTIAL_REF_PATTERN,\n 'must be a credential reference name such as FEISHU_APP_ID (resolved through the dsh credentials seam, e.g. $DSH_HOME/.credentials.yaml) — plaintext secrets are not accepted',\n)\n\nexport const configSchema = z.object({\n /** Feishu app id credential reference (e.g. `FEISHU_APP_ID`). */\n appId: credentialRefSchema,\n /** Feishu app secret credential reference (e.g. `FEISHU_APP_SECRET`). */\n appSecret: credentialRefSchema,\n /**\n * Root directory for per-(user, chat) workspace directories — the filesystem\n * root where files produced by channel sessions land. Read per session\n * establishment, so editing it only affects sessions created afterwards\n * (existing workspaces are never migrated). The Web settings panel may\n * override it, but only with an absolute path (`~` expanded) — the relative\n * default below is kept for profile-level compatibility and resolves\n * against the host process cwd.\n */\n baseDir: z.string().min(1).default('./channels-data'),\n /**\n * Optional path of the Web settings overlay (default\n * `$DSH_HOME/dsh-channels/settings.json`). The Web panel writes only\n * editable fields there; this path is for embedded hosts, tests, or users\n * who want multiple instances with different overlays.\n */\n settingsFile: z.string().min(1).optional(),\n /** LLM provider route for sessions this plugin creates (optional → deployment default). */\n provider: z.string().optional(),\n /** Model id for sessions this plugin creates (optional → deployment default). */\n model: z.string().optional(),\n /** Exact model-advertised reasoning-effort id (optional → adapter/provider default). */\n reasoningEffort: z.string().min(1).optional(),\n /** Output-token cap per model request (optional → adapter default). */\n maxTokens: z.number().int().positive().optional(),\n /**\n * Agent preset id sessions this plugin creates are composed from\n * (optional → the roster default, e.g. `standard`). In web-profile\n * deployments the model-facing tools exist only inside agent presets, so\n * leaving this unset mounts the default; name a leaner preset (e.g. one\n * with only bash/fs) to restrict what the bot can do.\n */\n agentPreset: z.string().optional(),\n /** Streaming card refresh interval in milliseconds. */\n refreshMs: z.number().int().min(200).max(10_000).default(500),\n /**\n * Keep-alive interval for the card's activity status line (tool calls,\n * subagent / workflow progress). While a turn produces no text deltas, the\n * line's elapsed counter is re-rendered at this cadence so the card never\n * sits silently. Min 1 s keeps Feishu card-update traffic modest.\n */\n statusRefreshMs: z.number().int().min(1_000).max(60_000).default(5_000),\n /** Accept p2p (private) chats. */\n dmMode: z.enum(['open', 'disabled']).default('open'),\n /**\n * In normal (non-topic) groups, require an @-mention of the bot to start a\n * session. Messages inside a topic the bot already owns never require a\n * mention. Disable to answer every group message.\n */\n requireMention: z.boolean().default(true),\n /** SDK logger level. */\n loggerLevel: z.enum(FEISHU_LOGGER_LEVELS).default('info'),\n /**\n * Reply-in-thread fallbacks. `degrade` (default) falls back to a chat-scoped\n * conversation when Feishu refuses a topic (p2p / topic-less groups);\n * `strict` fails the reply instead.\n */\n threadFallback: z.enum(['degrade', 'strict']).default('degrade'),\n /**\n * Route-table GC threshold. Route records (and stale in-flight repair\n * snapshots) whose last activity is older than this many milliseconds are\n * retired at startup and on every periodic sweep. Real thread routes leave a\n * minimal persistent tombstone, so a later message in the visibly old topic\n * is rejected instead of silently creating a context-free Session. Degraded\n * chat-scoped routes are deleted normally. The DSH session and workspace\n * stay untouched. `0` disables route retirement entirely.\n */\n routeTtlMs: z.number().int().min(0).default(DEFAULT_ROUTE_TTL_MS),\n /**\n * HITL approval decision window: how long one approval decision card waits\n * for an 允许/拒绝 click before failing closed (`rejected`; the card is\n * patched to 已超时). The pending turn is blocked while the question is\n * open, so the window trades user think-time against agent stall time.\n */\n approvalTimeoutMs: z.number().int().min(10_000).max(3_600_000).default(DEFAULT_APPROVAL_TIMEOUT_MS),\n /**\n * Accept inbound Feishu images, files, and merged-forward chat records and\n * save them below `<workspace>/attachments/`. Nothing is forwarded to the\n * model automatically: the Agent reads whichever attachment the\n * conversation needs with its ordinary filesystem tools. Disabled keeps\n * attachment messages inert (no save, no turn).\n */\n acceptAttachments: z.boolean().default(true),\n /**\n * Delete the workspace directories of stale conversations. `off` (default)\n * never deletes; `7d`/`30d`/`90d`/`180d` remove plugin-created workspaces\n * whose last activity is older than the threshold. The managed Web panel\n * exposes this as a select, previews the candidates, and can run one\n * cleanup manually. DSH Session logs are never deleted.\n */\n workspaceRetention: z.enum(WORKSPACE_RETENTION_VALUES).default('off'),\n /**\n * Run the workspace-retention cleanup at startup and once per day. Off by\n * default: deletion is irreversible, so it stays an explicit opt-in even\n * when a retention threshold is configured.\n */\n workspaceRetentionAuto: z.boolean().default(false),\n /**\n * Send a one-time onboarding card on the first private message of a chat\n * (facts + command cheat sheet). Persisted per chat; never repeats.\n */\n welcomeOnboarding: z.boolean().default(true),\n /**\n * Allow `/permissions <name>` to switch this Session's sandbox + approval\n * preset from Feishu. Read-only `/permissions` always works; switching is\n * security-sensitive (it can widen the sandbox), so it is off by default.\n */\n allowPermissionSwitch: z.boolean().default(false),\n /**\n * Admission policy for inbound Feishu traffic.\n *\n * - `guarded` (default): only the configured administrator can talk in\n * private chats, and a group conversation additionally requires the group\n * to be authorized and the sender to be the administrator or an explicitly\n * authorized user.\n * - `open`: legacy behavior — any user inside the app's availability scope\n * can start a conversation. Debug/compatibility only; unsafe.\n */\n accessMode: z.enum(['open', 'guarded']).default('guarded'),\n /**\n * The unique administrator's Feishu open_id (`ou_...`). Set it in the Web\n * settings panel (or the profile config). Get it by sending `/userid` to the\n * bot in a private chat. While `accessMode` is `guarded` and this is unset,\n * every conversation is refused (fail closed); `/userid` and a fixed notice\n * stay available so the deployment can be bootstrapped.\n */\n adminOpenId: z.string().regex(\n /^ou_[A-Za-z0-9_-]+$/,\n 'must be a Feishu open_id such as ou_xxx (send /userid to the bot in a private chat to see it)',\n ).optional(),\n /**\n * Lifetime of one pending authorization request and of the DM card that asks\n * the administrator to approve a group or a user. Expired requests stay in\n * the store as an audit trail but can no longer be decided.\n */\n authRequestTtlDays: z.number().int().min(1).max(30).default(7),\n /**\n * Structured-question decision window for the model's `ask_user_question`\n * tool (`user-questions/request`). A blocking ask that gets no answer in\n * this window fails closed with an explicit error; a host *timed* wait owns\n * its own deadline and is left to the host signal instead.\n */\n questionTimeoutMs: z.number().int().min(10_000).max(3_600_000).default(DEFAULT_QUESTION_TIMEOUT_MS),\n})\n\nexport type FeishuConfig = z.infer<typeof configSchema>\n\n/**\n * Validate the raw loader config. Values stay credential *references*; the\n * secrets themselves resolve through `ctx.credentials` at startup.\n * @param raw - the config object the cordis loader passed (may be undefined).\n * @returns the validated config.\n */\nexport function resolveConfig(raw: unknown): FeishuConfig {\n const parsed = configSchema.safeParse(raw ?? {})\n if (!parsed.success) {\n const details = parsed.error.issues\n .map(issue => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`)\n .join('\\n')\n throw new Error(`dsh-channels: invalid configuration:\\n${details}`)\n }\n return parsed.data\n}\n","/**\n * Web 设置面板的线上契约:Host 半侧的路由/面板数据与 Browser 半侧的渲染共用。\n *\n * 本模块**没有运行时依赖**:Host 与 Client bundle 都直接内联它。因此这里只放\n * 常量、字段清单与可序列化的 wire 类型,不 import zod / node / DSH 内部包。\n *\n * @module dsh-channels/settings/contract\n */\n\n/** Same-origin routes the settings panel talks to. */\nexport const STATUS_ROUTE = '/dsh-channels/status'\nexport const PANEL_ROUTE = '/dsh-channels/panel'\nexport const CONFIG_ROUTE = '/dsh-channels/config'\nexport const CREDENTIALS_ROUTE = '/dsh-channels/credentials'\nexport const RECONNECT_ROUTE = '/dsh-channels/reconnect'\n/** 模型/推理档位/预设选择器的宿主目录(provider → model → effort 级联刷新)。 */\nexport const CATALOG_ROUTE = '/dsh-channels/catalog'\n/** 手动执行一次过期工作区清理(POST,无请求体)。 */\nexport const CLEANUP_ROUTE = '/dsh-channels/cleanup'\n/** 访问与授权只读快照(GET/HEAD):准入模式、管理员、群/用户白名单、待处理申请。 */\nexport const ACCESS_ROUTE = '/dsh-channels/access'\n/** 群白名单增删(POST/DELETE,body `{ chatId, name? }` / `{ chatId }`)。 */\nexport const ACCESS_GROUPS_ROUTE = '/dsh-channels/access/groups'\n/** 用户白名单增删(POST/DELETE,body `{ openId, name? }` / `{ openId }`)。 */\nexport const ACCESS_USERS_ROUTE = '/dsh-channels/access/users'\n/** 结算一条待处理授权申请(POST,body `{ token, decision }`)。 */\nexport const ACCESS_REQUESTS_ROUTE = '/dsh-channels/access/requests'\n\n/**\n * 可由 Web 面板覆盖的配置项。全部是 `src/config.ts` 里已存在的字段;\n * 覆盖值写进 `$DSH_HOME/dsh-channels/settings.json`,优先级高于 profile\n * 的 `cordis.patch.yml`(见 README「Web 设置面板」)。\n *\n * 刻意不包含 `appId` / `appSecret`:它们是**凭据引用名**,面板改为直接\n * 编辑引用背后的值(`PUT /dsh-channels/credentials`,经 DSH 凭据服务写入),\n * 因此不会落进覆盖层文件。\n *\n * `baseDir`(工作区根目录 = 模型/工具产生文件的落点)可覆盖,但面板只接受\n * **绝对路径**(`~` 会展开);已有 Session / 工作区不迁移,只影响之后新建的\n * 会话。校验与保存时建目录见 `src/workspaces.ts`。\n */\nexport const EDITABLE_KEYS = [\n // 工作区\n 'baseDir',\n 'workspaceRetention',\n 'workspaceRetentionAuto',\n // 行为\n 'dmMode',\n 'requireMention',\n 'threadFallback',\n 'welcomeOnboarding',\n 'allowPermissionSwitch',\n // 准入与授权(管理员/准入模式/申请有效期;白名单由「访问与授权」卡片管理)\n 'adminOpenId',\n 'accessMode',\n 'authRequestTtlDays',\n // 内容\n 'acceptAttachments',\n // 模型\n 'provider',\n 'model',\n 'reasoningEffort',\n 'agentPreset',\n // 流式卡片\n 'refreshMs',\n 'statusRefreshMs',\n 'loggerLevel',\n] as const\n\nexport type EditableKey = (typeof EDITABLE_KEYS)[number]\n\n/**\n * 只有重连飞书长连接后才生效的字段:它们在 `FeishuService.start()` 建连时\n * 被交给官方 SDK 或流式器,运行中修改不会影响已建立的连接。\n */\nexport const RESTART_KEYS = [\n 'dmMode',\n 'requireMention',\n 'refreshMs',\n 'statusRefreshMs',\n 'loggerLevel',\n] as const satisfies readonly EditableKey[]\n\nexport type RestartKey = (typeof RESTART_KEYS)[number]\n\n/** Render kinds the generic panel form understands. */\nexport type FieldKind = 'boolean' | 'number' | 'text' | 'select'\n\n/** Field groups map one-to-one onto panel cards. */\nexport type FieldGroup = 'workspace' | 'behavior' | 'content' | 'model' | 'streaming'\n\n/** One selectable value rendered by the generic form. */\nexport interface FieldOptionWire {\n value: string\n label: string\n}\n\n/** One editable row, fully described by the Host so the client stays schema-free. */\nexport interface EditableFieldWire {\n key: EditableKey\n label: string\n kind: FieldKind\n group: FieldGroup\n help?: string\n options?: readonly FieldOptionWire[]\n /** Selector is not editable yet (e.g. pick the provider before the model). */\n disabled?: boolean\n min?: number\n max?: number\n step?: number\n placeholder?: string\n /** Render across the whole form width (long paths). */\n wide?: boolean\n /** 保存后需重连飞书通道才生效。 */\n restartRequired: boolean\n}\n\n/**\n * One selector group resolved from live host services. `selectable: false`\n * means the host could not enumerate values (service absent / lookup failed),\n * so the field falls back to the free-text control with the same key.\n */\nexport interface SelectCatalogWire {\n selectable: boolean\n /** Rendered in order; the leading `''` entry is the inherit/clear choice. */\n options: FieldOptionWire[]\n disabled?: boolean\n /** Host-side explanation shown instead of the static field help. */\n help?: string\n}\n\n/** `GET /dsh-channels/catalog` payload: the model-facing selector catalog. */\nexport interface CatalogWire {\n provider: SelectCatalogWire\n model: SelectCatalogWire\n reasoningEffort: SelectCatalogWire\n agentPreset: SelectCatalogWire\n /** Deployment default selection, when the host exposes `agentDefaultModel`. */\n deploymentDefault?: {\n provider?: string\n model?: string\n reasoningEffort?: string\n }\n /** Roster default preset id, when the host exposes the preset roster. */\n defaultPresetId?: string\n}\n\n/** Values the panel may submit. Empty strings for optional text fields clear the override. */\nexport type ConfigValue = string | number | boolean\nexport type EditableValues = Partial<Record<EditableKey, ConfigValue>>\n\n/** Read-only facts shown in the advanced card. */\nexport interface ReadOnlyFieldWire {\n key: string\n label: string\n value: string\n help?: string\n}\n\n/** One credential reference plus its Host-side state (values never cross the wire). */\nexport interface CredentialRefWire {\n ref: string\n configured: boolean\n /** false when the mounted credentials provider cannot store values. */\n writable: boolean\n /** Where the configured value comes from (e.g. `file`), when the provider reports it. */\n source?: string\n}\n\n/** The two credential slots the panel may write. */\nexport type CredentialField = 'appId' | 'appSecret'\n\n/** Official-SDK WebSocket lifecycle snapshot (structural copy, no SDK import on the wire). */\nexport interface ConnectionWire {\n state: string\n lastConnectTime?: number\n nextConnectTime?: number\n reconnectAttempts: number\n}\n\n/** Runtime connection/liveness facts. */\nexport interface ChannelStatusWire {\n running: boolean\n level: 'healthy' | 'degraded' | 'unhealthy'\n startedAt?: number\n uptimeMs?: number\n connection?: ConnectionWire\n botOpenIdTail?: string\n routeCount: number\n liveAgentCount: number\n inflightTurnCount: number\n /** 最近一次启动失败的原因(成功后清空)。 */\n lastStartError?: string\n /** 健康判定的原因说明(空数组 = 健康);由 Host 的 health 折叠给出。 */\n reasons: string[]\n /** 面板显示的所有键值对来源版本。 */\n version: string\n /** Web 覆盖文件绝对路径(用户可直接编辑)。 */\n overlayPath: string\n /** 正在重连时为 true,此时面板按钮禁用。 */\n busy: boolean\n}\n\n/** The whole editable configuration surface, with provenance. */\nexport interface PanelConfigWire {\n /** profile `cordis.patch.yml` 提供的值(覆盖层之下)。 */\n base: Record<string, ConfigValue | undefined>\n /** 合并后的生效值。 */\n effective: Record<string, ConfigValue | undefined>\n /** 已写入 Web 覆盖层的值。 */\n overlay: EditableValues\n overriddenKeys: EditableKey[]\n /** 与当前运行中连接不一致、需重连才生效的字段。 */\n restartPendingKeys: EditableKey[]\n fields: EditableFieldWire[]\n readOnly: ReadOnlyFieldWire[]\n credentials: {\n appId: CredentialRefWire\n appSecret: CredentialRefWire\n }\n}\n\n/** One authorized group (the first of the two admission gates). */\nexport interface AccessGroupWire {\n chatId: string\n /** Human-readable group name captured at authorization time. */\n name?: string\n /** Which authorization entry produced the grant. */\n source: 'command' | 'card' | 'panel'\n /** Epoch ms when it was authorized. */\n addedAt: number\n}\n\n/** One authorized user `open_id` (global across every authorized group). */\nexport interface AccessUserWire {\n openId: string\n /** Display name captured at authorization time. */\n name?: string\n /** Which authorization entry produced the grant. */\n source: 'command' | 'card' | 'panel'\n /** Epoch ms when it was authorized. */\n addedAt: number\n}\n\n/** One pending authorization request the panel may settle (same path as the DM card). */\nexport interface AccessRequestWire {\n /** Opaque token; never derived from the target id. */\n token: string\n kind: 'group' | 'user'\n /** `chat_id` for a group request, `open_id` for a user request. */\n targetId: string\n /** Display name of the requested target (group name / user name), when known. */\n name?: string\n /** Chat the request originated from, when applicable. */\n chatId?: string\n /** Group name captured for the card. */\n groupName?: string\n /** Open_id of the user who triggered a user request. */\n requester?: string\n /** Epoch ms when the request was created. */\n createdAt: number\n /** Epoch ms after which the request can no longer be decided. */\n expiresAt: number\n}\n\n/**\n * `GET /dsh-channels/access` payload: admission mode, the administrator\n * reference, both whitelists, and the still-pending authorization requests.\n */\nexport interface AccessWire {\n mode: 'open' | 'guarded'\n admin: {\n openId?: string\n /** false while guarded mode would refuse every conversation (fail closed). */\n configured: boolean\n }\n groups: AccessGroupWire[]\n users: AccessUserWire[]\n requests: AccessRequestWire[]\n}\n\n/** Write responses for every `/dsh-channels/access*` mutation. */\nexport type AccessMutationResponse =\n | { ok: true; access: AccessWire }\n | { ok: false; error: string }\n\n/** `GET /dsh-channels/panel` payload. */\nexport interface PanelWire {\n status: ChannelStatusWire\n config: PanelConfigWire\n cleanup: CleanupPreviewWire\n /** 访问与授权卡片的数据(准入模式、管理员、白名单、待处理申请)。 */\n access: AccessWire\n}\n\n/**\n * What the workspace-retention cleanup would delete right now. Counts are\n * zero and `error` is set when the Host cannot compute the preview (for\n * example the cleanup seam is unavailable); the panel stays usable.\n */\nexport interface CleanupPreviewWire {\n /** Effective `workspaceRetention` value (`off` / `7d` / `30d` / `90d` / `180d`). */\n retention: string\n /** Retention threshold in milliseconds; absent when `retention` is `off`. */\n thresholdMs?: number\n /** Periodic cleanup runs at startup and once per day. */\n auto: boolean\n /** Plugin-created workspaces under the current baseDir. */\n workspaces: number\n /** Workspaces older than the threshold (the deletable set). */\n candidates: number\n /** Total bytes currently occupied by the candidates. */\n candidateBytes: number\n /** Why the preview could not be computed (counts are zero then). */\n error?: string\n}\n\n/** Outcome of one manual cleanup run (partial failures do not abort the run). */\nexport interface CleanupRunWire {\n ok: boolean\n deletedWorkspaces: number\n retiredRoutes: number\n freedBytes: number\n /** One `path: message` line per workspace that could not be deleted. */\n failures: string[]\n error?: string\n}\n\n/** `POST /dsh-channels/cleanup` response. */\nexport type CleanupResponse =\n | { ok: true; panel: PanelWire; result: CleanupRunWire }\n | { ok: false; error: string }\n\n/** `PUT /dsh-channels/config` body. */\nexport interface SaveConfigRequest {\n values: EditableValues\n}\n\n/** Per-field validation failure. */\nexport interface FieldIssueWire {\n key: string\n message: string\n}\n\n/** `PUT /dsh-channels/config` response. */\nexport type SaveConfigResponse =\n | { ok: true; panel: PanelWire; clearedKeys: EditableKey[] }\n | { ok: false; error: string; issues?: FieldIssueWire[] }\n\n/** `POST /dsh-channels/reconnect` response. */\nexport type ReconnectResponse =\n | { ok: true; panel: PanelWire }\n | { ok: false; error: string }\n\n/** `DELETE /dsh-channels/config` response. */\nexport type ClearConfigResponse =\n | { ok: true; panel: PanelWire }\n | { ok: false; error: string }\n\n/** `PUT /dsh-channels/credentials` body: only provided, non-empty values are written. */\nexport interface SaveCredentialsRequest {\n appId?: string\n appSecret?: string\n}\n\n/** `DELETE /dsh-channels/credentials` body. */\nexport interface ClearCredentialRequest {\n field: CredentialField\n}\n\n/** How a saved credential reached the running channel. */\nexport type CredentialReconnectState = 'auto' | 'pending' | 'not-needed'\n\nexport interface CredentialsSavedPayload {\n panel: PanelWire\n reconnect: CredentialReconnectState\n /** Set when the automatic reconnect after a save failed (the value is still saved). */\n autoReconnectError?: string\n}\n\n/** Credential write/remove responses (values never appear in either direction). */\nexport type SaveCredentialsResponse =\n | ({ ok: true } & CredentialsSavedPayload)\n | { ok: false; error: string; issues?: FieldIssueWire[] }\n\nexport type ClearCredentialResponse =\n | ({ ok: true } & CredentialsSavedPayload)\n | { ok: false; error: string; issues?: FieldIssueWire[] }\n\n/** Display labels for the panel header/cards (kept on the wire so the client owns no copy). */\nexport const PANEL_COPY = {\n title: '飞书渠道',\n description: '飞书长连接状态、应用凭据与常用配置。凭据写入 DSH 凭据库且不回显;标注「需重连」的项需重启飞书连接生效。',\n statusCard: '连接状态',\n credentialsCard: '飞书应用凭据',\n modelCard: '模型与推理',\n modelCardHelp: '模型服务商、模型 id、推理档位与新会话的 Agent 预设。下拉选项来自宿主机实时目录;保存后对新会话生效。',\n configCard: '常用配置',\n advancedCard: '只读与高级信息',\n appIdLabel: '飞书 App ID',\n appSecretLabel: '飞书 App Secret',\n credentialsHelp: '值通过 DSH 凭据服务写入本机凭据库;面板只显示是否已配置,永不回显已存值。留空表示保持当前值。',\n credentialsRef: '凭据引用',\n saveCredentials: '保存凭据',\n savingCredentials: '保存中…',\n credentialSaved: '凭据已保存',\n credentialSavedReconnected: '凭据已保存,已自动重连飞书通道',\n credentialSavedPending: '凭据已保存,重启飞书连接后生效',\n credentialRemoved: '已移除存储的凭据',\n credentialRemovedPending: '已移除存储的凭据,重启飞书连接后生效',\n removeCredential: '移除已存值',\n removingCredential: '移除中…',\n credentialConfigured: '已配置',\n credentialMissing: '未配置',\n credentialReadOnly: '当前凭据存储不可写',\n appIdPlaceholder: '输入 App ID(cli_…)',\n appSecretPlaceholder: '输入 App Secret',\n save: '保存配置',\n saveAll: '保存全部配置',\n saveModel: '保存模型配置',\n modelActionsHint: '只保存模型与推理字段',\n saving: '保存中…',\n clear: '清除 Web 覆盖',\n clearing: '清除中…',\n reconnect: '重启飞书连接',\n reconnecting: '重连中…',\n refresh: '刷新状态',\n retry: '重试',\n loading: '加载中…',\n loadFailed: '设置面板加载失败',\n saved: '已保存',\n savedModel: '模型配置已保存',\n cleared: '已清除 Web 覆盖,恢复为 profile 配置',\n catalogFailed: '模型目录加载失败,已退回手填。',\n catalogLoading: '正在读取宿主机选项…',\n catalogUnavailable: '宿主机未提供可选项,请手填。',\n noOverrides: '当前没有 Web 覆盖',\n overridden: 'Web 覆盖',\n inherited: '继承 profile',\n restartTag: '需重连',\n restartBanner: '以下配置需重启飞书连接后生效:',\n restartWarning: '重启会断开当前飞书长连接并中断进行中的回复,请确认空闲时操作。',\n lastError: '最近一次启动失败',\n notRunning: '未运行',\n version: '插件版本',\n overlayPath: '覆盖文件',\n cleanupCard: '工作区清理',\n cleanupCardHelp: '删除超过保留期且不再活跃的飞书工作区目录;只会删除本插件在「工作区根目录」下创建的目录,DSH 会话日志不删除。删除不可恢复:相关旧话题会失效并提示已过期,工作区列表记录同步移除。',\n cleanupRetention: '保留期',\n cleanupAutoLabel: '自动清理',\n cleanupCandidates: '可清理工作区',\n cleanupCandidateBytes: '预计释放空间',\n cleanupRetentionOff: '关闭(不删除)',\n cleanupAutoOn: '已开启(启动时 + 每天一次)',\n cleanupAutoOff: '已关闭(仅手动执行)',\n cleanupAutoInactive: '未执行(保留期为关闭)',\n cleanupNow: '立即清理',\n cleanupRunning: '清理中…',\n cleanupConfirm: '确定清理过期工作区吗?该操作不可恢复,会删除这些工作区内的全部文件(包括 attachments/ 附件)。',\n cleanupConfirmHint: '删除前会再次确认;保留期为「关闭」或没有过期工作区时按钮不可用。',\n cleanupDisabledOff: '保留策略为「关闭」:请先在「常用配置 → 工作区与文件」中选择保留天数。',\n cleanupDisabledEmpty: '没有超过保留期的过期工作区。',\n cleanupPreviewFailed: '无法读取清理预览',\n cleanupDone: '清理完成',\n cleanupFailed: '清理失败',\n cleanupDeleted: '已删除工作区',\n cleanupRetired: '已清理话题路由',\n cleanupFreed: '释放空间',\n cleanupFailures: '以下工作区未能删除:',\n accessCard: '访问与授权',\n accessCardHelp: '准入采用 fail-closed:管理员不受白名单限制(私聊、任意群聊都可直接对话);非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。管理员 open_id 在「常用配置 → 行为」中填写,私聊机器人发送 /userid 获取。',\n accessModeOpenWarning: '当前准入模式为开放(open):应用可用范围内的任何人都能开启对话。该模式不安全,仅建议调试时使用。',\n accessAdminMissingWarning: '当前准入=guarded 但未配置管理员,所有对话都会被拒绝。请先在「常用配置 → 行为 → 管理员 open_id」填写。',\n accessAdminLabel: '管理员',\n accessAdminMissing: '未配置',\n accessAdminHint: '修改管理员请到「常用配置 → 行为 → 管理员 open_id」;私聊机器人发送 /userid 可查看自己的 open_id(ou_ 开头,不是 user_id/邮箱)。',\n accessGroupsTitle: '已授权群',\n accessGroupsEmpty: '尚未授权任何群。机器人被拉进群后,管理员会收到带 chat_id 的群信息卡;也可在群内发送 /groupid 获取。',\n accessUsersTitle: '已授权用户',\n accessUsersEmpty: '尚未授权任何用户。已授权用户可在所有已授权群里与机器人对话(私聊仍只开给管理员)。',\n accessRequestsTitle: '待处理申请',\n accessRequestsEmpty: '没有待处理的授权申请。',\n accessGroupChatIdPlaceholder: '群 chat_id(oc_…)',\n accessGroupNamePlaceholder: '群名(可选)',\n accessUserOpenIdPlaceholder: '用户 open_id(ou_…)',\n accessUserNamePlaceholder: '昵称(可选)',\n accessAddGroup: '添加群',\n accessAddUser: '添加用户',\n accessRemove: '移除',\n accessAllow: '授权',\n accessDeny: '忽略',\n accessWorking: '处理中…',\n accessSourceCommand: '对话命令',\n accessSourceCard: '授权卡',\n accessSourcePanel: '设置面板',\n accessRequestGroup: '群申请',\n accessRequestUser: '用户申请',\n accessRequestRequester: '申请人',\n accessRequestExpires: '有效期至',\n accessGroupAdded: '已添加群授权',\n accessGroupRemoved: '已移除群授权',\n accessUserAdded: '已添加用户授权',\n accessUserRemoved: '已移除用户授权',\n accessRequestAllowed: '已授权',\n accessRequestDenied: '已忽略',\n accessHints: '三种授权入口:群内 /auth 命令、入群与用户授权卡、本面板。撤销立即生效;白名单只约束非管理员(管理员在任何私聊/群聊都直接放行),非管理员需要群和用户两层同时命中。',\n accessChatIdHint: 'chat_id 可从入群群信息卡或群内 /groupid 获取;open_id 可私聊机器人发送 /userid 获取。',\n} as const\n","/**\n * Web 面板覆盖层:`$DSH_HOME/dsh-channels/settings.json` 的读写、schema 校验与合并。\n *\n * 设计取舍:\n * - 覆盖层只存 Web 面板可编辑的字段(`EDITABLE_KEYS`),凭据引用刻意不可\n * 覆盖(变动等于换凭据);`baseDir` 可覆盖,但走 `workspaces.ts` 的严格\n * 规范化(绝对路径 / `~` 展开 / 拒绝根目录),不接受相对路径。\n * - 文件损坏/字段过期时**降级为空覆盖**并告警,绝不阻止飞书通道启动;\n * 面板保存时则由 zod 完整校验后整体落盘。\n * - 写入走同目录临时文件 + rename,避免半截文件。\n *\n * @module dsh-channels/settings/overlay\n */\n\nimport { promises as fs } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\nimport type { z } from 'zod'\nimport { configSchema } from '../config.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport { normalizeWorkspaceBaseDir } from '../workspaces.ts'\nimport { EDITABLE_KEYS } from './contract.ts'\nimport type { ConfigValue, EditableKey } from './contract.ts'\n\n/** The subset of `FeishuConfig` the Web panel may override. */\nexport type OverlayPatch = Partial<Pick<FeishuConfig, EditableKey>>\n\n/** Fields whose empty string means \"clear the override\" (optional text config). */\nconst OPTIONAL_TEXT_KEYS = new Set<EditableKey>(['provider', 'model', 'reasoningEffort', 'agentPreset'])\n\n/**\n * Validate only the keys actually present in the input. Validating through\n * the canonical `configSchema` field-by-field keeps one source of truth for\n * bounds/enums — and, unlike `z.object(shape).partial()`, it does not leak\n * schema defaults into the overlay (which would otherwise turn a one-field\n * edit into \"override every editable key\").\n */\nexport function parseOverlay(raw: unknown): { ok: true; patch: OverlayPatch } | { ok: false; issues: Array<{ path: string; message: string }> } {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { ok: false, issues: [{ path: '', message: '必须是 JSON 对象' }] }\n }\n const source = raw as Record<string, unknown>\n const issues: Array<{ path: string; message: string }> = []\n const patch: Record<string, unknown> = {}\n const shape = configSchema.shape as unknown as Record<string, z.ZodType>\n for (const key of EDITABLE_KEYS) {\n if (!Object.hasOwn(source, key)) continue\n const value = source[key]\n if (value === undefined) continue\n // `baseDir` needs location semantics the shared string schema cannot\n // express (absolute only, `~` expansion, no filesystem root).\n if (key === 'baseDir') {\n const checked = normalizeWorkspaceBaseDir(value)\n if (!checked.ok) issues.push({ path: key, message: checked.message })\n else patch[key] = checked.value\n continue\n }\n const parsed = shape[key]!.safeParse(value)\n if (!parsed.success) {\n issues.push({ path: key, message: parsed.error.issues[0]?.message ?? '非法值' })\n continue\n }\n patch[key] = parsed.data\n }\n if (issues.length > 0) return { ok: false, issues }\n return { ok: true, patch: normalizeOverlay(patch) }\n}\n\n/** `$DSH_HOME` resolution shared with the rest of the DSH ecosystem. */\nexport function resolveDshHome(env: NodeJS.ProcessEnv = process.env): string {\n const configured = env.DSH_HOME?.trim()\n return configured !== undefined && configured !== '' ? configured : join(homedir(), '.dsh')\n}\n\n/** Default overlay path: `$DSH_HOME/dsh-channels/settings.json`. */\nexport function defaultOverlayPath(env: NodeJS.ProcessEnv = process.env): string {\n return join(resolveDshHome(env), 'dsh-channels', 'settings.json')\n}\n\n/** Warn sink used for degraded reads (never throws). */\nexport interface OverlayLogger {\n warn?(message: string, ...args: unknown[]): void\n}\n\n/** Strip `undefined`; drop empty-string overrides for optional text fields. */\nexport function normalizeOverlay(patch: Record<string, unknown>): OverlayPatch {\n const normalized: Record<string, unknown> = {}\n for (const key of EDITABLE_KEYS) {\n const value = patch[key]\n if (value === undefined) continue\n if (OPTIONAL_TEXT_KEYS.has(key) && value === '') continue\n normalized[key] = value\n }\n return normalized as OverlayPatch\n}\n\n/** Merge the Web overlay over the profile-provided base config. */\nexport function mergeOverlay(base: FeishuConfig, overlay: OverlayPatch): FeishuConfig {\n return { ...base, ...overlay }\n}\n\n/** Project one config onto the editable-key subset (wire shape). */\nexport function pickEditable(config: FeishuConfig): Record<EditableKey, ConfigValue | undefined> {\n const picked: Record<string, ConfigValue | undefined> = {}\n for (const key of EDITABLE_KEYS) picked[key] = config[key] as ConfigValue | undefined\n return picked as Record<EditableKey, ConfigValue | undefined>\n}\n\n/**\n * One JSON overlay file. Reads degrade to `{}` with a warning; writes are\n * atomic and owner-readable only.\n */\nexport class OverlayStore {\n constructor(\n readonly file: string,\n private readonly logger?: OverlayLogger,\n ) {}\n\n /** Load the overlay; a missing/corrupt/stale file degrades to an empty overlay. */\n async load(): Promise<OverlayPatch> {\n let text: string\n try {\n text = await fs.readFile(this.file, 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.logger?.warn?.('dsh-channels: Web 覆盖配置读取失败(%s),按未覆盖处理:%s', this.file, String(error))\n }\n return {}\n }\n let raw: unknown\n try {\n raw = JSON.parse(text)\n } catch (error) {\n this.logger?.warn?.('dsh-channels: Web 覆盖配置不是合法 JSON(%s),按未覆盖处理:%s', this.file, String(error))\n return {}\n }\n const parsed = parseOverlay(raw)\n if (!parsed.ok) {\n this.logger?.warn?.(\n 'dsh-channels: Web 覆盖配置含非法字段(%s),按未覆盖处理:%s',\n this.file,\n parsed.issues.map(issue => `${issue.path}: ${issue.message}`).join('; '),\n )\n return {}\n }\n return parsed.patch\n }\n\n /** Persist the validated overlay atomically. */\n async save(patch: OverlayPatch): Promise<void> {\n await fs.mkdir(dirname(this.file), { recursive: true })\n const temporary = `${this.file}.tmp-${process.pid}-${Date.now()}`\n await fs.writeFile(temporary, `${JSON.stringify(normalizeOverlay(patch as Record<string, unknown>), null, 2)}\\n`, {\n encoding: 'utf8',\n mode: 0o600,\n })\n await fs.rename(temporary, this.file)\n }\n\n /** Remove the overlay file; the plugin falls back to the profile config. */\n async clear(): Promise<void> {\n await fs.rm(this.file, { force: true })\n }\n}\n","/**\n * Web 设置面板的 HTTP 路由:status / panel / catalog / config(PUT,DELETE) /\n * credentials(PUT,DELETE) / reconnect / cleanup / access。\n *\n * 路由注册进宿主可选的 `webServer` 服务(headless profile 没有它时面板自然\n * 不存在,飞书通道不受影响)。写操作额外做同源校验 —— 本机非回环绑定下\n * 防止跨站页面改写插件配置或授权白名单;读取不敏感,保持只读 GET。凭据值\n * 只走写路径,响应永远不包含它们。`catalog` 只读宿主 LLM / preset roster 的\n * 枚举结果,用于面板的 provider → model → 推理档位级联选择。`access` 提供\n * 准入快照与白名单/授权申请的写入口(与飞书卡片共用同一结算函数)。\n *\n * @module dsh-channels/settings/routes\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { HostContext } from '../dsh-types.ts'\nimport {\n ACCESS_GROUPS_ROUTE,\n ACCESS_REQUESTS_ROUTE,\n ACCESS_ROUTE,\n ACCESS_USERS_ROUTE,\n CATALOG_ROUTE,\n CLEANUP_ROUTE,\n CONFIG_ROUTE,\n CREDENTIALS_ROUTE,\n PANEL_ROUTE,\n RECONNECT_ROUTE,\n STATUS_ROUTE,\n} from './contract.ts'\nimport type { CredentialField, EditableValues, SaveCredentialsRequest } from './contract.ts'\nimport type { SettingsSurface } from './surface.ts'\n\n/** The narrow `ctx.webServer` surface this module registers into. */\nexport interface HostWebServer {\n register(route: {\n kind: 'exact'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n }): () => void\n}\n\ntype WebHandler = (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n\n/** Request body cap: the panel payload is a handful of primitives. */\nconst MAX_BODY_BYTES = 64 * 1024\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nclass HttpError extends Error {\n constructor(readonly status: number, message: string) {\n super(message)\n }\n}\n\nfunction methodOf(req: IncomingMessage): string {\n return (req.method ?? 'GET').toUpperCase()\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n const body = JSON.stringify(payload)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-length': Buffer.byteLength(body),\n })\n res.end(body)\n}\n\n/** Reject non-allowed methods with 405 + Allow, returning false. */\nfunction allowOnly(req: IncomingMessage, res: ServerResponse, allowed: readonly string[]): boolean {\n const method = methodOf(req)\n if (allowed.includes(method)) return true\n const body = JSON.stringify({ ok: false, error: `方法 ${method} 不受支持` })\n res.writeHead(405, {\n allow: allowed.join(', '),\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-length': Buffer.byteLength(body),\n })\n res.end(body)\n return false\n}\n\n/**\n * Mutating requests must come from this same origin. Missing Origin is allowed\n * (non-browser local clients, e.g. curl); a present-but-mismatched Origin is\n * a cross-site write and is refused.\n */\nexport function isSameOriginRequest(req: IncomingMessage): boolean {\n const origin = req.headers.origin\n if (origin === undefined || origin === '') return true\n const host = req.headers.host\n if (host === undefined) return false\n try {\n return new URL(origin).host === host\n } catch {\n return false\n }\n}\n\nasync function readJsonBody(req: IncomingMessage): Promise<unknown> {\n const contentType = (req.headers['content-type'] ?? '').split(';')[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n throw new HttpError(415, '请求体必须是 application/json')\n }\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : chunk as Buffer\n size += buffer.length\n if (size > MAX_BODY_BYTES) throw new HttpError(413, '请求体过大')\n chunks.push(buffer)\n }\n if (size === 0) throw new HttpError(400, '请求体为空')\n try {\n return JSON.parse(Buffer.concat(chunks).toString('utf8'))\n } catch {\n throw new HttpError(400, '请求体不是合法 JSON')\n }\n}\n\n/**\n * Parse one JSON-object body for an access write. HttpErrors are answered\n * here (415/413/400); `undefined` means \"already responded, stop\".\n */\nasync function readAccessBody(\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<Record<string, unknown> | undefined> {\n let body: unknown\n try {\n body = await readJsonBody(req)\n } catch (error) {\n if (error instanceof HttpError) {\n sendJson(res, error.status, { ok: false, error: error.message })\n return undefined\n }\n throw error\n }\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n sendJson(res, 400, { ok: false, error: '请求体必须是 JSON 对象' })\n return undefined\n }\n return body as Record<string, unknown>\n}\n\n/** Wrap a handler so thrown errors become JSON instead of a dropped socket. */\nfunction guarded(handler: WebHandler): WebHandler {\n return async (req, res) => {\n try {\n await handler(req, res)\n } catch (error) {\n if (res.headersSent) {\n res.end()\n return\n }\n const status = error instanceof HttpError ? error.status : 500\n sendJson(res, status, {\n ok: false,\n error: error instanceof HttpError ? error.message : `内部错误:${messageOf(error)}`,\n })\n }\n }\n}\n\nfunction forbiddenWrite(res: ServerResponse): void {\n sendJson(res, 403, { ok: false, error: '跨站写入被拒绝(Origin 与 Host 不一致)' })\n}\n\n/** Register the panel routes; the returned disposer removes all of them. */\nexport function registerSettingsRoutes(server: HostWebServer, surface: SettingsSurface): () => void {\n const disposers = [\n server.register({\n kind: 'exact',\n path: STATUS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n sendJson(res, 200, await surface.status())\n }),\n }),\n server.register({\n kind: 'exact',\n path: PANEL_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n sendJson(res, 200, await surface.panel())\n }),\n }),\n server.register({\n kind: 'exact',\n path: CATALOG_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n const url = new URL(req.url ?? '/', 'http://dsh.local')\n sendJson(res, 200, await surface.catalogFor(\n url.searchParams.get('provider') ?? undefined,\n url.searchParams.get('model') ?? undefined,\n ))\n }),\n }),\n server.register({\n kind: 'exact',\n path: CONFIG_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['PUT', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n if (methodOf(req) === 'DELETE') {\n const result = await surface.clear()\n sendJson(res, result.ok ? 200 : 400, result)\n return\n }\n let body: unknown\n try {\n body = await readJsonBody(req)\n } catch (error) {\n if (error instanceof HttpError) {\n sendJson(res, error.status, { ok: false, error: error.message })\n return\n }\n throw error\n }\n const values = (body as { values?: unknown } | null)?.values\n if (typeof values !== 'object' || values === null || Array.isArray(values)) {\n sendJson(res, 400, { ok: false, error: '请求体缺少 values 对象' })\n return\n }\n const result = await surface.save(values as EditableValues)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: CREDENTIALS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['PUT', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n let body: unknown\n try {\n body = await readJsonBody(req)\n } catch (error) {\n if (error instanceof HttpError) {\n sendJson(res, error.status, { ok: false, error: error.message })\n return\n }\n throw error\n }\n const payload = body as { appId?: unknown; appSecret?: unknown; field?: unknown } | null\n if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {\n sendJson(res, 400, { ok: false, error: '请求体必须是 JSON 对象' })\n return\n }\n if (methodOf(req) === 'DELETE') {\n const field = payload.field\n if (field !== 'appId' && field !== 'appSecret') {\n sendJson(res, 400, { ok: false, error: 'field 必须是 appId 或 appSecret' })\n return\n }\n const result = await surface.clearCredential(field as CredentialField)\n sendJson(res, result.ok ? 200 : 400, result)\n return\n }\n const values: SaveCredentialsRequest = {}\n if (payload.appId !== undefined) values.appId = payload.appId as string\n if (payload.appSecret !== undefined) values.appSecret = payload.appSecret as string\n const result = await surface.saveCredentials(values)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: RECONNECT_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const result = await surface.reconnect()\n sendJson(res, result.ok ? 200 : 500, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: CLEANUP_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const result = await surface.runCleanup()\n sendJson(res, result.ok ? 200 : 500, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n sendJson(res, 200, await surface.access())\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_GROUPS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const payload = await readAccessBody(req, res)\n if (payload === undefined) return\n const result = methodOf(req) === 'DELETE'\n ? await surface.removeAccessGroup(payload.chatId)\n : await surface.saveAccessGroup(payload.chatId, payload.name)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_USERS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const payload = await readAccessBody(req, res)\n if (payload === undefined) return\n const result = methodOf(req) === 'DELETE'\n ? await surface.removeAccessUser(payload.openId)\n : await surface.saveAccessUser(payload.openId, payload.name)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_REQUESTS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const payload = await readAccessBody(req, res)\n if (payload === undefined) return\n const result = await surface.resolveAccessRequest(payload.token, payload.decision)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/**\n * Mount the routes once the host carries a Web server. The optional\n * `ctx.inject` keeps headless profiles from waiting on a service they will\n * never have; embedded/test hosts that resolve services synchronously fall\n * back to the direct attach path.\n */\nexport function mountSettingsRoutes(ctx: HostContext, surface: SettingsSurface): void {\n const attach = (webCtx: HostContext): void => {\n const server = webCtx.get?.('webServer') as HostWebServer | undefined\n if (server === undefined) return\n webCtx.effect(\n () => registerSettingsRoutes(server, surface),\n 'dsh-channels: settings panel routes',\n )\n }\n const inject = (ctx as {\n inject?: (dependencies: string[], callback: (child: HostContext) => void) => unknown\n }).inject\n if (typeof inject === 'function') {\n inject.call(ctx, ['webServer'], attach)\n return\n }\n attach(ctx)\n}\n","/**\n * Web 设置面板的 Host 视图模型:把 `FeishuService` 的运行时事实、覆盖层与\n * 字段描述组装成 wire 数据,并处理保存/清除/重连/清理四个动作。\n *\n * 这一层不碰 HTTP(见 `routes.ts`),也不碰 fs 细节(见 `overlay.ts`);\n * 唯一的文件系统动作是保存 `baseDir` 时通过 `workspaces.ts` 创建并检查\n * 工作区根目录,这样不可用的路径在保存时就暴露,而不是等到飞书里提示\n * 「会话创建失败」。因此可以只用假 target 做单测。\n *\n * @module dsh-channels/settings/surface\n */\n\nimport { DSH_CHANNELS_VERSION } from '../session-export.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport type { HealthSnapshot } from '../health.ts'\nimport { prepareWorkspaceBaseDir } from '../workspaces.ts'\nimport {\n EDITABLE_KEYS,\n PANEL_COPY,\n RESTART_KEYS,\n} from './contract.ts'\nimport type {\n AccessMutationResponse,\n AccessWire,\n CatalogWire,\n ChannelStatusWire,\n CleanupPreviewWire,\n CleanupResponse,\n CleanupRunWire,\n ClearConfigResponse,\n ClearCredentialResponse,\n ConfigValue,\n CredentialField,\n CredentialRefWire,\n EditableFieldWire,\n EditableKey,\n EditableValues,\n FieldIssueWire,\n FieldOptionWire,\n PanelConfigWire,\n PanelWire,\n ReadOnlyFieldWire,\n ReconnectResponse,\n SaveConfigResponse,\n SaveCredentialsRequest,\n SaveCredentialsResponse,\n} from './contract.ts'\nimport {\n parseOverlay,\n pickEditable,\n} from './overlay.ts'\nimport type { OverlayLogger, OverlayPatch, OverlayStore } from './overlay.ts'\nimport type { SettingsCatalog } from './catalog.ts'\n\n/** The slice of `FeishuService` the panel needs (structural, easy to fake). */\nexport interface SettingsTarget {\n isRunning(): boolean\n isRestarting(): boolean\n health(): HealthSnapshot\n startedAtMs(): number | undefined\n uptimeMs(): number | undefined\n startError(): string | undefined\n configSnapshot(): FeishuConfig\n baseConfigSnapshot(): FeishuConfig\n runningConfigSnapshot(): FeishuConfig | undefined\n applyOverlay(overlay: Partial<FeishuConfig>): void\n restart(): Promise<void>\n credentialStates(): Promise<{ appId: CredentialInfo; appSecret: CredentialInfo }>\n setCredential(field: CredentialField, value: string): Promise<void>\n clearCredential(field: CredentialField): Promise<void>\n /** What the workspace-retention cleanup would delete right now (read-only scan). */\n cleanupPreview(): Promise<CleanupPreviewWire>\n /** Run the workspace cleanup once; partial failures are reported per workspace. */\n runWorkspaceCleanup(): Promise<CleanupRunWire>\n /** Current admission snapshot: mode, administrator, whitelists, pending requests. */\n accessSnapshot(): AccessWire\n /** Authorize one group (panel entry); resolves with the refreshed snapshot. */\n addAccessGroup(chatId: string, name?: string): Promise<AccessMutationResponse>\n /** Revoke one group authorization (takes effect on the next message). */\n removeAccessGroup(chatId: string): Promise<AccessMutationResponse>\n /** Authorize one user `open_id` (global across authorized groups). */\n addAccessUser(openId: string, name?: string): Promise<AccessMutationResponse>\n /** Revoke one user authorization (takes effect on the next message). */\n removeAccessUser(openId: string): Promise<AccessMutationResponse>\n /** Settle one pending request (panel shares this path with the Feishu DM card). */\n resolveAccessRequest(token: string, decision: 'allow' | 'deny'): Promise<AccessMutationResponse>\n}\n\n/** Presence/source/writability facts returned by the credentials seam. */\nexport interface CredentialInfo {\n configured: boolean\n writable?: boolean\n source?: string\n}\n\nconst RESTART_SET = new Set<string>(RESTART_KEYS)\n\n/** Optional text fields: an empty string in the panel clears the override. */\nconst OPTIONAL_TEXT_KEYS = new Set<EditableKey>([\n 'provider',\n 'model',\n 'reasoningEffort',\n 'agentPreset',\n 'adminOpenId',\n])\n\nconst BOOLEAN_KEYS = new Set<EditableKey>([\n 'requireMention',\n 'welcomeOnboarding',\n 'allowPermissionSwitch',\n 'acceptAttachments',\n 'workspaceRetentionAuto',\n])\n\nconst SELECT_OPTIONS: Partial<Record<EditableKey, ReadonlyArray<{ value: string; label: string }>>> = {\n dmMode: [\n { value: 'open', label: '开放私聊' },\n { value: 'disabled', label: '禁用私聊' },\n ],\n threadFallback: [\n { value: 'degrade', label: '话题被拒时降级为会话级' },\n { value: 'strict', label: '话题被拒时直接失败' },\n ],\n workspaceRetention: [\n { value: 'off', label: '关闭(不删除)' },\n { value: '7d', label: '7 天' },\n { value: '30d', label: '30 天' },\n { value: '90d', label: '90 天' },\n { value: '180d', label: '180 天' },\n ],\n loggerLevel: [\n { value: 'debug', label: 'debug' },\n { value: 'info', label: 'info' },\n { value: 'warn', label: 'warn' },\n { value: 'error', label: 'error' },\n ],\n accessMode: [\n { value: 'guarded', label: '守卫模式(仅管理员与白名单)' },\n { value: 'open', label: '开放(不安全,仅调试)' },\n ],\n}\n\n/** Panel copy per editable key: label, help, and (for numbers) bounds. */\nconst FIELD_META: Record<EditableKey, { label: string; help: string; min?: number; max?: number; step?: number; placeholder?: string; wide?: boolean }> = {\n baseDir: {\n label: '工作区根目录(文件落点)',\n help: '每个飞书话题拥有独立工作区:私聊 <baseDir>/topics/p2p/<open_id>/<topic>/,群聊 <baseDir>/topics/groups/<chat_id>/<open_id>/<topic>/(topic 来自话题 id)。必须是绝对路径(~ 开头按家目录展开),保存时会创建该目录;只影响之后新建立的工作区 —— 已有话题即使 /renew 也沿用原工作区,旧文件不会迁移。',\n placeholder: '/Users/you/.dsh/dsh-channels/workspaces',\n wide: true,\n },\n dmMode: { label: '私聊模式', help: '禁用后私聊消息不再触发会话;群聊与已绑定话题不受影响。' },\n requireMention: { label: '普通群需 @ 机器人', help: '已绑定的主题内始终不需要 @;关闭后普通群的每条消息都会触发。' },\n threadFallback: { label: '话题被拒时的兜底', help: 'degrade 降级为会话级路由;strict 直接让回复失败。' },\n welcomeOnboarding: { label: '首次私聊引导卡', help: '每个私聊会话第一次触达时发送一张命令与用法引导卡,只发一次。' },\n allowPermissionSwitch: { label: '允许飞书侧切换权限预设', help: '安全敏感:开启后话题所有者可用 /permissions 切换沙箱与审批预设。' },\n adminOpenId: {\n label: '管理员 open_id',\n help: '唯一管理员。私聊机器人发送 /userid 获取(ou_ 开头,不是 user_id/邮箱);留空 = 清除 Web 覆盖。guarded 模式下未配置会拒绝所有对话。',\n placeholder: 'ou_xxx',\n },\n accessMode: {\n label: '准入模式',\n help: 'guarded(默认):私聊仅管理员,群聊需群授权且发送者是管理员或已授权用户;open:应用可用范围内的任何人都能对话,不安全,仅调试。',\n },\n authRequestTtlDays: {\n label: '授权申请有效期(天)',\n help: '入群/用户授权申请卡的有效期,1–30 天;过期后卡片需重新触发申请(也可直接在「访问与授权」面板处理)。',\n min: 1,\n max: 30,\n step: 1,\n },\n acceptAttachments: { label: '接收并保存附件', help: '接收飞书图片、文件与合并转发记录,保存到工作区的 attachments/ 目录;模型不会自动读取,需要时用文件工具自行查看。关闭后附件消息不保存、不触发回复。' },\n workspaceRetention: { label: '过期工作区保留期', help: '超过所选天数没有活动的飞书工作区会进入下方「工作区清理」列表;「关闭」表示永不删除。DSH 会话日志不受影响。' },\n workspaceRetentionAuto: { label: '自动执行工作区清理', help: '开启后插件在启动时及每天执行一次过期工作区清理;关闭时只能在下方卡片手动清理。开启前请确认保留期已设置。' },\n provider: { label: '模型服务商', help: '留空 = 使用部署默认;例如 deepseek-official。', placeholder: '留空 = 部署默认' },\n model: { label: '模型 id', help: '留空 = 使用部署默认;例如 deepseek-v4-flash。', placeholder: '留空 = 部署默认' },\n reasoningEffort: { label: '推理档位', help: '模型声明的精确档位 id(如 high / max);留空 = 适配器默认。', placeholder: '留空 = 适配器默认' },\n agentPreset: { label: 'Agent 预设', help: '新会话使用的预设 id(如 standard);留空 = roster 默认。', placeholder: '留空 = roster 默认' },\n refreshMs: { label: '流式刷新间隔(ms)', help: '流式卡片的 patch 节流间隔,200–10000;需重连生效。', min: 200, max: 10_000, step: 100 },\n statusRefreshMs: { label: '状态行保活间隔(ms)', help: '无文本输出时活动状态行的计时刷新间隔,1000–60000;需重连生效。', min: 1_000, max: 60_000, step: 500 },\n loggerLevel: { label: 'SDK 日志级别', help: '官方 SDK 的日志级别;需重连生效。' },\n}\n\nconst GROUP_OF: Record<EditableKey, EditableFieldWire['group']> = {\n baseDir: 'workspace',\n workspaceRetention: 'workspace',\n workspaceRetentionAuto: 'workspace',\n dmMode: 'behavior',\n requireMention: 'behavior',\n threadFallback: 'behavior',\n welcomeOnboarding: 'behavior',\n allowPermissionSwitch: 'behavior',\n adminOpenId: 'behavior',\n accessMode: 'behavior',\n authRequestTtlDays: 'behavior',\n acceptAttachments: 'content',\n provider: 'model',\n model: 'model',\n reasoningEffort: 'model',\n agentPreset: 'model',\n refreshMs: 'streaming',\n statusRefreshMs: 'streaming',\n loggerLevel: 'streaming',\n}\n\n/** One descriptor per editable key, in `EDITABLE_KEYS` order (Host-driven form). */\nexport const FIELD_DESCRIPTORS: readonly EditableFieldWire[] = EDITABLE_KEYS.map(key => {\n const meta = FIELD_META[key]\n const options = SELECT_OPTIONS[key]\n return {\n key,\n label: meta.label,\n kind: BOOLEAN_KEYS.has(key) ? 'boolean' : options !== undefined ? 'select' : meta.min !== undefined ? 'number' : 'text',\n group: GROUP_OF[key],\n help: meta.help,\n ...(options === undefined ? {} : { options }),\n ...(meta.min === undefined ? {} : { min: meta.min }),\n ...(meta.max === undefined ? {} : { max: meta.max }),\n ...(meta.step === undefined ? {} : { step: meta.step }),\n ...(meta.placeholder === undefined ? {} : { placeholder: meta.placeholder }),\n ...(meta.wide === undefined ? {} : { wide: meta.wide }),\n restartRequired: RESTART_SET.has(key),\n }\n})\n\n/** Keys whose options are resolved from the live host catalog at panel time. */\ntype CatalogKey = 'provider' | 'model' | 'reasoningEffort' | 'agentPreset'\n\nconst CATALOG_KEYS = new Set<EditableKey>(['provider', 'model', 'reasoningEffort', 'agentPreset'])\n\nfunction isCatalogKey(key: EditableKey): key is CatalogKey {\n return CATALOG_KEYS.has(key)\n}\n\n/** Every group switched off: the form keeps its free-text controls. */\nexport function emptyCatalog(): CatalogWire {\n const none = { selectable: false, options: [] as FieldOptionWire[] }\n return {\n provider: { ...none },\n model: { ...none },\n reasoningEffort: { ...none },\n agentPreset: { ...none },\n }\n}\n\n/** Longest accepted catalog query value; anything longer is treated as absent. */\nconst MAX_CATALOG_QUERY_LENGTH = 200\n\n/** Trim one provider/model query value, dropping empty, hostile, or oversized input. */\nfunction cleanCatalogQuery(value: string | undefined): string | undefined {\n if (value === undefined) return undefined\n const trimmed = value.trim()\n if (trimmed === '' || trimmed.length > MAX_CATALOG_QUERY_LENGTH) return undefined\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(trimmed)) return undefined\n return trimmed\n}\n\n/**\n * Attach the live catalog to the four selector fields. A configured value the\n * host no longer advertises stays visible as an extra option so opening the\n * panel never silently rewrites the effective config.\n */\nexport function applyCatalog(\n fields: readonly EditableFieldWire[],\n catalog: CatalogWire,\n current: Partial<Record<CatalogKey, ConfigValue | undefined>>,\n): EditableFieldWire[] {\n return fields.map((field) => {\n if (!isCatalogKey(field.key)) return field\n const selector = catalog[field.key]\n if (!selector.selectable) return field\n const value = current[field.key]\n const options = typeof value === 'string' && value !== ''\n && !selector.options.some(option => option.value === value)\n ? withCurrentOption(selector.options, value)\n : [...selector.options]\n const next: EditableFieldWire = {\n ...field,\n kind: 'select',\n options,\n }\n if (selector.disabled === true) next.disabled = true\n if (selector.help !== undefined) next.help = selector.help\n return next\n })\n}\n\n/** Keep the leading inherit option first, then surface the unlisted current value. */\nfunction withCurrentOption(\n options: readonly FieldOptionWire[],\n value: string,\n): FieldOptionWire[] {\n const [inherit, ...rest] = options\n const extra: FieldOptionWire = { value, label: `${value}(当前配置,不在宿主机列表中)` }\n return inherit === undefined ? [extra] : [inherit, extra, ...rest]\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction formatDays(ms: number): string {\n return ms === 0 ? '关闭(不过期)' : `${Math.round(ms / 86_400_000)} 天`\n}\n\nfunction formatSeconds(ms: number): string {\n return `${Math.round(ms / 1_000)} 秒`\n}\n\nfunction formatOptional(value: string | number | undefined, suffix = ''): string {\n return value === undefined ? '未设置' : `${value}${suffix}`\n}\n\n/** Credential values are opaque strings; trim, cap length, reject control chars. */\nconst MAX_CREDENTIAL_LENGTH = 512\n\nexport function normalizeCredentialValue(raw: unknown): { ok: true; value: string } | { ok: false; message: string } {\n if (typeof raw !== 'string') return { ok: false, message: '必须是字符串' }\n const value = raw.trim()\n if (value === '') return { ok: false, message: '不能为空' }\n if (value.length > MAX_CREDENTIAL_LENGTH) return { ok: false, message: `长度不得超过 ${MAX_CREDENTIAL_LENGTH}` }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(value)) return { ok: false, message: '包含非法控制字符' }\n return { ok: true, value }\n}\n\n/** Longest accepted whitelist identifier / display name. */\nconst MAX_ACCESS_VALUE_LENGTH = 128\n\n/** Feishu user open_ids (`ou_` + URL-safe id). Identity is open_id only. */\nconst OPEN_ID_PATTERN = /^ou_[A-Za-z0-9_-]+$/\n\n/** Trim one whitelist identifier, rejecting empty, oversized, or hostile input. */\nexport function normalizeAccessId(\n raw: unknown,\n label: string,\n invalidFormat?: string,\n): { ok: true; value: string } | { ok: false; message: string } {\n if (typeof raw !== 'string') return { ok: false, message: `${label}必须是字符串` }\n const value = raw.trim()\n if (value === '') return { ok: false, message: `${label}不能为空` }\n if (value.length > MAX_ACCESS_VALUE_LENGTH) {\n return { ok: false, message: `${label}长度不得超过 ${MAX_ACCESS_VALUE_LENGTH}` }\n }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(value)) return { ok: false, message: `${label}包含非法控制字符` }\n if (invalidFormat !== undefined && !OPEN_ID_PATTERN.test(value)) {\n return { ok: false, message: invalidFormat }\n }\n return { ok: true, value }\n}\n\n/** Optional display name: trimmed; empty means \"unknown\" and is dropped. */\nexport function normalizeAccessName(\n raw: unknown,\n): { ok: true; value: string | undefined } | { ok: false; message: string } {\n if (raw === undefined || raw === null) return { ok: true, value: undefined }\n if (typeof raw !== 'string') return { ok: false, message: '名称必须是字符串' }\n const value = raw.trim()\n if (value === '') return { ok: true, value: undefined }\n if (value.length > MAX_ACCESS_VALUE_LENGTH) {\n return { ok: false, message: `名称长度不得超过 ${MAX_ACCESS_VALUE_LENGTH}` }\n }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(value)) return { ok: false, message: '名称包含非法控制字符' }\n return { ok: true, value }\n}\n\n/** Build the read-only advanced card from the effective config. */\nexport function readOnlyFields(config: FeishuConfig): ReadOnlyFieldWire[] {\n return [\n { key: 'routeTtlMs', label: '话题路由过期', value: formatDays(config.routeTtlMs), help: '超期话题的后续消息会被明确拒绝,而不是新建 Context-free Session。' },\n { key: 'approvalTimeoutMs', label: '审批卡片等待', value: formatSeconds(config.approvalTimeoutMs) },\n { key: 'questionTimeoutMs', label: '提问卡片等待', value: formatSeconds(config.questionTimeoutMs) },\n { key: 'maxTokens', label: '单次输出上限', value: formatOptional(config.maxTokens, ' tokens'), help: '留空/未设置时使用模型适配器默认值。' },\n ]\n}\n\n/** Assemble the status half of the panel payload. */\nfunction buildStatus(target: SettingsTarget, overlayPath: string): ChannelStatusWire {\n const health = target.health()\n const connection = health.connection\n return {\n running: target.isRunning(),\n level: health.level,\n startedAt: target.startedAtMs(),\n uptimeMs: target.uptimeMs(),\n ...(connection === undefined ? {} : {\n connection: {\n state: connection.state,\n lastConnectTime: connection.lastConnectTime,\n nextConnectTime: connection.nextConnectTime,\n reconnectAttempts: connection.reconnectAttempts,\n },\n }),\n ...(health.botOpenIdTail === undefined ? {} : { botOpenIdTail: health.botOpenIdTail }),\n routeCount: health.routeCount,\n liveAgentCount: health.liveAgentCount,\n inflightTurnCount: health.inflightTurnCount,\n ...(target.startError() === undefined ? {} : { lastStartError: target.startError() }),\n reasons: health.reasons,\n version: DSH_CHANNELS_VERSION,\n overlayPath,\n busy: target.isRestarting(),\n }\n}\n\n/**\n * Stateful panel facade over one service + one overlay file. All mutations are\n * validated here; the HTTP layer only parses the transport. The optional\n * [`SettingsCatalog`] turns the four model selectors into host-driven choice\n * lists; without it (headless tests, host without LLM services) they stay\n * free-text controls.\n */\nexport class SettingsSurface {\n constructor(\n private readonly target: SettingsTarget,\n private readonly store: OverlayStore,\n private readonly logger?: OverlayLogger,\n private readonly catalog?: SettingsCatalog,\n ) {}\n\n /** Wire status only (used by the client's 5 s poll). */\n async status(): Promise<ChannelStatusWire> {\n return buildStatus(this.target, this.store.file)\n }\n\n /**\n * Resolve the selector catalog for one route (the client re-queries it when\n * the draft provider/model changes). A broken lookup degrades to the\n * free-text form instead of failing the panel.\n */\n async catalogFor(provider?: string, model?: string): Promise<CatalogWire> {\n if (this.catalog === undefined) return emptyCatalog()\n try {\n return await this.catalog.catalog(cleanCatalogQuery(provider), cleanCatalogQuery(model))\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 选择目录加载失败:%s', messageOf(error))\n return emptyCatalog()\n }\n }\n\n /** Status + editable config with provenance + read-only facts. */\n async panel(): Promise<PanelWire> {\n const base = this.target.baseConfigSnapshot()\n const effective = this.target.configSnapshot()\n const overlay = await this.store.load()\n const running = this.target.runningConfigSnapshot()\n const overriddenKeys = EDITABLE_KEYS.filter(key => overlay[key] !== undefined)\n const restartPendingKeys = this.target.isRunning() && running !== undefined\n ? RESTART_KEYS.filter(key => effective[key] !== running[key])\n : []\n const catalog = await this.catalogFor(effective.provider, effective.model)\n const fields = applyCatalog(FIELD_DESCRIPTORS, catalog, {\n provider: effective.provider,\n model: effective.model,\n reasoningEffort: effective.reasoningEffort,\n agentPreset: effective.agentPreset,\n })\n\n let credentialStates: { appId: CredentialInfo; appSecret: CredentialInfo } = {\n appId: { configured: false, writable: false },\n appSecret: { configured: false, writable: false },\n }\n try {\n credentialStates = await this.target.credentialStates()\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 凭据状态读取失败:%s', messageOf(error))\n }\n const toWire = (ref: string, state: CredentialInfo): CredentialRefWire => ({\n ref,\n configured: state.configured,\n writable: state.writable === true,\n ...(state.source === undefined ? {} : { source: state.source }),\n })\n const credentials: PanelConfigWire['credentials'] = {\n appId: toWire(effective.appId, credentialStates.appId),\n appSecret: toWire(effective.appSecret, credentialStates.appSecret),\n }\n\n // The cleanup preview is derived from live workspace/route state; a broken\n // seam must not break the whole panel, so it degrades to zero counts + error.\n let cleanup: CleanupPreviewWire\n try {\n cleanup = await this.target.cleanupPreview()\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 工作区清理预览失败:%s', messageOf(error))\n cleanup = {\n retention: effective.workspaceRetention,\n auto: effective.workspaceRetentionAuto,\n workspaces: 0,\n candidates: 0,\n candidateBytes: 0,\n error: messageOf(error),\n }\n }\n\n // The access snapshot comes from in-memory maps, but a broken seam must\n // still not take the whole panel down: degrade to an empty, fail-closed\n // view (the whitelist mutations will report their own errors).\n let access: AccessWire\n try {\n access = this.target.accessSnapshot()\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 访问授权快照读取失败:%s', messageOf(error))\n access = {\n mode: effective.accessMode,\n admin: { configured: false },\n groups: [],\n users: [],\n requests: [],\n }\n }\n\n return {\n status: buildStatus(this.target, this.store.file),\n config: {\n base: pickEditable(base) as Record<string, ConfigValue | undefined>,\n effective: pickEditable(effective) as Record<string, ConfigValue | undefined>,\n overlay: overlay as EditableValues,\n overriddenKeys,\n restartPendingKeys,\n fields,\n readOnly: readOnlyFields(effective),\n credentials,\n },\n cleanup,\n access,\n }\n }\n\n /** Validate + persist a partial edit; live fields take effect immediately. */\n async save(values: EditableValues): Promise<SaveConfigResponse> {\n const unknown = Object.keys(values).filter(key => !(EDITABLE_KEYS as readonly string[]).includes(key))\n if (unknown.length > 0) {\n return { ok: false, error: `未知配置项:${unknown.join(', ')}` }\n }\n\n const raw: Record<string, unknown> = {}\n const clearedKeys: EditableKey[] = []\n for (const key of EDITABLE_KEYS) {\n const value = values[key]\n if (value === undefined) continue\n if (typeof value === 'string' && value === '' && OPTIONAL_TEXT_KEYS.has(key)) {\n clearedKeys.push(key)\n continue\n }\n raw[key] = value\n }\n\n const parsed = parseOverlay(raw)\n if (!parsed.ok) {\n const issues: FieldIssueWire[] = parsed.issues.map(issue => ({\n key: issue.path,\n message: issue.message,\n }))\n return { ok: false, error: '配置校验失败', issues }\n }\n\n // A workspace root typed into the panel must be usable right now: create\n // and probe it before anything is persisted, so a typo (or a path that is\n // an existing file / unwritable) fails here rather than as a session\n // creation failure card inside Feishu.\n if (parsed.patch.baseDir !== undefined) {\n const problem = await prepareWorkspaceBaseDir(parsed.patch.baseDir)\n if (problem !== undefined) {\n return {\n ok: false,\n error: '工作区目录不可用',\n issues: [{ key: 'baseDir', message: problem }],\n }\n }\n }\n\n let merged: OverlayPatch\n try {\n merged = { ...await this.store.load() }\n for (const key of clearedKeys) delete merged[key]\n Object.assign(merged, parsed.patch)\n } catch (error) {\n return { ok: false, error: `写入覆盖配置失败:${messageOf(error)}` }\n }\n\n // The host resolves a request route as a provider/model PAIR: a lone\n // provider (or lone model) leaves the session without a complete route,\n // so an edit to either side must end with both set or both cleared.\n if (values.provider !== undefined || values.model !== undefined) {\n const prospective = { ...this.target.baseConfigSnapshot(), ...merged }\n const hasProvider = (prospective.provider ?? '') !== ''\n const hasModel = (prospective.model ?? '') !== ''\n if (hasProvider !== hasModel) {\n return {\n ok: false,\n error: '模型服务商与模型 id 必须同时配置,或都留空沿用部署默认',\n issues: [hasProvider\n ? { key: 'model', message: '已选择模型服务商,还需要选择模型 id(或把两者都留空)' }\n : { key: 'provider', message: '已选择模型 id,还需要选择模型服务商(或把两者都留空)' }],\n }\n }\n }\n\n try {\n await this.store.save(merged)\n } catch (error) {\n return { ok: false, error: `写入覆盖配置失败:${messageOf(error)}` }\n }\n this.target.applyOverlay(merged)\n return { ok: true, panel: await this.panel(), clearedKeys }\n }\n\n /** Drop every Web override and fall back to the profile config. */\n async clear(): Promise<ClearConfigResponse> {\n try {\n await this.store.clear()\n } catch (error) {\n return { ok: false, error: `清除覆盖配置失败:${messageOf(error)}` }\n }\n this.target.applyOverlay({})\n return { ok: true, panel: await this.panel() }\n }\n\n /** Write provided credential values through the host seam (never echoed back). */\n async saveCredentials(values: SaveCredentialsRequest): Promise<SaveCredentialsResponse> {\n const writes: Array<{ field: CredentialField; value: string }> = []\n const issues: FieldIssueWire[] = []\n for (const field of ['appId', 'appSecret'] as const) {\n const raw = values[field]\n if (raw === undefined) continue\n const normalized = normalizeCredentialValue(raw)\n if (!normalized.ok) {\n issues.push({ key: field, message: normalized.message })\n continue\n }\n writes.push({ field, value: normalized.value })\n }\n if (issues.length > 0) return { ok: false, error: '凭据校验失败', issues }\n if (writes.length === 0) return { ok: false, error: '请至少填写一项凭据(App ID 或 App Secret)' }\n\n try {\n for (const write of writes) await this.target.setCredential(write.field, write.value)\n } catch (error) {\n return { ok: false, error: messageOf(error) }\n }\n return this.finishCredentialChange('save')\n }\n\n /** Remove the stored value behind one credential reference. */\n async clearCredential(field: CredentialField): Promise<ClearCredentialResponse> {\n if (field !== 'appId' && field !== 'appSecret') {\n return { ok: false, error: '未知的凭据字段' }\n }\n try {\n await this.target.clearCredential(field)\n } catch (error) {\n return { ok: false, error: messageOf(error) }\n }\n return this.finishCredentialChange('clear')\n }\n\n /**\n * Credential values are resolved on connect. When the channel is stopped,\n * retry immediately (nothing to interrupt); when it is running, ask the user\n * to reconnect. A failed retry still reports the save as successful.\n */\n private async finishCredentialChange(kind: 'save' | 'clear'): Promise<SaveCredentialsResponse> {\n if (this.target.isRunning()) {\n return { ok: true, panel: await this.panel(), reconnect: 'pending' }\n }\n if (kind === 'clear') {\n return { ok: true, panel: await this.panel(), reconnect: 'not-needed' }\n }\n try {\n await this.target.restart()\n return { ok: true, panel: await this.panel(), reconnect: 'auto' }\n } catch (error) {\n return {\n ok: true,\n panel: await this.panel(),\n reconnect: 'auto',\n autoReconnectError: messageOf(error),\n }\n }\n }\n\n /** Close and reopen the Feishu long connection with the latest config. */\n async reconnect(): Promise<ReconnectResponse> {\n try {\n await this.target.restart()\n } catch (error) {\n return { ok: false, error: `重连失败:${messageOf(error)}` }\n }\n return { ok: true, panel: await this.panel() }\n }\n\n /** Read the current admission snapshot (mode, administrator, whitelists, requests). */\n async access(): Promise<AccessWire> {\n return this.target.accessSnapshot()\n }\n\n /** Authorize one group from the panel. Validation happens before the write. */\n async saveAccessGroup(chatId: unknown, name?: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(chatId, '群 chat_id')\n if (!id.ok) return { ok: false, error: id.message }\n const label = normalizeAccessName(name)\n if (!label.ok) return { ok: false, error: label.message }\n return this.mutateAccess(() => this.target.addAccessGroup(id.value, label.value))\n }\n\n /** Revoke one group authorization (immediate: the next message re-evaluates). */\n async removeAccessGroup(chatId: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(chatId, '群 chat_id')\n if (!id.ok) return { ok: false, error: id.message }\n return this.mutateAccess(() => this.target.removeAccessGroup(id.value))\n }\n\n /** Authorize one user `open_id`; `ou_` prefix is required (not user_id/email). */\n async saveAccessUser(openId: unknown, name?: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(openId, '用户 open_id', '用户 open_id 必须形如 ou_xxx(不是 user_id/邮箱)')\n if (!id.ok) return { ok: false, error: id.message }\n const label = normalizeAccessName(name)\n if (!label.ok) return { ok: false, error: label.message }\n return this.mutateAccess(() => this.target.addAccessUser(id.value, label.value))\n }\n\n /** Revoke one user authorization (immediate: the next message re-evaluates). */\n async removeAccessUser(openId: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(openId, '用户 open_id', '用户 open_id 必须形如 ou_xxx(不是 user_id/邮箱)')\n if (!id.ok) return { ok: false, error: id.message }\n return this.mutateAccess(() => this.target.removeAccessUser(id.value))\n }\n\n /**\n * Settle one pending request. Shares the target-side resolver with the DM\n * card, so the panel and the card can never disagree on the outcome.\n */\n async resolveAccessRequest(token: unknown, decision: unknown): Promise<AccessMutationResponse> {\n const normalized = normalizeAccessId(token, '申请 token')\n if (!normalized.ok) return { ok: false, error: normalized.message }\n if (decision !== 'allow' && decision !== 'deny') {\n return { ok: false, error: 'decision 必须是 allow 或 deny' }\n }\n return this.mutateAccess(() => this.target.resolveAccessRequest(normalized.value, decision))\n }\n\n /** Run one whitelist mutation, folding thrown seam errors into the wire type. */\n private async mutateAccess(\n mutation: () => Promise<AccessMutationResponse>,\n ): Promise<AccessMutationResponse> {\n try {\n return await mutation()\n } catch (error) {\n return { ok: false, error: messageOf(error) }\n }\n }\n\n /**\n * Run the workspace-retention cleanup once. Irreversible deletions are the\n * target's responsibility; the surface only reports the outcome and returns\n * a refreshed panel (its cleanup preview then reflects the remaining workspaces).\n */\n async runCleanup(): Promise<CleanupResponse> {\n let result: CleanupRunWire\n try {\n result = await this.target.runWorkspaceCleanup()\n } catch (error) {\n return { ok: false, error: `清理失败:${messageOf(error)}` }\n }\n return { ok: true, result, panel: await this.panel() }\n }\n}\n\nexport { PANEL_COPY }\n","/**\n * Web 设置面板的 Host 侧选择器目录:把宿主 LLM 服务注册的 provider / model\n * / 推理档位,以及 agent-preset roster 折叠成面板可渲染的选项列表。\n *\n * 目录只用于「选择」:任何查询失败(服务未挂载、方法缺失、超时、抛错)都\n * 降级为 `selectable: false`,客户端据此退回原有自由文本输入框,绝不让\n * 面板加载依赖宿主的网络状态。成功结果按短 TTL 缓存,避免用户来回切换\n * provider/model 时反复查询适配器。\n *\n * @module dsh-channels/settings/catalog\n */\n\nimport type {\n AgentDefaultModel,\n AgentPreset,\n AgentPresets,\n HostContext,\n LlmModelInfo,\n LlmProviderInfo,\n} from '../dsh-types.ts'\nimport type { CatalogWire, FieldOptionWire, SelectCatalogWire } from './contract.ts'\n\n/** 目录查询的硬上限:面板加载不能被宿主的模型发现/连接拖住。 */\nconst LOOKUP_TIMEOUT_MS = 4_000\n\n/** 成功查询的缓存时长(毫秒)。 */\nconst CACHE_TTL_MS = 30_000\n\nexport type CatalogWarn = (message: string, ...args: unknown[]) => void\n\n/** Host capability used by the settings panel to populate its selectors. */\nexport interface SettingsCatalog {\n /**\n * Resolve the selector catalog for one configured route.\n * @param provider - configured provider route (absent = inherit deployment default).\n * @param model - configured model id (absent = inherit deployment default).\n * @returns selector options; groups the host cannot enumerate are `selectable: false`.\n */\n catalog(provider?: string, model?: string): Promise<CatalogWire>\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Resolve `work` or `undefined` after `ms`, so a hanging adapter cannot stall the panel. */\nasync function within<T>(work: Promise<T>, ms: number): Promise<T | undefined> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => { resolve(undefined) }, ms)\n timer.unref?.()\n })\n try {\n return await Promise.race([work, timeout])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\n/** Leading \"inherit / clear\" choice every selector gets. */\nfunction inheritOption(label: string): FieldOptionWire {\n return { value: '', label }\n}\n\n/** `Display name(id)` when the host publishes both, else the id alone. */\nfunction labelledOption(id: string, name: string | undefined): FieldOptionWire {\n const display = name === undefined ? '' : name.trim()\n return { value: id, label: display === '' || display === id ? id : `${display}(${id})` }\n}\n\n/** Non-empty display text, otherwise undefined. */\nfunction textOf(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed === '' ? undefined : trimmed\n}\n\n/**\n * Build the catalog over one live host context.\n * @param ctx - host context carrying `llm` and the optional preset/default-model services.\n * @param warn - optional diagnostic sink for failed lookups.\n * @returns the catalog facade.\n */\nexport function createHostCatalog(ctx: HostContext, warn?: CatalogWarn): SettingsCatalog {\n const llm = ctx.llm\n const presets = ctx.get?.('agentPresets') as AgentPresets | undefined\n const defaultModel = ctx.get?.('agentDefaultModel') as AgentDefaultModel | undefined\n const cache = new Map<string, { at: number; value: unknown }>()\n\n /** Cache a successful lookup; failures are re-tried on the next panel refresh. */\n async function cached<T>(key: string, load: () => Promise<T> | T): Promise<T | undefined> {\n const hit = cache.get(key)\n if (hit !== undefined && Date.now() - hit.at < CACHE_TTL_MS) return hit.value as T\n try {\n const value = await within(Promise.resolve(load()), LOOKUP_TIMEOUT_MS)\n if (value === undefined) return undefined\n cache.set(key, { at: Date.now(), value })\n return value\n } catch (error) {\n warn?.('dsh-channels: 读取 %s 选择目录失败:%s', key, messageOf(error))\n return undefined\n }\n }\n\n /** Registered provider routes, `undefined` when the deployment exposes none. */\n async function providerOptions(): Promise<FieldOptionWire[] | undefined> {\n if (typeof llm.listProviders !== 'function') return undefined\n const rows = await cached<readonly LlmProviderInfo[]>('providers', () => llm.listProviders!())\n if (rows === undefined) return undefined\n const options: FieldOptionWire[] = []\n const seen = new Set<string>()\n for (const row of rows) {\n const id = textOf((row as { id?: unknown } | undefined)?.id)\n if (id === undefined || seen.has(id)) continue\n seen.add(id)\n options.push(labelledOption(id, textOf((row as { name?: unknown }).name)))\n }\n return options.length === 0 ? undefined : options\n }\n\n /** Models advertised for one provider route, `undefined` when it has no catalog. */\n async function modelOptions(provider: string): Promise<FieldOptionWire[] | undefined> {\n if (provider === '' || typeof llm.listModels !== 'function') return undefined\n const rows = await cached<readonly LlmModelInfo[]>(`models:${provider}`, () => llm.listModels!(provider))\n if (rows === undefined) return undefined\n const options: FieldOptionWire[] = []\n const seen = new Set<string>()\n for (const row of rows) {\n const id = textOf((row as { id?: unknown } | undefined)?.id)\n if (id === undefined || seen.has(id)) continue\n seen.add(id)\n options.push(labelledOption(id, textOf((row as { name?: unknown }).name)))\n }\n return options.length === 0 ? undefined : options\n }\n\n /** Adapter metadata for one exact route; `undefined` when it cannot be resolved. */\n async function modelInfo(provider: string, model: string): Promise<LlmModelInfo | undefined> {\n if (provider === '' || model === '') return undefined\n return await cached<LlmModelInfo>(`model-info:${provider}/${model}`, () => llm.resolveModelInfo(provider, model))\n }\n\n /** Declared preset rows, `undefined` when the roster has no `list()` or no rows. */\n async function presetRows(): Promise<readonly AgentPreset[] | undefined> {\n if (typeof presets?.list !== 'function') return undefined\n const rows = await cached<readonly AgentPreset[]>('presets', () => presets!.list!())\n if (rows === undefined || rows.length === 0) return undefined\n const filtered = rows.filter(row => textOf((row as { id?: unknown } | undefined)?.id) !== undefined)\n return filtered.length === 0 ? undefined : filtered\n }\n\n /** Roster default preset id, when the implementation publishes it. */\n function defaultPresetId(): string | undefined {\n try {\n return textOf(presets?.defaultId)\n } catch {\n return undefined\n }\n }\n\n /** Deployment default model selection, when the host exposes `agentDefaultModel`. */\n function deploymentDefault(): CatalogWire['deploymentDefault'] | undefined {\n try {\n const selection = defaultModel?.currentSelection?.()\n const provider = textOf(selection?.provider)\n const model = textOf(selection?.model)\n if (provider === undefined || model === undefined) return undefined\n const reasoningEffort = textOf(selection?.reasoningEffort)\n return { provider, model, ...(reasoningEffort === undefined ? {} : { reasoningEffort }) }\n } catch {\n return undefined\n }\n }\n\n /** Selector for the configured provider route. */\n function providerWire(\n providers: readonly FieldOptionWire[] | undefined,\n deployment: CatalogWire['deploymentDefault'],\n ): SelectCatalogWire {\n if (providers === undefined) return { selectable: false, options: [] }\n const label = deployment?.provider === undefined\n ? '留空 = 部署默认'\n : `留空 = 部署默认(${deployment.provider})`\n return { selectable: true, options: [inheritOption(label), ...providers] }\n }\n\n /** Selector for the configured model id. */\n function modelWire(\n models: readonly FieldOptionWire[] | undefined,\n provider: string,\n deployment: CatalogWire['deploymentDefault'],\n providers: readonly FieldOptionWire[] | undefined,\n ): SelectCatalogWire {\n const label = deployment?.model === undefined\n ? '留空 = 部署默认'\n : `留空 = 部署默认(${deployment.model})`\n if (models === undefined) {\n // Provider selectable but none configured yet: keep the model selector\n // visible and disabled so the dependency is discoverable. A provider the\n // adapter cannot enumerate instead falls back to free text.\n if (providers !== undefined && provider === '') {\n return {\n selectable: true,\n options: [inheritOption(label)],\n disabled: true,\n help: '先选择模型服务商,再选择模型 id。留空表示沿用部署默认。',\n }\n }\n return { selectable: false, options: [] }\n }\n const select: SelectCatalogWire = {\n selectable: true,\n options: [inheritOption(label), ...models],\n }\n if (provider === '') {\n select.disabled = true\n select.help = '先选择模型服务商,再选择模型 id。留空表示沿用部署默认。'\n }\n return select\n }\n\n /** Selector for the configured reasoning effort. */\n function effortWire(\n info: LlmModelInfo | undefined,\n provider: string,\n model: string,\n providers: readonly FieldOptionWire[] | undefined,\n ): SelectCatalogWire {\n // 无法枚举 provider 时,整条选择链都退回自由文本输入。\n if (providers === undefined) return { selectable: false, options: [] }\n if (provider === '' || model === '') {\n return {\n selectable: true,\n options: [inheritOption('留空 = 适配器默认')],\n disabled: true,\n help: '先选择模型服务商与模型 id,再选择推理档位。',\n }\n }\n // 路由无法解析(适配器报错/超时):退回自由文本,保留手填能力。\n if (info === undefined) return { selectable: false, options: [] }\n const efforts = (info.reasoning?.efforts ?? [])\n .filter(effort => textOf(effort.id) !== undefined)\n .map(effort => labelledOption(effort.id, textOf(effort.name)))\n if (efforts.length === 0) {\n return {\n selectable: true,\n options: [inheritOption('留空 = 适配器默认')],\n disabled: true,\n help: '当前模型没有声明可选推理档位,只能使用适配器默认。',\n }\n }\n const defaultEffort = textOf(info.reasoning?.defaultEffort)\n return {\n selectable: true,\n options: [\n inheritOption(defaultEffort === undefined\n ? '留空 = 适配器默认'\n : `留空 = 适配器默认(${defaultEffort})`),\n ...efforts,\n ],\n }\n }\n\n /** Selector for the agent preset id. */\n function presetWire(\n rows: readonly AgentPreset[] | undefined,\n defaultId: string | undefined,\n ): SelectCatalogWire {\n if (rows === undefined) return { selectable: false, options: [] }\n const options = rows.map((row) => {\n const id = textOf(row.id)!\n const option = labelledOption(id, textOf(row.name))\n if (row.broken === undefined) return option\n return { ...option, label: `${option.label}(不可用)` }\n })\n return {\n selectable: true,\n options: [\n inheritOption(defaultId === undefined ? '留空 = roster 默认' : `留空 = roster 默认(${defaultId})`),\n ...options,\n ],\n }\n }\n\n return {\n async catalog(provider?: string, model?: string): Promise<CatalogWire> {\n const wantedProvider = textOf(provider) ?? ''\n const wantedModel = textOf(model) ?? ''\n const deployment = deploymentDefault()\n const defaultPreset = defaultPresetId()\n\n const [providers, models, info, rows] = await Promise.all([\n providerOptions(),\n modelOptions(wantedProvider),\n modelInfo(wantedProvider, wantedModel),\n presetRows(),\n ])\n\n return {\n provider: providerWire(providers, deployment),\n model: modelWire(models, wantedProvider, deployment, providers),\n reasoningEffort: effortWire(info, wantedProvider, wantedModel, providers),\n agentPreset: presetWire(rows, defaultPreset),\n ...(deployment === undefined ? {} : { deploymentDefault: deployment }),\n ...(defaultPreset === undefined ? {} : { defaultPresetId: defaultPreset }),\n }\n },\n }\n}\n","/**\n * dsh-channels — DeepSeek Harness 飞书渠道插件。\n *\n * 一个 dsh 实例对应一个飞书机器人;用户通过私聊、群聊 @、话题群 @ 三种方式\n * 发起会话。每条触发消息:立即加 “Typing” 表情 → 以话题形式回复(创建话题)\n * → 绑定 DSH session → 卡片 2.0 流式回复(推理折叠面板 + 富文本正文 + 两行\n * session 信息,默认 500ms 刷新)→ 流式结束移除 Typing,成功更新为\n * “DONE”,失败显示结构化错误并更新为 “No”。\n *\n * 工作区归属:私聊一人一工作区;群聊中每个用户在该群拥有独立工作区\n * (目录即工作区,会话以 cwd 落盘)。\n *\n * @module dsh-channels\n */\n\nimport type { HostContext } from './dsh-types.ts'\nimport { FeishuService } from './service.ts'\nimport { configSchema, resolveConfig } from './config.ts'\nimport type { FeishuConfig } from './config.ts'\nimport { defaultChannelFactory } from './feishu-channel.ts'\nimport type { ChannelFactory } from './feishu-channel.ts'\nimport { OverlayStore, defaultOverlayPath } from './settings/overlay.ts'\nimport { mountSettingsRoutes } from './settings/routes.ts'\nimport { SettingsSurface } from './settings/surface.ts'\nimport { createHostCatalog } from './settings/catalog.ts'\n\nexport type * from './config.ts'\nexport type * from './cards.ts'\nexport type * from './domain.ts'\nexport type * from './feishu-channel.ts'\nexport type * from './approvals.ts'\nexport type * from './commands.ts'\nexport type * from './health.ts'\nexport type * from './session-commands.ts'\nexport type * from './session-export.ts'\nexport type * from './stats.ts'\nexport type * from './questions.ts'\nexport type * from './settings/contract.ts'\nexport * from './access/index.ts'\nexport type { AccessGroupRecord, AccessRequestRecord, AccessUserRecord } from './domain.ts'\nexport { CATALOG_ROUTE, CLEANUP_ROUTE, CONFIG_ROUTE, CREDENTIALS_ROUTE, EDITABLE_KEYS, PANEL_COPY, PANEL_ROUTE, RECONNECT_ROUTE, RESTART_KEYS, STATUS_ROUTE } from './settings/contract.ts'\nexport { defaultOverlayPath, mergeOverlay, normalizeOverlay, OverlayStore, parseOverlay, pickEditable, resolveDshHome } from './settings/overlay.ts'\nexport { applyCatalog, emptyCatalog, FIELD_DESCRIPTORS, normalizeCredentialValue, readOnlyFields, SettingsSurface } from './settings/surface.ts'\nexport type { SettingsTarget } from './settings/surface.ts'\nexport { createHostCatalog } from './settings/catalog.ts'\nexport type { CatalogWarn, SettingsCatalog } from './settings/catalog.ts'\nexport { isSameOriginRequest, mountSettingsRoutes, registerSettingsRoutes } from './settings/routes.ts'\nexport type { HostWebServer } from './settings/routes.ts'\nexport type { ApprovalOutcome, ApprovalRequest, ApprovalAnswererListener, AssistantStreamFrame, AskUserQuestionAnswer, AskUserQuestionAnswerItem, AskUserQuestionItem, AskUserQuestionRequestEvent, UserQuestionAnswererListener, AgentCancelCause, SessionStatsView, TokenUsageView, ContextPressureView, SessionProjections } from './dsh-types.ts'\nexport { buildCard, buildInterruptionCard, cardJsonBytes, CARD_MAX_BYTES, CARD_TARGET_BYTES, CARD_PANEL_RESERVE_BYTES, REASONING_MAX_BYTES, INTERRUPTION_NOTICE, MAX_TABLES_PER_CARD, PANEL_TITLES, PANEL_CHEVRON_TOKEN, segmentCardContent, splitBodyForCard, stripTables, tableBlocks, TRUNCATION_SUFFIX, buildApprovalCard, buildApprovalSettledCard, buildQuestionCard, buildQuestionSettledCard, MAX_QUESTION_OPTIONS } from './cards.ts'\nexport { ApprovalAnswerer } from './approvals.ts'\nexport { QuestionAnswerer } from './questions.ts'\nexport { armModelCommand, buildCommandResultCard, CHANNEL_COMMAND_NAMES, commandResultCardKind, isSupportedCommand, parseSlashCommand, registerChannelCommands, registerModelCommand, RouteOperationQueue, SUPPORTED_COMMAND_NAMES, supportedCommandHelp, supportedCommandList } from './commands.ts'\nexport { buildHealthCard, countFiberStates, foldHealthSnapshot, HEALTH_DEPENDENCY_NAMES, maskOpenIdTail } from './health.ts'\nexport { buildCompactComparisonCard, buildRenewCard, buildSessionHistoryCard, buildSessionStatusCard, buildWorkDirCard, compactFailureContext, querySessionHistory, resolveWorkDir, SESSION_HISTORY_DISPLAY_LIMIT } from './session-commands.ts'\nexport { createLogicalSessionExport, DSH_CHANNELS_VERSION, LOGICAL_EXPORT_FORMAT, LOGICAL_EXPORT_FORMAT_VERSION, serializeLogicalSession } from './session-export.ts'\nexport { addUsage, applyProjectionViews, averageDecodeTokensPerSecond, averageTtftMs, cacheHitPct, EMPTY_TOTALS, foldAll, foldEvent, foldSessionStats, measureSessionContext, readProjectionViews, usageFromBuckets } from './stats.ts'\nexport { FeishuService } from './service.ts'\nexport { TurnStreamer } from './streamer.ts'\nexport { ensureWorkspace, stagingPathFor, topicTokenFor, workspacePathFor, workspaceTitleFor } from './workspaces.ts'\nexport { accessGroupRecordSchema, accessRequestRecordSchema, accessUserRecordSchema, appendSessionHistory, chatRouteKey, expiredRouteRecordSchema, feishuDomain, normalizeRouteRecord, normalizeSessionHistory, routeRecordSchema, sessionHistoryEntrySchema, inflightSnapshotSchema, processedRecordSchema } from './domain.ts'\n\n/** Cordis function-plugin name. */\nexport const name = 'channels'\n\n/** Host services this plugin consumes (all resolved through ctx).\n *\n * `credentials` is listed even though the plugin could fall back to the\n * process environment: at boot the credentials provider may not be visible\n * yet (a race observed in real profiles), and inject makes the loader wait\n * for it. `llm` / `tokenMeter` feed the card's context line and ship with\n * every dsh-base profile.\n */\nexport const inject = ['agents', 'commands', 'workspaceRegistry', 'storageDomain', 'credentials', 'llm', 'tokenMeter'] as const\n\n/**\n * Cordis Standard Schema declaration for the plugin config.\n *\n * The loader validates (and default-fills) the raw composition config through\n * `Config['~standard'].validate()` *before* `apply()` runs, so a malformed\n * `cordis.patch.yml` entry fails at load time with a cordis `ValidationError`\n * instead of surfacing from inside the async service start. `apply()` keeps\n * its `resolveConfig()` fallback for direct invocation (unit tests, embedded\n * hosts) where config has not been pre-validated.\n */\nexport const Config = configSchema\n\nexport interface FeishuPluginOptions extends FeishuConfig {\n /** Test seam: substitute a channel implementation. */\n channelFactory?: ChannelFactory\n}\n\n/**\n * Install the bridge. Credentials stay references here — the actual secrets\n * resolve through `ctx.credentials` (env > `$DSH_HOME/.credentials.yaml`\n * > `.env`) when `FeishuService.start()` builds the channel.\n */\nexport function apply(ctx: HostContext, config?: unknown): void {\n const resolved = resolveConfig(config)\n const factory: ChannelFactory = (config as FeishuPluginOptions | undefined)?.channelFactory\n ?? defaultChannelFactory\n // Web settings overlay (optional file; degrades to \"no overrides\").\n const overlay = new OverlayStore(\n resolved.settingsFile ?? defaultOverlayPath(),\n { warn: (message, ...args) => ctx.logger?.warn?.(message, ...args) },\n )\n const service = new FeishuService(ctx, resolved, factory, undefined, overlay)\n // Settings routes ride the host's web server when this deployment has one.\n // The catalog resolves the panel's model selectors from live host services.\n const warn = (message: string, ...args: unknown[]): void => { ctx.logger?.warn?.(message, ...args) }\n mountSettingsRoutes(ctx, new SettingsSurface(service, overlay, { warn }, createHostCatalog(ctx, warn)))\n ctx.effect(() => {\n void service.boot().catch((error: unknown) => {\n ctx.logger?.error?.('dsh-channels failed to start: %s', String(error))\n // Critical lifecycle line duplicated to the console: cordis logger\n // messages emitted before a web exporter attaches are buffered and may\n // never reach stdout, which makes real-device debugging guesswork.\n console.error('[dsh-channels] failed to start:', error)\n })\n return () => service.shutdown()\n }, 'dsh-channels')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["addressedToBot","ALL_COMMANDS","messageOf","OPTIONAL_TEXT_KEYS","fs","messageOf","messageOf"],"sources":["../src/access/policy.ts","../src/access/controller.ts","../src/cards/primitives.ts","../src/access/cards.ts","../src/access/requests.ts","../src/access/store.ts","../src/cards/approval.ts","../src/interactions/pending.ts","../src/interactions/approvals.ts","../src/cards/question.ts","../src/interactions/questions.ts","../src/cards/streaming.ts","../src/commands/registry.ts","../src/commands.ts","../src/domain.ts","../src/cleanup.ts","../src/session/routes.ts","../src/session/repair.ts","../src/feishu/agent-context.ts","../src/feishu-channel.ts","../src/feishu/inbound-attachments.ts","../src/session/request-policy.ts","../src/workspaces.ts","../src/session/manager.ts","../src/feishu/delivery.ts","../src/routing/inbound.ts","../src/host/services.ts","../src/health.ts","../src/stats.ts","../src/session-commands.ts","../src/session-export.ts","../src/streamer.ts","../src/service.ts","../src/config.ts","../src/settings/contract.ts","../src/settings/overlay.ts","../src/settings/routes.ts","../src/settings/surface.ts","../src/settings/catalog.ts","../src/settings/directory-picker.ts","../src/index.ts"],"sourcesContent":["/**\n * Pure admission policy for inbound Feishu traffic.\n *\n * The policy deliberately has no I/O: it takes one normalized subject, the\n * relevant configuration slice, and a snapshot of the durable authorization\n * tables, and returns an allow/deny decision. The controller turns a denial\n * into the user-facing behavior (fixed private-chat notice, silent drop,\n * authorization request card), which keeps the security-critical matrix\n * cheap to unit test.\n *\n * Access model:\n * - the administrator may start/continue a conversation in any private chat\n * or any group, regardless of the whitelists;\n * - private chats: non-administrators are refused;\n * - group chats: non-administrators need the group to be authorized AND\n * their open_id to be on the global user whitelist.\n *\n * @module dsh-channels/access/policy\n */\n\nimport type { FeishuConfig } from '../config.ts'\n\n/** Chat kinds this policy decides about (matches the channel normalizer). */\nexport type AccessChatType = 'p2p' | 'group'\n\n/** The security-relevant facts of one inbound message. */\nexport interface AccessSubject {\n chatType: AccessChatType\n chatId: string\n senderId: string\n}\n\n/** Read-only view of the durable authorization tables. */\nexport interface AccessSnapshot {\n /** Authorized group `chat_id`s. */\n groups: ReadonlySet<string>\n /** Authorized user `open_id`s (global, across authorized groups). */\n users: ReadonlySet<string>\n}\n\n/** Why access was denied; drives the controller's response. */\nexport type AccessDenyReason =\n | 'admin-missing'\n | 'dm-non-admin'\n | 'group-not-authorized'\n | 'user-not-authorized'\n\n/** Result of evaluating one inbound message. */\nexport type AccessDecision =\n | { readonly kind: 'allow'; readonly reason: 'open' | 'admin' | 'authorized-user' }\n | {\n readonly kind: 'deny'\n readonly reason: AccessDenyReason\n /** Which authorization request a denial may trigger, when applicable. */\n readonly request?: 'group' | 'user'\n }\n\n/** The configuration slice admission reads (allows easy fakes in tests). */\nexport type AccessPolicyConfig = Pick<FeishuConfig, 'accessMode' | 'adminOpenId'>\n\n/** True when `openId` is exactly the configured administrator. */\nexport function isAdminOpenId(\n config: Pick<FeishuConfig, 'adminOpenId'>,\n openId: string | undefined,\n): boolean {\n const admin = config.adminOpenId?.trim()\n if (admin === undefined || admin === '') return false\n return openId !== undefined && openId.trim() === admin\n}\n\n/**\n * Evaluate the admission matrix for one message.\n *\n * `open` mode reproduces the legacy behavior (the SDK's app-availability scope\n * is then the only boundary) and exists for debugging/compatibility only.\n */\nexport function decideAccess(\n subject: AccessSubject,\n config: AccessPolicyConfig,\n snapshot: AccessSnapshot,\n): AccessDecision {\n if (config.accessMode === 'open') return { kind: 'allow', reason: 'open' }\n\n const admin = config.adminOpenId?.trim()\n if (admin === undefined || admin === '') return { kind: 'deny', reason: 'admin-missing' }\n\n // The administrator is never constrained by the whitelists: they may start\n // a conversation in any private chat or any group, even one that has not\n // been authorized yet. The group/user gates only restrict non-admins.\n if (subject.senderId.trim() === admin) return { kind: 'allow', reason: 'admin' }\n\n if (subject.chatType === 'p2p') {\n return { kind: 'deny', reason: 'dm-non-admin' }\n }\n\n // Non-admin group access needs both gates: the group must be authorized and\n // the sender must be on the global user whitelist.\n if (!snapshot.groups.has(subject.chatId)) {\n return { kind: 'deny', reason: 'group-not-authorized', request: 'group' }\n }\n if (snapshot.users.has(subject.senderId)) return { kind: 'allow', reason: 'authorized-user' }\n return { kind: 'deny', reason: 'user-not-authorized', request: 'user' }\n}\n","/**\n * Inbound admission controller.\n *\n * Every inbound Feishu message passes this gate *before* any side effect\n * (Session creation, workspace, attachment archive, model call). The pure\n * matrix lives in `policy.ts`; this module owns the user-facing behavior on\n * denial:\n *\n * - private chats: non-administrators get one fixed notice (10 s per-sender\n * cooldown); with no administrator configured they get the bootstrap\n * notice instead;\n * - group chats: fully silent, except that addressed denials call back into\n * the authorization-request lifecycle so the administrator can be asked;\n * - slash commands: `/userid` stays available to every private sender, the\n * administrator's commands always pass (including inside an unauthorized\n * group), everything else must pass admission.\n *\n * The controller never creates a Session or a workspace.\n *\n * @module dsh-channels/access/controller\n */\n\nimport type { FeishuConfig } from '../config.ts'\nimport type { RouteRecord } from '../domain.ts'\nimport type { FeishuChannel, NormalizedMessage } from '../feishu-channel.ts'\nimport { decideAccess, isAdminOpenId } from './policy.ts'\nimport type { AccessDecision } from './policy.ts'\nimport type { AccessStore, AccessStoreSnapshot } from './store.ts'\n\n/** Fixed private-chat notice for non-administrators (no model call). */\nexport const DM_DENY_TEXT = '非管理员禁用私聊功能哦~如需使用,请在管理员授权的群里 @ 我;或联系管理员把你的 open_id 加入白名单。'\n\n/** Fixed private-chat notice while no administrator is configured. */\nexport const DM_UNCONFIGURED_TEXT = '机器人尚未配置管理员。发送 /userid 可查看你的 open_id,交给部署者配置后即可启用。'\n\n/** Per-sender cooldown for the repeated private-chat notice (10 s). */\nconst DM_NOTICE_COOLDOWN_MS = 10_000\n\n/** Collaborators of the admission gate. */\nexport interface AccessControllerDeps {\n /** Current plugin configuration (administrator id + access mode). */\n config(): FeishuConfig\n /** Durable whitelists (read through the in-memory snapshot). */\n store: AccessStore\n /** Live channel, undefined before startup / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Best-effort diagnostics; never rethrown. */\n warn(message: string, ...args: unknown[]): void\n /** Addressed message in an unauthorized group (request lifecycle hook). */\n onDeniedGroup?(msg: NormalizedMessage): void\n /** Addressed message from an unauthorized user in an authorized group. */\n onDeniedUser?(msg: NormalizedMessage): void\n /** Clock seam for tests. */\n now?(): number\n}\n\nexport class AccessController {\n /** sender open_id → last fixed-notice delivery instant (DM spam guard). */\n private readonly dmNoticeAt = new Map<string, number>()\n\n constructor(private readonly deps: AccessControllerDeps) {}\n\n private config(): FeishuConfig {\n return this.deps.config()\n }\n\n private now(): number {\n return this.deps.now?.() ?? Date.now()\n }\n\n /** True when `openId` is exactly the configured administrator. */\n isAdmin(openId?: string): boolean {\n return isAdminOpenId(this.config(), openId)\n }\n\n /** Current whitelist snapshot used by the pure policy. */\n snapshot(): AccessStoreSnapshot {\n return this.deps.store.snapshot()\n }\n\n /** Evaluate the admission matrix for one message (pure; no side effects). */\n decide(msg: NormalizedMessage): AccessDecision {\n return decideAccess(\n { chatType: msg.chatType, chatId: msg.chatId, senderId: msg.senderId },\n this.config(),\n this.deps.store.snapshot(),\n )\n }\n\n /**\n * Full channel-plane admission for one non-slash message. `false` means the\n * message has been handled (denied silently or with the fixed notice) and\n * the caller must stop before any side effect.\n */\n async admit(msg: NormalizedMessage): Promise<boolean> {\n const decision = this.decide(msg)\n if (decision.kind === 'allow') return true\n try {\n if (decision.reason === 'dm-non-admin') {\n if (msg.chatType === 'p2p') await this.noticeDeniedDm(msg)\n return false\n }\n if (decision.reason === 'admin-missing') {\n // Private chats get the bootstrap instructions; groups stay silent.\n // Goes through the same per-sender cooldown as the regular notice so a\n // single sender cannot make the bot answer every message while the\n // deployment is unconfigured.\n if (msg.chatType === 'p2p') await this.noticeDeniedDm(msg)\n return false\n }\n // Group denials are silent. Only messages actually addressed to the bot\n // may trigger the (administrator-facing) authorization request.\n if (msg.chatType === 'group' && addressedToBot(msg)) {\n if (decision.reason === 'group-not-authorized') this.deps.onDeniedGroup?.(msg)\n else if (decision.reason === 'user-not-authorized') this.deps.onDeniedUser?.(msg)\n }\n } catch (error) {\n this.deps.warn('dsh-channels: admission side effect failed: %s', String(error))\n }\n return false\n }\n\n /**\n * Slash-command admission. Runs before any command card/topic/Session side\n * effect. `open` mode keeps the legacy behavior; otherwise private chats\n * stay administrator-only (with `/userid` as the bootstrap exception) and\n * group chats must pass the full admission matrix; the administrator is\n * never constrained by the whitelists, so all of their commands pass even\n * inside an unauthorized group (`/auth` is the in-conversation grant\n * entry).\n */\n async admitCommand(msg: NormalizedMessage, commandName: string | undefined): Promise<boolean> {\n if (this.config().accessMode === 'open') return true\n try {\n if (msg.chatType === 'p2p') {\n // `/userid` is the bootstrap entry for everyone: it only echoes the\n // sender's own open_id and has no storage/model side effect.\n if (commandName === 'userid') return true\n if (this.isAdmin(msg.senderId)) return true\n await this.noticeDeniedDm(msg)\n return false\n }\n if (this.isAdmin(msg.senderId) && (commandName === 'auth' || commandName === 'groupid')) {\n return true\n }\n return this.decide(msg).kind === 'allow'\n } catch (error) {\n this.deps.warn('dsh-channels: command admission failed: %s', String(error))\n return false\n }\n }\n\n /**\n * Topic/route ownership: a route belongs to the sender (and chat) that\n * created it. Used to reject foreign replies, attachments, and command\n * execution inside someone else's topic.\n */\n ownsRoute(msg: NormalizedMessage, record: RouteRecord): boolean {\n return record.senderOpenId === msg.senderId && record.chatId === msg.chatId\n }\n\n /** Drop in-memory notices (shutdown; durable state lives in the store). */\n clear(): void {\n this.dmNoticeAt.clear()\n }\n\n // ---- internals -------------------------------------------------------\n\n /**\n * Deliver the fixed private-chat notice (cooled down). While no\n * administrator is configured the bootstrap text is used instead, so users\n * are never pointed at an administrator that does not exist yet.\n */\n private async noticeDeniedDm(msg: NormalizedMessage): Promise<void> {\n const now = this.now()\n const last = this.dmNoticeAt.get(msg.senderId)\n if (last !== undefined && now - last < DM_NOTICE_COOLDOWN_MS) return\n this.dmNoticeAt.set(msg.senderId, now)\n const text = this.config().adminOpenId === undefined ? DM_UNCONFIGURED_TEXT : DM_DENY_TEXT\n await this.sendText(msg.chatId, text, msg.messageId)\n }\n\n /** Best-effort fixed-text send; unavailable channel/failures only warn. */\n private async sendText(chatId: string, text: string, replyTo?: string): Promise<void> {\n const channel = this.deps.channel()\n if (channel === undefined) {\n this.deps.warn('dsh-channels: channel unavailable; admission notice not delivered')\n return\n }\n try {\n await channel.send(chatId, { text }, replyTo === undefined ? undefined : { replyTo })\n } catch (error) {\n this.deps.warn('dsh-channels: delivering the admission notice failed: %s', String(error))\n }\n }\n}\n\n/** Late-trigger condition: only messages addressed to the bot count. */\nfunction addressedToBot(msg: NormalizedMessage): boolean {\n return msg.mentionedBot === true || msg.threadId !== undefined\n}\n","/**\n * Card JSON 2.0 primitives shared by every card this plugin renders.\n *\n * One envelope + one element vocabulary keeps the byte budget, the width\n * mode and the callback shape in a single place; card modules only describe\n * their content.\n *\n * @module dsh-channels/cards/primitives\n */\n\n/** Feishu card JSON schema version this plugin targets. */\nexport const CARD_SCHEMA = '2.0'\n\n/** Default card config: full-width cards in the chat pane. */\nexport const CARD_CONFIG: Record<string, unknown> = { width_mode: 'fill' }\n\n/** Wrap elements in a card 2.0 envelope. */\nexport function card(elements: object[], config: Record<string, unknown> = CARD_CONFIG): object {\n return { schema: CARD_SCHEMA, config, body: { elements } }\n}\n\n/** Markdown element with optional presentation fields (`text_size`, …). */\nexport function markdown(content: string, extra: Record<string, unknown> = {}): object {\n return { tag: 'markdown', content, ...extra }\n}\n\n/** Small notation-size markdown, used for hints and footers. */\nexport function note(content: string): object {\n return markdown(content, { text_size: 'notation' })\n}\n\n/** Horizontal divider. */\nexport function divider(): object {\n return { tag: 'hr' }\n}\n\nexport interface TextButtonOptions {\n label: string\n /** Callback payload carried back by `card.action.trigger`. */\n value: unknown\n type?: 'default' | 'primary' | 'danger'\n disabled?: boolean\n width?: 'fill' | 'default'\n margin?: string\n}\n\n/** One callback button (`behaviors[0].value` is the plugin's action object). */\nexport function textButton(options: TextButtonOptions): object {\n return {\n tag: 'button',\n type: options.type ?? 'default',\n width: options.width ?? 'fill',\n margin: options.margin ?? '4px 0 0 0',\n ...(options.disabled === true ? { disabled: true } : {}),\n text: { tag: 'plain_text', content: options.label },\n behaviors: [{ type: 'callback', value: options.value }],\n }\n}\n\n/** A row of equal-width columns, each holding one element. */\nexport function columnSet(\n columns: object[],\n options: { flexMode?: string; spacing?: string; margin?: string } = {},\n): object {\n return {\n tag: 'column_set',\n flex_mode: options.flexMode ?? 'bisect',\n horizontal_spacing: options.spacing ?? 'medium',\n margin: options.margin ?? '4px 0 0 0',\n columns: columns.map(element => ({ tag: 'column', width: 'weighted', weight: 1, elements: [element] })),\n }\n}\n\n/** Titled standalone card: bold title element followed by the body elements. */\nexport function standaloneCard(title: string, elements: object[], config?: Record<string, unknown>): object {\n return card([markdown(title), ...elements], config)\n}\n\n/** Titled card whose body is one markdown element per supplied line. */\nexport function standaloneInfoCard(title: string, lines: readonly string[]): object {\n return standaloneCard(title, lines.map(line => markdown(line)))\n}\n","/**\n * Access-control cards: the bot-added group information card, the\n * administrator's authorization request cards, and their terminal state.\n *\n * The request cards carry an opaque token in the button callback value; the\n * token is resolved server-side against the durable request table, so a card\n * can never grant anything by itself (see `docs/access-control-design.md`\n * §5.7/§5.8).\n *\n * @module dsh-channels/access/cards\n */\n\nimport { columnSet, divider, markdown, note, standaloneCard, textButton } from '../cards/primitives.ts'\n\n/** Terminal status of one settled authorization request. */\nexport type SettledRequestStatus = 'approved' | 'denied' | 'expired'\n\n/** Inputs of the group-information card sent to the administrator. */\nexport interface GroupInfoCardInput {\n /** Feishu `chat_id` of the group the bot was added to (or was seen in). */\n chatId: string\n /** Group name, when `getChatInfo()` could read it. */\n name?: string\n /** Member count, when available. */\n memberCount?: number\n /** Group owner `open_id`, when available. */\n ownerId?: string\n /** `open_id` of whoever invited/triggered the bot. */\n inviterOpenId?: string\n /** Whether the group already passed the first admission gate. */\n authorized: boolean\n /** Opaque request token; only present for a *pending* group request. */\n token?: string\n /** Render timestamp (defaults to now). */\n now?: number\n}\n\n/** Inputs of the user-authorization request card sent to the administrator. */\nexport interface UserRequestCardInput {\n /** `open_id` of the user asking for access. */\n openId: string\n /** Display name of that user, when known. */\n name?: string\n /** Chat the request originated from. */\n chatId?: string\n /** Name of the authorized group the user was seen in. */\n groupName?: string\n /** Display name of the requester (the inbound sender). */\n requesterName?: string\n /** Opaque request token. */\n token: string\n /** Render timestamp (defaults to now). */\n now?: number\n}\n\n/** Inputs of the terminal replacement card. */\nexport interface RequestSettledCardInput {\n /** What kind of grant the request was about. */\n kind: 'group' | 'user'\n /** `chat_id` (group) or `open_id` (user) the request targeted. */\n targetId: string\n /** Human-readable target name, when known. */\n targetName?: string\n /** Settled state; pending requests never use this card. */\n status: SettledRequestStatus\n /** Render timestamp (defaults to now). */\n now?: number\n}\n\n/** Truncate one display string so the card stays far below the size budget. */\nfunction clip(value: string, max = 120): string {\n const trimmed = value.trim()\n return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max)}…`\n}\n\n/** Render one epoch-ms instant as a stable UTC `YYYY-MM-DD HH:mm` string. */\nfunction formatTime(timestamp?: number): string {\n const value = timestamp ?? Date.now()\n if (!Number.isFinite(value)) return '—'\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return '—'\n return `${date.toISOString().replace('T', ' ').slice(0, 16)} UTC`\n}\n\n/** Two side-by-side decision buttons carrying the opaque request token. */\nfunction decisionButtons(token: string, allowLabel: string, denyLabel: string): object {\n return columnSet([\n textButton({\n label: allowLabel,\n type: 'primary',\n width: 'fill',\n margin: '0px',\n value: { token, action: 'allow' },\n }),\n textButton({\n label: denyLabel,\n type: 'danger',\n width: 'fill',\n margin: '0px',\n value: { token, action: 'deny' },\n }),\n ])\n}\n\n/** `**字段**:值` line used across both request cards. */\nfunction field(label: string, value: string): object {\n return markdown(`**${label}**:${value}`)\n}\n\n/**\n * The group-information card the administrator receives when the bot joins a\n * group (and, as a fallback, on the first denied access in an unauthorized\n * group). Unauthorized groups with a pending request carry「授权本群 / 忽略」.\n */\nexport function buildGroupInfoCard(input: GroupInfoCardInput): object {\n const name = input.name === undefined ? undefined : clip(input.name)\n const elements: object[] = []\n elements.push(field('群名', name === undefined || name === '' ? '(未知)' : name))\n elements.push(field('群 ID', `\\`${input.chatId}\\``))\n if (input.memberCount !== undefined) elements.push(field('成员数', String(input.memberCount)))\n if (input.ownerId !== undefined && input.ownerId !== '') {\n elements.push(field('群主', `\\`${input.ownerId}\\``))\n }\n if (input.inviterOpenId !== undefined && input.inviterOpenId !== '') {\n elements.push(field('邀请人', `\\`${input.inviterOpenId}\\``))\n }\n elements.push(field('加入时间', formatTime(input.now)))\n elements.push(divider())\n\n if (!input.authorized && input.token !== undefined && input.token !== '') {\n elements.push(note('该群尚未授权:群内目前只有管理员可以与我对话;授权后群内白名单用户也可使用。'))\n elements.push(decisionButtons(input.token, '授权本群', '忽略'))\n } else if (input.authorized) {\n elements.push(note('✅ 该群已授权,群内白名单用户可正常使用(管理员不受白名单限制)。'))\n } else {\n elements.push(note('该群当前未授权(仅管理员可用),且没有可用的授权令牌;可在 Web 设置面板的「访问与授权」中授权。'))\n }\n return standaloneCard('**📋 群信息**', elements)\n}\n\n/**\n * The user-authorization request card: an unauthorized user mentioned the bot\n * inside an authorized group, so the administrator can grant that `open_id`\n * from the DM card.\n */\nexport function buildUserRequestCard(input: UserRequestCardInput): object {\n const display = clip(input.name ?? input.requesterName ?? '')\n const groupName = input.groupName === undefined ? undefined : clip(input.groupName)\n const elements: object[] = [\n field('用户', display === '' ? '(未知昵称)' : display),\n field('open_id', `\\`${input.openId}\\``),\n ]\n if (groupName !== undefined && groupName !== '') {\n const group = input.chatId === undefined ? groupName : `${groupName}(\\`${input.chatId}\\`)`\n elements.push(field('所在群', group))\n } else if (input.chatId !== undefined && input.chatId !== '') {\n elements.push(field('所在群', `\\`${input.chatId}\\``))\n }\n elements.push(field('申请时间', formatTime(input.now)))\n elements.push(divider())\n elements.push(note('授权后该用户可在所有已授权群里使用;私聊仍仅管理员可用。'))\n elements.push(decisionButtons(input.token, '授权用户', '忽略'))\n return standaloneCard('**🙋 用户授权申请**', elements)\n}\n\n/** Title/description pairs for the three settled states. */\nconst SETTLED_PRESENTATION: Record<SettledRequestStatus, { title: string; line: string }> = {\n approved: { title: '✅ 已授权', line: '授权已生效。' },\n denied: { title: '❌ 已忽略', line: '本次申请已被忽略。' },\n expired: { title: '⏱ 已过期', line: '申请超过有效期,已自动失效。' },\n}\n\n/**\n * The terminal replacement for a request card (decision or expiry). Carries\n * no buttons: the request token can settle exactly once.\n */\nexport function buildRequestSettledCard(input: RequestSettledCardInput): object {\n const presentation = SETTLED_PRESENTATION[input.status]\n const kindLabel = input.kind === 'group' ? '群' : '用户'\n const name = input.targetName === undefined ? undefined : clip(input.targetName)\n const target = name === undefined || name === '' ? `\\`${input.targetId}\\`` : `${name}(\\`${input.targetId}\\`)`\n const elements: object[] = [\n field(kindLabel, target),\n field('处理时间', formatTime(input.now)),\n note(presentation.line),\n ]\n return standaloneCard(`**${presentation.title}**`, elements)\n}\n","/**\n * Authorization request lifecycle.\n *\n * Two fixed events can ask the administrator to grant access: the bot being\n * added to a group, and an unauthorized user mentioning the bot inside an\n * already-authorized group. Both create one durable\n * `AccessRequestRecord` (opaque token, TTL from `authRequestTtlDays`), deliver\n * a DM card carrying that token, and settle exactly once from the card or the\n * Web panel through the same `resolve()` path. Every network/persistence\n * failure degrades to a warning: admission must never throw because a card\n * could not be delivered.\n *\n * @module dsh-channels/access/requests\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { FeishuConfig } from '../config.ts'\nimport type { AccessRequestRecord } from '../domain.ts'\nimport type {\n BotAddedEvent,\n CardActionEvent,\n ChatInfo,\n FeishuChannel,\n NormalizedMessage,\n} from '../feishu-channel.ts'\nimport { buildGroupInfoCard, buildRequestSettledCard, buildUserRequestCard } from './cards.ts'\nimport type { SettledRequestStatus } from './cards.ts'\nimport { isAdminOpenId } from './policy.ts'\nimport type { AccessStore } from './store.ts'\n\n/** Same-target re-notification cooldown after a request was denied (24 h). */\nconst DENIED_COOLDOWN_MS = 24 * 60 * 60 * 1000\n\nconst MS_PER_DAY = 86_400_000\n\n/** Collaborators the request lifecycle needs, injected so tests stay offline. */\nexport interface AccessRequestsDeps {\n /** Current plugin configuration (administrator id + request TTL). */\n config(): FeishuConfig\n /** Durable whitelist/request store. */\n store: AccessStore\n /** Live channel, undefined before startup / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Best-effort diagnostics; never rethrown. */\n warn(message: string, ...args: unknown[]): void\n /** Clock seam for tests. */\n now?(): number\n}\n\n/** One decoded card callback value (the token is the only trusted input). */\ninterface CardDecisionValue {\n token: string\n action: 'allow' | 'deny'\n}\n\n/** Decode one button callback value; anything malformed is silently ignored. */\nfunction decodeCardValue(value: unknown): CardDecisionValue | undefined {\n if (typeof value !== 'object' || value === null) return undefined\n const record = value as Record<string, unknown>\n const token = record.token\n const action = record.action\n if (typeof token !== 'string' || token === '') return undefined\n if (action !== 'allow' && action !== 'deny') return undefined\n return { token, action }\n}\n\n/** Human label for a terminal request status (error messages and cards). */\nfunction statusLabel(status: AccessRequestRecord['status']): string {\n switch (status) {\n case 'approved':\n return '已授权'\n case 'denied':\n return '已忽略'\n case 'expired':\n return '已过期'\n default:\n return '处理中'\n }\n}\n\n/** Map a settled record status to the card vocabulary (`pending` has no card). */\nfunction settledStatus(status: AccessRequestRecord['status']): SettledRequestStatus | undefined {\n return status === 'pending' ? undefined : status\n}\n\n/**\n * Late-trigger condition: only messages addressed to the bot may create a\n * request. A plain group message in an unauthorized group stays silent.\n */\nfunction addressedToBot(msg: NormalizedMessage): boolean {\n return msg.mentionedBot === true || msg.threadId !== undefined\n}\n\nexport class AccessRequests {\n /** Targets with an in-flight create/deliver pass (avoids duplicate cards). */\n private readonly creating = new Set<string>()\n /** `${messageId}:${openId}` of non-administrator card clicks already answered. */\n private readonly unauthorizedNotified = new Set<string>()\n\n constructor(private readonly deps: AccessRequestsDeps) {}\n\n private now(): number {\n return this.deps.now?.() ?? Date.now()\n }\n\n private config(): FeishuConfig {\n return this.deps.config()\n }\n\n private adminOpenId(): string | undefined {\n const admin = this.config().adminOpenId?.trim()\n return admin === undefined || admin === '' ? undefined : admin\n }\n\n private ttlMs(): number {\n return this.config().authRequestTtlDays * MS_PER_DAY\n }\n\n /**\n * Bot added to a group (`im.chat.member.bot.added_v1`): DM the group\n * information card to the administrator. An authorized group only gets the\n * informational card; an unauthorized one gets an「授权本群 / 忽略」card\n * backed by a fresh pending request (deduplicated, 24 h deny cooldown).\n */\n async handleBotAdded(event: BotAddedEvent): Promise<void> {\n try {\n const admin = this.adminOpenId()\n if (admin === undefined) {\n this.deps.warn(\n 'dsh-channels: bot added to %s but no administrator open_id is configured; group card skipped',\n event.chatId,\n )\n return\n }\n const now = this.now()\n if (this.deps.store.hasGroup(event.chatId)) {\n const info = await this.chatInfo(event.chatId)\n await this.sendCard(admin, buildGroupInfoCard({\n chatId: event.chatId,\n ...(info?.name === undefined ? {} : { name: info.name }),\n ...(info?.memberCount === undefined ? {} : { memberCount: info.memberCount }),\n ...(info?.ownerId === undefined ? {} : { ownerId: info.ownerId }),\n inviterOpenId: event.operator.openId,\n authorized: true,\n now,\n }))\n return\n }\n if (!(await this.beginGroupRequest(event.chatId, event.operator.openId, now))) return\n } catch (error) {\n this.deps.warn('dsh-channels: bot-added group notice failed: %s', String(error))\n }\n }\n\n /**\n * Fallback trigger for an unauthorized group: the first *addressed* message\n * in it re-sends the group information/authorization card to the\n * administrator (used when the bot-added event is not subscribed).\n */\n async ensureGroupRequest(msg: NormalizedMessage): Promise<void> {\n if (!addressedToBot(msg) || msg.chatType !== 'group') return\n try {\n const admin = this.adminOpenId()\n if (admin === undefined) {\n this.deps.warn(\n 'dsh-channels: group %s needs authorization but no administrator open_id is configured',\n msg.chatId,\n )\n return\n }\n if (this.deps.store.hasGroup(msg.chatId)) return\n if (!(await this.beginGroupRequest(msg.chatId, msg.senderId, this.now()))) return\n } catch (error) {\n this.deps.warn('dsh-channels: group authorization request failed: %s', String(error))\n }\n }\n\n /**\n * Lazy trigger for an unauthorized user inside an authorized group: DM the\n * administrator a user-authorization request card (deduplicated, 24 h deny\n * cooldown). Never runs for unauthorized groups (the group gate is first).\n */\n async ensureUserRequest(msg: NormalizedMessage): Promise<void> {\n if (!addressedToBot(msg) || msg.chatType !== 'group') return\n try {\n const admin = this.adminOpenId()\n if (admin === undefined) {\n this.deps.warn(\n 'dsh-channels: user %s needs authorization but no administrator open_id is configured',\n msg.senderId,\n )\n return\n }\n const group = this.deps.store.getGroup(msg.chatId)\n if (group === undefined) return\n if (this.deps.store.hasUser(msg.senderId)) return\n await this.beginUserRequest(msg, group.name, this.now())\n } catch (error) {\n this.deps.warn('dsh-channels: user authorization request failed: %s', String(error))\n }\n }\n\n /**\n * Card button callback: administrator-only settlement. A stale card (already\n * settled or expired) is patched to its terminal state; a request that is\n * still pending goes through the same `resolve()` as the Web panel.\n */\n async onCardAction(event: CardActionEvent): Promise<void> {\n try {\n const decision = decodeCardValue(event.action?.value)\n if (decision === undefined) return\n const record = this.deps.store.getRequest(decision.token)\n if (record === undefined) return\n if (!isAdminOpenId(this.config(), event.operator.openId)) {\n await this.notifyUnauthorized(event)\n return\n }\n if (record.status !== 'pending') {\n await this.patchCard(record)\n return\n }\n if (record.expiresAt <= this.now()) {\n await this.settleExpired(record)\n return\n }\n const result = await this.resolve(decision.token, decision.action, event.operator.openId)\n if (!result.ok) {\n this.deps.warn('dsh-channels: settling access request %s failed: %s', decision.token, result.error)\n }\n } catch (error) {\n this.deps.warn('dsh-channels: access card action failed: %s', String(error))\n }\n }\n\n /**\n * Settle one pending request: `allow` writes the group/user grant (source\n * `card`), `deny` only records the decision. Shared by the DM card and the\n * Web panel; every failure is reported in the return value, never thrown.\n */\n async resolve(\n token: string,\n decision: 'allow' | 'deny',\n actorOpenId: string,\n ): Promise<{ ok: true } | { ok: false; error: string }> {\n if (!isAdminOpenId(this.config(), actorOpenId)) return { ok: false, error: '仅管理员可处理授权申请' }\n const record = this.deps.store.getRequest(token)\n if (record === undefined) return { ok: false, error: '授权申请不存在' }\n if (record.status !== 'pending') {\n return { ok: false, error: `该申请已处理(${statusLabel(record.status)})` }\n }\n const now = this.now()\n if (record.expiresAt <= now) {\n await this.settleExpired(record)\n return { ok: false, error: '授权申请已过期' }\n }\n const status = decision === 'allow' ? 'approved' : 'denied'\n try {\n if (decision === 'allow') await this.grant(record, actorOpenId, now)\n await this.deps.store.putRequest({ ...record, status })\n } catch (error) {\n this.deps.warn('dsh-channels: persisting access request %s settlement failed: %s', token, String(error))\n return { ok: false, error: '保存授权结果失败' }\n }\n await this.patchCard({ ...record, status })\n return { ok: true }\n }\n\n /** Mark every pending request past its deadline as expired and patch its card. */\n async expirePending(): Promise<void> {\n try {\n const expired = await this.deps.store.expireRequests(this.now())\n for (const record of expired) await this.patchCard(record)\n } catch (error) {\n this.deps.warn('dsh-channels: expiring access requests failed: %s', String(error))\n }\n }\n\n /** Pending, not-yet-expired requests (the administrator's queue). */\n pending(): AccessRequestRecord[] {\n return this.deps.store.pendingRequests(this.now())\n }\n\n /** Drop in-memory dedupe guards (shutdown). Durable state lives in the store. */\n clear(): void {\n this.creating.clear()\n this.unauthorizedNotified.clear()\n }\n\n // ---- internals -------------------------------------------------------\n\n /** Create + DM one group request unless deduplicated/cooling down. */\n private async beginGroupRequest(chatId: string, inviterOpenId: string, now: number): Promise<boolean> {\n const admin = this.adminOpenId()\n if (admin === undefined) return false\n if (await this.hasLivePending('group', chatId, now)) return false\n if (this.deps.store.recentlyDenied('group', chatId, DENIED_COOLDOWN_MS, now)) return false\n const key = `group:${chatId}`\n if (this.creating.has(key)) return false\n this.creating.add(key)\n try {\n const info = await this.chatInfo(chatId)\n const record = this.newRecord({\n kind: 'group',\n targetId: chatId,\n chatId,\n ...(info?.name === undefined ? {} : { groupName: info.name }),\n requesterOpenId: inviterOpenId,\n })\n if (!(await this.persist(record))) return false\n await this.sendCard(admin, buildGroupInfoCard({\n chatId,\n ...(info?.name === undefined ? {} : { name: info.name }),\n ...(info?.memberCount === undefined ? {} : { memberCount: info.memberCount }),\n ...(info?.ownerId === undefined ? {} : { ownerId: info.ownerId }),\n inviterOpenId,\n authorized: false,\n token: record.token,\n now,\n }), record)\n return true\n } finally {\n this.creating.delete(key)\n }\n }\n\n /** Create + DM one user request unless deduplicated/cooling down. */\n private async beginUserRequest(msg: NormalizedMessage, groupName: string | undefined, now: number): Promise<boolean> {\n const admin = this.adminOpenId()\n if (admin === undefined) return false\n if (await this.hasLivePending('user', msg.senderId, now)) return false\n if (this.deps.store.recentlyDenied('user', msg.senderId, DENIED_COOLDOWN_MS, now)) return false\n const key = `user:${msg.senderId}`\n if (this.creating.has(key)) return false\n this.creating.add(key)\n try {\n const record = this.newRecord({\n kind: 'user',\n targetId: msg.senderId,\n chatId: msg.chatId,\n ...(groupName === undefined ? {} : { groupName }),\n requesterOpenId: msg.senderId,\n ...(msg.senderName === undefined ? {} : { requesterName: msg.senderName }),\n })\n if (!(await this.persist(record))) return false\n await this.sendCard(admin, buildUserRequestCard({\n openId: msg.senderId,\n ...(msg.senderName === undefined ? {} : { name: msg.senderName }),\n chatId: msg.chatId,\n ...(groupName === undefined ? {} : { groupName }),\n ...(msg.senderName === undefined ? {} : { requesterName: msg.senderName }),\n token: record.token,\n now,\n }), record)\n return true\n } finally {\n this.creating.delete(key)\n }\n }\n\n /**\n * True when an unexpired request for this target is pending. An expired but\n * not-yet-swept record is settled as expired first so the new request is not\n * silently blocked by the stale one.\n */\n private async hasLivePending(\n kind: AccessRequestRecord['kind'],\n targetId: string,\n now: number,\n ): Promise<boolean> {\n const pending = this.deps.store.pendingRequestFor(kind, targetId)\n if (pending === undefined) return false\n if (pending.expiresAt > now) return true\n await this.settleExpired(pending)\n return false\n }\n\n /** Build one pending request record (token + TTL, status `pending`). */\n private newRecord(input: {\n kind: AccessRequestRecord['kind']\n targetId: string\n chatId?: string\n groupName?: string\n requesterOpenId?: string\n requesterName?: string\n }): AccessRequestRecord {\n const now = this.now()\n return {\n token: randomUUID(),\n kind: input.kind,\n targetId: input.targetId,\n ...(input.chatId === undefined ? {} : { chatId: input.chatId }),\n ...(input.groupName === undefined || input.groupName === '' ? {} : { groupName: input.groupName }),\n ...(input.requesterOpenId === undefined ? {} : { requesterOpenId: input.requesterOpenId }),\n ...(input.requesterName === undefined || input.requesterName === '' ? {} : { requesterName: input.requesterName }),\n status: 'pending',\n createdAt: now,\n expiresAt: now + this.ttlMs(),\n }\n }\n\n /** Persist a request; returns false (and warns) on failure. */\n private async persist(record: AccessRequestRecord): Promise<boolean> {\n try {\n await this.deps.store.putRequest(record)\n return true\n } catch (error) {\n this.deps.warn('dsh-channels: persisting access request %s failed: %s', record.token, String(error))\n return false\n }\n }\n\n /** DM one card to the administrator and remember its message id. */\n private async sendCard(to: string, card: object, record?: AccessRequestRecord): Promise<void> {\n if (to === '') return\n const channel = this.deps.channel()\n if (channel === undefined) {\n this.deps.warn('dsh-channels: channel unavailable; access card %s not delivered', record?.token ?? '(group info)')\n return\n }\n try {\n const result = await channel.send(to, { card })\n if (record === undefined) return\n const messageId = result?.messageId\n if (typeof messageId !== 'string' || messageId === '') return\n try {\n await this.deps.store.putRequest({ ...record, messageId })\n } catch (error) {\n this.deps.warn('dsh-channels: persisting card message id for %s failed: %s', record.token, String(error))\n }\n } catch (error) {\n this.deps.warn('dsh-channels: delivering access card %s failed: %s', record?.token ?? '(group info)', String(error))\n }\n }\n\n /** Group name/owner/member count, best-effort (missing permission → warn). */\n private async chatInfo(chatId: string): Promise<ChatInfo | undefined> {\n const channel = this.deps.channel()\n if (channel === undefined) return undefined\n try {\n return await channel.getChatInfo(chatId)\n } catch (error) {\n this.deps.warn('dsh-channels: reading chat info for %s failed: %s', chatId, String(error))\n return undefined\n }\n }\n\n /** Write the actual grant; throws so `resolve()` can report the failure. */\n private async grant(record: AccessRequestRecord, actorOpenId: string, now: number): Promise<void> {\n if (record.kind === 'group') {\n await this.deps.store.addGroup({\n chatId: record.targetId,\n ...(record.groupName === undefined ? {} : { name: record.groupName }),\n source: 'card',\n addedBy: actorOpenId,\n addedAt: now,\n })\n return\n }\n await this.deps.store.addUser({\n openId: record.targetId,\n ...(record.requesterName === undefined ? {} : { name: record.requesterName }),\n source: 'card',\n addedBy: actorOpenId,\n addedAt: now,\n })\n }\n\n /** Persist the expired status (best-effort) then patch the DM card. */\n private async settleExpired(record: AccessRequestRecord): Promise<void> {\n const expired: AccessRequestRecord = { ...record, status: 'expired' }\n try {\n await this.deps.store.putRequest(expired)\n } catch (error) {\n this.deps.warn('dsh-channels: persisting expired access request %s failed: %s', record.token, String(error))\n }\n await this.patchCard(expired)\n }\n\n /** Replace the DM card with its terminal state; delivery failures only warn. */\n private async patchCard(record: AccessRequestRecord): Promise<void> {\n const status = settledStatus(record.status)\n if (status === undefined || record.messageId === undefined) return\n const channel = this.deps.channel()\n if (channel === undefined) return\n const targetName = record.kind === 'group' ? record.groupName : record.requesterName\n try {\n await channel.updateCard(record.messageId, buildRequestSettledCard({\n kind: record.kind,\n targetId: record.targetId,\n ...(targetName === undefined ? {} : { targetName }),\n status,\n now: this.now(),\n }))\n } catch (error) {\n this.deps.warn('dsh-channels: patching access card %s failed: %s', record.messageId, String(error))\n }\n }\n\n /** One control card is delivered once; non-administrator clicks get one notice. */\n private async notifyUnauthorized(event: CardActionEvent): Promise<void> {\n const key = `${event.messageId}:${event.operator.openId}`\n if (this.unauthorizedNotified.has(key)) return\n this.unauthorizedNotified.add(key)\n const channel = this.deps.channel()\n if (channel === undefined) return\n try {\n await channel.send(event.chatId, { text: '仅管理员可操作该授权卡片。' }, { replyTo: event.messageId })\n } catch (error) {\n this.deps.warn('dsh-channels: notifying a non-administrator card click failed: %s', String(error))\n }\n }\n}\n","/**\n * Durable access-control store.\n *\n * Reads come from in-memory maps (seeded once from the plugin's\n * `storageDomain` tables) so admission decisions stay synchronous and cheap;\n * every mutation writes through to the domain first and then updates memory,\n * matching the store patterns used for routes and inflight snapshots.\n *\n * The tables (`access_groups`, `access_users`, `access_requests`) were added\n * without bumping the domain version on purpose: the JSON backend's `single`\n * layout refuses a stored-version mismatch, while unknown/missing tables are\n * simply empty on load (see `docs/access-control-design.md` §4.2).\n *\n * @module dsh-channels/access/store\n */\n\nimport type {\n AccessGroupRecord,\n AccessRequestRecord,\n AccessUserRecord,\n FeishuDomainHandle,\n} from '../domain.ts'\n\nexport interface AccessStoreDeps {\n /** The open domain handle, or undefined before start / after shutdown. */\n domain(): FeishuDomainHandle | undefined\n warn(message: string, ...args: unknown[]): void\n}\n\n/** Read-only whitespace snapshot handed to the pure policy. */\nexport interface AccessStoreSnapshot {\n groups: ReadonlySet<string>\n users: ReadonlySet<string>\n}\n\n/** One settled authorization request (audit trail). */\nexport type AccessRequestStatus = AccessRequestRecord['status']\n\nexport class AccessStore {\n private readonly groups = new Map<string, AccessGroupRecord>()\n private readonly users = new Map<string, AccessUserRecord>()\n private readonly requests = new Map<string, AccessRequestRecord>()\n /** Cached key sets so `snapshot()` can stay allocation-free per message. */\n private readonly groupKeys = new Set<string>()\n private readonly userKeys = new Set<string>()\n\n constructor(private readonly deps: AccessStoreDeps) {}\n\n /** Seed memory from the durable tables (called once after the domain opens). */\n load(): void {\n this.clear()\n const domain = this.deps.domain()\n if (domain === undefined) {\n this.deps.warn('dsh-channels: access tables unavailable; starting with an empty whitelist')\n return\n }\n for (const [key, record] of domain.accessGroups.entries()) {\n this.groups.set(key, record)\n this.groupKeys.add(key)\n }\n for (const [key, record] of domain.accessUsers.entries()) {\n this.users.set(key, record)\n this.userKeys.add(key)\n }\n for (const [key, record] of domain.accessRequests.entries()) {\n this.requests.set(key, record)\n }\n }\n\n /** Drop every in-memory record (shutdown). The domain owns durability. */\n clear(): void {\n this.groups.clear()\n this.users.clear()\n this.requests.clear()\n this.groupKeys.clear()\n this.userKeys.clear()\n }\n\n snapshot(): AccessStoreSnapshot {\n return { groups: this.groupKeys, users: this.userKeys }\n }\n\n hasGroup(chatId: string): boolean {\n return this.groupKeys.has(chatId)\n }\n\n hasUser(openId: string): boolean {\n return this.userKeys.has(openId)\n }\n\n getGroup(chatId: string): AccessGroupRecord | undefined {\n return this.groups.get(chatId)\n }\n\n getUser(openId: string): AccessUserRecord | undefined {\n return this.users.get(openId)\n }\n\n /** Every authorized group, oldest first. */\n groupRecords(): AccessGroupRecord[] {\n return [...this.groups.values()].sort((left, right) => left.addedAt - right.addedAt)\n }\n\n /** Every authorized user, oldest first. */\n userRecords(): AccessUserRecord[] {\n return [...this.users.values()].sort((left, right) => left.addedAt - right.addedAt)\n }\n\n async addGroup(record: AccessGroupRecord): Promise<AccessGroupRecord> {\n await this.requireDomain('add access group').accessGroups.put(record.chatId, record)\n this.groups.set(record.chatId, record)\n this.groupKeys.add(record.chatId)\n return record\n }\n\n async removeGroup(chatId: string): Promise<boolean> {\n if (!this.groups.has(chatId)) return false\n await this.requireDomain('remove access group').accessGroups.delete(chatId)\n this.groups.delete(chatId)\n this.groupKeys.delete(chatId)\n return true\n }\n\n async addUser(record: AccessUserRecord): Promise<AccessUserRecord> {\n await this.requireDomain('add access user').accessUsers.put(record.openId, record)\n this.users.set(record.openId, record)\n this.userKeys.add(record.openId)\n return record\n }\n\n async removeUser(openId: string): Promise<boolean> {\n if (!this.users.has(openId)) return false\n await this.requireDomain('remove access user').accessUsers.delete(openId)\n this.users.delete(openId)\n this.userKeys.delete(openId)\n return true\n }\n\n /** Every authorization request, including settled ones (audit). */\n requestRecords(): AccessRequestRecord[] {\n return [...this.requests.values()].sort((left, right) => left.createdAt - right.createdAt)\n }\n\n /**\n * Pending, not-yet-expired requests. Expired-but-unsettled records are\n * excluded here; `expireRequests()` is the sweep that persists the terminal\n * state (and lets the card surface patch the DM card).\n */\n pendingRequests(now = Date.now()): AccessRequestRecord[] {\n return this.requestRecords().filter(\n record => record.status === 'pending' && record.expiresAt > now,\n )\n }\n\n getRequest(token: string): AccessRequestRecord | undefined {\n return this.requests.get(token)\n }\n\n /** The current pending request for one target, if any. */\n pendingRequestFor(kind: AccessRequestRecord['kind'], targetId: string): AccessRequestRecord | undefined {\n for (const record of this.requests.values()) {\n if (record.kind === kind && record.targetId === targetId && record.status === 'pending') {\n return record\n }\n }\n return undefined\n }\n\n /**\n * True when the same target was denied recently — used to keep a rejected\n * request from immediately re-notifying the administrator.\n */\n recentlyDenied(\n kind: AccessRequestRecord['kind'],\n targetId: string,\n windowMs: number,\n now = Date.now(),\n ): boolean {\n const cutoff = now - windowMs\n for (const record of this.requests.values()) {\n if (record.kind === kind && record.targetId === targetId\n && record.status === 'denied' && record.createdAt >= cutoff) {\n return true\n }\n }\n return false\n }\n\n async putRequest(record: AccessRequestRecord): Promise<AccessRequestRecord> {\n await this.requireDomain('put access request').accessRequests.put(record.token, record)\n this.requests.set(record.token, record)\n return record\n }\n\n /**\n * Mark every pending request past its deadline as expired. Returns the\n * settled records so the caller can patch stale DM cards.\n */\n async expireRequests(now = Date.now()): Promise<AccessRequestRecord[]> {\n const expired: AccessRequestRecord[] = []\n for (const record of [...this.requests.values()]) {\n if (record.status !== 'pending' || record.expiresAt > now) continue\n const next: AccessRequestRecord = { ...record, status: 'expired' }\n try {\n await this.requireDomain('expire access request').accessRequests.put(next.token, next)\n } catch (error) {\n this.deps.warn('dsh-channels: persisting expired access request %s failed: %s', next.token, String(error))\n }\n this.requests.set(next.token, next)\n expired.push(next)\n }\n return expired\n }\n\n private requireDomain(action: string): FeishuDomainHandle {\n const domain = this.deps.domain()\n if (domain === undefined) throw new Error(`dsh-channels: access domain not open (${action})`)\n return domain\n }\n}\n","/**\n * HITL approval decision cards (`approval/request`).\n *\n * @module dsh-channels/cards/approval\n */\n\nimport { columnSet, divider, markdown, note, standaloneCard, textButton } from './primitives.ts'\n\n// ---- HITL approval decision cards ----------------------------------------\n\n/** Terminal states an approval decision card can settle into. */\nexport type ApprovalCardState = 'allowed' | 'denied' | 'expired' | 'cancelled'\n\n/** The question one approval decision card presents. */\nexport interface ApprovalQuestion {\n /** Opaque token binding the card to one pending approval (button value). */\n token: string\n /** Model-facing tool name asking for approval (e.g. `bash`). */\n toolName: string\n /** The asker's human-readable justification; clipped to a safe length. */\n reason?: string\n /** Timeout window shown on the card (milliseconds). */\n timeoutMs: number\n /** Session id trace line (omitted when absent). */\n sessionId?: string\n}\n\n/** Approval card rendering options. */\nexport interface ApprovalCardOptions {\n /** Add the owner-only notice (a non-owner clicked and was ignored). */\n unauthorizedNote?: boolean\n}\n\n/** Cap on the rendered reason so the card can never approach the size budget. */\nconst APPROVAL_REASON_MAX_CHARS = 1_000\n\n/** `⏱ 5 分钟内有效…` — human timeout label (whole minutes, min 1). */\nfunction approvalTimeoutLabel(timeoutMs: number): string {\n return `${Math.max(1, Math.round(timeoutMs / 60_000))} 分钟`\n}\n\n/** One approval decision button with its callback payload. */\nfunction approvalButton(question: ApprovalQuestion, decision: 'allow' | 'deny'): object {\n const allow = decision === 'allow'\n return textButton({\n label: allow ? '✅ 允许' : '❌ 拒绝',\n type: allow ? 'primary' : 'danger',\n width: 'fill',\n margin: '0px',\n value: { decision, token: question.token },\n })\n}\n\n/** Two-column row that keeps both decisions side by side. */\nfunction approvalActions(question: ApprovalQuestion): object {\n return columnSet((['allow', 'deny'] as const).map(decision => approvalButton(question, decision)))\n}\n\n/** The question summary shared by the pending and settled variants. */\nfunction approvalQuestionElements(question: ApprovalQuestion): object[] {\n const elements: object[] = [\n markdown(`工具 \\`${question.toolName}\\` 请求执行一个需要授权的操作。`),\n ]\n if (question.reason !== undefined && question.reason !== '') {\n const clipped = question.reason.length > APPROVAL_REASON_MAX_CHARS\n ? `${question.reason.slice(0, APPROVAL_REASON_MAX_CHARS)}…`\n : question.reason\n elements.push(markdown(`> ${clipped.replace(/\\s+/gu, ' ')}`))\n }\n return elements\n}\n\n/**\n * The pending approval decision card: question summary, timeout note, and the\n * enabled allow/deny buttons. Sent as a NEW standalone message anchored to\n * the session's latest streaming card (never embedded into the streamed\n * card, whose full-replacement patches would clobber the buttons).\n */\nexport function buildApprovalCard(question: ApprovalQuestion, options: ApprovalCardOptions = {}): object {\n const elements: object[] = [\n ...approvalQuestionElements(question),\n divider(),\n note(`⏱ ${approvalTimeoutLabel(question.timeoutMs)}内有效,超时自动拒绝 · 允许仅对本次动作生效`),\n ]\n if (options.unauthorizedNote === true) {\n elements.push(note('ℹ️ 仅发起该会话的用户可操作此卡片'))\n }\n if (question.sessionId !== undefined) {\n elements.push(note(`🔑 ${question.sessionId}`))\n }\n elements.push(approvalActions(question))\n return standaloneCard('**⚠️ 需要你的审批**', elements)\n}\n\n/** Settled-card status lines, one per terminal state. */\nconst APPROVAL_SETTLED_LINES: Record<ApprovalCardState, string> = {\n allowed: '✅ 已允许执行',\n denied: '❌ 已拒绝',\n expired: '⏱ 已超时未处理,自动拒绝',\n cancelled: '↩️ 请求已取消',\n}\n\n/**\n * The terminal replacement for a decision card after the question settles\n * (decision, timeout, or withdrawal). Keeps the question summary for context\n * and removes the decision controls entirely.\n */\nexport function buildApprovalSettledCard(question: ApprovalQuestion, state: ApprovalCardState): object {\n return standaloneCard(`**${APPROVAL_SETTLED_LINES[state]}**`, [\n ...approvalQuestionElements(question),\n divider(),\n ])\n}\n\n","/**\n * Shared lifecycle of a pending interaction card.\n *\n * Both HITL surfaces (approvals and `ask_user_question`) need the same\n * mechanics: a token-keyed registry, owner-gated card-action lookup, delivery\n * with a withdrawn-race guard, a local timeout, settle-once cleanup, and\n * shutdown withdrawal. Only the rendered card and the resolution semantics\n * differ, so those stay in the surface modules and everything else lives here.\n *\n * @module dsh-channels/interactions/pending\n */\n\nimport type { CardActionEvent, FeishuChannel } from '../feishu-channel.ts'\n\n/** Where one session's interaction cards are placed on Feishu. */\nexport interface InteractionRoute {\n /** Feishu chat the cards live in. */\n chatId: string\n /** Reply anchor: the session's latest streaming card message id. */\n cardMessageId: string\n /** Whether the anchor lives in a real topic (replyInThread). */\n inThread: boolean\n /** The session owner (open_id) — the only operator allowed to decide. */\n senderOpenId: string\n}\n\n/** Fields every pending interaction card carries. */\nexport interface PendingCardEntry {\n /** Opaque token binding the card to one pending interaction. */\n readonly token: string\n /** DSH session the interaction belongs to (routing + error logs). */\n readonly sessionId: string\n /** Feishu placement of this card. */\n readonly route: InteractionRoute\n /** Card message id once it lands; undefined while sending. */\n messageId: string | undefined\n /** Settle-once guard: a late click/signal/timeout is discarded. */\n settled: boolean\n /** Local timeout handle (absent for host-owned deadlines). */\n timer: ReturnType<typeof setTimeout> | undefined\n /** The ask's own abort signal, when it has one. */\n readonly signal: AbortSignal | undefined\n /** Listener registered for that signal, removed on close. */\n onAbort: (() => void) | undefined\n /** The owner-only notice was already patched onto the card. */\n unauthorizedNoted: boolean\n}\n\nexport interface DeliverHandlers<E extends PendingCardEntry> {\n /** The pending card to send as a topic/thread reply. */\n card: object\n /** The card landed and its message id is recorded (arm timeouts here). */\n onDelivered?(entry: E): void\n /** The request withdrew while the card was in flight. */\n onWithdrawn(entry: E): void\n /** Delivery failed: fail the request closed. */\n onFailure(entry: E, error: unknown): void\n}\n\n/**\n * The pending-card table: registry, delivery, owner gating, and cleanup.\n * Concrete surfaces supply rendering and the terminal action.\n */\nexport class PendingCardTable<E extends PendingCardEntry> {\n private readonly entries = new Map<string, E>()\n\n constructor(\n private readonly channel: FeishuChannel,\n private readonly onError: (sessionId: string, error: unknown) => void,\n ) {}\n\n add(entry: E): void {\n this.entries.set(entry.token, entry)\n }\n\n count(): number {\n return this.entries.size\n }\n\n /** Every pending entry (diagnostics and inbound text routing). */\n values(): IterableIterator<E> {\n return this.entries.values()\n }\n\n /**\n * Owner-gated lookup for one card action, containing the owner-only notice.\n * Returns `undefined` for unknown cards, settled entries, and non-owner\n * clicks (which call `onUnauthorized` once).\n */\n resolveAction(event: CardActionEvent, onUnauthorized: (entry: E) => void): E | undefined {\n let entry: E | undefined\n for (const candidate of this.entries.values()) {\n if (candidate.messageId === event.messageId) {\n entry = candidate\n break\n }\n }\n if (entry === undefined || entry.settled) return undefined\n if (event.operator.openId !== entry.route.senderOpenId) {\n onUnauthorized(entry)\n return undefined\n }\n return entry\n }\n\n /** Send the pending card and contain every outcome. */\n async deliver(entry: E, handlers: DeliverHandlers<E>): Promise<void> {\n try {\n const res = await this.channel.send(\n entry.route.chatId,\n { card: handlers.card },\n { replyTo: entry.route.cardMessageId, replyInThread: entry.route.inThread },\n )\n // Record the id before the withdrawn check: a request that settled\n // while the card was in flight still needs its card patched terminal.\n entry.messageId = res.messageId\n if (entry.settled) {\n handlers.onWithdrawn(entry)\n return\n }\n handlers.onDelivered?.(entry)\n } catch (error) {\n this.onError(entry.sessionId, error)\n handlers.onFailure(entry, error)\n }\n }\n\n /** Full-replacement patch of one delivered card; failures stay contained. */\n patch(entry: E, card: object): void {\n if (entry.messageId === undefined) return\n void this.channel.updateCard(entry.messageId, card)\n .catch((error: unknown) => { this.onError(entry.sessionId, error) })\n }\n\n /** Arm the local deadline for a request the host does not time itself. */\n armTimeout(entry: E, ms: number, onTimeout: () => void): void {\n entry.timer = setTimeout(onTimeout, ms)\n entry.timer.unref?.()\n }\n\n /**\n * Close one entry exactly once: clear the timer and abort listener, drop it\n * from the registry, then run the terminal action (usually a card patch).\n * @returns false when it was already settled.\n */\n close(entry: E, terminal: () => void): boolean {\n if (entry.settled) return false\n entry.settled = true\n if (entry.timer !== undefined) clearTimeout(entry.timer)\n if (entry.signal !== undefined && entry.onAbort !== undefined) {\n entry.signal.removeEventListener('abort', entry.onAbort)\n }\n this.entries.delete(entry.token)\n terminal()\n return true\n }\n\n /** Withdraw every pending entry (plugin shutdown). */\n withdrawAll(withdraw: (entry: E) => void): void {\n for (const entry of [...this.entries.values()]) withdraw(entry)\n }\n}\n","/**\n * HITL approval answerer: turns the host's `approval/request` waterfall\n * events into standalone Feishu decision cards.\n *\n * The answerer is registered with `prepend: true` on the plugin context so it\n * runs BEFORE generic answerers: a request for a session this channel routes\n * is claimed here, and the decision card becomes the only way to answer.\n * Requests for sessions the channel does NOT route delegate through `next()`.\n *\n * Rendering lives in `cards/approval.ts`; the shared pending-card lifecycle\n * (delivery, owner gating, timeout, settle-once, withdrawal) lives in\n * `interactions/pending.ts`.\n *\n * @module dsh-channels/interactions/approvals\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { buildApprovalCard, buildApprovalSettledCard } from '../cards/approval.ts'\nimport type { ApprovalCardState, ApprovalQuestion } from '../cards/approval.ts'\nimport type { ApprovalOutcome, ApprovalRequest } from '../dsh-types.ts'\nimport type { CardActionEvent, FeishuChannel } from '../feishu-channel.ts'\nimport { PendingCardTable } from './pending.ts'\nimport type { InteractionRoute, PendingCardEntry } from './pending.ts'\n\n/** Feishu placement of one session's approval cards. */\nexport type ApprovalRoute = InteractionRoute\n\nexport interface ApprovalHooks {\n /**\n * Resolve the Feishu placement for a session. `undefined` means the session\n * is not owned by this channel — the answerer delegates through `next()`.\n */\n routeFor(sessionId: string): ApprovalRoute | undefined\n /** Log a non-fatal error (delivery/update failures). */\n onError(sessionId: string, error: unknown): void\n}\n\n/** One in-flight approval question: its card content and its resolver. */\ninterface Pending extends PendingCardEntry {\n question: ApprovalQuestion\n resolve(outcome: ApprovalOutcome): void\n}\n\nexport class ApprovalAnswerer {\n private readonly table: PendingCardTable<Pending>\n\n constructor(\n channel: FeishuChannel,\n private readonly hooks: ApprovalHooks,\n private readonly timeoutMs: number,\n ) {\n this.table = new PendingCardTable(channel, hooks.onError)\n }\n\n /** In-flight question count (diagnostics/tests). */\n pendingCount(): number {\n return this.table.count()\n }\n\n /**\n * The `approval/request` waterfall listener. Never rejects: the host service\n * already contains answerer failures to the fail-closed `'unavailable'`.\n */\n onRequest(req: ApprovalRequest, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome> {\n const route = this.hooks.routeFor(req.agent.session.id)\n if (route === undefined) return next()\n // The abort window between the service's own signal check and this\n // listener is closed by settling synchronously: an aborted ask never\n // opens a question.\n if (req.signal?.aborted === true) return Promise.resolve('cancelled')\n\n const token = randomUUID()\n // `displayReason` (DSH 0.2.0) is presentation-only and locale-aware;\n // `reason` remains the audit-trail fallback.\n const displayReason = req.displayReason?.zh ?? req.displayReason?.en ?? req.reason\n const question: ApprovalQuestion = {\n token,\n toolName: req.toolName,\n ...(displayReason === undefined ? {} : { reason: displayReason }),\n timeoutMs: this.timeoutMs,\n sessionId: req.agent.session.id,\n }\n return new Promise<ApprovalOutcome>((resolve) => {\n const entry: Pending = {\n token,\n sessionId: req.agent.session.id,\n route,\n messageId: undefined,\n settled: false,\n timer: undefined,\n signal: req.signal,\n onAbort: undefined,\n unauthorizedNoted: false,\n question,\n resolve,\n }\n const onAbort = (): void => {\n this.settle(entry, 'cancelled', 'cancelled')\n }\n entry.onAbort = onAbort\n // Registered synchronously at promise creation so no abort can slip\n // between the ownership check and the card delivery; a residual abort\n // in the tiny window before addEventListener is still covered because\n // the host's decide() races the signal against this promise and\n // discards a late resolution (worst case: the card times out).\n req.signal?.addEventListener('abort', onAbort, { once: true })\n this.table.add(entry)\n void this.table.deliver(entry, {\n card: buildApprovalCard(question),\n onWithdrawn: e => this.table.patch(e, buildApprovalSettledCard(e.question, 'cancelled')),\n onDelivered: e => this.table.armTimeout(e, this.timeoutMs, () => {\n this.settle(e, 'rejected', 'expired')\n }),\n // No card → no human answerer → fail closed.\n onFailure: e => {\n this.table.close(e, () => {})\n e.resolve('unavailable')\n },\n })\n })\n }\n\n /**\n * Route one card button-callback event to its pending question. Unknown\n * cards and non-owner operators are ignored (a non-owner click adds the\n * owner-only notice to the card once); owner decisions settle the question.\n */\n onCardAction(event: CardActionEvent): void {\n const entry = this.table.resolveAction(event, candidate => {\n if (candidate.unauthorizedNoted) return\n candidate.unauthorizedNoted = true\n this.table.patch(candidate, buildApprovalCard(candidate.question, { unauthorizedNote: true }))\n })\n if (entry === undefined) return\n const decision = (event.action.value as { decision?: unknown } | undefined)?.decision\n if (decision === 'allow') this.settle(entry, 'allowed-once', 'allowed')\n else if (decision === 'deny') this.settle(entry, 'rejected', 'denied')\n }\n\n /** Withdraw every in-flight question (plugin shutdown). */\n dispose(): void {\n this.table.withdrawAll(entry => this.settle(entry, 'cancelled'))\n }\n\n /** Settle once, then patch the card to its terminal state. */\n private settle(entry: Pending, outcome: ApprovalOutcome, cardState?: ApprovalCardState): void {\n const closed = this.table.close(entry, () => {\n if (cardState !== undefined) {\n this.table.patch(entry, buildApprovalSettledCard(entry.question, cardState))\n }\n })\n if (closed) entry.resolve(outcome)\n }\n}\n","/**\n * Structured user-question cards (`user-questions/request`).\n *\n * @module dsh-channels/cards/question\n */\n\nimport { card, divider, markdown, note, standaloneCard, textButton } from './primitives.ts'\n\n// ---- structured user-question cards --------------------------------------\n\n/** Terminal states a user-question card can settle into. */\nexport type QuestionCardState = 'answered' | 'expired' | 'cancelled'\n\n/** One question as rendered (options are always present after normalization). */\nexport interface QuestionCardItem {\n id: string\n question: string\n detail?: string\n header?: string\n options: Array<{ label: string; description?: string }>\n multiSelect: boolean\n /** `plan-review` intent's approve label (presentation only). */\n intentApproved?: string\n}\n\n/** One pending question card's render input. */\nexport interface QuestionCardInput {\n /** Opaque token binding the card to one pending question set. */\n token: string\n questions: QuestionCardItem[]\n timeoutMs: number\n sessionId?: string\n /** Multi-select toggles accumulated so far, by question id. */\n selected?: Record<string, string[]>\n /** Question ids already answered (single-select picks). */\n answered?: readonly string[]\n /** Add the owner-only notice (a non-owner clicked and was ignored). */\n unauthorizedNote?: boolean\n}\n\n/** Cap on rendered strings so one question cannot approach the card budget. */\nconst QUESTION_TEXT_MAX_CHARS = 500\n/** Cap on rendered options per question (excess is reported, not dropped silently). */\nexport const MAX_QUESTION_OPTIONS = 8\n\nfunction clipQuestionText(text: string, max = QUESTION_TEXT_MAX_CHARS): string {\n return text.length <= max ? text : `${text.slice(0, max)}…`\n}\n\nfunction questionAnswered(input: QuestionCardInput, id: string): boolean {\n return (input.answered ?? []).includes(id)\n}\n\nfunction questionOptionButton(\n input: QuestionCardInput,\n question: QuestionCardItem,\n option: { label: string; description?: string },\n): object {\n const isMulti = question.multiSelect\n const selected = (input.selected?.[question.id] ?? []).includes(option.label)\n const approved = question.intentApproved === option.label\n const answered = questionAnswered(input, question.id)\n const type = isMulti\n ? (selected ? 'primary' : 'default')\n : (approved ? 'primary' : 'default')\n const label = isMulti && selected ? `✅ ${option.label}` : option.label\n return textButton({\n label: clipQuestionText(label, 80),\n type: answered ? 'default' : type,\n width: 'fill',\n disabled: answered,\n value: {\n decision: isMulti ? 'question-toggle' : 'question-answer',\n token: input.token,\n id: question.id,\n value: option.label,\n },\n })\n}\n\nfunction questionElements(input: QuestionCardInput): object[] {\n const elements: object[] = [markdown('**❓ 需要你的回答**')]\n input.questions.forEach((question, index) => {\n const heading = question.header === undefined || question.header === ''\n ? `**问题 ${index + 1}**`\n : `**问题 ${index + 1} · ${clipQuestionText(question.header, 80)}**`\n elements.push(markdown(`${heading}\\n${clipQuestionText(question.question)}`))\n if (question.detail !== undefined && question.detail !== '') {\n elements.push(markdown(`> ${clipQuestionText(question.detail, 1_200).replace(/\\s+/gu, ' ')}`))\n }\n const options = question.options.slice(0, MAX_QUESTION_OPTIONS)\n for (const option of options) {\n elements.push(questionOptionButton(input, question, option))\n }\n if (question.options.length === 0) {\n elements.push(note('本题没有预设选项,请直接回复文字作答。'))\n } else if (question.options.length > options.length) {\n elements.push(note(`另有 ${question.options.length - options.length} 个选项未展示,请在 Web 端回答。`))\n }\n if (question.multiSelect) {\n const selected = input.selected?.[question.id] ?? []\n if (selected.length > 0 && !questionAnswered(input, question.id)) {\n elements.push(textButton({\n label: '提交本题选择',\n type: 'primary',\n value: { decision: 'question-submit', token: input.token, id: question.id },\n }))\n }\n elements.push(note(`本题为多选:点选后请点“提交本题选择”(已选 ${selected.length} 项)。`))\n }\n })\n elements.push(divider())\n elements.push(note(`⏱ ${Math.max(1, Math.round(input.timeoutMs / 60_000))} 分钟内有效,超时后本轮提问失败 · 仅发起该会话的用户可操作 · 也可直接回复文字作答`))\n if (input.sessionId !== undefined) elements.push(note(`🔑 ${input.sessionId}`))\n if (input.unauthorizedNote === true) elements.push(note('ℹ️ 仅发起该会话的用户可操作此卡片'))\n return elements\n}\n\n/** The pending structured-question card (one card carries every question). */\nexport function buildQuestionCard(input: QuestionCardInput): object {\n return card(questionElements(input))\n}\n\nconst QUESTION_SETTLED_LINES: Record<QuestionCardState, string> = {\n answered: '✅ 已回答',\n expired: '⏱ 已超时未处理,本轮提问已失败',\n cancelled: '↩️ 提问已取消',\n}\n\n/** The terminal replacement after the question set settles; controls removed. */\nexport function buildQuestionSettledCard(input: QuestionCardInput, state: QuestionCardState): object {\n const elements: object[] = [\n ...input.questions.slice(0, 4).map((question, index) =>\n markdown(`**问题 ${index + 1}**:${clipQuestionText(question.question, 200)}`)),\n divider(),\n ]\n if (state === 'expired') {\n elements.push(note('提问窗口已关闭。可在下一条消息中要求模型重新提问。'))\n }\n return standaloneCard(`**${QUESTION_SETTLED_LINES[state]}**`, elements)\n}\n","/**\n * HITL structured-question answerer: turns the host's\n * `user-questions/request` waterfall events (the model's `ask_user_question`\n * tool) into standalone Feishu cards.\n *\n * The host's only shipped answerer is the Web client, so a Feishu-only\n * session otherwise fails every `ask_user_question` call with `NO_PROVIDER`.\n * This answerer is registered with `prepend: true` on the plugin context:\n * requests for a session this channel routes are claimed here, everything else\n * delegates through `next()`.\n *\n * Rendering lives in `cards/question.ts`; the shared pending-card lifecycle\n * lives in `interactions/pending.ts`.\n *\n * Deliberately unsupported in v1: questions that declare no options (a card\n * cannot ask for them); such a request fails with an explicit error instead of\n * hanging.\n *\n * @module dsh-channels/interactions/questions\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { buildQuestionCard, buildQuestionSettledCard } from '../cards/question.ts'\nimport type { QuestionCardInput, QuestionCardItem, QuestionCardState } from '../cards/question.ts'\nimport type { AskUserQuestionAnswer, AskUserQuestionItem, AskUserQuestionRequestEvent } from '../dsh-types.ts'\nimport type { CardActionEvent, FeishuChannel } from '../feishu-channel.ts'\nimport { PendingCardTable } from './pending.ts'\nimport type { InteractionRoute, PendingCardEntry } from './pending.ts'\n\nexport interface QuestionHooks {\n /**\n * Resolve the Feishu placement for a session. `undefined` means the session\n * is not owned by this channel — the answerer delegates through `next()`.\n */\n routeFor(sessionId: string): InteractionRoute | undefined\n /** Log a non-fatal error (delivery/update failures). */\n onError(sessionId: string, error: unknown): void\n}\n\n/** One in-flight question set: card content, partial answers, resolver. */\ninterface PendingQuestions extends PendingCardEntry {\n questions: QuestionCardItem[]\n /** Multi-select toggles not yet submitted, by question id. */\n selected: Map<string, string[]>\n /** Committed answers, by question id. */\n answered: Map<string, string[]>\n /** Free-text answers supplied by a plain reply, by question id. */\n custom: Map<string, string>\n /** Host owns the deadline (timed wait): no local timer is armed. */\n hostTimed: boolean\n resolve(answer: AskUserQuestionAnswer): void\n reject(error: unknown): void\n}\n\n/** Normalize one host question into its renderable form. */\nfunction normalizeQuestion(item: AskUserQuestionItem): QuestionCardItem {\n const intent = item.intent\n return {\n id: item.id,\n question: item.question,\n ...(item.detail === undefined ? {} : { detail: item.detail }),\n ...(item.header === undefined ? {} : { header: item.header }),\n options: (item.options ?? []).map(option => ({\n label: option.label,\n ...(option.description === undefined ? {} : { description: option.description }),\n })),\n multiSelect: item.multiSelect === true,\n ...(intent?.kind === 'plan-review' ? { intentApproved: intent.approve } : {}),\n }\n}\n\nexport class QuestionAnswerer {\n private readonly table: PendingCardTable<PendingQuestions>\n\n constructor(\n channel: FeishuChannel,\n private readonly hooks: QuestionHooks,\n private readonly timeoutMs: number,\n ) {\n this.table = new PendingCardTable(channel, hooks.onError)\n }\n\n /** In-flight question-set count (diagnostics/tests). */\n pendingCount(): number {\n return this.table.count()\n }\n\n /**\n * The `user-questions/request` waterfall listener. Non-owned sessions and\n * questions without options delegate through `next()`.\n */\n onRequest(\n request: AskUserQuestionRequestEvent,\n next: () => Promise<AskUserQuestionAnswer>,\n ): Promise<AskUserQuestionAnswer> {\n const agent = request.agent\n if (agent === undefined) return next()\n const route = this.hooks.routeFor(agent.session.id)\n if (route === undefined) return next()\n\n const questions = request.questions.map(normalizeQuestion)\n if (questions.length === 0) return next()\n\n const token = randomUUID()\n return new Promise<AskUserQuestionAnswer>((resolve, reject) => {\n const entry: PendingQuestions = {\n token,\n sessionId: agent.session.id,\n route,\n messageId: undefined,\n settled: false,\n timer: undefined,\n signal: request.signal,\n onAbort: undefined,\n unauthorizedNoted: false,\n questions,\n selected: new Map(),\n answered: new Map(),\n custom: new Map(),\n hostTimed: request.wait?.timed === true,\n resolve,\n reject,\n }\n const onAbort = (): void => {\n const reason = request.signal?.reason\n entry.reject(reason instanceof Error ? reason : new Error('dsh-channels: 提问已被取消。'))\n this.close(entry, 'cancelled')\n }\n entry.onAbort = onAbort\n if (request.signal?.aborted === true) {\n reject(new Error('dsh-channels: 提问已被取消。'))\n return\n }\n request.signal?.addEventListener('abort', onAbort, { once: true })\n this.table.add(entry)\n void this.table.deliver(entry, {\n card: buildQuestionCard(this.cardInput(entry)),\n onWithdrawn: e => this.table.patch(e, buildQuestionSettledCard(this.cardInput(e), 'cancelled')),\n // A timed wait is owned by the host: its signal settles the request at\n // the deadline (rejection with ASK_TIMED_OUT becomes a continued\n // question). Only blocking asks need a local timer.\n onDelivered: e => {\n if (e.hostTimed) return\n this.table.armTimeout(e, this.timeoutMs, () => {\n e.reject(new Error('dsh-channels: ask_user_question 等待超时,未收到回答。'))\n this.close(e, 'expired')\n })\n },\n onFailure: (e, error) => {\n this.table.close(e, () => {})\n e.reject(error instanceof Error ? error : new Error(String(error)))\n },\n })\n })\n }\n\n /** Snapshot the current selections for rendering. */\n private cardInput(entry: PendingQuestions): QuestionCardInput {\n const selected: Record<string, string[]> = {}\n for (const [id, labels] of entry.selected) selected[id] = [...labels]\n return {\n token: entry.token,\n questions: entry.questions,\n timeoutMs: this.timeoutMs,\n sessionId: entry.sessionId,\n selected,\n answered: [...entry.answered.keys()],\n ...(entry.unauthorizedNoted ? { unauthorizedNote: true } : {}),\n }\n }\n\n /**\n * Route one card button-callback event to its pending question set.\n * Unknown cards and non-owner operators are ignored (a non-owner click adds\n * the owner-only notice once); owner decisions settle the request.\n */\n onCardAction(event: CardActionEvent): void {\n const entry = this.table.resolveAction(event, candidate => {\n if (candidate.unauthorizedNoted) return\n candidate.unauthorizedNoted = true\n this.table.patch(candidate, buildQuestionCard(this.cardInput(candidate)))\n })\n if (entry === undefined) return\n const value = event.action.value as\n | { decision?: unknown; id?: unknown; value?: unknown }\n | undefined\n const decision = value?.decision\n const id = typeof value?.id === 'string' ? value.id : undefined\n const option = typeof value?.value === 'string' ? value.value : undefined\n if (id === undefined) return\n const question = entry.questions.find(item => item.id === id)\n if (question === undefined || entry.answered.has(id)) return\n\n if (decision === 'question-answer' && option !== undefined) {\n entry.answered.set(id, [option])\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n this.resolveIfComplete(entry)\n return\n }\n if (decision === 'question-toggle' && option !== undefined) {\n const current = new Set(entry.selected.get(id) ?? [])\n if (current.has(option)) current.delete(option)\n else current.add(option)\n entry.selected.set(id, [...current])\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n return\n }\n if (decision === 'question-submit') {\n const chosen = entry.selected.get(id) ?? []\n if (chosen.length === 0) return\n entry.answered.set(id, [...chosen])\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n this.resolveIfComplete(entry)\n }\n }\n\n /** Complete the waterfall once every question has a committed answer. */\n private resolveIfComplete(entry: PendingQuestions): void {\n if (entry.questions.some(question => !entry.answered.has(question.id))) return\n const answer: AskUserQuestionAnswer = {\n answers: entry.questions.map(question => ({\n id: question.id,\n selected: entry.answered.get(question.id) ?? [],\n ...(entry.custom.has(question.id) ? { custom: entry.custom.get(question.id) } : {}),\n })),\n }\n const closed = this.table.close(entry, () => {\n this.table.patch(entry, buildQuestionSettledCard(this.cardInput(entry), 'answered'))\n })\n if (closed) entry.resolve(answer)\n }\n\n /**\n * Feed one plain reply as a free-text answer.\n *\n * A session blocked on `ask_user_question` has no other use for an inbound\n * text message, so the next unanswered question consumes it as its \"Other\"\n * answer. Returns false when the session has no pending question.\n */\n submitText(sessionId: string, text: string): boolean {\n const trimmed = text.trim()\n if (trimmed === '') return false\n const entry = [...this.table.values()].find(candidate =>\n candidate.sessionId === sessionId && !candidate.settled)\n if (entry === undefined) return false\n const question = entry.questions.find(candidate => !entry.answered.has(candidate.id))\n if (question === undefined) return false\n entry.answered.set(question.id, [])\n entry.custom.set(question.id, trimmed)\n this.table.patch(entry, buildQuestionCard(this.cardInput(entry)))\n this.resolveIfComplete(entry)\n return true\n }\n\n /** Whether a session currently has an unanswered question set. */\n hasPendingFor(sessionId: string): boolean {\n return [...this.table.values()].some(candidate => candidate.sessionId === sessionId && !candidate.settled)\n }\n\n /** Withdraw every in-flight question (plugin shutdown). */\n dispose(): void {\n this.table.withdrawAll(entry => {\n entry.reject(new Error('dsh-channels: 插件已关闭,提问未完成。'))\n this.close(entry, 'cancelled')\n })\n }\n\n /** Settle once and patch the card to its terminal state. */\n private close(entry: PendingQuestions, state: QuestionCardState): void {\n this.table.close(entry, () => {\n this.table.patch(entry, buildQuestionSettledCard(this.cardInput(entry), state))\n })\n }\n}\n","/**\n * Streaming reply card: reasoning panel, activity lines, body segmentation\n * and the byte/table budgets that keep one card inside Feishu's limits.\n *\n * Two independent budgets: the reply body segments across cards at\n * `CARD_TARGET_BYTES` (never splitting reasoning), while the reasoning fold\n * panel is a fixed `REASONING_MAX_BYTES` (5 KB) window — clipped from the tail\n * with a truncation marker and never traded against the body in either\n * direction.\n *\n * @module dsh-channels/cards/streaming\n */\n\nexport type PanelPhase = 'thinking' | 'replying' | 'done' | 'error'\n\nexport const PANEL_TITLES: Record<PanelPhase, string> = {\n thinking: '推理中',\n replying: '回复中',\n done: '回复结束',\n error: '回复失败',\n}\n\n/** Built-in icon token for the fold-panel chevron (from the official component docs). */\nexport const PANEL_CHEVRON_TOKEN = 'down-small-ccm_outlined'\n\n/** Feishu hard cap for one card message body. */\nexport const CARD_MAX_BYTES = 30_000\n/** Target budget per card — 2 KB below the hard cap as headroom. */\nexport const CARD_TARGET_BYTES = 28_000\n/**\n * Hard byte cap for the reasoning fold panel's markdown (5 KB, JSON-escaped\n * UTF-8, including the truncation marker). The panel is clipped against this\n * alone — never against the card — so it neither competes with nor is squeezed\n * by the reply body: the visible reasoning window is identical whether the\n * answer is one line or a full 28 KB segment. Measured at the cap: ≈4.96 K\n * ASCII chars, ≈1.65 K CJK chars, ≈2.5 K emoji. The panel element's own\n * wrapper (border, header, chevron) is charged separately — see\n * `CARD_PANEL_RESERVE_BYTES`.\n */\nexport const REASONING_MAX_BYTES = 5_000\n/**\n * Bytes held back from the card target for the fold panel as a whole, so a\n * segmented card's body plus its panel stay inside `CARD_TARGET_BYTES`.\n * `REASONING_MAX_BYTES` caps the panel's markdown; the element wrapper around\n * it (border, header, chevron icon, `elements` array) measures a further 315 B\n * at the worst phase, and this reserve covers that with headroom.\n */\nexport const CARD_PANEL_RESERVE_BYTES = 5_500\n/**\n * Feishu hard-limits the number of markdown tables a single card may contain\n * (API error 11310 \"card table number over limit\"; verified on device: the\n * platform allows 5). Segmentation therefore keeps at most 5 tables per card.\n */\nexport const MAX_TABLES_PER_CARD = 5\n\n/** Appended to clipped reasoning (and, as an absolute last resort, clipped body). */\nexport const TRUNCATION_SUFFIX = '\\n\\n…(内容过长,已截断)'\n\n/** Session-scoped figures rendered on the info lines. */\nexport interface CardStats {\n /** DSH session id. */\n sessionId: string\n /** Cumulative uncached input tokens of the whole session. */\n inputTokens: number\n /** Cumulative output tokens of the whole session. */\n outputTokens: number\n /** Cache hit ratio of the current turn's latest provider sample, 0–1; undefined → `—`. */\n cacheHitPct?: number\n /** Latest provider-reported prompt pressure in tokens; undefined → `—`. */\n contextTokens?: number\n /** Model context window in tokens; undefined → `—`. */\n contextWindow?: number\n /** Model id (current turn's latest request header). */\n model?: string\n /** Reasoning effort (current turn's latest request header). */\n effort?: string\n}\n\nexport interface CardState {\n phase: PanelPhase\n /** Raw reasoning text accumulated so far (may be empty). */\n reasoning: string\n /** Reply body accumulated so far (may be empty). */\n body: string\n /**\n * Transient delegation status (subagent/workflow progress, settled workflow\n * outcome, heartbeat elapsed) rendered as a notation line between the panel\n * and the body — the FIRST of the two activity lines. Empty/undefined →\n * omitted. Callers cap its length so it stays inside the budget headroom.\n */\n status?: string\n /**\n * The parent agent's in-flight tool call, rendered as a notation line below\n * the status line — the SECOND activity line. Independent of `status`:\n * neither ever supersedes the other. Empty/undefined → omitted.\n */\n tool?: string\n stats: CardStats\n}\n\n/** Rendering variants. */\nexport interface CardOptions {\n /** Render the reasoning fold panel (default `true`). Continuation segment\n * cards pass `false` and get a segment header instead. */\n panel?: boolean\n /** 1-based segment number shown in the continuation header. */\n segment?: number\n}\n\n/** The result of splitting a body for one card. `head` + `tail` re-join to the\n * original body except for blank-line separators at the cut. */\nexport interface BodySplit {\n /** Body prefix that fits one card (cut at a clean breakpoint). */\n head: string\n /** Remainder to flow into the next segment card ('' when everything fits). */\n tail: string\n}\n\n/** UTF-8 byte size of a card's JSON — the metric behind Feishu's 30 KB cap. */\nexport function cardJsonBytes(card: object): number {\n return new TextEncoder().encode(JSON.stringify(card)).length\n}\n\n/** A contiguous run of markdown table lines; `end` is exclusive. */\nexport interface TableBlock {\n start: number\n end: number\n}\n\n/**\n * Locate markdown table blocks: runs of ≥ 2 consecutive lines that start with\n * `|` and contain a second `|` (outside fenced code blocks). Feishu renders\n * each such run as a `table` element and hard-limits their count per card\n * (error 11310), so tables are the atomic unit for segmentation.\n */\nexport function tableBlocks(body: string): TableBlock[] {\n const blocks: TableBlock[] = []\n let inFence = false\n let runStart = -1\n let runEnd = -1\n let runLines = 0\n let offset = 0\n for (const line of body.split('\\n')) {\n const fence = /^[ \\t]*```/.test(line)\n const pipeLine = !inFence && /^[ \\t]*\\|[^\\n]*\\|/.test(line)\n if (fence) inFence = !inFence\n if (pipeLine) {\n if (runLines === 0) runStart = offset\n runEnd = offset + line.length\n runLines++\n } else if (runLines > 0) {\n if (runLines >= 2) blocks.push({ start: runStart, end: runEnd })\n runStart = -1\n runLines = 0\n }\n offset += line.length + 1\n }\n if (runLines >= 2) blocks.push({ start: runStart, end: runEnd })\n return blocks\n}\n\n/**\n * Replace markdown table blocks with a placeholder. Used for the reasoning\n * panel, whose content is auxiliary (truncation has no impact) and is never\n * segmented — this keeps panel tables from tripping the per-card table cap.\n */\nexport function stripTables(text: string): string {\n const blocks = tableBlocks(text)\n if (blocks.length === 0) return text\n let out = ''\n let position = 0\n for (const block of blocks) {\n out += text.slice(position, block.start)\n if (out !== '' && !out.endsWith('\\n')) out += '\\n'\n out += '*(表格已省略)*'\n position = block.end\n }\n out += text.slice(position)\n return out\n}\n\nfunction fmt(n: number): string {\n return n.toLocaleString('en-US')\n}\n\nfunction fmtContext(tokens?: number, window?: number): string {\n if (tokens === undefined) return '—'\n if (window === undefined) return `${fmt(tokens)} tokens`\n return `${fmt(tokens)} / ${fmt(window)}`\n}\n\nfunction fmtCacheHit(pct?: number): string {\n return pct === undefined ? '—' : `${(pct * 100).toFixed(1)}%`\n}\n\n/** Info line 2: `icon - 模型 {model}({effort}) · icon - 缓存命中 {cache} · icon - 上下文 {context}` */\nfunction buildModelLine(stats: CardStats): string {\n const model = stats.model ?? '—'\n const effort = stats.effort !== undefined && stats.effort !== '' ? `(${stats.effort})` : ''\n return `🧠 模型 ${model}${effort} · ⚡ 缓存命中 ${fmtCacheHit(stats.cacheHitPct)} · 📊 上下文 ${fmtContext(stats.contextTokens, stats.contextWindow)}`\n}\n\n/** Info line 1: `icon - {session id} · icon - input token {input_token} · icon - output token {output_token}` */\nfunction buildSessionLine(stats: CardStats): string {\n return `🔑 ${stats.sessionId} · ⬆ input token ${fmt(stats.inputTokens)} · ⬇ output token ${fmt(stats.outputTokens)}`\n}\n\nfunction reasoningPlaceholder(phase: PanelPhase): string {\n return phase === 'thinking' ? '*等待模型输出…*' : '*(本次无推理内容)*'\n}\n\nfunction bodyPlaceholder(phase: PanelPhase): string {\n return phase === 'done' || phase === 'error' ? '*(本次无正文输出)*' : '*正在生成…*'\n}\n\n/** Continuation header: `📄 回复分段 {n} · {phase}` (or 续上一条卡片 without a number). */\nfunction segmentHeader(options: CardOptions, phase: PanelPhase): string {\n const seg = options.segment\n const title = seg === undefined ? '续上一条卡片' : `回复分段 ${seg}`\n return `📄 ${title} · ${PANEL_TITLES[phase]}`\n}\n\n/** Raw card assembly — no clipping of any kind (the budget machinery wraps this). */\nfunction assemble(\n phase: PanelPhase,\n reasoning: string,\n body: string,\n stats: CardStats,\n options: CardOptions,\n status?: string,\n tool?: string,\n): object {\n const elements: object[] = []\n\n if (options.panel ?? true) {\n const panelReasoning = stripTables(reasoning)\n elements.push({\n tag: 'collapsible_panel',\n expanded: false,\n border: { color: 'grey', corner_radius: '8px' },\n header: {\n title: { tag: 'plain_text', content: PANEL_TITLES[phase] },\n icon: { tag: 'standard_icon', token: PANEL_CHEVRON_TOKEN },\n icon_position: 'right',\n icon_expanded_angle: 180,\n },\n elements: [\n { tag: 'markdown', content: panelReasoning.trim() === '' ? reasoningPlaceholder(phase) : panelReasoning },\n ],\n })\n } else {\n elements.push({ tag: 'markdown', content: segmentHeader(options, phase), text_size: 'notation' })\n }\n\n if (status !== undefined && status !== '') {\n elements.push({ tag: 'markdown', content: status, text_size: 'notation' })\n }\n if (tool !== undefined && tool !== '') {\n elements.push({ tag: 'markdown', content: tool, text_size: 'notation' })\n }\n\n elements.push({ tag: 'hr' })\n elements.push({ tag: 'markdown', content: body.trim() === '' ? bodyPlaceholder(phase) : body })\n elements.push({ tag: 'hr' })\n elements.push({ tag: 'markdown', content: buildSessionLine(stats), text_size: 'notation' })\n elements.push({ tag: 'markdown', content: buildModelLine(stats), text_size: 'notation' })\n\n return {\n schema: '2.0',\n config: { width_mode: 'fill' },\n body: { elements },\n }\n}\n\n/**\n * Fit `text` into a byte budget measured by `render`: return `text` unchanged\n * when it already fits; otherwise binary-search the longest prefix that still\n * fits once `suffix` is appended. The result is guaranteed to satisfy the\n * same measurement within `budget`.\n */\nfunction clipText(text: string, suffix: string, render: (content: string) => number, budget: number): string {\n if (render(text) <= budget) return text\n let lo = 0\n let hi = text.length\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n if (render(text.slice(0, mid) + suffix) <= budget) {\n lo = mid\n } else {\n hi = mid - 1\n }\n }\n return text.slice(0, lo) + suffix\n}\n\n/** JSON-escaped UTF-8 size of a string — the metric Feishu's card cap counts. */\nfunction jsonBytes(text: string): number {\n return new TextEncoder().encode(JSON.stringify(text)).length\n}\n\n/**\n * JSON-escaped UTF-8 size of the fold panel's markdown, as rendered by one\n * assembled card. Reasoning is budgeted against THIS rather than against the\n * whole card: a card-wide budget makes the panel collapse to the bare\n * truncation marker as soon as the reply body grows, so the visible reasoning\n * window would depend on how long the answer is.\n */\nfunction panelMarkdownBytes(render: (reasoning: string) => object): (content: string) => number {\n return content => {\n const elements = (render(content) as { body?: { elements?: Array<{ elements?: Array<{ content?: string }> }> } })\n .body?.elements\n return jsonBytes(elements?.[0]?.elements?.[0]?.content ?? '')\n }\n}\n\n/** JSON-escaped UTF-8 size of the truncation marker — reserved so clips can\n * never push a card past its budget. */\nconst SUFFIX_JSON_BYTES = jsonBytes(TRUNCATION_SUFFIX)\n\n/**\n * Build one card.\n *\n * - Reasoning is clipped so its markdown stays within `REASONING_MAX_BYTES`\n * (including the truncation marker): a fixed, self-contained budget that\n * neither grants space to nor borrows space from the body, so the visible\n * reasoning window is the same whether the reply is one line or a 28 KB\n * segment.\n * - The body gets whatever is left of `CARD_MAX_BYTES` once the clipped\n * reasoning is accounted for, which keeps the total under Feishu's hard cap.\n * The streamer pre-splits with `segmentCardContent` (probed at the\n * worst-case phase), so a live stream never reaches this clip: it is a pure\n * guard for direct callers.\n *\n * Guarantee: the returned card is always ≤ `CARD_MAX_BYTES`, and its panel is\n * always ≤ `REASONING_MAX_BYTES`.\n */\nexport function buildCard(state: CardState, options: CardOptions = {}): object {\n const render = (reasoning: string, body: string) =>\n assemble(state.phase, reasoning, body, state.stats, options, state.status, state.tool)\n\n // 1. The panel, against its own budget. The panel markdown (and therefore the\n // visible reasoning window) does not depend on the body, the phase title,\n // or the info lines — only on the reasoning text and this cap.\n const panelBytes = panelMarkdownBytes(content => render(content, ''))\n const reasoning = clipText(state.reasoning, TRUNCATION_SUFFIX, panelBytes, REASONING_MAX_BYTES)\n\n // 2. The body, against what is left of the hard cap once the panel's real\n // bytes are counted. The suffix reserve keeps a clipped body sendable.\n const body = clipText(state.body, TRUNCATION_SUFFIX, content => cardJsonBytes(render('', content)),\n CARD_MAX_BYTES - panelBytes(reasoning) - SUFFIX_JSON_BYTES)\n return render(reasoning, body)\n}\n\n/** Interruption notice body for cards repaired after a restart mid-turn. */\nexport const INTERRUPTION_NOTICE = '⚠️ 服务已重启,本次回复中断,请重新发送消息。'\n\n/**\n * Terminal \"interrupted\" card used by the restart repair pass (`repairInflight`).\n *\n * Deliberately NOT built through `buildCard`: the local process has no copy of\n * the partial body/reasoning the card previously streamed (patch is full\n * replacement), and fabricating zero-valued token stats would be misleading.\n * The card is therefore minimal — a plain notice (+ segment header on\n * continuation cards) — and always far inside the size budget.\n */\nexport function buildInterruptionCard(options: CardOptions = {}): object {\n const elements: object[] = []\n if (options.panel === false) {\n elements.push({\n tag: 'markdown',\n content: `📄 ${options.segment === undefined ? '续上一条卡片' : `回复分段 ${options.segment}`} · 回复中断`,\n text_size: 'notation',\n })\n }\n elements.push({ tag: 'markdown', content: INTERRUPTION_NOTICE })\n return {\n schema: '2.0',\n config: { width_mode: 'fill' },\n body: { elements },\n }\n}\n\n/**\n * Largest body prefix (char count) whose card renders within `CARD_TARGET_BYTES`\n * minus `CARD_PANEL_RESERVE_BYTES`, reserving the panel's whole footprint for\n * the reasoning that travels with the segment. `segmentCardContent` only ever\n * segments bodies, so reasoning is never a segment budget input by construction\n * — but the panel still occupies the card, so its cap is held back. The probe renders\n * at phase `error`, the worst case (its segment header title is the longest),\n * so a frozen segment can never outgrow the budget merely by the phase flipping\n * 回复中 → 回复失败; stats growth between the split and the final patch consumes\n * the 2 KB redundancy (`CARD_MAX_BYTES` − `CARD_TARGET_BYTES`).\n */\nfunction largestBodyPrefix(state: CardState, options: CardOptions): number {\n const text = state.body\n const budget = CARD_TARGET_BYTES - CARD_PANEL_RESERVE_BYTES\n // `panel: false` keeps the reserved panel cap out of the measurement itself.\n const render = (content: string) =>\n assemble('error', '', content, state.stats, { ...options, panel: false }, state.status, state.tool)\n if (cardJsonBytes(render(text)) <= budget) return text.length\n let lo = 0\n let hi = text.length\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n if (cardJsonBytes(render(text.slice(0, mid))) <= budget) {\n lo = mid\n } else {\n hi = mid - 1\n }\n }\n return lo\n}\n\n/**\n * Split a body at a clean breakpoint so `head` fits one card:\n * 1. the head holds at most `maxTables` whole markdown tables — the boundary\n * is capped at the first excess table (Feishu rejects cards with more);\n * 2. never inside a fenced code block (pull the boundary back before the fence);\n * 3. never inside a table block (a breakpoint that would land inside one is\n * moved to its start);\n * 4. then the clean-breakpoint tiers: paragraph blank line → `#` heading line\n * → line break → hard cut as the escape hatch.\n */\nexport function splitBodyForCard(body: string, maxChars: number, maxTables: number = MAX_TABLES_PER_CARD): BodySplit {\n const tables = tableBlocks(body)\n if (body.length <= maxChars && tables.length <= maxTables) {\n return { head: body, tail: '' }\n }\n\n let limit = maxChars\n\n // 1. Table-count ceiling: the head may hold at most `maxTables` tables, so\n // never extend past the boundary right before the first excess table.\n const firstExcess = tables[maxTables]\n if (firstExcess !== undefined) {\n limit = Math.min(limit, firstExcess.start)\n }\n\n // 2. Never cut inside a fenced code block straddling the ceiling.\n const fencePattern = /^[ \\t]*```[^\\n]*$/gm\n let fenceStart = -1\n let inside = false\n for (const match of body.matchAll(fencePattern)) {\n const index = match.index ?? 0\n if (!inside) {\n if (index >= limit) break\n inside = true\n fenceStart = index\n } else if (index >= limit) {\n // The block opened before the ceiling and closes after it: move the\n // boundary to just before the opening fence (unless it starts at 0).\n if (fenceStart > 0) limit = fenceStart\n break\n } else {\n inside = false\n }\n }\n if (inside && fenceStart > 0 && fenceStart < limit) limit = fenceStart\n\n // 3. Never cut inside a table block straddling the ceiling (pull the\n // boundary back to its start; a table beginning the body is left to the\n // hard-cut escape hatch).\n for (const block of tables) {\n if (block.start >= limit) break\n if (block.end > limit) {\n if (block.start > 0) limit = block.start\n break\n }\n }\n\n /** Move a cut position out of any table block (to just before it). */\n const avoidTables = (at: number): number => {\n for (const block of tables) {\n if (block.start >= at) break\n if (at <= block.end && block.start > 0) return block.start\n }\n return at\n }\n\n const window = body.slice(0, limit)\n const cut = (at: number): BodySplit => ({\n head: body.slice(0, at).replace(/\\s+$/u, ''),\n tail: body.slice(at).replace(/^\\n+/u, ''),\n })\n\n // Preferred breakpoints scanning back from the ceiling; only accept one that\n // keeps the card reasonably full, else fall through to the next tier.\n const minFill = Math.floor(limit * 0.7)\n\n const paragraph = window.lastIndexOf('\\n\\n')\n if (paragraph >= minFill) return cut(avoidTables(paragraph + 2))\n\n let heading = -1\n for (const match of window.matchAll(/^#{1,6} .*$/gm)) {\n const start = match.index ?? 0\n heading = start === 0 ? 0 : start - 1\n }\n if (heading >= minFill) return cut(avoidTables(heading))\n\n const line = window.lastIndexOf('\\n')\n if (line >= minFill) return cut(avoidTables(line + 1))\n\n return { head: body.slice(0, avoidTables(limit)), tail: body.slice(avoidTables(limit)) }\n}\n\n/**\n * How the accumulated body should be laid out for one card: everything that\n * fits (clean breakpoint) plus the remainder for the next segment card.\n * `head` re-renders within `CARD_TARGET_BYTES`; the body is never dropped.\n */\nexport function segmentCardContent(state: CardState, options: CardOptions = {}): BodySplit {\n return splitBodyForCard(state.body, largestBodyPrefix(state, options))\n}\n\n/**\n * The initial (empty) streaming card for one session: thinking phase, empty\n * body and reasoning, zeroed stats. The service sends it as the topic-creating\n * first reply and then streams into the same message.\n */\nexport function initialStreamingCard(sessionId: string): object {\n const state: CardState = {\n phase: 'thinking',\n reasoning: '',\n body: '',\n stats: { sessionId, inputTokens: 0, outputTokens: 0 },\n }\n return buildCard(state)\n}\n\n/**\n * One-time onboarding card: what the bot does, how workspaces are scoped, and\n * the command cheat sheet. Sent on the first private message of a chat.\n */\nexport function buildOnboardingCard(): object {\n return {\n schema: '2.0',\n config: { width_mode: 'fill' },\n body: {\n elements: [\n { tag: 'markdown', content: '**👋 欢迎使用飞书 DSH**' },\n {\n tag: 'markdown',\n content: '直接发消息即可开始一个会话,我会在话题里流式回复;话题内继续发消息无需再 @ 我。',\n },\n {\n tag: 'markdown',\n content: '**工作区**:私聊一人一个;群里每位成员各自一个,互不影响。',\n },\n {\n tag: 'markdown',\n content: '**常用命令**:`/help` 命令列表 · `/status` 会话状态 · `/workspace` 工作目录\\n'\n + '`/effort` 推理强度 · `/model` 模型 · `/permissions` 权限预设\\n'\n + '`/new` 新建会话 · `/stop` 中断当前回合 · `/history` 历史 · `/export` 导出日志',\n },\n {\n tag: 'markdown',\n content: '需要授权或补充信息时,我会发一张卡片,点按钮或直接回复即可。',\n text_size: 'notation',\n },\n ],\n },\n }\n}\n","/**\n * The channel's slash-command registry: one declarative source for the\n * command vocabulary, admission rules, `/help` rendering, and dispatch.\n *\n * Adding a command means adding one entry here (plus its handler): the\n * supported-name list, the help text, and the admission gate all derive from\n * this table.\n *\n * @module dsh-channels/commands/registry\n */\n\n/** Help grouping, in render order. */\nexport const COMMAND_GROUPS = ['基础', '会话', '模型与上下文', '导出'] as const\nexport type CommandGroup = typeof COMMAND_GROUPS[number]\n\nexport interface ChannelCommandSpec {\n /** Canonical command name (without the leading slash). */\n readonly name: string\n readonly group: CommandGroup\n readonly description: string\n /** Argument hint shown in help, e.g. `[id|reset]`. */\n readonly usage?: string\n /** Answerable without any Session work (and inside a retired topic). */\n readonly routeFree?: boolean\n /** Answerable with or without a bound Session: bound runs on the command plane. */\n readonly sessionOptional?: boolean\n /** Unthreaded group messages require a bot mention before the command answers. */\n readonly mentionGate?: boolean\n /** Only usable in this chat type. */\n readonly chatType?: 'p2p' | 'group'\n /** Answer with a direct reply instead of creating a topic. */\n readonly directReply?: boolean\n /**\n * Answered by the channel itself (`/stop`), never through the agent command\n * plane, and never cold-resuming a Session just to answer.\n */\n readonly channelPlane?: boolean\n /**\n * Registered inside each Agent scope for the agent command plane. Defaults\n * to true; `false` for route-free commands, channel-plane commands, and the\n * native host `/compact`.\n */\n readonly agentScoped?: boolean\n}\n\n/** The complete command vocabulary. */\nexport const CHANNEL_COMMANDS = [\n {\n name: 'help', group: '基础', description: '显示当前支持的斜杠命令',\n routeFree: true, directReply: false,\n },\n {\n name: 'userid', group: '基础', description: '在私聊中显示当前用户 ID',\n routeFree: true, mentionGate: true, chatType: 'p2p', directReply: true, agentScoped: false,\n },\n {\n name: 'groupid', group: '基础', description: '在群聊中显示当前群组 ID',\n routeFree: true, mentionGate: true, chatType: 'group', directReply: true, agentScoped: false,\n },\n {\n name: 'health', group: '基础', description: '显示 Harness 与飞书通道健康状态',\n sessionOptional: true,\n },\n {\n name: 'status', group: '会话', description: '显示当前 Session 状态与性能指标',\n },\n {\n name: 'workspace', group: '会话', description: '显示当前 Session 工作目录',\n },\n {\n name: 'permissions', group: '会话', description: '查看(或按配置切换)当前 Session 权限预设',\n usage: '[name]', channelPlane: true, agentScoped: false,\n },\n {\n name: 'auth', group: '会话', description: '查看/管理访问授权(仅管理员)',\n usage: '[status|allow|revoke|list|user add <open_id>|user remove <open_id>]',\n routeFree: true, channelPlane: true, agentScoped: false,\n },\n {\n name: 'renew', group: '会话', description: '在当前话题创建并绑定新 Session',\n },\n {\n name: 'stop', group: '会话', description: '停止当前正在运行的回合',\n channelPlane: true, agentScoped: false,\n },\n {\n name: 'effort', group: '模型与上下文', description: '查看或切换当前 Session 推理强度',\n usage: '[id|reset]',\n },\n {\n name: 'model', group: '模型与上下文', description: '查看或切换当前 Session 模型',\n usage: '[id|provider/model|reset]',\n },\n {\n name: 'compact', group: '模型与上下文', description: '压缩当前 Session 上下文', agentScoped: false,\n },\n {\n name: 'history', group: '导出', description: '显示当前话题的 Session 历史',\n },\n {\n name: 'export', group: '导出', description: '导出当前 Session 的逻辑日志',\n },\n] as const satisfies readonly ChannelCommandSpec[]\n\n/** Canonical command name literal union. */\nexport type ChannelCommandName = typeof CHANNEL_COMMANDS[number]['name']\n\n/** User-facing aliases, resolved to their canonical command. */\nexport const COMMAND_ALIASES = { new: 'renew' } as const\nexport type ChannelCommandAlias = keyof typeof COMMAND_ALIASES\n\n/** Any command name a user may type. */\nexport type SupportedCommandName = ChannelCommandName | ChannelCommandAlias\n\nconst BY_NAME = new Map<string, ChannelCommandSpec>(\n CHANNEL_COMMANDS.map(spec => [spec.name, spec as ChannelCommandSpec]),\n)\n\n/** Resolve one typed name (canonical or alias) to its command spec. */\nexport function commandSpec(name: string): ChannelCommandSpec | undefined {\n const canonical = (COMMAND_ALIASES as Record<string, string>)[name] ?? name\n return BY_NAME.get(canonical)\n}\n\n/** Canonical command names, in registry order. */\nexport function commandNames(): readonly ChannelCommandName[] {\n return CHANNEL_COMMANDS.map(spec => spec.name)\n}\n\n/** Every typeable name (canonical + aliases), for the supported-name list. */\nexport function supportedCommandNames(): readonly SupportedCommandName[] {\n return [...commandNames(), ...Object.keys(COMMAND_ALIASES)] as SupportedCommandName[]\n}\n\n/** `用法:/effort [id|reset]` line, or undefined without a usage hint. */\nexport function commandUsage(spec: ChannelCommandSpec): string | undefined {\n if (spec.usage === undefined) return undefined\n return `/${spec.name} ${spec.usage}`\n}\n\n/** Grouped `/help` markdown, derived from the registry. */\nexport function groupedCommandHelp(): string {\n const sections: string[] = []\n const specs: readonly ChannelCommandSpec[] = CHANNEL_COMMANDS\n for (const group of COMMAND_GROUPS) {\n const lines = specs\n .filter(spec => spec.group === group)\n .map(spec => {\n const usage = commandUsage(spec)\n return `**/${spec.name}**${usage === undefined ? '' : ` ${spec.usage}`} — ${spec.description}`\n })\n if (lines.length > 0) sections.push(`**${group}**\\n${lines.join('\\n')}`)\n }\n return sections.join('\\n\\n')\n}\n","/**\n * Feishu slash-command admission primitives.\n *\n * This module deliberately contains no channel or Host I/O. It owns the\n * product command vocabulary, exact parsing, agent-scoped registrations,\n * per-route admission serialization, and standalone result-card rendering.\n * Command-specific folds and side effects are added by the later SC tasks.\n *\n * @module dsh-channels/commands\n */\n\nimport type {\n AgentContext, CommandDefinition, CommandDescriptor, CommandResult, CommandRuntime,\n} from './dsh-types.ts'\nimport { card } from './cards/primitives.ts'\n\nimport {\n CHANNEL_COMMANDS, commandNames, groupedCommandHelp, commandSpec, supportedCommandNames,\n} from './commands/registry.ts'\nimport type { ChannelCommandSpec, SupportedCommandName } from './commands/registry.ts'\n\n/** Canonical channel command names, in registry order (agent-scoped ones included). */\nexport const CHANNEL_COMMAND_NAMES = commandNames()\n\n/** Every typeable command name (canonical + aliases). */\nexport const SUPPORTED_COMMAND_NAMES = supportedCommandNames()\n\nexport type { SupportedCommandName }\n\nexport interface ParsedSlashCommand {\n readonly name: string\n /** Exact text following the command name, including separator whitespace. */\n readonly rawInput: string\n}\n\n/** Match Harness' lowercase command grammar without normalizing arguments. */\nexport function parseSlashCommand(line: string): ParsedSlashCommand | undefined {\n const match = /^\\/([a-z][a-z0-9_-]*)(?=$|[\\t\\n\\r ])/u.exec(line)\n if (match === null) return undefined\n const name = match[1]\n if (name === undefined) return undefined\n return { name, rawInput: line.slice(match[0].length) }\n}\n\nexport function isSupportedCommand(name: string): name is SupportedCommandName {\n return commandSpec(name) !== undefined\n}\n\n/** Stable display used by unknown-command and deployment diagnostics. */\nexport function supportedCommandList(): string {\n return SUPPORTED_COMMAND_NAMES.map(name => `/${name}`).join('、')\n}\n\n/** Markdown help, grouped and generated from the command registry. */\nexport function supportedCommandHelp(): string {\n return groupedCommandHelp()\n}\n\n/** Commands registered inside each Agent scope (route-free ones are excluded). */\nconst ALL_COMMANDS: readonly ChannelCommandSpec[] = CHANNEL_COMMANDS\nconst AGENT_SCOPED_COMMANDS = ALL_COMMANDS.filter(spec => spec.agentScoped !== false)\n\nconst DESCRIPTORS: ReadonlyArray<CommandDescriptor> = AGENT_SCOPED_COMMANDS.map(spec => ({\n name: spec.name,\n description: spec.description,\n ...(spec.usage === undefined ? {} : { input: { hint: spec.usage } }),\n}))\n\nfunction pendingHandler(name: string): CommandDefinition['handler'] {\n return ({ rawInput }): CommandResult => {\n if (rawInput.trim() !== '') {\n return { kind: 'error', text: `用法错误:/${name} 不接受参数。` }\n }\n return { kind: 'error', text: `命令 /${name} 的功能将在后续实施任务中启用。` }\n }\n}\n\nfunction readyHandler(name: string, text: string): CommandDefinition['handler'] {\n return ({ rawInput }): CommandResult => {\n if (rawInput.trim() !== '') {\n return { kind: 'error', text: `用法错误:/${name} 不接受参数。` }\n }\n return { kind: 'success', text }\n }\n}\n\nconst healthHandler: CommandDefinition['handler'] = ({ rawInput }): CommandResult => {\n if (rawInput.trim() !== '') {\n return { kind: 'error', text: '用法错误:/health 不接受参数。' }\n }\n return { kind: 'success', text: '健康检查已完成。' }\n}\n\ntype RenewOperation = (invocation: Parameters<CommandDefinition['handler']>[0]) =>\n ReturnType<CommandDefinition['handler']>\n\nexport type EffortCommandAction =\n | { readonly kind: 'show' }\n | { readonly kind: 'set'; readonly value: string }\n | { readonly kind: 'reset' }\n\ntype EffortOperation = (\n invocation: Parameters<CommandDefinition['handler']>[0],\n action: EffortCommandAction,\n) => ReturnType<CommandDefinition['handler']>\n\n/** One armed `/model` execution: lists, validates, and switches models. */\nexport type ModelOperation = (\n invocation: Parameters<CommandDefinition['handler']>[0],\n rawInput: string,\n) => ReturnType<CommandDefinition['handler']>\n\n/** Per-execution authority: handlers stay instance-neutral when an Agent is borrowed. */\nconst RENEW_OPERATIONS = new WeakMap<AbortSignal, RenewOperation>()\nconst EFFORT_OPERATIONS = new WeakMap<AbortSignal, EffortOperation>()\nconst MODEL_OPERATIONS = new WeakMap<AbortSignal, ModelOperation>()\n\nfunction parseEffortAction(rawInput: string): EffortCommandAction | undefined {\n const value = rawInput.trim()\n if (value === '') return { kind: 'show' }\n if (/\\s/u.test(value)) return undefined\n return value === 'reset' ? { kind: 'reset' } : { kind: 'set', value }\n}\n\nconst effortHandler: CommandDefinition['handler'] = invocation => {\n const action = parseEffortAction(invocation.rawInput)\n if (action === undefined) {\n return { kind: 'error', text: '用法错误:/effort [档位-id|reset]。' }\n }\n if (action.kind !== 'show' && (\n invocation.agent.status !== 'idle'\n || invocation.agent.inbox.nextTurn.length > 0\n || invocation.agent.inbox.nextStep.length > 0\n )) {\n return { kind: 'error', text: '当前 Session 正忙或仍有排队消息,暂时不能切换推理强度。' }\n }\n const operation = EFFORT_OPERATIONS.get(invocation.signal)\n if (operation === undefined) {\n return { kind: 'error', text: '/effort 只能通过已授权的飞书话题执行。' }\n }\n return operation(invocation, action)\n}\n\nconst modelHandler: CommandDefinition['handler'] = invocation => {\n const rawInput = invocation.rawInput.trim()\n if (/\\s/u.test(rawInput)) {\n return { kind: 'error', text: '用法错误:/model [模型-id|provider/model|reset]。' }\n }\n if (invocation.agent.status !== 'idle'\n || invocation.agent.inbox.nextTurn.length > 0\n || invocation.agent.inbox.nextStep.length > 0) {\n return { kind: 'error', text: '当前 Session 正忙或仍有排队消息,暂时不能切换模型。' }\n }\n const operation = MODEL_OPERATIONS.get(invocation.signal)\n if (operation === undefined) {\n return { kind: 'error', text: '/model 只能通过已授权的飞书话题执行。' }\n }\n return operation(invocation, rawInput)\n}\n\nconst renewHandler: CommandDefinition['handler'] = invocation => {\n if (invocation.rawInput.trim() !== '') {\n return { kind: 'error', text: '用法错误:/renew 不接受参数。' }\n }\n if (invocation.agent.status !== 'idle'\n || invocation.agent.inbox.nextTurn.length > 0\n || invocation.agent.inbox.nextStep.length > 0) {\n return { kind: 'error', text: '当前 Session 正忙或仍有排队消息,暂时不能续建。' }\n }\n const operation = RENEW_OPERATIONS.get(invocation.signal)\n if (operation === undefined) {\n return { kind: 'error', text: '/renew 只能通过已授权的飞书话题执行。' }\n }\n return operation(invocation)\n}\n\nconst READY_HANDLERS: Readonly<Record<string, CommandDefinition['handler']>> = {\n help: readyHandler('help', supportedCommandHelp()),\n health: healthHandler,\n status: readyHandler('status', 'Session 状态已读取。'),\n workspace: readyHandler('workspace', '工作目录已读取。'),\n effort: effortHandler,\n model: modelHandler,\n renew: renewHandler,\n history: readyHandler('history', 'Session 历史已读取。'),\n export: readyHandler('export', '导出已请求。'),\n}\n\n/** Arm exactly one Feishu-issued `/renew` execution, then return its disarmer. */\nexport function armRenewCommand(signal: AbortSignal, operation: RenewOperation): () => void {\n RENEW_OPERATIONS.set(signal, operation)\n return () => { RENEW_OPERATIONS.delete(signal) }\n}\n\n/** Arm exactly one Feishu-issued `/effort` execution, then return its disarmer. */\nexport function armEffortCommand(signal: AbortSignal, operation: EffortOperation): () => void {\n EFFORT_OPERATIONS.set(signal, operation)\n return () => { EFFORT_OPERATIONS.delete(signal) }\n}\n\n/** Arm exactly one Feishu-issued `/model` execution, then return its disarmer. */\nexport function armModelCommand(signal: AbortSignal, operation: ModelOperation): () => void {\n MODEL_OPERATIONS.set(signal, operation)\n return () => { MODEL_OPERATIONS.delete(signal) }\n}\n\n/** Install only `/model` into a borrowed Agent scope that lacks channel commands. */\nexport function registerModelCommand(commands: CommandRuntime): () => void {\n const descriptor = DESCRIPTORS.find(entry => entry.name === 'model')!\n return commands.register({ ...descriptor, handler: modelHandler })\n}\n\n/** Install only `/renew` into a borrowed Agent scope that lacks channel commands. */\nexport function registerRenewCommand(commands: CommandRuntime): () => void {\n const descriptor = DESCRIPTORS.find(entry => entry.name === 'renew')!\n return commands.register({ ...descriptor, handler: renewHandler })\n}\n\n/** Install only `/effort` into a borrowed Agent scope that lacks channel commands. */\nexport function registerEffortCommand(commands: CommandRuntime): () => void {\n const descriptor = DESCRIPTORS.find(entry => entry.name === 'effort')!\n return commands.register({ ...descriptor, handler: effortHandler })\n}\n\n/**\n * Register this channel's commands inside one agent scope. `/compact` stays\n * global and is intentionally not registered here.\n */\nexport function registerChannelCommands(commands: CommandRuntime): void {\n for (const descriptor of DESCRIPTORS) {\n commands.register({\n ...descriptor,\n handler: READY_HANDLERS[descriptor.name] ?? pendingHandler(descriptor.name),\n })\n }\n}\n\nfunction registerScopedChannelCommands(ctx: AgentContext): void {\n registerChannelCommands(ctx.commands)\n}\n\n/**\n * Mount channel commands through an Agent child plugin that explicitly\n * injects the command service. The Agent scope is minted by `agent-loop`, so\n * it does not inherit this package's top-level dependency declaration.\n */\nexport async function mountChannelCommands(agentCtx: AgentContext): Promise<void> {\n await agentCtx.inject(['commands'], registerScopedChannelCommands)\n}\n\n/**\n * FIFO admission queue keyed by the durable route key. Failures never poison\n * the tail, and an idle key is retired so the map remains bounded.\n */\nexport class RouteOperationQueue {\n private readonly tails = new Map<string, Promise<void>>()\n\n async run<T>(key: string, operation: () => Promise<T> | T): Promise<T> {\n const previous = this.tails.get(key) ?? Promise.resolve()\n const result = previous.catch(() => {}).then(operation)\n const tail = result.then(() => {}, () => {})\n this.tails.set(key, tail)\n try {\n return await result\n } finally {\n if (this.tails.get(key) === tail) this.tails.delete(key)\n }\n }\n\n clear(): void {\n this.tails.clear()\n }\n}\n\nexport type CommandCardKind =\n | 'success'\n | 'help'\n | 'context-error'\n | 'input-error'\n | 'unknown'\n | 'unbound'\n | 'expired'\n | 'forbidden'\n | 'busy'\n | 'unavailable'\n | 'internal-error'\n\nexport interface CommandCardState {\n readonly kind: CommandCardKind\n readonly command?: string\n readonly text: string\n readonly supported?: string\n readonly sessionId?: string\n}\n\n/** Map stable command outcomes onto the shared result-card vocabulary. */\nexport function commandResultCardKind(result: CommandResult): CommandCardKind {\n if (result.kind === 'success') return 'success'\n if (/^(?:用法错误:|Usage:)/u.test(result.text)) return 'input-error'\n if (/\\b(?:active compaction|not idle|busy)\\b|正忙|正在运行/iu.test(result.text)) return 'busy'\n return 'unavailable'\n}\n\nconst CARD_TITLES: Record<CommandCardKind, string> = {\n success: '✅ 命令执行完成',\n help: '📖 斜杠命令帮助',\n 'context-error': '⚠️ 使用场景不匹配',\n 'input-error': '⚠️ 命令格式不正确',\n unknown: '❓ 未知命令',\n unbound: '🔗 尚未绑定会话',\n expired: '⌛ 话题已过期',\n forbidden: '🔒 无权操作此会话',\n busy: '⏳ 会话正忙',\n unavailable: '🧩 命令暂不可用',\n 'internal-error': '❌ 命令执行失败',\n}\n\n/** Render a small, non-streaming Feishu card that never contains raw errors. */\nexport function buildCommandResultCard(state: CommandCardState): object {\n const elements: object[] = [\n { tag: 'markdown', content: `**${CARD_TITLES[state.kind]}**` },\n { tag: 'markdown', content: state.text },\n ]\n if (state.supported !== undefined) {\n elements.push({ tag: 'markdown', content: `可用命令:${state.supported}`, text_size: 'notation' })\n }\n if (state.command !== undefined || state.sessionId !== undefined) {\n const trace = [\n state.command === undefined ? undefined : `/${state.command}`,\n state.sessionId,\n ].filter((value): value is string => value !== undefined).join(' · ')\n elements.push({ tag: 'markdown', content: trace, text_size: 'notation' })\n }\n return card(elements)\n}\n","/**\n * Durable routing domain: thread/chat keys → bound DSH session records.\n *\n * Opened through the host's `ctx.storageDomain` facility; the spec is plain\n * data (no runtime import of DSH packages), so the plugin carries zero\n * runtime dependency on host internals — everything flows through `ctx`.\n *\n * @module dsh-channels/domain\n */\n\nimport { z } from 'zod'\n\n/** Route key kinds. `thread` = real Feishu topic; `chat` = degraded chat-scoped fallback. */\nexport const ROUTE_RECORD_VERSION = 1\n\nexport const sessionHistoryEntrySchema = z.object({\n /** One Session that has been bound to this route. */\n sessionId: z.string().min(1),\n /** Epoch ms when the Session was created. */\n createdAt: z.number().int().nonnegative(),\n})\n\nexport type SessionHistoryEntry = z.infer<typeof sessionHistoryEntrySchema>\n\nexport const routeRecordSchema = z.object({\n /** Route key the record is stored under (thread_id or chat:<chatId>). */\n key: z.string().min(1),\n /** Kind of the key. */\n kind: z.enum(['thread', 'chat']),\n /** Bound DSH session id. */\n sessionId: z.string().min(1),\n /** Feishu chat the session lives in. */\n chatId: z.string().min(1),\n /** `p2p` or `group`. */\n chatType: z.enum(['p2p', 'group']),\n /** Workspace owner (sender open_id). */\n senderOpenId: z.string().min(1),\n /** Workspace directory for this (chat, user) pair. */\n workspacePath: z.string().min(1),\n /** Message id of the latest streaming card (updated per turn). */\n cardMessageId: z.string().min(1),\n /** Epoch ms when the record was created. */\n createdAt: z.number().int().nonnegative(),\n /** Epoch ms of the last activity routed through this record. */\n lastActivityAt: z.number().int().nonnegative(),\n /** Sessions this topic has pointed at, oldest first. Optional for v1 compatibility. */\n sessionHistory: z.array(sessionHistoryEntrySchema).optional(),\n})\n\nexport type RouteRecord = z.infer<typeof routeRecordSchema>\n\n/**\n * Minimal persistent tombstone for a thread route retired by route TTL.\n *\n * Deleting the active route alone would make a later message look like a new\n * conversation and silently create another Session in the same Feishu topic.\n * The tombstone preserves only enough identity to reject that message across\n * process restarts; the old Session and workspace remain host-owned.\n */\nexport const expiredRouteRecordSchema = z.object({\n /** Expired Feishu thread id (also the table key). */\n key: z.string().min(1),\n /** Feishu chat containing the expired topic. */\n chatId: z.string().min(1),\n /** Original chat type, retained for diagnostics and future policy. */\n chatType: z.enum(['p2p', 'group']),\n /** Original route owner; never used to grant access after expiry. */\n senderOpenId: z.string().min(1),\n /** Epoch ms when the route was retired. */\n expiredAt: z.number().int().nonnegative(),\n /** Workspace the retired route belonged to; absent in tombstones written by\n * older plugin versions, so the field must stay optional. */\n workspacePath: z.string().min(1).optional(),\n /** Epoch ms of the last activity through the route before retirement.\n * Optional for the same legacy-compatibility reason; consumers fall back to\n * `expiredAt` when it is absent. */\n lastActivityAt: z.number().int().nonnegative().optional(),\n})\n\nexport type ExpiredRouteRecord = z.infer<typeof expiredRouteRecordSchema>\n\n/**\n * Normalize legacy and partially duplicated histories without mutating the\n * durable record. The current Session is always represented, duplicate ids\n * collapse to their earliest timestamp, and output order is deterministic.\n */\nexport function normalizeSessionHistory(record: RouteRecord): SessionHistoryEntry[] {\n const bySession = new Map<string, SessionHistoryEntry>()\n for (const entry of record.sessionHistory ?? []) {\n const existing = bySession.get(entry.sessionId)\n if (existing === undefined || entry.createdAt < existing.createdAt) {\n bySession.set(entry.sessionId, { ...entry })\n }\n }\n if (!bySession.has(record.sessionId)) {\n bySession.set(record.sessionId, { sessionId: record.sessionId, createdAt: record.createdAt })\n }\n return [...bySession.values()].sort((left, right) => left.createdAt - right.createdAt)\n}\n\n/** Materialize the optional legacy history field for in-memory consumers. */\nexport function normalizeRouteRecord(record: RouteRecord): RouteRecord {\n return { ...record, sessionHistory: normalizeSessionHistory(record) }\n}\n\n/** Append a newly created Session to one normalized route history. */\nexport function appendSessionHistory(\n record: RouteRecord,\n sessionId: string,\n createdAt: number,\n): SessionHistoryEntry[] {\n return normalizeSessionHistory({\n ...record,\n sessionId,\n sessionHistory: [...normalizeSessionHistory(record), { sessionId, createdAt }],\n })\n}\n\n/**\n * In-flight turn snapshot: everything needed to repair a streaming card that a\n * restart left mid-flight (stuck panel, leftover Typing reaction, never-patched\n * segment cards). Keyed by the turn's MAIN card message id — unique per turn\n * and known at attach time — so overlapping turns of one session never\n * overwrite each other. Written before the route record, updated as segment\n * cards resolve, deleted when the turn finalizes (or the creation fails).\n */\nexport const INFLIGHT_VERSION = 1\n\nexport const inflightCardSchema = z.object({\n /** Feishu message id of one segment card. */\n messageId: z.string().min(1),\n /** Delivery mode; `text` messages are complete and never patched by repair. */\n mode: z.enum(['card', 'text']),\n})\n\nexport type InflightCard = z.infer<typeof inflightCardSchema>\n\nexport const inflightSnapshotSchema = z.object({\n /** Key the snapshot is stored under (= `cards[0].messageId`, the main card). */\n key: z.string().min(1),\n /** DSH session the turn belongs to. */\n sessionId: z.string().min(1),\n /** Feishu chat the cards live in (reaction targeting). */\n chatId: z.string().min(1),\n /** User message that triggered the turn (Typing/DONE reaction anchor). */\n triggerMessageId: z.string().min(1),\n /** Reaction id of the Typing reaction (absent when the add failed). */\n reactionId: z.string().optional(),\n /** Segment cards in order; `[0]` is the main card. */\n cards: z.array(inflightCardSchema).min(1),\n /** Epoch ms when the turn attached. */\n startedAt: z.number().int().nonnegative(),\n /** Repair attempts spent (cap: 2 — see `repairInflight`). */\n attempts: z.number().int().nonnegative(),\n})\n\nexport type InflightSnapshot = z.infer<typeof inflightSnapshotSchema>\n\n/**\n * Access-control grants (v1 format, added without a domain version bump —\n * see the notes on `feishuDomain` below).\n *\n * One record per authorized group: the group gate is the first of the two\n * independent admission gates. Inside an authorized group only the\n * administrator and explicitly authorized users may start conversations.\n */\nexport const accessGroupRecordSchema = z.object({\n /** Feishu chat id (`oc_...`) — also the table key. */\n chatId: z.string().min(1),\n /** Human-readable group name captured at authorization time (optional). */\n name: z.string().optional(),\n /** Which authorization entry produced this grant. */\n source: z.enum(['command', 'card', 'panel']),\n /** Administrator open_id that authorized it. */\n addedBy: z.string().min(1),\n /** Epoch ms when it was authorized. */\n addedAt: z.number().int().nonnegative(),\n})\n\nexport type AccessGroupRecord = z.infer<typeof accessGroupRecordSchema>\n\n/**\n * One authorized user (`open_id`), global across every authorized group.\n * Private chats are administrator-only, so this grant only affects group\n * conversations.\n */\nexport const accessUserRecordSchema = z.object({\n /** Feishu open id (`ou_...`) — also the table key. */\n openId: z.string().min(1),\n /** Display name captured when the user was authorized (optional). */\n name: z.string().optional(),\n /** Which authorization entry produced this grant. */\n source: z.enum(['command', 'card', 'panel']),\n /** Administrator open_id that authorized it. */\n addedBy: z.string().min(1),\n /** Epoch ms when it was authorized. */\n addedAt: z.number().int().nonnegative(),\n})\n\nexport type AccessUserRecord = z.infer<typeof accessUserRecordSchema>\n\n/**\n * One authorization request the administrator has been (or should be) asked\n * about. Requests are persisted so the DM card stays decidable across plugin\n * restarts; settled records are kept as an audit trail and for request\n * de-duplication/cooldown.\n */\nexport const accessRequestRecordSchema = z.object({\n /** Opaque token carried by the card buttons — also the table key. */\n token: z.string().min(1),\n /** What is being requested. */\n kind: z.enum(['group', 'user']),\n /** `chat_id` for a group request; `open_id` for a user request. */\n targetId: z.string().min(1),\n /** Chat the request originated from, when applicable. */\n chatId: z.string().optional(),\n /** Group name captured for the card (group requests, or the group the user was seen in). */\n groupName: z.string().optional(),\n /** User who triggered a user request. */\n requesterOpenId: z.string().optional(),\n /** Display name of that user, when the event carried one. */\n requesterName: z.string().optional(),\n /** Lifecycle state; only `pending` requests accept a card action. */\n status: z.enum(['pending', 'approved', 'denied', 'expired']),\n /** Epoch ms when the request was created. */\n createdAt: z.number().int().nonnegative(),\n /** Epoch ms after which a pending request can no longer be decided. */\n expiresAt: z.number().int().nonnegative(),\n /** DM message id of the administrator card, once delivery succeeded. */\n messageId: z.string().optional(),\n})\n\nexport type AccessRequestRecord = z.infer<typeof accessRequestRecordSchema>\n\n/**\n * One processed inbound message, keyed by its Feishu message id. Feishu\n * redelivers un-acked events on WS reconnect, so every accepted message is\n * recorded here BEFORE its side effects begin; redeliveries then hit the\n * table (or the in-process guard) and are dropped. Records are pruned by the\n * GC sweep after a fixed retention window (24 h — redelivery windows are\n * minutes).\n */\nexport const PROCESSED_VERSION = 1\n\nexport const processedRecordSchema = z.object({\n /** Key the record is stored under (the Feishu message id). */\n key: z.string().min(1),\n /** Epoch ms when the message was accepted for processing. */\n processedAt: z.number().int().nonnegative(),\n})\n\nexport type ProcessedRecord = z.infer<typeof processedRecordSchema>\n\nexport interface FeishuDomainSpecShape {\n name: 'channels_routes'\n version: 1\n global: {\n schema: z.ZodType<FeishuDomainGlobal>\n initial: FeishuDomainGlobal\n }\n tables: {\n routes: {\n valueSchema: typeof routeRecordSchema\n __key?: string\n }\n inflight: {\n valueSchema: typeof inflightSnapshotSchema\n __key?: string\n }\n processed: {\n valueSchema: typeof processedRecordSchema\n __key?: string\n }\n expired_routes: {\n valueSchema: typeof expiredRouteRecordSchema\n __key?: string\n }\n access_groups: {\n valueSchema: typeof accessGroupRecordSchema\n __key?: string\n }\n access_users: {\n valueSchema: typeof accessUserRecordSchema\n __key?: string\n }\n access_requests: {\n valueSchema: typeof accessRequestRecordSchema\n __key?: string\n }\n }\n}\n\nexport const feishuDomainGlobalSchema = z.object({\n /** Legacy: img_key of a previously uploaded chevron icon. Kept optional so\n * storage written by older plugin versions still validates; the card now\n * uses the built-in `standard_icon` chevron and never writes this. */\n chevronImgKey: z.string().optional(),\n /** p2p chats already shown the one-time onboarding card (bounded list). */\n welcomedChats: z.array(z.string()).optional(),\n})\n\nexport type FeishuDomainGlobal = z.infer<typeof feishuDomainGlobalSchema>\n\n/**\n * Domain declaration for the host's storage facility. Plain data plus zod\n * schemas — deliberately not built with `defineDomain` so this module has no\n * runtime import of `@deepseek-ai/dsh-storage-domain`.\n */\nexport const feishuDomain: FeishuDomainSpecShape = {\n name: 'channels_routes',\n version: 1,\n global: {\n schema: feishuDomainGlobalSchema,\n initial: {} satisfies FeishuDomainGlobal,\n },\n tables: {\n routes: { valueSchema: routeRecordSchema },\n inflight: { valueSchema: inflightSnapshotSchema },\n processed: { valueSchema: processedRecordSchema },\n expired_routes: { valueSchema: expiredRouteRecordSchema },\n access_groups: { valueSchema: accessGroupRecordSchema },\n access_users: { valueSchema: accessUserRecordSchema },\n access_requests: { valueSchema: accessRequestRecordSchema },\n },\n}\n\nexport type FeishuDomainSpec = typeof feishuDomain\n\n/** Normalized table handle for the routes table. */\nexport interface RoutesTable {\n get(key: string): RouteRecord | undefined\n put(key: string, value: RouteRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, RouteRecord]>\n}\n\n/** Normalized table handle for the inflight table (repair snapshots). */\nexport interface InflightTable {\n get(key: string): InflightSnapshot | undefined\n put(key: string, value: InflightSnapshot): Promise<unknown> | void\n /**\n * Atomic read-modify-write on the host's write chain (mirrors `KvTable`).\n * Rejects with `missing-key` when the key is absent (e.g. the turn finalize\n * deleted the snapshot while a late segment delivery was still landing).\n */\n update(key: string, fn: (current: InflightSnapshot) => InflightSnapshot): Promise<unknown>\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, InflightSnapshot]>\n}\n\n/** Normalized table handle for the processed-message dedup table. */\nexport interface ProcessedTable {\n get(key: string): ProcessedRecord | undefined\n put(key: string, value: ProcessedRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, ProcessedRecord]>\n}\n\n/** Persistent thread tombstones used to reject post-TTL messages. */\nexport interface ExpiredRoutesTable {\n get(key: string): ExpiredRouteRecord | undefined\n put(key: string, value: ExpiredRouteRecord): Promise<unknown> | void\n entries(): IterableIterator<[string, ExpiredRouteRecord]>\n}\n\n/** Normalized table handle for authorized groups (key = `chat_id`). */\nexport interface AccessGroupsTable {\n get(key: string): AccessGroupRecord | undefined\n put(key: string, value: AccessGroupRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, AccessGroupRecord]>\n}\n\n/** Normalized table handle for authorized users (key = `open_id`). */\nexport interface AccessUsersTable {\n get(key: string): AccessUserRecord | undefined\n put(key: string, value: AccessUserRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, AccessUserRecord]>\n}\n\n/** Normalized table handle for authorization requests (key = opaque token). */\nexport interface AccessRequestsTable {\n get(key: string): AccessRequestRecord | undefined\n put(key: string, value: AccessRequestRecord): Promise<unknown> | void\n delete(key: string): Promise<unknown> | void\n entries(): IterableIterator<[string, AccessRequestRecord]>\n}\n\n/** Normalized domain handle. */\nexport interface FeishuDomainHandle {\n routes: RoutesTable\n inflight: InflightTable\n processed: ProcessedTable\n expiredRoutes: ExpiredRoutesTable\n accessGroups: AccessGroupsTable\n accessUsers: AccessUsersTable\n accessRequests: AccessRequestsTable\n getGlobal(): FeishuDomainGlobal\n setGlobal(value: FeishuDomainGlobal): Promise<unknown> | void\n close(): Promise<void>\n}\n\n/** The raw shape `ctx.storageDomain.open()` resolves to (cast at the seam).\n * Mirrors `KvTable` from `@deepseek-ai/dsh-storage-domain`: reads are sync,\n * writes go through `put`/`delete` (NOT `set`), and `update` provides the\n * atomic read-modify-write used by the inflight merge. */\nexport interface RawDomainHandle {\n table(name: string): {\n get(key: string): unknown\n put(key: string, value: unknown): Promise<unknown>\n update(key: string, fn: (current: unknown) => unknown): Promise<unknown>\n delete(key: string): Promise<unknown>\n entries(): IterableIterator<[string, unknown]>\n }\n global: {\n get(): unknown\n set(value: unknown): Promise<unknown>\n }\n close(): Promise<void>\n}\n\n/** Build a chat-scoped fallback key for degraded (topic-less) routing. */\nexport function chatRouteKey(chatType: 'p2p' | 'group', chatId: string, senderOpenId: string): string {\n return chatType === 'p2p'\n ? `chat:p2p:${chatId}:${senderOpenId}`\n : `chat:group:${chatId}:${senderOpenId}`\n}\n","/**\n * Stale workspace cleanup: pure planning and application policy.\n *\n * The plugin creates one host workspace per (user × chat) pair below its\n * configured `baseDir` (see `workspaces.ts`). The host never removes those\n * directories on its own, so a long-running deployment slowly accumulates\n * attachments and agent output for conversations nobody uses anymore. This\n * module decides which plugin-created workspaces are stale and applies the\n * removal while deliberately owning no I/O: every filesystem and registry\n * effect is injected through `CleanupDeps`, which keeps the policy small and\n * testable and keeps the dangerous half (recursive deletion + registry\n * unregistration) in the caller.\n *\n * Activity precedence for one workspace:\n *\n * 1. the newest `lastActivityAt` among its active routes; a workspace with a\n * live session is never offered for deletion,\n * 2. otherwise the newest tombstone that names the workspace\n * (`lastActivityAt`, falling back to `expiredAt` for tombstones written\n * before routes recorded their workspace path),\n * 3. otherwise the directory's own `mtime` (an orphan workspace).\n *\n * Tombstones written before this feature carry neither `workspacePath` nor\n * `lastActivityAt`, so they cannot be attributed to a workspace; such a\n * workspace falls through to the `mtime` rule instead. A workspace with no\n * usable activity evidence at all (missing or unreadable directory) is never\n * offered for deletion: irreversible removal requires positive evidence.\n *\n * @module dsh-channels/cleanup\n */\n\nimport path from 'node:path'\n\n/** Values the `workspaceRetention` config accepts. `off` disables cleanup. */\nexport const WORKSPACE_RETENTION_VALUES = ['off', '7d', '30d', '90d', '180d'] as const\n\n/** One workspace-retention setting. */\nexport type WorkspaceRetention = (typeof WORKSPACE_RETENTION_VALUES)[number]\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/** Days represented by every non-`off` retention value. */\nconst RETENTION_DAYS: Readonly<Record<Exclude<WorkspaceRetention, 'off'>, number>> = {\n '7d': 7,\n '30d': 30,\n '90d': 90,\n '180d': 180,\n}\n\n/**\n * Convert a retention setting to its window in milliseconds.\n *\n * @param value - configured retention value.\n * @returns the window in milliseconds, or `undefined` when cleanup is off.\n */\nexport function retentionMs(value: WorkspaceRetention): number | undefined {\n return value === 'off' ? undefined : RETENTION_DAYS[value] * DAY_MS\n}\n\n/** Title prefixes `workspaces.ts` gives plugin-created workspaces. */\nconst PLUGIN_TITLE_PREFIXES = ['飞书私聊 ·', '飞书群聊 ·'] as const\n\n/**\n * Whether `target` is strictly below `baseDir`.\n *\n * Both sides are normalized lexically (`path.resolve`), so a `..` segment or a\n * relative value cannot escape `baseDir`; the base directory itself and\n * siblings sharing a name prefix (e.g. `base-other`) are not \"inside\". The\n * check is lexical on purpose: this module owns no I/O. The caller's\n * `removeWorkspace` should still refuse symlinked escapes with a realpath\n * check before deleting.\n *\n * @param baseDir - directory the target must stay below.\n * @param target - candidate path.\n * @returns `true` when `target` is strictly inside `baseDir`.\n */\nexport function isPathInside(baseDir: string, target: string): boolean {\n const base = path.resolve(baseDir)\n const resolved = path.resolve(target)\n if (base === resolved) return false\n const relative = path.relative(base, resolved)\n if (relative === '' || path.isAbsolute(relative)) return false\n return relative !== '..' && !relative.startsWith(`..${path.sep}`)\n}\n\n/** Filesystem facts the caller measures for one workspace candidate. */\nexport interface WorkspaceStats {\n /** Directory mtime in epoch ms — the activity fallback for orphan workspaces. */\n mtimeMs: number\n /** Total regular-file bytes below the directory. */\n bytes: number\n /** Regular-file count below the directory. */\n files: number\n}\n\n/** Minimal workspace shape passed around by plan/apply (structurally `Workspace`). */\nexport interface CleanupWorkspace {\n /** Host workspace-registry id (needed to unregister the record). */\n readonly id: string\n /** Workspace directory. */\n readonly path: string\n /** Host display title; plugin-created workspaces use the `飞书…` prefixes. */\n readonly title: string\n}\n\n/** One active route record, reduced to the facts cleanup needs. */\nexport interface CleanupRoute {\n /** Route key (thread_id or `chat:<chatId>`). */\n readonly key: string\n /** `thread` for a real Feishu topic, `chat` for a degraded chat route. */\n readonly kind: 'thread' | 'chat'\n /** Workspace directory this route's session works in. */\n readonly workspacePath: string\n /** DSH session the route points at. */\n readonly sessionId: string\n /** Epoch ms of the last activity routed through the record. */\n readonly lastActivityAt: number\n}\n\n/**\n * One retired-route tombstone.\n *\n * Older plugin versions stored only route identity, so `workspacePath` and\n * `lastActivityAt` stay optional and legacy tombstones remain parseable.\n */\nexport interface CleanupTombstone {\n /** Route key the tombstone replaced. */\n readonly key: string\n /** Workspace the retired route belonged to; absent in legacy tombstones. */\n readonly workspacePath?: string\n /** Epoch ms when the route was retired. */\n readonly expiredAt: number\n /** Epoch ms of the last activity before retirement; absent in legacy tombstones. */\n readonly lastActivityAt?: number\n}\n\n/** A dependency seam that may be sync (registry tables) or async (filesystem). */\nexport type MaybePromise<T> = T | Promise<T>\n\n/**\n * Every effect cleanup needs, injected so this module stays I/O-free.\n *\n * The service supplies the real implementations: the workspace registry for\n * `listWorkspaces`/`removeWorkspace`, the routing tables for\n * `activeRoutes`/`tombstones`, the live-agent set for `isLive`, and a\n * filesystem walk for `statWorkspace`.\n */\nexport interface CleanupDeps {\n /** Workspace root the plugin owns; candidates must be strictly inside it. */\n readonly baseDir: string\n /** Retention value from config (`off` disables planning). */\n readonly retention: WorkspaceRetention\n /** Every workspace currently registered with the host. */\n listWorkspaces(): MaybePromise<readonly CleanupWorkspace[]>\n /** Every active route record. */\n activeRoutes(): MaybePromise<readonly CleanupRoute[]>\n /** Every retired-route tombstone. */\n tombstones(): MaybePromise<readonly CleanupTombstone[]>\n /** Whether a DSH session is still live; live workspaces are never offered. */\n isLive(sessionId: string): boolean\n /** Measure one directory; `undefined` when it cannot be read (missing, permission). */\n statWorkspace(workspacePath: string): MaybePromise<WorkspaceStats | undefined>\n /** Delete one workspace directory and unregister its registry record. */\n removeWorkspace(workspace: CleanupWorkspace): MaybePromise<void>\n /** Retire one active route into a tombstone (same bookkeeping as route TTL). */\n retireRoute(route: CleanupRoute): MaybePromise<void>\n /** Clock seam; defaults to `Date.now()`. */\n now?(): number\n /** Warning seam for best-effort failures; defaults to silence. */\n warn?(message: string): void\n}\n\n/** One workspace old enough to delete, with its measured footprint. */\nexport interface CleanupCandidate {\n /** Workspace to delete. */\n readonly workspace: CleanupWorkspace\n /** Newest activity observed for the workspace (epoch ms). */\n readonly activityAt: number\n /** Regular-file bytes below the directory (0 when unreadable). */\n readonly bytes: number\n /** Regular-file count below the directory (0 when unreadable). */\n readonly files: number\n /** Active routes still pointing at the workspace; retired before removal. */\n readonly routes: readonly CleanupRoute[]\n}\n\n/** Result of one dry-run planning pass. */\nexport interface CleanupPlan {\n /** Retention window used for this plan, in milliseconds. */\n readonly retentionMs: number\n /** Activity cutoff (epoch ms); workspaces at or after it were kept. */\n readonly cutoff: number\n /** Eligible workspaces, oldest activity first. */\n readonly candidates: readonly CleanupCandidate[]\n /** Plugin workspaces kept because a live session still uses them. */\n readonly skippedLive: number\n}\n\n/** One failed removal; the pass continues with the remaining candidates. */\nexport interface CleanupFailure {\n /** Workspace directory the failure belongs to. */\n readonly path: string\n /** Human-readable reason. */\n readonly message: string\n}\n\n/** Result of one apply pass. */\nexport interface CleanupOutcome {\n /** Workspaces whose directory was removed and registry record deleted. */\n readonly deletedWorkspaces: number\n /** Active routes retired before their workspace was removed. */\n readonly retiredRoutes: number\n /** Measured bytes of the deleted workspaces. */\n readonly freedBytes: number\n /** Per-workspace failures; other candidates were still processed. */\n readonly failures: readonly CleanupFailure[]\n}\n\n/** Whether a host workspace title marks the workspace as plugin-created. */\nfunction pluginWorkspaceTitle(title: string): boolean {\n return PLUGIN_TITLE_PREFIXES.some(prefix => title.startsWith(prefix))\n}\n\n/** Lexical path equality (both sides resolved the same way as `isPathInside`). */\nfunction samePath(left: string, right: string): boolean {\n return path.resolve(left) === path.resolve(right)\n}\n\n/** Newest finite timestamp of a list, or `undefined` when none is usable. */\nfunction newestActivity(values: readonly number[]): number | undefined {\n let newest: number | undefined\n for (const value of values) {\n if (!Number.isFinite(value)) continue\n if (newest === undefined || value > newest) newest = value\n }\n return newest\n}\n\n/** Count/byte values never go negative or non-finite on the outcome path. */\nfunction nonNegative(value: number): number {\n return Number.isFinite(value) && value > 0 ? value : 0\n}\n\n/** Best-effort error text (never a stack trace). */\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** A filesystem root must never become the cleanup base directory. */\nfunction isFilesystemRoot(dir: string): boolean {\n return path.parse(dir).root === dir\n}\n\n/** Liveness probe that fails safe: an unknowable session counts as live. */\nfunction sessionIsLive(deps: CleanupDeps, sessionId: string): boolean {\n try {\n return deps.isLive(sessionId)\n } catch (error) {\n deps.warn?.(`dsh-channels: workspace cleanup liveness check failed for ${sessionId}: ${errorMessage(error)}`)\n return true\n }\n}\n\n/** Measure one directory, tolerating a throwing or absent implementation. */\nasync function readWorkspaceStats(\n deps: CleanupDeps,\n workspacePath: string,\n): Promise<WorkspaceStats | undefined> {\n try {\n return await deps.statWorkspace(workspacePath)\n } catch (error) {\n deps.warn?.(`dsh-channels: workspace stats unavailable for ${workspacePath}: ${errorMessage(error)}`)\n return undefined\n }\n}\n\n/**\n * Build the dry-run cleanup plan for the configured retention window.\n *\n * Only plugin-created workspaces (`飞书私聊 ·` / `飞书群聊 ·` titles) strictly\n * below `baseDir` are considered. A workspace is a candidate when its newest\n * activity is older than the cutoff; its footprint is measured for the panel\n * preview and for `freedBytes` reporting.\n *\n * @param deps - injected effect seams (nothing is deleted here).\n * @returns the plan, or `undefined` when retention is off or the base\n * directory is unusable (filesystem root).\n */\nexport async function planWorkspaceCleanup(deps: CleanupDeps): Promise<CleanupPlan | undefined> {\n const window = retentionMs(deps.retention)\n if (window === undefined) return undefined\n\n const baseDir = path.resolve(deps.baseDir)\n if (isFilesystemRoot(baseDir)) {\n deps.warn?.('dsh-channels: workspace cleanup disabled: baseDir is a filesystem root')\n return undefined\n }\n\n const now = deps.now?.() ?? Date.now()\n const cutoff = now - window\n const activeRoutes = await deps.activeRoutes()\n const tombstones = await deps.tombstones()\n const candidates: CleanupCandidate[] = []\n let skippedLive = 0\n\n for (const workspace of await deps.listWorkspaces()) {\n if (!pluginWorkspaceTitle(workspace.title)) continue\n if (!isPathInside(baseDir, workspace.path)) continue\n\n const routes = activeRoutes.filter(route => samePath(route.workspacePath, workspace.path))\n if (routes.some(route => sessionIsLive(deps, route.sessionId))) {\n skippedLive++\n continue\n }\n\n let stats: WorkspaceStats | undefined\n let activityAt = newestActivity(routes.map(route => route.lastActivityAt))\n if (activityAt === undefined) {\n activityAt = newestActivity(\n tombstones\n .filter(tombstone => tombstone.workspacePath !== undefined\n && samePath(tombstone.workspacePath, workspace.path))\n .map(tombstone => tombstone.lastActivityAt ?? tombstone.expiredAt),\n )\n }\n if (activityAt === undefined) {\n // Orphan workspace: the directory's own mtime is the only age evidence.\n stats = await readWorkspaceStats(deps, workspace.path)\n activityAt = stats !== undefined && Number.isFinite(stats.mtimeMs) ? stats.mtimeMs : undefined\n }\n if (activityAt === undefined || activityAt >= cutoff) continue\n\n stats ??= await readWorkspaceStats(deps, workspace.path)\n candidates.push({\n workspace,\n activityAt,\n bytes: stats === undefined ? 0 : nonNegative(stats.bytes),\n files: stats === undefined ? 0 : nonNegative(stats.files),\n routes,\n })\n }\n\n candidates.sort((left, right) =>\n left.activityAt - right.activityAt || left.workspace.path.localeCompare(right.workspace.path))\n return { retentionMs: window, cutoff, candidates, skippedLive }\n}\n\n/**\n * Apply a plan: retire the candidate's active routes, then remove the\n * workspace directory and its registry record.\n *\n * A candidate is the unit of failure: if retiring any of its routes fails, the\n * workspace is left untouched (route bookkeeping must not point at a deleted\n * directory), the failure is collected, and the remaining candidates are still\n * processed. Paths are revalidated immediately before the destructive step, so\n * a stale or hand-built plan can never delete something outside `baseDir` (or\n * `baseDir` itself).\n *\n * @param deps - injected effect seams.\n * @param plan - plan produced by `planWorkspaceCleanup` (or an equivalent preview).\n * @returns aggregate counts plus per-workspace failures.\n */\nexport async function applyWorkspaceCleanup(\n deps: CleanupDeps,\n plan: CleanupPlan,\n): Promise<CleanupOutcome> {\n const baseDir = path.resolve(deps.baseDir)\n const baseDirIsRoot = isFilesystemRoot(baseDir)\n const failures: CleanupFailure[] = []\n let deletedWorkspaces = 0\n let retiredRoutes = 0\n let freedBytes = 0\n\n for (const candidate of plan.candidates) {\n const target = candidate.workspace.path\n // Defense in depth: never trust a plan object for the destructive step.\n if (baseDirIsRoot || !isPathInside(baseDir, target)) {\n failures.push({ path: target, message: 'refused: workspace path is outside the cleanup base directory' })\n continue\n }\n try {\n for (const route of candidate.routes) {\n if (!samePath(route.workspacePath, target)) continue\n await deps.retireRoute(route)\n retiredRoutes++\n }\n await deps.removeWorkspace(candidate.workspace)\n deletedWorkspaces++\n freedBytes += nonNegative(candidate.bytes)\n } catch (error) {\n const message = errorMessage(error)\n failures.push({ path: target, message })\n deps.warn?.(`dsh-channels: workspace cleanup failed for ${target}: ${message}`)\n }\n }\n\n return { deletedWorkspaces, retiredRoutes, freedBytes, failures }\n}\n","/**\n * Durable route store: thread/chat keys → bound DSH session records.\n *\n * Owns the hot cache over the `channels_routes` domain tables, the TTL\n * retirement that leaves a persistent tombstone, and session → route lookup.\n * The domain handle is late-bound (`domain()`), because the service opens it\n * during `start()` while the store is constructed earlier.\n *\n * @module dsh-channels/session/routes\n */\n\nimport type { ExpiredRouteRecord, FeishuDomainHandle, RouteRecord } from '../domain.ts'\nimport { normalizeRouteRecord } from '../domain.ts'\n\nexport interface RouteStoreDeps {\n /** The opened domain handle, or undefined before `start()` / after shutdown. */\n domain(): FeishuDomainHandle | undefined\n /** Route retirement TTL in milliseconds; `0` disables retirement. */\n ttlMs(): number\n /** Structured warning sink (never throws). */\n warn(message: string, ...args: unknown[]): void\n}\n\nexport class RouteStore {\n /** Active route records, hydrated once and kept in sync with writes. */\n private readonly active = new Map<string, RouteRecord>()\n /** Thread ids retired by TTL; retained so later messages cannot fork context. */\n private readonly expired = new Map<string, ExpiredRouteRecord>()\n\n constructor(private readonly deps: RouteStoreDeps) {}\n\n /**\n * Hydrate the caches from the opened domain. A tombstone wins over an active\n * row left behind by an interrupted or failed delete: expired Sessions must\n * not remain approval-routable.\n */\n load(): void {\n const domain = this.deps.domain()\n if (domain === undefined) return\n for (const [key, record] of domain.routes.entries()) {\n this.active.set(key, normalizeRouteRecord(record))\n }\n for (const [key, record] of domain.expiredRoutes.entries()) {\n this.expired.set(key, record)\n this.active.delete(key)\n }\n }\n\n /** One active route by key (cache-first, normalized). */\n get(key: string): RouteRecord | undefined {\n const cached = this.active.get(key)\n if (cached !== undefined) return cached\n const stored = this.deps.domain()?.routes.get(key)\n if (stored === undefined) return undefined\n const normalized = normalizeRouteRecord(stored)\n this.active.set(key, normalized)\n return normalized\n }\n\n /** One durable tombstone by expired thread id. */\n expiredRoute(key: string): ExpiredRouteRecord | undefined {\n const cached = this.expired.get(key)\n if (cached !== undefined) return cached\n const stored = this.deps.domain()?.expiredRoutes.get(key)\n if (stored === undefined) return undefined\n this.expired.set(key, stored)\n return stored\n }\n\n /** Persist one active route and refresh the cache. */\n async put(key: string, record: RouteRecord): Promise<void> {\n await this.deps.domain()?.routes.put(key, record)\n this.active.set(key, normalizeRouteRecord(record))\n }\n\n /** Delete one active route from the medium and the cache. */\n async remove(key: string): Promise<void> {\n await this.deps.domain()?.routes.delete(key)\n this.active.delete(key)\n }\n\n /** Drop one key from the cache only (used when a tombstone takes over). */\n forget(key: string): void {\n this.active.delete(key)\n }\n\n /** Find the route record owning a session (a thread route wins). */\n findBySession(sessionId: string): RouteRecord | undefined {\n let fallback: RouteRecord | undefined\n for (const record of this.active.values()) {\n if (record.sessionId !== sessionId) continue\n if (record.kind === 'thread') return record\n fallback ??= record\n }\n return fallback\n }\n\n size(): number {\n return this.active.size\n }\n\n clear(): void {\n this.active.clear()\n this.expired.clear()\n }\n\n /** Whether one active route has crossed the configured inactivity deadline. */\n private hasExpired(record: RouteRecord, now: number): boolean {\n const ttlMs = this.deps.ttlMs()\n return ttlMs > 0 && record.lastActivityAt < now - ttlMs\n }\n\n /**\n * Resolve or create the persistent tombstone for a thread whose active route\n * has crossed TTL. Tombstone publication comes first and wins even if the\n * best-effort active-route delete fails.\n */\n async expireThread(key: string, now = Date.now()): Promise<ExpiredRouteRecord | undefined> {\n const existing = this.expiredRoute(key)\n if (existing !== undefined) {\n this.active.delete(key)\n if (this.deps.domain()?.routes.get(key) !== undefined) {\n try {\n await this.deps.domain()?.routes.delete(key)\n } catch (error) {\n this.deps.warn('dsh-channels: deleting tombstoned route %s failed: %s', key, String(error))\n }\n }\n return existing\n }\n const record = this.get(key)\n if (record === undefined || record.kind !== 'thread' || !this.hasExpired(record, now)) return undefined\n return await this.retire(key, now)\n }\n\n /**\n * Retire one active thread route into a persistent tombstone regardless of\n * the configured TTL. Used by the workspace-retention cleanup, which may\n * close topics earlier than the route TTL.\n */\n async retire(key: string, now = Date.now()): Promise<ExpiredRouteRecord | undefined> {\n const record = this.get(key)\n if (record === undefined) return this.expiredRoute(key)\n const tombstone: ExpiredRouteRecord = {\n key,\n chatId: record.chatId,\n chatType: record.chatType,\n senderOpenId: record.senderOpenId,\n expiredAt: now,\n workspacePath: record.workspacePath,\n lastActivityAt: record.lastActivityAt,\n }\n await this.deps.domain()?.expiredRoutes.put(key, tombstone)\n this.expired.set(key, tombstone)\n this.active.delete(key)\n try {\n await this.deps.domain()?.routes.delete(key)\n } catch (error) {\n // The durable tombstone already makes expiry fail-closed. Keep serving\n // the explicit rejection and let a later sweep retry the stale delete.\n this.deps.warn('dsh-channels: deleting retired route %s failed: %s', key, String(error))\n }\n return tombstone\n }\n}\n","/**\n * In-flight turn snapshots and the startup repair pass.\n *\n * Every streaming turn records the credentials needed to settle its cards if\n * the process dies mid-turn (trigger message, Typing reaction, segment card\n * ids). The snapshot is keyed by the turn's MAIN card message id — unique per\n * turn — so overlapping turns never overwrite each other. On the next boot\n * `repairAll()` patches each interrupted card to its terminal state and drops\n * the snapshot.\n *\n * @module dsh-channels/session/repair\n */\n\nimport { buildInterruptionCard } from '../cards/streaming.ts'\nimport type { InflightSnapshot } from '../domain.ts'\nimport type { FeishuChannel } from '../feishu-channel.ts'\nimport type { FeishuDomainHandle } from '../domain.ts'\n\nexport interface InflightRepairDeps {\n /** The opened domain handle, or undefined before `start()` / after shutdown. */\n domain(): FeishuDomainHandle | undefined\n /** The live channel, or undefined before `start()` / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Route retirement TTL; snapshots older than it are the GC pass's job. */\n ttlMs(): number\n info(message: string, ...args: unknown[]): void\n warn(message: string, ...args: unknown[]): void\n}\n\nexport class InflightRepair {\n constructor(private readonly deps: InflightRepairDeps) {}\n\n /** Write the turn's repair snapshot. Keyed by the main card message id. */\n async write(snapshot: InflightSnapshot): Promise<void> {\n await this.deps.domain()?.inflight.put(snapshot.key, snapshot)\n }\n\n /** Retire a snapshot (turn finalized / creation failed). Never throws. */\n async clear(key: string): Promise<void> {\n if (key === '') return\n try {\n await this.deps.domain()?.inflight.delete(key)\n } catch (error) {\n this.deps.warn('dsh-channels: clearing inflight snapshot %s failed: %s', key, String(error))\n }\n }\n\n /** Outstanding repair snapshots (health diagnostics). */\n count(): number {\n let total = 0\n try {\n const table = this.deps.domain()?.inflight\n if (table !== undefined) for (const _entry of table.entries()) total++\n } catch {\n // The storage dependency is reported separately; never expose details.\n }\n return total\n }\n\n /**\n * Fold a resolved segment card id into the turn's snapshot (atomic\n * read-modify-write). A missing snapshot means the turn already finalized\n * (the finalize delete won the race) — silently skipped.\n */\n async recordSegment(\n mainCardMessageId: string,\n segmentIndex: number,\n messageId: string,\n mode: 'card' | 'text',\n ): Promise<void> {\n if (mainCardMessageId === '') return\n try {\n await this.deps.domain()?.inflight.update(mainCardMessageId, snapshot => {\n // The snapshot starts with only the main card; segment sends are\n // serialized in order, so a delivery for index `cards.length` is the\n // next segment (append), anything below replaces, and a gap beyond\n // the tail cannot happen under the serialized chain (ignored).\n const cards = [...snapshot.cards]\n if (segmentIndex === cards.length) {\n cards.push({ messageId, mode })\n } else if (segmentIndex >= 0 && segmentIndex < cards.length) {\n cards[segmentIndex] = { messageId, mode }\n } else {\n return snapshot\n }\n return { ...snapshot, cards }\n })\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: updating inflight snapshot %s failed: %s', mainCardMessageId, String(error),\n )\n }\n }\n\n /**\n * Startup repair pass: settle every card a previous instance left\n * mid-flight. Runs in the background right after the long connection is\n * established; each snapshot is independent and best-effort.\n *\n * Per snapshot: patch every `card`-mode card to the terminal interrupted\n * state (the partial body is gone with the old process; patch is full\n * replacement), best-effort swap Typing → DONE, then delete. A failed card\n * patch keeps the snapshot for one more attempt (capped at 2) so a\n * transient failure at boot does not lose the repair, while a medium that\n * permanently rejects the patch cannot trigger an endless retry loop.\n */\n async repairAll(): Promise<void> {\n const table = this.deps.domain()?.inflight\n if (table === undefined) return\n const snapshots = [...table.entries()]\n if (snapshots.length === 0) return\n // With GC enabled, snapshots older than the TTL are the GC pass's job:\n // repairing an ancient card would surprise the user, so repair skips\n // them and lets the GC drop them silently (both passes run at boot).\n const ttlMs = this.deps.ttlMs()\n const cutoff = ttlMs === 0 ? undefined : Date.now() - ttlMs\n const due = snapshots.filter(([, snapshot]) => cutoff === undefined || snapshot.startedAt >= cutoff)\n if (due.length === 0) return\n this.deps.info('dsh-channels: repairing %d interrupted turn(s)', due.length)\n for (const [key, snapshot] of due) {\n try {\n await this.repairSnapshot(snapshot)\n await table.delete(key)\n this.deps.info('dsh-channels: repaired interrupted turn card %s', key)\n } catch (error) {\n this.deps.warn('dsh-channels: repair of turn card %s failed: %s', key, String(error))\n const attempts = snapshot.attempts + 1\n try {\n if (attempts >= 2) {\n await table.delete(key)\n } else {\n await table.put(key, { ...snapshot, attempts })\n }\n } catch {\n // The domain may already be closing (shutdown raced the repair).\n }\n }\n }\n }\n\n /** Patch the cards of one interrupted turn to their terminal state. */\n private async repairSnapshot(snapshot: InflightSnapshot): Promise<void> {\n const channel = this.deps.channel()\n if (channel === undefined) throw new Error('dsh-channels: channel not started')\n for (const [index, card] of snapshot.cards.entries()) {\n if (card.mode !== 'card') continue\n // Full replacement: the interrupted card's streamed content lived only\n // in the previous process. Continuation cards keep their segment header.\n const options = index === 0 ? {} : { panel: false, segment: index + 1 }\n await channel.updateCard(card.messageId, buildInterruptionCard(options))\n }\n if (snapshot.reactionId !== undefined) {\n await channel.removeReaction(snapshot.triggerMessageId, snapshot.reactionId).catch(() => {})\n }\n await channel.addReaction(snapshot.triggerMessageId, 'DONE').catch(() => {})\n }\n}\n","/**\n * Agent-scoped system-prompt guidance for Feishu attachments.\n *\n * The channel archives inbound images/files/forwarded records below\n * `<workspace>/attachments/` without any model turn. This section is what\n * makes that convention discoverable: it tells the Agent where attachments\n * live, how to read them, and that reading is opt-in per user request.\n *\n * Registered inside the Agent scope (through `inject(['systemPrompt'])`), so\n * it is disposed with the Agent and does not leak into other compositions.\n *\n * @module dsh-channels/feishu/agent-context\n */\n\nimport type { AgentContext } from '../host/agent.ts'\n\n/** Stable prompt-section name; one registration per Agent scope. */\nexport const FEISHU_ATTACHMENTS_SECTION = 'dsh-channels:feishu-attachments'\n\n/**\n * Placement between the harness file-reference section (900) and the\n * filesystem tool descriptions (1000): the convention is read before the\n * tool that implements it.\n */\nconst FEISHU_ATTACHMENTS_ORDER = 950\n\n/** Model-facing text. Kept stable so unchanged sessions keep their KV cache. */\nexport const FEISHU_ATTACHMENTS_GUIDANCE = `## Feishu attachments\nEvery Feishu conversation (topic) has its own workspace and its own attachments directory. Images, files, and forwarded chat records sent through Feishu are archived verbatim in this conversation's \\`attachments/\\` directory. Each file name starts with its receipt time (\\`YYYYMMDD-HHmmss-\\`), so newer attachments sort later and \\`ls -lt <dir>\\` lists the newest first.\n\nAttachments are never inserted into the conversation automatically. Read them only when the user's request needs them, and do not read attachments the user did not mention:\n- images: use \\`read_image\\` with the file path;\n- text and code: use \\`read\\` when the session mounts that tool, otherwise \\`bash\\` (for example \\`sed -n '1,200p' <file>\\`);\n- other formats (PDF, Office, archives, ...): inspect or convert them with \\`bash\\`.\nIf the user refers to \"the file/image I just sent\" without a name, list this conversation's attachments directory and pick the most recent matching attachment. Other Feishu conversations have separate workspaces: never read outside this workspace, and never mention files from another conversation.`\n\n/**\n * Full guidance for one Session. The absolute per-session directory is\n * appended when known so the convention still resolves after the Agent changes\n * its working directory with the `working_directory` tool.\n */\nexport function attachmentsGuidance(attachmentsDir?: string): string {\n if (attachmentsDir === undefined || attachmentsDir === '') return FEISHU_ATTACHMENTS_GUIDANCE\n return `${FEISHU_ATTACHMENTS_GUIDANCE}\\n\\nThis conversation's attachment directory is \\`${attachmentsDir.replace(/`/gu, \"'\")}\\` (absolute).`\n}\n\n/**\n * Mount the attachment guidance into one Agent scope. Call from agent setup;\n * a missing `systemPrompt` service degrades to a no-op (headless hosts).\n */\nexport async function mountChannelGuidance(\n agentCtx: AgentContext,\n attachmentsDir?: string,\n): Promise<void> {\n await agentCtx.inject(['systemPrompt'], (ctx) => {\n ctx.systemPrompt?.section({\n name: FEISHU_ATTACHMENTS_SECTION,\n order: FEISHU_ATTACHMENTS_ORDER,\n text: attachmentsGuidance(attachmentsDir),\n })\n })\n}\n","/**\n * Thin wrapper over the official SDK's `createLarkChannel` high-level module.\n *\n * The wrapper exists to pin the exact surface the plugin uses behind one\n * interface so the unit tests can stub the official SDK wholesale (no\n * network, no creds).\n *\n * @module dsh-channels/feishu-channel\n */\n\nimport { createLarkChannel, LoggerLevel } from '@larksuiteoapi/node-sdk'\nimport type {\n BotAddedEvent,\n ChatInfo,\n LarkChannel,\n NormalizedMessage,\n SendResult,\n WSConnectionStatus,\n} from '@larksuiteoapi/node-sdk'\n\nexport type { BotAddedEvent, ChatInfo, NormalizedMessage, SendResult } from '@larksuiteoapi/node-sdk'\nexport type FeishuConnectionStatus = WSConnectionStatus\n\n/** Outbound payloads used by this plugin, including Buffer-backed file upload. */\nexport type FeishuSendInput =\n | { card: object }\n | { text: string }\n | { file: { source: string | Buffer; fileName: string } }\n\n/**\n * One normalized card action (button click) event, as delivered by the\n * official SDK's `card.action.trigger` handler (card JSON 2.0 callback\n * behaviors). `messageId` is the card's own message id; `action.value` is the\n * callback value object the button carried (SDK contract: object values).\n */\nexport interface CardActionEvent {\n /** Message id of the card the action belongs to. */\n messageId: string\n chatId: string\n operator: { openId: string; userId?: string; name?: string }\n action: { value: unknown; tag: string; name?: string; option?: string }\n raw?: unknown\n}\n\n/** The minimal channel surface the plugin consumes. */\nexport interface FeishuChannel {\n /** Bound handler for normalized inbound messages. */\n onMessage(handler: (message: NormalizedMessage) => void | Promise<void>): () => void\n /** Bound handler for card button-callback events. */\n onCardAction(handler: (event: CardActionEvent) => void | Promise<void>): () => void\n /**\n * Bound handler for bot-added-to-chat events (`im.chat.member.bot.added_v1`).\n * Requires the app to subscribe that event; without it the plugin falls\n * back to the first denied @-mention for group information cards.\n */\n onBotAdded(handler: (event: BotAddedEvent) => void | Promise<void>): () => void\n /** Chat name/owner/member count (needs chat-info read permission). */\n getChatInfo(chatId: string): Promise<ChatInfo>\n /** Bot open_id, available after `connect()` resolves. */\n botOpenId(): string | undefined\n /** Current official-SDK WebSocket lifecycle snapshot, when initialized. */\n getConnectionStatus(): FeishuConnectionStatus | undefined\n /** Start the WebSocket long connection (resolves after the handshake). */\n connect(): Promise<void>\n /** Stop the connection. */\n disconnect(): Promise<void>\n /** Send one message (text or card) with optional reply targeting. */\n send(\n chatId: string,\n input: FeishuSendInput,\n options?: { replyTo?: string; replyInThread?: boolean },\n ): Promise<SendResult>\n /** Full-card replacement of a previously sent card message. */\n updateCard(messageId: string, card: object): Promise<void>\n /** Add an emoji reaction; resolves with the reaction id needed for removal. */\n addReaction(messageId: string, emojiType: string): Promise<string>\n /** Remove a reaction previously added by the bot. */\n removeReaction(messageId: string, reactionId: string): Promise<void>\n /** Fetch the thread id of a sent message (undefined when not a topic message). */\n threadIdOfMessage(messageId: string): Promise<string | undefined>\n /**\n * Stream one inbound message resource. Files and images share the same\n * download endpoint; only the `type` query differs. The stream is bounded\n * chunk-by-chunk so arbitrarily large files never have to fit in memory.\n */\n downloadResource(\n messageId: string,\n fileKey: string,\n type: 'image' | 'file',\n ): Promise<ResourceChunkStream>\n}\n\n/** Ordered byte chunks of one inbound resource. */\nexport type ResourceChunkStream = AsyncIterable<Uint8Array>\n\n/**\n * Chunk size for ranged file downloads. Feishu's message-resource endpoint\n * rejects a single response larger than 100 MB (error 234037), but honors\n * bounded Range requests, so large files are fetched in independent chunks.\n */\nexport const RANGE_DOWNLOAD_CHUNK_BYTES = 32 * 1024 * 1024\n\n/** Total object size from a `Content-Range: bytes start-end/total` header. */\nexport function contentRangeTotal(headers: unknown): number | undefined {\n const value = headerValue(headers, 'content-range')\n if (value === undefined) return undefined\n const match = /\\/(\\d+)\\s*$/u.exec(value)\n if (match === null) return undefined\n const total = Number(match[1])\n return Number.isSafeInteger(total) && total >= 0 ? total : undefined\n}\n\n/** Case-insensitive header lookup over a plain object or an AxiosHeaders-like getter. */\nfunction headerValue(headers: unknown, name: string): string | undefined {\n if (headers === null || typeof headers !== 'object') return undefined\n const record = headers as Record<string, unknown>\n const lower = name.toLowerCase()\n for (const key of Object.keys(record)) {\n if (key.toLowerCase() !== lower) continue\n const value = record[key]\n if (typeof value === 'string') return value\n }\n const get = (headers as { get?: (key: string) => unknown }).get\n if (typeof get === 'function') {\n const value = get.call(headers, name)\n return typeof value === 'string' ? value : undefined\n }\n return undefined\n}\n\n/** One bounded range request returning the response headers and byte stream. */\nexport type ResourceRangeFetcher = (range: string) => Promise<{\n headers?: unknown\n stream: AsyncIterable<Uint8Array>\n}>\n\n/**\n * Download one object through sequential bounded Range requests. The caller\n * supplies the first response's total size and a fetcher for the remaining\n * ranges; bytes are yielded as they arrive so the archive writes them\n * straight to disk.\n */\nexport async function* downloadResourceInRanges(\n fetchRange: ResourceRangeFetcher,\n totalBytes: number,\n chunkBytes = RANGE_DOWNLOAD_CHUNK_BYTES,\n): AsyncGenerator<Uint8Array> {\n let offset = 0\n while (offset < totalBytes) {\n const end = Math.min(offset + chunkBytes, totalBytes) - 1\n const response = await fetchRange(`bytes=${offset}-${end}`)\n let received = 0\n for await (const chunk of response.stream) {\n received += chunk.length\n yield chunk\n }\n if (received === 0) {\n throw new Error(`dsh-channels: ranged download stalled at byte ${offset}/${totalBytes}`)\n }\n offset += received\n }\n}\n\nexport interface ChannelConnectOptions {\n appId: string\n appSecret: string\n loggerLevel: 'debug' | 'info' | 'warn' | 'error'\n requireMention: boolean\n dmMode: 'open' | 'disabled'\n}\n\nclass LarkChannelAdapter implements FeishuChannel {\n private readonly channel: LarkChannel\n private readonly requireMention: boolean\n private readonly dmMode: 'open' | 'disabled'\n\n constructor(options: ChannelConnectOptions) {\n this.channel = createLarkChannel({\n appId: options.appId,\n appSecret: options.appSecret,\n loggerLevel: LOGGER_LEVELS[options.loggerLevel],\n // Keep the raw event body: rich-text messages attach files in a\n // top-level `files` array the SDK normalizer drops, so the attachment\n // intake recovers them from `msg.raw` instead.\n includeRawEvent: true,\n policy: {\n // The plugin routes mentions itself (thread routing must work without\n // mention), so the SDK policy layer stays permissive.\n requireMention: false,\n dmMode: options.dmMode === 'open' ? 'open' : 'disabled',\n respondToMentionAll: false,\n },\n })\n this.requireMention = options.requireMention\n this.dmMode = options.dmMode\n }\n\n onMessage(handler: (message: NormalizedMessage) => void | Promise<void>): () => void {\n return this.channel.on('message', handler)\n }\n\n onCardAction(handler: (event: CardActionEvent) => void | Promise<void>): () => void {\n return this.channel.on('cardAction', handler)\n }\n\n onBotAdded(handler: (event: BotAddedEvent) => void | Promise<void>): () => void {\n // The SDK event handler is synchronous; the plugin's handler is\n // fire-and-forget (the service wraps it with its own catch/log).\n return this.channel.on('botAdded', event => {\n void handler(event)\n })\n }\n\n async getChatInfo(chatId: string): Promise<ChatInfo> {\n return this.channel.getChatInfo(chatId)\n }\n\n botOpenId(): string | undefined {\n return this.channel.botIdentity?.openId\n }\n\n getConnectionStatus(): FeishuConnectionStatus | undefined {\n return this.channel.getConnectionStatus()\n }\n\n async connect(): Promise<void> {\n await this.channel.connect()\n }\n\n async disconnect(): Promise<void> {\n await this.channel.disconnect()\n }\n\n async send(\n chatId: string,\n input: FeishuSendInput,\n options?: { replyTo?: string; replyInThread?: boolean },\n ): Promise<SendResult> {\n return this.channel.send(chatId, input, options)\n }\n\n async updateCard(messageId: string, card: object): Promise<void> {\n await this.channel.updateCard(messageId, card)\n }\n\n async addReaction(messageId: string, emojiType: string): Promise<string> {\n return this.channel.addReaction(messageId, emojiType)\n }\n\n async removeReaction(messageId: string, reactionId: string): Promise<void> {\n await this.channel.removeReaction(messageId, reactionId)\n }\n\n async threadIdOfMessage(messageId: string): Promise<string | undefined> {\n const res = await this.channel.rawClient.im.v1.message.get({ path: { message_id: messageId } })\n return res?.data?.items?.[0]?.thread_id\n }\n\n async downloadResource(\n messageId: string,\n fileKey: string,\n type: 'image' | 'file',\n ): Promise<ResourceChunkStream> {\n if (type === 'image') {\n // Images are platform-capped far below the download limit; one request.\n const response = await this.rawResource(messageId, fileKey, type)\n return chunkStream(response.stream)\n }\n // Probe one byte to learn the total size without triggering the whole-file\n // size check. Feishu honors a bounded Range request even when the object\n // is larger than the API's single-response limit.\n const probe = await this.rawResource(messageId, fileKey, type, 'bytes=0-0')\n const total = contentRangeTotal(probe.headers)\n if (total === undefined) {\n // The server ignored Range and returned the whole object; use it as is.\n return chunkStream(probe.stream)\n }\n ;(probe.stream as { destroy?: () => void }).destroy?.()\n if (total === 0) {\n return {\n async *[Symbol.asyncIterator]() { /* empty resource */ },\n }\n }\n return downloadResourceInRanges(async range => {\n const response = await this.rawResource(messageId, fileKey, type, range)\n return { headers: response.headers, stream: chunkStream(response.stream) }\n }, total)\n }\n\n /** One message-resource GET, with an optional Range header. */\n private async rawResource(\n messageId: string,\n fileKey: string,\n type: 'image' | 'file',\n range?: string,\n ): Promise<{ headers: unknown; stream: AsyncIterable<Uint8Array | Buffer> }> {\n const response = await this.channel.rawClient.im.v1.messageResource.get(\n { params: { type }, path: { message_id: messageId, file_key: fileKey } },\n range === undefined ? undefined : { headers: { Range: range } },\n ) as unknown as { getReadableStream(): AsyncIterable<Uint8Array | Buffer>; headers?: unknown }\n return { headers: response.headers, stream: response.getReadableStream() }\n }\n}\n\n/** Wrap one Node stream as a plain byte-chunk iterable. */\nfunction chunkStream(source: AsyncIterable<Uint8Array | Buffer>): ResourceChunkStream {\n return {\n async *[Symbol.asyncIterator]() {\n for await (const chunk of source) {\n yield Buffer.isBuffer(chunk) ? new Uint8Array(chunk) : chunk\n }\n },\n }\n}\n\n/**\n * Detect the raster format from magic bytes. Feishu's resource download does\n * not hand back a media type, and DSH validates the declared type against the\n * decoded bytes, so the plugin sniffs instead of trusting the file name.\n */\nexport function sniffImageMediaType(data: Uint8Array): 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif' | undefined {\n if (data.length >= 8 && data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47) return 'image/png'\n if (data.length >= 3 && data[0] === 0xFF && data[1] === 0xD8 && data[2] === 0xFF) return 'image/jpeg'\n if (data.length >= 12 && data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50) return 'image/webp'\n if (data.length >= 6 && data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) return 'image/gif'\n return undefined\n}\n\nconst LOGGER_LEVELS: Record<ChannelConnectOptions['loggerLevel'], LoggerLevel> = {\n debug: LoggerLevel.debug,\n info: LoggerLevel.info,\n warn: LoggerLevel.warn,\n error: LoggerLevel.error,\n}\n\n/** Factory seam: tests substitute a stub adapter. */\nexport type ChannelFactory = (options: ChannelConnectOptions) => FeishuChannel\n\nexport const defaultChannelFactory: ChannelFactory = options => new LarkChannelAdapter(options)\n","/**\n * Inbound attachment archival: Feishu image/file resources and merged-forward\n * chat records are written verbatim below `<workspace>/attachments/`.\n *\n * Nothing is forwarded to the model. The Agent's own filesystem tools read\n * whichever attachment the conversation needs, so an attachment message costs\n * no model request at all and unreferenced files never enter the context.\n * Files are streamed straight to disk: there is no plugin-side size cap, so a\n * large upload must not be collected in memory.\n *\n * Two payload shapes need care because the SDK normalizer drops them:\n * rich-text (`post`) messages keep their attached files in a top-level\n * `files` array that `convertPost` ignores, and merged-forward records are\n * plain text rather than resources. Both are recovered here; the post files\n * come from the raw event, which the channel adapter asks the SDK to attach.\n *\n * @module dsh-channels/feishu/inbound-attachments\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { createWriteStream } from 'node:fs'\nimport { mkdir, open, readdir, rename, rm, stat, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport type { FeishuChannel, NormalizedMessage } from '../feishu-channel.ts'\nimport { sniffImageMediaType } from '../feishu-channel.ts'\n\n/** Workspace subdirectory that holds every archived attachment. */\nexport const ATTACHMENTS_DIR = 'attachments'\n\n/** `<workspace>/attachments` directory of one topic workspace. */\nexport function attachmentsDir(workspacePath: string): string {\n return path.join(workspacePath, ATTACHMENTS_DIR)\n}\n\n/**\n * Move every staged attachment into one topic workspace. Staging holds files\n * received before their topic (and therefore its workspace) existed; they\n * belong to the first Session created for that topic. Best-effort per file.\n *\n * @returns how many files were adopted.\n */\nexport async function adoptStagedAttachments(stagingDir: string, workspacePath: string): Promise<number> {\n let entries: Array<{ name: string; isFile(): boolean }>\n try {\n entries = await readdir(stagingDir, { withFileTypes: true })\n } catch {\n return 0\n }\n const files = entries.filter(entry => entry.isFile() && !entry.name.startsWith('.'))\n if (files.length === 0) return 0\n const target = attachmentsDir(workspacePath)\n try {\n await mkdir(target, { recursive: true })\n } catch {\n return 0\n }\n let adopted = 0\n for (const entry of files) {\n const destination = await uniqueName(target, entry.name)\n try {\n await rename(path.join(stagingDir, entry.name), path.join(target, destination))\n adopted += 1\n } catch {\n // Leave the staged file for the next attempt; never block the turn.\n }\n }\n return adopted\n}\n\n/** The SDK placeholder for a rich-text message that carried no visible text. */\nconst EMPTY_POST_PLACEHOLDER = '[rich text message]'\n\n/** Placeholders the SDK leaves in normalized content for attachment payloads. */\nconst IMAGE_PLACEHOLDER = /!\\[image\\]\\([^)]*\\)/gu\nconst FILE_PLACEHOLDER = /<file[^>]*\\/>/gu\nconst FORWARDED_PLACEHOLDER = /<forwarded_messages(?:\\/>|>[\\s\\S]*?<\\/forwarded_messages>)/gu\n/** An expanded record always starts with this wrapper; a bare empty one means the fetch failed. */\nconst EMPTY_FORWARDED = '<forwarded_messages/>'\n\nexport interface AttachmentIntakeDeps {\n /** Whether this deployment archives inbound attachments at all. */\n enabled(): boolean\n /** Live channel; undefined after shutdown. */\n channel(): FeishuChannel | undefined\n /** Structured warning sink (never throws). */\n warn(message: string, ...args: unknown[]): void\n /** Deterministic clock seam for file-name prefixes. */\n now?(): number\n}\n\n/** One decoded attachment recovered from the normalized message or its raw post payload. */\nexport interface AttachmentResource {\n readonly type: 'image' | 'file'\n readonly fileKey: string\n readonly fileName?: string\n /** Feishu folder messages cannot be downloaded through the resource API. */\n readonly folder?: boolean\n}\n\n/** One attachment that could not be archived, with a user-facing reason. */\nexport interface AttachmentFailure {\n readonly name: string\n readonly reason: string\n}\n\nexport interface AttachmentArchiveResult {\n /** Resources durably written to the workspace. */\n saved: number\n /** Resources that failed to download, write, or are unsupported. */\n failed: number\n /** User-facing per-attachment failure notes (only when `failed > 0`). */\n failures: readonly AttachmentFailure[]\n /** True when the message carried no prompt text after removing placeholders. */\n attachmentOnly: boolean\n /** Remaining text after attachment placeholders are removed. */\n text: string\n}\n\n/** One file attached to a rich-text (`post`) message. */\nexport interface PostAttachedFile {\n readonly fileKey: string\n readonly fileName?: string\n readonly folder?: boolean\n}\n\n/**\n * Every attachment payload of one message, merging SDK-normalized resources\n * with files the `post` converter dropped.\n */\nexport function attachmentResources(msg: NormalizedMessage): AttachmentResource[] {\n const resources: AttachmentResource[] = (msg.resources ?? [])\n .filter(resource => resource.type === 'image' || resource.type === 'file')\n .map(resource => ({\n type: resource.type === 'file' ? 'file' : 'image',\n fileKey: resource.fileKey,\n ...(resource.fileName === undefined ? {} : { fileName: resource.fileName }),\n }))\n const seen = new Set(resources.map(resource => resource.fileKey))\n for (const file of postAttachedFiles(msg.rawContentType, msg.raw)) {\n if (seen.has(file.fileKey)) continue\n seen.add(file.fileKey)\n resources.push({ type: 'file', ...file })\n }\n return resources\n}\n\n/** Whether one message may carry an attachment payload worth archiving. */\nexport function hasAttachmentPayload(msg: NormalizedMessage): boolean {\n if (msg.rawContentType === 'merge_forward') return true\n if (msg.rawContentType === 'post') return true\n return attachmentResources(msg).length > 0\n}\n\n/**\n * Recover the files a rich-text message attached.\n *\n * Feishu serializes a post with text and files as `{ title, content, files }`.\n * The SDK's `convertPost` walks `content` elements only, so every entry of\n * `files` is lost from `NormalizedMessage.resources`; the raw event (enabled by\n * `includeRawEvent`) still carries it. Locale-wrapped bodies are searched one\n * level deep for the same shape.\n */\nexport function postAttachedFiles(rawContentType: string, raw: unknown): PostAttachedFile[] {\n if (rawContentType !== 'post' || raw === null || raw === undefined) return []\n const message = (raw as { message?: { content?: unknown } }).message\n if (message === undefined) return []\n const content = typeof message.content === 'string' ? safeJson(message.content) : message.content\n if (content === undefined) return []\n const entries = collectFileEntries(content, 0)\n const files: PostAttachedFile[] = []\n for (const entry of entries) {\n const fileKey = typeof entry.file_key === 'string' && entry.file_key !== '' ? entry.file_key : undefined\n if (fileKey === undefined) continue\n files.push({\n fileKey,\n ...(typeof entry.file_name === 'string' && entry.file_name !== '' ? { fileName: entry.file_name } : {}),\n ...(entry.is_folder === true ? { folder: true } : {}),\n })\n }\n return files\n}\n\nfunction safeJson(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return undefined\n }\n}\n\n/** Find every `files` array in a post body (flat or locale-wrapped), depth-bounded. */\nfunction collectFileEntries(node: unknown, depth: number): Array<Record<string, unknown>> {\n if (depth > 3 || node === null || typeof node !== 'object' || Array.isArray(node)) return []\n const record = node as Record<string, unknown>\n const files = record.files\n if (Array.isArray(files)) {\n return files.filter(\n (entry): entry is Record<string, unknown> => entry !== null && typeof entry === 'object' && !Array.isArray(entry),\n )\n }\n const out: Array<Record<string, unknown>> = []\n for (const value of Object.values(record)) out.push(...collectFileEntries(value, depth + 1))\n return out\n}\n\nexport class AttachmentIntake {\n constructor(private readonly deps: AttachmentIntakeDeps) {}\n\n /**\n * Archive every image/file resource and merged-forward record of one\n * inbound message. Failures are contained per attachment; the method never\n * throws and always reports the remaining prompt text.\n */\n async archive(msg: NormalizedMessage, directory: string): Promise<AttachmentArchiveResult> {\n const resources = attachmentResources(msg)\n const forwarded = msg.rawContentType === 'merge_forward'\n if (resources.length === 0 && !forwarded) {\n return { saved: 0, failed: 0, failures: [], attachmentOnly: false, text: msg.content ?? '' }\n }\n\n const stripped = stripAttachmentPlaceholders(msg.content ?? '', forwarded)\n const remainingText = stripped === EMPTY_POST_PLACEHOLDER ? '' : stripped\n const result = (\n saved: number,\n failures: readonly AttachmentFailure[],\n ): AttachmentArchiveResult => ({\n saved,\n failed: failures.length,\n failures,\n attachmentOnly: remainingText === '',\n text: remainingText,\n })\n\n if (!this.deps.enabled()) return result(0, [])\n\n const channel = this.deps.channel()\n if (channel === undefined) {\n return result(0, resources.map(resource => ({\n name: resourceName(resource),\n reason: '飞书连接未运行,附件未保存',\n })))\n }\n\n const dir = directory\n let saved = 0\n const failures: AttachmentFailure[] = []\n try {\n await mkdir(dir, { recursive: true })\n } catch (error) {\n this.deps.warn('dsh-channels: creating attachments directory %s failed: %s', dir, String(error))\n return result(0, resources.map(resource => ({\n name: resourceName(resource),\n reason: `无法创建工作区附件目录:${describeFailure(error)}`,\n })))\n }\n\n for (const resource of resources) {\n const name = resourceName(resource)\n if (resource.folder === true) {\n failures.push({ name, reason: '飞书文件夹消息暂不支持下载' })\n continue\n }\n try {\n await this.saveResource(msg, resource, dir)\n saved += 1\n } catch (error) {\n failures.push({ name, reason: describeFailure(error) })\n this.deps.warn(\n 'dsh-channels: archiving resource %s of message %s failed: %s',\n resource.fileKey, msg.messageId, String(error),\n )\n }\n }\n\n if (forwarded) {\n try {\n await this.saveForwardedRecord(msg, dir)\n saved += 1\n } catch (error) {\n failures.push({ name: '会话记录', reason: describeFailure(error) })\n this.deps.warn(\n 'dsh-channels: archiving forwarded record of message %s failed: %s',\n msg.messageId, String(error),\n )\n }\n }\n\n if (saved > 0) {\n await channel.addReaction(msg.messageId, 'DONE').catch(() => undefined)\n } else if (failures.length > 0) {\n await channel.addReaction(msg.messageId, 'No').catch(() => undefined)\n }\n\n return result(saved, failures)\n }\n\n /** Stream one image/file resource to disk, then publish it under a stable name. */\n private async saveResource(\n msg: NormalizedMessage,\n resource: AttachmentResource,\n dir: string,\n ): Promise<void> {\n const channel = this.deps.channel()\n if (channel === undefined) throw new Error('channel is not running')\n const stream = await channel.downloadResource(\n msg.messageId,\n resource.fileKey,\n resource.type === 'file' ? 'file' : 'image',\n )\n const temporary = path.join(dir, `.tmp-${randomUUID()}`)\n try {\n await pipeline(Readable.from(stream), createWriteStream(temporary, { mode: 0o600 }))\n const original = sanitizeFileName(resource.fileName)\n const base = original ?? (resource.type === 'image'\n ? `image${await sniffExtension(temporary)}`\n : 'file.bin')\n const finalName = await uniqueName(dir, `${this.stamp()}-${base}`)\n await rename(temporary, path.join(dir, finalName))\n } catch (error) {\n await rm(temporary, { force: true }).catch(() => undefined)\n throw error\n }\n }\n\n /**\n * Persist one expanded merged-forward record as UTF-8 text. The content is\n * already plain text (`<forwarded_messages>…`), so no parsing or conversion\n * happens here — the Agent reads the file when the conversation needs it.\n */\n private async saveForwardedRecord(msg: NormalizedMessage, dir: string): Promise<void> {\n const content = msg.content ?? ''\n if (content.trim() === '' || content.trim() === EMPTY_FORWARDED) {\n throw new Error('会话记录为空(机器人可能缺少源消息的读取权限)')\n }\n const name = await uniqueName(dir, `${this.stamp()}-forwarded-record.txt`)\n await writeFile(path.join(dir, name), `${content}\\n`, { encoding: 'utf8', mode: 0o600 })\n }\n\n private stamp(): string {\n return compactTimestamp(this.deps.now?.() ?? Date.now())\n }\n}\n\n/** Remove attachment placeholders while keeping ordinary prompt text. */\nexport function stripAttachmentPlaceholders(content: string, forwarded: boolean): string {\n let text = content.replace(IMAGE_PLACEHOLDER, ' ').replace(FILE_PLACEHOLDER, ' ')\n if (forwarded) text = text.replace(FORWARDED_PLACEHOLDER, ' ')\n return text.replace(/[ \\t]+/gu, ' ').replace(/\\s*\\n\\s*/gu, '\\n').trim()\n}\n\n/** `YYYYMMDD-HHmmss`, UTC, filesystem- and sort-friendly. */\nexport function compactTimestamp(epochMs: number): string {\n return new Date(epochMs).toISOString().replace(/[-:]/gu, '').replace(/\\..+$/u, '').replace('T', '-')\n}\n\n/** Strip directories, control characters, and reserved path characters from a display name. */\nexport function sanitizeFileName(raw: string | undefined): string | undefined {\n if (raw === undefined) return undefined\n const base = raw.split(/[\\\\/]/u).pop() ?? raw\n const cleaned = base\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f\\u007f<>:\"\\\\|?*]/gu, '_')\n .replace(/^[.\\s]+/u, '')\n .replace(/[.\\s]+$/u, '')\n if (cleaned === '') return undefined\n return truncateUtf8(cleaned, 120)\n}\n\n/** User-facing name for one attachment. */\nfunction resourceName(resource: AttachmentResource): string {\n if (resource.fileName !== undefined && resource.fileName !== '') return resource.fileName\n return resource.type === 'image' ? '图片' : '文件'\n}\n\n/**\n * Turn a download/write failure into a short user-facing reason. Feishu's\n * message-resource API refuses files above 100 MB, which is the common case\n * for a 400 on an otherwise valid file.\n */\nexport function describeFailure(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error)\n if (/status code 400|bad request/iu.test(message)) {\n return '飞书下载接口拒绝请求(文件可能已过期、已被删除或暂不可下载)'\n }\n if (/status code 403|forbidden/iu.test(message)) return '飞书下载接口无权限'\n return message\n}\n\n/** Truncate a UTF-8 string on a character boundary. */\nfunction truncateUtf8(value: string, maxBytes: number): string {\n if (Buffer.byteLength(value) <= maxBytes) return value\n let out = ''\n for (const char of value) {\n if (Buffer.byteLength(out + char) > maxBytes) break\n out += char\n }\n return out\n}\n\n/** Pick a non-colliding destination name inside one directory. */\nasync function uniqueName(dir: string, candidate: string): Promise<string> {\n const parsed = path.parse(candidate)\n for (let attempt = 1; attempt <= 100; attempt += 1) {\n const name = attempt === 1 ? candidate : `${parsed.name}-${attempt}${parsed.ext}`\n try {\n await stat(path.join(dir, name))\n } catch {\n return name\n }\n }\n throw new Error('no free attachment file name after 100 attempts')\n}\n\n/** Detect an image extension from the first bytes of a staged file. */\nasync function sniffExtension(file: string): Promise<string> {\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(file, 'r')\n const buffer = Buffer.alloc(16)\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0)\n const type = sniffImageMediaType(new Uint8Array(buffer.subarray(0, bytesRead)))\n switch (type) {\n case 'image/png': return '.png'\n case 'image/jpeg': return '.jpg'\n case 'image/webp': return '.webp'\n case 'image/gif': return '.gif'\n default: return '.bin'\n }\n } catch {\n return '.bin'\n } finally {\n await handle?.close().catch(() => undefined)\n }\n}\n","/**\n * Per-session request policy: provider / model / reasoning-effort overrides.\n *\n * Cordis waterfalls run the earliest listener as the outermost wrapper, so the\n * channel policy registers with `prepend: true`: preset/model-selection\n * listeners may be standing registrations that predate this Agent, and they\n * rewrite the request after `next()` resolves. The outer channel wrapper\n * therefore writes last.\n *\n * Session overrides are recovered from successful command lifecycles\n * (`/effort`, `/model`). Reusing Harness' durable `command/run` /\n * `command/done` pair avoids inventing an out-of-repo Session event that\n * persistence could not safely recognize on restore.\n *\n * @module dsh-channels/session/request-policy\n */\n\nimport type { AgentContext, CommandId, SessionEvent } from '../dsh-types.ts'\n\n/** A model selection override: a bare model keeps the composed provider. */\nexport interface ModelOverride {\n /** Provider route; absent keeps the route composed by earlier listeners. */\n readonly provider?: string\n readonly model: string\n}\n\n/** Channel-configured defaults for sessions this plugin creates. */\nexport interface RequestPolicyConfig {\n /** Configured reasoning effort (optional → adapter/provider default). */\n effort?: string\n /** Configured provider pin (optional → deployment default). */\n provider?: string\n /** Configured model pin (optional → deployment default). */\n model?: string\n}\n\nexport interface RequestPolicyState {\n readonly configuredEffort: string | undefined\n readonly configuredModel: ModelOverride | undefined\n /** Session-level `/effort` override. */\n readonly effortOverride: string | undefined\n /** Session-level `/model` override. */\n readonly modelOverride: ModelOverride | undefined\n /** Effort this channel writes into the next request, when defined. */\n readonly effectiveEffort: string | undefined\n /** Model this channel writes into the next request, when defined. */\n readonly effectiveModel: ModelOverride | undefined\n readonly effortSource: 'session' | 'config' | 'model-default'\n readonly modelSource: 'session' | 'config' | 'deployment-default'\n}\n\nexport interface RequestPolicyController {\n readonly state: RequestPolicyState\n /** Re-fold the durable log after resume or a settled command lifecycle. */\n sync(events: readonly SessionEvent[]): void\n /** Remove only this controller's Agent-scoped request wrapper. */\n dispose(): void\n}\n\ntype PendingSelection =\n | { readonly kind: 'effort'; readonly value: string | undefined }\n | { readonly kind: 'model'; readonly value: ModelOverride | undefined }\n\n/** Parse `/effort` input; only values the handler can accept. */\nfunction effortSelection(args: string | undefined): string | undefined | 'invalid' {\n if (args === undefined) return 'invalid'\n const value = args.trim()\n if (value === '' || /\\s/u.test(value)) return 'invalid'\n return value === 'reset' ? undefined : value\n}\n\n/**\n * Parse `/model` input: `reset`, `<model>` (keeps the composed provider), or\n * `<provider>/<model>`.\n */\nfunction modelSelection(args: string | undefined): ModelOverride | undefined | 'invalid' {\n if (args === undefined) return 'invalid'\n const value = args.trim()\n if (value === '' || /\\s/u.test(value)) return 'invalid'\n if (value === 'reset') return undefined\n const slash = value.indexOf('/')\n if (slash === -1) return { model: value }\n const provider = value.slice(0, slash)\n const model = value.slice(slash + 1)\n if (provider === '' || model === '' || model.includes('/')) return 'invalid'\n return { provider, model }\n}\n\n/** One folded policy: the last successful Session-level selections. */\nexport interface FoldedRequestPolicy {\n effort: string | undefined\n model: ModelOverride | undefined\n}\n\n/**\n * Fold the durable log into the Session-level policy.\n *\n * A run changes nothing until its matching `command/done` succeeds. Failed,\n * incomplete, query-only, and malformed commands are therefore inert.\n */\nexport function foldRequestPolicy(events: readonly SessionEvent[]): FoldedRequestPolicy {\n const pending = new Map<CommandId, PendingSelection>()\n let effort: string | undefined\n let model: ModelOverride | undefined\n for (const event of events) {\n if (event.type === 'command/run') {\n if (event.data.name === 'effort') {\n const selection = effortSelection(event.data.args)\n if (selection !== 'invalid') pending.set(event.data.commandId, { kind: 'effort', value: selection })\n } else if (event.data.name === 'model') {\n const selection = modelSelection(event.data.args)\n if (selection !== 'invalid') pending.set(event.data.commandId, { kind: 'model', value: selection })\n }\n continue\n }\n if (event.type !== 'command/done') continue\n const selection = pending.get(event.data.commandId)\n if (selection === undefined) continue\n pending.delete(event.data.commandId)\n if (event.data.kind !== 'success') continue\n if (selection.kind === 'effort') effort = selection.value\n else model = selection.value\n }\n return { effort, model }\n}\n\n/** Fold only the last successful Session-level `/effort` selection. */\nexport function foldEffortOverride(events: readonly SessionEvent[]): string | undefined {\n return foldRequestPolicy(events).effort\n}\n\n/** Fold only the last successful Session-level `/model` selection. */\nexport function foldModelOverride(events: readonly SessionEvent[]): ModelOverride | undefined {\n return foldRequestPolicy(events).model\n}\n\n/** Install the mutable request policy outside every other request wrapper. */\nexport function installRequestPolicy(\n agentCtx: AgentContext,\n config: RequestPolicyConfig = {},\n): RequestPolicyController {\n let effortOverride: string | undefined\n let modelOverride: ModelOverride | undefined\n let disposed = false\n const configuredModel: ModelOverride | undefined = config.provider !== undefined && config.model !== undefined\n ? { provider: config.provider, model: config.model }\n : undefined\n\n const disposeListener = agentCtx.on('agent/request', async (_payload, next) => {\n const request = await next()\n const effort = effortOverride ?? config.effort\n const model = modelOverride ?? configuredModel\n if (effort === undefined && model === undefined) return request\n return {\n ...request,\n ...(effort === undefined ? {} : { reasoningEffort: effort }),\n ...(model === undefined ? {} : { model: model.model }),\n ...(model?.provider === undefined ? {} : { provider: model.provider }),\n }\n }, { prepend: true })\n\n return {\n get state(): RequestPolicyState {\n const effectiveEffort = effortOverride ?? config.effort\n const effectiveModel = modelOverride ?? configuredModel\n return {\n configuredEffort: config.effort,\n configuredModel,\n effortOverride,\n modelOverride,\n effectiveEffort,\n effectiveModel,\n effortSource: effortOverride !== undefined\n ? 'session'\n : config.effort !== undefined ? 'config' : 'model-default',\n modelSource: modelOverride !== undefined\n ? 'session'\n : configuredModel !== undefined ? 'config' : 'deployment-default',\n }\n },\n sync(events) {\n const folded = foldRequestPolicy(events)\n effortOverride = folded.effort\n modelOverride = folded.model\n },\n dispose() {\n if (disposed) return\n disposed = true\n disposeListener()\n },\n }\n}\n","/**\n * Workspace directory resolution: one DSH workspace per (user × chat).\n *\n * Every Feishu topic gets its own workspace, grouped by the (chat, user) pair:\n * - p2p: `<baseDir>/p2p/<open_id>/<topic>/`\n * - group: `<baseDir>/groups/<chat_id>/<open_id>/<topic>/`\n *\n * Attachments received before a topic exists are staged below\n * `<baseDir>/_staging/<chatType>/<chat_id>/<open_id>/` and adopted into the\n * topic workspace once its first Session is created.\n *\n * Feishu ids (`ou_…`, `oc_…`) are safe path segments, but they are validated\n * anyway so a hostile id can never escape `baseDir`.\n *\n * @module dsh-channels/workspaces\n */\n\nimport { constants } from 'node:fs'\nimport { access, mkdir, stat } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport path from 'node:path'\nimport type { HostContext, Workspace } from './dsh-types.ts'\n\nconst SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/\n\n/** Upper bound for a panel-supplied workspace root (filesystem path limit headroom). */\nconst MAX_BASE_DIR_LENGTH = 4096\n\n/** Lexical validation result for a workspace root. */\nexport type BaseDirCheck =\n | { ok: true; value: string }\n | { ok: false; message: string }\n\n/**\n * Validate and normalize a workspace root supplied by the settings panel.\n *\n * The profile layer keeps accepting whatever the deployment wrote (including\n * the historical relative default), but a value *typed into the Web panel*\n * must name an unambiguous location: a relative path would resolve against\n * the host process's unpredictable cwd. `~` is expanded for convenience and\n * the result is normalized; the root itself is rejected because workspace\n * directories are appended to it.\n *\n * @param raw - untrusted panel value.\n * @param home - home directory seam (tests pin it; production uses `homedir()`).\n * @returns the normalized absolute path, or a user-facing message.\n */\nexport function normalizeWorkspaceBaseDir(raw: unknown, home: string = homedir()): BaseDirCheck {\n if (typeof raw !== 'string') return { ok: false, message: '必须是字符串' }\n const trimmed = raw.trim()\n if (trimmed === '') return { ok: false, message: '不能为空' }\n if (trimmed.length > MAX_BASE_DIR_LENGTH) {\n return { ok: false, message: `路径长度不得超过 ${MAX_BASE_DIR_LENGTH} 个字符` }\n }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(trimmed)) return { ok: false, message: '包含非法控制字符' }\n let expanded = trimmed\n if (expanded === '~') {\n expanded = home\n } else if (expanded.startsWith('~/') || expanded.startsWith('~\\\\')) {\n expanded = path.join(home, expanded.slice(2))\n }\n // Check absoluteness before resolving: `path.resolve` would silently turn a\n // relative panel value into a cwd-dependent absolute path.\n if (!path.isAbsolute(expanded)) {\n return { ok: false, message: '必须是绝对路径(例如 /Users/you/.dsh/dsh-channels/workspaces)' }\n }\n const normalized = path.resolve(expanded)\n if (path.parse(normalized).root === normalized) {\n return { ok: false, message: '不能使用文件系统根目录' }\n }\n return { ok: true, value: normalized }\n}\n\n/**\n * Materialize one panel-supplied workspace root and prove it is usable.\n *\n * Called when the panel saves `baseDir` so an unusable location fails at save\n * time instead of surfacing later as \"会话创建失败\" inside a Feishu chat.\n * The directory is created here (the same `mkdir -p` the first session would\n * perform); an existing non-directory is refused.\n *\n * @returns an error message, or `undefined` when the directory is writable.\n */\nexport async function prepareWorkspaceBaseDir(dir: string): Promise<string | undefined> {\n try {\n await mkdir(dir, { recursive: true })\n } catch (error) {\n return `无法创建工作区目录:${String(error)}`\n }\n try {\n const info = await stat(dir)\n if (!info.isDirectory()) return '该路径已存在但不是目录'\n } catch (error) {\n return `无法读取工作区目录:${String(error)}`\n }\n try {\n await access(dir, constants.W_OK)\n } catch {\n return '工作区目录不可写'\n }\n return undefined\n}\n\nexport function safeSegment(raw: string, what: string): string {\n const segment = raw.replace(/^\\/+|\\/+$/g, '')\n if (!SAFE_SEGMENT.test(segment) || segment === '.' || segment === '..') {\n throw new Error(`dsh-channels: unsafe ${what} for workspace path: ${JSON.stringify(raw)}`)\n }\n return segment\n}\n\n/**\n * Stable, filesystem-safe token for one Feishu conversation: a real topic uses\n * its thread id, while a degraded chat-scoped conversation uses the Session id\n * that owns it (there is no topic boundary to key on).\n */\nexport function topicTokenFor(\n routeKey: string,\n kind: 'thread' | 'chat',\n sessionId: string,\n): string {\n return kind === 'thread' ? safeSegment(routeKey, 'route key') : safeSegment(sessionId, 'session id')\n}\n\n/**\n * Deterministic workspace directory for one Feishu topic: every route gets its\n * own workspace below its (user × chat) grouping directory.\n *\n * - p2p: `<baseDir>/topics/p2p/<open_id>/<topic>/`\n * - group: `<baseDir>/topics/groups/<chat_id>/<open_id>/<topic>/`\n *\n * A separate `topics/` root keeps new workspaces out of the legacy (user ×\n * chat) workspace directories left by older versions, so cleaning one can\n * never delete the other.\n */\nexport function workspacePathFor(\n baseDir: string,\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n topicToken: string,\n): string {\n const chat = safeSegment(chatId, 'chatId')\n const user = safeSegment(senderOpenId, 'senderOpenId')\n const topic = safeSegment(topicToken, 'topic token')\n return chatType === 'p2p'\n ? path.join(baseDir, 'topics', 'p2p', user, topic)\n : path.join(baseDir, 'topics', 'groups', chat, user, topic)\n}\n\n/**\n * Staging directory for attachments that arrive before their topic (and so\n * its workspace) exists. Not a registered workspace: files move into the real\n * workspace as soon as the first Session of that topic is created.\n */\nexport function stagingPathFor(\n baseDir: string,\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n): string {\n const chat = safeSegment(chatId, 'chatId')\n const user = safeSegment(senderOpenId, 'senderOpenId')\n return path.join(baseDir, '_staging', chatType, chat, user)\n}\n\n/** Human-friendly workspace title shown in the DSH workspace list. */\nexport function workspaceTitleFor(\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n topicToken?: string,\n): string {\n const base = chatType === 'p2p'\n ? `飞书私聊 · ${senderOpenId}`\n : `飞书群聊 · ${chatId} · ${senderOpenId}`\n return topicToken === undefined ? base : `${base} · ${topicToken}`\n}\n\n/**\n * Ensure one topic's workspace directory exists and is registered with the\n * host's workspace registry. Idempotent: repeated calls return the same record.\n */\nexport async function ensureWorkspace(\n ctx: HostContext,\n baseDir: string,\n chatType: 'p2p' | 'group',\n chatId: string,\n senderOpenId: string,\n topicToken: string,\n): Promise<Workspace> {\n const dir = workspacePathFor(baseDir, chatType, chatId, senderOpenId, topicToken)\n await mkdir(dir, { recursive: true })\n return ctx.workspaceRegistry.create(\n dir,\n workspaceTitleFor(chatType, chatId, senderOpenId, topicToken),\n )\n}\n","/**\n * Agent/session lifecycle for this channel.\n *\n * Owns the live-agent registry (including agents BORROWED from another owner),\n * cold resume serialization, agent-preset composition, the per-session\n * reasoning-effort policy, and the `/renew` transaction that atomically binds\n * a new Session to an existing Feishu route.\n *\n * `agents.create()` never applies a preset on its own — the caller composes\n * the agent's world through the `setup` callback, which the factory awaits\n * before publishing the session. In web-profile deployments the model-facing\n * tools exist only inside agent presets (the host disables the tool rows at\n * the host layer), so sessions created without mounting one reach the model\n * with an empty tool list: the agent writes `<tool_calls>` as plain text and\n * stops. Web-UI sessions mount the default preset via `session.create`;\n * channels sessions must do the same through `setup`, and record the preset id\n * on the header meta exactly like the host's own `composeAgent` does.\n *\n * @module dsh-channels/session/manager\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type {\n Agent, AgentContext, AgentHandle, AgentPresets, AgentSetup, HostContext, Session, SessionId,\n} from '../dsh-types.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport { mountChannelCommands } from '../commands.ts'\nimport { appendSessionHistory } from '../domain.ts'\nimport type { RouteRecord } from '../domain.ts'\nimport { mountChannelGuidance } from '../feishu/agent-context.ts'\nimport { attachmentsDir } from '../feishu/inbound-attachments.ts'\nimport { installRequestPolicy } from './request-policy.ts'\nimport type { RequestPolicyController } from './request-policy.ts'\nimport { workspaceTitleFor } from '../workspaces.ts'\n\n/** One live agent plus this plugin's ownership handle. */\nexport interface LiveAgent {\n agent: Agent\n handle: AgentHandle\n}\n\n/** A committed `/renew`: the old and new route records plus the old handle. */\nexport interface RenewCommit {\n previous: RouteRecord\n current: RouteRecord\n oldLive: LiveAgent\n}\n\n/** What `agents.create`/`resume` need to compose one agent from a preset. */\nexport interface PresetComposition {\n /** Preset id recorded on the session header meta (absent without a roster). */\n agentPreset?: string\n /** Mounts the resolved preset inside the agent factory's setup. */\n setup?: AgentSetup\n}\n\nexport interface SessionManagerDeps {\n ctx: HostContext\n config: FeishuConfig\n /** Persist one route record (the `/renew` commit writes the new binding). */\n persistRoute(key: string, record: RouteRecord): Promise<void>\n /** Whether the service still accepts work (shutdown race guard). */\n started(): boolean\n /** Drop streamer state for a session whose owner handle is being released. */\n detachStream(sessionId: string): void\n warn(message: string, ...args: unknown[]): void\n}\n\n/**\n * Wrap a live agent owned by someone else (web UI api-remote resolver, a\n * previous plugin instance, a won cold-resume race). The borrow is fully\n * deliverable through `followup`; disposal releases only the channel-owned\n * scoped policy and never the foreign Agent itself.\n */\nfunction borrowLiveAgent(agent: Agent, release: () => void = () => {}): LiveAgent {\n return { agent, handle: { agent, dispose: async () => { release() } } }\n}\n\nexport class SessionManager {\n private readonly live = new Map<string, LiveAgent>()\n private readonly pendingResume = new Map<string, Promise<LiveAgent>>()\n private readonly policies = new WeakMap<AgentContext, RequestPolicyController>()\n\n constructor(private readonly deps: SessionManagerDeps) {}\n\n /** Live agent entry for a session, when this plugin holds one. */\n get(sessionId: string): LiveAgent | undefined {\n return this.live.get(sessionId)\n }\n\n /** Number of live agent entries this plugin owns or borrows. */\n liveCount(): number {\n return this.live.size\n }\n\n /** Provider/model/token caps sessions this plugin creates. */\n agentOptions(): Record<string, unknown> {\n const options: Record<string, unknown> = {}\n const { provider, model, maxTokens } = this.deps.config\n if (provider !== undefined) options.provider = provider\n if (model !== undefined) options.model = model\n if (maxTokens !== undefined) options.maxTokens = maxTokens\n return options\n }\n\n /**\n * Create the root agent for one freshly routed session and register it live.\n * The caller owns the workspace attachment and the failure card.\n */\n async createSession(\n sessionId: SessionId,\n cwd: string,\n agentOptions: Record<string, unknown> = this.agentOptions(),\n ): Promise<AgentHandle> {\n const composition = await this.presetComposition(undefined, true, attachmentsDir(cwd))\n const handle = await this.deps.ctx.agents.create({\n sessionId,\n meta: {\n cwd,\n ...(composition.agentPreset === undefined ? {} : { agentPreset: composition.agentPreset }),\n },\n agentOptions,\n setup: composition.setup,\n })\n this.requestPolicy(handle.agent)\n this.live.set(sessionId, { agent: handle.agent, handle })\n return handle\n }\n\n /**\n * Live agent for a route: this plugin's own → a foreign live agent (borrow)\n * → a serialized cold resume.\n *\n * The session may already be live under another owner — the web UI's\n * api-remote resolver, a previous plugin instance whose shutdown is still\n * draining, or a concurrent cold resume that won the race. `agents.resume`\n * refuses a live session (\"cannot prepare … while it is live\"), so BORROW\n * the live agent instead: `followup` delivery works identically and the\n * owning side keeps the dispose responsibility.\n */\n async ensureLive(record: RouteRecord): Promise<LiveAgent> {\n const sessionId = record.sessionId\n const own = this.live.get(sessionId)\n if (own !== undefined) return own\n\n const foreign = this.deps.ctx.agents.get(sessionId as SessionId)\n if (foreign !== undefined) return this.borrow(sessionId, foreign)\n\n let pending = this.pendingResume.get(sessionId)\n if (pending === undefined) {\n pending = this.resume(record).finally(() => {\n this.pendingResume.delete(sessionId)\n })\n this.pendingResume.set(sessionId, pending)\n }\n return await pending\n }\n\n /** Borrow one foreign live agent and mount this channel's scoped policy. */\n private borrow(sessionId: string, agent: Agent): LiveAgent {\n const borrowed = borrowLiveAgent(agent, this.mountRequestPolicy(agent.ctx, agent.session))\n this.live.set(sessionId, borrowed)\n return borrowed\n }\n\n /** Cold-resume one persisted session, losing a resume race gracefully. */\n private async resume(record: RouteRecord): Promise<LiveAgent> {\n const sessionId = record.sessionId as SessionId\n try {\n // Compose the resumed world from the same preset resolution as creation\n // (config override or roster default). A session persisted before the\n // preset fix has no preset recorded on its header; re-resolving here is\n // what repairs it on the first resume after a restart.\n const composition = await this.presetComposition(\n undefined, true, attachmentsDir(record.workspacePath),\n )\n const handle = await this.deps.ctx.agents.resume({\n resumeSessionId: sessionId,\n agentOptions: this.agentOptions(),\n setup: composition.setup,\n })\n if (!this.deps.started()) {\n // Shutdown raced this in-flight resume: dispose the just-published\n // agent so it cannot leak into the live registry of the next instance\n // (whose every resume would then fail as \"while it is live\").\n void handle.dispose().catch(() => {})\n throw new Error('dsh-channels: session resume aborted by shutdown')\n }\n this.requestPolicy(handle.agent)\n const liveAgent: LiveAgent = { agent: handle.agent, handle }\n this.live.set(sessionId, liveAgent)\n return liveAgent\n } catch (error) {\n // Lost a cold-resume race: another owner published the session while our\n // resume was in flight. Borrow the winner instead of failing the turn.\n const winner = this.deps.ctx.agents.get(sessionId)\n if (winner !== undefined) return this.borrow(sessionId, winner)\n throw error\n }\n }\n\n /** Mount one mutable Session-aware request policy outside every other wrapper. */\n mountRequestPolicy(agentCtx: AgentContext, session?: Session): () => void {\n const controller = installRequestPolicy(agentCtx, {\n effort: this.deps.config.reasoningEffort,\n provider: this.deps.config.provider,\n model: this.deps.config.model,\n })\n if (session !== undefined) controller.sync(session.snapshotEvents())\n this.policies.set(agentCtx, controller)\n return () => {\n controller.dispose()\n if (this.policies.get(agentCtx) === controller) this.policies.delete(agentCtx)\n }\n }\n\n /** Resolve and synchronize the policy composed for this exact Agent scope. */\n requestPolicy(agent: Agent): RequestPolicyController {\n const controller = this.policies.get(agent.ctx)\n if (controller === undefined) {\n throw new Error('dsh-channels: request policy is not mounted for this Agent')\n }\n controller.sync(agent.session.snapshotEvents())\n return controller\n }\n\n /** Create, workspace-attach, and atomically publish one renewed route. */\n async renew(record: RouteRecord, oldLive: LiveAgent): Promise<RenewCommit> {\n const sessionId = randomUUID() as SessionId\n const preset = this.effectivePreset(oldLive.agent.session)\n const composition = await this.presetComposition(preset, false, attachmentsDir(record.workspacePath))\n const workspace = await this.deps.ctx.workspaceRegistry.create(\n record.workspacePath,\n workspaceTitleFor(\n record.chatType, record.chatId, record.senderOpenId,\n record.kind === 'thread' ? record.key : record.sessionId,\n ),\n )\n\n let newHandle: AgentHandle | undefined\n let attached = false\n try {\n newHandle = await this.deps.ctx.agents.create({\n sessionId,\n meta: {\n cwd: record.workspacePath,\n ...(composition.agentPreset === undefined ? {} : { agentPreset: composition.agentPreset }),\n },\n agentOptions: { ...oldLive.agent.options },\n setup: composition.setup,\n })\n this.requestPolicy(newHandle.agent)\n await workspace.attachSession(sessionId)\n attached = true\n const createdAt = newHandle.agent.session.header.createdAt\n\n const current: RouteRecord = {\n ...record,\n sessionId,\n sessionHistory: appendSessionHistory(record, sessionId, createdAt),\n lastActivityAt: Date.now(),\n }\n await this.deps.persistRoute(record.key, current)\n this.live.set(sessionId, { agent: newHandle.agent, handle: newHandle })\n return { previous: record, current, oldLive }\n } catch (error) {\n if (attached) {\n await Promise.resolve(workspace.detachSession(sessionId)).catch((detachError: unknown) => {\n this.deps.warn(\n 'dsh-channels: rolling back workspace attachment for session %s failed: %s',\n sessionId, String(detachError),\n )\n })\n }\n await newHandle?.dispose().catch(() => {})\n throw error\n }\n }\n\n /** Release only this plugin's old handle, after `command/done` has landed. */\n async releaseRenewed(commit: RenewCommit): Promise<void> {\n const oldSessionId = commit.previous.sessionId\n if (this.live.get(oldSessionId) === commit.oldLive) {\n this.live.delete(oldSessionId)\n this.deps.detachStream(oldSessionId)\n }\n try {\n await commit.oldLive.handle.dispose()\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: releasing renewed session %s handle failed: %s',\n oldSessionId, String(error),\n )\n }\n }\n\n /** Stop and dispose every live agent this plugin owns. */\n async disposeAll(): Promise<void> {\n for (const { handle } of this.live.values()) {\n await handle.dispose().catch(() => {})\n }\n this.live.clear()\n this.pendingResume.clear()\n }\n\n /**\n * Resolve the agent-preset composition for sessions this plugin creates or\n * resumes. The id resolves BEFORE the session exists because the session\n * boundary snapshots `meta` before asynchronous setup begins; mounting stays\n * inside `setup`, where a failure rolls the whole creation back.\n */\n private async presetComposition(\n requestedPreset: string | undefined = this.deps.config.agentPreset,\n useRosterDefault = true,\n attachmentsDir?: string,\n ): Promise<PresetComposition> {\n // Read through ctx.get (safe lookup): only the web profile ships a roster,\n // and direct property access on a cordis context throws for services the\n // plugin did not inject.\n const presets = this.deps.ctx.get?.('agentPresets') as AgentPresets | undefined\n const channelCommandsOnly = async (agentCtx: AgentContext): Promise<void> => {\n // `prepend` is essential: standing preset listeners may have been\n // mounted before this Agent existed, so setup registration order alone\n // cannot guarantee that the explicit channel value wins.\n this.mountRequestPolicy(agentCtx)\n await mountChannelCommands(agentCtx)\n await mountChannelGuidance(agentCtx, attachmentsDir)\n }\n if (presets === undefined || (requestedPreset === undefined && !useRosterDefault)) {\n return { setup: channelCommandsOnly }\n }\n const resolved = await presets.resolve(requestedPreset)\n return {\n agentPreset: resolved.id,\n setup: async (agentCtx) => {\n this.mountRequestPolicy(agentCtx)\n await presets.mount(agentCtx, resolved.id)\n await mountChannelCommands(agentCtx)\n await mountChannelGuidance(agentCtx, attachmentsDir)\n },\n }\n }\n\n /** Effective preset after folding any durable runtime preset switch. */\n private effectivePreset(session: Session): string | undefined {\n let selected = session.header.agentPreset\n for (const event of session.snapshotEvents()) {\n if (event.type === 'agent-preset/selected') selected = event.data.agentPreset\n }\n return selected\n }\n}\n","/**\n * Outbound Feishu delivery: card sends, the topic-creating first reply, body\n * segment fallbacks, and the command reaction lifecycle.\n *\n * Every send is gated on the service being started, and commands are the only\n * plane that adds/deletes reactions (`Typing` → `DONE`); ordinary turns get\n * their reaction lifecycle from the streamer's finalize path.\n *\n * @module dsh-channels/feishu/delivery\n */\n\nimport { buildCommandResultCard } from '../commands.ts'\nimport type { CommandCardState } from '../commands.ts'\nimport type { RouteRecord } from '../domain.ts'\nimport type { FeishuChannel, NormalizedMessage, SendResult } from '../feishu-channel.ts'\n\n/** Rejection text for a message in a topic retired by route TTL. */\nexport const EXPIRED_ROUTE_TEXT = '该话题已超过路由保留时间,并已与原 Session 解绑。为避免误接续上下文,插件不会在此话题创建新 Session;请从私聊或群聊主会话发起新消息(群聊请 @ 机器人)创建新话题。'\n\nexport interface CardDeliveryDeps {\n /** The live channel, or undefined before `start()` / after shutdown. */\n channel(): FeishuChannel | undefined\n /** Whether the service is accepting sends. */\n started(): boolean\n /** Route owning a session (segment-card anchoring). */\n routeForSession(sessionId: string): RouteRecord | undefined\n warn(message: string, ...args: unknown[]): void\n}\n\nexport class CardDelivery {\n constructor(private readonly deps: CardDeliveryDeps) {}\n\n /** The live channel; throws instead of silently dropping a delivery. */\n requireChannel(): FeishuChannel {\n const channel = this.deps.channel()\n if (channel === undefined) throw new Error('dsh-channels: channel not started')\n return channel\n }\n\n /** Apply the command-only Typing → DONE reaction lifecycle. */\n async withCommandReaction(msg: NormalizedMessage, operation: () => Promise<void>): Promise<void> {\n if (!this.deps.started()) return\n const channel = this.requireChannel()\n const reactionId = await channel.addReaction(msg.messageId, 'Typing').catch(() => undefined)\n try {\n await operation()\n } finally {\n if (reactionId !== undefined) {\n await channel.removeReaction(msg.messageId, reactionId).catch(() => {})\n }\n await channel.addReaction(msg.messageId, 'DONE').catch(() => {})\n }\n }\n\n /** Standalone, non-streaming command result card with a non-threaded fallback. */\n async commandCard(\n msg: NormalizedMessage,\n state: CommandCardState,\n options: { createThread?: boolean } = {},\n ): Promise<void> {\n await this.standaloneReply(msg, buildCommandResultCard(state), options)\n }\n\n /** Reject a post-TTL topic message without creating or resuming an Agent. */\n async expiredRouteCard(msg: NormalizedMessage, command?: string): Promise<void> {\n await this.commandCard(msg, {\n kind: 'expired',\n ...(command === undefined ? {} : { command }),\n text: EXPIRED_ROUTE_TEXT,\n })\n }\n\n /**\n * Shared delivery policy for command-plane cards: reply in a new topic by\n * default, then degrade to a plain reply when Feishu refuses the topic.\n */\n async standaloneReply(\n msg: NormalizedMessage,\n card: object,\n options: { createThread?: boolean } = {},\n ): Promise<void> {\n const channel = this.deps.channel()\n if (!this.deps.started() || channel === undefined) return\n const createThread = options.createThread ?? true\n try {\n await channel.send(msg.chatId, { card }, createThread\n ? { replyTo: msg.messageId, replyInThread: true }\n : { replyTo: msg.messageId })\n } catch (error) {\n if (!createThread) {\n this.deps.warn(\n 'dsh-channels: direct command result delivery failed for message %s: %s',\n msg.messageId, String(error),\n )\n return\n }\n try {\n await channel.send(msg.chatId, { card }, { replyTo: msg.messageId })\n } catch (fallbackError) {\n this.deps.warn(\n 'dsh-channels: command result delivery failed for message %s: %s / %s',\n msg.messageId, String(error), String(fallbackError),\n )\n }\n }\n }\n\n /**\n * Send one short plain-text notice anchored to an inbound message. Replies\n * inside an existing topic when the message belongs to one, and never\n * creates a topic: an attachment-only message must stay a notice, not a new\n * conversation. Best-effort — a failed notice only warns.\n */\n async noticeText(msg: NormalizedMessage, text: string): Promise<void> {\n const channel = this.deps.channel()\n if (!this.deps.started() || channel === undefined) return\n try {\n await channel.send(msg.chatId, { text }, {\n replyTo: msg.messageId,\n ...(msg.threadId === undefined ? {} : { replyInThread: true }),\n })\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: attachment notice delivery failed for message %s: %s',\n msg.messageId, String(error),\n )\n }\n }\n\n /**\n * Reply with the first card frame. Prefers topic replies (`reply_in_thread`)\n * — the documented way a bot creates a topic — and falls back to a plain\n * reply when Feishu refuses (degraded chat-scoped routing).\n */\n async replyFirstCard(\n msg: NormalizedMessage,\n card: object,\n ): Promise<{ messageId: string; threadId?: string; degraded: boolean }> {\n const channel = this.requireChannel()\n try {\n const res = await channel.send(\n msg.chatId,\n { card },\n { replyTo: msg.messageId, replyInThread: true },\n )\n const threadId = await channel.threadIdOfMessage(res.messageId).catch(() => undefined)\n return { messageId: res.messageId, threadId, degraded: false }\n } catch (error) {\n if (!this.deps.started()) throw error\n const res = await channel.send(msg.chatId, { card }, { replyTo: msg.messageId })\n return { messageId: res.messageId, degraded: true }\n }\n }\n\n /**\n * Send one body-segment continuation card into the session's topic (or, for\n * degraded chat-scoped routes, as a plain reply anchored to the main card).\n * When Feishu rejects the card (e.g. per-card table limit, error 11310) the\n * segment is re-delivered as a plain-text message so the content is never\n * lost — text messages carry no card constraints.\n */\n async segmentCard(\n sessionId: string,\n segment: number,\n card: object,\n fallbackText: string,\n ): Promise<{ messageId: string; mode: 'card' | 'text' }> {\n const record = this.deps.routeForSession(sessionId)\n if (record === undefined) {\n throw new Error(`dsh-channels: no route record for session ${sessionId}; segment card not sent`)\n }\n const channel = this.requireChannel()\n const options = { replyTo: record.cardMessageId, replyInThread: record.kind === 'thread' }\n try {\n const res: SendResult = await channel.send(record.chatId, { card }, options)\n return { messageId: res.messageId, mode: 'card' }\n } catch (error) {\n this.deps.warn(\n 'dsh-channels: segment %d card for session %s rejected, falling back to plain text: %s',\n segment, sessionId, String(error),\n )\n const res = await channel.send(record.chatId, {\n text: `📄 回复分段 ${segment}(卡片发送失败,已降级为纯文本)\\n\\n${fallbackText}`,\n }, options)\n return { messageId: res.messageId, mode: 'text' }\n }\n }\n}\n","/**\n * Inbound routing: dedup, admission, the route decision tree, and the\n * session-establishment / continuation flow.\n *\n * Everything here is channel-plane policy; the command plane (slash commands)\n * and the answerers stay in the service and are reached through narrow ports.\n * Dependency reads go through accessors so the moved logic stays readable.\n *\n * @module dsh-channels/routing/inbound\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { AgentHandle, HostContext, SessionId, UserMessage, Workspace } from '../dsh-types.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport { buildCard, buildOnboardingCard, initialStreamingCard } from '../cards.ts'\nimport { RouteOperationQueue } from '../commands.ts'\nimport { chatRouteKey } from '../domain.ts'\nimport type { FeishuDomainHandle, RouteRecord } from '../domain.ts'\nimport type { CardDelivery } from '../feishu/delivery.ts'\nimport {\n adoptStagedAttachments, attachmentsDir, hasAttachmentPayload,\n} from '../feishu/inbound-attachments.ts'\nimport { AttachmentIntake } from '../feishu/inbound-attachments.ts'\nimport type { AttachmentArchiveResult, AttachmentFailure } from '../feishu/inbound-attachments.ts'\nimport type { NormalizedMessage } from '../feishu-channel.ts'\nimport type { QuestionAnswerer } from '../interactions/questions.ts'\nimport type { InflightRepair } from '../session/repair.ts'\nimport type { SessionManager } from '../session/manager.ts'\nimport type { RouteStore } from '../session/routes.ts'\nimport type { TurnStreamer } from '../streamer.ts'\nimport { ensureWorkspace, stagingPathFor, topicTokenFor } from '../workspaces.ts'\n\n/** The command plane the inbound router delegates admission-time work to. */\nexport interface CommandPlanePort {\n /** Slash-command admission + dispatch. */\n handleSlash(msg: NormalizedMessage, line: string): Promise<void>\n}\n\n/**\n * Channel-plane admission port (structurally satisfied by the access\n * controller). Kept as a local interface so routing has no dependency on the\n * access module's implementation details.\n */\nexport interface InboundAccessPort {\n /** Full admission for a non-slash message; false = handled (deny/notice). */\n admit(msg: NormalizedMessage): Promise<boolean>\n /**\n * Route ownership: only the sender who created a topic may continue it.\n * Also guards against cross-chat id confusion.\n */\n ownsRoute(msg: NormalizedMessage, record: RouteRecord): boolean\n}\n\nexport interface InboundDeps {\n ctx: HostContext\n config: FeishuConfig\n started(): boolean\n domain(): FeishuDomainHandle | undefined\n routes: RouteStore\n repair: InflightRepair\n delivery: CardDelivery\n sessions: SessionManager\n attachments: AttachmentIntake\n /** Channel-plane access control (administrator + whitelist gates). */\n access: InboundAccessPort\n /** Shared per-route FIFO admission (commands and turns must not interleave). */\n queue: RouteOperationQueue\n streamer(): TurnStreamer | undefined\n questionAnswerer(): QuestionAnswerer | undefined\n commands: CommandPlanePort\n}\n\n/**\n * User-facing text for attachments that could not be archived. Delivered as a\n * plain reply (never a card) so the user learns why the Agent cannot read a\n * file instead of silently getting an answer about older attachments.\n */\nexport function buildAttachmentFailureNotice(failures: readonly AttachmentFailure[]): string {\n const lines = failures.map(failure => `• ${failure.name}:${failure.reason}`)\n return [\n '⚠️ 附件保存失败,Agent 无法读取:',\n ...lines,\n '可尝试压缩/拆分后重发,或把文件直接放到工作区目录。',\n ].join('\\n')\n}\n\n/** Build one durable user message from inbound Feishu text. */\nexport function feishuUserMessage(text: string): UserMessage {\n return {\n id: randomUUID(),\n role: 'user',\n content: [{ type: 'text', text }],\n source: { kind: 'user' },\n } as unknown as UserMessage\n}\n\nexport class InboundRouter {\n /**\n * In-process dedup guard: message id → accept timestamp. Set synchronously\n * at acceptance so a same-process redelivery can never slip through the\n * window between the persisted dedup check and its `put` landing.\n */\n private readonly accepted = new Map<string, number>()\n /**\n * chat-scoped key → in-flight session establishment. Concurrent first\n * messages of one (chat, user) pair all observe the same promise: they wait\n * for the establishment to settle, then either continue the just-created\n * session (same topic, or degraded chat route) or fall through to start\n * their own — never racing each other into duplicate sessions, route\n * overwrites, or the \"no route, no mention\" drop window.\n */\n private readonly establishing = new Map<string, Promise<RouteRecord>>()\n\n constructor(private readonly deps: InboundDeps) {}\n\n private get ctx(): HostContext { return this.deps.ctx }\n private get config(): FeishuConfig { return this.deps.config }\n private get started(): boolean { return this.deps.started() }\n private get routes(): RouteStore { return this.deps.routes }\n private get repair(): InflightRepair { return this.deps.repair }\n private get delivery(): CardDelivery { return this.deps.delivery }\n private get sessions(): SessionManager { return this.deps.sessions }\n private get attachments(): AttachmentIntake { return this.deps.attachments }\n private get routeOperations(): RouteOperationQueue { return this.deps.queue }\n private get streamer(): TurnStreamer | undefined { return this.deps.streamer() }\n private get questionAnswerer(): QuestionAnswerer | undefined { return this.deps.questionAnswerer() }\n private get domain(): FeishuDomainHandle | undefined { return this.deps.domain() }\n private get access(): InboundAccessPort { return this.deps.access }\n\n /** Retire dedup entries older than the retention cutoff (GC sweep). */\n pruneAccepted(cutoff: number): void {\n for (const [messageId, acceptedAt] of this.accepted) {\n if (acceptedAt < cutoff) this.accepted.delete(messageId)\n }\n }\n\n/**\n * Send the one-time onboarding card for a private chat, then remember the\n * chat in the domain global. Best-effort: a storage failure only risks a\n * repeated card, never a blocked session.\n */\n private async sendOnboardingCard(msg: NormalizedMessage): Promise<void> {\n const domain = this.domain\n if (domain === undefined) return\n try {\n const global = domain.getGlobal()\n const welcomed = global.welcomedChats ?? []\n if (welcomed.includes(msg.chatId)) return\n await this.delivery.standaloneReply(msg, buildOnboardingCard(), { createThread: false })\n // Bounded: keep the most recent 500 chats.\n const next = [...welcomed, msg.chatId].slice(-500)\n await domain.setGlobal({ ...global, welcomedChats: next })\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: onboarding card failed: %s', String(error))\n }\n }\n\n /** Drop all in-memory routing state (shutdown). */\n clear(): void {\n this.accepted.clear()\n this.establishing.clear()\n }\n\n /** Entry point: one normalized inbound message. */\n async handle(msg: NormalizedMessage): Promise<void> {\n const botId = this.delivery.requireChannel().botOpenId()\n if (botId !== undefined && msg.senderId === botId) return\n if (msg.chatType !== 'p2p' && msg.chatType !== 'group') return\n let content = (msg.content ?? '').trim()\n if (content === '') return\n\n if (msg.chatType === 'p2p' && this.config.dmMode === 'disabled') return\n\n // 0. Idempotency gate: Feishu redelivers un-acked events on WS reconnect.\n // The in-process guard closes the window between the persisted check\n // and its `put` landing; the durable record covers restarts. Recording\n // happens BEFORE any side effect so a redelivery is dropped, never\n // double-processed (at-most-once per message id).\n if (this.accepted.has(msg.messageId)) return\n if (this.domain?.processed.get(msg.messageId) !== undefined) return\n this.accepted.set(msg.messageId, Date.now())\n try {\n await this.domain?.processed.put(msg.messageId, {\n key: msg.messageId,\n processedAt: Date.now(),\n })\n this.accepted.delete(msg.messageId)\n } catch (error) {\n // Best-effort: the memory guard still covers this process; the failed\n // record only widens the crash window (pruned by the GC sweep later).\n this.ctx.logger?.warn?.('dsh-channels: recording processed message %s failed: %s', msg.messageId, String(error))\n }\n\n // Slash admission stays behind message-id dedup but ahead of every route,\n // mention, workspace, Session, card-stream, and model side effect. The\n // command plane applies its own administrator/bootstrap rules.\n if (content.startsWith('/')) {\n await this.deps.commands.handleSlash(msg, content)\n return\n }\n\n // Channel-plane admission (administrator / group whitelist / user\n // whitelist). Nothing below this point may run for a denied sender: no\n // TTL tombstone, attachment archive, workspace, Session, or model call.\n if (!(await this.access.admit(msg))) return\n\n // Enforce TTL at admission as well as in the periodic sweep. A message that\n // arrives after the deadline must not get one chance to revive the route\n // merely because the background GC has not run yet.\n const expiredThread = msg.threadId === undefined\n ? undefined\n : await this.routes.expireThread(msg.threadId)\n\n // A TTL-retired topic is permanently closed on the channel plane. Keeping\n // a durable tombstone avoids silently forking a fresh Session in a topic\n // whose visible history still belongs to the old Session.\n if (expiredThread !== undefined) {\n await this.delivery.expiredRouteCard(msg)\n return\n }\n\n // Attachments are archived before session work: an attachment-only message\n // produces no model turn, and a caption mixed with attachments keeps only\n // the caption as the prompt. The Agent reads the files on demand later.\n const archived = await this.archiveAttachments(msg)\n if (archived !== undefined) {\n if (archived.failures.length > 0) {\n await this.delivery.noticeText(msg, buildAttachmentFailureNotice(archived.failures))\n }\n if (archived.attachmentOnly) return\n content = archived.text\n if (content === '') return\n }\n\n // A session blocked on `ask_user_question` has no other use for an inbound\n // text message: the next reply becomes the free-text answer.\n if (await this.tryAnswerPendingQuestion(msg, content)) return\n\n // 1. Real topic route first (works without a mention, per product spec).\n if (msg.threadId !== undefined) {\n const record = this.routes.get(msg.threadId)\n if (record !== undefined) {\n // Only the topic creator may continue its Session (admission already\n // passed, but another authorized user could be in the same group).\n if (!this.access.ownsRoute(msg, record)) return\n await this.routeOperations.run(record.key, () => {\n if (!this.started) return\n return this.continueSession(msg, this.routes.get(record.key) ?? record, content)\n })\n return\n }\n }\n\n // 2. Degraded chat-scoped route (topic-less fallback sessions).\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const chatRecord = this.routes.get(chatKey)\n if (chatRecord !== undefined) {\n if (!this.access.ownsRoute(msg, chatRecord)) return\n await this.routeOperations.run(chatRecord.key, () => {\n if (!this.started) return\n return this.continueSession(msg, this.routes.get(chatRecord.key) ?? chatRecord, content)\n })\n return\n }\n\n // 3. In-flight establishment for this (chat, user) pair: wait for it and\n // re-route with serial semantics (see the `establishing` field docs).\n const pending = this.establishing.get(chatKey)\n if (pending !== undefined) {\n const record = await pending.catch(() => undefined)\n if (record !== undefined\n && (record.kind === 'chat' || (msg.threadId !== undefined && msg.threadId === record.key))) {\n await this.routeOperations.run(record.key, () => {\n if (!this.started) return\n return this.continueSession(msg, this.routes.get(record.key) ?? record, content)\n })\n return\n }\n // Thread-scoped record but this message is not in that topic (or the\n // establishment failed): fall through — a fresh session is what the\n // serial order would have produced too.\n }\n\n // 4. New session: groups require a mention (unless disabled).\n if (msg.chatType === 'group' && this.config.requireMention && !msg.mentionedBot) return\n\n await this.startSession(msg, content)\n }\n\n /**\n * Archive the attachment payload of one message, if any. Returns `undefined`\n * when the message carries no archivable payload; otherwise the archive\n * result whose `text` is the remaining prompt (possibly empty).\n *\n * Runs inside the same per-route FIFO as turns so a caption sent right after\n * a file is queued behind the file's download instead of racing it.\n */\n private async archiveAttachments(msg: NormalizedMessage): Promise<AttachmentArchiveResult | undefined> {\n if (!hasAttachmentPayload(msg)) return undefined\n\n const threadRecord = msg.threadId === undefined ? undefined : this.routes.get(msg.threadId)\n // A foreign topic must never receive this sender's attachments: the\n // archive path resolves the workspace from the route record.\n if (threadRecord !== undefined && !this.access.ownsRoute(msg, threadRecord)) return undefined\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const record = threadRecord ?? this.routes.get(chatKey)\n // A brand-new group conversation still needs a mention; a topic the bot\n // already owns does not.\n if (record === undefined && msg.chatType === 'group'\n && this.config.requireMention && !msg.mentionedBot) {\n return { saved: 0, failed: 0, failures: [], attachmentOnly: true, text: '' }\n }\n\n return await this.routeOperations.run(record?.key ?? chatKey, async () => {\n if (!this.started) return { saved: 0, failed: 0, failures: [], attachmentOnly: true, text: '' }\n // An existing topic has a workspace already; a conversation without one\n // stages files below baseDir until its first Session creates the topic\n // workspace and adopts them.\n const directory = record === undefined\n ? stagingPathFor(this.config.baseDir, msg.chatType, msg.chatId, msg.senderId)\n : attachmentsDir(record.workspacePath)\n return await this.attachments.archive(msg, directory)\n })\n }\n /**\n * Consume one plain reply as an `ask_user_question` free-text answer.\n *\n * Only sessions with a pending question are intercepted, so ordinary\n * conversation and commands are untouched.\n */\n private async tryAnswerPendingQuestion(msg: NormalizedMessage, content: string): Promise<boolean> {\n const answerer = this.questionAnswerer\n if (answerer === undefined || answerer.pendingCount() === 0) return false\n const route = await this.commandRoute(msg)\n if (route === undefined || !this.access.ownsRoute(msg, route)) return false\n if (!answerer.hasPendingFor(route.sessionId)) return false\n if (!answerer.submitText(route.sessionId, content)) return false\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'ask_user_question', sessionId: route.sessionId,\n text: '已记录你的文字回答。',\n }, { createThread: false })\n return true\n }\n /** Resolve the route visible to one command without creating a Session. */\n /** Public read-only route resolution (used by the command plane too). */\n async resolveRoute(msg: NormalizedMessage): Promise<RouteRecord | undefined> {\n return await this.commandRoute(msg)\n }\n\n private async commandRoute(msg: NormalizedMessage): Promise<RouteRecord | undefined> {\n if (msg.threadId !== undefined) {\n const threaded = this.routes.get(msg.threadId)\n if (threaded !== undefined) return threaded\n }\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const degraded = this.routes.get(chatKey)\n if (degraded !== undefined) return degraded\n const pending = this.establishing.get(chatKey)\n if (pending === undefined) return undefined\n const record = await pending.catch(() => undefined)\n if (record?.kind === 'chat') return record\n if (record !== undefined && msg.threadId === record.key) return record\n return undefined\n }\n /**\n * Serialize session establishment per (chat, user) pair. The promise is\n * registered under the chat-scoped key SYNCHRONOUSLY (the key is known\n * before any network op), so concurrent first messages all observe the same\n * in-flight establishment instead of racing each other. The entry lives\n * exactly as long as the establishment; waiters holding the promise still\n * see the settled record after the entry is gone.\n */\n private startSession(msg: NormalizedMessage, content: string): Promise<RouteRecord> {\n const chatKey = chatRouteKey(msg.chatType, msg.chatId, msg.senderId)\n const existing = this.establishing.get(chatKey)\n if (existing !== undefined) return existing\n const promise = this.establishSession(msg, content, chatKey)\n this.establishing.set(chatKey, promise)\n void promise.then(\n () => { this.establishing.delete(chatKey) },\n () => { this.establishing.delete(chatKey) },\n )\n return promise\n }\n private async establishSession(msg: NormalizedMessage, content: string, chatKey: string): Promise<RouteRecord> {\n // 0. One-time onboarding for a private chat (persisted, never repeats).\n if (this.config.welcomeOnboarding && msg.chatType === 'p2p') {\n await this.sendOnboardingCard(msg)\n }\n\n // 1. \"Typing\" reaction immediately (id captured for the swap).\n const typingPromise = this.delivery.requireChannel().addReaction(msg.messageId, 'Typing').catch(() => undefined)\n\n const sessionId = randomUUID() as SessionId\n const initialStats = { sessionId, inputTokens: 0, outputTokens: 0 }\n\n // 2. First card frame — also creates the topic (reply_in_thread). The\n // topic identity is only known after Feishu answers, and it decides the\n // workspace: every new topic gets its own directory.\n const sent = await this.delivery.replyFirstCard(msg, initialStreamingCard(sessionId))\n const typingId = await typingPromise\n const key = sent.degraded || sent.threadId === undefined ? chatKey : sent.threadId\n const kind = sent.degraded || sent.threadId === undefined ? 'chat' : 'thread'\n\n // 3. Workspace for this topic, then adopt attachments staged before it.\n let workspace: Workspace\n try {\n workspace = await ensureWorkspace(\n this.ctx, this.config.baseDir, msg.chatType, msg.chatId, msg.senderId,\n topicTokenFor(key, kind, sessionId),\n )\n await adoptStagedAttachments(\n stagingPathFor(this.config.baseDir, msg.chatType, msg.chatId, msg.senderId),\n workspace.path,\n )\n } catch (error) {\n void this.repair.clear(sent.messageId)\n await this.delivery.requireChannel().updateCard(sent.messageId, buildCard({\n phase: 'done',\n reasoning: '',\n body: `会话创建失败:${String(error)}`,\n stats: { ...initialStats },\n })).catch(() => {})\n throw error\n }\n\n // 3.5 Repair snapshot FIRST (before the route record and the agent):\n // the crash window between the two writes then degrades to an orphan\n // snapshot — which startup repair handles safely — instead of a zombie\n // streaming card with no snapshot at all.\n await this.repair.write({\n key: sent.messageId,\n sessionId,\n chatId: msg.chatId,\n triggerMessageId: msg.messageId,\n reactionId: typingId,\n cards: [{ messageId: sent.messageId, mode: 'card' }],\n startedAt: Date.now(),\n attempts: 0,\n })\n\n // 4. Create the agent/session bound to the workspace directory.\n let handle: AgentHandle\n try {\n handle = await this.sessions.createSession(sessionId, workspace.path)\n } catch (error) {\n // The card lands in a final failure state — no repair needed later.\n void this.repair.clear(sent.messageId)\n await this.delivery.requireChannel().updateCard(sent.messageId, buildCard({\n phase: 'done',\n reasoning: '',\n body: `会话创建失败:${String(error)}`,\n stats: { ...initialStats },\n })).catch(() => {})\n throw error\n }\n void Promise.resolve(workspace.attachSession(sessionId)).catch(error => {\n this.ctx.logger?.warn?.(\n 'dsh-channels: attaching initial session %s to workspace failed: %s',\n sessionId, String(error),\n )\n })\n\n // 5. Persist the route (thread-scoped, or degraded chat-scoped).\n const createdAt = handle.agent.session.header.createdAt\n const record: RouteRecord = {\n key,\n kind,\n sessionId,\n chatId: msg.chatId,\n chatType: msg.chatType,\n senderOpenId: msg.senderId,\n workspacePath: workspace.path,\n cardMessageId: sent.messageId,\n createdAt,\n lastActivityAt: createdAt,\n sessionHistory: [{ sessionId, createdAt }],\n }\n await this.routes.put(key, record)\n\n // 6. Stream into the card and hand the message to the driver.\n this.streamer?.attach(handle.agent.session, sent.messageId, msg.messageId, typingId)\n handle.agent.followup(feishuUserMessage(content))\n return record\n }\n private async continueSession(msg: NormalizedMessage, record: RouteRecord, content: string): Promise<void> {\n // Defence in depth: every current caller checks this first, but a future\n // call path must not be able to continue another user's Session.\n if (!this.access.ownsRoute(msg, record)) return\n // Files staged before this conversation's topic existed follow its next\n // turn (for example a file sent in the chat before the first prompt).\n await adoptStagedAttachments(\n stagingPathFor(this.config.baseDir, msg.chatType, msg.chatId, msg.senderId),\n record.workspacePath,\n ).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: adopting staged attachments failed: %s', String(error))\n })\n const typingPromise = this.delivery.requireChannel().addReaction(msg.messageId, 'Typing').catch(() => undefined)\n const live = await this.sessions.ensureLive(record)\n\n const sent = await this.delivery.replyFirstCard(msg, initialStreamingCard(record.sessionId))\n const typingId = await typingPromise\n\n // Repair snapshot first — same crash-window reasoning as `startSession`.\n await this.repair.write({\n key: sent.messageId,\n sessionId: record.sessionId,\n chatId: msg.chatId,\n triggerMessageId: msg.messageId,\n reactionId: typingId,\n cards: [{ messageId: sent.messageId, mode: 'card' }],\n startedAt: Date.now(),\n attempts: 0,\n })\n\n await this.routes.put(record.key, {\n ...record,\n cardMessageId: sent.messageId,\n lastActivityAt: Date.now(),\n })\n this.streamer?.attach(live.agent.session, sent.messageId, msg.messageId, typingId)\n live.agent.followup(feishuUserMessage(content))\n }\n}\n","/**\n * Host bridge, service surface: workspaces, storage domains, credentials,\n * token meter, LLM catalog, session projections, Cordis diagnostics, and the\n * `HostContext` slice this plugin consumes.\n *\n * @module dsh-channels/host/services\n */\n\nimport type { AgentRegistry } from './agent.ts'\nimport type { CommandRuntime } from './commands.ts'\nimport type { AssistantStreamPayload, Session, SessionEvent, SessionId } from './core.ts'\nimport type { ApprovalAnswererListener, UserQuestionAnswererListener } from './interaction.ts'\n\n// ---- Workspace surface --------------------------------------------------\n\nexport interface Workspace {\n readonly id: string\n readonly path: string\n readonly title: string\n attachSession(id: SessionId): Promise<void> | void\n detachSession(id: SessionId): Promise<void> | void\n}\n\nexport interface WorkspaceRegistry {\n create(path: string, title?: string): Promise<Workspace>\n /** Every workspace currently registered with the host. */\n list(): Workspace[]\n /** Unregister one workspace record (the directory itself is the caller's concern). */\n delete(id: string): Promise<boolean>\n}\n\n// ---- Storage / token-meter / llm / credentials surface ------------------\n\n/** The host's storage-domain facility (narrowly typed at the call site). */\nexport interface StorageDomainFacility {\n open(spec: unknown): Promise<unknown>\n}\n\n/** One resolved credential value and the source layer that supplied it. */\nexport interface ResolvedCredential {\n value: string\n source: string\n}\n\n/** Presence/writability facts for one credential reference (never the value). */\nexport interface CredentialInfo {\n configured: boolean\n writable?: boolean\n source?: string\n}\n\n/**\n * The host credentials seam (`@deepseek-ai/dsh-credentials`): resolves a\n * reference per call with the provider's layer precedence\n * (env > $DSH_HOME/.credentials.yaml > .env fallbacks), and can store a value\n * for a reference through the same seam (the Web panel's credential editor).\n * Every member beyond the read path is optional so embedded/test hosts can\n * supply only what they support.\n */\nexport interface CredentialProvider {\n resolve(ref: string): Promise<ResolvedCredential | undefined>\n /** Presence check without revealing the value (the host's `credentials.describe`). */\n describe?(ref: string): Promise<CredentialInfo | undefined>\n /** Store a value for the reference; stored values outrank environment fallbacks. */\n set?(ref: string, value: string): Promise<void>\n /** Remove the stored value (environment fallbacks then apply again). */\n unset?(ref: string): Promise<void>\n}\n\nexport interface TokenMeasurement {\n readonly totalTokens: number\n}\n\nexport interface TokenMeter {\n measure(session: Session, requestHeader?: unknown): TokenMeasurement\n}\n\n/** One registered provider route, as advertised by the LLM service. */\nexport interface LlmProviderInfo {\n id: string\n name: string\n}\n\n/** One adapter-owned reasoning effort advertised for an exact model route. */\nexport interface LlmReasoningEffortInfo {\n /** Opaque stable id accepted by model requests. */\n id: string\n /** Human-readable effort name for selectors. */\n name: string\n description?: string\n}\n\n/** Selectable reasoning efforts for one exact provider/model route. */\nexport interface LlmModelReasoningInfo {\n /** Supported efforts from least to greatest. */\n efforts: readonly LlmReasoningEffortInfo[]\n /** Adapter-configured default effort; absent means the provider default. */\n defaultEffort?: string\n}\n\nexport interface LlmModelInfo {\n /** Provider route that owns this entry. */\n provider?: string\n /** Model id passed to request generation. */\n id?: string\n /** Human-readable model name for selectors. */\n name?: string\n description?: string\n context?: { contextWindow: number }\n /** Adapter-owned reasoning levels, when `resolveModelInfo` reports them. */\n reasoning?: LlmModelReasoningInfo\n}\n\nexport interface LlmService {\n resolveModelInfo(provider: string, model: string): Promise<LlmModelInfo>\n /** Registered provider routes, when the deployment exposes them. */\n listProviders?(): readonly LlmProviderInfo[]\n /** Models advertised for one provider route. */\n listModels?(provider: string): Promise<readonly LlmModelInfo[]>\n}\n\n/**\n * Deployment default model selection (`@deepseek-ai/dsh-agent-default-model`).\n * Web UI and headless entry points read it before creating an Agent; the\n * settings panel only reads it to label the \"inherit\" choice.\n */\nexport interface AgentDefaultModel {\n currentSelection(): { provider: string; model: string; reasoningEffort?: string }\n}\n\n// ---- Session projections -------------------------------------------------\n\n/**\n * Whole-log figures maintained by the host's `sessionStats` projection\n * (`@deepseek-ai/dsh-session-stats`, mounted by the web-app bundle). Reading\n * these replaces re-folding the event log for wall times and TTFT.\n */\nexport interface SessionStatsView {\n turns: number\n steps: number\n llmMs: number\n toolMs: number\n ttftMs: number\n ttftSteps: number\n decodeMs: number\n decodeTokens: number\n}\n\n/** Provider-reported token buckets maintained by the `tokenUsage` projection. */\nexport interface TokenUsageBuckets {\n uncachedInputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n}\n\nexport interface TokenUsageView {\n totals: TokenUsageBuckets\n last: { turn: number; step: number; buckets: TokenUsageBuckets } | null\n}\n\n/** Route capacity and prompt pressure maintained by the `contextPressure` projection. */\nexport interface ContextPressureView {\n pressureTokens?: number\n projectedTokens?: number\n contextWindow?: number\n}\n\n/**\n * The host projection registry (`ctx.sessionProjections`). Read through\n * `ctx.get` so deployments without the registry keep working: `stateOf`\n * returns `undefined` for an unregistered key.\n */\nexport interface SessionProjections {\n stateOf(session: Session, key: string): unknown\n}\n\n\n// ---- Session query -------------------------------------------------------\n\n/** Latest log-backed title of one session. */\nexport interface SessionTitleSnapshot {\n title: string\n updatedAt: number\n}\n\nexport interface SessionTitleObservation {\n /** Absent when the observed log has no title event. */\n title?: SessionTitleSnapshot\n}\n\n/** One ordered result from a batch title observation. */\nexport type SessionTitleObservationResult =\n | { sessionId: SessionId; status: 'fulfilled'; value: SessionTitleObservation }\n | { sessionId: SessionId; status: 'rejected'; reason: unknown }\n\n/**\n * The narrow read side of `ctx.sessionQuery` this plugin consumes: live-first\n * title folding for the sessions a Feishu topic has bound.\n */\nexport interface SessionQuery {\n readTitleSnapshots(sessionIds: readonly SessionId[]): Promise<readonly SessionTitleObservationResult[]>\n}\n\n// ---- Permission presets --------------------------------------------------\n\n/** One selectable sandbox+approval preset offered to clients. */\nexport interface PermissionPresetOption {\n value: string\n name: string\n description?: string\n}\n\n/** The process-level preset catalog exposed to current-session controls. */\nexport interface PermissionCatalog {\n options: PermissionPresetOption[]\n defaultOptions: PermissionPresetOption[]\n defaultPreset: string\n}\n\n/**\n * The host permission-preset service (`ctx.permissionPresets`): reads the\n * current Session's effective preset and records a switched one.\n */\nexport interface PermissionPresets {\n catalog(): PermissionCatalog\n current(session: Session): string\n optionOf(name: string): PermissionPresetOption\n set(session: Session, name: string): void\n}\n\n// ---- Cordis diagnostic surface -----------------------------------------\n\n/** Numeric values mirror Cordis 4's public `FiberState` enum. */\nexport const CordisFiberState = {\n PENDING: 0,\n LOADING: 1,\n ACTIVE: 2,\n FAILED: 3,\n DISPOSED: 4,\n UNLOADING: 5,\n} as const\n\nexport type CordisFiberState = typeof CordisFiberState[keyof typeof CordisFiberState]\n\nexport interface CordisFiber {\n readonly state: CordisFiberState\n /** Best-effort plugin display name (`fiber.name`), used only for health diagnostics. */\n readonly name?: string\n}\n\nexport interface CordisPluginRuntime {\n readonly fibers: Iterable<CordisFiber>\n}\n\nexport interface CordisRegistry {\n readonly size: number\n values(): IterableIterator<CordisPluginRuntime>\n}\n\n// ---- Host context surface ----------------------------------------------\n\n/**\n * The host (Cordis) context slice the plugin consumes. Declared locally and\n * structurally: the host injects every capability through `ctx`, and the\n * plugin never imports cordis at runtime, so there is no dependency on the\n * npm-published cordis typings (whose `.ts`-specifier augmentations do not\n * resolve under bundler module resolution).\n */\nexport interface HostLogger {\n debug?(...args: unknown[]): void\n info?(...args: unknown[]): void\n warn?(...args: unknown[]): void\n error?(...args: unknown[]): void\n}\n\nexport interface HostContext {\n agents: AgentRegistry\n commands: CommandRuntime\n workspaceRegistry: WorkspaceRegistry\n storageDomain: StorageDomainFacility\n /** Resolved through the injected `credentials` seam (env > .credentials.yaml > .env). */\n credentials: CredentialProvider\n tokenMeter: TokenMeter\n llm: LlmService\n /** Built-in Cordis plugin registry used for aggregate health diagnostics. */\n registry: CordisRegistry\n logger?: HostLogger\n /** Safe service lookup (returns undefined for absent services). */\n get?(name: string): unknown\n /** Host events this plugin subscribes to (overloads per event). */\n on(name: 'session/event', listener: (session: Session, event: SessionEvent) => void): () => boolean\n /** Live (transient) model stream for every agent; replaces the removed `assistant/chunk` events. */\n on(name: 'agent/assistant-stream', listener: (payload: AssistantStreamPayload) => void): () => boolean\n /**\n * The host's structured-question waterfall (`ask_user_question`). Same\n * shape as `approval/request`: return an answer to claim, or delegate with\n * `next()`. Untagged plugin contexts receive requests for every agent, so\n * ownership filtering is mandatory.\n */\n on(\n name: 'user-questions/request',\n listener: UserQuestionAnswererListener,\n options?: { prepend?: boolean },\n ): () => boolean\n /**\n * The host's HITL answerer waterfall. Registered with `prepend: true` so\n * this channel claims its own sessions before generic answerers (the web\n * gateway); non-owned requests delegate through `next()`. Untagged plugin\n * contexts receive requests for EVERY agent, so ownership filtering is\n * mandatory. Safe to register without the approval service mounted — the\n * event simply never fires.\n */\n on(\n name: 'approval/request',\n listener: ApprovalAnswererListener,\n options?: { prepend?: boolean },\n ): () => boolean\n effect(callback: () => (() => void | Promise<void>) | void, label?: string): void\n}\n","/**\n * Privacy-preserving health snapshot and Feishu card rendering.\n *\n * The fold consumes only aggregate lifecycle and service-presence signals.\n * It never accepts configuration values, credentials, fiber errors, or stack\n * traces, which keeps accidental disclosure out of the rendering boundary.\n *\n * @module dsh-channels/health\n */\n\nimport { CordisFiberState } from './dsh-types.ts'\nimport type { CordisFiber } from './dsh-types.ts'\nimport type { FeishuConnectionStatus } from './feishu-channel.ts'\nimport { standaloneCard } from './cards/primitives.ts'\n\nexport const HEALTH_DEPENDENCY_NAMES = [\n 'agents',\n 'commands',\n 'workspace',\n 'storage',\n 'credentials',\n 'llm',\n 'token-meter',\n] as const\n\nexport type HealthDependencyName = typeof HEALTH_DEPENDENCY_NAMES[number]\nexport type HealthLevel = 'healthy' | 'degraded' | 'unhealthy'\n\nexport interface FiberHealthCounts {\n active: number\n pending: number\n loading: number\n failed: number\n unloading: number\n}\n\n/** States that make the Fold report something other than healthy. */\nexport type FiberIssueState = 'pending' | 'loading' | 'unloading' | 'failed'\n\n/** One non-active fiber with a best-effort plugin name (no errors/stacks). */\nexport interface FiberHealthIssue {\n state: FiberIssueState\n label: string\n}\n\n/** Upper bound on the diagnostic list; counts still include every fiber. */\nconst MAX_INACTIVE_FIBERS = 12\n\nexport interface HealthSnapshotInput {\n fibers: Iterable<CordisFiber>\n dependencies: Readonly<Record<HealthDependencyName, boolean>>\n connection?: FeishuConnectionStatus\n botOpenId?: string\n started: boolean\n routeCount: number\n liveAgentCount: number\n inflightTurnCount: number\n}\n\nexport interface HealthSnapshot extends Omit<HealthSnapshotInput, 'fibers' | 'botOpenId'> {\n level: HealthLevel\n fibers: FiberHealthCounts\n /** Masked display only; the complete open_id never crosses this boundary. */\n botOpenIdTail?: string\n /** Non-active plugin fibers (capped at MAX_INACTIVE_FIBERS), diagnostics only. */\n inactiveFibers: FiberHealthIssue[]\n /** Total non-active fibers, including those beyond the capped list. */\n inactiveTotal: number\n /** Human-readable explanation of `level`; empty when healthy. */\n reasons: string[]\n}\n\n/** Best-effort label for a fiber: Cordis exposes the plugin display name on the fiber. */\nfunction fiberLabel(fiber: CordisFiber): string {\n const name = fiber.name?.trim()\n return name !== undefined && name !== '' ? name : '(未命名插件)'\n}\n\n/** One pass over the fiber table: state counts plus the non-active diagnostics. */\nfunction foldFiberStates(fibers: Iterable<CordisFiber>): {\n counts: FiberHealthCounts\n inactive: FiberHealthIssue[]\n inactiveTotal: number\n} {\n const counts: FiberHealthCounts = { active: 0, pending: 0, loading: 0, failed: 0, unloading: 0 }\n const inactive: FiberHealthIssue[] = []\n let inactiveTotal = 0\n for (const fiber of fibers) {\n const issueState: FiberIssueState | undefined =\n fiber.state === CordisFiberState.PENDING ? 'pending'\n : fiber.state === CordisFiberState.LOADING ? 'loading'\n : fiber.state === CordisFiberState.UNLOADING ? 'unloading'\n : fiber.state === CordisFiberState.FAILED ? 'failed'\n : undefined\n if (fiber.state === CordisFiberState.ACTIVE) {\n counts.active++\n continue\n }\n if (issueState === undefined) continue // disposed fibers are not health signals\n counts[issueState === 'pending' ? 'pending' : issueState === 'loading' ? 'loading' : issueState === 'unloading' ? 'unloading' : 'failed']++\n inactiveTotal++\n if (inactive.length < MAX_INACTIVE_FIBERS) inactive.push({ state: issueState, label: fiberLabel(fiber) })\n }\n return { counts, inactive, inactiveTotal }\n}\n\n/** Fold every non-disposed Cordis fiber into the five product-facing states. */\nexport function countFiberStates(fibers: Iterable<CordisFiber>): FiberHealthCounts {\n return foldFiberStates(fibers).counts\n}\n\n/** Human-readable connection state for diagnostics (keeps raw states visible). */\nfunction connectionReason(state: string | undefined): string {\n switch (state) {\n case 'connected': return '已连接'\n case 'connecting': return '连接中'\n case 'reconnecting': return '重连中'\n case 'failed': return '连接失败'\n case 'idle': return '空闲(未连接)'\n default: return state ?? '未知'\n }\n}\n\nfunction labelList(labels: readonly string[], total: number, max = 6): string {\n if (labels.length === 0) return ''\n const shown = labels.slice(0, max).join('、')\n return total > Math.min(labels.length, max) ? `${shown} 等 ${total} 个` : shown\n}\n\n/** Reveal only the final four characters, and nothing for unusually short ids. */\nexport function maskOpenIdTail(openId?: string): string | undefined {\n if (openId === undefined || openId.length <= 4) return undefined\n return `…${openId.slice(-4)}`\n}\n\n/** One deterministic fold owns both the snapshot and its overall conclusion. */\nexport function foldHealthSnapshot(input: HealthSnapshotInput): HealthSnapshot {\n const { counts: fibers, inactive, inactiveTotal } = foldFiberStates(input.fibers)\n const missingDependencies = HEALTH_DEPENDENCY_NAMES.filter(name => !input.dependencies[name])\n const dependencyMissing = missingDependencies.length > 0\n const connectionState = input.connection?.state\n\n let level: HealthLevel\n if (!input.started || dependencyMissing || connectionState === 'failed' || fibers.failed > 0) {\n level = 'unhealthy'\n } else if (\n connectionState !== 'connected'\n || fibers.pending > 0\n || fibers.loading > 0\n || fibers.unloading > 0\n ) {\n level = 'degraded'\n } else {\n level = 'healthy'\n }\n\n const reasons: string[] = []\n if (!input.started) reasons.push('飞书通道尚未启动')\n if (dependencyMissing) reasons.push(`主机服务未就绪:${missingDependencies.join('、')}`)\n if (input.connection === undefined) reasons.push('飞书长连接尚未建立')\n else if (connectionState === 'failed') reasons.push('飞书长连接失败')\n else if (connectionState !== 'connected') reasons.push(`飞书长连接状态:${connectionReason(connectionState)}`)\n\n const failedLabels = inactive.filter(issue => issue.state === 'failed').map(issue => issue.label)\n if (fibers.failed > 0) {\n reasons.push(`插件加载失败 ${fibers.failed} 个${failedLabels.length === 0 ? '' : `:${labelList(failedLabels, fibers.failed)}`}`)\n }\n const stalled = fibers.pending + fibers.loading + fibers.unloading\n if (stalled > 0) {\n const stalledLabels = inactive\n .filter(issue => issue.state === 'pending' || issue.state === 'loading' || issue.state === 'unloading')\n .map(issue => issue.label)\n reasons.push(\n `宿主其它插件仍有 ${stalled} 个入口在等待依赖或加载/卸载(与飞书通道无关)`\n + (stalledLabels.length === 0 ? '' : `:${labelList(stalledLabels, stalled)}`),\n )\n }\n\n return {\n level,\n fibers,\n dependencies: input.dependencies,\n connection: input.connection,\n botOpenIdTail: maskOpenIdTail(input.botOpenId),\n started: input.started,\n routeCount: input.routeCount,\n liveAgentCount: input.liveAgentCount,\n inflightTurnCount: input.inflightTurnCount,\n inactiveFibers: inactive,\n inactiveTotal,\n reasons,\n }\n}\n\nconst LEVEL_PRESENTATION: Record<HealthLevel, { icon: string; title: string }> = {\n healthy: { icon: '✅', title: '运行健康' },\n degraded: { icon: '⚠️', title: '运行降级' },\n unhealthy: { icon: '❌', title: '运行异常' },\n}\n\nfunction formatTimestamp(timestamp?: number): string {\n if (timestamp === undefined || !Number.isFinite(timestamp)) return '—'\n const date = new Date(timestamp)\n return Number.isNaN(date.getTime()) ? '—' : date.toISOString()\n}\n\n/** Render a standalone non-streaming health card from the sanitized snapshot. */\nexport function buildHealthCard(snapshot: HealthSnapshot): object {\n const presentation = LEVEL_PRESENTATION[snapshot.level]\n const dependencyLine = HEALTH_DEPENDENCY_NAMES\n .map(name => `${snapshot.dependencies[name] ? '✅' : '❌'} ${name}`)\n .join(' · ')\n const connection = snapshot.connection\n const connectionState = connection?.state ?? 'unknown'\n\n return standaloneCard(`**${presentation.icon} 健康检查 · ${presentation.title}**`, [\n { tag: 'markdown', content: `整体结论:**${snapshot.level}**` },\n { tag: 'hr' },\n {\n tag: 'markdown',\n content: `**Harness fibers**\\nactive ${snapshot.fibers.active} · pending ${snapshot.fibers.pending} · loading ${snapshot.fibers.loading} · failed ${snapshot.fibers.failed} · unloading ${snapshot.fibers.unloading}`,\n },\n { tag: 'markdown', content: `**关键依赖**\\n${dependencyLine}` },\n {\n tag: 'markdown',\n content: `**飞书 WebSocket**\\n${connectionState} · 最近连接 ${formatTimestamp(connection?.lastConnectTime)} · 下次重连 ${formatTimestamp(connection?.nextConnectTime)} · 重连次数 ${connection?.reconnectAttempts ?? 0} · bot open_id ${snapshot.botOpenIdTail ?? '—'}`,\n },\n {\n tag: 'markdown',\n content: `**dsh-channels**\\n${snapshot.started ? 'started' : 'stopped'} · 持久化路由 ${snapshot.routeCount} · live Agent ${snapshot.liveAgentCount} · in-flight turn ${snapshot.inflightTurnCount}`,\n },\n ])\n}\n","/**\n * Whole-session diagnostic folds and context measurement.\n *\n * All historical figures come from the durable event log, so a live Agent\n * and the same Session resumed after restart produce identical output.\n * Command lifecycle records are intentionally ignored.\n *\n * @module dsh-channels/stats\n */\n\nimport type {\n AssistantStreamRecord, ContextPressureView, LlmService, Session, SessionEvent, SessionProjections, SessionStatsView,\n TokenMeter, TokenUsage, TokenUsageView,\n} from './dsh-types.ts'\n\nexport interface SessionTotals {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n reasoningTokens: number\n}\n\nexport const EMPTY_TOTALS: SessionTotals = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n reasoningTokens: 0,\n}\n\nfunction usableCount(value: number | undefined): number {\n return value !== undefined && Number.isFinite(value) && value >= 0 ? value : 0\n}\n\nexport function addUsage(totals: SessionTotals, usage: TokenUsage): SessionTotals {\n return {\n inputTokens: totals.inputTokens + usableCount(usage.inputTokens),\n outputTokens: totals.outputTokens + usableCount(usage.outputTokens),\n cacheReadTokens: totals.cacheReadTokens + usableCount(usage.cacheReadTokens),\n cacheWriteTokens: totals.cacheWriteTokens + usableCount(usage.cacheWriteTokens),\n reasoningTokens: totals.reasoningTokens + usableCount(usage.reasoningTokens),\n }\n}\n\n/** Latest provider usage sample carried by an `assistant/message` event. */\nexport interface UsageSample {\n usage: TokenUsage\n turn: number\n step: number\n}\n\nexport interface HeaderSample {\n provider: string\n model: string\n effort?: string\n}\n\nexport type ToolEventStatus = 'success' | 'error' | 'running'\n\nexport interface ToolEventSample {\n callId: string\n name: string\n status: ToolEventStatus\n durationMs?: number\n startedAt?: number\n}\n\ninterface OpenStep {\n turn: number\n step: number\n startTime?: number\n}\n\ninterface PendingTool {\n callId: string\n name: string\n startedAt?: number\n}\n\n/** Complete fold state and public whole-log status figures. */\nexport interface StatsFold {\n totals: SessionTotals\n usageSamples: number\n lastUsage: UsageSample | undefined\n lastHeader: HeaderSample | undefined\n llmMs: number\n llmSteps: number\n toolMs: number\n toolTimedCalls: number\n ttftMs: number\n ttftSteps: number\n decodeMs: number\n decodeTokens: number\n toolCalls: number\n pendingTools: Map<string, PendingTool>\n recentTools: ToolEventSample[]\n openStep: OpenStep | undefined\n}\n\nfunction eventTime(event: SessionEvent): number | undefined {\n return event.time !== undefined && Number.isFinite(event.time) ? event.time : undefined\n}\n\n/**\n * First non-whitespace delta time inside one durable embedded assistant\n * stream (`assistant/message.stream`). Local fallback for deployments where\n * the host `sessionStats` projection is not mounted; the projection remains\n * authoritative when present.\n */\nfunction firstTokenTime(stream: readonly AssistantStreamRecord[] | undefined): number | undefined {\n let best: number | undefined\n for (const record of stream ?? []) {\n const time0 = record.time0\n if (typeof time0 !== 'number' || !Number.isFinite(time0)) continue\n if (record.type !== 'text-chunks' && record.type !== 'reasoning-chunks') continue\n const texts = record.texts\n const hasVisible = Array.isArray(texts)\n && texts.some(text => typeof text === 'string' && /\\S/u.test(text))\n if (hasVisible && (best === undefined || time0 < best)) best = time0\n }\n return best\n}\n\n/**\n * Authoritative call correlation and failure flag of one `tool/result`.\n *\n * DSH's `ToolResultMessage` is `{ role:'tool', source:{kind:'tool', callId},\n * content, isError? }` and has no `tool-result` content-block type, so the\n * message-level `source.callId` / `isError` decide; the first content block is\n * only a legacy fallback.\n */\nfunction toolResultCallId(event: Extract<SessionEvent, { type: 'tool/result' }>): string | undefined {\n const sourceId = event.data.message.source?.callId\n if (typeof sourceId === 'string' && sourceId !== '') return sourceId\n const blockId = event.data.message.content?.find(block => typeof block.toolCallId === 'string')?.toolCallId\n return typeof blockId === 'string' && blockId !== '' ? blockId : undefined\n}\n\nfunction toolResultFailed(event: Extract<SessionEvent, { type: 'tool/result' }>): boolean {\n if (event.data.error !== undefined) return true\n if (event.data.message.isError === true) return true\n return event.data.message.content?.some(block => block.isError === true) ?? false\n}\n\nfunction elapsed(start: number | undefined, end: number | undefined): number | undefined {\n if (start === undefined || end === undefined) return undefined\n return Math.max(0, end - start)\n}\n\nfunction pushRecentTool(fold: StatsFold, sample: ToolEventSample): void {\n fold.recentTools.push(sample)\n if (fold.recentTools.length > 5) fold.recentTools.shift()\n}\n\n/** Fold one durable event into the running Session figures. */\nexport function foldEvent(fold: StatsFold, event: SessionEvent): void {\n switch (event.type) {\n case 'step/start':\n fold.openStep = {\n turn: event.data.turn,\n step: event.data.step,\n startTime: eventTime(event),\n }\n break\n case 'assistant/message': {\n const usage = event.data.usage\n if (usage !== undefined) {\n fold.totals = addUsage(fold.totals, usage)\n fold.usageSamples++\n fold.lastUsage = { usage, turn: event.data.turn, step: event.data.step }\n }\n const open = fold.openStep\n if (open === undefined || open.turn !== event.data.turn || open.step !== event.data.step) break\n const completedAt = eventTime(event)\n const llmDuration = elapsed(open.startTime, completedAt)\n if (llmDuration !== undefined) {\n fold.llmMs += llmDuration\n fold.llmSteps++\n }\n // Live `assistant/chunk` events no longer exist; the first-token time is\n // recovered from the durable embedded stream. The host `sessionStats`\n // projection (overlaid by `applyProjectionViews`) is authoritative when\n // mounted.\n const firstToken = firstTokenTime(event.data.stream)\n if (firstToken !== undefined) {\n const ttft = elapsed(open.startTime, firstToken)\n if (ttft !== undefined) {\n fold.ttftMs += ttft\n fold.ttftSteps++\n }\n const outputTokens = usage?.outputTokens\n const decodeDuration = elapsed(firstToken, completedAt)\n if (decodeDuration !== undefined && outputTokens !== undefined\n && Number.isFinite(outputTokens) && outputTokens >= 0) {\n fold.decodeMs += decodeDuration\n fold.decodeTokens += outputTokens\n }\n }\n fold.openStep = undefined\n break\n }\n case 'step/end':\n fold.openStep = undefined\n break\n case 'request/header': {\n const config = event.data.header?.config\n if (config !== undefined && typeof config === 'object') {\n const cfg = config as { provider?: unknown; model?: unknown; reasoningEffort?: unknown }\n fold.lastHeader = {\n provider: typeof cfg.provider === 'string' ? cfg.provider : '',\n model: typeof cfg.model === 'string' ? cfg.model : '',\n effort: typeof cfg.reasoningEffort === 'string' ? cfg.reasoningEffort : undefined,\n }\n }\n break\n }\n case 'tool/call': {\n fold.toolCalls++\n const pending: PendingTool = {\n callId: event.data.callId,\n name: event.data.name,\n startedAt: eventTime(event),\n }\n fold.pendingTools.set(event.data.callId, pending)\n pushRecentTool(fold, { ...pending, status: 'running' })\n break\n }\n case 'tool/result': {\n const callId = toolResultCallId(event)\n if (callId === undefined) break\n const pending = fold.pendingTools.get(callId)\n if (pending === undefined) break\n fold.pendingTools.delete(callId)\n const durationMs = elapsed(pending.startedAt, eventTime(event))\n const settled: ToolEventSample = {\n callId,\n name: pending.name,\n status: toolResultFailed(event) ? 'error' : 'success',\n startedAt: pending.startedAt,\n ...(durationMs === undefined ? {} : { durationMs }),\n }\n const recentIndex = fold.recentTools.findIndex(sample => sample.callId === callId)\n if (recentIndex !== -1) fold.recentTools.splice(recentIndex, 1)\n pushRecentTool(fold, settled)\n if (durationMs !== undefined) {\n fold.toolMs += durationMs\n fold.toolTimedCalls++\n }\n break\n }\n default:\n break\n }\n}\n\n/** Fold one complete Session log. */\nexport function foldAll(events: readonly SessionEvent[]): StatsFold {\n const fold: StatsFold = {\n totals: { ...EMPTY_TOTALS },\n usageSamples: 0,\n lastUsage: undefined,\n lastHeader: undefined,\n llmMs: 0,\n llmSteps: 0,\n toolMs: 0,\n toolTimedCalls: 0,\n ttftMs: 0,\n ttftSteps: 0,\n decodeMs: 0,\n decodeTokens: 0,\n toolCalls: 0,\n pendingTools: new Map(),\n recentTools: [],\n openStep: undefined,\n }\n for (const event of events) foldEvent(fold, event)\n return fold\n}\n\n/** Cache hit ratio of the latest usage sample: cacheRead / (cacheRead + uncached input). */\nexport function cacheHitPct(lastUsage: UsageSample | undefined): number | undefined {\n if (lastUsage === undefined) return undefined\n const hit = lastUsage.usage.cacheReadTokens\n if (hit === undefined || !Number.isFinite(hit) || hit < 0) return undefined\n const input = lastUsage.usage.inputTokens\n if (!Number.isFinite(input) || input < 0) return undefined\n const total = hit + input\n return total > 0 ? hit / total : undefined\n}\n\nexport interface ContextMeasurement {\n contextTokens?: number\n contextWindow?: number\n}\n\nexport interface ContextServices {\n tokenMeter?: TokenMeter\n llm?: LlmService\n /** Optional projection registry; supplies the authoritative context window. */\n projections?: SessionProjections\n}\n\n/** Projection readings for one session; every key is optional (unmounted unit). */\nexport interface ProjectionViews {\n sessionStats?: SessionStatsView\n tokenUsage?: TokenUsageView\n contextPressure?: ContextPressureView\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null ? value as Record<string, unknown> : undefined\n}\n\nfunction finiteNonNegative(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined\n}\n\nfunction positive(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined\n}\n\n/** Read the host projection units this plugin consumes, tolerating absence. */\nexport function readProjectionViews(\n projections: SessionProjections | undefined,\n session: Session,\n): ProjectionViews {\n if (projections === undefined) return {}\n const views: ProjectionViews = {}\n try {\n const stats = asRecord(projections.stateOf(session, 'sessionStats'))\n if (stats !== undefined) {\n const parsed: SessionStatsView = {\n turns: finiteNonNegative(stats.turns) ?? 0,\n steps: finiteNonNegative(stats.steps) ?? 0,\n llmMs: finiteNonNegative(stats.llmMs) ?? 0,\n toolMs: finiteNonNegative(stats.toolMs) ?? 0,\n ttftMs: finiteNonNegative(stats.ttftMs) ?? 0,\n ttftSteps: finiteNonNegative(stats.ttftSteps) ?? 0,\n decodeMs: finiteNonNegative(stats.decodeMs) ?? 0,\n decodeTokens: finiteNonNegative(stats.decodeTokens) ?? 0,\n }\n views.sessionStats = parsed\n }\n } catch { /* a failing projection must never break the fold */ }\n try {\n const usage = asRecord(projections.stateOf(session, 'tokenUsage'))\n const totals = asRecord(usage?.totals)\n if (usage !== undefined && totals !== undefined) {\n const buckets = (input: Record<string, unknown> | undefined) => ({\n uncachedInputTokens: finiteNonNegative(input?.uncachedInputTokens) ?? 0,\n outputTokens: finiteNonNegative(input?.outputTokens) ?? 0,\n cacheReadTokens: finiteNonNegative(input?.cacheReadTokens) ?? 0,\n cacheWriteTokens: finiteNonNegative(input?.cacheWriteTokens) ?? 0,\n })\n const last = asRecord(usage.last)\n views.tokenUsage = {\n totals: buckets(totals),\n last: last === undefined\n ? null\n : {\n turn: finiteNonNegative(last.turn) ?? 0,\n step: finiteNonNegative(last.step) ?? 0,\n buckets: buckets(asRecord(last.buckets)),\n },\n }\n }\n } catch { /* ignore malformed projection state */ }\n try {\n const pressure = asRecord(projections.stateOf(session, 'contextPressure'))\n if (pressure !== undefined) {\n views.contextPressure = {\n ...(finiteNonNegative(pressure.pressureTokens) === undefined\n ? {} : { pressureTokens: finiteNonNegative(pressure.pressureTokens) }),\n ...(finiteNonNegative(pressure.projectedTokens) === undefined\n ? {} : { projectedTokens: finiteNonNegative(pressure.projectedTokens) }),\n ...(positive(pressure.contextWindow) === undefined\n ? {} : { contextWindow: positive(pressure.contextWindow) }),\n }\n }\n } catch { /* ignore malformed projection state */ }\n return views\n}\n\n/** Map a projection token bucket into the plugin's usage vocabulary. */\nexport function usageFromBuckets(buckets: TokenUsageView['totals']): TokenUsage {\n return {\n inputTokens: buckets.uncachedInputTokens,\n outputTokens: buckets.outputTokens,\n cacheReadTokens: buckets.cacheReadTokens,\n cacheWriteTokens: buckets.cacheWriteTokens,\n }\n}\n\n/**\n * Overlay host projection figures onto a log fold. The host units are\n * authoritative where mounted (they fold the same log, including the\n * embedded streams the plugin no longer sees as live events); the log fold\n * stays as the fallback and still owns tool-detail fields.\n */\nexport function applyProjectionViews(fold: StatsFold, views: ProjectionViews): StatsFold {\n const stats = views.sessionStats\n if (stats !== undefined) {\n // Times are host-authoritative; counts (llmSteps / toolTimedCalls) stay\n // with the fold, which sees the same boundaries.\n fold.llmMs = stats.llmMs\n fold.toolMs = stats.toolMs\n fold.ttftMs = stats.ttftMs\n fold.ttftSteps = stats.ttftSteps\n fold.decodeMs = stats.decodeMs\n fold.decodeTokens = stats.decodeTokens\n }\n const usage = views.tokenUsage\n if (usage !== undefined) {\n fold.totals = {\n inputTokens: usage.totals.uncachedInputTokens,\n outputTokens: usage.totals.outputTokens,\n cacheReadTokens: usage.totals.cacheReadTokens,\n cacheWriteTokens: usage.totals.cacheWriteTokens,\n reasoningTokens: fold.totals.reasoningTokens,\n }\n if (usage.last !== null) {\n fold.lastUsage = {\n usage: usageFromBuckets(usage.last.buckets),\n turn: usage.last.turn,\n step: usage.last.step,\n }\n fold.usageSamples = Math.max(fold.usageSamples, 1)\n }\n }\n return fold\n}\n\n/** Read one session's figures from the projection registry, falling back to the log fold. */\nexport function foldSessionStats(\n session: Session,\n projections: SessionProjections | undefined,\n): StatsFold {\n const fold = foldAll(session.snapshotEvents())\n return applyProjectionViews(fold, readProjectionViews(projections, session))\n}\n\n/** Measure current surface pressure and resolve the latest model's context capacity. */\nexport async function measureSessionContext(\n session: Session,\n services: ContextServices,\n header = session.requestHeader?.()?.config ?? foldAll(session.snapshotEvents()).lastHeader,\n): Promise<ContextMeasurement> {\n let contextTokens: number | undefined\n try {\n const measured = services.tokenMeter?.measure(session).totalTokens\n if (measured !== undefined && Number.isFinite(measured) && measured >= 0) contextTokens = measured\n } catch {\n contextTokens = undefined\n }\n\n let contextWindow: number | undefined\n if (services.projections !== undefined) {\n try {\n contextWindow = readProjectionViews(services.projections, session).contextPressure?.contextWindow\n } catch {\n contextWindow = undefined\n }\n }\n if (contextWindow === undefined && header !== undefined\n && typeof header.provider === 'string' && header.provider !== ''\n && typeof header.model === 'string' && header.model !== '') {\n try {\n const resolved = await services.llm?.resolveModelInfo(header.provider, header.model)\n const window = resolved?.context?.contextWindow\n if (window !== undefined && Number.isFinite(window) && window > 0) contextWindow = window\n } catch {\n contextWindow = undefined\n }\n }\n return { contextTokens, contextWindow }\n}\n\nexport function averageTtftMs(stats: StatsFold): number | undefined {\n return stats.ttftSteps > 0 ? stats.ttftMs / stats.ttftSteps : undefined\n}\n\nexport function averageDecodeTokensPerSecond(stats: StatsFold): number | undefined {\n return stats.decodeMs > 0 ? stats.decodeTokens / stats.decodeMs * 1000 : undefined\n}\n","/**\n * Pure result rendering and validation for Session-scoped slash commands.\n *\n * @module dsh-channels/session-commands\n */\n\nimport path from 'node:path'\nimport { standaloneCard } from './cards/primitives.ts'\nimport type { RouteRecord, SessionHistoryEntry } from './domain.ts'\nimport { normalizeSessionHistory } from './domain.ts'\nimport type { ContextMeasurement, StatsFold, ToolEventSample } from './stats.ts'\nimport {\n averageDecodeTokensPerSecond, averageTtftMs, cacheHitPct,\n} from './stats.ts'\n\nfunction fmtCount(value: number): string {\n return value.toLocaleString('en-US')\n}\n\nfunction fmtDuration(value: number | undefined): string {\n if (value === undefined || !Number.isFinite(value)) return '暂无数据'\n if (value < 1000) return `${Math.round(value)} ms`\n return `${(value / 1000).toFixed(2)} s`\n}\n\nfunction fmtPercent(value: number | undefined): string {\n return value === undefined || !Number.isFinite(value) ? '暂无数据' : `${(value * 100).toFixed(1)}%`\n}\n\nfunction fmtRate(value: number | undefined): string {\n return value === undefined || !Number.isFinite(value) ? '暂无数据' : `${value.toFixed(1)} tokens/s`\n}\n\nfunction contextLine(measurement: ContextMeasurement): string {\n const tokens = measurement.contextTokens\n const window = measurement.contextWindow\n if (tokens === undefined) return '暂无数据'\n if (window === undefined) return `${fmtCount(tokens)} tokens`\n return `${fmtCount(tokens)} / ${fmtCount(window)} (${(tokens / window * 100).toFixed(1)}%)`\n}\n\nfunction escapeInlineCode(value: string): string {\n return value.replaceAll('`', '\\\\`')\n}\n\nfunction toolLine(sample: ToolEventSample): string {\n const status = sample.status === 'running' ? '运行中' : sample.status === 'success' ? '成功' : '失败'\n const duration = sample.status === 'running' ? '尚未完成' : fmtDuration(sample.durationMs)\n return `- \\`${escapeInlineCode(sample.name)}\\` · ${status} · ${duration}`\n}\n\nexport interface SessionStatusCardInput {\n sessionId: string\n stats: StatsFold\n context: ContextMeasurement\n /** Optional operational extras (permission preset, workspace, uptime, …). */\n extras?: SessionStatusExtras\n}\n\n/** Operational extras appended to the `/status` card when available. */\nexport interface SessionStatusExtras {\n /** Effective permission preset of this Session (`ctx.permissionPresets`). */\n permissionPreset?: string\n /** Workspace directory bound to this route. */\n workspacePath?: string\n /** Plugin uptime in milliseconds. */\n uptimeMs?: number\n /** Live agent entries this channel owns/borrows. */\n liveAgents?: number\n /** Persisted route records. */\n routeCount?: number\n}\n\n/** Render the complete `/status` snapshot without exposing tool arguments or results. */\nexport function buildSessionStatusCard(input: SessionStatusCardInput): object {\n const { stats } = input\n const model = stats.lastHeader === undefined\n ? '尚未产生模型请求'\n : `${stats.lastHeader.provider}/${stats.lastHeader.model}`\n const effort = stats.lastHeader?.effort ?? '暂无数据'\n const tools = stats.recentTools.length === 0\n ? '暂无工具事件'\n : [...stats.recentTools].reverse().map(toolLine).join('\\n')\n const pendingNames = [...stats.pendingTools.values()].slice(-5).map(tool => `\\`${escapeInlineCode(tool.name)}\\``)\n const hiddenPending = stats.pendingTools.size - pendingNames.length\n const pending = pendingNames.length === 0\n ? '无'\n : `${pendingNames.join('、')}${hiddenPending > 0 ? `,另有 ${hiddenPending} 个` : ''}`\n const tokenSummary = stats.usageSamples === 0\n ? '暂无数据'\n : `输入 ${fmtCount(stats.totals.inputTokens)} · 输出 ${fmtCount(stats.totals.outputTokens)} · 推理 ${fmtCount(stats.totals.reasoningTokens)}`\n const cacheSummary = stats.usageSamples === 0\n ? '暂无数据'\n : `读取 ${fmtCount(stats.totals.cacheReadTokens)} · 写入 ${fmtCount(stats.totals.cacheWriteTokens)} · 最近命中率 ${fmtPercent(cacheHitPct(stats.lastUsage))}`\n\n return standaloneCard('**📊 Session 状态**', [\n { tag: 'markdown', content: `**模型**:${model}\\n**推理强度**:${effort}` },\n {\n tag: 'markdown',\n content: `**Token 累计**:${tokenSummary}\\n`\n + `**缓存**:${cacheSummary}\\n`\n + `**上下文**:${contextLine(input.context)}`,\n },\n {\n tag: 'markdown',\n content: `**LLM 时间**:${fmtDuration(stats.llmSteps === 0 ? undefined : stats.llmMs)}\\n`\n + `**工具时间**:${fmtDuration(stats.toolTimedCalls === 0 ? undefined : stats.toolMs)}\\n`\n + `**平均首 token**:${fmtDuration(averageTtftMs(stats))}\\n`\n + `**平均生成速度**:${fmtRate(averageDecodeTokensPerSecond(stats))}`,\n },\n {\n tag: 'markdown',\n content: `**工具调用**:累计 ${fmtCount(stats.toolCalls)} · 未完成 ${fmtCount(stats.pendingTools.size)}(${pending})\\n`\n + `**最近工具事件(最多 5 条)**\\n${tools}`,\n },\n ...statusExtrasElements(input.extras),\n { tag: 'markdown', content: input.sessionId, text_size: 'notation' },\n ])\n}\n\n/** Render the optional operational lines, omitting unavailable ones. */\nfunction statusExtrasElements(extras: SessionStatusExtras | undefined): object[] {\n if (extras === undefined) return []\n const lines: string[] = []\n if (extras.permissionPreset !== undefined) lines.push(`**权限预设**:\\`${escapeInlineCode(extras.permissionPreset)}\\``)\n if (extras.workspacePath !== undefined) lines.push(`**工作区**:\\`${escapeInlineCode(extras.workspacePath)}\\``)\n if (extras.uptimeMs !== undefined) lines.push(`**插件运行时长**:${fmtUptime(extras.uptimeMs)}`)\n if (extras.liveAgents !== undefined || extras.routeCount !== undefined) {\n const parts: string[] = []\n if (extras.liveAgents !== undefined) parts.push(`live Agent ${fmtCount(extras.liveAgents)}`)\n if (extras.routeCount !== undefined) parts.push(`持久化路由 ${fmtCount(extras.routeCount)}`)\n lines.push(`**通道**:${parts.join(' · ')}`)\n }\n return lines.length === 0 ? [] : [{ tag: 'markdown', content: lines.join('\\n') }]\n}\n\n/** `2 小时 5 分` / `45 秒` — coarse human uptime. */\nfunction fmtUptime(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000))\n if (totalSeconds < 60) return `${totalSeconds} 秒`\n const minutes = Math.floor(totalSeconds / 60) % 60\n const hours = Math.floor(totalSeconds / 3600)\n return hours === 0 ? `${minutes} 分` : `${hours} 小时 ${minutes} 分`\n}\n\nexport type WorkDirResolution =\n | { kind: 'success'; path: string }\n | { kind: 'missing'; text: string }\n | { kind: 'mismatch'; text: string }\n\n/** Validate the Session header cwd against the plugin's durable route. */\nexport function resolveWorkDir(headerCwd: string | undefined, routeWorkspacePath: string): WorkDirResolution {\n if (headerCwd === undefined || headerCwd === '' || !path.isAbsolute(headerCwd)) {\n return {\n kind: 'missing',\n text: '当前 Session header 未记录可用的绝对工作目录;请检查 Session 创建或恢复配置。',\n }\n }\n if (!path.isAbsolute(routeWorkspacePath)) {\n return {\n kind: 'missing',\n text: '当前飞书路由未记录可用的绝对工作目录;请检查路由持久化状态。',\n }\n }\n if (headerCwd !== routeWorkspacePath) {\n return {\n kind: 'mismatch',\n text: 'Session header 与飞书路由记录的工作目录不一致;为避免返回错误目录,已拒绝显示。',\n }\n }\n return { kind: 'success', path: headerCwd }\n}\n\n/** Render one verified absolute workspace path. */\nexport function buildWorkDirCard(sessionId: string, workspacePath: string): object {\n return standaloneCard('**📁 当前工作目录**', [\n { tag: 'markdown', content: `\\`${escapeInlineCode(workspacePath)}\\`` },\n { tag: 'markdown', content: sessionId, text_size: 'notation' },\n ])\n}\n\nexport interface RenewCardInput {\n previousSessionId: string\n currentSessionId: string\n workspacePath: string\n}\n\n/** Render the committed route switch without implying that the old log was deleted. */\nexport function buildRenewCard(input: RenewCardInput): object {\n return standaloneCard('**🔄 Session 已续建**', [\n {\n tag: 'markdown',\n content: `**旧 Session**:${input.previousSessionId}\\n`\n + `**新 Session**:${input.currentSessionId}\\n`\n + `**复用工作区**:\\`${escapeInlineCode(input.workspacePath)}\\``,\n },\n { tag: 'markdown', content: '旧 Session 及其日志仍由 Harness 保留。', text_size: 'notation' },\n ])\n}\n\nexport const SESSION_HISTORY_DISPLAY_LIMIT = 20\n\nexport interface SessionHistoryItem extends SessionHistoryEntry {\n current: boolean\n}\n\nexport interface SessionHistoryView {\n total: number\n items: SessionHistoryItem[]\n hidden: number\n}\n\n/** Read one route's normalized history without consulting the Session repository. */\nexport function querySessionHistory(record: RouteRecord): SessionHistoryView {\n const history = normalizeSessionHistory(record)\n const items = history\n .slice(-SESSION_HISTORY_DISPLAY_LIMIT)\n .reverse()\n .map(entry => ({ ...entry, current: entry.sessionId === record.sessionId }))\n return {\n total: history.length,\n items,\n hidden: Math.max(0, history.length - items.length),\n }\n}\n\n/** Optional per-session facts folded from the host session query. */\nexport interface SessionHistoryAnnotation {\n /** Latest log-backed session title, when one exists. */\n title?: string\n /** Whether the session is currently live (held by any owner). */\n live?: boolean\n}\n\nfunction historyLine(\n item: SessionHistoryItem,\n index: number,\n annotation: SessionHistoryAnnotation | undefined,\n): string {\n const markers = [\n ...(item.current ? ['**当前**'] : []),\n ...(annotation?.live === true && !item.current ? ['live'] : []),\n ]\n const marker = markers.length === 0 ? '' : ` · ${markers.join(' · ')}`\n const title = annotation?.title === undefined || annotation.title === ''\n ? ''\n : `\\n 标题:${escapeInlineCode(annotation.title)}`\n return `${index + 1}. \\`${escapeInlineCode(item.sessionId)}\\`${marker}${title}\\n`\n + ` 创建时间:${new Date(item.createdAt).toISOString()}`\n}\n\n/** Render the newest route-local Session history with host-query annotations. */\nexport function buildSessionHistoryCard(\n record: RouteRecord,\n annotations: Readonly<Record<string, SessionHistoryAnnotation>> = {},\n): object {\n const view = querySessionHistory(record)\n const elements: object[] = [\n { tag: 'markdown', content: `**总数**:${fmtCount(view.total)}\\n**当前 Session**:\\`${escapeInlineCode(record.sessionId)}\\`` },\n { tag: 'markdown', content: view.items.map((item, index) => historyLine(item, index, annotations[item.sessionId])).join('\\n') },\n ]\n if (view.hidden > 0) {\n elements.push({ tag: 'markdown', content: `另有 ${fmtCount(view.hidden)} 条未展示。`, text_size: 'notation' })\n }\n return standaloneCard('**🕘 Session 历史**', elements)\n}\n\nfunction comparisonLine(label: string, tokens: number | undefined, window: number | undefined): string {\n if (tokens === undefined) return `${label} 暂无数据`\n if (window === undefined) return `${label} ${fmtCount(tokens)} tokens`\n const ratio = tokens / window\n const filled = Math.min(10, Math.max(0, Math.round(ratio * 10)))\n return `${label} ${'█'.repeat(filled)}${'░'.repeat(10 - filled)} ${(ratio * 100).toFixed(0)}% ${fmtCount(tokens)} / ${fmtCount(window)}`\n}\n\nexport interface CompactComparisonInput {\n sessionId: string\n resultText: string\n before: ContextMeasurement\n after: ContextMeasurement\n}\n\n/** Render successful and no-op compaction outcomes with one shared 10-cell scale. */\nexport function buildCompactComparisonCard(input: CompactComparisonInput): object {\n const window = input.after.contextWindow ?? input.before.contextWindow\n const beforeTokens = input.before.contextTokens\n const afterTokens = input.after.contextTokens\n const released = beforeTokens === undefined || afterTokens === undefined\n ? undefined\n : Math.max(0, beforeTokens - afterTokens)\n return standaloneCard('**🗜️ 上下文压缩完成**', [\n { tag: 'markdown', content: input.resultText },\n {\n tag: 'markdown',\n content: `${comparisonLine('压缩前', beforeTokens, window)}\\n`\n + `${comparisonLine('压缩后', afterTokens, window)}\\n`\n + `释放 ${released === undefined ? '暂无数据' : `${fmtCount(released)} tokens`}`,\n },\n { tag: 'markdown', content: input.sessionId, text_size: 'notation' },\n ])\n}\n\n/** Describe whether a failed compaction left the measured surface unchanged. */\nexport function compactFailureContext(before: ContextMeasurement, after: ContextMeasurement): string {\n if (before.contextTokens === undefined || after.contextTokens === undefined) return '上下文状态:暂无可用测量数据。'\n if (before.contextTokens === after.contextTokens) {\n return `上下文保持不变:${fmtCount(before.contextTokens)} tokens。`\n }\n return `上下文状态可能已变化:执行前 ${fmtCount(before.contextTokens)} tokens,当前 ${fmtCount(after.contextTokens)} tokens;请检查 Session 后再重试。`\n}\n","/**\n * Logical Session export format and in-memory gzip packaging.\n *\n * This is deliberately a projection of the live logical Session surface. It\n * does not walk descendant Sessions, fetch attachments, or copy persistence\n * backend artifacts.\n *\n * @module dsh-channels/session-export\n */\n\nimport { readFileSync } from 'node:fs'\nimport { promisify } from 'node:util'\nimport { gzip } from 'node:zlib'\nimport type { Session } from './dsh-types.ts'\n\ninterface PackageMetadata {\n version?: unknown\n}\n\nfunction packageVersion(): string {\n const metadata = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n ) as PackageMetadata\n if (typeof metadata.version !== 'string' || metadata.version === '') {\n throw new Error('dsh-channels: package version is unavailable')\n }\n return metadata.version\n}\n\nexport const LOGICAL_EXPORT_FORMAT = 'dsh-logical-session-jsonl'\nexport const LOGICAL_EXPORT_FORMAT_VERSION = 1\nexport const DSH_CHANNELS_VERSION = packageVersion()\n\nexport interface LogicalExportManifest {\n format: typeof LOGICAL_EXPORT_FORMAT\n formatVersion: typeof LOGICAL_EXPORT_FORMAT_VERSION\n logical: true\n exportedAt: string\n plugin: {\n name: 'dsh-channels'\n version: string\n }\n includes: {\n descendants: false\n attachments: false\n rawArtifacts: false\n }\n session: {\n id: string\n header: Session['header']\n }\n}\n\nexport interface LogicalSessionExportFile {\n fileName: string\n source: Buffer\n}\n\nexport type SessionExportBuilder = (session: Session) => Promise<LogicalSessionExportFile>\n\nexport interface LogicalSessionExportOptions {\n /** Deterministic clock seam for format tests. */\n exportedAt?: Date\n /** Explicit package-version seam for format tests. */\n pluginVersion?: string\n /** Compression seam used to verify gzip failures without filesystem I/O. */\n compress?: (source: Buffer) => Promise<Buffer>\n}\n\nconst gzipBuffer = promisify(gzip)\n\n/** Build one immutable UTF-8 JSONL snapshot before any asynchronous work. */\nexport function serializeLogicalSession(\n session: Session,\n options: Pick<LogicalSessionExportOptions, 'exportedAt' | 'pluginVersion'> = {},\n): Buffer {\n const manifest: LogicalExportManifest = {\n format: LOGICAL_EXPORT_FORMAT,\n formatVersion: LOGICAL_EXPORT_FORMAT_VERSION,\n logical: true,\n exportedAt: (options.exportedAt ?? new Date()).toISOString(),\n plugin: {\n name: 'dsh-channels',\n version: options.pluginVersion ?? DSH_CHANNELS_VERSION,\n },\n includes: {\n descendants: false,\n attachments: false,\n rawArtifacts: false,\n },\n session: {\n id: session.id,\n header: session.header,\n },\n }\n const lines = [JSON.stringify(manifest)]\n for (const event of session.snapshotEvents()) lines.push(JSON.stringify(event))\n return Buffer.from(`${lines.join('\\n')}\\n`, 'utf8')\n}\n\n/** Serialize the logical snapshot, then gzip it entirely in memory. */\nexport async function createLogicalSessionExport(\n session: Session,\n options: LogicalSessionExportOptions = {},\n): Promise<LogicalSessionExportFile> {\n const snapshot = serializeLogicalSession(session, options)\n const source = await (options.compress ?? gzipBuffer)(snapshot)\n return {\n fileName: `dsh-session-${session.id}.logical.jsonl.gz`,\n source,\n }\n}\n","/**\n * Per-turn streaming card state: folds live session events into card snapshots\n * and patches the Feishu card on a fixed refresh interval (default 500 ms).\n *\n * Lifecycle per (session, user message):\n * 1. `attach()` — the reply card was just sent; bind it to the session as\n * segment 1.\n * 2. `session/event` consumption — text deltas fill the body, reasoning\n * deltas fill the fold panel, usage/header events advance the stats fold,\n * and tool-call / workflow-record events feed the two transient activity\n * lines: the delegation status line (子代理 / 工作流 progress, settled\n * workflow outcome, heartbeat) and the tool line (the parent's in-flight\n * tool call). The lines are INDEPENDENT — a later tool call never\n * supersedes the settled workflow status. Subagent children live in their\n * own sessions: their `subagent/descriptor` / `tool/call` / `turn/end`\n * events are folded into the parent turn's status line through\n * `session.header.parentSession` (status only — the child's text output\n * never reaches the card).\n * 3. When the body outgrows one card's budget, the overflow is split at a\n * clean breakpoint and sent as a continuation segment card (segment 2, 3,\n * …) into the same topic; the stream then keeps flowing into the newest\n * card. Reasoning is never segmented — it stays on card 1 and is clipped\n * to the leftover budget (body has priority).\n * 4. `turnEnded(turn)` — finalize every segment card (`回复结束` / `回复失败`)\n * and request the Typing → DONE/No reaction swap on the triggering message.\n *\n * The first visible text delta flips the panel title 推理中 → 回复中; the\n * final patch flips it to 回复结束 or 回复失败. Patching is throttled to `refreshMs` and\n * only runs when the content actually changed. Segment sends are serialized\n * per session so cards arrive in order.\n *\n * Keep-alive: turns that produce no text deltas for a while (tool calls,\n * subagent/workflow runs, provider-side work) are kept visibly alive by the\n * activity lines, whose elapsed counters are re-rendered on a slow heartbeat\n * (`heartbeatMs`, default 5 s) — the card never sits silently on a live turn.\n *\n * @module dsh-channels/streamer\n */\n\nimport type { AssistantStreamFrame, Session, SessionEvent, TurnEndReason } from './dsh-types.ts'\nimport { buildCard, segmentCardContent } from './cards.ts'\nimport type { CardState, PanelPhase } from './cards.ts'\nimport type { FeishuChannel } from './feishu-channel.ts'\nimport { cacheHitPct, foldAll, foldEvent } from './stats.ts'\nimport type { HeaderSample, StatsFold, UsageSample } from './stats.ts'\n\n/**\n * Compact build marker printed by the service at startup so a deployment's\n * console can confirm which build of the activity status line is loaded.\n * Bump on every behavior change to this feature.\n */\nexport const ACTIVITY_BUILD_MARKER = 'activity-status-v6'\n\n/** How a segment card reached Feishu. */\nexport interface SegmentDelivery {\n messageId: string\n /** `card` segments stream via `updateCard`; `text` messages are final\n * (delivered as a plain-text fallback and never patched afterwards). */\n mode: 'card' | 'text'\n}\n\n/** Card figures read from host projections (all optional: unit may be unmounted). */\nexport interface ProjectedStats {\n inputTokens?: number\n outputTokens?: number\n cacheHitPct?: number\n contextWindow?: number\n}\n\nexport interface StreamerHooks {\n /** Fetch current context pressure for one session (may be expensive → cached by caller). */\n measureContext(session: Session): { contextTokens?: number; contextWindow?: number }\n /**\n * Host-projection-backed figures for one session. When present these win\n * over the local incremental fold (the host units fold the durable embedded\n * assistant streams the plugin cannot see as live events).\n */\n projectStats?(session: Session): ProjectedStats | undefined\n /** Resolve the session id label shown on the card. */\n sessionLabel(sessionId: string): string\n /** Log a non-fatal streaming error. */\n onError(sessionId: string, error: unknown): void\n /**\n * Invoked once when a turn's card reaches its final state. Carries the main\n * card's message id so the caller can retire the turn's in-flight repair\n * snapshot (the id is the snapshot's storage key).\n */\n onTurnFinalized(sessionId: string, turn: number, mainCardMessageId: string): void\n /**\n * Invoked when a continuation segment's message id resolves (delivery\n * succeeded). Lets the caller fold the resolved id into the turn's repair\n * snapshot. `segmentIndex` is the card index within the turn (`0` is the\n * main card; never reported).\n */\n onSegmentDelivered?(\n mainCardMessageId: string,\n segmentIndex: number,\n messageId: string,\n mode: SegmentDelivery['mode'],\n ): void\n /**\n * Send one continuation segment card for a turn (calls arrive in segment\n * order per session). Implementations may fall back to a plain-text message\n * (reporting `mode: 'text'`) when the card is rejected — e.g. Feishu's\n * per-card table limit (error 11310) — so the content always reaches the\n * user. `fallbackText` carries the raw segment body for that fallback.\n */\n sendSegmentCard(\n sessionId: string,\n segment: number,\n card: object,\n fallbackText: string,\n ): Promise<SegmentDelivery>\n}\n\n/** One segment card of a turn. Card 1 is the attached reply card; further\n * cards are created lazily when the body overflows the previous card. */\ninterface SegmentCard {\n /** Feishu message id; undefined until the segment send resolves. */\n messageId: string | undefined\n /** Body accumulated for this segment (frozen once a later segment exists). */\n body: string\n /** True when the card has unsent content for its message id. */\n dirty: boolean\n /** True once this card stopped accepting body growth. */\n frozen: boolean\n /** Delivery mode once known; `text` segments are never patched. */\n mode: SegmentDelivery['mode'] | undefined\n /** True when delivery failed permanently (logged; never retried again). */\n failed: boolean\n /** True while a segment send is enqueued/in flight (prevents duplicate sends). */\n sending: boolean\n /** Consecutive update failures (drives the retry backoff). */\n failures: number\n /** Earliest timestamp for the next update attempt. */\n nextRetryAt: number\n}\n\ninterface TurnState {\n /** Turn number once known; undefined until the first `step/start`. */\n turn: number | undefined\n /** Message whose reaction carries Typing → DONE/No. */\n triggerMessageId: string\n /** Reaction id of the Typing reaction (undefined when the add failed). */\n reactionId: string | undefined\n phase: PanelPhase\n reasoning: string\n /** Request header produced during THIS turn; never inherited from history. */\n header: HeaderSample | undefined\n /** Provider usage produced during THIS turn; never inherited from history. */\n lastUsage: UsageSample | undefined\n /** Structured terminal failure for this turn, when `turn/end.kind=error`. */\n failure: { message: string; code?: string } | undefined\n /** Segment cards in order; `cards[0]` is the attached reply card. */\n cards: SegmentCard[]\n dirty: boolean\n lastPatchAt: number\n timer: ReturnType<typeof setTimeout> | undefined\n finalized: boolean\n /** Set by `detach()`; pending sends then stop scheduling further patches. */\n disposed: boolean\n /** Unix ms when the turn was attached (heartbeat elapsed base). */\n turnStartedAt: number\n /** The in-flight tool call, if any (cleared by its `tool/result`). */\n toolActivity: { name: string; label: string; since: number } | undefined\n /** Live workflow-run folds projected by the workflow tool into this session. */\n workflowRuns: Map<string, WorkflowRun>\n /** The last settled workflow run (kept visible until the next activity). */\n workflowEndStatus: { name: string; stopReason: string } | undefined\n /**\n * Live subagent child sessions folded into this turn (child session id →\n * status). Children are discovered through `session.header.parentSession`\n * and removed when their own turn ends.\n */\n children: Map<string, ChildActivity>\n /**\n * Labels of subagent children that FINISHED during this turn (capped). They\n * leave the live `children` fold at their turn/end but are summarized on\n * the settled card: `🤖 子代理完成:a、b`.\n */\n settled: string[]\n /** Currently rendered delegation status line (recomputed on every snapshot). */\n status: string\n /** Kind of the status line currently logged (change-detection only). */\n statusKind: string\n /** Currently rendered tool-call line (recomputed on every snapshot). */\n tool: string\n /** Kind of the tool line currently logged (change-detection only). */\n toolKind: string\n /** Slow keep-alive timer re-rendering the activity elapsed counter. */\n heartbeatTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Rollback marks for live stream attempts (`attemptId` → lengths at the\n * attempt's `start` frame). An `abandoned` attempt's deltas never reach the\n * durable model history, so the card must drop them again.\n */\n attempts: Map<string, AttemptMark>\n}\n\n/** Card/body lengths captured at one stream attempt's `start` frame. */\ninterface AttemptMark {\n cardCount: number\n bodyLengths: number[]\n reasoningLength: number\n}\n\n/** One live `tool-workflow/run-*` fold: run identity and member progress. */\ninterface WorkflowRun {\n name: string\n startedAt: number\n /** Members started so far. */\n started: number\n /** Members settled so far. */\n ended: number\n /** Still-running members (started minus ended), in start order. */\n active: Array<{ seq: number; label: string }>\n /** Latest phase declared by a member (`phase()` in the workflow script). */\n phase?: string\n /**\n * Child session ids of this run's members — their own events are already\n * aggregated by the workflow line and must not be folded again as generic\n * children.\n */\n memberIds: Set<string>\n}\n\n/** One running subagent child's displayable status. */\ninterface ChildActivity {\n /** Delegation description (subagent/descriptor label), default 子代理. */\n label: string\n /** The child's latest tool call, if any. */\n tool?: string\n /** When the child was first observed (elapsed base). */\n since: number\n}\n\n/** Hard cap on the activity line so it can never threaten the card budget. */\nconst ACTIVITY_MAX_CHARS = 64\n\n/** Trim an activity line to `ACTIVITY_MAX_CHARS` (ellipsis counts as one char). */\nfunction clipActivity(line: string): string {\n return line.length <= ACTIVITY_MAX_CHARS ? line : `${line.slice(0, ACTIVITY_MAX_CHARS - 1)}…`\n}\n\n/** `已运行 45s` / `已运行 2分5s` — the activity line's elapsed counter. */\nfunction fmtElapsed(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000))\n if (total < 60) return `已运行 ${total}s`\n return `已运行 ${Math.floor(total / 60)}分${total % 60}s`\n}\n\n/**\n * Human label for one tool call: the subagent tool contributes its\n * `description`, the workflow tool its `meta.name`; anything else uses the\n * bare tool name. Arguments are model-produced JSON — parsing failures fall\n * back to the tool name instead of failing the fold.\n */\nfunction toolActivityLabel(name: string, rawArguments: string): string {\n if (name !== 'subagent' && name !== 'workflow') return name\n try {\n const args = JSON.parse(rawArguments) as Record<string, unknown>\n if (name === 'subagent' && typeof args.description === 'string' && args.description !== '') {\n return args.description\n }\n if (name === 'workflow') {\n const meta = args.meta as Record<string, unknown> | undefined\n if (meta !== undefined && typeof meta.name === 'string' && meta.name !== '') return meta.name\n }\n } catch {\n // Unparseable arguments: fall back to the bare tool name.\n }\n return name\n}\n\n/** `completed`/`failed`/`cancelled` → the Chinese line fragment. */\nconst WORKFLOW_OUTCOMES: Record<string, string> = {\n completed: '完成',\n failed: '失败',\n cancelled: '已取消',\n error: '失败',\n}\n\nexport class TurnStreamer {\n private readonly channel: FeishuChannel\n private readonly refreshMs: number\n private readonly hooks: StreamerHooks\n /** Slow keep-alive interval for the activity line's elapsed counter. */\n private readonly heartbeatMs: number\n /** sessionId → queue of turn states (FIFO consumption of the driver). */\n private readonly bySession = new Map<string, TurnState[]>()\n /** sessionId → session-log stats fold (session cumulative). */\n private readonly folds = new Map<string, StatsFold>()\n /** sessionId → the Session object for context measurement. */\n private readonly sessions = new Map<string, Session>()\n /** sessionId → context pressure cache (refreshed every N patches). */\n private readonly contextCache = new Map<string, { contextTokens?: number; contextWindow?: number }>()\n /** sessionId → serialized segment-send chain (keeps card order). */\n private readonly segmentChains = new Map<string, Promise<void>>()\n\n constructor(channel: FeishuChannel, refreshMs: number, hooks: StreamerHooks, heartbeatMs: number = 5_000) {\n this.channel = channel\n this.refreshMs = refreshMs\n this.hooks = hooks\n this.heartbeatMs = heartbeatMs\n }\n\n /** Bind a freshly sent reply card to a session; seeds the stats fold from the log. */\n attach(session: Session, cardMessageId: string, triggerMessageId: string, reactionId: string | undefined): void {\n this.sessions.set(session.id, session)\n let fold = this.folds.get(session.id)\n if (fold === undefined) {\n fold = foldAll(session.snapshotEvents())\n this.folds.set(session.id, fold)\n }\n const queue = this.bySession.get(session.id) ?? []\n queue.push({\n turn: undefined,\n triggerMessageId,\n reactionId,\n phase: 'thinking',\n reasoning: '',\n header: undefined,\n lastUsage: undefined,\n failure: undefined,\n cards: [{\n messageId: cardMessageId,\n body: '',\n dirty: true,\n frozen: false,\n mode: 'card',\n failed: false,\n sending: false,\n failures: 0,\n nextRetryAt: 0,\n }],\n dirty: true,\n lastPatchAt: 0,\n timer: undefined,\n finalized: false,\n disposed: false,\n turnStartedAt: Date.now(),\n toolActivity: undefined,\n workflowRuns: new Map(),\n workflowEndStatus: undefined,\n children: new Map(),\n settled: [],\n status: '',\n statusKind: '',\n tool: '',\n toolKind: '',\n heartbeatTimer: undefined,\n attempts: new Map(),\n })\n this.bySession.set(session.id, queue)\n const state = queue[queue.length - 1]!\n this.scheduleHeartbeat(session.id, state)\n }\n\n /** Drop all state for a session (agent disposed / route removed). */\n detach(sessionId: string): void {\n const queue = this.bySession.get(sessionId) ?? []\n for (const state of queue) {\n state.disposed = true\n if (state.timer !== undefined) clearTimeout(state.timer)\n if (state.heartbeatTimer !== undefined) clearTimeout(state.heartbeatTimer)\n }\n this.bySession.delete(sessionId)\n this.folds.delete(sessionId)\n this.sessions.delete(sessionId)\n this.contextCache.delete(sessionId)\n this.segmentChains.delete(sessionId)\n }\n\n hasSession(sessionId: string): boolean {\n return this.bySession.has(sessionId)\n }\n\n /** The front (oldest unfinished) turn state of a session. */\n private front(sessionId: string): TurnState | undefined {\n const queue = this.bySession.get(sessionId)\n if (queue === undefined || queue.length === 0) return undefined\n for (const state of queue) {\n if (!state.finalized) return state\n }\n return undefined\n }\n\n /** Consume one appended session event. */\n onSessionEvent(session: Session, event: SessionEvent): void {\n const fold = this.folds.get(session.id)\n if (fold !== undefined) foldEvent(fold, event)\n this.sessions.set(session.id, session)\n\n // Subagent children live in their OWN sessions (header.parentSession →\n // caller). Their events never touch the parent's log, so fold their\n // status into the parent turn's activity line here.\n const parentId = session.header.parentSession\n if (parentId !== undefined) {\n const parentState = this.front(parentId)\n if (parentState !== undefined) {\n this.foldChildEvent(parentId, parentState, session, event)\n return\n }\n }\n\n const state = this.front(session.id)\n if (state === undefined) return\n\n switch (event.type) {\n case 'step/start': {\n if (state.turn === undefined) {\n state.turn = event.data.turn\n this.markDirty(session.id, state)\n }\n break\n }\n case 'assistant/message': {\n if (state.turn === undefined) state.turn = event.data.turn\n if (event.data.usage !== undefined) {\n state.lastUsage = {\n usage: event.data.usage,\n turn: event.data.turn,\n step: event.data.step,\n }\n this.markDirty(session.id, state)\n }\n break\n }\n case 'request/header': {\n const config = event.data.header.config\n // Parse this exact event instead of copying the session fold's latest\n // sample, so malformed/current events can never expose prior history.\n state.header = {\n provider: typeof config.provider === 'string' ? config.provider : '',\n model: typeof config.model === 'string' ? config.model : '',\n effort: typeof config.reasoningEffort === 'string' ? config.reasoningEffort : undefined,\n }\n this.markDirty(session.id, state)\n break\n }\n case 'tool/call': {\n if (state.turn === undefined) state.turn = event.data.turn\n state.toolActivity = {\n name: event.data.name,\n label: toolActivityLabel(event.data.name, event.data.arguments),\n since: Date.now(),\n }\n // The tool line is independent: it never supersedes the settled\n // workflow end status on the delegation line (both render together).\n this.markDirty(session.id, state)\n break\n }\n case 'tool/result': {\n if (state.turn === undefined) state.turn = event.data.turn\n state.toolActivity = undefined\n this.markDirty(session.id, state)\n break\n }\n case 'tool-workflow/run-start': {\n // Workflow record events carry no turn/step — they belong to the\n // front (oldest unfinished) turn by construction.\n state.workflowRuns.set(event.data.runId, {\n name: event.data.name,\n startedAt: Date.now(),\n started: 0,\n ended: 0,\n active: [],\n memberIds: new Set(),\n })\n state.workflowEndStatus = undefined\n this.markDirty(session.id, state)\n break\n }\n case 'tool-workflow/agent-start': {\n const run = state.workflowRuns.get(event.data.runId)\n if (run !== undefined) {\n run.started++\n run.active.push({ seq: event.data.seq, label: event.data.label })\n run.memberIds.add(event.data.childId)\n if (event.data.phase !== undefined) run.phase = event.data.phase\n this.markDirty(session.id, state)\n }\n break\n }\n case 'tool-workflow/agent-end': {\n const run = state.workflowRuns.get(event.data.runId)\n if (run !== undefined) {\n run.ended++\n const index = run.active.findIndex(member => member.seq === event.data.seq)\n if (index >= 0) run.active.splice(index, 1)\n this.markDirty(session.id, state)\n }\n break\n }\n case 'tool-workflow/run-end': {\n const run = state.workflowRuns.get(event.data.runId)\n if (run !== undefined) {\n state.workflowRuns.delete(event.data.runId)\n state.workflowEndStatus = {\n name: run.name,\n stopReason: WORKFLOW_OUTCOMES[event.data.stopReason] ?? event.data.stopReason,\n }\n this.markDirty(session.id, state)\n }\n break\n }\n case 'turn/end': {\n // The ended turn belongs to the front state: assign it if a turn was\n // rejected/emptied before any step started.\n if (state.turn === undefined) state.turn = event.data.turn\n this.finalize(session.id, state, turnFailure(event.data.reason))\n break\n }\n default:\n break\n }\n }\n\n /**\n * Consume one live assistant-stream frame (`agent/assistant-stream`).\n *\n * DSH stopped emitting durable `assistant/chunk` events (commit\n * `f99b06eaed`), so this transient agent-scoped publication is the only\n * live delta source. `start` records rollback marks; `end` keeps the deltas\n * only when the attempt committed to the durable log — an `abandoned`\n * attempt's text is not model-visible history and must leave the card again.\n */\n onAssistantFrame(session: Session, frame: AssistantStreamFrame): void {\n this.sessions.set(session.id, session)\n const state = this.front(session.id)\n if (state === undefined || state.disposed) return\n switch (frame.type) {\n case 'start': {\n state.attempts.set(frame.attemptId, this.attemptMark(state))\n if (state.turn === undefined) state.turn = frame.turn\n break\n }\n case 'chunk': {\n const chunk = frame.chunk\n if (chunk.type === 'text-delta') {\n const last = state.cards[state.cards.length - 1]\n if (last !== undefined && !last.frozen && chunk.text !== '') {\n last.body += chunk.text\n if (state.phase === 'thinking') state.phase = 'replying'\n this.markDirty(session.id, state)\n }\n } else if (chunk.type === 'reasoning-delta' && chunk.text !== '') {\n state.reasoning += chunk.text\n this.markDirty(session.id, state)\n }\n break\n }\n case 'end': {\n const mark = state.attempts.get(frame.attemptId)\n state.attempts.delete(frame.attemptId)\n if (frame.outcome.kind === 'abandoned' && mark !== undefined) {\n this.rollbackAttempt(state, mark)\n this.markDirty(session.id, state)\n }\n break\n }\n }\n }\n\n /** Capture the card/body lengths an attempt's deltas start from. */\n private attemptMark(state: TurnState): AttemptMark {\n return {\n cardCount: state.cards.length,\n bodyLengths: state.cards.map(card => card.body.length),\n reasoningLength: state.reasoning.length,\n }\n }\n\n /**\n * Drop one abandoned attempt's deltas. The attempt's text is a contiguous\n * suffix of the logical body: segments created during the attempt are\n * removed, the segment that was last at `start` is truncated, and the\n * reasoning tail is cut. Already-delivered plain-text segments cannot be\n * retracted and stop the removal.\n */\n private rollbackAttempt(state: TurnState, mark: AttemptMark): void {\n const lastIndex = mark.cardCount - 1\n for (let index = state.cards.length - 1; index > lastIndex; index -= 1) {\n const card = state.cards[index]\n if (card === undefined) continue\n if (card.mode === 'text' && card.messageId !== undefined) break\n state.cards.splice(index, 1)\n }\n const tail = state.cards[lastIndex]\n const length = mark.bodyLengths[lastIndex]\n if (tail !== undefined && length !== undefined\n && !(tail.mode === 'text' && tail.messageId !== undefined)) {\n tail.body = tail.body.slice(0, length)\n tail.dirty = true\n }\n state.reasoning = state.reasoning.slice(0, mark.reasoningLength)\n }\n\n private markDirty(sessionId: string, state: TurnState): void {\n if (state.disposed) return\n state.dirty = true\n if (state.timer !== undefined) return\n const wait = state.lastPatchAt === 0\n ? this.refreshMs\n : Math.max(0, this.refreshMs - (Date.now() - state.lastPatchAt))\n state.timer = setTimeout(() => {\n state.timer = undefined\n this.flush(sessionId, state)\n }, wait)\n }\n\n private statsFor(sessionId: string, state: TurnState): CardState['stats'] {\n const fold = this.folds.get(sessionId)\n const lastUsage = state.lastUsage\n const header = state.header\n const cached = this.contextCache.get(sessionId)\n const session = this.sessions.get(sessionId)\n let context = cached\n if (session !== undefined) {\n try {\n context = this.hooks.measureContext(session)\n this.contextCache.set(sessionId, context)\n } catch {\n context = cached\n }\n }\n // Host projections are authoritative when mounted: they fold the durable\n // embedded assistant streams the plugin can no longer see as live events.\n const projected = session === undefined ? undefined : this.hooks.projectStats?.(session)\n return {\n sessionId: this.hooks.sessionLabel(sessionId),\n inputTokens: projected?.inputTokens ?? fold?.totals.inputTokens ?? 0,\n outputTokens: projected?.outputTokens ?? fold?.totals.outputTokens ?? 0,\n cacheHitPct: projected?.cacheHitPct ?? cacheHitPct(lastUsage),\n contextTokens: context?.contextTokens,\n // The context-token count is session-wide, but the model window belongs\n // to the current request. Suppress it when this turn failed before a\n // request header was emitted, just like model/effort/cache.\n contextWindow: header === undefined\n ? undefined\n : context?.contextWindow ?? projected?.contextWindow,\n model: header?.model,\n effort: header?.effort,\n }\n }\n\n /**\n * Fold one event from a subagent child session into its parent turn's\n * activity state. Only identity/progress signals are kept — the child's\n * text output stays out of the user-facing card (the parent model receives\n * it as the tool result and composes the final reply itself).\n *\n * Workflow members are skipped here: their childId is recorded on the run\n * fold and the workflow line already aggregates them.\n */\n private foldChildEvent(parentId: string, state: TurnState, child: Session, event: SessionEvent): void {\n for (const run of state.workflowRuns.values()) {\n if (run.memberIds.has(child.id)) return\n }\n switch (event.type) {\n case 'subagent/descriptor': {\n const label = typeof event.data.label === 'string' && event.data.label !== ''\n ? event.data.label\n : '子代理'\n const existing = state.children.get(child.id)\n if (existing === undefined) {\n state.children.set(child.id, { label, tool: undefined, since: Date.now() })\n } else if (existing.label === '子代理') {\n existing.label = label\n }\n this.markDirty(parentId, state)\n break\n }\n case 'tool/call': {\n const existing = state.children.get(child.id)\n if (existing === undefined) {\n state.children.set(child.id, { label: '子代理', tool: event.data.name, since: Date.now() })\n } else {\n existing.tool = event.data.name\n }\n this.markDirty(parentId, state)\n break\n }\n case 'tool/result': {\n const existing = state.children.get(child.id)\n if (existing !== undefined && existing.tool !== undefined) {\n existing.tool = undefined\n this.markDirty(parentId, state)\n }\n break\n }\n case 'turn/end': {\n const entry = state.children.get(child.id)\n if (entry === undefined) break\n state.children.delete(child.id)\n // A finished child leaves the live fold but is summarized on the\n // settled card (capped; placeholder labels are skipped).\n if (entry.label !== '子代理' && state.settled.length < 3) {\n state.settled.push(entry.label)\n }\n this.markDirty(parentId, state)\n break\n }\n default:\n break\n }\n }\n\n /**\n * The delegation status line, by priority:\n * 1. the most recently started live workflow run (member progress + phase);\n * 2. running subagent children (label + current tool + elapsed);\n * 3. the last settled workflow run's outcome — it persists across later\n * tool calls and text streaming (only a new run-start replaces it), so\n * the workflow conclusion stays visible;\n * 4. heartbeat fallback — a thinking turn with no output yet still shows\n * an elapsed counter so the card never sits silently.\n * Once the turn is finalized, the line switches to the SETTLED form\n * (workflow conclusion + finished-subagent summary) — the final card keeps\n * the delegation status; only the tool line disappears.\n */\n private delegationLine(state: TurnState, now: number): string {\n if (state.failure !== undefined) {\n const suffix = state.failure.code === undefined ? '' : ` · ${state.failure.code}`\n return clipActivity(`🔴 本轮运行失败${suffix}`)\n }\n if (state.finalized) return this.settledLine(state)\n\n let latest: WorkflowRun | undefined\n for (const run of state.workflowRuns.values()) {\n if (latest === undefined || run.startedAt > latest.startedAt) latest = run\n }\n if (latest !== undefined) {\n let line = `🧵 工作流 ${latest.name} · ${latest.ended}/${latest.started} 子代理完成`\n if (latest.phase !== undefined) line += ` · 阶段 ${latest.phase}`\n const labels = latest.active.slice(0, 3).map(member => member.label)\n if (labels.length > 0) line += ` · ${labels.join('、')}`\n line += ` · ${fmtElapsed(now - latest.startedAt)}`\n return clipActivity(line)\n }\n\n const children = [...state.children.values()]\n if (children.length > 0) {\n const listed = children.slice(0, 2).map(child => {\n const tool = child.tool === undefined ? '' : ` (${child.tool})`\n return `${child.label}${tool}`\n })\n const head = children.length === 1\n ? `🤖 子代理执行中:${listed[0]}`\n : `🤖 ${children.length} 个子代理执行中:${listed.join('、')}${children.length > 2 ? '、…' : ''}`\n const oldest = Math.min(...children.map(child => child.since))\n return clipActivity(`${head} · ${fmtElapsed(now - oldest)}`)\n }\n\n if (state.workflowEndStatus !== undefined) {\n const { name, stopReason } = state.workflowEndStatus\n return clipActivity(`🧵 工作流 ${name} 结束(${stopReason})`)\n }\n\n const empty = state.cards.every(card => card.body === '')\n // The tool line already shows liveness while a call is in flight, so the\n // heartbeat fallback stays out of the way.\n if (state.phase === 'thinking' && empty && state.toolActivity === undefined) {\n return clipActivity(`⏳ 处理中 · ${fmtElapsed(now - state.turnStartedAt)}`)\n }\n return ''\n }\n\n /**\n * The settled-card form of the delegation status line: the workflow\n * conclusion and/or the finished-subagent summary, without elapsed counters.\n * Still-running (background) children are noted factually. Empty when the\n * turn delegated nothing.\n */\n private settledLine(state: TurnState): string {\n const parts: string[] = []\n if (state.workflowEndStatus !== undefined) {\n const { name, stopReason } = state.workflowEndStatus\n parts.push(`🧵 工作流 ${name} 结束(${stopReason})`)\n }\n if (state.settled.length > 0) {\n parts.push(`🤖 子代理完成:${state.settled.join('、')}`)\n }\n if (parts.length > 0) return clipActivity(parts.join(' · '))\n\n const children = [...state.children.values()]\n if (children.length > 0) {\n const listed = children.slice(0, 2).map(child =>\n `${child.label}${child.tool === undefined ? '' : ` (${child.tool})`}`)\n const head = children.length === 1\n ? `🤖 子代理执行中:${listed[0]}`\n : `🤖 ${children.length} 个子代理执行中:${listed.join('、')}${children.length > 2 ? '、…' : ''}`\n return clipActivity(head)\n }\n return ''\n }\n\n /**\n * The tool-call line: the parent agent's in-flight tool call, independent\n * of the delegation status line — workflow/subagent status never hides it\n * and it never hides them. Cleared by the tool's `tool/result`; always\n * empty on the settled card.\n */\n private toolLine(state: TurnState, now: number): string {\n if (state.finalized) return ''\n const tool = state.toolActivity\n if (tool === undefined) return ''\n const prefix = tool.name === 'subagent'\n ? '🤖 调用子代理:'\n : tool.name === 'workflow'\n ? '🧵 调用工作流:'\n : '🔧 调用工具:'\n return clipActivity(`${prefix}${tool.label} · ${fmtElapsed(now - tool.since)}`)\n }\n\n /**\n * Slow keep-alive: recompute both activity lines and patch only when their\n * rendered text actually changed (the elapsed counter ticking). Reschedules\n * itself while the turn is pending; `finalize`/`detach` cancel it.\n */\n private scheduleHeartbeat(sessionId: string, state: TurnState): void {\n if (state.disposed || state.finalized || state.heartbeatTimer !== undefined) return\n state.heartbeatTimer = setTimeout(() => {\n state.heartbeatTimer = undefined\n if (state.disposed || state.finalized) return\n const now = Date.now()\n if (this.delegationLine(state, now) !== state.status\n || this.toolLine(state, now) !== state.tool) {\n this.markDirty(sessionId, state)\n }\n this.scheduleHeartbeat(sessionId, state)\n }, this.heartbeatMs)\n }\n\n private cardState(sessionId: string, state: TurnState, body: string): CardState {\n // The snapshot point recomputes (and remembers) both activity lines so\n // every patch — including heartbeat-driven ones — carries the current\n // status and tool call.\n const now = Date.now()\n const status = this.delegationLine(state, now)\n if (status !== state.status) {\n state.status = status\n const kind = this.noteKind('status', status, state.statusKind)\n if (kind !== undefined) state.statusKind = kind\n }\n const tool = this.toolLine(state, now)\n if (tool !== state.tool) {\n state.tool = tool\n const kind = this.noteKind('tool', tool, state.toolKind)\n if (kind !== undefined) state.toolKind = kind\n }\n return {\n phase: state.phase,\n reasoning: state.reasoning,\n body,\n status: state.status,\n tool: state.tool,\n stats: this.statsFor(sessionId, state),\n }\n }\n\n /**\n * Diagnostic: log activity-type transitions only (first segment of the\n * line, stable across elapsed/count ticks) so a live test can confirm\n * exactly which status the card is being patched with.\n */\n private noteKind(slot: 'status' | 'tool', line: string, current: string): string | undefined {\n const kind = line === '' ? '' : (line.split('·')[0] ?? '').trim()\n if (kind === current) return undefined\n console.info(`[dsh-channels] activity ${slot} →`, kind === '' ? '(收起)' : kind)\n return kind\n }\n\n /**\n * Freeze the newest card once its body stops fitting its budget (bytes or\n * the per-card table cap) and move the overflow into fresh segment cards\n * (their sends are chained in order).\n */\n private splitOverflow(sessionId: string, state: TurnState): void {\n for (;;) {\n const index = state.cards.length - 1\n const card = state.cards[index]\n if (card === undefined || card.frozen || card.mode === 'text') return\n\n const options = index === 0 ? {} : { panel: false, segment: index + 1 }\n const split = segmentCardContent(this.cardState(sessionId, state, card.body), options)\n if (split.head === '') {\n // Degenerate budget (cannot happen with the real fixed overhead):\n // keep streaming into this card; buildCard's absolute clip still\n // guards the card size.\n return\n }\n if (split.tail === '') {\n card.body = split.head\n card.dirty = true\n // A card with content but no message id yet (e.g. the continuation of\n // a text-fallback segment) needs its own send, not just a patch.\n if (card.messageId === undefined && !card.sending && !card.failed && card.body !== '') {\n this.sendSegment(sessionId, state, index)\n }\n return\n }\n\n card.body = split.head\n card.frozen = true\n card.dirty = true\n const next: SegmentCard = this.freshSegment(split.tail)\n state.cards.push(next)\n this.sendSegment(sessionId, state, state.cards.length - 1)\n }\n }\n\n private freshSegment(body: string): SegmentCard {\n return {\n messageId: undefined,\n body,\n dirty: true,\n frozen: false,\n mode: undefined,\n failed: false,\n sending: false,\n failures: 0,\n nextRetryAt: 0,\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n }\n\n /**\n * Serialize one continuation-card send per session (order on Feishu), with\n * up to two retries (1s / 2s backoff) for transient failures. When delivery\n * resolves as a text fallback the segment is frozen and further growth\n * continues in a new segment — text messages cannot be patched afterwards.\n */\n private sendSegment(sessionId: string, state: TurnState, index: number): void {\n const card = state.cards[index]\n if (card === undefined || card.failed || card.sending || state.disposed) return\n card.sending = true\n\n const previous = this.segmentChains.get(sessionId) ?? Promise.resolve()\n const next = previous.then(async () => {\n let lastError: unknown\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n const snapshot = buildCard(this.cardState(sessionId, state, card.body), {\n panel: false,\n segment: index + 1,\n })\n const delivery = await this.hooks.sendSegmentCard(sessionId, index + 1, snapshot, card.body)\n card.sending = false\n card.messageId = delivery.messageId\n card.mode = delivery.mode\n this.hooks.onSegmentDelivered?.(\n state.cards[0]?.messageId ?? '',\n index,\n delivery.messageId,\n delivery.mode,\n )\n if (delivery.mode === 'card') {\n card.dirty = true\n this.markDirty(sessionId, state)\n } else if (!card.frozen && !state.disposed) {\n // Text fallback: this card is final; keep streaming into a new one.\n card.frozen = true\n if (state.cards[state.cards.length - 1] === card) {\n state.cards.push(this.freshSegment(''))\n }\n this.markDirty(sessionId, state)\n }\n return\n } catch (error) {\n lastError = error\n if (attempt >= 2 || state.disposed) break\n await this.delay(1_000 * 2 ** attempt)\n }\n }\n card.sending = false\n card.failed = true\n this.hooks.onError(sessionId, lastError)\n })\n this.segmentChains.set(sessionId, next.then(() => {}, () => {}))\n }\n\n /** Patch one card when due; failed updates retry with exponential backoff\n * (logged on the first failure and every 10th — no per-flush spam).\n *\n * The patch gate covers both per-card body growth (`card.dirty`) and\n * turn-scoped snapshot changes (`turnDirty`: phase, reasoning, activity\n * line, session stats), so activity-only events still reach the card. */\n private patchCard(sessionId: string, state: TurnState, index: number, force: boolean, turnDirty: boolean): void {\n const card = state.cards[index]\n if (card === undefined || card.messageId === undefined || card.mode === 'text' || card.failed) return\n const now = Date.now()\n if (!force && !card.dirty && !turnDirty) return\n if (!force && card.nextRetryAt > now) return\n card.dirty = false\n const options = index === 0 ? {} : { panel: false, segment: index + 1 }\n const snapshot = buildCard(this.cardState(sessionId, state, card.body), options)\n this.channel.updateCard(card.messageId, snapshot).catch((error: unknown) => {\n card.dirty = true\n card.failures++\n card.nextRetryAt = Date.now() + Math.min(1_000 * 2 ** Math.min(card.failures, 6), 60_000)\n if (card.failures === 1 || card.failures % 10 === 0) {\n this.hooks.onError(sessionId, error)\n }\n })\n }\n\n private flush(sessionId: string, state: TurnState): void {\n state.lastPatchAt = Date.now()\n const turnDirty = state.dirty\n if (state.dirty) {\n state.dirty = false\n this.splitOverflow(sessionId, state)\n }\n for (const [index] of state.cards.entries()) {\n this.patchCard(sessionId, state, index, false, turnDirty)\n }\n }\n\n private finalize(\n sessionId: string,\n state: TurnState,\n failure: { message: string; code?: string } | undefined,\n ): void {\n if (state.finalized) return\n state.finalized = true\n state.failure = failure\n state.phase = failure === undefined ? 'done' : 'error'\n if (failure !== undefined) {\n const code = failure.code === undefined ? '' : `\\n\\n错误码:\\`${failure.code}\\``\n const notice = `❌ **本轮运行失败**\\n\\n${failure.message}${code}`\n const tail = state.cards[state.cards.length - 1]\n if (tail !== undefined) {\n if (tail.mode === 'text' || tail.failed) {\n // An already-delivered plain-text fallback cannot be patched, and a\n // permanently failed card cannot surface new content. Put the\n // terminal diagnostic in its own continuation card instead.\n state.cards.push(this.freshSegment(notice))\n } else {\n tail.body = tail.body.trim() === '' ? notice : `${tail.body}\\n\\n---\\n\\n${notice}`\n tail.dirty = true\n }\n }\n }\n if (state.timer !== undefined) {\n clearTimeout(state.timer)\n state.timer = undefined\n }\n // The settled card keeps the delegation status (workflow conclusion /\n // subagent summary — `delegationLine` renders its settled form once\n // `finalized` is set); only the transient tool line disappears. The\n // heartbeat stops.\n if (state.heartbeatTimer !== undefined) {\n clearTimeout(state.heartbeatTimer)\n state.heartbeatTimer = undefined\n }\n state.tool = ''\n // Freeze the tail and spawn any remaining segment cards.\n this.splitOverflow(sessionId, state)\n\n // Final patches with the settled content: every patchable card flips to\n // 回复结束 / 回复失败 (backoff is bypassed once so the final state always lands;\n // late-arriving sends are patched when they resolve).\n for (const [index] of state.cards.entries()) {\n this.patchCard(sessionId, state, index, true, false)\n }\n\n // Reaction swap: Typing → DONE on success, Typing → No on failure.\n void this.swapReaction(state, failure !== undefined)\n\n // Prune the finalized state lazily (kept until the next attach so the\n // front-scan skips it, then removed).\n void Promise.resolve().then(() => {\n const queue = this.bySession.get(sessionId)\n if (queue === undefined) return\n const index = queue.indexOf(state)\n if (index >= 0) queue.splice(index, 1)\n })\n\n this.hooks.onTurnFinalized(sessionId, state.turn ?? 0, state.cards[0]?.messageId ?? '')\n }\n\n private async swapReaction(state: TurnState, failed: boolean): Promise<void> {\n try {\n if (state.reactionId !== undefined) {\n await this.channel.removeReaction(state.triggerMessageId, state.reactionId)\n }\n await this.channel.addReaction(state.triggerMessageId, failed ? 'No' : 'DONE')\n } catch (error) {\n // Reaction failures are cosmetic: never surface them to the user.\n this.hooks.onError(state.triggerMessageId, error)\n }\n }\n}\n\n/** Extract the user-visible failure from the current Harness turn-end union. */\nfunction turnFailure(reason: TurnEndReason): { message: string; code?: string } | undefined {\n if (typeof reason !== 'object' || reason === null || reason.kind !== 'error') return undefined\n const error = reason.error\n const message = typeof error?.message === 'string' && error.message !== ''\n ? error.message\n : '模型请求失败'\n const code = typeof error?.code === 'string' && error.code !== '' ? error.code : undefined\n return { message, code }\n}\n","/**\n * The Feishu bridge service: inbound routing, workspace/session ownership,\n * topic creation, and streaming-card orchestration.\n *\n * Ownership model (one workspace per user × chat):\n * - p2p: one workspace per (bot, user), all of that user's sessions inside;\n * - group: one workspace per (chat, user), sessions grouped per user.\n *\n * Routing model:\n * - `thread_id` → bound session (no @ needed inside an owned topic);\n * - degraded chat-scoped keys when Feishu refuses topic replies;\n * - new topic + new session for an @-triggered message without a route.\n *\n * @module dsh-channels/service\n */\n\nimport { lstat, readdir, realpath, rm } from 'node:fs/promises'\nimport path from 'node:path'\nimport { AccessController } from './access/controller.ts'\nimport { AccessRequests } from './access/requests.ts'\nimport { AccessStore } from './access/store.ts'\nimport { ApprovalAnswerer } from './approvals.ts'\nimport type { ApprovalRoute } from './approvals.ts'\nimport { QuestionAnswerer } from './questions.ts'\nimport type {\n CommandInvocation,\n CommandResult,\n CredentialInfo,\n CredentialProvider,\n CordisFiber,\n HostContext,\n LlmService,\n Session,\n SessionId,\n SessionProjections,\n TokenMeter,\n Workspace,\n} from './dsh-types.ts'\nimport { standaloneInfoCard } from './cards.ts'\nimport {\n armEffortCommand, armModelCommand, armRenewCommand, registerModelCommand, commandResultCardKind, parseSlashCommand,\n registerEffortCommand, registerRenewCommand, RouteOperationQueue, supportedCommandHelp, supportedCommandList,\n} from './commands.ts'\nimport type { EffortCommandAction, ParsedSlashCommand } from './commands.ts'\nimport { commandSpec } from './commands/registry.ts'\nimport type { ChannelCommandSpec } from './commands/registry.ts'\nimport type { PermissionPresets, SessionQuery } from './dsh-types.ts'\nimport { feishuDomain } from './domain.ts'\nimport {\n applyWorkspaceCleanup, isPathInside, planWorkspaceCleanup, retentionMs,\n} from './cleanup.ts'\nimport type { CleanupDeps, WorkspaceStats } from './cleanup.ts'\nimport type {\n AccessGroupRecord, AccessRequestRecord, AccessUserRecord,\n ExpiredRouteRecord, FeishuDomainGlobal, FeishuDomainHandle, InflightSnapshot, ProcessedRecord, RawDomainHandle,\n RouteRecord,\n} from './domain.ts'\nimport { RouteStore } from './session/routes.ts'\nimport { InflightRepair } from './session/repair.ts'\nimport { SessionManager } from './session/manager.ts'\nimport type { LiveAgent, RenewCommit } from './session/manager.ts'\nimport { CardDelivery } from './feishu/delivery.ts'\nimport { AttachmentIntake } from './feishu/inbound-attachments.ts'\nimport { InboundRouter } from './routing/inbound.ts'\nimport type { RequestPolicyController } from './session/request-policy.ts'\nimport type { ChannelFactory, FeishuChannel, NormalizedMessage } from './feishu-channel.ts'\nimport { buildHealthCard, foldHealthSnapshot, HEALTH_DEPENDENCY_NAMES } from './health.ts'\nimport type { HealthDependencyName, HealthSnapshot } from './health.ts'\nimport {\n buildCompactComparisonCard, buildRenewCard, buildSessionHistoryCard, buildSessionStatusCard, buildWorkDirCard,\n compactFailureContext, resolveWorkDir, SESSION_HISTORY_DISPLAY_LIMIT,\n} from './session-commands.ts'\nimport type { SessionHistoryAnnotation } from './session-commands.ts'\nimport { normalizeSessionHistory } from './domain.ts'\nimport { foldSessionStats, measureSessionContext, readProjectionViews } from './stats.ts'\nimport type { ContextMeasurement, ContextServices } from './stats.ts'\nimport { createLogicalSessionExport } from './session-export.ts'\nimport type { SessionExportBuilder } from './session-export.ts'\nimport { ACTIVITY_BUILD_MARKER, TurnStreamer } from './streamer.ts'\nimport type { ProjectedStats, StreamerHooks } from './streamer.ts'\nimport type { FeishuConfig } from './config.ts'\nimport type {\n AccessMutationResponse, AccessRequestWire, AccessWire,\n} from './settings/contract.ts'\nimport type { OverlayStore } from './settings/overlay.ts'\n\n/** Periodic route-GC sweep cadence (a long-running instance also accumulates\n * dead records; TTL semantics make 6 h granularity more than sufficient). */\nconst GC_SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1000\n\n/** Dedup-record retention: Feishu redelivery windows are minutes-scale, so a\n * day is 100×+ headroom while keeping the table bounded. */\nconst PROCESSED_RETENTION_MS = 24 * 60 * 60 * 1000\n\n/** Workspace-retention sweep cadence when automatic cleanup is enabled. */\nconst RETENTION_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000\n\n\ninterface RenewAdmission {\n route: RouteRecord\n live: LiveAgent\n commit?: RenewCommit\n}\n\ninterface EffortAdmission {\n live: LiveAgent\n controller: RequestPolicyController\n}\n\ninterface ModelAdmission {\n live: LiveAgent\n controller: RequestPolicyController\n}\n\n/** Wrap the host's storage facility open behind a stable local type. */\nasync function openFeishuDomain(ctx: HostContext): Promise<FeishuDomainHandle> {\n const domain = await ctx.storageDomain.open(feishuDomain) as unknown as RawDomainHandle\n const table = domain.table('routes')\n const inflightRaw = domain.table('inflight')\n const processedRaw = domain.table('processed')\n const expiredRoutesRaw = domain.table('expired_routes')\n const accessGroupsRaw = domain.table('access_groups')\n const accessUsersRaw = domain.table('access_users')\n const accessRequestsRaw = domain.table('access_requests')\n return {\n routes: {\n get: key => table.get(key) as RouteRecord | undefined,\n put: (key, value) => table.put(key, value),\n delete: key => table.delete(key),\n entries: () => table.entries() as IterableIterator<[string, RouteRecord]>,\n },\n inflight: {\n get: key => inflightRaw.get(key) as InflightSnapshot | undefined,\n put: (key, value) => inflightRaw.put(key, value),\n update: (key, fn) => inflightRaw.update(key, current => fn(current as InflightSnapshot)),\n delete: key => inflightRaw.delete(key),\n entries: () => inflightRaw.entries() as IterableIterator<[string, InflightSnapshot]>,\n },\n processed: {\n get: key => processedRaw.get(key) as ProcessedRecord | undefined,\n put: (key, value) => processedRaw.put(key, value),\n delete: key => processedRaw.delete(key),\n entries: () => processedRaw.entries() as IterableIterator<[string, ProcessedRecord]>,\n },\n expiredRoutes: {\n get: key => expiredRoutesRaw.get(key) as ExpiredRouteRecord | undefined,\n put: (key, value) => expiredRoutesRaw.put(key, value),\n entries: () => expiredRoutesRaw.entries() as IterableIterator<[string, ExpiredRouteRecord]>,\n },\n accessGroups: {\n get: key => accessGroupsRaw.get(key) as AccessGroupRecord | undefined,\n put: (key, value) => accessGroupsRaw.put(key, value),\n delete: key => accessGroupsRaw.delete(key),\n entries: () => accessGroupsRaw.entries() as IterableIterator<[string, AccessGroupRecord]>,\n },\n accessUsers: {\n get: key => accessUsersRaw.get(key) as AccessUserRecord | undefined,\n put: (key, value) => accessUsersRaw.put(key, value),\n delete: key => accessUsersRaw.delete(key),\n entries: () => accessUsersRaw.entries() as IterableIterator<[string, AccessUserRecord]>,\n },\n accessRequests: {\n get: key => accessRequestsRaw.get(key) as AccessRequestRecord | undefined,\n put: (key, value) => accessRequestsRaw.put(key, value),\n delete: key => accessRequestsRaw.delete(key),\n entries: () => accessRequestsRaw.entries() as IterableIterator<[string, AccessRequestRecord]>,\n },\n getGlobal: () => (domain.global.get() ?? {}) as FeishuDomainGlobal,\n setGlobal: value => domain.global.set(value),\n close: () => domain.close(),\n }\n}\n\n/**\n * Resolve one credential reference through the host credentials seam.\n *\n * Precedence is owned by the provider (env > `$DSH_HOME/.credentials.yaml`\n * > `.env` fallbacks). Without a mounted seam the process environment is the\n * whole credential plane.\n */\nasync function resolveCredentialRef(ctx: HostContext, ref: string): Promise<string> {\n const provider = ctx.get?.('credentials') as CredentialProvider | undefined\n if (provider !== undefined) {\n const hit = await provider.resolve(ref)\n if (hit !== undefined) return hit.value\n } else {\n const ambient = process.env[ref]\n if (ambient !== undefined && ambient !== '') return ambient\n }\n throw new Error(\n `dsh-channels: credential \"${ref}\" is not configured; add it to $DSH_HOME/.credentials.yaml `\n + `(e.g. \"${ref}: <value>\") or export it in the launching environment`,\n )\n}\n\n/** Best-effort human-readable error text for the settings panel. */\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/**\n * Measure one workspace directory for the cleanup preview: total regular-file\n * bytes and file count. Symlinks are never followed; unreadable entries are\n * skipped so a permission problem cannot fail the whole pass.\n */\nasync function measureWorkspace(dir: string): Promise<WorkspaceStats | undefined> {\n let info\n try {\n info = await lstat(dir)\n } catch {\n return undefined\n }\n if (!info.isDirectory()) return undefined\n let bytes = 0\n let files = 0\n const pending = [dir]\n while (pending.length > 0) {\n const current = pending.pop()!\n let entries\n try {\n entries = await readdir(current, { withFileTypes: true })\n } catch {\n continue\n }\n for (const entry of entries) {\n const child = path.join(current, entry.name)\n if (entry.isSymbolicLink()) continue\n if (entry.isDirectory()) {\n pending.push(child)\n continue\n }\n if (!entry.isFile()) continue\n try {\n const stat = await lstat(child)\n if (stat.isFile()) {\n bytes += stat.size\n files += 1\n }\n } catch {\n // The directory changed while walking it; the byte total is advisory.\n }\n }\n }\n return { mtimeMs: info.mtimeMs, bytes, files }\n}\n\nexport class FeishuService {\n private readonly ctx: HostContext\n /** Live config object. Mutated in place — collaborators captured this reference. */\n private readonly config: FeishuConfig\n /** Loader/profile-provided config, the layer below the Web overlay. */\n private readonly baseConfig: FeishuConfig\n /** Web overlay store (`$DSH_HOME/dsh-channels/settings.json`), when mounted. */\n private readonly overlay: OverlayStore | undefined\n private readonly channelFactory: ChannelFactory\n private readonly sessionExportBuilder: SessionExportBuilder\n private channel: FeishuChannel | undefined\n private streamer: TurnStreamer | undefined\n private domain: FeishuDomainHandle | undefined\n /** Durable route store (cache + TTL retirement). */\n private readonly routes: RouteStore\n /** In-flight turn snapshots + startup repair. */\n private readonly repair: InflightRepair\n /** Outbound card delivery + reaction lifecycle. */\n private readonly delivery: CardDelivery\n /** Inbound attachment archival (Feishu resources → workspace files). */\n private readonly attachments: AttachmentIntake\n /** Inbound routing: dedup, admission, decision tree, session establishment. */\n private readonly inbound: InboundRouter\n /** Agent/session lifecycle: live registry, resume, presets, renew. */\n private readonly sessions: SessionManager\n /** FIFO admission for ordinary turns and commands targeting one route. */\n private readonly routeOperations = new RouteOperationQueue()\n /** Active Host command executions, aborted before shutdown tears peers down. */\n private readonly commandControllers = new Set<AbortController>()\n /** Whole command admissions, drained before channel/storage teardown. */\n private readonly commandOperations = new Set<Promise<void>>()\n /** `<provider>/<model>` → context window (lazy async fill; sync reads). */\n private readonly contextWindows = new Map<string, number>()\n private readonly windowRequests = new Set<string>()\n private started = false\n /** Epoch ms when `start()` completed (uptime for `/status`). */\n private startedAt: number | undefined\n /** Last failed connection attempt, surfaced in the Web settings panel. */\n private lastStartError: string | undefined\n /** Whether both Feishu application credentials currently resolve. */\n private configured = false\n /** Names of the missing credential references (never their values). */\n private missingConfigItems: string[] = []\n /** Guards the panel's explicit connect action (distinct from a restart). */\n private connecting = false\n /** Effective config captured at the last successful connect (restart-pending diff). */\n private runningConfig: FeishuConfig | undefined\n /** Guards overlapping panel-triggered reconnects. */\n private restarting = false\n private unlisten: (() => void) | undefined\n /** Disposers for host-scoped listeners registered by `start()`. */\n private readonly unlistenHost: Array<() => boolean> = []\n /** Periodic route-GC timer (unref'd; only armed when `routeTtlMs > 0`). */\n private gcTimer: ReturnType<typeof setInterval> | undefined\n /** Periodic workspace-retention timer (only armed when auto cleanup is on). */\n private retentionTimer: ReturnType<typeof setInterval> | undefined\n /** Guards overlapping cleanup passes (panel button / daily timer). */\n private cleanupRunning = false\n /** HITL approval answerer (created in `start()`). */\n private approvalAnswerer: ApprovalAnswerer | undefined\n /** HITL structured-question answerer (`ask_user_question`). */\n private questionAnswerer: QuestionAnswerer | undefined\n /** Disposers for the host approval waterfall listener + card actions. */\n private unlistenApproval: (() => boolean) | undefined\n /** Disposer for the host user-questions waterfall listener. */\n private unlistenQuestions: (() => boolean) | undefined\n private unlistenCardAction: (() => void) | undefined\n /** Durable access-control whitelists (groups + users + authorization requests). */\n private readonly accessStore: AccessStore\n /** Channel-plane admission gate (administrator / group / user). */\n private readonly accessController: AccessController\n /** Administrator DM authorization cards + bot-added group info cards. */\n private accessRequests: AccessRequests | undefined\n private unlistenBotAdded: (() => void) | undefined\n\n constructor(\n ctx: HostContext,\n config: FeishuConfig,\n channelFactory: ChannelFactory,\n sessionExportBuilder: SessionExportBuilder = createLogicalSessionExport,\n overlay?: OverlayStore,\n ) {\n this.ctx = ctx\n this.config = config\n this.baseConfig = { ...config }\n this.overlay = overlay\n this.channelFactory = channelFactory\n this.sessionExportBuilder = sessionExportBuilder\n this.routes = new RouteStore({\n domain: () => this.domain,\n ttlMs: () => this.config.routeTtlMs,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.repair = new InflightRepair({\n domain: () => this.domain,\n channel: () => this.channel,\n ttlMs: () => this.config.routeTtlMs,\n info: (message, ...args) => this.ctx.logger?.info?.(message, ...args),\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.sessions = new SessionManager({\n ctx: this.ctx,\n config: this.config,\n persistRoute: (key, record) => this.routes.put(key, record),\n started: () => this.started,\n detachStream: sessionId => this.streamer?.detach(sessionId),\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.attachments = new AttachmentIntake({\n enabled: () => this.config.acceptAttachments,\n channel: () => this.channel,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.delivery = new CardDelivery({\n channel: () => this.channel,\n started: () => this.started,\n routeForSession: sessionId => this.routes.findBySession(sessionId),\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.accessStore = new AccessStore({\n domain: () => this.domain,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.accessController = new AccessController({\n config: () => this.config,\n store: this.accessStore,\n channel: () => this.channel,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n onDeniedGroup: msg => {\n void this.accessRequests?.ensureGroupRequest(msg).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: group authorization request failed: %s', String(error))\n })\n },\n onDeniedUser: msg => {\n void this.accessRequests?.ensureUserRequest(msg).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: user authorization request failed: %s', String(error))\n })\n },\n })\n this.inbound = new InboundRouter({\n ctx: this.ctx,\n config: this.config,\n started: () => this.started,\n domain: () => this.domain,\n routes: this.routes,\n repair: this.repair,\n delivery: this.delivery,\n sessions: this.sessions,\n attachments: this.attachments,\n access: this.accessController,\n queue: this.routeOperations,\n streamer: () => this.streamer,\n questionAnswerer: () => this.questionAnswerer,\n commands: { handleSlash: (msg, line) => this.handleSlashMessage(msg, line) },\n })\n }\n\n // ---- lifecycle / Web settings panel ----------------------------------\n\n /**\n * Load the Web overlay and open the long connection when — and only when —\n * the Feishu application credentials are complete.\n *\n * An incomplete configuration is a normal state, not an error: the plugin\n * stays loaded so the Web settings panel can guide the user through the\n * credentials form, and no connection is attempted. A failed connection\n * attempt is recorded for the panel instead of failing the plugin fiber.\n */\n async boot(): Promise<void> {\n if (this.connecting || this.restarting) return\n // Acquire the lifecycle lock before the first await so a panel action can\n // never interleave with the boot sequence.\n this.connecting = true\n try {\n await this.loadOverlay()\n await this.refreshReadiness()\n if (!this.configured) {\n this.lastStartError = undefined\n this.runningConfig = undefined\n this.ctx.logger?.info?.(\n 'dsh-channels: 飞书应用凭据未配置完整(缺少 %s),本次不连接飞书;'\n + '请在 Web 设置面板 → 飞书渠道中配置,保存后会自动连接',\n this.missingConfigItems.join('、'),\n )\n return\n }\n await this.openConnection()\n } catch {\n // Recorded in `lastStartError`; the plugin itself stays loaded.\n } finally {\n this.connecting = false\n }\n }\n\n /**\n * Refresh the cached configuration readiness from the credentials seam.\n * Only presence is checked (describe/resolve), never values; the secrets\n * themselves are resolved by `start()` when a connection is opened.\n */\n async refreshReadiness(): Promise<void> {\n const states = await this.credentialStates()\n const missing: string[] = []\n if (!states.appId.configured) missing.push(`${this.config.appId}(飞书 App ID)`)\n if (!states.appSecret.configured) missing.push(`${this.config.appSecret}(飞书 App Secret)`)\n this.missingConfigItems = missing\n this.configured = missing.length === 0\n }\n\n /**\n * Open the connection with the live config. A failure tears the partial\n * service down and records the reason for the panel before rethrowing it to\n * the caller; the plugin fiber is never failed by a connection problem.\n */\n private async openConnection(): Promise<void> {\n try {\n await this.start()\n this.lastStartError = undefined\n this.runningConfig = { ...this.config }\n } catch (error) {\n this.lastStartError = messageOf(error)\n this.ctx.logger?.error?.('dsh-channels: 飞书连接失败:%s', String(error))\n // Headless profiles have no settings panel; keep the failure visible on\n // the console, symmetric with the success line at the end of `start()`.\n console.error('[dsh-channels] feishu connection failed:', error)\n try {\n await this.shutdown()\n } catch {\n // The connection error is the useful one.\n }\n throw error\n }\n }\n\n /**\n * Panel action: open the long connection when the channel is stopped.\n * Credentials are re-checked first — an incomplete pair throws a message the\n * panel shows next to the credentials form instead of attempting a connect.\n */\n async connect(): Promise<void> {\n if (this.started) return\n // Boot, an explicit connect and a restart all drive the same connection\n // wiring; serialize them instead of racing two `start()` bodies. The lock\n // is acquired synchronously, before any await.\n if (this.connecting || this.restarting) throw new Error('飞书连接正在连接或重启,请稍候再试')\n this.connecting = true\n try {\n // A stopped channel may have been edited on disk (the overlay file is\n // writable by hand); connecting re-reads it like a restart does.\n await this.loadOverlay()\n await this.refreshReadiness()\n if (!this.configured) {\n throw new Error(`飞书应用凭据尚未配置完整:缺少 ${this.missingConfigItems.join('、')}`)\n }\n await this.openConnection()\n } finally {\n this.connecting = false\n }\n }\n\n /** Re-read the Web overlay over the profile config (no channel touch). */\n private async loadOverlay(): Promise<void> {\n const overlay = this.overlay === undefined ? {} : await this.overlay.load()\n this.replaceConfig({ ...this.baseConfig, ...overlay })\n }\n\n /**\n * Replace every own key of the live config object. `Object.assign` alone\n * would keep stale overlay-only keys (e.g. `provider`) when the next layer\n * no longer defines them; collaborators hold this object identity, so the\n * mutation must happen in place.\n */\n private replaceConfig(next: FeishuConfig): void {\n const live = this.config as unknown as Record<string, unknown>\n for (const key of Object.keys(live)) delete live[key]\n Object.assign(live, next)\n }\n\n /**\n * Apply an already-validated overlay over the profile config. Fields read\n * per event/turn take effect immediately; `RESTART_KEYS` need `restart()`.\n */\n applyOverlay(overlay: Partial<FeishuConfig>): void {\n this.replaceConfig({ ...this.baseConfig, ...overlay })\n this.refreshRetentionTimer()\n }\n\n /**\n * Tear the connection down and bring it back with the latest config. When the\n * credentials are no longer complete the channel simply stays stopped (no\n * error): the panel then shows the configuration guide instead of a failure.\n */\n async restart(): Promise<void> {\n if (this.restarting || this.connecting) throw new Error('飞书连接正在连接或重启,请稍候再试')\n this.restarting = true\n try {\n await this.shutdown()\n await this.loadOverlay()\n await this.refreshReadiness()\n if (!this.configured) {\n this.lastStartError = undefined\n this.runningConfig = undefined\n return\n }\n await this.openConnection()\n } finally {\n this.restarting = false\n }\n }\n\n isRunning(): boolean { return this.started }\n isRestarting(): boolean { return this.restarting }\n /** True while the panel's explicit connect action is in flight. */\n isConnecting(): boolean { return this.connecting }\n /** Whether both Feishu application credentials currently resolve. */\n isConfigured(): boolean { return this.configured }\n /** Names of the missing configuration items (never values). */\n missingConfig(): readonly string[] { return this.missingConfigItems }\n startedAtMs(): number | undefined { return this.startedAt }\n uptimeMs(): number | undefined {\n return this.startedAt === undefined || !this.started ? undefined : Math.max(0, Date.now() - this.startedAt)\n }\n startError(): string | undefined { return this.lastStartError }\n configSnapshot(): FeishuConfig { return { ...this.config } }\n baseConfigSnapshot(): FeishuConfig { return { ...this.baseConfig } }\n runningConfigSnapshot(): FeishuConfig | undefined {\n return this.runningConfig === undefined ? undefined : { ...this.runningConfig }\n }\n\n // ---- access-control panel surface ------------------------------------\n\n /** Whitelists + pending authorization requests (read-only Web projection). */\n accessSnapshot(): AccessWire {\n const admin = this.config.adminOpenId\n return {\n mode: this.config.accessMode,\n // Whitelists live in the storage domain, which is only open while the\n // channel runs; an unconnected panel must not present them as empty.\n loaded: this.domain !== undefined,\n admin: {\n ...(admin === undefined ? {} : { openId: admin }),\n configured: admin !== undefined,\n },\n groups: this.accessStore.groupRecords().map(record => ({\n chatId: record.chatId,\n ...(record.name === undefined ? {} : { name: record.name }),\n source: record.source,\n addedAt: record.addedAt,\n })),\n users: this.accessStore.userRecords().map(record => ({\n openId: record.openId,\n ...(record.name === undefined ? {} : { name: record.name }),\n source: record.source,\n addedAt: record.addedAt,\n })),\n requests: this.accessStore.pendingRequests().map(record => {\n const wire: AccessRequestWire = {\n token: record.token,\n kind: record.kind,\n targetId: record.targetId,\n createdAt: record.createdAt,\n expiresAt: record.expiresAt,\n }\n const name = record.kind === 'group' ? record.groupName : record.requesterName\n if (name !== undefined) wire.name = name\n if (record.chatId !== undefined) wire.chatId = record.chatId\n if (record.groupName !== undefined) wire.groupName = record.groupName\n const requester = record.requesterName ?? record.requesterOpenId\n if (requester !== undefined) wire.requester = requester\n return wire\n }),\n }\n }\n\n /** Panel: authorize one group by id. */\n async addAccessGroup(chatId: string, name?: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessTargetId(chatId)\n if (id === undefined) return { ok: false, error: 'chat_id 不合法(应为 oc_ 开头的群 ID)' }\n const admin = this.config.adminOpenId\n if (admin === undefined) return { ok: false, error: '请先在「访问授权」标签页设置管理员 open_id' }\n try {\n if (!this.accessStore.hasGroup(id)) {\n await this.accessStore.addGroup({\n chatId: id,\n ...(name === undefined || name.trim() === '' ? {} : { name: name.trim() }),\n source: 'panel',\n addedBy: admin,\n addedAt: Date.now(),\n })\n }\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `写入授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: revoke one group. */\n async removeAccessGroup(chatId: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessTargetId(chatId)\n if (id === undefined) return { ok: false, error: 'chat_id 不合法' }\n try {\n await this.accessStore.removeGroup(id)\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `撤销授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: authorize one user (global across authorized groups). */\n async addAccessUser(openId: string, name?: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessUserId(openId)\n if (id === undefined) return { ok: false, error: 'open_id 不合法(应以 ou_ 开头)' }\n const admin = this.config.adminOpenId\n if (admin === undefined) return { ok: false, error: '请先在「访问授权」标签页设置管理员 open_id' }\n try {\n if (!this.accessStore.hasUser(id)) {\n await this.accessStore.addUser({\n openId: id,\n ...(name === undefined || name.trim() === '' ? {} : { name: name.trim() }),\n source: 'panel',\n addedBy: admin,\n addedAt: Date.now(),\n })\n }\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `写入授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: revoke one user. */\n async removeAccessUser(openId: string): Promise<AccessMutationResponse> {\n const id = this.cleanAccessUserId(openId)\n if (id === undefined) return { ok: false, error: 'open_id 不合法' }\n try {\n await this.accessStore.removeUser(id)\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `撤销授权失败:${messageOf(error)}` }\n }\n }\n\n /** Panel: decide one pending authorization request (same path as the card). */\n async resolveAccessRequest(token: string, decision: 'allow' | 'deny'): Promise<AccessMutationResponse> {\n const requests = this.accessRequests\n if (requests === undefined) return { ok: false, error: '授权服务尚未启动' }\n const actor = this.config.adminOpenId\n if (actor === undefined) return { ok: false, error: '请先在「访问授权」标签页配置管理员 open_id' }\n try {\n const result = await requests.resolve(token, decision, actor)\n if (!result.ok) return { ok: false, error: result.error }\n return { ok: true, access: this.accessSnapshot() }\n } catch (error) {\n return { ok: false, error: `处理授权申请失败:${messageOf(error)}` }\n }\n }\n\n /** Trim + bound one panel-supplied identifier; rejects control characters. */\n private cleanAccessTargetId(raw: string): string | undefined {\n const value = raw.trim()\n if (value === '' || value.length > 128) return undefined\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/u.test(value)) return undefined\n return value\n }\n\n /** Same normalization for user open_ids, which must look like ou_xxx. */\n private cleanAccessUserId(raw: string): string | undefined {\n const value = this.cleanAccessTargetId(raw)\n if (value === undefined || !/^ou_[A-Za-z0-9_-]+$/u.test(value)) return undefined\n return value\n }\n\n // ---- workspace retention cleanup -------------------------------------\n\n /**\n * Workspace-retention preview for the Web panel: how many plugin-created\n * workspaces exist, how many are past the configured window, and their\n * measured footprint. Never deletes anything.\n */\n async cleanupPreview(): Promise<{\n retention: string\n thresholdMs?: number\n auto: boolean\n workspaces: number\n candidates: number\n candidateBytes: number\n error?: string\n }> {\n const retention = this.config.workspaceRetention\n const window = retentionMs(retention)\n const base = {\n retention,\n ...(window === undefined ? {} : { thresholdMs: window }),\n auto: this.config.workspaceRetentionAuto,\n }\n if (this.domain === undefined) {\n // No connection: routes live in the closed storage domain, so a preview\n // would mistake every workspace for a candidate. Report why instead.\n return {\n ...base,\n workspaces: 0,\n candidates: 0,\n candidateBytes: 0,\n error: '飞书连接建立后即可预览和清理工作区',\n }\n }\n try {\n const workspaces = this.ctx.workspaceRegistry.list().filter(workspace => this.isPluginWorkspace(workspace))\n const plan = await planWorkspaceCleanup(this.cleanupDeps())\n return {\n ...base,\n workspaces: workspaces.length,\n candidates: plan?.candidates.length ?? 0,\n candidateBytes: plan?.candidates.reduce((total, candidate) => total + candidate.bytes, 0) ?? 0,\n }\n } catch (error) {\n return { ...base, workspaces: 0, candidates: 0, candidateBytes: 0, error: messageOf(error) }\n }\n }\n\n /**\n * Delete every stale plugin workspace the configured retention allows.\n * Routes bound to a removed workspace are retired first (thread routes leave\n * a tombstone), so an old topic fails with the explicit expired card instead\n * of resuming into a missing directory. DSH Session logs are not touched.\n */\n async runWorkspaceCleanup(): Promise<{\n ok: boolean\n deletedWorkspaces: number\n retiredRoutes: number\n freedBytes: number\n failures: string[]\n error?: string\n }> {\n const empty = { ok: false, deletedWorkspaces: 0, retiredRoutes: 0, freedBytes: 0, failures: [] as string[] }\n if (this.domain === undefined) {\n // Without the storage domain the route table is unreadable, so a cleanup\n // could mistake live workspaces for candidates and fail to retire their\n // routes. Refuse rather than rely on the panel disabling the button.\n return { ...empty, error: '飞书连接建立后才能清理工作区' }\n }\n if (retentionMs(this.config.workspaceRetention) === undefined) {\n return { ...empty, error: '未启用工作区清理阈值(workspaceRetention 为 off)' }\n }\n if (this.cleanupRunning) return { ...empty, error: '工作区清理正在进行中,请稍候' }\n this.cleanupRunning = true\n try {\n const deps = this.cleanupDeps()\n const plan = await planWorkspaceCleanup(deps)\n if (plan === undefined) return { ...empty, error: '工作区清理不可用:baseDir 配置无效' }\n const outcome = await applyWorkspaceCleanup(deps, plan)\n this.ctx.logger?.info?.(\n 'dsh-channels: workspace cleanup removed %d workspace(s), retired %d route(s), freed %d bytes',\n outcome.deletedWorkspaces, outcome.retiredRoutes, outcome.freedBytes,\n )\n return {\n ok: true,\n deletedWorkspaces: outcome.deletedWorkspaces,\n retiredRoutes: outcome.retiredRoutes,\n freedBytes: outcome.freedBytes,\n failures: outcome.failures.map(failure => `${failure.path}: ${failure.message}`),\n }\n } catch (error) {\n return { ...empty, error: messageOf(error) }\n } finally {\n this.cleanupRunning = false\n }\n }\n\n /** Whether one registry workspace was created by this plugin inside `baseDir`. */\n private isPluginWorkspace(workspace: Workspace): boolean {\n const pluginTitle = workspace.title.startsWith('飞书私聊 ·') || workspace.title.startsWith('飞书群聊 ·')\n return pluginTitle && isPathInside(path.resolve(this.config.baseDir), workspace.path)\n }\n\n /** Effect seams for the cleanup policy module (all I/O stays here). */\n private cleanupDeps(): CleanupDeps {\n const baseDir = path.resolve(this.config.baseDir)\n return {\n baseDir,\n retention: this.config.workspaceRetention,\n listWorkspaces: () => this.ctx.workspaceRegistry.list(),\n activeRoutes: () => [...(this.domain?.routes.entries() ?? [])].map(([key, record]) => ({\n key,\n kind: record.kind,\n workspacePath: record.workspacePath,\n sessionId: record.sessionId,\n lastActivityAt: record.lastActivityAt,\n })),\n tombstones: () => [...(this.domain?.expiredRoutes.entries() ?? [])].map(([key, record]) => ({\n key,\n ...(record.workspacePath === undefined ? {} : { workspacePath: record.workspacePath }),\n expiredAt: record.expiredAt,\n ...(record.lastActivityAt === undefined ? {} : { lastActivityAt: record.lastActivityAt }),\n })),\n isLive: sessionId => this.sessions.get(sessionId) !== undefined\n || this.ctx.agents.get(sessionId as SessionId) !== undefined,\n statWorkspace: workspacePath => measureWorkspace(workspacePath),\n removeWorkspace: async workspace => {\n const target = path.resolve(workspace.path)\n if (!isPathInside(baseDir, target)) {\n throw new Error(`refusing to delete a path outside baseDir (${target})`)\n }\n // Re-verify the physical path: a symlinked ancestor inside baseDir\n // must not let the recursive delete escape it.\n try {\n const [physicalBase, physicalTarget] = await Promise.all([realpath(baseDir), realpath(target)])\n if (!isPathInside(physicalBase, physicalTarget)) {\n throw new Error(`refusing to delete a symlink escape outside baseDir (${physicalTarget})`)\n }\n } catch (error) {\n if (error instanceof Error && error.message.startsWith('refusing to delete a symlink escape')) throw error\n // A missing workspace is already effectively removed (rm is idempotent).\n }\n await rm(target, { recursive: true, force: true })\n try {\n await this.ctx.workspaceRegistry.delete(workspace.id)\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: unregistering cleaned workspace %s failed: %s', workspace.path, String(error),\n )\n }\n },\n retireRoute: async route => {\n if (route.kind === 'thread') await this.routes.retire(route.key)\n else await this.routes.remove(route.key)\n },\n warn: message => this.ctx.logger?.warn?.(message),\n }\n }\n\n /**\n * (Re)arm the daily retention timer to match the live config. `runNow`\n * performs one pass immediately; it is only used at startup so saving any\n * unrelated setting can never trigger a surprise deletion pass.\n */\n private refreshRetentionTimer(runNow = false): void {\n if (this.retentionTimer !== undefined) {\n clearInterval(this.retentionTimer)\n this.retentionTimer = undefined\n }\n if (!this.started\n || !this.config.workspaceRetentionAuto\n || retentionMs(this.config.workspaceRetention) === undefined) return\n const sweep = (): void => {\n void this.runWorkspaceCleanup().then(result => {\n if (!result.ok && result.error !== '工作区清理正在进行中,请稍候') {\n this.ctx.logger?.warn?.('dsh-channels: automatic workspace cleanup failed: %s', result.error)\n }\n })\n }\n if (runNow) sweep()\n const timer = setInterval(sweep, RETENTION_SWEEP_INTERVAL_MS)\n timer.unref?.()\n this.retentionTimer = timer\n }\n\n /** Aggregate health used by `/health` and the Web settings panel. */\n health(): HealthSnapshot { return this.healthSnapshot() }\n\n /**\n * Credential facts for the two Feishu refs. Values are never returned:\n * `describe()` reports presence/source/writability only.\n */\n async credentialStates(): Promise<{ appId: CredentialInfo; appSecret: CredentialInfo }> {\n const provider = this.credentialProvider()\n const read = async (ref: string): Promise<CredentialInfo> => {\n try {\n if (provider?.describe !== undefined) {\n const info = await provider.describe(ref)\n return {\n configured: info?.configured === true,\n writable: info?.writable !== false && provider.set !== undefined,\n ...(info?.source === undefined ? {} : { source: info.source }),\n }\n }\n if (provider === undefined) {\n const ambient = process.env[ref]\n return { configured: ambient !== undefined && ambient !== '', writable: false }\n }\n return { configured: (await provider.resolve(ref)) !== undefined, writable: provider.set !== undefined }\n } catch {\n return { configured: false, writable: false }\n }\n }\n const [appId, appSecret] = await Promise.all([read(this.config.appId), read(this.config.appSecret)])\n return { appId, appSecret }\n }\n\n /** The host credentials seam, when mounted. */\n private credentialProvider(): CredentialProvider | undefined {\n return this.ctx.get?.('credentials') as CredentialProvider | undefined\n }\n\n /** Store one Feishu credential value through the host seam; the value is never logged. */\n async setCredential(field: 'appId' | 'appSecret', value: string): Promise<void> {\n const provider = this.credentialProvider()\n if (provider?.set === undefined) {\n throw new Error('主机的凭据服务不可写(未挂载 credentials.set,或当前提供方只读)')\n }\n const ref = this.credentialRef(field)\n try {\n await provider.set(ref, value)\n } catch (error) {\n throw new Error(`写入凭据引用 ${ref} 失败:${messageOf(error)}`)\n }\n await this.refreshReadiness()\n }\n\n /** Remove the stored value of one Feishu credential reference. */\n async clearCredential(field: 'appId' | 'appSecret'): Promise<void> {\n const provider = this.credentialProvider()\n if (provider?.unset === undefined) {\n throw new Error('主机的凭据服务不支持移除凭据')\n }\n const ref = this.credentialRef(field)\n try {\n await provider.unset(ref)\n } catch (error) {\n throw new Error(`移除凭据引用 ${ref} 失败:${messageOf(error)}`)\n }\n await this.refreshReadiness()\n }\n\n /** The overlay-independent ref name backing one editable credential slot. */\n private credentialRef(field: 'appId' | 'appSecret'): string {\n return field === 'appId' ? this.config.appId : this.config.appSecret\n }\n\n /** Wire events and open the long connection. */\n async start(): Promise<void> {\n if (this.started) return\n this.started = true\n\n // 1. Resolve credentials through the host seam (env > .credentials.yaml > .env).\n const appId = await resolveCredentialRef(this.ctx, this.config.appId)\n const appSecret = await resolveCredentialRef(this.ctx, this.config.appSecret)\n\n // 2. Build the channel with the resolved secrets.\n const channel = this.channelFactory({\n appId,\n appSecret,\n loggerLevel: this.config.loggerLevel,\n requireMention: this.config.requireMention,\n dmMode: this.config.dmMode,\n })\n this.channel = channel\n const hooks: StreamerHooks = {\n measureContext: session => this.measureContext(session),\n projectStats: session => this.projectCardStats(session),\n sessionLabel: sessionId => sessionId,\n onError: (_key, error) => {\n this.ctx.logger?.warn?.('dsh-channels stream error: %s', String(error))\n },\n onTurnFinalized: (_sessionId, _turn, mainCardMessageId) => {\n // The turn settled (final patches fired, reaction swap requested):\n // retire its repair snapshot. Kept deliberately non-awaited — repair\n // state is a safety net, never a dependency of the turn lifecycle.\n void this.repair.clear(mainCardMessageId)\n },\n onSegmentDelivered: (mainCardMessageId, segmentIndex, messageId, mode) => {\n void this.repair.recordSegment(mainCardMessageId, segmentIndex, messageId, mode)\n },\n sendSegmentCard: (sessionId, segment, card, fallbackText) =>\n this.delivery.segmentCard(sessionId, segment, card, fallbackText),\n }\n this.streamer = new TurnStreamer(\n channel,\n this.config.refreshMs,\n hooks,\n this.config.statusRefreshMs,\n )\n\n this.domain = await openFeishuDomain(this.ctx)\n this.routes.load()\n this.accessStore.load()\n\n // HITL: answer the host's approval/request waterfall for sessions this\n // channel routes. `prepend` claims such requests BEFORE the web gateway's\n // generic answerer (its panel would be invisible to a Feishu user), while\n // non-owned sessions delegate through `next()`.\n const answerer = new ApprovalAnswerer(channel, {\n routeFor: sessionId => this.approvalRouteFor(sessionId),\n onError: (sessionId, error) => {\n this.ctx.logger?.warn?.('dsh-channels approval error (session %s): %s', sessionId, String(error))\n },\n }, this.config.approvalTimeoutMs)\n this.approvalAnswerer = answerer\n this.unlistenApproval = this.ctx.on('approval/request', (req, next) => answerer.onRequest(req, next), { prepend: true })\n // Structured questions (`ask_user_question`): the host's only shipped\n // answerer is the Web client, so a Feishu-only session would otherwise\n // fail every ask with NO_PROVIDER.\n const questions = new QuestionAnswerer(channel, {\n routeFor: sessionId => this.approvalRouteFor(sessionId),\n onError: (sessionId, error) => {\n this.ctx.logger?.warn?.('dsh-channels question error (session %s): %s', sessionId, String(error))\n },\n }, this.config.questionTimeoutMs)\n this.questionAnswerer = questions\n this.unlistenQuestions = this.ctx.on(\n 'user-questions/request',\n (request, next) => questions.onRequest(request, next),\n { prepend: true },\n )\n const accessRequests = new AccessRequests({\n config: () => this.config,\n store: this.accessStore,\n channel: () => this.channel,\n warn: (message, ...args) => this.ctx.logger?.warn?.(message, ...args),\n })\n this.accessRequests = accessRequests\n this.unlistenBotAdded = channel.onBotAdded(event => {\n void accessRequests.handleBotAdded(event).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: bot-added group info card failed: %s', String(error))\n })\n })\n void accessRequests.expirePending().catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: access request expiry sweep failed: %s', String(error))\n })\n this.unlistenCardAction = channel.onCardAction(event => {\n void accessRequests.onCardAction(event)\n answerer.onCardAction(event)\n questions.onCardAction(event)\n })\n\n this.unlistenHost.push(this.ctx.on('session/event', (session, event) => {\n this.streamer?.onSessionEvent(session, event)\n }))\n // DSH removed the durable `assistant/chunk` event (format v2 embeds the\n // stream in the settlement); the live deltas arrive here instead.\n this.unlistenHost.push(this.ctx.on('agent/assistant-stream', ({ agent, frame }) => {\n this.streamer?.onAssistantFrame(agent.session, frame)\n }))\n this.unlisten = channel.onMessage(message => {\n void this.inbound.handle(message).catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels inbound error: %s', String(error))\n // User-facing safety net: never leave the user without a reply.\n void channel\n .send(message.chatId, { text: `dsh-channels 处理失败:${String(error)}` }, { replyTo: message.messageId })\n .catch(() => {})\n })\n })\n await channel.connect()\n // Repair cards a previous instance left mid-flight (best-effort, background).\n void this.repair.repairAll().catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: inflight repair failed: %s', String(error))\n })\n // Record GC: one sweep right after boot, then a slow periodic sweep.\n // Dedup records are pruned unconditionally (fixed retention); route\n // records of deleted topics (their thread id never reappears) are pruned\n // when `routeTtlMs` is enabled.\n void this.gcExpiredEntries().catch((error: unknown) => {\n this.ctx.logger?.warn?.('dsh-channels: record GC failed: %s', String(error))\n })\n const timer = setInterval(() => {\n void this.gcExpiredEntries().catch(() => {})\n }, GC_SWEEP_INTERVAL_MS)\n timer.unref?.()\n this.gcTimer = timer\n // Workspace retention: opt-in automatic cleanup (default off).\n this.refreshRetentionTimer(true)\n this.ctx.logger?.info?.(\n 'dsh-channels: feishu long connection established (bot open_id %s, %d route(s) loaded)',\n channel.botOpenId() ?? 'unknown',\n this.routes.size(),\n )\n console.info('[dsh-channels] feishu long connection established, bot open_id:', channel.botOpenId())\n console.info('[dsh-channels] activity build:', ACTIVITY_BUILD_MARKER)\n this.startedAt = Date.now()\n console.info('[dsh-channels] model request overrides:', {\n provider: this.config.provider ?? 'deployment-default',\n model: this.config.model ?? 'deployment-default',\n reasoningEffort: this.config.reasoningEffort ?? 'adapter-default',\n })\n if (this.config.accessMode === 'open') {\n console.warn('[dsh-channels] 准入=open:任何应用可用范围内的用户都可以对话(不安全,仅调试)')\n } else if (this.config.adminOpenId === undefined) {\n console.warn(\n '[dsh-channels] 准入=guarded 但未配置管理员 open_id:所有对话都会被拒绝;'\n + '私聊机器人发送 /userid 取得 open_id 后填入 Web 设置面板',\n )\n } else {\n console.info('[dsh-channels] 准入=guarded,管理员已配置')\n }\n const approvalMounted = this.ctx.get?.('approval') !== undefined\n console.info(\n approvalMounted\n ? `[dsh-channels] HITL 审批应答已就绪(决策卡片超时 ${this.config.approvalTimeoutMs} ms)`\n : '[dsh-channels] 主机未挂载 approval 服务,HITL 审批应答关闭',\n )\n const questionsMounted = this.ctx.get?.('userQuestions') !== undefined\n console.info(\n questionsMounted\n ? `[dsh-channels] ask_user_question 应答已就绪(提问卡片超时 ${this.config.questionTimeoutMs} ms)`\n : '[dsh-channels] 主机未挂载 userQuestions 服务,结构化提问应答关闭',\n )\n }\n\n /** Tear down: close the connection and dispose all live agents. */\n async shutdown(): Promise<void> {\n // Close admission first. In-flight Host commands receive cancellation\n // before their Agent/channel/storage dependencies are disposed, while\n // already queued route operations observe `started === false` and retire.\n this.started = false\n for (const controller of this.commandControllers) controller.abort()\n await Promise.allSettled([...this.commandOperations])\n this.commandControllers.clear()\n this.commandOperations.clear()\n this.unlisten?.()\n this.unlisten = undefined\n this.unlistenCardAction?.()\n this.unlistenCardAction = undefined\n this.unlistenBotAdded?.()\n this.unlistenBotAdded = undefined\n this.accessRequests?.clear()\n this.accessRequests = undefined\n this.accessController.clear()\n this.unlistenApproval?.()\n this.unlistenApproval = undefined\n this.unlistenQuestions?.()\n this.unlistenQuestions = undefined\n // Host-scoped listeners must be released on every teardown: a failed\n // connect retry or a reconnect calls `start()` again, and a leaked pair\n // would feed the same session events into the streamer twice.\n for (const dispose of this.unlistenHost.splice(0)) dispose()\n // Withdraw in-flight approval questions (the host settles them cancelled).\n this.approvalAnswerer?.dispose()\n this.approvalAnswerer = undefined\n this.questionAnswerer?.dispose()\n this.questionAnswerer = undefined\n if (this.gcTimer !== undefined) {\n clearInterval(this.gcTimer)\n this.gcTimer = undefined\n }\n if (this.retentionTimer !== undefined) {\n clearInterval(this.retentionTimer)\n this.retentionTimer = undefined\n }\n await this.sessions.disposeAll()\n this.routeOperations.clear()\n this.inbound.clear()\n this.routes.clear()\n this.accessStore.clear()\n try {\n await this.channel?.disconnect()\n } catch {\n // Already disconnected is fine.\n }\n this.channel = undefined\n this.streamer = undefined\n await this.domain?.close()\n this.domain = undefined\n this.runningConfig = undefined\n }\n\n // ---- inbound routing -------------------------------------------------\n\n \n /** Slash-command plane. Nothing in this method calls `agent.followup()`. */\n /**\n * Slash-command admission. The command vocabulary and its admission rules\n * come from the command registry; nothing here calls `agent.followup()`.\n */\n private async handleSlashMessage(msg: NormalizedMessage, line: string): Promise<void> {\n if (!this.started) return\n const parsed = parseSlashCommand(line)\n // Channel-plane command admission runs before ANY card/topic/side effect:\n // denied senders get at most a fixed private-chat notice (or silence in\n // groups), never an unknown-command card or a command thread.\n if (!(await this.accessController.admitCommand(msg, parsed?.name))) return\n if (parsed === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'input-error',\n text: '命令名必须以小写字母开头,且只能包含小写字母、数字、下划线或连字符。',\n supported: supportedCommandList(),\n })\n return\n }\n const spec = commandSpec(parsed.name)\n if (spec === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'unknown', command: parsed.name,\n text: `未找到命令 /${parsed.name}。`, supported: supportedCommandList(),\n })\n return\n }\n\n // `/auth` is administrator-only, route-free, and handled right here so it\n // works even inside an unauthorized group (that is the \"authorize from\n // the conversation\" entry point).\n if (spec.name === 'auth') {\n await this.runAuthCommand(msg, parsed)\n return\n }\n\n // Route-free commands answer without any Session work.\n if (spec.routeFree === true) {\n await this.runRouteFreeCommand(spec, msg, parsed)\n return\n }\n\n // A TTL-retired topic is permanently closed except for session-optional\n // diagnostics (`/health`).\n const expiredRoute = msg.threadId === undefined ? undefined : this.routes.expiredRoute(msg.threadId)\n if (expiredRoute !== undefined) {\n if (spec.sessionOptional === true) {\n await this.runRouteFreeCommand(spec, msg, parsed)\n return\n }\n await this.delivery.expiredRouteCard(msg, parsed.name)\n return\n }\n\n const route = await this.inbound.resolveRoute(msg)\n if (route === undefined) {\n if (spec.sessionOptional === true) {\n await this.runRouteFreeCommand(spec, msg, parsed)\n return\n }\n await this.delivery.commandCard(msg, {\n kind: 'unbound', command: parsed.name,\n text: '当前话题尚未绑定 Session。请先发送普通消息创建会话,或使用 /health 检查通道。',\n })\n return\n }\n\n if (msg.senderId !== route.senderOpenId) {\n await this.delivery.commandCard(msg, {\n kind: 'forbidden', command: parsed.name, text: '无权操作此会话。',\n })\n return\n }\n\n const operation = this.routeOperations.run(route.key, async () => {\n if (!this.started) return\n const current = this.routes.get(route.key) ?? route\n if (msg.senderId !== current.senderOpenId) {\n await this.delivery.commandCard(msg, {\n kind: 'forbidden', command: parsed.name, text: '无权操作此会话。',\n })\n return\n }\n await this.runRouteCommand(spec, msg, parsed, current)\n })\n this.commandOperations.add(operation)\n try {\n await operation\n } finally {\n this.commandOperations.delete(operation)\n }\n }\n\n /**\n * `/auth`: administrator-only access management on the channel plane. It\n * never creates a Session or resumes an Agent; every reply is a fixed card.\n */\n private async runAuthCommand(msg: NormalizedMessage, parsed: ParsedSlashCommand): Promise<void> {\n if (!this.accessController.isAdmin(msg.senderId)) return\n const direct = { createThread: false }\n const words = parsed.rawInput.trim().split(/\\s+/u).filter(word => word !== '')\n const action = words[0] ?? 'status'\n const now = Date.now()\n try {\n if (action === 'status') {\n const groups = this.accessStore.groupRecords()\n const users = this.accessStore.userRecords()\n const pending = this.accessStore.pendingRequests()\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 访问授权状态**', [\n `**准入模式**:\\`${this.config.accessMode}\\``,\n `**管理员**:${this.config.adminOpenId === undefined ? '未配置' : `\\`${this.config.adminOpenId}\\``}`,\n ...(msg.chatType === 'group'\n ? [`**本群**:${this.accessStore.hasGroup(msg.chatId) ? '已授权' : '未授权'}(\\`${msg.chatId}\\`)`]\n : []),\n `**白名单**:群 ${groups.length} 个 · 用户 ${users.length} 个 · 待处理申请 ${pending.length} 个`,\n '群授权:`/auth allow` 或 `/auth revoke`(在目标群内发);用户授权:`/auth user add <open_id>`。',\n ]), direct)\n return\n }\n if (action === 'allow' || action === 'revoke') {\n if (msg.chatType !== 'group') {\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'auth',\n text: '群授权命令只能在群聊中使用:在目标群里发送 /auth allow 或 /auth revoke。',\n }, direct)\n return\n }\n if (action === 'allow') {\n if (this.accessStore.hasGroup(msg.chatId)) {\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth', text: `本群已经授权(\\`${msg.chatId}\\`)。`,\n }, direct)\n return\n }\n let name: string | undefined\n try {\n name = (await this.channel?.getChatInfo(msg.chatId))?.name\n } catch {\n name = undefined\n }\n await this.accessStore.addGroup({\n chatId: msg.chatId,\n ...(name === undefined || name === '' ? {} : { name }),\n source: 'command',\n addedBy: msg.senderId,\n addedAt: now,\n })\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth',\n text: '✅ 已授权本群。群内只有管理员与已授权用户能与我对话。',\n }, direct)\n return\n }\n const removed = await this.accessStore.removeGroup(msg.chatId)\n await this.delivery.commandCard(msg, {\n kind: removed ? 'success' : 'unavailable', command: 'auth',\n text: removed ? '已撤销本群授权,立即生效。' : '本群当前未被授权。',\n }, direct)\n return\n }\n if (action === 'user') {\n const sub = words[1]\n const openId = words[2]\n if ((sub !== 'add' && sub !== 'remove') || openId === undefined\n || !/^ou_[A-Za-z0-9_-]+$/u.test(openId)) {\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'auth',\n text: '用法:/auth user add <open_id> 或 /auth user remove <open_id>(open_id 以 ou_ 开头,可用 /userid 查看)。',\n }, direct)\n return\n }\n if (sub === 'add') {\n if (this.accessStore.hasUser(openId)) {\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth', text: `该用户已在白名单中(\\`${openId}\\`)。`,\n }, direct)\n return\n }\n await this.accessStore.addUser({\n openId, source: 'command', addedBy: msg.senderId, addedAt: now,\n })\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'auth',\n text: `✅ 已授权用户 \\`${openId}\\`(在所有已授权群里生效)。`,\n }, direct)\n return\n }\n const removed = await this.accessStore.removeUser(openId)\n await this.delivery.commandCard(msg, {\n kind: removed ? 'success' : 'unavailable', command: 'auth',\n text: removed ? `已移除用户 \\`${openId}\\`,立即生效。` : `用户 \\`${openId}\\` 不在白名单中。`,\n }, direct)\n return\n }\n if (action === 'list') {\n const groups = this.accessStore.groupRecords()\n const users = this.accessStore.userRecords()\n const pending = this.accessStore.pendingRequests()\n const groupLines = groups.length === 0\n ? ['(暂无)']\n : groups.slice(0, 30).map(group => `- ${group.name ?? group.chatId} \\`${group.chatId}\\``)\n const userLines = users.length === 0\n ? ['(暂无)']\n : users.slice(0, 30).map(user => `- ${user.name ?? user.openId} \\`${user.openId}\\``)\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 访问授权清单**', [\n `**已授权群(${groups.length})**`,\n ...groupLines,\n `**已授权用户(${users.length})**`,\n ...userLines,\n `**待处理申请**:${pending.length} 个(可在 Web 设置面板处理)`,\n ]), direct)\n return\n }\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'auth',\n text: '用法:/auth [status|allow|revoke|list|user add <open_id>|user remove <open_id>]',\n }, direct)\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: /auth failed: %s', String(error))\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: 'auth', text: `授权操作失败:${messageOf(error)}`,\n }, direct).catch(() => {})\n }\n }\n\n /** Answer one command that needs no Session (help / userid / groupid / health). */\n private async runRouteFreeCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n ): Promise<void> {\n const createThread = spec.directReply !== true\n if (parsed.rawInput.trim() !== '') {\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: spec.name, text: `用法错误:/${spec.name} 不接受参数。`,\n }, { createThread })\n return\n }\n if (spec.mentionGate === true\n && msg.chatType === 'group'\n && msg.threadId === undefined\n && this.config.requireMention\n && !msg.mentionedBot) {\n return\n }\n if (spec.chatType !== undefined && msg.chatType !== spec.chatType) {\n await this.delivery.commandCard(msg, {\n kind: 'context-error', command: spec.name,\n text: spec.chatType === 'p2p'\n ? `命令 /${spec.name} 仅支持在私聊中使用。`\n : `命令 /${spec.name} 仅支持在群聊中使用。`,\n }, { createThread })\n return\n }\n await this.delivery.withCommandReaction(msg, async () => {\n switch (spec.name) {\n case 'help':\n await this.delivery.commandCard(msg, { kind: 'help', text: supportedCommandHelp() }, { createThread })\n return\n case 'userid':\n await this.delivery.commandCard(msg, {\n kind: 'success', command: spec.name, text: `当前用户 ID:\\`${msg.senderId}\\``,\n }, { createThread })\n return\n case 'groupid':\n await this.delivery.commandCard(msg, {\n kind: 'success', command: spec.name, text: `当前群组 ID:\\`${msg.chatId}\\``,\n }, { createThread })\n return\n case 'health':\n await this.delivery.standaloneReply(msg, buildHealthCard(this.healthSnapshot()), { createThread })\n return\n default:\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name, text: `命令 /${spec.name} 暂不可用。`,\n }, { createThread })\n }\n })\n }\n\n /** Answer one route-bound command inside the per-route admission queue. */\n private async runRouteCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n await this.delivery.withCommandReaction(msg, async () => {\n try {\n if (spec.channelPlane === true) {\n await this.runChannelCommand(spec, msg, parsed, current)\n return\n }\n await this.runAgentCommand(spec, msg, parsed, current)\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: command /%s failed for session %s: %s',\n spec.name, current.sessionId, String(error),\n )\n await this.delivery.commandCard(msg, {\n kind: 'internal-error', command: spec.name,\n text: '命令执行失败,请稍后重试;若持续发生,请查看 Harness 日志。',\n sessionId: current.sessionId,\n })\n }\n })\n }\n\n /**\n * Commands the channel answers itself. `/stop` aborts the live turn\n * directly and deliberately never cold-resumes a Session just to cancel\n * nothing.\n */\n private async runChannelCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n if (spec.name === 'permissions') {\n await this.runPermissionsCommand(msg, parsed, current)\n return\n }\n if (spec.name === 'stop') {\n const agent = this.sessions.get(current.sessionId)?.agent\n ?? this.ctx.agents.get(current.sessionId as SessionId)\n const running = agent?.status === 'running'\n agent?.cancel?.({ kind: 'user' })\n await this.delivery.commandCard(msg, {\n kind: 'success', command: 'stop', sessionId: current.sessionId,\n text: running ? '已请求停止当前回合。' : '当前没有正在运行的回合。',\n })\n return\n }\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name, text: `命令 /${spec.name} 暂不可用。`,\n sessionId: current.sessionId,\n })\n }\n\n /**\n * `/permissions`: show this Session's effective sandbox+approval preset and\n * the selectable catalog; switch only when the deployment opted in.\n */\n private async runPermissionsCommand(\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n const presets = this.ctx.get?.('permissionPresets') as PermissionPresets | undefined\n if (presets === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: 'permissions', sessionId: current.sessionId,\n text: '当前部署未挂载 permissionPresets 服务,无法查看权限预设。',\n })\n return\n }\n const live = await this.sessions.ensureLive(current)\n let active: string | undefined\n let catalogLine = ''\n try {\n active = presets.current(live.agent.session)\n const catalog = presets.catalog()\n catalogLine = catalog.options.map(option => `\\`${option.value}\\``).join('、')\n } catch {\n active = undefined\n }\n const requested = parsed.rawInput.trim()\n if (requested === '') {\n const lines = [\n `**当前权限预设**:${active === undefined ? '暂无数据' : `\\`${active}\\``}`,\n ...(catalogLine === '' ? [] : [`**可选预设**:${catalogLine}`]),\n ...(this.config.allowPermissionSwitch\n ? ['使用 `/permissions <name>` 切换;切换会同时调整沙箱与审批策略。']\n : ['当前部署未开启飞书侧切换(`allowPermissionSwitch: false`),仅支持查看。']),\n ]\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 权限预设**', lines))\n return\n }\n if (!this.config.allowPermissionSwitch) {\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: 'permissions', sessionId: current.sessionId,\n text: '当前部署未开启飞书侧权限切换,仅支持 /permissions 查看。',\n })\n return\n }\n try {\n presets.set(live.agent.session, requested)\n await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 权限预设已切换**', [\n `**当前权限预设**:\\`${requested}\\``,\n '沙箱模式与审批策略已按该预设更新。',\n ]))\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: permission switch for session %s failed: %s', current.sessionId, String(error),\n )\n await this.delivery.commandCard(msg, {\n kind: 'input-error', command: 'permissions', sessionId: current.sessionId,\n text: `未知或不可用的权限预设:\\`${requested}\\`。可用:${catalogLine === '' ? '暂无数据' : catalogLine}`,\n })\n }\n }\n\n /**\n * Execute one command through the agent command plane: resolve the live\n * agent, mount borrowed channel commands when needed, arm the channel-scoped\n * `/renew` and `/effort` handlers, then render the settled result.\n */\n private async runAgentCommand(\n spec: ChannelCommandSpec,\n msg: NormalizedMessage,\n parsed: ParsedSlashCommand,\n current: RouteRecord,\n ): Promise<void> {\n // An alias (`/new`) resolves to this canonical spec; execute the canonical\n // line so the agent command plane finds the registered definition.\n const line = `/${spec.name}${parsed.rawInput}`\n const live = await this.sessions.ensureLive(current)\n let definition = this.ctx.commands.find(live.agent, spec.name)\n let unregisterBorrowedCommand: (() => void) | undefined\n if (definition === undefined && spec.name === 'renew') {\n unregisterBorrowedCommand = registerRenewCommand(live.agent.ctx.commands)\n definition = this.ctx.commands.find(live.agent, spec.name)\n }\n if (definition === undefined && spec.name === 'effort') {\n unregisterBorrowedCommand = registerEffortCommand(live.agent.ctx.commands)\n definition = this.ctx.commands.find(live.agent, spec.name)\n }\n if (definition === undefined && spec.name === 'model') {\n unregisterBorrowedCommand = registerModelCommand(live.agent.ctx.commands)\n definition = this.ctx.commands.find(live.agent, spec.name)\n }\n if (definition === undefined) {\n unregisterBorrowedCommand?.()\n const text = spec.name === 'compact'\n ? '当前部署未挂载 Harness 原生 /compact 命令。'\n : `当前 Session 未挂载 /${spec.name} 命令。`\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name, text, sessionId: current.sessionId,\n })\n return\n }\n const compactBefore = spec.name === 'compact'\n ? await this.measureCommandContext(live.agent.session)\n : undefined\n const controller = new AbortController()\n this.commandControllers.add(controller)\n const renewAdmission: RenewAdmission | undefined = spec.name === 'renew'\n ? { route: current, live }\n : undefined\n const effortAdmission: EffortAdmission | undefined = spec.name === 'effort'\n ? { live, controller: this.sessions.requestPolicy(live.agent) }\n : undefined\n const modelAdmission: ModelAdmission | undefined = spec.name === 'model'\n ? { live, controller: this.sessions.requestPolicy(live.agent) }\n : undefined\n const disarmRenew = renewAdmission === undefined\n ? undefined\n : armRenewCommand(controller.signal, invocation => this.runRenewCommand(invocation, renewAdmission))\n const disarmEffort = effortAdmission === undefined\n ? undefined\n : armEffortCommand(\n controller.signal,\n (invocation, action) => this.runEffortCommand(invocation, action, effortAdmission),\n )\n const disarmModel = modelAdmission === undefined\n ? undefined\n : armModelCommand(\n controller.signal,\n (invocation, rawInput) => this.runModelCommand(invocation, rawInput, modelAdmission),\n )\n let execution\n try {\n execution = await this.ctx.commands.execute(live.agent, line, [], controller.signal)\n } finally {\n this.commandControllers.delete(controller)\n disarmRenew?.()\n disarmEffort?.()\n disarmModel?.()\n unregisterBorrowedCommand?.()\n if (renewAdmission?.commit !== undefined) {\n await this.sessions.releaseRenewed(renewAdmission.commit)\n }\n }\n if (controller.signal.aborted || !this.started) return\n if (execution === undefined) {\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name,\n text: `当前 Session 无法执行 /${spec.name}。`, sessionId: current.sessionId,\n })\n return\n }\n await this.renderCommandResult({\n spec, msg, current, live,\n result: execution.result,\n compactBefore,\n renewAdmission,\n effortAdmission,\n modelAdmission,\n })\n }\n\n /** Render one settled agent-plane command result. */\n private async renderCommandResult(input: {\n spec: ChannelCommandSpec\n msg: NormalizedMessage\n current: RouteRecord\n live: LiveAgent\n result: CommandResult\n compactBefore: ContextMeasurement | undefined\n renewAdmission: RenewAdmission | undefined\n effortAdmission: EffortAdmission | undefined\n modelAdmission: ModelAdmission | undefined\n }): Promise<void> {\n const { spec, msg, current, live, result, compactBefore, renewAdmission, effortAdmission, modelAdmission } = input\n if (spec.name === 'effort') effortAdmission?.controller.sync(live.agent.session.snapshotEvents())\n if (spec.name === 'model') modelAdmission?.controller.sync(live.agent.session.snapshotEvents())\n if (spec.name === 'renew' && result.kind === 'success' && renewAdmission?.commit !== undefined) {\n await this.delivery.standaloneReply(msg, buildRenewCard({\n previousSessionId: renewAdmission.commit.previous.sessionId,\n currentSessionId: renewAdmission.commit.current.sessionId,\n workspacePath: renewAdmission.commit.current.workspacePath,\n }))\n return\n }\n if (spec.name === 'health' && result.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildHealthCard(this.healthSnapshot()))\n return\n }\n if (spec.name === 'status' && result.kind === 'success') {\n const session = live.agent.session\n const stats = foldSessionStats(session, this.projectionRegistry())\n const context = await measureSessionContext(\n session, this.commandContextServices(), stats.lastHeader,\n )\n await this.delivery.standaloneReply(msg, buildSessionStatusCard({\n sessionId: current.sessionId,\n stats,\n context,\n extras: {\n permissionPreset: this.permissionPresetOf(session),\n workspacePath: current.workspacePath,\n uptimeMs: this.startedAt === undefined ? undefined : Date.now() - this.startedAt,\n liveAgents: this.sessions.liveCount(),\n routeCount: this.routes.size(),\n },\n }))\n return\n }\n if (spec.name === 'workspace' && result.kind === 'success') {\n const resolved = resolveWorkDir(live.agent.session.header.cwd, current.workspacePath)\n if (resolved.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildWorkDirCard(current.sessionId, resolved.path))\n return\n }\n if (resolved.kind === 'mismatch') {\n this.ctx.logger?.warn?.(\n 'dsh-channels: workspace mismatch for session %s (paths redacted)', current.sessionId,\n )\n }\n await this.delivery.commandCard(msg, {\n kind: 'unavailable', command: spec.name,\n text: resolved.text, sessionId: current.sessionId,\n })\n return\n }\n if (spec.name === 'history' && result.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildSessionHistoryCard(\n current, await this.sessionHistoryAnnotations(current),\n ))\n return\n }\n if (spec.name === 'export' && result.kind === 'success') {\n try {\n const file = await this.sessionExportBuilder(live.agent.session)\n await this.delivery.requireChannel().send(msg.chatId, { file }, {\n replyTo: msg.messageId,\n replyInThread: true,\n })\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: logical export delivery failed for session %s: %s',\n current.sessionId, String(error),\n )\n await this.delivery.commandCard(msg, {\n kind: 'internal-error', command: spec.name,\n text: 'Session 逻辑日志导出失败,未交付文件;请稍后重试并查看 Harness 日志。',\n sessionId: current.sessionId,\n })\n }\n return\n }\n if (spec.name === 'compact' && compactBefore !== undefined) {\n const compactAfter = await this.measureCommandContext(live.agent.session)\n if (result.kind === 'success') {\n await this.delivery.standaloneReply(msg, buildCompactComparisonCard({\n sessionId: current.sessionId,\n resultText: result.text ?? '命令已执行。',\n before: compactBefore,\n after: compactAfter,\n }))\n return\n }\n await this.delivery.commandCard(msg, {\n kind: commandResultCardKind(result), command: spec.name,\n text: `${result.text}\\n\\n${compactFailureContext(compactBefore, compactAfter)}`,\n sessionId: current.sessionId,\n })\n return\n }\n await this.delivery.commandCard(msg, {\n kind: commandResultCardKind(result), command: spec.name,\n text: result.text ?? '命令已执行。', sessionId: current.sessionId,\n })\n }\n\n /**\n * Annotate a route's session history with host-query facts: the latest\n * log-backed title per session and whether it is currently live. Both are\n * best-effort — a missing or failing `sessionQuery` only drops the titles.\n */\n private async sessionHistoryAnnotations(record: RouteRecord): Promise<Record<string, SessionHistoryAnnotation>> {\n const ids = normalizeSessionHistory(record)\n .slice(-SESSION_HISTORY_DISPLAY_LIMIT)\n .map(entry => entry.sessionId)\n const annotations: Record<string, SessionHistoryAnnotation> = {}\n for (const id of ids) {\n annotations[id] = { live: this.sessions.get(id) !== undefined || this.ctx.agents.get(id as SessionId) !== undefined }\n }\n const query = this.ctx.get?.('sessionQuery') as SessionQuery | undefined\n if (query === undefined) return annotations\n try {\n const results = await query.readTitleSnapshots(ids as SessionId[])\n for (const item of results) {\n if (item.status !== 'fulfilled' || item.value.title === undefined) continue\n annotations[item.sessionId] = { ...annotations[item.sessionId], title: item.value.title.title }\n }\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: session title lookup failed: %s', String(error))\n }\n return annotations\n }\n\n /** Effective permission preset of one Session, when the service is mounted. */\n private permissionPresetOf(session: Session): string | undefined {\n const presets = this.ctx.get?.('permissionPresets') as PermissionPresets | undefined\n try {\n return presets?.current(session)\n } catch {\n return undefined\n }\n }\n\n /** Optional diagnostic services resolved without topology-sensitive property access. */\n private commandContextServices(): ContextServices {\n return {\n tokenMeter: this.ctx.get?.('tokenMeter') as TokenMeter | undefined,\n llm: this.ctx.get?.('llm') as LlmService | undefined,\n }\n }\n\n /** Exact command-time measurement shared by `/status` and `/compact`. */\n private measureCommandContext(session: Session): Promise<ContextMeasurement> {\n return measureSessionContext(session, this.commandContextServices())\n }\n\n /** Capture aggregate health only; raw configuration and errors are excluded. */\n private healthSnapshot(): HealthSnapshot {\n const serviceKeys: Record<HealthDependencyName, string> = {\n agents: 'agents',\n commands: 'commands',\n workspace: 'workspaceRegistry',\n storage: 'storageDomain',\n credentials: 'credentials',\n llm: 'llm',\n 'token-meter': 'tokenMeter',\n }\n const directServices: Record<HealthDependencyName, () => unknown> = {\n agents: () => this.ctx.agents,\n commands: () => this.ctx.commands,\n workspace: () => this.ctx.workspaceRegistry,\n storage: () => this.ctx.storageDomain,\n credentials: () => this.ctx.credentials,\n llm: () => this.ctx.llm,\n 'token-meter': () => this.ctx.tokenMeter,\n }\n const dependencies = Object.fromEntries(HEALTH_DEPENDENCY_NAMES.map(name => {\n try {\n const service = this.ctx.get === undefined\n ? directServices[name]()\n : this.ctx.get(serviceKeys[name])\n return [name, service !== undefined]\n } catch {\n return [name, false]\n }\n })) as Record<HealthDependencyName, boolean>\n\n const fibers: CordisFiber[] = []\n try {\n for (const runtime of this.ctx.registry.values()) {\n for (const fiber of runtime.fibers) fibers.push(fiber)\n }\n } catch {\n // Registry inspection is diagnostic only. An empty fold is safer than\n // exposing or propagating an internal registry error to the user.\n }\n\n const inflightTurnCount = this.repair.count()\n\n let connection\n let botOpenId: string | undefined\n try { connection = this.channel?.getConnectionStatus() } catch {}\n try { botOpenId = this.channel?.botOpenId() } catch {}\n\n return foldHealthSnapshot({\n fibers,\n dependencies,\n connection,\n botOpenId,\n started: this.started,\n routeCount: this.routes.size(),\n liveAgentCount: this.sessions.liveCount(),\n inflightTurnCount,\n })\n }\n\n\n\n\n\n\n\n\n\n\n\n /**\n * GC pass over the routing domain. Two independent halves:\n *\n * 1. Dedup records (unconditional): `processed` records and in-process\n * guard entries older than `PROCESSED_RETENTION_MS` are dropped —\n * redelivery windows are minutes, so a day of retention is ample and\n * keeps the table bounded regardless of the route TTL setting.\n * 2. Route/inflight GC (`routeTtlMs > 0`): thread routes idle past the TTL\n * are retired into minimal persistent tombstones before their active\n * records are deleted. A later message in that visible topic is then\n * rejected instead of silently creating a context-free Session. Degraded\n * chat-scoped routes have no topic boundary and are deleted normally.\n * Stale in-flight snapshots are deleted without surprising the user with\n * a repair of an ancient card. Sessions and workspaces remain host-owned.\n *\n * Per-key failures are logged and skipped; concurrent writes are safe\n * because `entries()` snapshots.\n */\n private async gcExpiredEntries(): Promise<void> {\n const now = Date.now()\n const processedCutoff = now - PROCESSED_RETENTION_MS\n let removedRoutes = 0\n let retiredThreadRoutes = 0\n let removedInflight = 0\n let removedProcessed = 0\n\n const processed = this.domain?.processed\n if (processed !== undefined) {\n for (const [key, record] of [...processed.entries()]) {\n if (record.processedAt >= processedCutoff) continue\n try {\n await processed.delete(key)\n removedProcessed++\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: GC of processed record %s failed: %s', key, String(error))\n }\n }\n }\n this.inbound.pruneAccepted(processedCutoff)\n\n const ttlMs = this.config.routeTtlMs\n if (ttlMs === 0) {\n if (removedProcessed > 0) {\n this.ctx.logger?.info?.('dsh-channels: record GC removed %d dedup record(s)', removedProcessed)\n }\n return\n }\n const cutoff = now - ttlMs\n\n const routes = this.domain?.routes\n if (routes !== undefined) {\n for (const [key, record] of [...routes.entries()]) {\n if (record.lastActivityAt >= cutoff) continue\n try {\n if (record.kind === 'thread') {\n await this.routes.expireThread(key, now)\n retiredThreadRoutes++\n } else {\n await this.routes.remove(key)\n }\n removedRoutes++\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: GC of route %s failed: %s', key, String(error))\n }\n }\n }\n\n const inflight = this.domain?.inflight\n if (inflight !== undefined) {\n for (const [key, snapshot] of [...inflight.entries()]) {\n if (snapshot.startedAt >= cutoff) continue\n try {\n await inflight.delete(key)\n removedInflight++\n } catch (error) {\n this.ctx.logger?.warn?.('dsh-channels: GC of inflight snapshot %s failed: %s', key, String(error))\n }\n }\n }\n\n if (removedRoutes > 0 || removedInflight > 0 || removedProcessed > 0) {\n this.ctx.logger?.info?.(\n 'dsh-channels: record GC removed %d route(s) (%d thread tombstone(s)), %d stale snapshot(s), %d dedup record(s)',\n removedRoutes, retiredThreadRoutes, removedInflight, removedProcessed,\n )\n }\n }\n\n\n /**\n * HITL answerer hook: the Feishu placement of one session's approval cards,\n * or `undefined` when this channel does not route the session (the answerer\n * then delegates to other answerers).\n */\n private approvalRouteFor(sessionId: string): ApprovalRoute | undefined {\n const record = this.routes.findBySession(sessionId)\n if (record === undefined) return undefined\n return {\n chatId: record.chatId,\n cardMessageId: record.cardMessageId,\n inThread: record.kind === 'thread',\n senderOpenId: record.senderOpenId,\n }\n }\n\n /**\n * Send one body-segment continuation card into the session's topic (or, for\n * degraded chat-scoped routes, as a plain reply anchored to the main card).\n * When Feishu rejects the card (e.g. per-card table limit, error 11310) the\n * segment is re-delivered as a plain-text message so the content is never\n * lost — text messages carry no card constraints.\n */\n /** Authorized Session-level `/model` view/switch operation. */\n private async runModelCommand(\n invocation: CommandInvocation,\n rawInput: string,\n admission: ModelAdmission,\n ): Promise<CommandResult> {\n if (admission.live.agent !== invocation.agent) {\n return { kind: 'error', text: '/model 只能通过已授权的飞书话题执行。' }\n }\n const session = invocation.agent.session\n const state = admission.controller.state\n const header = session.requestHeader?.()?.config\n const currentProvider = state.effectiveModel?.provider\n ?? (typeof header?.provider === 'string' && header.provider !== '' ? header.provider : undefined)\n const currentModel = state.effectiveModel?.model\n ?? (typeof header?.model === 'string' && header.model !== '' ? header.model : undefined)\n const source = state.modelSource === 'session'\n ? 'Session 覆盖'\n : state.modelSource === 'config' ? '配置默认' : '部署默认'\n\n if (rawInput === '') {\n const lines = [\n `**当前模型**:${currentModel === undefined ? '尚无模型请求记录' : `\\`${currentModel}\\``}`\n + `${currentProvider === undefined ? '' : `(provider \\`${currentProvider}\\`)`} · ${source}`,\n ]\n const models = await this.listModels(currentProvider)\n if (models.length > 0) {\n lines.push(`**可选模型(${models.length})**:${models.slice(0, 20).map(id => `\\`${id}\\``).join('、')}`\n + (models.length > 20 ? ' …' : ''))\n } else if (currentProvider === undefined) {\n lines.push('当前还没有模型请求记录;可先用 `/model <provider>/<model>` 指定,或先发一条普通消息。')\n }\n lines.push('用法:`/model <模型-id>`(沿用当前 provider)· `/model <provider>/<model>` · `/model reset`')\n return { kind: 'success', text: lines.join('\\n') }\n }\n\n if (rawInput === 'reset') {\n return { kind: 'success', text: '已恢复部署默认模型(下一条消息生效)。' }\n }\n\n const slash = rawInput.indexOf('/')\n const provider = slash === -1 ? currentProvider : rawInput.slice(0, slash)\n const model = slash === -1 ? rawInput : rawInput.slice(slash + 1)\n if (model === '' || (slash !== -1 && (provider === undefined || provider === ''))) {\n return { kind: 'error', text: '用法错误:/model [模型-id|provider/model|reset]。' }\n }\n // Validate against the advertised catalog when the deployment exposes one;\n // an unsupported value would otherwise only fail at the next request.\n if (provider !== undefined) {\n const models = await this.listModels(provider)\n if (models.length > 0 && !models.includes(model)) {\n return {\n kind: 'error',\n text: `provider \\`${provider}\\` 未提供模型 \\`${model}\\`。可选:${models.slice(0, 20).map(id => `\\`${id}\\``).join('、')}`,\n }\n }\n }\n const label = provider === undefined ? model : `${provider}/${model}`\n return { kind: 'success', text: `当前 Session 模型已切换为 \\`${label}\\`(下一条消息生效)。` }\n }\n\n /** Model ids advertised for one provider route (empty when unavailable). */\n private async listModels(provider: string | undefined): Promise<string[]> {\n if (provider === undefined) return []\n try {\n const llm = this.ctx.get?.('llm') as LlmService | undefined\n const models = await llm?.listModels?.(provider)\n return (models ?? []).map(entry => entry.id ?? entry.name ?? '').filter(id => id !== '')\n } catch {\n return []\n }\n }\n\n /**\n * Agent-scoped `/renew` handler. Only the Feishu admission path arms its\n * AbortSignal, preventing another Host client from switching a Feishu route\n * by invoking the scoped command out of band.\n */\n private async runRenewCommand(\n invocation: CommandInvocation,\n admission: RenewAdmission,\n ): Promise<CommandResult> {\n if (admission.live.agent !== invocation.agent) {\n return { kind: 'error', text: '/renew 只能通过已授权的飞书话题执行。' }\n }\n const current = this.routes.get(admission.route.key)\n if (current === undefined || current.sessionId !== admission.route.sessionId) {\n return { kind: 'error', text: '当前话题绑定已变化,请重试 /renew。' }\n }\n try {\n admission.commit = await this.sessions.renew(current, admission.live)\n return { kind: 'success', text: '新 Session 已创建并绑定。' }\n } catch (error) {\n this.ctx.logger?.warn?.(\n 'dsh-channels: renew transaction failed for session %s: %s',\n current.sessionId, String(error),\n )\n return { kind: 'error', text: 'Session 续建失败,原 Session 仍保持绑定;请稍后重试。' }\n }\n }\n\n /** Authorized Session-level `/effort` view/switch operation. */\n private runEffortCommand(\n invocation: CommandInvocation,\n action: EffortCommandAction,\n admission: EffortAdmission,\n ): CommandResult {\n if (admission.live.agent !== invocation.agent) {\n return { kind: 'error', text: '/effort 只能通过已授权的飞书话题执行。' }\n }\n const state = admission.controller.state\n if (action.kind === 'show') {\n if (state.effortSource === 'session') {\n return { kind: 'success', text: `当前 Session 推理强度:\\`${state.effectiveEffort}\\`(Session 覆盖)。` }\n }\n if (state.effortSource === 'config') {\n return { kind: 'success', text: `当前 Session 推理强度:\\`${state.effectiveEffort}\\`(配置默认)。` }\n }\n return { kind: 'success', text: '当前 Session 未设置推理强度覆盖,使用模型适配器/provider 默认。' }\n }\n if (action.kind === 'reset') {\n return state.configuredEffort === undefined\n ? { kind: 'success', text: '已恢复模型适配器/provider 默认推理强度。' }\n : { kind: 'success', text: `已恢复配置默认推理强度:\\`${state.configuredEffort}\\`。` }\n }\n return { kind: 'success', text: `当前 Session 推理强度已切换为:\\`${action.value}\\`。` }\n }\n\n // ---- outbound helpers ------------------------------------------------\n\n /** Context pressure for the card's second info line (synchronous reads only). */\n private measureContext(session: Session): { contextTokens?: number; contextWindow?: number } {\n let contextTokens: number | undefined\n try {\n // Optional peers are read through ctx.get (safe lookup); direct property\n // access on a cordis context throws for services the plugin did not inject.\n const meter = this.ctx.get?.('tokenMeter') as TokenMeter | undefined\n contextTokens = meter?.measure(session).totalTokens\n } catch {\n contextTokens = undefined\n }\n\n // The route-capacity window is published by the host's `contextPressure`\n // projection; the old request/header scan + resolveModelInfo call stays as\n // a fallback for deployments without the projection registry.\n let contextWindow = readProjectionViews(this.projectionRegistry(), session).contextPressure?.contextWindow\n if (contextWindow === undefined) {\n try {\n const header = session.requestHeader?.()?.config\n const provider = typeof header?.provider === 'string' ? header.provider : undefined\n const model = typeof header?.model === 'string' ? header.model : undefined\n if (provider !== undefined && provider !== '' && model !== undefined && model !== '') {\n const routeKey = `${provider}/${model}`\n contextWindow = this.contextWindows.get(routeKey)\n if (contextWindow === undefined && !this.windowRequests.has(routeKey)) {\n this.windowRequests.add(routeKey)\n const llm = this.ctx.get?.('llm') as LlmService | undefined\n void llm?.resolveModelInfo(provider, model)\n .then(info => {\n if (info?.context?.contextWindow !== undefined) {\n this.contextWindows.set(routeKey, info.context.contextWindow)\n }\n })\n .catch(() => {})\n }\n }\n } catch {\n contextWindow = undefined\n }\n }\n\n return { contextTokens, contextWindow }\n }\n\n /** Host projection registry, when this deployment mounts one. */\n private projectionRegistry(): SessionProjections | undefined {\n return this.ctx.get?.('sessionProjections') as SessionProjections | undefined\n }\n\n /** Card figures from host projections (token totals / cache hit / window). */\n private projectCardStats(session: Session): ProjectedStats | undefined {\n const views = readProjectionViews(this.projectionRegistry(), session)\n const usage = views.tokenUsage\n const pressure = views.contextPressure\n if (usage === undefined && pressure === undefined) return undefined\n const last = usage?.last?.buckets\n const hit = last === undefined ? undefined : last.cacheReadTokens\n const uncached = last === undefined ? undefined : last.uncachedInputTokens\n const cacheHit = hit !== undefined && uncached !== undefined && hit + uncached > 0\n ? hit / (hit + uncached)\n : undefined\n return {\n ...(usage === undefined ? {} : {\n inputTokens: usage.totals.uncachedInputTokens,\n outputTokens: usage.totals.outputTokens,\n }),\n ...(cacheHit === undefined ? {} : { cacheHitPct: cacheHit }),\n ...(pressure?.contextWindow === undefined ? {} : { contextWindow: pressure.contextWindow }),\n }\n }\n}\n","/**\n * Plugin configuration: schema, defaults, and credential-reference validation.\n *\n * `appId` / `appSecret` are NOT secrets themselves — they are credential\n * *references* (POSIX shell identifiers such as `FEISHU_APP_ID`), resolved at\n * startup through the host's `ctx.credentials` seam, which layers\n * `$DSH_HOME/.credentials.yaml` (the managed, writable store) between the\n * process environment and `.env` fallbacks — the same seam the DeepSeek\n * adapter uses for `DEEPSEEK_API_KEY`. Secrets therefore never sit in the\n * composition file.\n *\n * @module dsh-channels/config\n */\n\nimport { z } from 'zod'\nimport { WORKSPACE_RETENTION_VALUES } from './cleanup.ts'\n\nexport const FEISHU_LOGGER_LEVELS = ['debug', 'info', 'warn', 'error'] as const\nexport type FeishuLoggerLevel = (typeof FEISHU_LOGGER_LEVELS)[number]\n\n/** Same shape as the host credentials seam's `CredentialRef` (shell identifier). */\nexport const CREDENTIAL_REF_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/\n\n/** Conventional credential-reference names used when the loader passes no config. */\nexport const DEFAULT_APP_ID_REF = 'FEISHU_APP_ID'\nexport const DEFAULT_APP_SECRET_REF = 'FEISHU_APP_SECRET'\n\n/** Default route-table GC threshold: 30 days without activity. */\nexport const DEFAULT_ROUTE_TTL_MS = 30 * 24 * 60 * 60 * 1000\n\n/** Default HITL approval decision window: 5 minutes per question. */\nexport const DEFAULT_APPROVAL_TIMEOUT_MS = 5 * 60 * 1000\n\n/** Default structured-question window: 5 minutes per `ask_user_question` call. */\nexport const DEFAULT_QUESTION_TIMEOUT_MS = 5 * 60 * 1000\n\n/** A credential reference: `FEISHU_APP_ID`-style name resolved through ctx.credentials. */\nconst credentialRefSchema = z.string().regex(\n CREDENTIAL_REF_PATTERN,\n 'must be a credential reference name such as FEISHU_APP_ID (resolved through the dsh credentials seam, e.g. $DSH_HOME/.credentials.yaml) — plaintext secrets are not accepted',\n)\n\nexport const configSchema = z.object({\n /** Feishu app id credential reference (defaults to `FEISHU_APP_ID`). */\n appId: credentialRefSchema.default(DEFAULT_APP_ID_REF),\n /** Feishu app secret credential reference (defaults to `FEISHU_APP_SECRET`). */\n appSecret: credentialRefSchema.default(DEFAULT_APP_SECRET_REF),\n /**\n * Root directory for per-(user, chat) workspace directories — the filesystem\n * root where files produced by channel sessions land. Read per session\n * establishment, so editing it only affects sessions created afterwards\n * (existing workspaces are never migrated). The Web settings panel may\n * override it, but only with an absolute path (`~` expanded) — the relative\n * default below is kept for profile-level compatibility and resolves\n * against the host process cwd.\n */\n baseDir: z.string().min(1).default('./channels-data'),\n /**\n * Optional path of the Web settings overlay (default\n * `$DSH_HOME/dsh-channels/settings.json`). The Web panel writes only\n * editable fields there; this path is for embedded hosts, tests, or users\n * who want multiple instances with different overlays.\n */\n settingsFile: z.string().min(1).optional(),\n /** LLM provider route for sessions this plugin creates (optional → deployment default). */\n provider: z.string().optional(),\n /** Model id for sessions this plugin creates (optional → deployment default). */\n model: z.string().optional(),\n /** Exact model-advertised reasoning-effort id (optional → adapter/provider default). */\n reasoningEffort: z.string().min(1).optional(),\n /** Output-token cap per model request (optional → adapter default). */\n maxTokens: z.number().int().positive().optional(),\n /**\n * Agent preset id sessions this plugin creates are composed from\n * (optional → the roster default, e.g. `standard`). In web-profile\n * deployments the model-facing tools exist only inside agent presets, so\n * leaving this unset mounts the default; name a leaner preset (e.g. one\n * with only bash/fs) to restrict what the bot can do.\n */\n agentPreset: z.string().optional(),\n /** Streaming card refresh interval in milliseconds. */\n refreshMs: z.number().int().min(200).max(10_000).default(500),\n /**\n * Keep-alive interval for the card's activity status line (tool calls,\n * subagent / workflow progress). While a turn produces no text deltas, the\n * line's elapsed counter is re-rendered at this cadence so the card never\n * sits silently. Min 1 s keeps Feishu card-update traffic modest.\n */\n statusRefreshMs: z.number().int().min(1_000).max(60_000).default(5_000),\n /** Accept p2p (private) chats. */\n dmMode: z.enum(['open', 'disabled']).default('open'),\n /**\n * In normal (non-topic) groups, require an @-mention of the bot to start a\n * session. Messages inside a topic the bot already owns never require a\n * mention. Disable to answer every group message.\n */\n requireMention: z.boolean().default(true),\n /** SDK logger level. */\n loggerLevel: z.enum(FEISHU_LOGGER_LEVELS).default('info'),\n /**\n * Reply-in-thread fallbacks. `degrade` (default) falls back to a chat-scoped\n * conversation when Feishu refuses a topic (p2p / topic-less groups);\n * `strict` fails the reply instead.\n */\n threadFallback: z.enum(['degrade', 'strict']).default('degrade'),\n /**\n * Route-table GC threshold. Route records (and stale in-flight repair\n * snapshots) whose last activity is older than this many milliseconds are\n * retired at startup and on every periodic sweep. Real thread routes leave a\n * minimal persistent tombstone, so a later message in the visibly old topic\n * is rejected instead of silently creating a context-free Session. Degraded\n * chat-scoped routes are deleted normally. The DSH session and workspace\n * stay untouched. `0` disables route retirement entirely.\n */\n routeTtlMs: z.number().int().min(0).default(DEFAULT_ROUTE_TTL_MS),\n /**\n * HITL approval decision window: how long one approval decision card waits\n * for an 允许/拒绝 click before failing closed (`rejected`; the card is\n * patched to 已超时). The pending turn is blocked while the question is\n * open, so the window trades user think-time against agent stall time.\n */\n approvalTimeoutMs: z.number().int().min(10_000).max(3_600_000).default(DEFAULT_APPROVAL_TIMEOUT_MS),\n /**\n * Accept inbound Feishu images, files, and merged-forward chat records and\n * save them below `<workspace>/attachments/`. Nothing is forwarded to the\n * model automatically: the Agent reads whichever attachment the\n * conversation needs with its ordinary filesystem tools. Disabled keeps\n * attachment messages inert (no save, no turn).\n */\n acceptAttachments: z.boolean().default(true),\n /**\n * Delete the workspace directories of stale conversations. `off` (default)\n * never deletes; `7d`/`30d`/`90d`/`180d` remove plugin-created workspaces\n * whose last activity is older than the threshold. The managed Web panel\n * exposes this as a select, previews the candidates, and can run one\n * cleanup manually. DSH Session logs are never deleted.\n */\n workspaceRetention: z.enum(WORKSPACE_RETENTION_VALUES).default('off'),\n /**\n * Run the workspace-retention cleanup at startup and once per day. Off by\n * default: deletion is irreversible, so it stays an explicit opt-in even\n * when a retention threshold is configured.\n */\n workspaceRetentionAuto: z.boolean().default(false),\n /**\n * Send a one-time onboarding card on the first private message of a chat\n * (facts + command cheat sheet). Persisted per chat; never repeats.\n */\n welcomeOnboarding: z.boolean().default(true),\n /**\n * Allow `/permissions <name>` to switch this Session's sandbox + approval\n * preset from Feishu. Read-only `/permissions` always works; switching is\n * security-sensitive (it can widen the sandbox), so it is off by default.\n */\n allowPermissionSwitch: z.boolean().default(false),\n /**\n * Admission policy for inbound Feishu traffic.\n *\n * - `guarded` (default): only the configured administrator can talk in\n * private chats, and a group conversation additionally requires the group\n * to be authorized and the sender to be the administrator or an explicitly\n * authorized user.\n * - `open`: legacy behavior — any user inside the app's availability scope\n * can start a conversation. Debug/compatibility only; unsafe.\n */\n accessMode: z.enum(['open', 'guarded']).default('guarded'),\n /**\n * The unique administrator's Feishu open_id (`ou_...`). Set it in the Web\n * settings panel (or the profile config). Get it by sending `/userid` to the\n * bot in a private chat. While `accessMode` is `guarded` and this is unset,\n * every conversation is refused (fail closed); `/userid` and a fixed notice\n * stay available so the deployment can be bootstrapped.\n */\n adminOpenId: z.string().regex(\n /^ou_[A-Za-z0-9_-]+$/,\n 'must be a Feishu open_id such as ou_xxx (send /userid to the bot in a private chat to see it)',\n ).optional(),\n /**\n * Lifetime of one pending authorization request and of the DM card that asks\n * the administrator to approve a group or a user. Expired requests stay in\n * the store as an audit trail but can no longer be decided.\n */\n authRequestTtlDays: z.number().int().min(1).max(30).default(7),\n /**\n * Structured-question decision window for the model's `ask_user_question`\n * tool (`user-questions/request`). A blocking ask that gets no answer in\n * this window fails closed with an explicit error; a host *timed* wait owns\n * its own deadline and is left to the host signal instead.\n */\n questionTimeoutMs: z.number().int().min(10_000).max(3_600_000).default(DEFAULT_QUESTION_TIMEOUT_MS),\n})\n\nexport type FeishuConfig = z.infer<typeof configSchema>\n\n/**\n * DSH loader-facing schema. The bundle patch inserts this plugin without a\n * `config` object; the loader validates that row with `undefined` before the\n * user's patch layer supplies values. Accept that absence and fill the same\n * defaults as {@link resolveConfig}, so enabling the plugin from the Web UI\n * cannot fail with \"expected object, received undefined\".\n */\nexport const pluginConfigSchema = configSchema.prefault({})\n\n/**\n * Validate the raw loader config. Values stay credential *references*; the\n * secrets themselves resolve through `ctx.credentials` at startup.\n * @param raw - the config object the cordis loader passed (may be undefined).\n * @returns the validated config.\n */\nexport function resolveConfig(raw: unknown): FeishuConfig {\n const parsed = configSchema.safeParse(raw ?? {})\n if (!parsed.success) {\n const details = parsed.error.issues\n .map(issue => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`)\n .join('\\n')\n throw new Error(`dsh-channels: invalid configuration:\\n${details}`)\n }\n return parsed.data\n}\n","/**\n * Web 设置面板的线上契约:Host 半侧的路由/面板数据与 Browser 半侧的渲染共用。\n *\n * 本模块**没有运行时依赖**:Host 与 Client bundle 都直接内联它。因此这里只放\n * 常量、字段清单与可序列化的 wire 类型,不 import zod / node / DSH 内部包。\n *\n * @module dsh-channels/settings/contract\n */\n\n/** Same-origin routes the settings panel talks to. */\nexport const STATUS_ROUTE = '/dsh-channels/status'\nexport const PANEL_ROUTE = '/dsh-channels/panel'\nexport const CONFIG_ROUTE = '/dsh-channels/config'\nexport const CREDENTIALS_ROUTE = '/dsh-channels/credentials'\n/**\n * Connection lifecycle action (POST). While the channel runs it restarts the\n * long connection; while it is stopped it opens one — so the panel has exactly\n * one \"connect / reconnect\" button. Credentials are resolved again either way.\n */\nexport const RECONNECT_ROUTE = '/dsh-channels/reconnect'\n/** 模型/推理档位/预设选择器的宿主目录(provider → model → effort 级联刷新)。 */\nexport const CATALOG_ROUTE = '/dsh-channels/catalog'\n/** 手动执行一次过期工作区清理(POST,无请求体)。 */\nexport const CLEANUP_ROUTE = '/dsh-channels/cleanup'\n/** 访问与授权只读快照(GET/HEAD):准入模式、管理员、群/用户白名单、待处理申请。 */\nexport const ACCESS_ROUTE = '/dsh-channels/access'\n/** 群白名单增删(POST/DELETE,body `{ chatId, name? }` / `{ chatId }`)。 */\nexport const ACCESS_GROUPS_ROUTE = '/dsh-channels/access/groups'\n/** 用户白名单增删(POST/DELETE,body `{ openId, name? }` / `{ openId }`)。 */\nexport const ACCESS_USERS_ROUTE = '/dsh-channels/access/users'\n/** 结算一条待处理授权申请(POST,body `{ token, decision }`)。 */\nexport const ACCESS_REQUESTS_ROUTE = '/dsh-channels/access/requests'\n/**\n * 打开宿主机的系统目录选择框(POST,无请求体),返回绝对路径。\n *\n * 只有宿主挂载了 `directoryPicker` 的 native 后端(桌面/本地 GUI 会话)才\n * 会注册实际选择逻辑;远程/无头会话的 browse 后端不可用,面板不下发\n * [`EditableFieldWire.browse`],保留手写输入。\n */\nexport const PICK_DIRECTORY_ROUTE = '/dsh-channels/pick-directory'\n\n/**\n * 可由 Web 面板覆盖的配置项。全部是 `src/config.ts` 里已存在的字段;\n * 覆盖值写进 `$DSH_HOME/dsh-channels/settings.json`,优先级高于 profile\n * 的 `cordis.patch.yml`(见 README「Web 设置面板」)。\n *\n * 刻意不包含 `appId` / `appSecret`:它们是**凭据引用名**,面板改为直接\n * 编辑引用背后的值(`PUT /dsh-channels/credentials`,经 DSH 凭据服务写入),\n * 因此不会落进覆盖层文件。\n *\n * `baseDir`(工作区根目录 = 模型/工具产生文件的落点)可覆盖,但面板只接受\n * **绝对路径**(`~` 会展开);已有 Session / 工作区不迁移,只影响之后新建的\n * 会话。校验与保存时建目录见 `src/workspaces.ts`。\n */\nexport const EDITABLE_KEYS = [\n // 工作区\n 'baseDir',\n 'workspaceRetention',\n 'workspaceRetentionAuto',\n // 行为\n 'dmMode',\n 'requireMention',\n 'threadFallback',\n 'welcomeOnboarding',\n 'allowPermissionSwitch',\n // 准入与授权(管理员/准入模式/申请有效期;白名单由「访问与授权」卡片管理)\n 'adminOpenId',\n 'accessMode',\n 'authRequestTtlDays',\n // 内容\n 'acceptAttachments',\n // 模型\n 'provider',\n 'model',\n 'reasoningEffort',\n 'agentPreset',\n // 流式卡片\n 'refreshMs',\n 'statusRefreshMs',\n 'loggerLevel',\n] as const\n\nexport type EditableKey = (typeof EDITABLE_KEYS)[number]\n\n/**\n * 只有重连飞书长连接后才生效的字段:它们在 `FeishuService.start()` 建连时\n * 被交给官方 SDK 或流式器,运行中修改不会影响已建立的连接。\n */\nexport const RESTART_KEYS = [\n 'dmMode',\n 'requireMention',\n 'refreshMs',\n 'statusRefreshMs',\n 'loggerLevel',\n] as const satisfies readonly EditableKey[]\n\nexport type RestartKey = (typeof RESTART_KEYS)[number]\n\n/** Render kinds the generic panel form understands. */\nexport type FieldKind = 'boolean' | 'number' | 'text' | 'select'\n\n/**\n * Field groups partition the editable form. Each group is rendered inside one\n * panel tab (see {@link TAB_FIELD_GROUPS}); the Host is the source of truth for\n * which key belongs to which group.\n */\nexport type FieldGroup = 'workspace' | 'behavior' | 'content' | 'model' | 'streaming' | 'access'\n\n/** One selectable value rendered by the generic form. */\nexport interface FieldOptionWire {\n value: string\n label: string\n}\n\n/** One editable row, fully described by the Host so the client stays schema-free. */\nexport interface EditableFieldWire {\n key: EditableKey\n label: string\n kind: FieldKind\n group: FieldGroup\n help?: string\n options?: readonly FieldOptionWire[]\n /** Selector is not editable yet (e.g. pick the provider before the model). */\n disabled?: boolean\n min?: number\n max?: number\n step?: number\n placeholder?: string\n /** Render across the whole form width (long paths). */\n wide?: boolean\n /**\n * `directory`:给这个 text 字段加一个「选择目录…」按钮,点击后由 Host\n * 打开宿主机系统目录选择框并把绝对路径写回草稿。仅当宿主提供 native\n * 目录选择器时下发;缺省(含 browse 后端)保持纯手写输入。\n */\n browse?: 'directory'\n /** 保存后需重连飞书通道才生效。 */\n restartRequired: boolean\n}\n\n/**\n * One selector group resolved from live host services. `selectable: false`\n * means the host could not enumerate values (service absent / lookup failed),\n * so the field falls back to the free-text control with the same key.\n */\nexport interface SelectCatalogWire {\n selectable: boolean\n /** Rendered in order; the leading `''` entry is the inherit/clear choice. */\n options: FieldOptionWire[]\n disabled?: boolean\n /** Host-side explanation shown instead of the static field help. */\n help?: string\n}\n\n/** `GET /dsh-channels/catalog` payload: the model-facing selector catalog. */\nexport interface CatalogWire {\n provider: SelectCatalogWire\n model: SelectCatalogWire\n reasoningEffort: SelectCatalogWire\n agentPreset: SelectCatalogWire\n /** Deployment default selection, when the host exposes `agentDefaultModel`. */\n deploymentDefault?: {\n provider?: string\n model?: string\n reasoningEffort?: string\n }\n /** Roster default preset id, when the host exposes the preset roster. */\n defaultPresetId?: string\n}\n\n/** Values the panel may submit. Empty strings for optional text fields clear the override. */\nexport type ConfigValue = string | number | boolean\nexport type EditableValues = Partial<Record<EditableKey, ConfigValue>>\n\n/** Read-only facts shown in the advanced card. */\nexport interface ReadOnlyFieldWire {\n key: string\n label: string\n value: string\n help?: string\n}\n\n/** One credential reference plus its Host-side state (values never cross the wire). */\nexport interface CredentialRefWire {\n ref: string\n configured: boolean\n /** false when the mounted credentials provider cannot store values. */\n writable: boolean\n /** Where the configured value comes from (e.g. `file`), when the provider reports it. */\n source?: string\n}\n\n/** The two credential slots the panel may write. */\nexport type CredentialField = 'appId' | 'appSecret'\n\n/** Official-SDK WebSocket lifecycle snapshot (structural copy, no SDK import on the wire). */\nexport interface ConnectionWire {\n state: string\n lastConnectTime?: number\n nextConnectTime?: number\n reconnectAttempts: number\n}\n\n/**\n * The panel-facing lifecycle phase, derived by the Host from configuration\n * readiness and runtime state. Enabling the plugin starts in `unconfigured`\n * (or `idle` when credentials already resolve) and never forces a connection\n * before the Feishu application credentials are complete.\n */\nexport type ChannelPhaseWire =\n /** Credentials are incomplete: the plugin is loaded but deliberately not connected. */\n | 'unconfigured'\n /** Credentials are complete, no connection is open yet. */\n | 'idle'\n /** A connection attempt / reconnect is in flight. */\n | 'connecting'\n /** The Feishu long connection is open. */\n | 'connected'\n /** The last connection attempt failed; the reason is in `lastStartError`. */\n | 'error'\n\n/** Runtime connection/liveness facts. */\nexport interface ChannelStatusWire {\n running: boolean\n /**\n * Whether the Feishu application credentials are complete. When false the\n * plugin stays loaded (settings panel usable) but opens no long connection.\n */\n configured: boolean\n /** Display phase derived from `configured` / `running` / `lastStartError`. */\n phase: ChannelPhaseWire\n /** Human-readable config items that are still missing (names only, never values). */\n missingConfig: string[]\n level: 'healthy' | 'degraded' | 'unhealthy'\n startedAt?: number\n uptimeMs?: number\n connection?: ConnectionWire\n botOpenIdTail?: string\n routeCount: number\n liveAgentCount: number\n inflightTurnCount: number\n /** 最近一次连接失败的原因(成功后清空)。 */\n lastStartError?: string\n /** 健康判定的原因说明(空数组 = 健康);由 Host 的 health 折叠给出。 */\n reasons: string[]\n /** 面板显示的所有键值对来源版本。 */\n version: string\n /** Web 覆盖文件绝对路径(用户可直接编辑)。 */\n overlayPath: string\n /** 正在连接或重连时为 true,此时面板的写操作按钮禁用。 */\n busy: boolean\n}\n\n/** The whole editable configuration surface, with provenance. */\nexport interface PanelConfigWire {\n /** profile `cordis.patch.yml` 提供的值(覆盖层之下)。 */\n base: Record<string, ConfigValue | undefined>\n /** 合并后的生效值。 */\n effective: Record<string, ConfigValue | undefined>\n /** 已写入 Web 覆盖层的值。 */\n overlay: EditableValues\n overriddenKeys: EditableKey[]\n /** 与当前运行中连接不一致、需重连才生效的字段。 */\n restartPendingKeys: EditableKey[]\n fields: EditableFieldWire[]\n readOnly: ReadOnlyFieldWire[]\n credentials: {\n appId: CredentialRefWire\n appSecret: CredentialRefWire\n }\n}\n\n/** One authorized group (the first of the two admission gates). */\nexport interface AccessGroupWire {\n chatId: string\n /** Human-readable group name captured at authorization time. */\n name?: string\n /** Which authorization entry produced the grant. */\n source: 'command' | 'card' | 'panel'\n /** Epoch ms when it was authorized. */\n addedAt: number\n}\n\n/** One authorized user `open_id` (global across every authorized group). */\nexport interface AccessUserWire {\n openId: string\n /** Display name captured at authorization time. */\n name?: string\n /** Which authorization entry produced the grant. */\n source: 'command' | 'card' | 'panel'\n /** Epoch ms when it was authorized. */\n addedAt: number\n}\n\n/** One pending authorization request the panel may settle (same path as the DM card). */\nexport interface AccessRequestWire {\n /** Opaque token; never derived from the target id. */\n token: string\n kind: 'group' | 'user'\n /** `chat_id` for a group request, `open_id` for a user request. */\n targetId: string\n /** Display name of the requested target (group name / user name), when known. */\n name?: string\n /** Chat the request originated from, when applicable. */\n chatId?: string\n /** Group name captured for the card. */\n groupName?: string\n /** Open_id of the user who triggered a user request. */\n requester?: string\n /** Epoch ms when the request was created. */\n createdAt: number\n /** Epoch ms after which the request can no longer be decided. */\n expiresAt: number\n}\n\n/**\n * `GET /dsh-channels/access` payload: admission mode, the administrator\n * reference, both whitelists, and the still-pending authorization requests.\n */\nexport interface AccessWire {\n mode: 'open' | 'guarded'\n admin: {\n openId?: string\n /** false while guarded mode would refuse every conversation (fail closed). */\n configured: boolean\n }\n /**\n * Whether the persisted whitelists were loaded. While the channel holds no\n * connection the plugin storage domain is closed, so the lists are unknown\n * (not empty) and the panel must not offer mutations.\n */\n loaded: boolean\n groups: AccessGroupWire[]\n users: AccessUserWire[]\n requests: AccessRequestWire[]\n}\n\n/** Write responses for every `/dsh-channels/access*` mutation. */\nexport type AccessMutationResponse =\n | { ok: true; access: AccessWire }\n | { ok: false; error: string }\n\n/** `GET /dsh-channels/panel` payload. */\nexport interface PanelWire {\n status: ChannelStatusWire\n config: PanelConfigWire\n cleanup: CleanupPreviewWire\n /** 访问与授权卡片的数据(准入模式、管理员、白名单、待处理申请)。 */\n access: AccessWire\n}\n\n/**\n * What the workspace-retention cleanup would delete right now. Counts are\n * zero and `error` is set when the Host cannot compute the preview (for\n * example the cleanup seam is unavailable); the panel stays usable.\n */\nexport interface CleanupPreviewWire {\n /** Effective `workspaceRetention` value (`off` / `7d` / `30d` / `90d` / `180d`). */\n retention: string\n /** Retention threshold in milliseconds; absent when `retention` is `off`. */\n thresholdMs?: number\n /** Periodic cleanup runs at startup and once per day. */\n auto: boolean\n /** Plugin-created workspaces under the current baseDir. */\n workspaces: number\n /** Workspaces older than the threshold (the deletable set). */\n candidates: number\n /** Total bytes currently occupied by the candidates. */\n candidateBytes: number\n /** Why the preview could not be computed (counts are zero then). */\n error?: string\n}\n\n/** Outcome of one manual cleanup run (partial failures do not abort the run). */\nexport interface CleanupRunWire {\n ok: boolean\n deletedWorkspaces: number\n retiredRoutes: number\n freedBytes: number\n /** One `path: message` line per workspace that could not be deleted. */\n failures: string[]\n error?: string\n}\n\n/** `POST /dsh-channels/cleanup` response. */\nexport type CleanupResponse =\n | { ok: true; panel: PanelWire; result: CleanupRunWire }\n | { ok: false; error: string }\n\n/**\n * `POST /dsh-channels/pick-directory` response. `canceled` is a normal\n * outcome (the operator dismissed the OS dialog), not an error; `path` is an\n * absolute directory the Host's native chooser returned.\n */\nexport type PickDirectoryResponse =\n | { ok: true; path: string }\n | { ok: true; canceled: true }\n | { ok: false; error: string }\n\n/** `PUT /dsh-channels/config` body. */\nexport interface SaveConfigRequest {\n values: EditableValues\n}\n\n/** Per-field validation failure. */\nexport interface FieldIssueWire {\n key: string\n message: string\n}\n\n/** `PUT /dsh-channels/config` response. */\nexport type SaveConfigResponse =\n | { ok: true; panel: PanelWire; clearedKeys: EditableKey[] }\n | { ok: false; error: string; issues?: FieldIssueWire[] }\n\n/** `POST /dsh-channels/reconnect` response. */\nexport type ReconnectResponse =\n | { ok: true; panel: PanelWire }\n /** `unconfigured` tells the panel to route the user to the credentials form. */\n | { ok: false; error: string; unconfigured?: boolean }\n\n/** `DELETE /dsh-channels/config` response. */\nexport type ClearConfigResponse =\n | { ok: true; panel: PanelWire }\n | { ok: false; error: string }\n\n/** `PUT /dsh-channels/credentials` body: only provided, non-empty values are written. */\nexport interface SaveCredentialsRequest {\n appId?: string\n appSecret?: string\n}\n\n/** `DELETE /dsh-channels/credentials` body. */\nexport interface ClearCredentialRequest {\n field: CredentialField\n}\n\n/**\n * How a saved credential reached the running channel.\n *\n * - `auto`: the channel was stopped and connected immediately after the save.\n * - `pending`: the channel is running; reconnect for the value to take effect.\n * - `not-needed`: removed while stopped, nothing to reconnect.\n * - `unconfigured`: saved, but the other credential of the pair is still\n * missing — the plugin keeps waiting (no connection attempt) until it is\n * configured.\n * - `failed`: the pair is complete and a connection was attempted, but it\n * failed; the value is stored and `autoReconnectError` explains why.\n */\nexport type CredentialReconnectState = 'auto' | 'pending' | 'not-needed' | 'unconfigured' | 'failed'\n\nexport interface CredentialsSavedPayload {\n panel: PanelWire\n reconnect: CredentialReconnectState\n /** Set when the automatic reconnect after a save failed (the value is still saved). */\n autoReconnectError?: string\n}\n\n/** Credential write/remove responses (values never appear in either direction). */\nexport type SaveCredentialsResponse =\n | ({ ok: true } & CredentialsSavedPayload)\n | { ok: false; error: string; issues?: FieldIssueWire[] }\n\nexport type ClearCredentialResponse =\n | ({ ok: true } & CredentialsSavedPayload)\n | { ok: false; error: string; issues?: FieldIssueWire[] }\n\n/**\n * Top-level panel tabs, in render order. The panel shows the connection state\n * header above every tab, so switching tabs never hides whether the bridge is\n * connected.\n */\nexport const PANEL_TABS = [\n { id: 'connection', label: '连接' },\n { id: 'sessions', label: '会话与行为' },\n { id: 'model', label: '模型' },\n { id: 'workspace', label: '工作区' },\n { id: 'access', label: '访问授权' },\n { id: 'advanced', label: '高级' },\n] as const\n\nexport type PanelTabId = (typeof PANEL_TABS)[number]['id']\n\n/** Which editable field groups each tab renders (the tabs own one or two). */\nexport const TAB_FIELD_GROUPS: Readonly<Record<PanelTabId, readonly FieldGroup[]>> = {\n connection: [],\n sessions: ['behavior', 'content'],\n model: ['model'],\n workspace: ['workspace'],\n access: ['access'],\n advanced: ['streaming'],\n}\n\n/** Section heading for one field group inside its tab. */\nexport const FIELD_GROUP_LABELS: Readonly<Record<FieldGroup, string>> = {\n workspace: '工作区与文件',\n behavior: '行为',\n content: '内容与附件',\n model: '模型与推理',\n streaming: '流式卡片与日志',\n access: '准入与授权',\n}\n\n/** Display labels for the panel header/cards (kept on the wire so the client owns no copy). */\nexport const PANEL_COPY = {\n title: '飞书渠道',\n description: '飞书长连接、应用凭据与渠道配置。凭据只写入 DSH 凭据库且不回显;标注「需重连」的项在连接建立时读取。',\n // ---- lifecycle / status -------------------------------------------------\n statusCard: '连接状态',\n phaseUnconfigured: '待配置',\n phaseIdle: '未连接',\n phaseConnecting: '连接中',\n phaseConnected: '已连接',\n phaseError: '连接失败',\n configuredBadge: '凭据已配置',\n unconfiguredBadge: '凭据未配置',\n statusSummaryConnected: '飞书长连接已建立,机器人可以收发消息。',\n statusSummaryUnconfigured: '尚未配置飞书应用凭据:插件已加载,但不会连接飞书。填写凭据并保存后会自动连接。',\n statusSummaryIdle: '飞书应用凭据已就绪,但当前没有建立长连接。',\n statusSummaryConnecting: '正在连接飞书长连接…',\n statusSummaryError: '最近一次连接飞书失败,请检查凭据与网络后重试。',\n lastError: '最近一次连接失败',\n missingConfig: '缺少配置',\n statusHintUnconfigured: '配置完成后插件会自动连接;也可以稍后在本页手动连接。',\n connectionInfo: '连接信息',\n connectionStateLabel: '飞书长连接',\n botOpenId: 'Bot open_id',\n uptime: '运行时长',\n routeCount: '话题路由',\n liveAgentCount: '活动会话',\n inflightTurnCount: '在途回复',\n healthReasons: '健康判定',\n unknownValue: '—',\n connect: '连接飞书',\n connecting: '连接中…',\n // ---- onboarding ---------------------------------------------------------\n onboardingTitle: '开始使用飞书渠道',\n onboardingIntro: '只需三步:在飞书开放平台创建自建应用,把 App ID / App Secret 填入本页,保存后自动连接。',\n onboardingStep1Title: '创建飞书自建应用',\n onboardingStep1Text: '在飞书开放平台创建企业自建应用,开启机器人能力;事件订阅选择「长连接」,订阅 im.message.receive_v1(接收消息)。',\n onboardingStep2Title: '填写应用凭据',\n onboardingStep2Text: '把应用的 App ID(cli_ 开头)与 App Secret 填入下方「飞书应用凭据」,值会写入 DSH 凭据库。',\n onboardingStep3Title: '保存并等待连接',\n onboardingStep3Text: '两项凭据都保存后插件会自动连接;状态变为「已连接」即可在飞书里私聊机器人。',\n onboardingDocsHint: '开放平台地址:open.feishu.cn/app;应用需要开通 im:message、im:message:send_as_bot 等消息权限。',\n // ---- credentials --------------------------------------------------------\n credentialsCard: '飞书应用凭据',\n credentialsCardHelp: '凭据值经 DSH 凭据服务写入本机凭据库;面板只显示「已配置 / 未配置」,永不回显已存值。两项都配置后才会连接飞书。',\n appIdLabel: '飞书 App ID',\n appSecretLabel: '飞书 App Secret',\n credentialsRef: '凭据引用',\n saveCredentials: '保存凭据',\n savingCredentials: '保存中…',\n credentialSaved: '凭据已保存',\n credentialSavedReconnected: '凭据已保存,飞书已连接',\n credentialSavedPending: '凭据已保存,重启飞书连接后生效',\n credentialSavedUnconfigured: '凭据已保存;还缺少另一项凭据,补齐后会自动连接',\n credentialAutoConnectFailed: '凭据已保存,但自动连接失败',\n credentialRemoved: '已移除存储的凭据',\n credentialRemovedPending: '已移除存储的凭据,重启飞书连接后生效',\n removeCredential: '移除已存值',\n removingCredential: '移除中…',\n credentialConfigured: '已配置',\n credentialMissing: '未配置',\n credentialReadOnly: '当前凭据存储不可写',\n appIdPlaceholder: '输入 App ID(cli_…)',\n appSecretPlaceholder: '输入 App Secret',\n credentialsPendingHint: '填写后点「保存凭据」;两项齐全时插件会自动连接飞书。',\n // ---- tabs / forms -------------------------------------------------------\n connectionTabHelp: '连接状态与飞书应用凭据。凭据不完整时不会连接飞书。',\n sessionsTabHelp: '私聊、群聊触发方式与入站内容;「需重连」项在下次连接时读取。',\n modelTabHelp: '飞书会话使用的模型服务商、模型、推理档位与 Agent 预设;下拉选项来自宿主机实时目录,保存后对新会话生效。',\n workspaceTabHelp: '每个飞书话题一个独立工作区(模型/工具产生文件的落点);只影响之后新建的话题。',\n accessTabHelp: '准入采用 fail-closed:管理员不受白名单限制,非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。',\n advancedTabHelp: '流式刷新等低层参数、Web 覆盖文件与只读信息。',\n save: '保存更改',\n saving: '保存中…',\n saveHintDirty: '只保存改动过的字段;标注「需重连」的项在下次连接时生效。',\n discardTab: '放弃本页更改',\n saveHintClean: '没有未保存的更改。',\n dirtyCount: '未保存项',\n browseDirectory: '选择目录…',\n browsingDirectory: '选择中…',\n directoryPicked: '已选择目录',\n directoryPickFailed: '无法打开系统目录选择器',\n saved: '已保存',\n cleared: '已清除 Web 覆盖,恢复为 profile 配置',\n clear: '清除 Web 覆盖',\n clearing: '清除中…',\n noOverrides: '当前没有 Web 覆盖',\n overridden: 'Web 覆盖',\n inherited: '继承 profile',\n restartTag: '需重连',\n restartBanner: '以下配置需重启飞书连接后生效:',\n restartWarning: '重启会断开当前飞书长连接并中断进行中的回复,请确认空闲时操作。',\n reconnect: '重启飞书连接',\n reconnecting: '重连中…',\n refresh: '刷新状态',\n retry: '重试',\n pollFailed: '状态刷新失败',\n connectDone: '已连接飞书',\n reconnectDone: '已重启飞书连接',\n saveInvalid: '请先修正标红的配置项',\n enabled: '已启用',\n disabled: '已关闭',\n credentialRemoveConfirm: '确定移除已存储的凭据值?移除后不会回显,需要时须重新填写。',\n accessNotLoaded: '飞书连接建立后加载授权列表',\n loading: '加载中…',\n loadFailed: '设置面板加载失败',\n catalogFailed: '模型目录加载失败,已退回手填。',\n catalogLoading: '正在读取宿主机选项…',\n catalogUnavailable: '宿主机未提供可选项,请手填。',\n version: '插件版本',\n overlayPath: '覆盖文件',\n overlayPathHelp: '可以直接编辑该 JSON 文件;面板保存会整体重写它,「清除 Web 覆盖」会删除它并回落到 profile 配置。',\n advancedCard: '只读与高级信息',\n advancedHelp: '这些值来自 profile 配置,不能在面板里编辑。',\n readOnlyCard: '部署级参数',\n // ---- model --------------------------------------------------------------\n // ---- cleanup ------------------------------------------------------------\n cleanupCard: '工作区清理',\n cleanupCardHelp: '删除超过保留期且不再活跃的飞书工作区目录;只会删除本插件在「工作区根目录」下创建的目录,DSH 会话日志不删除。删除不可恢复:相关旧话题会失效并提示已过期,工作区列表记录同步移除。',\n cleanupRetention: '保留期',\n cleanupAutoLabel: '自动清理',\n cleanupCandidates: '可清理工作区',\n cleanupCandidateBytes: '预计释放空间',\n cleanupRetentionOff: '关闭(不删除)',\n cleanupAutoOn: '已开启(启动时 + 每天一次)',\n cleanupAutoOff: '已关闭(仅手动执行)',\n cleanupAutoInactive: '未执行(保留期为关闭)',\n cleanupNow: '立即清理',\n cleanupRunning: '清理中…',\n cleanupConfirm: '确定清理过期工作区吗?该操作不可恢复,会删除这些工作区内的全部文件(包括 attachments/ 附件)。',\n cleanupConfirmHint: '删除前会再次确认;保留期为「关闭」或没有过期工作区时按钮不可用。',\n cleanupDisabledOff: '保留策略为「关闭」:请先在上方「过期工作区保留期」中选择保留天数。',\n cleanupDisabledEmpty: '没有超过保留期的过期工作区。',\n cleanupPreviewFailed: '无法读取清理预览',\n cleanupDone: '清理完成',\n cleanupFailed: '清理失败',\n cleanupDeleted: '已删除工作区',\n cleanupRetired: '已清理话题路由',\n cleanupFreed: '释放空间',\n cleanupFailures: '以下工作区未能删除:',\n // ---- access -------------------------------------------------------------\n accessCard: '访问与授权',\n accessCardHelp: '准入采用 fail-closed:管理员不受白名单限制(私聊、任意群聊都可直接对话);非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。管理员与准入模式在上方「准入与授权」中编辑;私聊机器人发送 /userid 获取自己的 open_id。',\n accessModeOpenWarning: '当前准入模式为开放(open):应用可用范围内的任何人都能开启对话。该模式不安全,仅建议调试时使用。',\n accessAdminMissingWarning: '当前准入=guarded 但未配置管理员,所有对话都会被拒绝。请在上方「准入与授权」填写管理员 open_id。',\n accessGroupsTitle: '已授权群',\n accessGroupsEmpty: '尚未授权任何群。机器人被拉进群后,管理员会收到带 chat_id 的群信息卡;也可在群内发送 /groupid 获取。',\n accessUsersTitle: '已授权用户',\n accessUsersEmpty: '尚未授权任何用户。已授权用户可在所有已授权群里与机器人对话(私聊仍只开给管理员)。',\n accessRequestsTitle: '待处理申请',\n accessRequestsEmpty: '没有待处理的授权申请。',\n accessGroupChatIdPlaceholder: '群 chat_id(oc_…)',\n accessGroupNamePlaceholder: '群名(可选)',\n accessUserOpenIdPlaceholder: '用户 open_id(ou_…)',\n accessUserNamePlaceholder: '昵称(可选)',\n accessAddGroup: '添加群',\n accessAddUser: '添加用户',\n accessRemove: '移除',\n accessAllow: '授权',\n accessDeny: '忽略',\n accessWorking: '处理中…',\n accessSourceCommand: '对话命令',\n accessSourceCard: '授权卡',\n accessSourcePanel: '设置面板',\n accessRequestGroup: '群申请',\n accessRequestUser: '用户申请',\n accessRequestRequester: '申请人',\n accessRequestExpires: '有效期至',\n accessGroupAdded: '已添加群授权',\n accessGroupRemoved: '已移除群授权',\n accessUserAdded: '已添加用户授权',\n accessUserRemoved: '已移除用户授权',\n accessRequestAllowed: '已授权',\n accessRequestDenied: '已忽略',\n accessHints: '三种授权入口:群内 /auth 命令、入群与用户授权卡、本面板。撤销立即生效;白名单只约束非管理员(管理员在任何私聊/群聊都直接放行),非管理员需要群和用户两层同时命中。',\n accessChatIdHint: 'chat_id 可从入群群信息卡或群内 /groupid 获取;open_id 可私聊机器人发送 /userid 获取。',\n} as const\n","/**\n * Web 面板覆盖层:`$DSH_HOME/dsh-channels/settings.json` 的读写、schema 校验与合并。\n *\n * 设计取舍:\n * - 覆盖层只存 Web 面板可编辑的字段(`EDITABLE_KEYS`),凭据引用刻意不可\n * 覆盖(变动等于换凭据);`baseDir` 可覆盖,但走 `workspaces.ts` 的严格\n * 规范化(绝对路径 / `~` 展开 / 拒绝根目录),不接受相对路径。\n * - 文件损坏/字段过期时**降级为空覆盖**并告警,绝不阻止飞书通道启动;\n * 面板保存时则由 zod 完整校验后整体落盘。\n * - 写入走同目录临时文件 + rename,避免半截文件。\n *\n * @module dsh-channels/settings/overlay\n */\n\nimport { promises as fs } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\nimport type { z } from 'zod'\nimport { configSchema } from '../config.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport { normalizeWorkspaceBaseDir } from '../workspaces.ts'\nimport { EDITABLE_KEYS } from './contract.ts'\nimport type { ConfigValue, EditableKey } from './contract.ts'\n\n/** The subset of `FeishuConfig` the Web panel may override. */\nexport type OverlayPatch = Partial<Pick<FeishuConfig, EditableKey>>\n\n/** Fields whose empty string means \"clear the override\" (optional text config). */\nconst OPTIONAL_TEXT_KEYS = new Set<EditableKey>(['provider', 'model', 'reasoningEffort', 'agentPreset'])\n\n/**\n * Validate only the keys actually present in the input. Validating through\n * the canonical `configSchema` field-by-field keeps one source of truth for\n * bounds/enums — and, unlike `z.object(shape).partial()`, it does not leak\n * schema defaults into the overlay (which would otherwise turn a one-field\n * edit into \"override every editable key\").\n */\nexport function parseOverlay(raw: unknown): { ok: true; patch: OverlayPatch } | { ok: false; issues: Array<{ path: string; message: string }> } {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { ok: false, issues: [{ path: '', message: '必须是 JSON 对象' }] }\n }\n const source = raw as Record<string, unknown>\n const issues: Array<{ path: string; message: string }> = []\n const patch: Record<string, unknown> = {}\n const shape = configSchema.shape as unknown as Record<string, z.ZodType>\n for (const key of EDITABLE_KEYS) {\n if (!Object.hasOwn(source, key)) continue\n const value = source[key]\n if (value === undefined) continue\n // `baseDir` needs location semantics the shared string schema cannot\n // express (absolute only, `~` expansion, no filesystem root).\n if (key === 'baseDir') {\n const checked = normalizeWorkspaceBaseDir(value)\n if (!checked.ok) issues.push({ path: key, message: checked.message })\n else patch[key] = checked.value\n continue\n }\n const parsed = shape[key]!.safeParse(value)\n if (!parsed.success) {\n issues.push({ path: key, message: parsed.error.issues[0]?.message ?? '非法值' })\n continue\n }\n patch[key] = parsed.data\n }\n if (issues.length > 0) return { ok: false, issues }\n return { ok: true, patch: normalizeOverlay(patch) }\n}\n\n/** `$DSH_HOME` resolution shared with the rest of the DSH ecosystem. */\nexport function resolveDshHome(env: NodeJS.ProcessEnv = process.env): string {\n const configured = env.DSH_HOME?.trim()\n return configured !== undefined && configured !== '' ? configured : join(homedir(), '.dsh')\n}\n\n/** Default overlay path: `$DSH_HOME/dsh-channels/settings.json`. */\nexport function defaultOverlayPath(env: NodeJS.ProcessEnv = process.env): string {\n return join(resolveDshHome(env), 'dsh-channels', 'settings.json')\n}\n\n/** Warn sink used for degraded reads (never throws). */\nexport interface OverlayLogger {\n warn?(message: string, ...args: unknown[]): void\n}\n\n/** Strip `undefined`; drop empty-string overrides for optional text fields. */\nexport function normalizeOverlay(patch: Record<string, unknown>): OverlayPatch {\n const normalized: Record<string, unknown> = {}\n for (const key of EDITABLE_KEYS) {\n const value = patch[key]\n if (value === undefined) continue\n if (OPTIONAL_TEXT_KEYS.has(key) && value === '') continue\n normalized[key] = value\n }\n return normalized as OverlayPatch\n}\n\n/** Merge the Web overlay over the profile-provided base config. */\nexport function mergeOverlay(base: FeishuConfig, overlay: OverlayPatch): FeishuConfig {\n return { ...base, ...overlay }\n}\n\n/** Project one config onto the editable-key subset (wire shape). */\nexport function pickEditable(config: FeishuConfig): Record<EditableKey, ConfigValue | undefined> {\n const picked: Record<string, ConfigValue | undefined> = {}\n for (const key of EDITABLE_KEYS) picked[key] = config[key] as ConfigValue | undefined\n return picked as Record<EditableKey, ConfigValue | undefined>\n}\n\n/**\n * One JSON overlay file. Reads degrade to `{}` with a warning; writes are\n * atomic and owner-readable only.\n */\nexport class OverlayStore {\n constructor(\n readonly file: string,\n private readonly logger?: OverlayLogger,\n ) {}\n\n /** Load the overlay; a missing/corrupt/stale file degrades to an empty overlay. */\n async load(): Promise<OverlayPatch> {\n let text: string\n try {\n text = await fs.readFile(this.file, 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.logger?.warn?.('dsh-channels: Web 覆盖配置读取失败(%s),按未覆盖处理:%s', this.file, String(error))\n }\n return {}\n }\n let raw: unknown\n try {\n raw = JSON.parse(text)\n } catch (error) {\n this.logger?.warn?.('dsh-channels: Web 覆盖配置不是合法 JSON(%s),按未覆盖处理:%s', this.file, String(error))\n return {}\n }\n const parsed = parseOverlay(raw)\n if (!parsed.ok) {\n this.logger?.warn?.(\n 'dsh-channels: Web 覆盖配置含非法字段(%s),按未覆盖处理:%s',\n this.file,\n parsed.issues.map(issue => `${issue.path}: ${issue.message}`).join('; '),\n )\n return {}\n }\n return parsed.patch\n }\n\n /** Persist the validated overlay atomically. */\n async save(patch: OverlayPatch): Promise<void> {\n await fs.mkdir(dirname(this.file), { recursive: true })\n const temporary = `${this.file}.tmp-${process.pid}-${Date.now()}`\n await fs.writeFile(temporary, `${JSON.stringify(normalizeOverlay(patch as Record<string, unknown>), null, 2)}\\n`, {\n encoding: 'utf8',\n mode: 0o600,\n })\n await fs.rename(temporary, this.file)\n }\n\n /** Remove the overlay file; the plugin falls back to the profile config. */\n async clear(): Promise<void> {\n await fs.rm(this.file, { force: true })\n }\n}\n","/**\n * Web 设置面板的 HTTP 路由:status / panel / catalog / config(PUT,DELETE) /\n * credentials(PUT,DELETE) / reconnect / cleanup / access。\n *\n * 路由注册进宿主可选的 `webServer` 服务(headless profile 没有它时面板自然\n * 不存在,飞书通道不受影响)。写操作额外做同源校验 —— 本机非回环绑定下\n * 防止跨站页面改写插件配置或授权白名单;读取不敏感,保持只读 GET。凭据值\n * 只走写路径,响应永远不包含它们。`catalog` 只读宿主 LLM / preset roster 的\n * 枚举结果,用于面板的 provider → model → 推理档位级联选择。`access` 提供\n * 准入快照与白名单/授权申请的写入口(与飞书卡片共用同一结算函数)。\n *\n * @module dsh-channels/settings/routes\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { HostContext } from '../dsh-types.ts'\nimport {\n ACCESS_GROUPS_ROUTE,\n ACCESS_REQUESTS_ROUTE,\n ACCESS_ROUTE,\n ACCESS_USERS_ROUTE,\n CATALOG_ROUTE,\n CLEANUP_ROUTE,\n CONFIG_ROUTE,\n CREDENTIALS_ROUTE,\n PANEL_ROUTE,\n PICK_DIRECTORY_ROUTE,\n RECONNECT_ROUTE,\n STATUS_ROUTE,\n} from './contract.ts'\nimport type {\n CredentialField,\n EditableValues,\n PickDirectoryResponse,\n SaveCredentialsRequest,\n} from './contract.ts'\nimport type { SettingsSurface } from './surface.ts'\n\n/** The narrow `ctx.webServer` surface this module registers into. */\nexport interface HostWebServer {\n register(route: {\n kind: 'exact'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n }): () => void\n}\n\ntype WebHandler = (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n\n/** Request body cap: the panel payload is a handful of primitives. */\nconst MAX_BODY_BYTES = 64 * 1024\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nclass HttpError extends Error {\n constructor(readonly status: number, message: string) {\n super(message)\n }\n}\n\nfunction methodOf(req: IncomingMessage): string {\n return (req.method ?? 'GET').toUpperCase()\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n const body = JSON.stringify(payload)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-length': Buffer.byteLength(body),\n })\n res.end(body)\n}\n\n/** Reject non-allowed methods with 405 + Allow, returning false. */\nfunction allowOnly(req: IncomingMessage, res: ServerResponse, allowed: readonly string[]): boolean {\n const method = methodOf(req)\n if (allowed.includes(method)) return true\n const body = JSON.stringify({ ok: false, error: `方法 ${method} 不受支持` })\n res.writeHead(405, {\n allow: allowed.join(', '),\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-length': Buffer.byteLength(body),\n })\n res.end(body)\n return false\n}\n\n/**\n * Mutating requests must come from this same origin. Missing Origin is allowed\n * (non-browser local clients, e.g. curl); a present-but-mismatched Origin is\n * a cross-site write and is refused.\n */\nexport function isSameOriginRequest(req: IncomingMessage): boolean {\n const origin = req.headers.origin\n if (origin === undefined || origin === '') return true\n const host = req.headers.host\n if (host === undefined) return false\n try {\n return new URL(origin).host === host\n } catch {\n return false\n }\n}\n\nasync function readJsonBody(req: IncomingMessage): Promise<unknown> {\n const contentType = (req.headers['content-type'] ?? '').split(';')[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n throw new HttpError(415, '请求体必须是 application/json')\n }\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n const buffer = typeof chunk === 'string' ? Buffer.from(chunk) : chunk as Buffer\n size += buffer.length\n if (size > MAX_BODY_BYTES) throw new HttpError(413, '请求体过大')\n chunks.push(buffer)\n }\n if (size === 0) throw new HttpError(400, '请求体为空')\n try {\n return JSON.parse(Buffer.concat(chunks).toString('utf8'))\n } catch {\n throw new HttpError(400, '请求体不是合法 JSON')\n }\n}\n\n/**\n * Parse one JSON-object body for an access write. HttpErrors are answered\n * here (415/413/400); `undefined` means \"already responded, stop\".\n */\nasync function readAccessBody(\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<Record<string, unknown> | undefined> {\n let body: unknown\n try {\n body = await readJsonBody(req)\n } catch (error) {\n if (error instanceof HttpError) {\n sendJson(res, error.status, { ok: false, error: error.message })\n return undefined\n }\n throw error\n }\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n sendJson(res, 400, { ok: false, error: '请求体必须是 JSON 对象' })\n return undefined\n }\n return body as Record<string, unknown>\n}\n\n/** Wrap a handler so thrown errors become JSON instead of a dropped socket. */\nfunction guarded(handler: WebHandler): WebHandler {\n return async (req, res) => {\n try {\n await handler(req, res)\n } catch (error) {\n if (res.headersSent) {\n res.end()\n return\n }\n const status = error instanceof HttpError ? error.status : 500\n sendJson(res, status, {\n ok: false,\n error: error instanceof HttpError ? error.message : `内部错误:${messageOf(error)}`,\n })\n }\n }\n}\n\nfunction forbiddenWrite(res: ServerResponse): void {\n sendJson(res, 403, { ok: false, error: '跨站写入被拒绝(Origin 与 Host 不一致)' })\n}\n\n/** Register the panel routes; the returned disposer removes all of them. */\nexport function registerSettingsRoutes(server: HostWebServer, surface: SettingsSurface): () => void {\n const disposers = [\n server.register({\n kind: 'exact',\n path: STATUS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n sendJson(res, 200, await surface.status())\n }),\n }),\n server.register({\n kind: 'exact',\n path: PANEL_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n sendJson(res, 200, await surface.panel())\n }),\n }),\n server.register({\n kind: 'exact',\n path: CATALOG_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n const url = new URL(req.url ?? '/', 'http://dsh.local')\n sendJson(res, 200, await surface.catalogFor(\n url.searchParams.get('provider') ?? undefined,\n url.searchParams.get('model') ?? undefined,\n ))\n }),\n }),\n server.register({\n kind: 'exact',\n path: CONFIG_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['PUT', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n if (methodOf(req) === 'DELETE') {\n const result = await surface.clear()\n sendJson(res, result.ok ? 200 : 400, result)\n return\n }\n let body: unknown\n try {\n body = await readJsonBody(req)\n } catch (error) {\n if (error instanceof HttpError) {\n sendJson(res, error.status, { ok: false, error: error.message })\n return\n }\n throw error\n }\n const values = (body as { values?: unknown } | null)?.values\n if (typeof values !== 'object' || values === null || Array.isArray(values)) {\n sendJson(res, 400, { ok: false, error: '请求体缺少 values 对象' })\n return\n }\n const result = await surface.save(values as EditableValues)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: CREDENTIALS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['PUT', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n let body: unknown\n try {\n body = await readJsonBody(req)\n } catch (error) {\n if (error instanceof HttpError) {\n sendJson(res, error.status, { ok: false, error: error.message })\n return\n }\n throw error\n }\n const payload = body as { appId?: unknown; appSecret?: unknown; field?: unknown } | null\n if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {\n sendJson(res, 400, { ok: false, error: '请求体必须是 JSON 对象' })\n return\n }\n if (methodOf(req) === 'DELETE') {\n const field = payload.field\n if (field !== 'appId' && field !== 'appSecret') {\n sendJson(res, 400, { ok: false, error: 'field 必须是 appId 或 appSecret' })\n return\n }\n const result = await surface.clearCredential(field as CredentialField)\n sendJson(res, result.ok ? 200 : 400, result)\n return\n }\n const values: SaveCredentialsRequest = {}\n if (payload.appId !== undefined) values.appId = payload.appId as string\n if (payload.appSecret !== undefined) values.appSecret = payload.appSecret as string\n const result = await surface.saveCredentials(values)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: RECONNECT_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const result = await surface.reconnect()\n // An incomplete configuration is a client-side state (the panel routes\n // the user to the credentials form), not a server failure.\n sendJson(res, result.ok ? 200 : (result.unconfigured === true ? 400 : 500), result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: CLEANUP_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const result = await surface.runCleanup()\n sendJson(res, result.ok ? 200 : 500, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: PICK_DIRECTORY_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n // The chooser is a local desktop modal: it can stay open far longer\n // than a normal request, and the operator may close the page instead\n // of the dialog. Abort the host picker when the response socket goes\n // away so no orphaned modal outlives its panel.\n const controller = new AbortController()\n let answered = false\n if (typeof res.on === 'function') {\n res.on('close', () => {\n if (!answered) controller.abort()\n })\n }\n let path: string | null\n try {\n path = await surface.pickDirectory(controller.signal)\n } catch (error) {\n answered = true\n // A request the browser already dropped has nobody left to answer.\n if (controller.signal.aborted) {\n res.end()\n return\n }\n throw error\n }\n answered = true\n const payload: PickDirectoryResponse = path === null\n ? { ok: true, canceled: true }\n : { ok: true, path }\n sendJson(res, 200, payload)\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['GET', 'HEAD'])) return\n if (methodOf(req) === 'HEAD') {\n res.writeHead(200, { 'cache-control': 'no-store' })\n res.end()\n return\n }\n sendJson(res, 200, await surface.access())\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_GROUPS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const payload = await readAccessBody(req, res)\n if (payload === undefined) return\n const result = methodOf(req) === 'DELETE'\n ? await surface.removeAccessGroup(payload.chatId)\n : await surface.saveAccessGroup(payload.chatId, payload.name)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_USERS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST', 'DELETE'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const payload = await readAccessBody(req, res)\n if (payload === undefined) return\n const result = methodOf(req) === 'DELETE'\n ? await surface.removeAccessUser(payload.openId)\n : await surface.saveAccessUser(payload.openId, payload.name)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n server.register({\n kind: 'exact',\n path: ACCESS_REQUESTS_ROUTE,\n handler: guarded(async (req, res) => {\n if (!allowOnly(req, res, ['POST'])) return\n if (!isSameOriginRequest(req)) {\n forbiddenWrite(res)\n return\n }\n const payload = await readAccessBody(req, res)\n if (payload === undefined) return\n const result = await surface.resolveAccessRequest(payload.token, payload.decision)\n sendJson(res, result.ok ? 200 : 400, result)\n }),\n }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/**\n * Mount the routes once the host carries a Web server. The optional\n * `ctx.inject` keeps headless profiles from waiting on a service they will\n * never have; embedded/test hosts that resolve services synchronously fall\n * back to the direct attach path.\n */\nexport function mountSettingsRoutes(ctx: HostContext, surface: SettingsSurface): void {\n const attach = (webCtx: HostContext): void => {\n const server = webCtx.get?.('webServer') as HostWebServer | undefined\n if (server === undefined) return\n webCtx.effect(\n () => registerSettingsRoutes(server, surface),\n 'dsh-channels: settings panel routes',\n )\n }\n const inject = (ctx as {\n inject?: (dependencies: string[], callback: (child: HostContext) => void) => unknown\n }).inject\n if (typeof inject === 'function') {\n inject.call(ctx, ['webServer'], attach)\n return\n }\n attach(ctx)\n}\n","/**\n * Web 设置面板的 Host 视图模型:把 `FeishuService` 的运行时事实、覆盖层与\n * 字段描述组装成 wire 数据,并处理保存/清除/重连/清理四个动作。\n *\n * 这一层不碰 HTTP(见 `routes.ts`),也不碰 fs 细节(见 `overlay.ts`);\n * 唯一的文件系统动作是保存 `baseDir` 时通过 `workspaces.ts` 创建并检查\n * 工作区根目录,这样不可用的路径在保存时就暴露,而不是等到飞书里提示\n * 「会话创建失败」。因此可以只用假 target 做单测。\n *\n * @module dsh-channels/settings/surface\n */\n\nimport { DSH_CHANNELS_VERSION } from '../session-export.ts'\nimport type { FeishuConfig } from '../config.ts'\nimport type { HealthSnapshot } from '../health.ts'\nimport { prepareWorkspaceBaseDir } from '../workspaces.ts'\nimport {\n EDITABLE_KEYS,\n PANEL_COPY,\n RESTART_KEYS,\n} from './contract.ts'\nimport type {\n AccessMutationResponse,\n AccessWire,\n CatalogWire,\n ChannelPhaseWire,\n ChannelStatusWire,\n CleanupPreviewWire,\n CleanupResponse,\n CleanupRunWire,\n ClearConfigResponse,\n ClearCredentialResponse,\n ConfigValue,\n CredentialField,\n CredentialRefWire,\n EditableFieldWire,\n EditableKey,\n EditableValues,\n FieldIssueWire,\n FieldOptionWire,\n PanelConfigWire,\n PanelWire,\n ReadOnlyFieldWire,\n ReconnectResponse,\n SaveConfigResponse,\n SaveCredentialsRequest,\n SaveCredentialsResponse,\n} from './contract.ts'\nimport {\n parseOverlay,\n pickEditable,\n} from './overlay.ts'\nimport type { OverlayLogger, OverlayPatch, OverlayStore } from './overlay.ts'\nimport type { SettingsCatalog } from './catalog.ts'\nimport type { SettingsDirectoryPicker } from './directory-picker.ts'\n\n/** The slice of `FeishuService` the panel needs (structural, easy to fake). */\nexport interface SettingsTarget {\n isRunning(): boolean\n isRestarting(): boolean\n /** True while an explicit connect action is in flight (panel button busy state). */\n isConnecting(): boolean\n health(): HealthSnapshot\n startedAtMs(): number | undefined\n uptimeMs(): number | undefined\n startError(): string | undefined\n /**\n * Whether the Feishu application credentials resolve. A false value means the\n * plugin is loaded but deliberately holds no connection: the panel guides the\n * user through configuring credentials instead of reporting a failure.\n */\n isConfigured(): boolean\n /** Human-readable names of the missing configuration items (never values). */\n missingConfig(): readonly string[]\n /** Re-read credential presence (the credentials store is hot-reloaded on disk). */\n refreshReadiness(): Promise<void>\n /** Connect with the current config; throws when credentials are incomplete or the handshake fails. */\n connect(): Promise<void>\n configSnapshot(): FeishuConfig\n baseConfigSnapshot(): FeishuConfig\n runningConfigSnapshot(): FeishuConfig | undefined\n applyOverlay(overlay: Partial<FeishuConfig>): void\n restart(): Promise<void>\n credentialStates(): Promise<{ appId: CredentialInfo; appSecret: CredentialInfo }>\n setCredential(field: CredentialField, value: string): Promise<void>\n clearCredential(field: CredentialField): Promise<void>\n /** What the workspace-retention cleanup would delete right now (read-only scan). */\n cleanupPreview(): Promise<CleanupPreviewWire>\n /** Run the workspace cleanup once; partial failures are reported per workspace. */\n runWorkspaceCleanup(): Promise<CleanupRunWire>\n /** Current admission snapshot: mode, administrator, whitelists, pending requests. */\n accessSnapshot(): AccessWire\n /** Authorize one group (panel entry); resolves with the refreshed snapshot. */\n addAccessGroup(chatId: string, name?: string): Promise<AccessMutationResponse>\n /** Revoke one group authorization (takes effect on the next message). */\n removeAccessGroup(chatId: string): Promise<AccessMutationResponse>\n /** Authorize one user `open_id` (global across authorized groups). */\n addAccessUser(openId: string, name?: string): Promise<AccessMutationResponse>\n /** Revoke one user authorization (takes effect on the next message). */\n removeAccessUser(openId: string): Promise<AccessMutationResponse>\n /** Settle one pending request (panel shares this path with the Feishu DM card). */\n resolveAccessRequest(token: string, decision: 'allow' | 'deny'): Promise<AccessMutationResponse>\n}\n\n/** Presence/source/writability facts returned by the credentials seam. */\nexport interface CredentialInfo {\n configured: boolean\n writable?: boolean\n source?: string\n}\n\nconst RESTART_SET = new Set<string>(RESTART_KEYS)\n\n/** Optional text fields: an empty string in the panel clears the override. */\nconst OPTIONAL_TEXT_KEYS = new Set<EditableKey>([\n 'provider',\n 'model',\n 'reasoningEffort',\n 'agentPreset',\n 'adminOpenId',\n])\n\nconst BOOLEAN_KEYS = new Set<EditableKey>([\n 'requireMention',\n 'welcomeOnboarding',\n 'allowPermissionSwitch',\n 'acceptAttachments',\n 'workspaceRetentionAuto',\n])\n\nconst SELECT_OPTIONS: Partial<Record<EditableKey, ReadonlyArray<{ value: string; label: string }>>> = {\n dmMode: [\n { value: 'open', label: '开放私聊' },\n { value: 'disabled', label: '禁用私聊' },\n ],\n threadFallback: [\n { value: 'degrade', label: '话题被拒时降级为会话级' },\n { value: 'strict', label: '话题被拒时直接失败' },\n ],\n workspaceRetention: [\n { value: 'off', label: '关闭(不删除)' },\n { value: '7d', label: '7 天' },\n { value: '30d', label: '30 天' },\n { value: '90d', label: '90 天' },\n { value: '180d', label: '180 天' },\n ],\n loggerLevel: [\n { value: 'debug', label: 'debug' },\n { value: 'info', label: 'info' },\n { value: 'warn', label: 'warn' },\n { value: 'error', label: 'error' },\n ],\n accessMode: [\n { value: 'guarded', label: '守卫模式(仅管理员与白名单)' },\n { value: 'open', label: '开放(不安全,仅调试)' },\n ],\n}\n\n/** Panel copy per editable key: label, help, and (for numbers) bounds. */\nconst FIELD_META: Record<EditableKey, { label: string; help: string; min?: number; max?: number; step?: number; placeholder?: string; wide?: boolean }> = {\n baseDir: {\n label: '工作区根目录(文件落点)',\n help: '每个飞书话题拥有独立工作区:私聊 <baseDir>/topics/p2p/<open_id>/<topic>/,群聊 <baseDir>/topics/groups/<chat_id>/<open_id>/<topic>/(topic 来自话题 id)。必须是绝对路径(~ 开头按家目录展开),保存时会创建该目录;只影响之后新建立的工作区 —— 已有话题即使 /renew 也沿用原工作区,旧文件不会迁移。',\n placeholder: '/Users/you/.dsh/dsh-channels/workspaces',\n wide: true,\n },\n dmMode: { label: '私聊模式', help: '禁用后私聊消息不再触发会话;群聊与已绑定话题不受影响。' },\n requireMention: { label: '普通群需 @ 机器人', help: '已绑定的主题内始终不需要 @;关闭后普通群的每条消息都会触发。' },\n threadFallback: { label: '话题被拒时的兜底', help: 'degrade 降级为会话级路由;strict 直接让回复失败。' },\n welcomeOnboarding: { label: '首次私聊引导卡', help: '每个私聊会话第一次触达时发送一张命令与用法引导卡,只发一次。' },\n allowPermissionSwitch: { label: '允许飞书侧切换权限预设', help: '安全敏感:开启后话题所有者可用 /permissions 切换沙箱与审批预设。' },\n adminOpenId: {\n label: '管理员 open_id',\n help: '唯一管理员。私聊机器人发送 /userid 获取(ou_ 开头,不是 user_id/邮箱);留空 = 清除 Web 覆盖。guarded 模式下未配置会拒绝所有对话。',\n placeholder: 'ou_xxx',\n },\n accessMode: {\n label: '准入模式',\n help: 'guarded(默认):私聊仅管理员,群聊需群授权且发送者是管理员或已授权用户;open:应用可用范围内的任何人都能对话,不安全,仅调试。',\n },\n authRequestTtlDays: {\n label: '授权申请有效期(天)',\n help: '入群/用户授权申请卡的有效期,1–30 天;过期后卡片需重新触发申请(也可在本页下方「待处理申请」中处理)。',\n min: 1,\n max: 30,\n step: 1,\n },\n acceptAttachments: { label: '接收并保存附件', help: '接收飞书图片、文件与合并转发记录,保存到工作区的 attachments/ 目录;模型不会自动读取,需要时用文件工具自行查看。关闭后附件消息不保存、不触发回复。' },\n workspaceRetention: { label: '过期工作区保留期', help: '超过所选天数没有活动的飞书工作区会进入下方「工作区清理」列表;「关闭」表示永不删除。DSH 会话日志不受影响。' },\n workspaceRetentionAuto: { label: '自动执行工作区清理', help: '开启后插件在启动时及每天执行一次过期工作区清理;关闭时只能在下方卡片手动清理。开启前请确认保留期已设置。' },\n provider: { label: '模型服务商', help: '留空 = 使用部署默认;例如 deepseek-official。', placeholder: '留空 = 部署默认' },\n model: { label: '模型 id', help: '留空 = 使用部署默认;例如 deepseek-v4-flash。', placeholder: '留空 = 部署默认' },\n reasoningEffort: { label: '推理档位', help: '模型声明的精确档位 id(如 high / max);留空 = 适配器默认。', placeholder: '留空 = 适配器默认' },\n agentPreset: { label: 'Agent 预设', help: '新会话使用的预设 id(如 standard);留空 = roster 默认。', placeholder: '留空 = roster 默认' },\n refreshMs: { label: '流式刷新间隔(ms)', help: '流式卡片的 patch 节流间隔,200–10000;需重连生效。', min: 200, max: 10_000, step: 100 },\n statusRefreshMs: { label: '状态行保活间隔(ms)', help: '无文本输出时活动状态行的计时刷新间隔,1000–60000;需重连生效。', min: 1_000, max: 60_000, step: 500 },\n loggerLevel: { label: 'SDK 日志级别', help: '官方 SDK 的日志级别;需重连生效。' },\n}\n\nconst GROUP_OF: Record<EditableKey, EditableFieldWire['group']> = {\n baseDir: 'workspace',\n workspaceRetention: 'workspace',\n workspaceRetentionAuto: 'workspace',\n dmMode: 'behavior',\n requireMention: 'behavior',\n threadFallback: 'behavior',\n welcomeOnboarding: 'behavior',\n allowPermissionSwitch: 'behavior',\n adminOpenId: 'access',\n accessMode: 'access',\n authRequestTtlDays: 'access',\n acceptAttachments: 'content',\n provider: 'model',\n model: 'model',\n reasoningEffort: 'model',\n agentPreset: 'model',\n refreshMs: 'streaming',\n statusRefreshMs: 'streaming',\n loggerLevel: 'streaming',\n}\n\n/** One descriptor per editable key, in `EDITABLE_KEYS` order (Host-driven form). */\nexport const FIELD_DESCRIPTORS: readonly EditableFieldWire[] = EDITABLE_KEYS.map(key => {\n const meta = FIELD_META[key]\n const options = SELECT_OPTIONS[key]\n return {\n key,\n label: meta.label,\n kind: BOOLEAN_KEYS.has(key) ? 'boolean' : options !== undefined ? 'select' : meta.min !== undefined ? 'number' : 'text',\n group: GROUP_OF[key],\n help: meta.help,\n ...(options === undefined ? {} : { options }),\n ...(meta.min === undefined ? {} : { min: meta.min }),\n ...(meta.max === undefined ? {} : { max: meta.max }),\n ...(meta.step === undefined ? {} : { step: meta.step }),\n ...(meta.placeholder === undefined ? {} : { placeholder: meta.placeholder }),\n ...(meta.wide === undefined ? {} : { wide: meta.wide }),\n restartRequired: RESTART_SET.has(key),\n }\n})\n\n/** Keys whose options are resolved from the live host catalog at panel time. */\ntype CatalogKey = 'provider' | 'model' | 'reasoningEffort' | 'agentPreset'\n\nconst CATALOG_KEYS = new Set<EditableKey>(['provider', 'model', 'reasoningEffort', 'agentPreset'])\n\nfunction isCatalogKey(key: EditableKey): key is CatalogKey {\n return CATALOG_KEYS.has(key)\n}\n\n/** Every group switched off: the form keeps its free-text controls. */\nexport function emptyCatalog(): CatalogWire {\n const none = { selectable: false, options: [] as FieldOptionWire[] }\n return {\n provider: { ...none },\n model: { ...none },\n reasoningEffort: { ...none },\n agentPreset: { ...none },\n }\n}\n\n/** Longest accepted catalog query value; anything longer is treated as absent. */\nconst MAX_CATALOG_QUERY_LENGTH = 200\n\n/** Trim one provider/model query value, dropping empty, hostile, or oversized input. */\nfunction cleanCatalogQuery(value: string | undefined): string | undefined {\n if (value === undefined) return undefined\n const trimmed = value.trim()\n if (trimmed === '' || trimmed.length > MAX_CATALOG_QUERY_LENGTH) return undefined\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(trimmed)) return undefined\n return trimmed\n}\n\n/**\n * Attach the live catalog to the four selector fields. A configured value the\n * host no longer advertises stays visible as an extra option so opening the\n * panel never silently rewrites the effective config.\n */\nexport function applyCatalog(\n fields: readonly EditableFieldWire[],\n catalog: CatalogWire,\n current: Partial<Record<CatalogKey, ConfigValue | undefined>>,\n): EditableFieldWire[] {\n return fields.map((field) => {\n if (!isCatalogKey(field.key)) return field\n const selector = catalog[field.key]\n if (!selector.selectable) return field\n const value = current[field.key]\n const options = typeof value === 'string' && value !== ''\n && !selector.options.some(option => option.value === value)\n ? withCurrentOption(selector.options, value)\n : [...selector.options]\n const next: EditableFieldWire = {\n ...field,\n kind: 'select',\n options,\n }\n if (selector.disabled === true) next.disabled = true\n if (selector.help !== undefined) next.help = selector.help\n return next\n })\n}\n\n/** Keep the leading inherit option first, then surface the unlisted current value. */\nfunction withCurrentOption(\n options: readonly FieldOptionWire[],\n value: string,\n): FieldOptionWire[] {\n const [inherit, ...rest] = options\n const extra: FieldOptionWire = { value, label: `${value}(当前配置,不在宿主机列表中)` }\n return inherit === undefined ? [extra] : [inherit, extra, ...rest]\n}\n\n/**\n * Mark the workspace-root field as browsable when the host can open its native\n * directory chooser. Every other host (no `directoryPicker`, or the `browse`\n * backend of remote/headless sessions) keeps the plain text input.\n *\n * @param fields - Host field descriptors after the selector catalog is applied.\n * @param available - whether a native directory chooser can be opened.\n * @returns the descriptors with `browse: 'directory'` on `baseDir` when usable.\n */\nexport function withDirectoryBrowse(\n fields: readonly EditableFieldWire[],\n available: boolean,\n): EditableFieldWire[] {\n if (!available) return [...fields]\n return fields.map(field => (field.key === 'baseDir' ? { ...field, browse: 'directory' } : field))\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction formatDays(ms: number): string {\n return ms === 0 ? '关闭(不过期)' : `${Math.round(ms / 86_400_000)} 天`\n}\n\nfunction formatSeconds(ms: number): string {\n return `${Math.round(ms / 1_000)} 秒`\n}\n\nfunction formatOptional(value: string | number | undefined, suffix = ''): string {\n return value === undefined ? '未设置' : `${value}${suffix}`\n}\n\n/** Credential values are opaque strings; trim, cap length, reject control chars. */\nconst MAX_CREDENTIAL_LENGTH = 512\n\nexport function normalizeCredentialValue(raw: unknown): { ok: true; value: string } | { ok: false; message: string } {\n if (typeof raw !== 'string') return { ok: false, message: '必须是字符串' }\n const value = raw.trim()\n if (value === '') return { ok: false, message: '不能为空' }\n if (value.length > MAX_CREDENTIAL_LENGTH) return { ok: false, message: `长度不得超过 ${MAX_CREDENTIAL_LENGTH}` }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(value)) return { ok: false, message: '包含非法控制字符' }\n return { ok: true, value }\n}\n\n/** Longest accepted whitelist identifier / display name. */\nconst MAX_ACCESS_VALUE_LENGTH = 128\n\n/** Feishu user open_ids (`ou_` + URL-safe id). Identity is open_id only. */\nconst OPEN_ID_PATTERN = /^ou_[A-Za-z0-9_-]+$/\n\n/** Trim one whitelist identifier, rejecting empty, oversized, or hostile input. */\nexport function normalizeAccessId(\n raw: unknown,\n label: string,\n invalidFormat?: string,\n): { ok: true; value: string } | { ok: false; message: string } {\n if (typeof raw !== 'string') return { ok: false, message: `${label}必须是字符串` }\n const value = raw.trim()\n if (value === '') return { ok: false, message: `${label}不能为空` }\n if (value.length > MAX_ACCESS_VALUE_LENGTH) {\n return { ok: false, message: `${label}长度不得超过 ${MAX_ACCESS_VALUE_LENGTH}` }\n }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(value)) return { ok: false, message: `${label}包含非法控制字符` }\n if (invalidFormat !== undefined && !OPEN_ID_PATTERN.test(value)) {\n return { ok: false, message: invalidFormat }\n }\n return { ok: true, value }\n}\n\n/** Optional display name: trimmed; empty means \"unknown\" and is dropped. */\nexport function normalizeAccessName(\n raw: unknown,\n): { ok: true; value: string | undefined } | { ok: false; message: string } {\n if (raw === undefined || raw === null) return { ok: true, value: undefined }\n if (typeof raw !== 'string') return { ok: false, message: '名称必须是字符串' }\n const value = raw.trim()\n if (value === '') return { ok: true, value: undefined }\n if (value.length > MAX_ACCESS_VALUE_LENGTH) {\n return { ok: false, message: `名称长度不得超过 ${MAX_ACCESS_VALUE_LENGTH}` }\n }\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(value)) return { ok: false, message: '名称包含非法控制字符' }\n return { ok: true, value }\n}\n\n/** Build the read-only advanced card from the effective config. */\nexport function readOnlyFields(config: FeishuConfig): ReadOnlyFieldWire[] {\n return [\n { key: 'routeTtlMs', label: '话题路由过期', value: formatDays(config.routeTtlMs), help: '超期话题的后续消息会被明确拒绝,而不是新建 Context-free Session。' },\n { key: 'approvalTimeoutMs', label: '审批卡片等待', value: formatSeconds(config.approvalTimeoutMs) },\n { key: 'questionTimeoutMs', label: '提问卡片等待', value: formatSeconds(config.questionTimeoutMs) },\n { key: 'maxTokens', label: '单次输出上限', value: formatOptional(config.maxTokens, ' tokens'), help: '留空/未设置时使用模型适配器默认值。' },\n ]\n}\n\n/**\n * Derive the panel phase from readiness + runtime facts. The order matters:\n * an incomplete configuration always wins (no connection is attempted), and a\n * failed attempt is only reported while the channel is stopped.\n */\nfunction phaseOf(target: SettingsTarget): ChannelPhaseWire {\n // A connection attempt in flight is the honest label even though `start()`\n // flips `started` before the handshake finishes. A live connection outranks\n // missing credentials: removing a stored value does not drop the open socket,\n // so the panel keeps the connection actions available and flags the missing\n // credential separately (`configured: false`).\n if (target.isRestarting()) return 'connecting'\n if (target.isConnecting()) return target.isConfigured() ? 'connecting' : 'unconfigured'\n if (target.isRunning()) return 'connected'\n if (!target.isConfigured()) return 'unconfigured'\n if (target.startError() !== undefined) return 'error'\n return 'idle'\n}\n\n/** Assemble the status half of the panel payload. */\nfunction buildStatus(target: SettingsTarget, overlayPath: string): ChannelStatusWire {\n const health = target.health()\n const connection = health.connection\n return {\n running: target.isRunning(),\n configured: target.isConfigured(),\n phase: phaseOf(target),\n missingConfig: [...target.missingConfig()],\n level: health.level,\n startedAt: target.startedAtMs(),\n uptimeMs: target.uptimeMs(),\n ...(connection === undefined ? {} : {\n connection: {\n state: connection.state,\n lastConnectTime: connection.lastConnectTime,\n nextConnectTime: connection.nextConnectTime,\n reconnectAttempts: connection.reconnectAttempts,\n },\n }),\n ...(health.botOpenIdTail === undefined ? {} : { botOpenIdTail: health.botOpenIdTail }),\n routeCount: health.routeCount,\n liveAgentCount: health.liveAgentCount,\n inflightTurnCount: health.inflightTurnCount,\n ...(target.startError() === undefined ? {} : { lastStartError: target.startError() }),\n reasons: health.reasons,\n version: DSH_CHANNELS_VERSION,\n overlayPath,\n busy: target.isRestarting() || target.isConnecting(),\n }\n}\n\n/**\n * Stateful panel facade over one service + one overlay file. All mutations are\n * validated here; the HTTP layer only parses the transport. The optional\n * [`SettingsCatalog`] turns the four model selectors into host-driven choice\n * lists; without it (headless tests, host without LLM services) they stay\n * free-text controls.\n */\nexport class SettingsSurface {\n constructor(\n private readonly target: SettingsTarget,\n private readonly store: OverlayStore,\n private readonly logger?: OverlayLogger,\n private readonly catalog?: SettingsCatalog,\n private readonly directoryPicker?: SettingsDirectoryPicker,\n ) {}\n\n /**\n * Re-read credential presence. The credentials store is hot-reloaded from\n * disk, so a value edited outside the panel becomes visible within one poll\n * instead of only after a restart/connect.\n */\n private async syncReadiness(): Promise<void> {\n try {\n await this.target.refreshReadiness()\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 凭据状态刷新失败:%s', messageOf(error))\n }\n }\n\n /**\n * Reject panel writes while the connection lifecycle is in flight: saving an\n * overlay or credentials then would race `applyOverlay()`/`start()` and can\n * record a running-config snapshot that never took effect.\n */\n private writeBusy(): { ok: false; error: string } | undefined {\n if (!this.target.isRestarting() && !this.target.isConnecting()) return undefined\n return { ok: false, error: '飞书连接正在连接或重启,请稍候再试' }\n }\n\n /** Wire status only (used by the client's 5 s poll). */\n async status(): Promise<ChannelStatusWire> {\n await this.syncReadiness()\n return buildStatus(this.target, this.store.file)\n }\n\n /**\n * Resolve the selector catalog for one route (the client re-queries it when\n * the draft provider/model changes). A broken lookup degrades to the\n * free-text form instead of failing the panel.\n */\n async catalogFor(provider?: string, model?: string): Promise<CatalogWire> {\n if (this.catalog === undefined) return emptyCatalog()\n try {\n return await this.catalog.catalog(cleanCatalogQuery(provider), cleanCatalogQuery(model))\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 选择目录加载失败:%s', messageOf(error))\n return emptyCatalog()\n }\n }\n\n /** Status + editable config with provenance + read-only facts. */\n async panel(): Promise<PanelWire> {\n await this.syncReadiness()\n const base = this.target.baseConfigSnapshot()\n const effective = this.target.configSnapshot()\n const overlay = await this.store.load()\n const running = this.target.runningConfigSnapshot()\n const overriddenKeys = EDITABLE_KEYS.filter(key => overlay[key] !== undefined)\n const restartPendingKeys = this.target.isRunning() && running !== undefined\n ? RESTART_KEYS.filter(key => effective[key] !== running[key])\n : []\n const catalog = await this.catalogFor(effective.provider, effective.model)\n const fields = withDirectoryBrowse(\n applyCatalog(FIELD_DESCRIPTORS, catalog, {\n provider: effective.provider,\n model: effective.model,\n reasoningEffort: effective.reasoningEffort,\n agentPreset: effective.agentPreset,\n }),\n this.directoryPicker?.available === true,\n )\n\n let credentialStates: { appId: CredentialInfo; appSecret: CredentialInfo } = {\n appId: { configured: false, writable: false },\n appSecret: { configured: false, writable: false },\n }\n try {\n credentialStates = await this.target.credentialStates()\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 凭据状态读取失败:%s', messageOf(error))\n }\n const toWire = (ref: string, state: CredentialInfo): CredentialRefWire => ({\n ref,\n configured: state.configured,\n writable: state.writable === true,\n ...(state.source === undefined ? {} : { source: state.source }),\n })\n const credentials: PanelConfigWire['credentials'] = {\n appId: toWire(effective.appId, credentialStates.appId),\n appSecret: toWire(effective.appSecret, credentialStates.appSecret),\n }\n\n // The cleanup preview is derived from live workspace/route state; a broken\n // seam must not break the whole panel, so it degrades to zero counts + error.\n let cleanup: CleanupPreviewWire\n try {\n cleanup = await this.target.cleanupPreview()\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 工作区清理预览失败:%s', messageOf(error))\n cleanup = {\n retention: effective.workspaceRetention,\n auto: effective.workspaceRetentionAuto,\n workspaces: 0,\n candidates: 0,\n candidateBytes: 0,\n error: messageOf(error),\n }\n }\n\n // The access snapshot comes from in-memory maps, but a broken seam must\n // still not take the whole panel down: degrade to an empty, fail-closed\n // view (the whitelist mutations will report their own errors).\n let access: AccessWire\n try {\n access = this.target.accessSnapshot()\n } catch (error) {\n this.logger?.warn?.('dsh-channels: 访问授权快照读取失败:%s', messageOf(error))\n access = {\n mode: effective.accessMode,\n admin: { configured: false },\n loaded: false,\n groups: [],\n users: [],\n requests: [],\n }\n }\n\n return {\n status: buildStatus(this.target, this.store.file),\n config: {\n base: pickEditable(base) as Record<string, ConfigValue | undefined>,\n effective: pickEditable(effective) as Record<string, ConfigValue | undefined>,\n overlay: overlay as EditableValues,\n overriddenKeys,\n restartPendingKeys,\n fields,\n readOnly: readOnlyFields(effective),\n credentials,\n },\n cleanup,\n access,\n }\n }\n\n /** Validate + persist a partial edit; live fields take effect immediately. */\n async save(values: EditableValues): Promise<SaveConfigResponse> {\n const busy = this.writeBusy()\n if (busy !== undefined) return busy\n const unknown = Object.keys(values).filter(key => !(EDITABLE_KEYS as readonly string[]).includes(key))\n if (unknown.length > 0) {\n return { ok: false, error: `未知配置项:${unknown.join(', ')}` }\n }\n\n const raw: Record<string, unknown> = {}\n const clearedKeys: EditableKey[] = []\n for (const key of EDITABLE_KEYS) {\n const value = values[key]\n if (value === undefined) continue\n if (typeof value === 'string' && value === '' && OPTIONAL_TEXT_KEYS.has(key)) {\n clearedKeys.push(key)\n continue\n }\n raw[key] = value\n }\n\n const parsed = parseOverlay(raw)\n if (!parsed.ok) {\n const issues: FieldIssueWire[] = parsed.issues.map(issue => ({\n key: issue.path,\n message: issue.message,\n }))\n return { ok: false, error: '配置校验失败', issues }\n }\n\n // A workspace root typed into the panel must be usable right now: create\n // and probe it before anything is persisted, so a typo (or a path that is\n // an existing file / unwritable) fails here rather than as a session\n // creation failure card inside Feishu.\n if (parsed.patch.baseDir !== undefined) {\n const problem = await prepareWorkspaceBaseDir(parsed.patch.baseDir)\n if (problem !== undefined) {\n return {\n ok: false,\n error: '工作区目录不可用',\n issues: [{ key: 'baseDir', message: problem }],\n }\n }\n }\n\n let merged: OverlayPatch\n try {\n merged = { ...await this.store.load() }\n for (const key of clearedKeys) delete merged[key]\n Object.assign(merged, parsed.patch)\n } catch (error) {\n return { ok: false, error: `写入覆盖配置失败:${messageOf(error)}` }\n }\n\n // The host resolves a request route as a provider/model PAIR: a lone\n // provider (or lone model) leaves the session without a complete route,\n // so an edit to either side must end with both set or both cleared.\n if (values.provider !== undefined || values.model !== undefined) {\n const prospective = { ...this.target.baseConfigSnapshot(), ...merged }\n const hasProvider = (prospective.provider ?? '') !== ''\n const hasModel = (prospective.model ?? '') !== ''\n if (hasProvider !== hasModel) {\n return {\n ok: false,\n error: '模型服务商与模型 id 必须同时配置,或都留空沿用部署默认',\n issues: [hasProvider\n ? { key: 'model', message: '已选择模型服务商,还需要选择模型 id(或把两者都留空)' }\n : { key: 'provider', message: '已选择模型 id,还需要选择模型服务商(或把两者都留空)' }],\n }\n }\n }\n\n try {\n await this.store.save(merged)\n } catch (error) {\n return { ok: false, error: `写入覆盖配置失败:${messageOf(error)}` }\n }\n this.target.applyOverlay(merged)\n return { ok: true, panel: await this.panel(), clearedKeys }\n }\n\n /** Drop every Web override and fall back to the profile config. */\n async clear(): Promise<ClearConfigResponse> {\n const busy = this.writeBusy()\n if (busy !== undefined) return busy\n try {\n await this.store.clear()\n } catch (error) {\n return { ok: false, error: `清除覆盖配置失败:${messageOf(error)}` }\n }\n this.target.applyOverlay({})\n return { ok: true, panel: await this.panel() }\n }\n\n /** Write provided credential values through the host seam (never echoed back). */\n async saveCredentials(values: SaveCredentialsRequest): Promise<SaveCredentialsResponse> {\n const busy = this.writeBusy()\n if (busy !== undefined) return busy\n const writes: Array<{ field: CredentialField; value: string }> = []\n const issues: FieldIssueWire[] = []\n for (const field of ['appId', 'appSecret'] as const) {\n const raw = values[field]\n if (raw === undefined) continue\n const normalized = normalizeCredentialValue(raw)\n if (!normalized.ok) {\n issues.push({ key: field, message: normalized.message })\n continue\n }\n writes.push({ field, value: normalized.value })\n }\n if (issues.length > 0) return { ok: false, error: '凭据校验失败', issues }\n if (writes.length === 0) return { ok: false, error: '请至少填写一项凭据(App ID 或 App Secret)' }\n\n try {\n for (const write of writes) await this.target.setCredential(write.field, write.value)\n } catch (error) {\n return { ok: false, error: messageOf(error) }\n }\n return this.finishCredentialChange('save')\n }\n\n /** Remove the stored value behind one credential reference. */\n async clearCredential(field: CredentialField): Promise<ClearCredentialResponse> {\n const busy = this.writeBusy()\n if (busy !== undefined) return busy\n if (field !== 'appId' && field !== 'appSecret') {\n return { ok: false, error: '未知的凭据字段' }\n }\n try {\n await this.target.clearCredential(field)\n } catch (error) {\n return { ok: false, error: messageOf(error) }\n }\n return this.finishCredentialChange('clear')\n }\n\n /**\n * Credential values are resolved on connect. Opening a connection is gated on\n * readiness: saving one half of the pair stores the value but keeps the\n * plugin idle until the other half exists. When the channel is stopped and\n * the pair is complete, connect immediately (nothing to interrupt); when it\n * is running, the user reconnects to pick the value up.\n */\n private async finishCredentialChange(kind: 'save' | 'clear'): Promise<SaveCredentialsResponse> {\n if (this.target.isRunning()) {\n return { ok: true, panel: await this.panel(), reconnect: 'pending' }\n }\n if (kind === 'clear' || !this.target.isConfigured()) {\n return { ok: true, panel: await this.panel(), reconnect: kind === 'clear' ? 'not-needed' : 'unconfigured' }\n }\n try {\n await this.target.connect()\n return { ok: true, panel: await this.panel(), reconnect: 'auto' }\n } catch (error) {\n // The value is stored, but the channel did not come up: report the\n // failure state instead of claiming a successful connection.\n return {\n ok: true,\n panel: await this.panel(),\n reconnect: 'failed',\n autoReconnectError: messageOf(error),\n }\n }\n }\n\n /**\n * The panel's single connect/reconnect action: reopen a running connection,\n * or open the first one when the channel is stopped. Credentials must be\n * complete — otherwise the panel points the user back at the credentials form\n * instead of surfacing a startup error.\n */\n async reconnect(): Promise<ReconnectResponse> {\n // Credentials may have been edited on disk since the last poll; re-check\n // presence before refusing the action.\n await this.syncReadiness()\n if (!this.target.isConfigured()) {\n return {\n ok: false,\n error: '飞书应用凭据尚未配置完整,请先填写 App ID 与 App Secret 并保存',\n unconfigured: true,\n }\n }\n const wasRunning = this.target.isRunning()\n try {\n if (wasRunning) await this.target.restart()\n else await this.target.connect()\n } catch (error) {\n return { ok: false, error: `${wasRunning ? '重连' : '连接'}失败:${messageOf(error)}` }\n }\n // `restart()` intentionally does not throw when the credentials vanished;\n // verify the connection actually exists before reporting success.\n if (!this.target.isRunning()) {\n return {\n ok: false,\n unconfigured: !this.target.isConfigured(),\n error: this.target.startError()\n ?? (this.target.isConfigured() ? '飞书连接未建立' : '飞书应用凭据尚未配置完整,请先填写并保存凭据'),\n }\n }\n return { ok: true, panel: await this.panel() }\n }\n\n /** Read the current admission snapshot (mode, administrator, whitelists, requests). */\n async access(): Promise<AccessWire> {\n return this.target.accessSnapshot()\n }\n\n /** Authorize one group from the panel. Validation happens before the write. */\n async saveAccessGroup(chatId: unknown, name?: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(chatId, '群 chat_id')\n if (!id.ok) return { ok: false, error: id.message }\n const label = normalizeAccessName(name)\n if (!label.ok) return { ok: false, error: label.message }\n return this.mutateAccess(() => this.target.addAccessGroup(id.value, label.value))\n }\n\n /** Revoke one group authorization (immediate: the next message re-evaluates). */\n async removeAccessGroup(chatId: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(chatId, '群 chat_id')\n if (!id.ok) return { ok: false, error: id.message }\n return this.mutateAccess(() => this.target.removeAccessGroup(id.value))\n }\n\n /** Authorize one user `open_id`; `ou_` prefix is required (not user_id/email). */\n async saveAccessUser(openId: unknown, name?: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(openId, '用户 open_id', '用户 open_id 必须形如 ou_xxx(不是 user_id/邮箱)')\n if (!id.ok) return { ok: false, error: id.message }\n const label = normalizeAccessName(name)\n if (!label.ok) return { ok: false, error: label.message }\n return this.mutateAccess(() => this.target.addAccessUser(id.value, label.value))\n }\n\n /** Revoke one user authorization (immediate: the next message re-evaluates). */\n async removeAccessUser(openId: unknown): Promise<AccessMutationResponse> {\n const id = normalizeAccessId(openId, '用户 open_id', '用户 open_id 必须形如 ou_xxx(不是 user_id/邮箱)')\n if (!id.ok) return { ok: false, error: id.message }\n return this.mutateAccess(() => this.target.removeAccessUser(id.value))\n }\n\n /**\n * Settle one pending request. Shares the target-side resolver with the DM\n * card, so the panel and the card can never disagree on the outcome.\n */\n async resolveAccessRequest(token: unknown, decision: unknown): Promise<AccessMutationResponse> {\n const normalized = normalizeAccessId(token, '申请 token')\n if (!normalized.ok) return { ok: false, error: normalized.message }\n if (decision !== 'allow' && decision !== 'deny') {\n return { ok: false, error: 'decision 必须是 allow 或 deny' }\n }\n return this.mutateAccess(() => this.target.resolveAccessRequest(normalized.value, decision))\n }\n\n /** Run one whitelist mutation, folding thrown seam errors into the wire type. */\n private async mutateAccess(\n mutation: () => Promise<AccessMutationResponse>,\n ): Promise<AccessMutationResponse> {\n try {\n return await mutation()\n } catch (error) {\n return { ok: false, error: messageOf(error) }\n }\n }\n\n /**\n * Open the host's native directory chooser (the panel's「选择目录…」button).\n *\n * @param signal - caller lifetime; abort closes the OS dialog.\n * @returns the chosen absolute path, or `null` when the operator cancels.\n * @throws when the host provides no native directory chooser.\n */\n async pickDirectory(signal: AbortSignal): Promise<string | null> {\n if (this.directoryPicker === undefined) throw new Error(PANEL_COPY.directoryPickFailed)\n return await this.directoryPicker.pick(signal)\n }\n\n /**\n * Run the workspace-retention cleanup once. Irreversible deletions are the\n * target's responsibility; the surface only reports the outcome and returns\n * a refreshed panel (its cleanup preview then reflects the remaining workspaces).\n */\n async runCleanup(): Promise<CleanupResponse> {\n const busy = this.writeBusy()\n if (busy !== undefined) return busy\n let result: CleanupRunWire\n try {\n result = await this.target.runWorkspaceCleanup()\n } catch (error) {\n return { ok: false, error: `清理失败:${messageOf(error)}` }\n }\n return { ok: true, result, panel: await this.panel() }\n }\n}\n\nexport { PANEL_COPY }\n","/**\n * Web 设置面板的 Host 侧选择器目录:把宿主 LLM 服务注册的 provider / model\n * / 推理档位,以及 agent-preset roster 折叠成面板可渲染的选项列表。\n *\n * 目录只用于「选择」:任何查询失败(服务未挂载、方法缺失、超时、抛错)都\n * 降级为 `selectable: false`,客户端据此退回原有自由文本输入框,绝不让\n * 面板加载依赖宿主的网络状态。成功结果按短 TTL 缓存,避免用户来回切换\n * provider/model 时反复查询适配器。\n *\n * @module dsh-channels/settings/catalog\n */\n\nimport type {\n AgentDefaultModel,\n AgentPreset,\n AgentPresets,\n HostContext,\n LlmModelInfo,\n LlmProviderInfo,\n} from '../dsh-types.ts'\nimport type { CatalogWire, FieldOptionWire, SelectCatalogWire } from './contract.ts'\n\n/** 目录查询的硬上限:面板加载不能被宿主的模型发现/连接拖住。 */\nconst LOOKUP_TIMEOUT_MS = 4_000\n\n/** 成功查询的缓存时长(毫秒)。 */\nconst CACHE_TTL_MS = 30_000\n\nexport type CatalogWarn = (message: string, ...args: unknown[]) => void\n\n/** Host capability used by the settings panel to populate its selectors. */\nexport interface SettingsCatalog {\n /**\n * Resolve the selector catalog for one configured route.\n * @param provider - configured provider route (absent = inherit deployment default).\n * @param model - configured model id (absent = inherit deployment default).\n * @returns selector options; groups the host cannot enumerate are `selectable: false`.\n */\n catalog(provider?: string, model?: string): Promise<CatalogWire>\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Resolve `work` or `undefined` after `ms`, so a hanging adapter cannot stall the panel. */\nasync function within<T>(work: Promise<T>, ms: number): Promise<T | undefined> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => { resolve(undefined) }, ms)\n timer.unref?.()\n })\n try {\n return await Promise.race([work, timeout])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\n/** Leading \"inherit / clear\" choice every selector gets. */\nfunction inheritOption(label: string): FieldOptionWire {\n return { value: '', label }\n}\n\n/** `Display name(id)` when the host publishes both, else the id alone. */\nfunction labelledOption(id: string, name: string | undefined): FieldOptionWire {\n const display = name === undefined ? '' : name.trim()\n return { value: id, label: display === '' || display === id ? id : `${display}(${id})` }\n}\n\n/** Non-empty display text, otherwise undefined. */\nfunction textOf(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed === '' ? undefined : trimmed\n}\n\n/**\n * Build the catalog over one live host context.\n * @param ctx - host context carrying `llm` and the optional preset/default-model services.\n * @param warn - optional diagnostic sink for failed lookups.\n * @returns the catalog facade.\n */\nexport function createHostCatalog(ctx: HostContext, warn?: CatalogWarn): SettingsCatalog {\n const llm = ctx.llm\n const presets = ctx.get?.('agentPresets') as AgentPresets | undefined\n const defaultModel = ctx.get?.('agentDefaultModel') as AgentDefaultModel | undefined\n const cache = new Map<string, { at: number; value: unknown }>()\n\n /** Cache a successful lookup; failures are re-tried on the next panel refresh. */\n async function cached<T>(key: string, load: () => Promise<T> | T): Promise<T | undefined> {\n const hit = cache.get(key)\n if (hit !== undefined && Date.now() - hit.at < CACHE_TTL_MS) return hit.value as T\n try {\n const value = await within(Promise.resolve(load()), LOOKUP_TIMEOUT_MS)\n if (value === undefined) return undefined\n cache.set(key, { at: Date.now(), value })\n return value\n } catch (error) {\n warn?.('dsh-channels: 读取 %s 选择目录失败:%s', key, messageOf(error))\n return undefined\n }\n }\n\n /** Registered provider routes, `undefined` when the deployment exposes none. */\n async function providerOptions(): Promise<FieldOptionWire[] | undefined> {\n if (typeof llm.listProviders !== 'function') return undefined\n const rows = await cached<readonly LlmProviderInfo[]>('providers', () => llm.listProviders!())\n if (rows === undefined) return undefined\n const options: FieldOptionWire[] = []\n const seen = new Set<string>()\n for (const row of rows) {\n const id = textOf((row as { id?: unknown } | undefined)?.id)\n if (id === undefined || seen.has(id)) continue\n seen.add(id)\n options.push(labelledOption(id, textOf((row as { name?: unknown }).name)))\n }\n return options.length === 0 ? undefined : options\n }\n\n /** Models advertised for one provider route, `undefined` when it has no catalog. */\n async function modelOptions(provider: string): Promise<FieldOptionWire[] | undefined> {\n if (provider === '' || typeof llm.listModels !== 'function') return undefined\n const rows = await cached<readonly LlmModelInfo[]>(`models:${provider}`, () => llm.listModels!(provider))\n if (rows === undefined) return undefined\n const options: FieldOptionWire[] = []\n const seen = new Set<string>()\n for (const row of rows) {\n const id = textOf((row as { id?: unknown } | undefined)?.id)\n if (id === undefined || seen.has(id)) continue\n seen.add(id)\n options.push(labelledOption(id, textOf((row as { name?: unknown }).name)))\n }\n return options.length === 0 ? undefined : options\n }\n\n /** Adapter metadata for one exact route; `undefined` when it cannot be resolved. */\n async function modelInfo(provider: string, model: string): Promise<LlmModelInfo | undefined> {\n if (provider === '' || model === '') return undefined\n return await cached<LlmModelInfo>(`model-info:${provider}/${model}`, () => llm.resolveModelInfo(provider, model))\n }\n\n /** Declared preset rows, `undefined` when the roster has no `list()` or no rows. */\n async function presetRows(): Promise<readonly AgentPreset[] | undefined> {\n if (typeof presets?.list !== 'function') return undefined\n const rows = await cached<readonly AgentPreset[]>('presets', () => presets!.list!())\n if (rows === undefined || rows.length === 0) return undefined\n const filtered = rows.filter(row => textOf((row as { id?: unknown } | undefined)?.id) !== undefined)\n return filtered.length === 0 ? undefined : filtered\n }\n\n /** Roster default preset id, when the implementation publishes it. */\n function defaultPresetId(): string | undefined {\n try {\n return textOf(presets?.defaultId)\n } catch {\n return undefined\n }\n }\n\n /** Deployment default model selection, when the host exposes `agentDefaultModel`. */\n function deploymentDefault(): CatalogWire['deploymentDefault'] | undefined {\n try {\n const selection = defaultModel?.currentSelection?.()\n const provider = textOf(selection?.provider)\n const model = textOf(selection?.model)\n if (provider === undefined || model === undefined) return undefined\n const reasoningEffort = textOf(selection?.reasoningEffort)\n return { provider, model, ...(reasoningEffort === undefined ? {} : { reasoningEffort }) }\n } catch {\n return undefined\n }\n }\n\n /** Selector for the configured provider route. */\n function providerWire(\n providers: readonly FieldOptionWire[] | undefined,\n deployment: CatalogWire['deploymentDefault'],\n ): SelectCatalogWire {\n if (providers === undefined) return { selectable: false, options: [] }\n const label = deployment?.provider === undefined\n ? '留空 = 部署默认'\n : `留空 = 部署默认(${deployment.provider})`\n return { selectable: true, options: [inheritOption(label), ...providers] }\n }\n\n /** Selector for the configured model id. */\n function modelWire(\n models: readonly FieldOptionWire[] | undefined,\n provider: string,\n deployment: CatalogWire['deploymentDefault'],\n providers: readonly FieldOptionWire[] | undefined,\n ): SelectCatalogWire {\n const label = deployment?.model === undefined\n ? '留空 = 部署默认'\n : `留空 = 部署默认(${deployment.model})`\n if (models === undefined) {\n // Provider selectable but none configured yet: keep the model selector\n // visible and disabled so the dependency is discoverable. A provider the\n // adapter cannot enumerate instead falls back to free text.\n if (providers !== undefined && provider === '') {\n return {\n selectable: true,\n options: [inheritOption(label)],\n disabled: true,\n help: '先选择模型服务商,再选择模型 id。留空表示沿用部署默认。',\n }\n }\n return { selectable: false, options: [] }\n }\n const select: SelectCatalogWire = {\n selectable: true,\n options: [inheritOption(label), ...models],\n }\n if (provider === '') {\n select.disabled = true\n select.help = '先选择模型服务商,再选择模型 id。留空表示沿用部署默认。'\n }\n return select\n }\n\n /** Selector for the configured reasoning effort. */\n function effortWire(\n info: LlmModelInfo | undefined,\n provider: string,\n model: string,\n providers: readonly FieldOptionWire[] | undefined,\n ): SelectCatalogWire {\n // 无法枚举 provider 时,整条选择链都退回自由文本输入。\n if (providers === undefined) return { selectable: false, options: [] }\n if (provider === '' || model === '') {\n return {\n selectable: true,\n options: [inheritOption('留空 = 适配器默认')],\n disabled: true,\n help: '先选择模型服务商与模型 id,再选择推理档位。',\n }\n }\n // 路由无法解析(适配器报错/超时):退回自由文本,保留手填能力。\n if (info === undefined) return { selectable: false, options: [] }\n const efforts = (info.reasoning?.efforts ?? [])\n .filter(effort => textOf(effort.id) !== undefined)\n .map(effort => labelledOption(effort.id, textOf(effort.name)))\n if (efforts.length === 0) {\n return {\n selectable: true,\n options: [inheritOption('留空 = 适配器默认')],\n disabled: true,\n help: '当前模型没有声明可选推理档位,只能使用适配器默认。',\n }\n }\n const defaultEffort = textOf(info.reasoning?.defaultEffort)\n return {\n selectable: true,\n options: [\n inheritOption(defaultEffort === undefined\n ? '留空 = 适配器默认'\n : `留空 = 适配器默认(${defaultEffort})`),\n ...efforts,\n ],\n }\n }\n\n /** Selector for the agent preset id. */\n function presetWire(\n rows: readonly AgentPreset[] | undefined,\n defaultId: string | undefined,\n ): SelectCatalogWire {\n if (rows === undefined) return { selectable: false, options: [] }\n const options = rows.map((row) => {\n const id = textOf(row.id)!\n const option = labelledOption(id, textOf(row.name))\n if (row.broken === undefined) return option\n return { ...option, label: `${option.label}(不可用)` }\n })\n return {\n selectable: true,\n options: [\n inheritOption(defaultId === undefined ? '留空 = roster 默认' : `留空 = roster 默认(${defaultId})`),\n ...options,\n ],\n }\n }\n\n return {\n async catalog(provider?: string, model?: string): Promise<CatalogWire> {\n const wantedProvider = textOf(provider) ?? ''\n const wantedModel = textOf(model) ?? ''\n const deployment = deploymentDefault()\n const defaultPreset = defaultPresetId()\n\n const [providers, models, info, rows] = await Promise.all([\n providerOptions(),\n modelOptions(wantedProvider),\n modelInfo(wantedProvider, wantedModel),\n presetRows(),\n ])\n\n return {\n provider: providerWire(providers, deployment),\n model: modelWire(models, wantedProvider, deployment, providers),\n reasoningEffort: effortWire(info, wantedProvider, wantedModel, providers),\n agentPreset: presetWire(rows, defaultPreset),\n ...(deployment === undefined ? {} : { deploymentDefault: deployment }),\n ...(defaultPreset === undefined ? {} : { defaultPresetId: defaultPreset }),\n }\n },\n }\n}\n","/**\n * 系统目录选择器的 Host 适配层:把宿主可选的 `directoryPicker` 服务折叠成\n * 设置面板的最小 picker。\n *\n * 宿主的目录选择 seam 有两种后端(`native` / `browse`,见\n * `@deepseek-ai/dsh-host-directory-picker-auto` 的启动判定):\n *\n * - `native`:桌面/本地 GUI 会话,`pick(signal)` 打开操作系统的目录选择框;\n * 面板据此给「工作区根目录」加「选择目录…」按钮。\n * - `browse`:远程/无头/SSH 会话,没有可服务的显示会话,只能由前端自绘\n * 目录浏览器;本插件不实现那套 UI,`browse` 后端按“不可用”处理,字段\n * 保持手写输入。\n *\n * 与 `src/host/` 的其余桥接一致:这里只声明消费到的结构类型,不 import\n * 任何 DSH 内部包,因此宿主缺该服务(旧版本/无 GUI)时插件照常加载。\n *\n * @module dsh-channels/settings/directory-picker\n */\n\nimport type { HostContext } from '../dsh-types.ts'\n\n/** 宿主 native 能力的最小结构副本(`kind` + `pick`,其余字段不关心)。 */\nexport interface NativeDirectoryPickerCapability {\n kind: 'native'\n /**\n * 打开系统目录选择框。\n * @param signal - 调用方生命周期;中止会关闭选择框。\n * @returns 绝对路径;操作者取消时为 `null`。\n */\n pick(signal: AbortSignal): Promise<string | null>\n}\n\n/** 宿主能力联合的保守副本:只识别 `native`,其余后端一律视为不可用。 */\nexport type HostDirectoryPickerCapability =\n | NativeDirectoryPickerCapability\n | { kind: string; [key: string]: unknown }\n\n/** `ctx.directoryPicker` 的结构副本(抽象服务,只有一个 `capability()`)。 */\nexport interface HostDirectoryPickerService {\n capability(): HostDirectoryPickerCapability\n}\n\n/** 设置面板消费的最小 picker(surface / routes 只依赖这个接口,便于单测)。 */\nexport interface SettingsDirectoryPicker {\n /** 宿主是否提供可用的系统目录选择框。 */\n readonly available: boolean\n /**\n * 打开系统目录选择框;同一 picker 同时只允许一个选择框。\n * @param signal - 调用方生命周期;中止会关闭选择框。\n * @returns 绝对路径;操作者取消时为 `null`。\n */\n pick(signal: AbortSignal): Promise<string | null>\n}\n\n/** 取出 native 能力;服务缺失、后端为 browse 或 capability 抛错都返回 undefined。 */\nfunction nativeCapability(\n service: HostDirectoryPickerService | undefined,\n): NativeDirectoryPickerCapability | undefined {\n if (service === undefined || typeof service.capability !== 'function') return undefined\n let capability: unknown\n try {\n capability = service.capability()\n } catch {\n // A broken picker seam must not break the settings panel: fall back to text.\n return undefined\n }\n if (typeof capability !== 'object' || capability === null) return undefined\n const record = capability as { kind?: unknown; pick?: unknown }\n if (record.kind !== 'native' || typeof record.pick !== 'function') return undefined\n return capability as NativeDirectoryPickerCapability\n}\n\n/**\n * 从宿主上下文取出可用的系统目录选择器。\n *\n * 解析是**惰性**的:插件可能先于 `dsh-web-app` 的 directory-picker 行完成\n * apply,而设置面板只会在宿主全部加载完成后被请求。因此 `available` 与\n * `pick()` 每次都按需解析(成功结果缓存;失败不缓存,晚挂载的服务仍能被\n * 发现),避免把加载时序固化成「永远没有按钮」。\n *\n * @param ctx - 宿主上下文;`ctx.get('directoryPicker')` 是可选服务。\n * @returns picker;`available` 为 false 时面板保留手写输入。\n */\nexport function createSettingsDirectoryPicker(ctx: HostContext): SettingsDirectoryPicker {\n let resolved: NativeDirectoryPickerCapability | undefined\n const resolve = (): NativeDirectoryPickerCapability | undefined => {\n if (resolved !== undefined) return resolved\n try {\n const service = ctx.get?.('directoryPicker') as HostDirectoryPickerService | undefined\n resolved = nativeCapability(service)\n } catch {\n // A throwing service lookup must not take the settings panel down.\n resolved = undefined\n }\n return resolved\n }\n /** 互斥:一个原生模态框同时只应存在一个(双击/多标签页不得叠窗)。 */\n let picking = false\n return {\n get available(): boolean {\n return resolve() !== undefined\n },\n async pick(signal: AbortSignal): Promise<string | null> {\n const capability = resolve()\n if (capability === undefined) throw new Error('当前宿主机不提供系统目录选择器')\n if (picking) throw new Error('已有目录选择窗口正在打开')\n if (signal.aborted) return null\n picking = true\n try {\n const picked = await capability.pick.call(capability, signal)\n if (picked === null || picked === undefined) return null\n if (typeof picked !== 'string' || picked.trim() === '') {\n throw new Error('目录选择器返回了无效路径')\n }\n return picked\n } finally {\n picking = false\n }\n },\n }\n}\n","/**\n * dsh-channels — DeepSeek Harness 飞书渠道插件。\n *\n * 一个 dsh 实例对应一个飞书机器人;用户通过私聊、群聊 @、话题群 @ 三种方式\n * 发起会话。每条触发消息:立即加 “Typing” 表情 → 以话题形式回复(创建话题)\n * → 绑定 DSH session → 卡片 2.0 流式回复(推理折叠面板 + 富文本正文 + 两行\n * session 信息,默认 500ms 刷新)→ 流式结束移除 Typing,成功更新为\n * “DONE”,失败显示结构化错误并更新为 “No”。\n *\n * 工作区归属:私聊一人一工作区;群聊中每个用户在该群拥有独立工作区\n * (目录即工作区,会话以 cwd 落盘)。\n *\n * @module dsh-channels\n */\n\nimport type { HostContext } from './dsh-types.ts'\nimport { FeishuService } from './service.ts'\nimport { pluginConfigSchema, resolveConfig } from './config.ts'\nimport type { FeishuConfig } from './config.ts'\nimport { defaultChannelFactory } from './feishu-channel.ts'\nimport type { ChannelFactory } from './feishu-channel.ts'\nimport { OverlayStore, defaultOverlayPath } from './settings/overlay.ts'\nimport { mountSettingsRoutes } from './settings/routes.ts'\nimport { SettingsSurface } from './settings/surface.ts'\nimport { createHostCatalog } from './settings/catalog.ts'\nimport { createSettingsDirectoryPicker } from './settings/directory-picker.ts'\n\nexport type * from './config.ts'\nexport type * from './cards.ts'\nexport type * from './domain.ts'\nexport type * from './feishu-channel.ts'\nexport type * from './approvals.ts'\nexport type * from './commands.ts'\nexport type * from './health.ts'\nexport type * from './session-commands.ts'\nexport type * from './session-export.ts'\nexport type * from './stats.ts'\nexport type * from './questions.ts'\nexport type * from './settings/contract.ts'\nexport * from './access/index.ts'\nexport type { AccessGroupRecord, AccessRequestRecord, AccessUserRecord } from './domain.ts'\nexport { CATALOG_ROUTE, CLEANUP_ROUTE, CONFIG_ROUTE, CREDENTIALS_ROUTE, EDITABLE_KEYS, PANEL_COPY, PANEL_ROUTE, PICK_DIRECTORY_ROUTE, RECONNECT_ROUTE, RESTART_KEYS, STATUS_ROUTE } from './settings/contract.ts'\nexport type { PickDirectoryResponse } from './settings/contract.ts'\nexport { defaultOverlayPath, mergeOverlay, normalizeOverlay, OverlayStore, parseOverlay, pickEditable, resolveDshHome } from './settings/overlay.ts'\nexport { applyCatalog, emptyCatalog, FIELD_DESCRIPTORS, normalizeCredentialValue, readOnlyFields, SettingsSurface, withDirectoryBrowse } from './settings/surface.ts'\nexport type { SettingsTarget } from './settings/surface.ts'\nexport { createHostCatalog } from './settings/catalog.ts'\nexport type { CatalogWarn, SettingsCatalog } from './settings/catalog.ts'\nexport { createSettingsDirectoryPicker } from './settings/directory-picker.ts'\nexport type { HostDirectoryPickerService, SettingsDirectoryPicker } from './settings/directory-picker.ts'\nexport { isSameOriginRequest, mountSettingsRoutes, registerSettingsRoutes } from './settings/routes.ts'\nexport type { HostWebServer } from './settings/routes.ts'\nexport type { ApprovalOutcome, ApprovalRequest, ApprovalAnswererListener, AssistantStreamFrame, AskUserQuestionAnswer, AskUserQuestionAnswerItem, AskUserQuestionItem, AskUserQuestionRequestEvent, UserQuestionAnswererListener, AgentCancelCause, SessionStatsView, TokenUsageView, ContextPressureView, SessionProjections } from './dsh-types.ts'\nexport { buildCard, buildInterruptionCard, cardJsonBytes, CARD_MAX_BYTES, CARD_TARGET_BYTES, CARD_PANEL_RESERVE_BYTES, REASONING_MAX_BYTES, INTERRUPTION_NOTICE, MAX_TABLES_PER_CARD, PANEL_TITLES, PANEL_CHEVRON_TOKEN, segmentCardContent, splitBodyForCard, stripTables, tableBlocks, TRUNCATION_SUFFIX, buildApprovalCard, buildApprovalSettledCard, buildQuestionCard, buildQuestionSettledCard, MAX_QUESTION_OPTIONS } from './cards.ts'\nexport { ApprovalAnswerer } from './approvals.ts'\nexport { QuestionAnswerer } from './questions.ts'\nexport { armModelCommand, buildCommandResultCard, CHANNEL_COMMAND_NAMES, commandResultCardKind, isSupportedCommand, parseSlashCommand, registerChannelCommands, registerModelCommand, RouteOperationQueue, SUPPORTED_COMMAND_NAMES, supportedCommandHelp, supportedCommandList } from './commands.ts'\nexport { buildHealthCard, countFiberStates, foldHealthSnapshot, HEALTH_DEPENDENCY_NAMES, maskOpenIdTail } from './health.ts'\nexport { buildCompactComparisonCard, buildRenewCard, buildSessionHistoryCard, buildSessionStatusCard, buildWorkDirCard, compactFailureContext, querySessionHistory, resolveWorkDir, SESSION_HISTORY_DISPLAY_LIMIT } from './session-commands.ts'\nexport { createLogicalSessionExport, DSH_CHANNELS_VERSION, LOGICAL_EXPORT_FORMAT, LOGICAL_EXPORT_FORMAT_VERSION, serializeLogicalSession } from './session-export.ts'\nexport { addUsage, applyProjectionViews, averageDecodeTokensPerSecond, averageTtftMs, cacheHitPct, EMPTY_TOTALS, foldAll, foldEvent, foldSessionStats, measureSessionContext, readProjectionViews, usageFromBuckets } from './stats.ts'\nexport { FeishuService } from './service.ts'\nexport { TurnStreamer } from './streamer.ts'\nexport { ensureWorkspace, stagingPathFor, topicTokenFor, workspacePathFor, workspaceTitleFor } from './workspaces.ts'\nexport { accessGroupRecordSchema, accessRequestRecordSchema, accessUserRecordSchema, appendSessionHistory, chatRouteKey, expiredRouteRecordSchema, feishuDomain, normalizeRouteRecord, normalizeSessionHistory, routeRecordSchema, sessionHistoryEntrySchema, inflightSnapshotSchema, processedRecordSchema } from './domain.ts'\n\n/** Cordis function-plugin name. */\nexport const name = 'channels'\n\n/** Host services this plugin consumes (all resolved through ctx).\n *\n * `credentials` is listed even though the plugin could fall back to the\n * process environment: at boot the credentials provider may not be visible\n * yet (a race observed in real profiles), and inject makes the loader wait\n * for it. `llm` / `tokenMeter` feed the card's context line and ship with\n * every dsh-base profile.\n */\nexport const inject = ['agents', 'commands', 'workspaceRegistry', 'storageDomain', 'credentials', 'llm', 'tokenMeter'] as const\n\n/**\n * Cordis Standard Schema declaration for the plugin config.\n *\n * The loader validates (and default-fills) the raw composition config through\n * `Config['~standard'].validate()` *before* `apply()` runs, so a malformed\n * `cordis.patch.yml` entry fails at load time with a cordis `ValidationError`\n * instead of surfacing from inside the async service start. `apply()` keeps\n * its `resolveConfig()` fallback for direct invocation (unit tests, embedded\n * hosts) where config has not been pre-validated.\n */\nexport const Config = pluginConfigSchema\n\nexport interface FeishuPluginOptions extends FeishuConfig {\n /** Test seam: substitute a channel implementation. */\n channelFactory?: ChannelFactory\n}\n\n/**\n * Install the bridge. Credentials stay references here — the actual secrets\n * resolve through `ctx.credentials` (env > `$DSH_HOME/.credentials.yaml`\n * > `.env`) when `FeishuService.start()` builds the channel.\n */\nexport function apply(ctx: HostContext, config?: unknown): void {\n const resolved = resolveConfig(config)\n const factory: ChannelFactory = (config as FeishuPluginOptions | undefined)?.channelFactory\n ?? defaultChannelFactory\n // Web settings overlay (optional file; degrades to \"no overrides\").\n const overlay = new OverlayStore(\n resolved.settingsFile ?? defaultOverlayPath(),\n { warn: (message, ...args) => ctx.logger?.warn?.(message, ...args) },\n )\n const service = new FeishuService(ctx, resolved, factory, undefined, overlay)\n // Settings routes ride the host's web server when this deployment has one.\n // The catalog resolves the panel's model selectors from live host services;\n // the optional native directory picker backs the workspace-root「选择目录…」\n // button (absent in headless/remote compositions → plain text input).\n const warn = (message: string, ...args: unknown[]): void => { ctx.logger?.warn?.(message, ...args) }\n mountSettingsRoutes(ctx, new SettingsSurface(\n service,\n overlay,\n { warn },\n createHostCatalog(ctx, warn),\n createSettingsDirectoryPicker(ctx),\n ))\n ctx.effect(() => {\n // Boot loads the Web overlay and connects only when the Feishu application\n // credentials are complete; an incomplete configuration or a failed\n // connection is a panel-visible state, not a plugin failure. Only an\n // unexpected boot error (e.g. unreadable overlay) reaches this catch.\n void service.boot().catch((error: unknown) => {\n ctx.logger?.error?.('dsh-channels failed to boot: %s', String(error))\n // Critical lifecycle line duplicated to the console: cordis logger\n // messages emitted before a web exporter attaches are buffered and may\n // never reach stdout, which makes real-device debugging guesswork.\n console.error('[dsh-channels] failed to boot:', error)\n })\n return () => service.shutdown()\n }, 'dsh-channels')\n}\n"],"mappings":"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"}