@modesto_ma/dsh-channels 0.5.0 → 0.7.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/README.md +10 -10
- package/lib/client.js +53 -37
- package/lib/client.js.map +1 -1
- package/lib/index.js +446 -665
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/analyze/contract.ts +8 -3
- package/src/analyze/launcher.ts +28 -5
- package/src/analyze/server.ts +60 -54
- package/src/client/api.ts +28 -30
- package/src/client/catalog.ts +1 -1
- package/src/client/panel/advanced-tab.tsx +1 -1
- package/src/config.ts +4 -13
- package/src/health.ts +0 -12
- package/src/host/agent.ts +24 -1
- package/src/index.ts +6 -4
- package/src/service.ts +18 -38
- package/src/session/manager.ts +4 -4
- package/src/session/request-policy.ts +64 -5
- package/src/settings/contract.ts +49 -34
- package/src/settings/routes.ts +159 -443
- package/src/settings/surface.ts +5 -6
- package/src/streamer.ts +3 -2
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["messageOf","sourceLabel","requestDirectoryPicker"],"sources":["../src/settings/contract.ts","../src/client/icon.tsx","../src/client/avatar.tsx","../src/client/api.ts","../src/client/origin-cache.ts","../src/client/origin.tsx","../src/client/catalog.ts","../src/client/number-field.ts","../src/feishu/wiki/links.ts","../src/client/string-list.ts","../src/client/panel/shared.tsx","../src/client/panel/whitelist.tsx","../src/client/panel/access-tab.tsx","../src/client/panel/credentials-card.tsx","../src/client/panel/advanced-tab.tsx","../src/client/panel/connection-tab.tsx","../src/client/panel/fields-tab.tsx","../src/client/panel/profiles.ts","../src/client/panel/wiki-tab.tsx","../src/client/panel/index.tsx","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["/**\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/** 访问与授权只读快照(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 `{ userId, name? }` / `{ userId }`)。 */\nexport const ACCESS_USERS_ROUTE = '/dsh-channels/access/users'\n/** 知识库白名单增删(POST/DELETE,body `{ userId, name? }` / `{ userId }`)。 */\nexport const ACCESS_WIKI_USERS_ROUTE = '/dsh-channels/access/wiki-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/** 会话的飞书来源(GET/HEAD,`?sessionId=`):对话右侧栏「飞书来源」标签与会话头部入口使用。 */\nexport const SESSION_ORIGIN_ROUTE = '/dsh-channels/session-origin'\n\n/** Where one Feishu-routed Session came from; names are resolved live and may be absent. */\nexport interface SessionOriginWire {\n sessionId: string\n /** `p2p` = 私聊,`group` = 群聊。 */\n chatType: 'p2p' | 'group'\n /** `thread` = 话题内会话,`chat` = 整个聊天一个会话(话题不可用时的降级)。 */\n kind: 'thread' | 'chat'\n chatId: string\n /** 群名(群聊且能读取群信息时)。 */\n chatName?: string\n /** 群头像 URL(群聊且能读取群信息时)。 */\n chatAvatarUrl?: string\n /** 发起人 user_id(日志平台分析话题为请求人)。 */\n senderUserId: string\n /** 发起人姓名(通讯录或群成员列表能查到时)。 */\n senderName?: string\n /** 发起人头像 URL(通讯录能查到时)。 */\n senderAvatarUrl?: string\n /** 日志平台分析话题:群内成员都可以继续。 */\n shared: boolean\n /** Epoch ms when the Session was bound to its Feishu route. */\n createdAt: number\n /** Whether the Feishu route still points at this Session (false after `/renew`, `/new` or route expiry). */\n bound: boolean\n /** Epoch ms of the route's last activity, while bound. */\n lastActivityAt?: number\n /** 在飞书客户端打开该聊天的链接。 */\n chatLink: string\n}\n\n/**\n * `GET PROFILES_ROUTE?users=<user_id,...>&chats=<chat_id,...>`: live Feishu\n * names and avatars for the users and chats the panel shows. At most\n * {@link PROFILES_MAX_IDS} ids per list.\n */\nexport const PROFILES_ROUTE = '/dsh-channels/profiles'\n\n/** Most `user_id`s, and separately most `chat_id`s, one profiles request may name. */\nexport const PROFILES_MAX_IDS = 200\n\n/** Display name and avatar of one Feishu user or chat. */\nexport interface FeishuProfileWire {\n name?: string\n /** `https:` image URL on the Feishu CDN. */\n avatarUrl?: string\n}\n\n/**\n * `GET PROFILES_ROUTE` response. Ids Feishu could not resolve, and ids that\n * are not valid Feishu ids, are absent; `warning` carries the Feishu reason\n * when a lookup failed (typically a missing scope).\n */\nexport interface FeishuProfilesWire {\n users: Record<string, FeishuProfileWire>\n chats: Record<string, FeishuProfileWire>\n warning?: string\n}\n\n/** `GET SESSION_ORIGIN_ROUTE` response: `origin` is null for a Session not started from Feishu. */\nexport interface SessionOriginResponse {\n origin: SessionOriginWire | null\n}\n\n/**\n * 可由 Web 面板覆盖的配置项。顶层键对应 `src/config.ts` 的字段;`feishuTools.*`\n * 是嵌套字段(覆盖层里仍写成 `{ \"feishuTools\": { ... } }` 对象)。覆盖值写进\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 *\n * 数组顺序就是面板里同一分组内字段的显示顺序。\n */\nexport const EDITABLE_KEYS = [\n // 会话:触发\n 'dmMode',\n 'requireMention',\n 'threadFallback',\n // 会话:工作区\n 'baseDir',\n 'topicIsolation',\n // 会话:话题与卡片\n 'routeTtlMs',\n 'approvalTimeoutMs',\n 'questionTimeoutMs',\n 'refreshMs',\n 'statusRefreshMs',\n // 会话:飞书侧功能\n 'welcomeOnboarding',\n 'slashCommandPanel',\n 'allowPermissionSwitch',\n // 模型\n 'provider',\n 'model',\n 'reasoningEffort',\n 'maxTokens',\n 'agentPreset',\n // 附件(v2 管线)\n 'acceptAttachments',\n 'attachmentStore',\n 'prefetchImages',\n 'historyBootstrapLimit',\n // 模型工具(`feishuTools.*` 是嵌套配置,覆盖层仍写成嵌套对象)\n 'feishuTools.attachmentFetch',\n 'feishuTools.topicMessages',\n 'feishuTools.sendFile',\n // 限额与超时\n 'prefetchTimeoutMs',\n 'prefetchImageMaxBytes',\n 'prefetchTotalMaxBytes',\n 'feishuTools.attachmentFetchMaxResources',\n 'feishuTools.attachmentFetchMaxBytes',\n 'feishuTools.attachmentFetchTimeoutMs',\n 'feishuTools.topicMessagesTimeoutMs',\n 'promptContextTimeoutMs',\n // 准入与授权(白名单由「访问授权」tab 的列表管理)\n 'accessMode',\n 'dmAllowWhitelist',\n 'adminUserId',\n 'authRequestTtlDays',\n // 知识库(顶层 `wikiRoots` 字符串列表;为空时不挂载任何知识库工具)\n 'feishuTools.wikiRead',\n 'feishuTools.wikiWrite',\n 'wikiRoots',\n // 日志平台分析接口(访问令牌是凭据引用 analyzeToken,值写在 DSH 凭据里)\n 'analyzeListen',\n // 日志\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 'slashCommandPanel',\n 'analyzeListen',\n 'refreshMs',\n 'statusRefreshMs',\n 'loggerLevel',\n] as const satisfies readonly EditableKey[]\n\nexport type RestartKey = (typeof RESTART_KEYS)[number]\n\n/**\n * Render kinds the generic panel form understands. `string-list` renders one\n * single-line input per entry with a remove button each and an add button\n * below; its value is a `readonly string[]`.\n */\nexport type FieldKind = 'boolean' | 'number' | 'text' | 'select' | 'string-list'\n\n/**\n * Per-entry format of a `string-list` field. Host and Client both map the\n * format onto the same entry parser, so an invalid entry is marked before\n * saving and the Host re-validates it on save.\n *\n * - `wiki-root-link`: a Feishu wiki space link or wiki node link (`wikiRoots`).\n */\nexport type StringListItemFormat = 'wiki-root-link'\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 =\n | 'trigger'\n | 'workspace'\n | 'cards'\n | 'features'\n | 'model'\n | 'attachments'\n | 'tools'\n | 'limits'\n | 'access'\n | 'wiki'\n | 'analyze'\n | 'logging'\n\n/** One selectable value rendered by the generic form. */\nexport interface FieldOptionWire {\n value: string\n label: string\n}\n\n/**\n * A two-value `select` rendered as a switch: `on` is the value stored while\n * the switch is on, `off` while it is off. Both are members of `options`.\n */\nexport interface FieldToggleWire {\n on: string\n off: 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 /** One-line description always shown under the label. */\n summary?: string\n /** Longer explanation, collapsed behind the row's「详情」link. */\n help?: string\n options?: readonly FieldOptionWire[]\n /** `select` only: render the two options as a switch. */\n toggle?: FieldToggleWire\n /**\n * `number` only: display unit next to the input. With `scale`, the input\n * shows `stored / scale` and the panel submits `round(input * scale)`;\n * `min` / `max` / `step` stay in stored units.\n */\n unit?: string\n /** `number` only: stored units per displayed unit (e.g. 1000 for 秒 over ms). */\n scale?: number\n /** `number` only: an empty input clears the override instead of failing. */\n optional?: boolean\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 /** Input placeholder; for `string-list` it applies to every entry input. */\n placeholder?: string\n /** `string-list` only: most non-empty entries the Host accepts. */\n maxItems?: number\n /** `string-list` only: format every non-empty entry must satisfy. */\n itemFormat?: StringListItemFormat\n /** Render the control below the label across the row width (paths, lists). */\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/**\n * Values the panel may submit. Empty strings for optional text fields clear the\n * override. A `string-list` field submits its entries as typed (untrimmed,\n * empty inputs included) so Host issue indexes match the rendered inputs; the\n * Host trims entries and drops empty ones before persisting.\n */\nexport type ConfigValue = string | number | boolean | readonly string[]\nexport type EditableValues = Partial<Record<EditableKey, ConfigValue>>\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/**\n * The credential slots the panel may write: the Feishu application pair\n * (resolved on connect) and the analyze API token (resolved per request).\n */\nexport type CredentialField = 'appId' | 'appSecret' | 'analyzeToken'\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 scopeCheck?: ScopeCheckWire\n /** 连接后飞书原生斜杠指令面板的同步结果;未连接或同步尚未完成时缺省。 */\n slashCommandSync?: SlashCommandSyncWire\n}\n\n/** One plugin need whose scopes are all missing. */\nexport interface MissingScopeWire {\n /** Scope names, preferred first; granting any one satisfies the need. */\n scopes: string[]\n /** `required` breaks a core path; `optional` only degrades a feature. */\n level: 'required' | 'optional'\n /** What the plugin loses without it. */\n purpose: string\n /** Developer-console permission page filtered to the preferred scope. */\n consoleUrl: string\n}\n\n/** Post-connect scope self-check outcome. */\nexport type ScopeCheckWire =\n /** Every applicable need is granted. */\n | { state: 'ok'; checkedAt: number }\n /** Some needs are missing; `consoleUrl` opens the console filtered to all of them. */\n | { state: 'missing'; checkedAt: number; missing: MissingScopeWire[]; consoleUrl: string }\n /** The grant list could not be read; `error` carries the Feishu reason. */\n | { state: 'error'; checkedAt: number; error: string }\n\n/** Post-connect native slash-command panel sync outcome. */\nexport type SlashCommandSyncWire =\n /** The panel lists exactly the registry commands; counts are this sync's writes. */\n | { state: 'ok'; checkedAt: number; created: number; updated: number; removed: number; total: number }\n /** A Feishu call failed; `error` carries the Feishu reason. */\n | { state: 'error'; checkedAt: number; error: string }\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 credentials: {\n appId: CredentialRefWire\n appSecret: CredentialRefWire\n analyzeToken: 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 `user_id` (global across every authorized group). */\nexport interface AccessUserWire {\n userId: 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, `user_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, the group, user, and wiki whitelists, and the still-pending\n * authorization requests.\n */\nexport interface AccessWire {\n mode: 'open' | 'guarded'\n admin: {\n /** The effective administrator: configured `adminUserId`, else the app owner. */\n userId?: string\n /** Where `userId` came from: explicit config or the app-owner fallback. */\n source?: 'config' | 'owner'\n /** false while guarded mode would refuse every conversation (fail closed). */\n configured: boolean\n /** Why the app-owner fallback is unavailable, when its lookup failed. */\n ownerLookupError?: string\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 /** Wiki whitelist (`/auth wiki add`): topic owners allowed to use the wiki tools. */\n wikiUsers: 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 /** 访问与授权卡片的数据(准入模式、管理员、白名单、待处理申请)。 */\n access: AccessWire\n}\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 * `string-list` only: zero-based position of the rejected entry in the\n * submitted array (empty inputs included). Absent for a whole-field issue.\n */\n index?: number\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 analyzeToken?: 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, or only the analyze API token\n * changed (it applies to the next request), 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: 'attachments', label: '附件与工具' },\n { id: 'access', label: '访问授权' },\n { id: 'wiki', 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, in section order. */\nexport const TAB_FIELD_GROUPS: Readonly<Record<PanelTabId, readonly FieldGroup[]>> = {\n connection: [],\n sessions: ['trigger', 'workspace', 'cards', 'features'],\n model: ['model'],\n attachments: ['attachments', 'tools', 'limits'],\n access: ['access'],\n wiki: ['wiki'],\n advanced: ['analyze', 'logging'],\n}\n\n/** Section heading for one field group inside its tab. */\nexport const FIELD_GROUP_LABELS: Readonly<Record<FieldGroup, string>> = {\n trigger: '触发',\n workspace: '工作区',\n cards: '话题与卡片',\n features: '飞书侧功能',\n model: '模型',\n attachments: '入站附件',\n tools: '模型可用的飞书工具',\n limits: '限额与超时',\n access: '准入',\n wiki: '知识库工具',\n analyze: '日志分析接口',\n logging: '日志',\n}\n\n/** Groups rendered collapsed behind one summary row (rarely changed values). */\nexport const COLLAPSED_FIELD_GROUPS: readonly FieldGroup[] = ['limits']\n\n/** `unit` of byte-count fields; the panel adds a MiB reading next to them. */\nexport const BYTES_UNIT = '字节'\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 // ---- lifecycle / status -------------------------------------------------\n phaseUnconfigured: '待配置',\n phaseIdle: '未连接',\n phaseConnecting: '连接中',\n phaseConnected: '已连接',\n phaseError: '连接失败',\n statusSummaryConnected: '机器人在线,可以收发消息',\n statusSummaryUnconfigured: '还没有填写飞书应用凭据,插件不会连接飞书',\n statusSummaryIdle: '凭据已就绪,还没有建立连接',\n statusSummaryConnecting: '正在连接飞书…',\n statusSummaryError: '连接飞书失败,请检查凭据与网络后重试',\n uptimePrefix: '运行',\n lastError: '最近一次连接失败',\n checksTitle: '自检',\n scopeCheckOk: '插件需要的飞书权限都已开通',\n scopeCheckMissing: '{n} 项权限未开通',\n scopeCheckMissingRequired: '{n} 项必需权限未开通,相关功能无法使用',\n scopeCheckMissingOptional: '不影响收发消息,只有相关功能不可用。开通后需在开发者后台发布新版本。',\n scopeCheckPublish: '开通后需在开发者后台发布新版本。',\n scopeCheckOpenAll: '全部开通',\n scopeCheckOpenOne: '去开通',\n scopeCheckRequired: '必需',\n scopeCheckError: '无法读取权限开通情况',\n slashCommandSyncTitle: '斜杠指令面板',\n slashCommandSyncOk: '已同步 {total} 条命令,飞书客户端约 5 分钟后刷新',\n slashCommandSyncError: '同步失败,直接输入命令不受影响',\n missingConfig: '缺少配置',\n routeCount: '话题路由',\n liveAgentCount: '活动会话',\n inflightTurnCount: '在途回复',\n botOpenId: 'Bot open_id',\n healthReasons: '健康判定',\n unknownValue: '—',\n connect: '连接飞书',\n connecting: '连接中…',\n // ---- onboarding ---------------------------------------------------------\n onboardingTitle: '开始使用飞书渠道',\n onboardingStep1Title: '创建飞书自建应用',\n onboardingStep1Text: '在 open.feishu.cn/app 创建企业自建应用,开启机器人能力;事件订阅选「长连接」,订阅 im.message.receive_v1。',\n onboardingStep2Title: '填写应用凭据',\n onboardingStep2Text: '把 App ID(cli_ 开头)和 App Secret 填到下方「应用凭据」。',\n onboardingStep3Title: '保存后自动连接',\n onboardingStep3Text: '两项都保存后插件会自动连接,状态变成「已连接」就能在飞书里私聊机器人。',\n // ---- credentials --------------------------------------------------------\n credentialsTitle: '应用凭据',\n credentialsHint: '值只存在本机凭据库,面板不回显。',\n appIdLabel: 'App ID',\n appSecretLabel: 'App Secret',\n credentialStore: '凭据库',\n saveCredentials: '保存凭据',\n savingCredentials: '保存中…',\n credentialSaved: '凭据已保存',\n credentialSavedReconnected: '凭据已保存,飞书已连接',\n credentialSavedPending: '凭据已保存,重启飞书连接后生效',\n credentialSavedUnconfigured: '凭据已保存;还缺少另一项凭据,补齐后会自动连接',\n credentialAutoConnectFailed: '凭据已保存,但自动连接失败',\n credentialRemoved: '已移除存储的凭据',\n credentialRemovedPending: '已移除存储的凭据,重启飞书连接后生效',\n replaceCredential: '更换',\n setCredential: '设置',\n cancel: '取消',\n removeCredential: '移除',\n removingCredential: '移除中…',\n credentialConfigured: '已配置',\n credentialMissing: '未配置',\n credentialReadOnly: '当前凭据存储不可写',\n appIdPlaceholder: '输入 App ID(cli_…)',\n appSecretPlaceholder: '输入 App Secret',\n credentialRemoveConfirm: '确定移除已存储的凭据值?移除后不会回显,需要时须重新填写。',\n // ---- forms --------------------------------------------------------------\n save: '保存',\n saving: '保存中…',\n discard: '放弃',\n dirtyCount: '{n} 项未保存',\n dirtyRestart: ',其中 {n} 项需要重连后生效',\n details: '详情',\n expand: '展开',\n collapse: '收起',\n collapsedCount: '{n} 项',\n collapsedClean: '一般不用改',\n collapsedChanged: '{n} 项已在面板修改',\n restartTag: '需重连',\n listAdd: '添加',\n listRemove: '移除',\n listEntry: '第 {n} 项',\n listTooMany: '最多 {max} 项(空输入框不计)',\n listNotArray: '必须是字符串列表',\n listEntryNotString: '必须是字符串',\n browseDirectory: '选择目录…',\n browsingDirectory: '选择中…',\n directoryPicked: '已选择目录',\n directoryPickFailed: '无法打开系统目录选择器',\n saved: '已保存',\n saveInvalid: '请先修正标红的配置项',\n numberInvalid: '请输入数字',\n numberMin: '不得小于 {n}',\n numberMax: '不得大于 {n}',\n modelFootnote: '对之后新建的会话生效。',\n catalogFailed: '模型目录加载失败,已退回手填。',\n catalogLoading: '正在读取宿主机选项…',\n catalogUnavailable: '宿主机未提供可选项,请手填。',\n // ---- restart ------------------------------------------------------------\n restartBanner: '以下配置要重启飞书连接后生效:',\n restartWarning: '重启会断开当前飞书连接并中断进行中的回复,确定现在重启?',\n reconnect: '重启连接',\n reconnecting: '重连中…',\n refresh: '刷新',\n retry: '重试',\n pollFailed: '状态刷新失败',\n connectDone: '已连接飞书',\n reconnectDone: '已重启飞书连接',\n loading: '加载中…',\n loadFailed: '设置面板加载失败',\n // ---- advanced -----------------------------------------------------------\n analyzeTokenLabel: '访问令牌',\n analyzeTokenSummary: '请求头 Authorization: Bearer 的值,保存后立即生效;未配置时接口拒绝所有请求',\n analyzeTokenPlaceholder: '填写新令牌(建议 32 位以上随机串)',\n analyzeTokenSaved: '令牌已保存,下一次请求生效',\n analyzeTokenRemoved: '令牌已移除,接口将拒绝所有请求',\n overlayTitle: 'Web 覆盖',\n overlayPath: '覆盖文件',\n overlayCount: '{n} 项配置由面板覆盖',\n noOverrides: '当前没有面板覆盖的配置',\n overlayPathHelp: '面板保存的配置写在这个 JSON 文件里,优先级高于 profile。可以直接编辑;面板保存会整体重写它。',\n clear: '恢复 profile 配置',\n clearing: '恢复中…',\n clearConfirm: '确定删除所有面板覆盖,恢复为 profile 配置?',\n cleared: '已恢复为 profile 配置',\n // ---- access -------------------------------------------------------------\n debugModeWarning: '调试模式已打开:应用可用范围内的任何人都能和机器人对话。用完请在「访问授权」关闭。',\n accessAdminMissingWarning: '没有可用的管理员,所有对话都会被拒绝。请在下方填写管理员 user_id(私聊机器人发送 /userid 获取)。',\n accessAdminCurrent: '当前管理员',\n accessAdminSourceConfig: '已配置',\n accessAdminSourceOwner: '应用所有者',\n profilesWarning: '部分飞书名字和头像未能读取:{reason}',\n accessNotLoaded: '飞书连接建立后加载授权列表',\n accessRequestsTitle: '待处理申请',\n accessGroupsTitle: '已授权群',\n accessUsersTitle: '已授权用户',\n accessGroupsEmpty: '还没有授权任何群',\n accessUsersEmpty: '还没有授权任何用户',\n accessGroupChatIdPlaceholder: 'chat_id(oc_…,群里发 /groupid 获取)',\n accessGroupNamePlaceholder: '群名(可选)',\n accessUserIdPlaceholder: 'user_id(私聊机器人发 /userid 获取)',\n accessUserNamePlaceholder: '昵称(可选)',\n accessAddGroup: '添加群',\n accessAddUser: '添加用户',\n accessAdd: '添加',\n accessRemove: '移除',\n accessAllow: '授权',\n accessDeny: '忽略',\n accessWorking: '处理中…',\n accessSourceCommand: '对话命令',\n accessSourceCard: '授权卡',\n accessSourcePanel: '设置面板',\n accessRequestGroup: '申请授权群',\n accessRequestUser: '申请使用',\n accessRequestIn: '在「{group}」',\n accessRequestExpires: '{date} 前有效',\n accessGroupAdded: '已添加群授权',\n accessGroupRemoved: '已移除群授权',\n accessUserAdded: '已添加用户授权',\n accessUserRemoved: '已移除用户授权',\n accessRequestAllowed: '已授权',\n accessRequestDenied: '已忽略',\n // ---- wiki ---------------------------------------------------------------\n wikiUsersTitle: '可使用的人',\n wikiUsersAdminPlus: '管理员 + {n}',\n wikiUsersEmpty: '目前只有管理员发起的话题能用知识库工具',\n wikiUsersHint: '也可以在飞书发 /auth wiki add @用户',\n wikiUserAdded: '已加入知识库白名单',\n wikiUserRemoved: '已移出知识库白名单',\n} as const\n\n/**\n * Fill `{name}` placeholders of one `PANEL_COPY` template.\n *\n * @param template - Copy containing `{name}` placeholders.\n * @param values - Replacement per placeholder name.\n * @returns The filled string; unknown placeholders stay as written.\n */\nexport function fillCopy(template: string, values: Readonly<Record<string, string | number>>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) => {\n const value = values[name]\n return value === undefined ? match : String(value)\n })\n}\n","/**\n * 左侧栏「飞书渠道」行的装饰图标;行本身负责可访问名称(列表注册的 label)。\n *\n * @module dsh-channels/client/icon\n */\n\n/** Owner share of `sidebar.panellist`: requested square edge and selection state. */\nexport interface FeishuChannelsIconProps {\n size: number\n active: boolean\n}\n\n/**\n * Paper-plane glyph drawn in `currentColor`, so it follows the row's text color.\n * @param props - the sidebar's icon share.\n * @returns decorative icon.\n */\nexport function FeishuChannelsIcon({ size }: FeishuChannelsIconProps): JSX.Element {\n return (\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.6} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M21 3 3.5 10.2a.6.6 0 0 0 0 1.1l6.6 2.6 2.6 6.6a.6.6 0 0 0 1.1 0L21 3Z\" />\n <path d=\"m10.1 13.9 4.4-4.4\" />\n </svg>\n )\n}\n","/**\n * Round Feishu avatar: the user or chat image when Feishu returned one,\n * otherwise (and when the image fails to load) the first character of the\n * display text.\n *\n * @module dsh-channels/client/avatar\n */\n\nimport { useEffect, useState } from 'react'\n\n/**\n * One avatar badge. Decorative: the adjacent label names the user or chat.\n * @param props - `text` supplies the fallback initial, `url` the image, `size` the diameter in px (default 26).\n * @returns the badge.\n */\nexport function Avatar(props: { text: string; url?: string; size?: number }): JSX.Element {\n const { text, url, size = 26 } = props\n const [failed, setFailed] = useState(false)\n useEffect(() => { setFailed(false) }, [url])\n const style = { width: size, height: size, fontSize: Math.round(size * 0.46) }\n if (url !== undefined && !failed) {\n return (\n <img\n className=\"dsh-channels-avatar\"\n style={style}\n src={url}\n alt=\"\"\n aria-hidden=\"true\"\n referrerPolicy=\"no-referrer\"\n loading=\"lazy\"\n onError={() => setFailed(true)}\n />\n )\n }\n return <span className=\"dsh-channels-avatar\" style={style} aria-hidden=\"true\">{Array.from(text)[0] ?? '?'}</span>\n}\n","/**\n * Same-origin HTTP client for the settings panel. No typert contract: the\n * Host half registers plain exact routes and this module fetches them.\n *\n * @module dsh-channels/client/api\n */\n\nimport {\n ACCESS_GROUPS_ROUTE,\n ACCESS_REQUESTS_ROUTE,\n ACCESS_ROUTE,\n ACCESS_USERS_ROUTE,\n ACCESS_WIKI_USERS_ROUTE,\n CATALOG_ROUTE,\n CONFIG_ROUTE,\n CREDENTIALS_ROUTE,\n PANEL_ROUTE,\n PICK_DIRECTORY_ROUTE,\n PROFILES_MAX_IDS,\n PROFILES_ROUTE,\n RECONNECT_ROUTE,\n SESSION_ORIGIN_ROUTE,\n STATUS_ROUTE,\n} from '../settings/contract.ts'\nimport type {\n AccessMutationResponse,\n AccessWire,\n CatalogWire,\n ChannelStatusWire,\n ClearConfigResponse,\n ClearCredentialResponse,\n CredentialField,\n EditableValues,\n FeishuProfilesWire,\n PanelWire,\n PickDirectoryResponse,\n ReconnectResponse,\n SaveConfigResponse,\n SaveCredentialsRequest,\n SaveCredentialsResponse,\n SessionOriginResponse,\n} from '../settings/contract.ts'\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Pull the JSON `error` field out of a failed response body. */\nfunction errorFromBody(status: number, text: string): string {\n try {\n const parsed = JSON.parse(text) as { error?: unknown }\n if (typeof parsed.error === 'string' && parsed.error !== '') return parsed.error\n } catch {\n // Not JSON — fall through to the HTTP status.\n }\n return `HTTP ${status}`\n}\n\n/**\n * Fetch one Host route. Routes are registered as absolute pathnames, but the\n * browser addresses them relative to the DSH document (`<base href=\"./\">`), so\n * a reverse proxy that mounts DSH under a prefix such as `/dsh/` still routes\n * them to DSH with the login cookie it scoped to that prefix.\n */\nasync function request(route: string, init?: RequestInit): Promise<Response> {\n try {\n return await fetch(route.slice(1), {\n cache: 'no-store',\n credentials: 'same-origin',\n ...init,\n headers: { accept: 'application/json', ...(init?.headers ?? {}) },\n })\n } catch (error) {\n throw new Error(`无法连接设置接口:${messageOf(error)}`)\n }\n}\n\nasync function readJson<T>(response: Response): Promise<T> {\n const text = await response.text()\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as T\n}\n\n/** Full panel payload (status + editable config + read-only facts). */\nexport async function fetchPanel(): Promise<PanelWire> {\n return readJson<PanelWire>(await request(PANEL_ROUTE))\n}\n\n/** Status-only payload polled while the page is open. */\nexport async function fetchStatus(): Promise<ChannelStatusWire> {\n return readJson<ChannelStatusWire>(await request(STATUS_ROUTE))\n}\n\n/** Feishu origin of one Session (`origin: null` when Feishu never routed it). */\nexport async function fetchSessionOrigin(sessionId: string): Promise<SessionOriginResponse> {\n const query = new URLSearchParams({ sessionId }).toString()\n return readJson<SessionOriginResponse>(await request(`${SESSION_ORIGIN_ROUTE}?${query}`))\n}\n\n/**\n * Live Feishu names and avatars of the given users and chats, split into\n * requests of at most {@link PROFILES_MAX_IDS} ids per list and merged.\n * @param userIds - user `user_id`s.\n * @param chatIds - chat `chat_id`s.\n * @returns the profiles Feishu resolved, by id, with every request's warning.\n */\nexport async function fetchFeishuProfiles(userIds: readonly string[], chatIds: readonly string[]): Promise<FeishuProfilesWire> {\n const requests: Array<Promise<FeishuProfilesWire>> = []\n const rounds = Math.max(1, Math.ceil(userIds.length / PROFILES_MAX_IDS), Math.ceil(chatIds.length / PROFILES_MAX_IDS))\n for (let round = 0; round < rounds; round += 1) {\n const window = (ids: readonly string[]): string => ids.slice(round * PROFILES_MAX_IDS, (round + 1) * PROFILES_MAX_IDS).join(',')\n const query = new URLSearchParams({ users: window(userIds), chats: window(chatIds) }).toString()\n requests.push(request(`${PROFILES_ROUTE}?${query}`).then(response => readJson<FeishuProfilesWire>(response)))\n }\n const merged: FeishuProfilesWire = { users: {}, chats: {} }\n const warnings = new Set<string>()\n for (const part of await Promise.all(requests)) {\n Object.assign(merged.users, part.users)\n Object.assign(merged.chats, part.chats)\n if (part.warning !== undefined) warnings.add(part.warning)\n }\n if (warnings.size > 0) merged.warning = [...warnings].join(';')\n return merged\n}\n\n/**\n * Model-selector catalog for one route: providers, the provider's models, the\n * model's reasoning efforts, and the preset roster. The client re-queries it\n * whenever the draft provider/model changes.\n */\nexport async function fetchCatalog(provider?: string, model?: string): Promise<CatalogWire> {\n const params = new URLSearchParams()\n if (provider !== undefined && provider !== '') params.set('provider', provider)\n if (model !== undefined && model !== '') params.set('model', model)\n const query = params.toString()\n return readJson<CatalogWire>(await request(query === '' ? CATALOG_ROUTE : `${CATALOG_ROUTE}?${query}`))\n}\n\n/** Persist a partial edit. Validation failures come back as `{ok:false}`. */\nexport async function saveConfig(values: EditableValues): Promise<SaveConfigResponse> {\n const response = await request(CONFIG_ROUTE, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ values }),\n })\n const text = await response.text()\n if (response.status === 400) {\n // Validation/IO refusal: the body is already the SaveConfigResponse.\n try {\n return JSON.parse(text) as SaveConfigResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as SaveConfigResponse\n}\n\n/** Drop every Web override and return the refreshed panel. */\nexport async function clearConfig(): Promise<ClearConfigResponse> {\n const response = await request(CONFIG_ROUTE, { method: 'DELETE' })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as ClearConfigResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as ClearConfigResponse\n}\n\n/** Reopen the Feishu long connection (interrupts in-flight replies). */\nexport async function reconnect(): Promise<ReconnectResponse> {\n const response = await request(RECONNECT_ROUTE, { method: 'POST' })\n const text = await response.text()\n if (!response.ok) {\n try {\n return JSON.parse(text) as ReconnectResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n return JSON.parse(text) as ReconnectResponse\n}\n\n/**\n * Ask the Host to open its native directory chooser (the panel's「选择目录…」\n * button). The request stays open while the OS dialog does; a cancel comes back\n * as `{ ok: true, canceled: true }`, not as an error.\n */\nexport async function pickDirectory(): Promise<PickDirectoryResponse> {\n const response = await request(PICK_DIRECTORY_ROUTE, { method: 'POST' })\n const text = await response.text()\n if (!response.ok) {\n try {\n return JSON.parse(text) as PickDirectoryResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n return JSON.parse(text) as PickDirectoryResponse\n}\n\n/** Store provided credential values (the request/response never carry stored values). */\nexport async function saveCredentials(values: SaveCredentialsRequest): Promise<SaveCredentialsResponse> {\n const response = await request(CREDENTIALS_ROUTE, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(values),\n })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as SaveCredentialsResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as SaveCredentialsResponse\n}\n\n/** Remove the stored value behind one credential reference. */\nexport async function clearCredential(field: CredentialField): Promise<ClearCredentialResponse> {\n const response = await request(CREDENTIALS_ROUTE, {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ field }),\n })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as ClearCredentialResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as ClearCredentialResponse\n}\n\n/** Admission snapshot: mode, administrator, whitelists, pending requests. */\nexport async function fetchAccess(): Promise<AccessWire> {\n return readJson<AccessWire>(await request(ACCESS_ROUTE))\n}\n\n/** One access write: a 400 body already is the wire union; others throw. */\nasync function mutateAccess(\n path: string,\n method: 'POST' | 'DELETE',\n body: unknown,\n): Promise<AccessMutationResponse> {\n const response = await request(path, {\n method,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as AccessMutationResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as AccessMutationResponse\n}\n\n/** Authorize one group; resolves with the refreshed access snapshot. */\nexport async function addAccessGroup(chatId: string, name?: string): Promise<AccessMutationResponse> {\n return mutateAccess(\n ACCESS_GROUPS_ROUTE,\n 'POST',\n name === undefined || name === '' ? { chatId } : { chatId, name },\n )\n}\n\n/** Revoke one group authorization (takes effect on the next message). */\nexport async function removeAccessGroup(chatId: string): Promise<AccessMutationResponse> {\n return mutateAccess(ACCESS_GROUPS_ROUTE, 'DELETE', { chatId })\n}\n\n/** Authorize one user `user_id` (global across authorized groups). */\nexport async function addAccessUser(userId: string, name?: string): Promise<AccessMutationResponse> {\n return mutateAccess(\n ACCESS_USERS_ROUTE,\n 'POST',\n name === undefined || name === '' ? { userId } : { userId, name },\n )\n}\n\n/** Revoke one user authorization (takes effect on the next message). */\nexport async function removeAccessUser(userId: string): Promise<AccessMutationResponse> {\n return mutateAccess(ACCESS_USERS_ROUTE, 'DELETE', { userId })\n}\n\n/** Add one user `user_id` to the wiki whitelist. */\nexport async function addWikiUser(userId: string, name?: string): Promise<AccessMutationResponse> {\n return mutateAccess(\n ACCESS_WIKI_USERS_ROUTE,\n 'POST',\n name === undefined || name === '' ? { userId } : { userId, name },\n )\n}\n\n/** Remove one user from the wiki whitelist (takes effect on the next wiki tool call). */\nexport async function removeWikiUser(userId: string): Promise<AccessMutationResponse> {\n return mutateAccess(ACCESS_WIKI_USERS_ROUTE, 'DELETE', { userId })\n}\n\n/** Settle one pending authorization request (same resolver as the Feishu card). */\nexport async function resolveAccessRequest(\n token: string,\n decision: 'allow' | 'deny',\n): Promise<AccessMutationResponse> {\n return mutateAccess(ACCESS_REQUESTS_ROUTE, 'POST', { token, decision })\n}\n","/**\n * 按会话缓存的飞书来源查询,会话头部入口与右侧栏标签页共用。\n *\n * @module dsh-channels/client/origin-cache\n */\n\nimport type { SessionOriginWire } from '../settings/contract.ts'\nimport { fetchSessionOrigin } from './api.ts'\n\n/**\n * How long a \"not from Feishu\" answer is reused. A Session shows up in the\n * Web list before its Feishu route is written, so an early null must expire.\n */\nconst NULL_ORIGIN_TTL_MS = 15_000\n\ninterface Lookup {\n pending: Promise<SessionOriginWire | null>\n /** Epoch ms when the Host answered null; absent while pending or for an origin. */\n nullAt?: number\n}\n\n/** One settled or pending origin lookup per Session; failures are not kept. */\nconst lookups = new Map<string, Lookup>()\n\n/**\n * Origin of one Session through the shared cache. An origin is kept for the\n * page's lifetime; a null answer only for {@link NULL_ORIGIN_TTL_MS}.\n * @param sessionId - DSH session id.\n * @param refresh - drop the cached answer and ask the Host again.\n * @returns the origin, or null for a Session Feishu never routed.\n */\nexport function loadSessionOrigin(sessionId: string, refresh = false): Promise<SessionOriginWire | null> {\n const cached = refresh ? undefined : lookups.get(sessionId)\n if (cached !== undefined && (cached.nullAt === undefined || Date.now() - cached.nullAt < NULL_ORIGIN_TTL_MS)) {\n return cached.pending\n }\n const pending = fetchSessionOrigin(sessionId).then(response => response.origin)\n const lookup: Lookup = { pending }\n lookups.set(sessionId, lookup)\n pending.then(\n (origin) => { if (origin === null) lookup.nullAt = Date.now() },\n () => { if (lookups.get(sessionId) === lookup) lookups.delete(sessionId) },\n )\n return pending\n}\n","/**\n * 对话里的「飞书来源」:会话头部的入口按钮(只在飞书发起的会话里出现)与\n * 右侧栏的来源标签页。两者共用一份按会话缓存的来源查询。\n *\n * @module dsh-channels/client/origin\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { SessionOriginWire } from '../settings/contract.ts'\nimport { Avatar } from './avatar.tsx'\nimport { loadSessionOrigin } from './origin-cache.ts'\nimport { FeishuChannelsIcon } from './icon.tsx'\n\n/** Right-sidebar tab kind opened by the header button. */\nexport const ORIGIN_TAB_KIND = 'feishu-origin'\n/** Tab-type implementation id and keyed body/title slot key. */\nexport const ORIGIN_TAB_ID = '@modesto_ma/dsh-channels/origin'\n/** Tab chip text. */\nexport const ORIGIN_TAB_TITLE = '飞书来源'\n\ntype OriginState =\n | { phase: 'loading' }\n | { phase: 'ready'; origin: SessionOriginWire | null }\n | { phase: 'error'; message: string }\n\n/**\n * Load one Session's origin; `reload` asks the Host again. `fresh` skips the\n * shared cache on the first load too, so names and avatars are current.\n */\nfunction useSessionOrigin(sessionId: string, fresh = false): [OriginState, () => void] {\n const [state, setState] = useState<OriginState>({ phase: 'loading' })\n // A reload is tied to the Session it was asked for, so switching Sessions\n // never shows the previous Session's origin while the new one loads.\n const [reloadOf, setReloadOf] = useState<{ sessionId: string; ticket: number }>({ sessionId, ticket: 0 })\n const ticket = reloadOf.sessionId === sessionId ? reloadOf.ticket : 0\n useEffect(() => {\n let live = true\n setState(current => (current.phase === 'ready' && ticket > 0 ? current : { phase: 'loading' }))\n loadSessionOrigin(sessionId, fresh || ticket > 0).then(\n origin => { if (live) setState({ phase: 'ready', origin }) },\n (error: unknown) => {\n if (live) setState({ phase: 'error', message: error instanceof Error ? error.message : String(error) })\n },\n )\n return () => { live = false }\n }, [fresh, sessionId, ticket])\n const reload = useCallback(() => {\n setReloadOf(current => ({ sessionId, ticket: current.sessionId === sessionId ? current.ticket + 1 : 1 }))\n }, [sessionId])\n return [state, reload]\n}\n\n/** Header chip text: group name, or 私聊 with the peer's name. */\nfunction sourceLabel(origin: SessionOriginWire): string {\n if (origin.chatType === 'p2p') return origin.senderName === undefined ? '飞书私聊' : `私聊 · ${origin.senderName}`\n return origin.chatName === undefined ? '飞书群聊' : origin.chatName\n}\n\n/** Tab heading: the group, or the private-chat peer, with its avatar. */\nfunction originHeading(origin: SessionOriginWire): { title: string; avatarUrl?: string } {\n if (origin.chatType === 'p2p') {\n return { title: origin.senderName ?? '飞书私聊', ...(origin.senderAvatarUrl === undefined ? {} : { avatarUrl: origin.senderAvatarUrl }) }\n }\n return { title: origin.chatName ?? '飞书群聊', ...(origin.chatAvatarUrl === undefined ? {} : { avatarUrl: origin.chatAvatarUrl }) }\n}\n\n/** Times a mounted header re-asks after a null answer, and the delay between asks. */\nconst HEADER_NULL_RETRIES = 3\nconst HEADER_NULL_RETRY_MS = 5_000\n\n/** Props of the header entry: the session standard kit's `sessionId`. */\nexport interface OriginHeaderActionProps {\n sessionId: string\n /** Open the origin tab in this Session's right sidebar. */\n openOrigin: () => void\n}\n\n/**\n * Header button shown only for Sessions Feishu routed; renders nothing for\n * every other Session and while the lookup is pending or failed.\n * @param props - session id and the tab opener.\n * @returns the button or null.\n */\nexport function OriginHeaderAction({ sessionId, openOrigin }: OriginHeaderActionProps): JSX.Element | null {\n const [state, reload] = useSessionOrigin(sessionId)\n const [retries, setRetries] = useState(0)\n useEffect(() => { setRetries(0) }, [sessionId])\n // A Session opened while Feishu is still creating it answers null until its\n // route is written; look again a few times while the header stays mounted.\n const waiting = state.phase === 'ready' && state.origin === null && retries < HEADER_NULL_RETRIES\n useEffect(() => {\n if (!waiting) return\n const timer = setTimeout(() => {\n setRetries(value => value + 1)\n reload()\n }, HEADER_NULL_RETRY_MS)\n return () => { clearTimeout(timer) }\n }, [waiting, reload])\n if (state.phase !== 'ready' || state.origin === null) return null\n const label = sourceLabel(state.origin)\n return (\n <button\n type=\"button\"\n className=\"dsh-channels-origin-chip\"\n title={`来自${label},点击查看飞书来源`}\n aria-label={`飞书来源:${label}`}\n onClick={openOrigin}\n >\n <FeishuChannelsIcon size={13} active={false} />\n <span className=\"dsh-channels-origin-chip__text\">{label}</span>\n </button>\n )\n}\n\n/** Props of the tab body: the session standard kit's `sessionId`. */\nexport interface OriginTabProps {\n sessionId: string\n}\n\nfunction formatTime(ms: number): string {\n const date = new Date(ms)\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\n}\n\n/**\n * Right-sidebar body: where this Session came from in Feishu.\n * @param props - the tab's Session.\n * @returns the origin page.\n */\nexport function OriginTab({ sessionId }: OriginTabProps): JSX.Element {\n const [state, reload] = useSessionOrigin(sessionId, true)\n return (\n <div className=\"dsh-channels-origin\">\n {state.phase === 'loading' ? <div className=\"dsh-channels-origin__muted\">正在读取飞书来源…</div> : null}\n {state.phase === 'error'\n ? (\n <div className=\"dsh-channels-origin__error\">\n <span>读取飞书来源失败:{state.message}</span>\n <button type=\"button\" className=\"dsh-channels-origin__link\" onClick={reload}>重试</button>\n </div>\n )\n : null}\n {state.phase === 'ready' && state.origin === null\n ? (\n <div className=\"dsh-channels-origin__muted\">\n 这个会话不是从飞书发起的。\n <button type=\"button\" className=\"dsh-channels-origin__link\" onClick={reload}>刷新</button>\n </div>\n )\n : null}\n {state.phase === 'ready' && state.origin !== null ? <OriginDetails origin={state.origin} onRefresh={reload} /> : null}\n </div>\n )\n}\n\nfunction OriginDetails({ origin, onRefresh }: { origin: SessionOriginWire; onRefresh: () => void }): JSX.Element {\n const p2p = origin.chatType === 'p2p'\n const rows: Array<[string, JSX.Element | string]> = [\n ['来源', p2p ? '私聊' : (origin.kind === 'thread' ? '群聊 · 话题' : '群聊')],\n ]\n // The heading already names a resolved group; an unnamed one shows its id.\n if (!p2p && origin.chatName === undefined) {\n rows.push(['群 ID', <span className=\"dsh-channels-origin__mono\">{origin.chatId}</span>])\n }\n rows.push([origin.shared ? '请求人' : '发起人', (\n <span className=\"dsh-channels-origin__who\">\n <Avatar text={origin.senderName ?? origin.senderUserId} url={origin.senderAvatarUrl} size={20} />\n {origin.senderName === undefined ? null : <span>{origin.senderName}</span>}\n <span className=\"dsh-channels-origin__mono dsh-channels-origin__muted\">{origin.senderUserId}</span>\n </span>\n )])\n const heading = originHeading(origin)\n if (origin.shared) rows.push(['说明', '日志平台分析话题,群内成员都可以继续'])\n rows.push(['开始于', formatTime(origin.createdAt)])\n rows.push(origin.bound && origin.lastActivityAt !== undefined\n ? ['最近活跃', formatTime(origin.lastActivityAt)]\n : ['状态', '飞书侧已切换到新会话或已过期'])\n return (\n <>\n <div className=\"dsh-channels-origin__head\">\n {heading.avatarUrl === undefined\n ? <FeishuChannelsIcon size={16} active={false} />\n : <Avatar text={heading.title} url={heading.avatarUrl} size={28} />}\n <span className=\"dsh-channels-origin__title\">{heading.title}</span>\n </div>\n <dl className=\"dsh-channels-origin__list\">\n {rows.map(([label, value]) => (\n <div key={label} className=\"dsh-channels-origin__row\">\n <dt>{label}</dt>\n <dd>{value}</dd>\n </div>\n ))}\n </dl>\n <div className=\"dsh-channels-origin__actions\">\n <a className=\"dsh-channels-origin__button\" href={origin.chatLink} target=\"_blank\" rel=\"noreferrer\">在飞书中打开</a>\n <button type=\"button\" className=\"dsh-channels-origin__link\" onClick={onRefresh}>刷新</button>\n </div>\n </>\n )\n}\n","/**\n * 浏览器半区的设置面板选择器目录折叠:把 Host 下发的字段描述与实时目录\n * (`/dsh-channels/catalog`)合成通用表单需要的 select 描述。\n *\n * 拆成独立模块(不含 JSX/React)以便单测;`panel/index.tsx` 只负责串起状态与渲染。\n *\n * @module dsh-channels/client/catalog\n */\n\nimport { PANEL_COPY } from '../settings/contract.ts'\nimport type {\n CatalogWire,\n EditableFieldWire,\n EditableKey,\n FieldOptionWire,\n SelectCatalogWire,\n} from '../settings/contract.ts'\n\n/** Keys whose options come from the Host selector catalog. */\nexport const CATALOG_KEYS = ['provider', 'model', 'reasoningEffort', 'agentPreset'] as const\nexport type CatalogKey = (typeof CATALOG_KEYS)[number]\n\nexport function isCatalogKey(key: EditableKey): key is CatalogKey {\n return (CATALOG_KEYS as readonly string[]).includes(key)\n}\n\n/** Read one selector group back out of the Host-resolved panel fields. */\nexport function selectorOf(field: EditableFieldWire | undefined): SelectCatalogWire {\n if (field?.kind !== 'select') return { selectable: false, options: [] }\n return {\n selectable: true,\n options: field.options === undefined ? [] : [...field.options],\n ...(field.disabled === true ? { disabled: true } : {}),\n ...(field.help === undefined ? {} : { help: field.help }),\n }\n}\n\n/** Seed the client catalog from the options the Host already resolved for the panel. */\nexport function catalogFromFields(fields: readonly EditableFieldWire[]): CatalogWire | null {\n const byKey = new Map(fields.map(field => [field.key, field]))\n const catalog: CatalogWire = {\n provider: selectorOf(byKey.get('provider')),\n model: selectorOf(byKey.get('model')),\n reasoningEffort: selectorOf(byKey.get('reasoningEffort')),\n agentPreset: selectorOf(byKey.get('agentPreset')),\n }\n return CATALOG_KEYS.every(key => !catalog[key].selectable) ? null : catalog\n}\n\n/** Current-value option kept when a saved value is missing from the live list. */\nfunction withCurrentOption(options: readonly FieldOptionWire[], value: string): 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 * Layer the live catalog over the Host panel fields. A group the Host cannot\n * enumerate falls back to the free-text control; the rest render as selects.\n *\n * `disabled` must be cleared explicitly: the Host's initial panel descriptor\n * carries `disabled: true` for dependent selectors (model without a provider,\n * effort without a model), and a fetched catalog that resolved the dependency\n * has to re-enable them. Forgetting this is exactly what made the model and\n * reasoning selects permanently unusable after choosing a provider/model.\n */\nexport function applyCatalogFields(\n fields: readonly EditableFieldWire[],\n catalog: CatalogWire | null,\n current: Partial<Record<CatalogKey, string | undefined>> = {},\n): EditableFieldWire[] {\n if (catalog === null) return [...fields]\n return fields.map((field) => {\n if (!isCatalogKey(field.key)) return field\n const selector = catalog[field.key]\n if (!selector.selectable) {\n return field.kind === 'select'\n ? { ...field, kind: 'text', options: undefined, help: PANEL_COPY.catalogUnavailable, disabled: undefined }\n : field\n }\n const value = current[field.key] ?? ''\n const options = value !== '' && !selector.options.some(option => option.value === value)\n ? withCurrentOption(selector.options, value)\n : [...selector.options]\n const next: EditableFieldWire = { ...field, kind: 'select', options }\n if (selector.disabled === true) next.disabled = true\n else delete next.disabled\n if (selector.help === undefined) delete next.help\n else next.help = selector.help\n return next\n })\n}","/**\n * 浏览器半区的 `number` 字段辅助:按 Host 描述的 `scale` 在显示单位与存储\n * 单位之间换算、保存前校验上下界,以及字节数的 MiB 读数。\n *\n * 不含 JSX/React,便于单测。Host 保存时按 config schema 复核。\n *\n * @module dsh-channels/client/number-field\n */\n\nimport { fillCopy, PANEL_COPY } from '../settings/contract.ts'\nimport type { ConfigValue, EditableFieldWire } from '../settings/contract.ts'\n\n/** Drop float noise from `stored / scale` (e.g. 0.30000000000000004). */\nfunction tidy(value: number): number {\n return Number(value.toPrecision(12))\n}\n\n/**\n * Value the input shows for one field.\n *\n * @param field - The Host descriptor (`kind: 'number'`).\n * @param value - Draft (the typed string) or effective value (stored units).\n * @returns The input text.\n */\nexport function numberInputValue(field: EditableFieldWire, value: ConfigValue | undefined): string {\n if (typeof value === 'string') return value\n if (typeof value !== 'number') return ''\n return String(field.scale === undefined ? value : tidy(value / field.scale))\n}\n\n/**\n * Input `step` in display units.\n *\n * @param field - The Host descriptor (`kind: 'number'`).\n * @returns The step, or `undefined` when the Host gives none.\n */\nexport function numberInputStep(field: EditableFieldWire): number | undefined {\n if (field.step === undefined) return undefined\n return field.scale === undefined ? field.step : tidy(field.step / field.scale)\n}\n\n/** One bound in display units with the field's unit appended. */\nfunction boundLabel(field: EditableFieldWire, stored: number): string {\n const shown = field.scale === undefined ? stored : tidy(stored / field.scale)\n return field.unit === undefined ? String(shown) : `${shown} ${field.unit}`\n}\n\n/**\n * Turn one number draft into its save payload, checking the Host bounds.\n *\n * @param field - The Host descriptor (`kind: 'number'`).\n * @param raw - The draft: the typed string, or a stored-unit number.\n * @returns The stored-unit value (`''` clears an optional field), or the reason it is rejected.\n */\nexport function numberDraftValue(\n field: EditableFieldWire,\n raw: ConfigValue | undefined,\n): { ok: true; value: number | '' } | { ok: false; message: string } {\n let stored: number\n if (typeof raw === 'number') {\n stored = raw\n } else {\n const text = typeof raw === 'string' ? raw.trim() : ''\n if (text === '') {\n return field.optional === true ? { ok: true, value: '' } : { ok: false, message: PANEL_COPY.numberInvalid }\n }\n const parsed = Number(text)\n if (!Number.isFinite(parsed)) return { ok: false, message: PANEL_COPY.numberInvalid }\n stored = field.scale === undefined ? parsed : Math.round(parsed * field.scale)\n }\n if (field.min !== undefined && stored < field.min) {\n return { ok: false, message: fillCopy(PANEL_COPY.numberMin, { n: boundLabel(field, field.min) }) }\n }\n if (field.max !== undefined && stored > field.max) {\n return { ok: false, message: fillCopy(PANEL_COPY.numberMax, { n: boundLabel(field, field.max) }) }\n }\n return { ok: true, value: stored }\n}\n\n/**\n * Human reading of a byte count, e.g. `20 MiB`.\n *\n * @param text - The input text (bytes).\n * @returns The reading, or `undefined` when the text is not a positive number.\n */\nexport function bytesReading(text: string): string | undefined {\n const bytes = Number(text)\n if (text.trim() === '' || !Number.isFinite(bytes) || bytes <= 0) return undefined\n const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']\n let value = bytes\n let unit = 0\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit += 1\n }\n return `${Number(value.toFixed(value >= 100 || unit === 0 ? 0 : 1))} ${units[unit]}`\n}\n","/**\n * Feishu wiki (知识库) link parsing: the `wikiRoots` allow-list entries and\n * the document references the model passes to the wiki tools.\n *\n * Pure module: no I/O. Hosts are not checked because tenants use their own\n * subdomains (`<tenant>.feishu.cn`) and the API never uses the link host.\n *\n * @module dsh-channels/feishu/wiki/links\n */\n\n/** Feishu wiki space id: decimal digits. */\nconst SPACE_ID_PATTERN = /^\\d{1,32}$/u\n\n/** Feishu wiki node / docx token: letters and digits. */\nconst TOKEN_PATTERN = /^[A-Za-z0-9]{8,64}$/u\n\n/** One configured `wikiRoots` entry. */\nexport type WikiRoot =\n | {\n /** A whole wiki space (`/wiki/space/<space_id>`). */\n readonly kind: 'space'\n readonly spaceId: string\n /** The link as configured. */\n readonly link: string\n }\n | {\n /** One wiki node and every node below it (`/wiki/<node_token>`). */\n readonly kind: 'node'\n readonly nodeToken: string\n /** The link as configured. */\n readonly link: string\n }\n\n/** Why a `wikiRoots` entry was rejected. */\nexport type WikiRootLinkError =\n | 'not-a-url'\n | 'not-https'\n | 'not-a-wiki-link'\n\n/** Readable reasons for {@link WikiRootLinkError}. */\nexport const WIKI_ROOT_LINK_ERRORS: Readonly<Record<WikiRootLinkError, string>> = {\n 'not-a-url': '不是有效的链接',\n 'not-https': '必须是 https 链接',\n 'not-a-wiki-link': '只支持知识空间链接 https://<租户>.feishu.cn/wiki/space/<space_id> 或节点链接 https://<租户>.feishu.cn/wiki/<node_token>',\n}\n\n/**\n * Parse one `wikiRoots` entry.\n *\n * @param link Link as configured (surrounding whitespace is ignored).\n * @returns The root, or the reason it is invalid.\n */\nexport function parseWikiRootLink(link: string): { ok: true; root: WikiRoot } | { ok: false; error: WikiRootLinkError } {\n const trimmed = link.trim()\n let url: URL\n try {\n url = new URL(trimmed)\n } catch {\n // Unparseable input: reported as a configuration error below.\n return { ok: false, error: 'not-a-url' }\n }\n if (url.protocol !== 'https:') return { ok: false, error: 'not-https' }\n const segments = url.pathname.split('/').filter(segment => segment !== '')\n if (segments[0] !== 'wiki') return { ok: false, error: 'not-a-wiki-link' }\n if (segments.length === 3 && segments[1] === 'space' && SPACE_ID_PATTERN.test(segments[2]!)) {\n return { ok: true, root: { kind: 'space', spaceId: segments[2]!, link: trimmed } }\n }\n if (segments.length === 2 && segments[1] !== 'space' && TOKEN_PATTERN.test(segments[1]!)) {\n return { ok: true, root: { kind: 'node', nodeToken: segments[1]!, link: trimmed } }\n }\n return { ok: false, error: 'not-a-wiki-link' }\n}\n\n/**\n * Parse every configured root. Callers validate the config first, so an\n * invalid entry here is a programming error.\n *\n * @param links Validated `wikiRoots` entries.\n * @returns The parsed roots, in configuration order.\n */\nexport function parseWikiRoots(links: readonly string[]): WikiRoot[] {\n return links.map((link) => {\n const parsed = parseWikiRootLink(link)\n if (!parsed.ok) throw new Error(`dsh-channels: invalid wikiRoots entry ${link}: ${WIKI_ROOT_LINK_ERRORS[parsed.error]}`)\n return parsed.root\n })\n}\n\n/** A document or node the model names: a wiki node, a docx, or a whole space. */\nexport type WikiRef =\n | { readonly kind: 'node'; readonly token: string }\n | { readonly kind: 'docx'; readonly token: string }\n | { readonly kind: 'space'; readonly spaceId: string }\n\n/**\n * Parse a model-supplied reference: a wiki node link, a docx link, a space\n * link, or a bare node token.\n *\n * @param input Raw tool argument.\n * @returns The reference, or `undefined` when it names nothing recognizable.\n */\nexport function parseWikiRef(input: string): WikiRef | undefined {\n const trimmed = input.trim()\n if (TOKEN_PATTERN.test(trimmed)) return { kind: 'node', token: trimmed }\n let url: URL\n try {\n url = new URL(trimmed)\n } catch {\n // Neither a token nor a URL.\n return undefined\n }\n if (url.protocol !== 'https:') return undefined\n const segments = url.pathname.split('/').filter(segment => segment !== '')\n if (segments[0] === 'wiki' && segments.length === 3 && segments[1] === 'space' && SPACE_ID_PATTERN.test(segments[2]!)) {\n return { kind: 'space', spaceId: segments[2]! }\n }\n if (segments.length === 2 && TOKEN_PATTERN.test(segments[1]!)) {\n if (segments[0] === 'wiki' && segments[1] !== 'space') return { kind: 'node', token: segments[1]! }\n if (segments[0] === 'docx') return { kind: 'docx', token: segments[1]! }\n }\n return undefined\n}\n","/**\n * 浏览器半区的 `string-list` 字段辅助:把草稿值读成条目数组,并在保存前\n * 逐项校验,供面板即时标红。Host 保存时按同一规则复核(见\n * `settings/surface.ts` 的 `normalizeStringList`)。\n *\n * 不含 JSX/React,便于单测。条目格式校验直接复用纯函数模块\n * `feishu/wiki/links.ts`(无运行时依赖,可内联进 Client bundle)。\n *\n * @module dsh-channels/client/string-list\n */\n\nimport { parseWikiRootLink, WIKI_ROOT_LINK_ERRORS } from '../feishu/wiki/links.ts'\nimport { PANEL_COPY } from '../settings/contract.ts'\nimport type {\n ConfigValue,\n EditableFieldWire,\n FieldIssueWire,\n StringListItemFormat,\n} from '../settings/contract.ts'\n\n/** Entry checks per item format: the readable reason, or `undefined` when valid. */\nconst ITEM_FORMAT_CHECKS: Readonly<Record<StringListItemFormat, (entry: string) => string | undefined>> = {\n 'wiki-root-link': (entry) => {\n const parsed = parseWikiRootLink(entry)\n return parsed.ok ? undefined : WIKI_ROOT_LINK_ERRORS[parsed.error]\n },\n}\n\n/**\n * Entries of a `string-list` value; any other value reads as no entries.\n *\n * @param value - Draft or effective value of a `string-list` field.\n * @returns The entries as stored (untrimmed).\n */\nexport function listItems(value: ConfigValue | undefined): readonly string[] {\n return typeof value === 'object' ? value : []\n}\n\n/**\n * Validate the entries of one `string-list` draft the way the Host will:\n * empty inputs are ignored, every other entry is trimmed and checked against\n * the field's `itemFormat`, and the non-empty count is bounded by `maxItems`.\n *\n * @param field - The Host descriptor (`kind: 'string-list'`).\n * @param items - The draft entries, empty inputs included.\n * @returns Per-entry issues (with `index` into `items`) and a whole-field issue when over `maxItems`.\n */\nexport function stringListIssues(field: EditableFieldWire, items: readonly string[]): FieldIssueWire[] {\n const issues: FieldIssueWire[] = []\n const check = field.itemFormat === undefined ? undefined : ITEM_FORMAT_CHECKS[field.itemFormat]\n let count = 0\n items.forEach((item, index) => {\n const entry = item.trim()\n if (entry === '') return\n count += 1\n const reason = check?.(entry)\n if (reason === undefined) return\n const position = PANEL_COPY.listEntry.replace('{n}', String(index + 1))\n issues.push({ key: field.key, index, message: `${position}「${entry}」:${reason}` })\n })\n if (field.maxItems !== undefined && count > field.maxItems) {\n issues.push({ key: field.key, message: PANEL_COPY.listTooMany.replace('{max}', String(field.maxItems)) })\n }\n return issues\n}\n\n/**\n * Turn one `string-list` draft into its save payload. Entries are submitted as\n * typed (untrimmed, empty inputs included) so Host issue indexes point at the\n * same rendered inputs; the Host trims and drops empty inputs.\n *\n * @param field - The Host descriptor (`kind: 'string-list'`).\n * @param raw - The draft value.\n * @returns The payload entries, or the issues that block the save.\n */\nexport function stringListDraftValue(\n field: EditableFieldWire,\n raw: ConfigValue | undefined,\n): { ok: true; value: string[] } | { ok: false; issues: FieldIssueWire[] } {\n const items = listItems(raw)\n const issues = stringListIssues(field, items)\n return issues.length > 0 ? { ok: false, issues } : { ok: true, value: [...items] }\n}\n\n/**\n * Inputs the list control renders: one per entry, or one blank input for an\n * empty list so the first entry can be typed directly.\n *\n * @param value - Draft or effective value.\n * @returns The input values, in order.\n */\nexport function listRows(value: ConfigValue | undefined): readonly string[] {\n const items = listItems(value)\n return items.length === 0 ? [''] : items\n}\n\n/**\n * Replace the entry at one input position.\n *\n * @param rows - Rendered inputs ({@link listRows}).\n * @param index - Position of the edited input.\n * @param entry - The typed value.\n * @returns The next draft entries.\n */\nexport function withListEntry(rows: readonly string[], index: number, entry: string): string[] {\n return rows.map((item, position) => (position === index ? entry : item))\n}\n\n/**\n * Drop the entry at one input position.\n *\n * @param rows - Rendered inputs ({@link listRows}).\n * @param index - Position of the removed input.\n * @returns The next draft entries.\n */\nexport function withoutListEntry(rows: readonly string[], index: number): string[] {\n return rows.filter((_, position) => position !== index)\n}\n\n/**\n * Append one blank input.\n *\n * @param rows - Rendered inputs ({@link listRows}).\n * @returns The next draft entries.\n */\nexport function withBlankListEntry(rows: readonly string[]): string[] {\n return [...rows, '']\n}\n\n/**\n * Whether the add button is disabled: the inputs already reach `maxItems`.\n *\n * @param field - The Host descriptor.\n * @param rows - Rendered inputs ({@link listRows}).\n * @returns `true` when no further input may be added.\n */\nexport function listIsFull(field: EditableFieldWire, rows: readonly string[]): boolean {\n return field.maxItems !== undefined && rows.length >= field.maxItems\n}\n","/**\n * 设置面板共享层:页面状态类型、Host 字段渲染原语(SettingRow /\n * FieldSections / SaveBar)、分区与列表原语、生命周期徽标与纯格式化工具。\n *\n * 这一层不发请求、不持有页面状态,只把 Host 下发的 wire 数据渲染成控件;\n * 页面外壳在 `index.tsx`,各 tab 只组合这些原语。\n *\n * @module dsh-channels/client/panel/shared\n */\n\nimport { useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n BYTES_UNIT,\n COLLAPSED_FIELD_GROUPS,\n FIELD_GROUP_LABELS,\n fillCopy,\n PANEL_COPY,\n} from '../../settings/contract.ts'\nimport type {\n ChannelPhaseWire,\n ConfigValue,\n CredentialField,\n EditableFieldWire,\n EditableKey,\n EditableValues,\n FieldGroup,\n FieldIssueWire,\n PanelWire,\n} from '../../settings/contract.ts'\nimport { bytesReading, numberDraftValue, numberInputStep, numberInputValue } from '../number-field.ts'\nimport {\n listIsFull,\n listItems,\n listRows,\n stringListDraftValue,\n withBlankListEntry,\n withListEntry,\n withoutListEntry,\n} from '../string-list.ts'\n\n/** Initial panel load: one request, retried from the error card. */\nexport type LoadState =\n | { phase: 'loading' }\n | { phase: 'error'; message: string }\n | { phase: 'ready'; panel: PanelWire }\n\n/** One transient banner above the tab body (save/credentials/reconnect/catalog). */\nexport interface Notice {\n kind: 'ok' | 'error'\n text: string\n /** Per-field validation failures rendered inline by SettingRow. */\n issues?: FieldIssueWire[]\n}\n\n/** One in-flight panel mutation (only one action runs at a time). */\nexport type BusyAction =\n | 'save'\n | 'clear'\n | 'reconnect'\n | 'credentials'\n | 'credentials-clear'\n | 'access'\n\n/** Tone of a small pill; maps onto `dsh-channels-cfg__pill--*` classes. */\nexport type BadgeTone = 'ok' | 'warn' | 'bad' | 'muted'\n\nexport function messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Compact duration for the page header. */\nexport function formatUptime(ms: number | undefined): string {\n if (ms === undefined || !Number.isFinite(ms)) return PANEL_COPY.unknownValue\n const totalSeconds = Math.max(0, Math.floor(ms / 1_000))\n const days = Math.floor(totalSeconds / 86_400)\n const hours = Math.floor((totalSeconds % 86_400) / 3_600)\n const minutes = Math.floor((totalSeconds % 3_600) / 60)\n if (days > 0) return `${days} 天 ${hours} 小时`\n if (hours > 0) return `${hours} 小时 ${minutes} 分`\n if (minutes > 0) return `${minutes} 分 ${totalSeconds % 60} 秒`\n return `${totalSeconds} 秒`\n}\n\n/** Effective value for one key (falling back to the profile base). */\nexport function effectiveValue(panel: PanelWire, key: EditableKey): ConfigValue | undefined {\n return panel.config.effective[key] ?? panel.config.base[key]\n}\n\n/** Host label for one editable key; the raw key is the technical fallback. */\nexport function fieldLabel(fields: readonly EditableFieldWire[], key: EditableKey): string {\n return fields.find(field => field.key === key)?.label ?? key\n}\n\n/**\n * Turn the dirty draft into the wire payload, keeping the Host-descriptor\n * bounds check client-side (the Host re-validates every value on save).\n */\nexport function validateDraft(\n panel: PanelWire,\n draft: EditableValues,\n): { values: EditableValues; issues: FieldIssueWire[] } {\n const values: EditableValues = {}\n const issues: FieldIssueWire[] = []\n for (const field of panel.config.fields) {\n if (!(field.key in draft)) continue\n const raw = draft[field.key]\n if (field.kind === 'number') {\n const parsed = numberDraftValue(field, raw)\n if (parsed.ok) values[field.key] = parsed.value\n else issues.push({ key: field.key, message: parsed.message })\n continue\n }\n if (field.kind === 'boolean') {\n values[field.key] = raw === true\n continue\n }\n if (field.kind === 'string-list') {\n const list = stringListDraftValue(field, raw)\n if (list.ok) values[field.key] = list.value\n else issues.push(...list.issues)\n continue\n }\n values[field.key] = String(raw ?? '')\n }\n return { values, issues }\n}\n\n/** Header pill for the Host-derived lifecycle phase. */\nexport function phaseBadge(phase: ChannelPhaseWire): { text: string; tone: BadgeTone } {\n switch (phase) {\n case 'connected': return { text: PANEL_COPY.phaseConnected, tone: 'ok' }\n case 'connecting': return { text: PANEL_COPY.phaseConnecting, tone: 'warn' }\n case 'error': return { text: PANEL_COPY.phaseError, tone: 'bad' }\n case 'idle': return { text: PANEL_COPY.phaseIdle, tone: 'muted' }\n default: return { text: PANEL_COPY.phaseUnconfigured, tone: 'muted' }\n }\n}\n\n/** One-line explanation of the current phase (connection tab status block). */\nexport function phaseSummary(phase: ChannelPhaseWire): string {\n switch (phase) {\n case 'connected': return PANEL_COPY.statusSummaryConnected\n case 'connecting': return PANEL_COPY.statusSummaryConnecting\n case 'error': return PANEL_COPY.statusSummaryError\n case 'idle': return PANEL_COPY.statusSummaryIdle\n default: return PANEL_COPY.statusSummaryUnconfigured\n }\n}\n\n/** Credential editor data owned by the page shell, rendered by the connection tab. */\nexport interface CredentialsView {\n appId: string\n appSecret: string\n issues: FieldIssueWire[]\n busy: BusyAction | null\n /** Host-side lifecycle busy (connecting/restarting): block writes. */\n locked: boolean\n onAppIdChange: (value: string) => void\n onAppSecretChange: (value: string) => void\n onSave: () => void\n onClear: (field: CredentialField) => void\n}\n\n/** Analyze API token editor data owned by the page shell, rendered by the advanced tab. */\nexport interface AnalyzeTokenView {\n value: string\n issues: FieldIssueWire[]\n busy: BusyAction | null\n /** Host-side lifecycle busy (connecting/restarting): block writes. */\n locked: boolean\n onChange: (value: string) => void\n onSave: () => void\n onClear: () => void\n}\n\n/** Everything a field-bearing tab needs from the page shell's single draft. */\nexport interface FormView {\n panel: PanelWire\n /** Host descriptors after the live selector catalog has been layered on. */\n fields: readonly EditableFieldWire[]\n draft: EditableValues\n issues: FieldIssueWire[]\n busy: BusyAction | null\n /** Host-side lifecycle busy (connecting/restarting): block writes. */\n locked: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n /**\n * Open the Host's native directory chooser and write the picked absolute\n * path into the draft (not saved until「保存」). Absent when the Host\n * cannot offer a native chooser.\n */\n onPickDirectory?: (key: EditableKey) => Promise<void>\n}\n\n/** Small rounded status pill. */\nexport function Pill(props: { tone: BadgeTone; dot?: boolean; children: ReactNode }): JSX.Element {\n return (\n <span className={`dsh-channels-cfg__pill dsh-channels-cfg__pill--${props.tone}`}>\n {props.dot === true ? <span className=\"dsh-channels-cfg__dot\" /> : null}\n {props.children}\n </span>\n )\n}\n\n/** One titled section: a muted heading above a bordered row list. */\nexport function Section(props: {\n title: ReactNode\n footnote?: ReactNode\n tone?: 'warn'\n children: ReactNode\n}): JSX.Element {\n const { title, footnote, tone, children } = props\n return (\n <section className=\"dsh-channels-cfg__sec\">\n <h3 className=\"dsh-channels-cfg__secTitle\">{title}</h3>\n <div className={`dsh-channels-cfg__list${tone === 'warn' ? ' dsh-channels-cfg__list--warn' : ''}`}>\n {children}\n </div>\n {footnote === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{footnote}</p>}\n </section>\n )\n}\n\n/**\n * One setting row: label (with tags and the「详情」toggle) and a one-line\n * summary on the left, the control on the right. `details` renders below the\n * row once expanded; `wide` puts the control under the text.\n */\nexport function Row(props: {\n label: ReactNode\n tags?: ReactNode\n summary?: ReactNode\n details?: ReactNode\n control?: ReactNode\n error?: ReactNode\n wide?: boolean\n leading?: ReactNode\n}): JSX.Element {\n const { label, tags, summary, details, control, error, wide = false, leading } = props\n const [open, setOpen] = useState(false)\n return (\n <div className=\"dsh-channels-cfg__rowWrap\">\n <div className={`dsh-channels-cfg__row${wide ? ' dsh-channels-cfg__row--wide' : ''}`}>\n {leading}\n <div className=\"dsh-channels-cfg__rowText\">\n <div className=\"dsh-channels-cfg__rowLabel\">\n <span>{label}</span>\n {tags}\n {details === undefined\n ? null\n : (\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__more\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n {PANEL_COPY.details}\n </button>\n )}\n </div>\n {summary === undefined ? null : <div className=\"dsh-channels-cfg__rowSummary\">{summary}</div>}\n </div>\n {control === undefined ? null : <div className=\"dsh-channels-cfg__rowCtl\">{control}</div>}\n </div>\n {open && details !== undefined ? <div className=\"dsh-channels-cfg__details\">{details}</div> : null}\n {error === undefined || error === null ? null : <div className=\"dsh-channels-cfg__rowError\">{error}</div>}\n </div>\n )\n}\n\n/** Accessible on/off switch. */\nexport function Switch(props: {\n checked: boolean\n label: string\n disabled?: boolean\n onChange: (next: boolean) => void\n}): JSX.Element {\n const { checked, label, disabled = false, onChange } = props\n return (\n <button\n type=\"button\"\n role=\"switch\"\n className=\"dsh-channels-cfg__switch\"\n aria-checked={checked}\n aria-label={label}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n />\n )\n}\n\n/**\n * Text input paired with the host-native「选择目录…」button. The button owns\n * its own busy state: the OS dialog can stay open for a long time, so the\n * rest of the form stays usable while the operator browses.\n */\nfunction DirectoryTextInput(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n inputClass: string\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n onPick: () => Promise<void>\n}): JSX.Element {\n const { field, value, inputClass, disabled, onChange, onPick } = props\n const [picking, setPicking] = useState(false)\n const pick = async (): Promise<void> => {\n setPicking(true)\n try {\n await onPick()\n } finally {\n setPicking(false)\n }\n }\n return (\n <div className=\"dsh-channels-cfg__inputRow\">\n <input\n type=\"text\"\n className={inputClass}\n value={String(value ?? '')}\n placeholder={field.placeholder}\n aria-label={field.label}\n disabled={disabled || picking}\n onChange={event => onChange(field.key, event.target.value)}\n />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn\"\n disabled={disabled || picking}\n onClick={() => { void pick() }}\n >\n {picking ? PANEL_COPY.browsingDirectory : PANEL_COPY.browseDirectory}\n </button>\n </div>\n )\n}\n\n/**\n * `string-list` control: one single-line input per entry, each with a remove\n * button, and an add link below (disabled at `maxItems` inputs). An empty\n * list renders one blank input so the first entry can be typed directly.\n * Issues carrying an `index` mark that input and show the reason under it.\n */\nfunction StringListInput(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n issues: readonly FieldIssueWire[]\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n}): JSX.Element {\n const { field, value, issues, disabled, onChange } = props\n const empty = listItems(value).length === 0\n const rows = listRows(value)\n return (\n <div className=\"dsh-channels-cfg__stringList\">\n {rows.map((item, index) => {\n const issue = issues.find(entry => entry.index === index)\n const position = PANEL_COPY.listEntry.replace('{n}', String(index + 1))\n // Entries have no identity beyond their position, so the index is the key.\n return (\n <div className=\"dsh-channels-cfg__stringListItem\" key={index}>\n <div className=\"dsh-channels-cfg__inputRow\">\n <input\n type=\"text\"\n className={`dsh-channels-cfg__input${issue === undefined ? '' : ' dsh-channels-cfg__input--invalid'}`}\n value={item}\n placeholder={field.placeholder}\n aria-label={`${field.label} ${position}`}\n aria-invalid={issue === undefined ? undefined : true}\n disabled={disabled}\n onChange={event => onChange(field.key, withListEntry(rows, index, event.target.value))}\n />\n {empty\n ? null\n : (\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--remove\"\n aria-label={`${PANEL_COPY.listRemove} ${position}`}\n disabled={disabled}\n onClick={() => onChange(field.key, withoutListEntry(rows, index))}\n >\n {PANEL_COPY.listRemove}\n </button>\n )}\n </div>\n {issue === undefined ? null : <span className=\"dsh-channels-cfg__fieldError\">{issue.message}</span>}\n </div>\n )\n })}\n <div>\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__link\"\n disabled={disabled || listIsFull(field, rows)}\n onClick={() => onChange(field.key, withBlankListEntry(rows))}\n >\n + {PANEL_COPY.listAdd}\n </button>\n </div>\n </div>\n )\n}\n\n/** Number input with its unit (and a MiB reading for byte counts). */\nfunction NumberInput(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n inputClass: string\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n}): JSX.Element {\n const { field, value, inputClass, disabled, onChange } = props\n const text = numberInputValue(field, value)\n const reading = field.unit === BYTES_UNIT ? bytesReading(text) : undefined\n return (\n <div className=\"dsh-channels-cfg__inputRow\">\n {reading === undefined ? null : <span className=\"dsh-channels-cfg__bytes\">{reading}</span>}\n <input\n type=\"number\"\n className={`${inputClass} dsh-channels-cfg__input--num`}\n value={text}\n placeholder={field.placeholder}\n step={numberInputStep(field)}\n aria-label={field.label}\n disabled={disabled}\n onChange={event => onChange(field.key, event.target.value)}\n />\n {field.unit === undefined ? null : <span className=\"dsh-channels-cfg__unit\">{field.unit}</span>}\n </div>\n )\n}\n\n/** The control for one Host field descriptor. */\nfunction FieldControl(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n issues: readonly FieldIssueWire[]\n invalid: boolean\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n onPickDirectory?: () => Promise<void>\n}): JSX.Element | null {\n const { field, value, issues, invalid, disabled, onChange, onPickDirectory } = props\n const inputClass = `dsh-channels-cfg__input${invalid ? ' dsh-channels-cfg__input--invalid' : ''}`\n switch (field.kind) {\n case 'boolean':\n return <Switch checked={value === true} label={field.label} disabled={disabled} onChange={next => onChange(field.key, next)} />\n case 'select': {\n const toggle = field.toggle\n if (toggle !== undefined) {\n return (\n <Switch\n checked={value === toggle.on}\n label={field.label}\n disabled={disabled}\n onChange={next => onChange(field.key, next ? toggle.on : toggle.off)}\n />\n )\n }\n return (\n <select\n className={`${inputClass} dsh-channels-cfg__select`}\n value={String(value ?? '')}\n disabled={disabled || field.disabled === true}\n aria-label={field.label}\n onChange={event => onChange(field.key, event.target.value)}\n >\n {(field.options ?? []).map(option => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n )\n }\n case 'number':\n return <NumberInput field={field} value={value} inputClass={inputClass} disabled={disabled} onChange={onChange} />\n case 'text':\n return field.browse === 'directory' && onPickDirectory !== undefined\n ? (\n <DirectoryTextInput\n field={field}\n value={value}\n inputClass={inputClass}\n disabled={disabled}\n onChange={onChange}\n onPick={onPickDirectory}\n />\n )\n : (\n <input\n type=\"text\"\n className={`${inputClass}${field.wide === true ? '' : ' dsh-channels-cfg__input--text'}`}\n value={String(value ?? '')}\n placeholder={field.placeholder}\n aria-label={field.label}\n disabled={disabled}\n onChange={event => onChange(field.key, event.target.value)}\n />\n )\n case 'string-list':\n return <StringListInput field={field} value={value} issues={issues} disabled={disabled} onChange={onChange} />\n default:\n // Unknown kinds come from a newer Host; render nothing instead of a broken control.\n return null\n }\n}\n\n/** One Host field rendered as a setting row. */\nexport function SettingRow(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n issues: readonly FieldIssueWire[]\n disabled?: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n /** Present only for `field.browse === 'directory'` rows the page can serve. */\n onPickDirectory?: () => Promise<void>\n}): JSX.Element {\n const { field, value, issues, disabled = false, onChange, onPickDirectory } = props\n // Entry issues (`index` set) are rendered next to their input by StringListInput.\n const issue = issues.find(item => item.key === field.key && item.index === undefined)\n const entryIssues = issues.filter(item => item.key === field.key && item.index !== undefined)\n return (\n <Row\n label={field.label}\n tags={field.restartRequired ? <span className=\"dsh-channels-cfg__tag\">{PANEL_COPY.restartTag}</span> : undefined}\n summary={field.summary}\n details={field.help}\n wide={field.wide === true}\n error={issue?.message}\n control={(\n <FieldControl\n field={field}\n value={value}\n issues={entryIssues}\n invalid={issue !== undefined}\n disabled={disabled}\n onChange={onChange}\n onPickDirectory={onPickDirectory}\n />\n )}\n />\n )\n}\n\n/** Rows for every field of one group. */\nfunction GroupRows(props: { form: FormView; fields: readonly EditableFieldWire[] }): JSX.Element {\n const { form, fields } = props\n return (\n <>\n {fields.map((field) => {\n const onPick = form.onPickDirectory\n return (\n <SettingRow\n key={field.key}\n field={field}\n value={form.draft[field.key] ?? effectiveValue(form.panel, field.key)}\n issues={form.issues}\n disabled={form.locked}\n onChange={form.onChange}\n onPickDirectory={onPick === undefined ? undefined : () => onPick(field.key)}\n />\n )\n })}\n </>\n )\n}\n\n/**\n * A group of rarely changed fields behind one summary row. It opens on its\n * own when one of its fields has a validation issue.\n */\nfunction CollapsedGroup(props: { form: FormView; fields: readonly EditableFieldWire[] }): JSX.Element {\n const { form, fields } = props\n const [open, setOpen] = useState(false)\n const keys = new Set<string>(fields.map(field => field.key))\n const changed = fields.filter(field =>\n form.panel.config.overriddenKeys.includes(field.key) || field.key in form.draft).length\n const expanded = open || form.issues.some(issue => keys.has(issue.key))\n return (\n <>\n <Row\n label={fillCopy(PANEL_COPY.collapsedCount, { n: fields.length })}\n summary={changed === 0 ? PANEL_COPY.collapsedClean : fillCopy(PANEL_COPY.collapsedChanged, { n: changed })}\n control={(\n <button type=\"button\" className=\"dsh-channels-cfg__more\" aria-expanded={expanded} onClick={() => setOpen(!expanded)}>\n {expanded ? PANEL_COPY.collapse : PANEL_COPY.expand}\n </button>\n )}\n />\n {expanded ? <GroupRows form={form} fields={fields} /> : null}\n </>\n )\n}\n\n/**\n * One tab's editable fields as titled sections, one per Host field group, in\n * the given order. Empty groups render nothing, so a Host that moves every key\n * out of a group does not leave a stray heading behind. `extra` appends rows\n * that are not Host fields (e.g. a credential) to one group's list.\n */\nexport function FieldSections(props: {\n form: FormView\n groups: readonly FieldGroup[]\n extra?: Partial<Record<FieldGroup, ReactNode>>\n footnotes?: Partial<Record<FieldGroup, ReactNode>>\n}): JSX.Element {\n const { form, groups, extra = {}, footnotes = {} } = props\n return (\n <>\n {groups.map((group) => {\n const fields = form.fields.filter(field => field.group === group)\n const more = extra[group]\n if (fields.length === 0 && more === undefined) return null\n return (\n <Section key={group} title={FIELD_GROUP_LABELS[group]} footnote={footnotes[group]}>\n {COLLAPSED_FIELD_GROUPS.includes(group)\n ? <CollapsedGroup form={form} fields={fields} />\n : <GroupRows form={form} fields={fields} />}\n {more}\n </Section>\n )\n })}\n </>\n )\n}\n\n/**\n * Sticky bar shown only while the draft has unsaved keys. Saving submits the\n * whole draft (every tab); discarding drops it.\n */\nexport function SaveBar(props: {\n dirtyCount: number\n restartCount: number\n busy: BusyAction | null\n locked: boolean\n onSave: () => void\n onDiscard: () => void\n}): JSX.Element | null {\n const { dirtyCount, restartCount, busy, locked, onSave, onDiscard } = props\n if (dirtyCount === 0) return null\n const disabled = busy !== null || locked\n return (\n <div className=\"dsh-channels-cfg__saveBar\" role=\"region\" aria-label={PANEL_COPY.save}>\n <span className=\"dsh-channels-cfg__saveMsg\">\n {fillCopy(PANEL_COPY.dirtyCount, { n: dirtyCount })}\n {restartCount === 0 ? '' : fillCopy(PANEL_COPY.dirtyRestart, { n: restartCount })}\n </span>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\" onClick={onDiscard} disabled={disabled}>\n {PANEL_COPY.discard}\n </button>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\" onClick={onSave} disabled={disabled}>\n {busy === 'save' ? PANEL_COPY.saving : PANEL_COPY.save}\n </button>\n </div>\n )\n}\n\n/** Transient banner (save / credentials / reconnect / catalog / access). */\nexport function NoticeBar(props: { kind: 'ok' | 'error'; text: string }): JSX.Element {\n const { kind, text } = props\n return (\n <div\n className={`dsh-channels-cfg__notice dsh-channels-cfg__notice--${kind}`}\n role={kind === 'error' ? 'alert' : 'status'}\n aria-live=\"polite\"\n >\n {text}\n </div>\n )\n}\n\n/** Muted row inside a list (empty or not-yet-loaded state). */\nexport function EmptyRow(props: { text: string }): JSX.Element {\n return <div className=\"dsh-channels-cfg__emptyRow\">{props.text}</div>\n}\n","/**\n * 白名单列表:群白名单、用户白名单与知识库白名单共用的条目行、添加表单\n * (点「+ 添加」才展开)与待处理申请行。\n *\n * @module dsh-channels/client/panel/whitelist\n */\n\nimport { useState } from 'react'\nimport { fillCopy, PANEL_COPY } from '../../settings/contract.ts'\nimport type { AccessGroupWire, AccessRequestWire } from '../../settings/contract.ts'\nimport { Avatar } from '../avatar.tsx'\nimport type { ProfileView } from './profiles.ts'\nimport { EmptyRow, Row, Section } from './shared.tsx'\n\n/** Local `MM-DD HH:mm` rendering for authorization timestamps. */\nexport function formatAccessDate(ms: number): string {\n if (!Number.isFinite(ms) || ms <= 0) return PANEL_COPY.unknownValue\n const date = new Date(ms)\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\n}\n\n/** Which authorization entry produced one grant. */\nfunction sourceLabel(source: AccessGroupWire['source']): string {\n switch (source) {\n case 'command': return PANEL_COPY.accessSourceCommand\n case 'card': return PANEL_COPY.accessSourceCard\n default: return PANEL_COPY.accessSourcePanel\n }\n}\n\n/** One whitelist entry: a group `chat_id` or a user `user_id`. */\nexport interface WhitelistEntry {\n id: string\n /** Live Feishu name, else the name captured at authorization time. */\n name?: string\n /** Live Feishu avatar. */\n avatarUrl?: string\n source: AccessGroupWire['source']\n}\n\n/**\n * One titled whitelist with remove buttons and a collapsed add form.\n * `onAdd` resolves true when the entry was added (the form then closes).\n */\nexport function Whitelist(props: {\n title: string\n /** Shown after the title once loaded; defaults to the entry count. */\n countLabel?: string\n entries: readonly WhitelistEntry[]\n /** Fallback avatar text for entries without an image; defaults to the entry name. */\n avatar?: string\n empty: string\n loaded: boolean\n addLabel: string\n idPlaceholder: string\n namePlaceholder: string\n footnote?: string\n disabled: boolean\n working: boolean\n onAdd: (id: string, name: string) => Promise<boolean>\n onRemove: (id: string) => void\n}): JSX.Element {\n const { title, countLabel, entries, avatar, empty, loaded, addLabel, idPlaceholder, namePlaceholder, footnote, disabled, working, onAdd, onRemove } = props\n const [adding, setAdding] = useState(false)\n const [id, setId] = useState('')\n const [name, setName] = useState('')\n\n const submit = async (): Promise<void> => {\n if (id.trim() === '') return\n if (await onAdd(id, name)) {\n setId('')\n setName('')\n setAdding(false)\n }\n }\n\n return (\n <Section title={loaded ? `${title} · ${countLabel ?? entries.length}` : title} footnote={footnote}>\n {!loaded ? <EmptyRow text={PANEL_COPY.accessNotLoaded} /> : null}\n {loaded && entries.length === 0 ? <EmptyRow text={empty} /> : null}\n {entries.map(entry => (\n <Row\n key={entry.id}\n leading={<Avatar text={avatar ?? entry.name ?? entry.id} url={entry.avatarUrl} />}\n label={entry.name ?? entry.id}\n summary={<><span className=\"dsh-channels-cfg__mono\">{entry.id}</span> · {sourceLabel(entry.source)}</>}\n control={(\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--remove\"\n disabled={disabled}\n onClick={() => onRemove(entry.id)}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove}\n </button>\n )}\n />\n ))}\n {!loaded\n ? null\n : adding\n ? (\n <div className=\"dsh-channels-cfg__addRow\">\n <input\n type=\"text\"\n className=\"dsh-channels-cfg__input\"\n value={id}\n placeholder={idPlaceholder}\n aria-label={idPlaceholder}\n disabled={disabled}\n onChange={event => setId(event.target.value)}\n />\n <input\n type=\"text\"\n className=\"dsh-channels-cfg__input\"\n value={name}\n placeholder={namePlaceholder}\n aria-label={namePlaceholder}\n disabled={disabled}\n onChange={event => setName(event.target.value)}\n />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\"\n disabled={disabled || id.trim() === ''}\n onClick={() => { void submit() }}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAdd}\n </button>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\" onClick={() => setAdding(false)}>\n {PANEL_COPY.cancel}\n </button>\n </div>\n )\n : (\n <div className=\"dsh-channels-cfg__addRow\">\n <button type=\"button\" className=\"dsh-channels-cfg__link\" disabled={disabled} onClick={() => setAdding(true)}>\n + {addLabel}\n </button>\n </div>\n )}\n </Section>\n )\n}\n\n/** Pending authorization requests, settled with the same resolver as the DM card. */\nexport function RequestList(props: {\n requests: readonly AccessRequestWire[]\n profiles: ProfileView\n disabled: boolean\n working: boolean\n onResolve: (token: string, decision: 'allow' | 'deny') => void\n}): JSX.Element {\n const { requests, profiles, disabled, working, onResolve } = props\n return (\n <Section title={`${PANEL_COPY.accessRequestsTitle} · ${requests.length}`} tone=\"warn\">\n {requests.map((request) => {\n const profile = request.kind === 'user' ? profiles.user(request.targetId) : profiles.chat(request.targetId)\n const target = profile?.name ?? request.name ?? request.targetId\n const group = request.chatId === undefined\n ? request.groupName\n : profiles.chat(request.chatId)?.name ?? request.groupName ?? request.chatId\n const where = request.kind === 'user' && group !== undefined\n ? fillCopy(PANEL_COPY.accessRequestIn, { group })\n : ''\n return (\n <Row\n key={request.token}\n leading={<Avatar text={target} url={profile?.avatarUrl} />}\n label={`${target} ${where}${request.kind === 'group' ? PANEL_COPY.accessRequestGroup : PANEL_COPY.accessRequestUser}`}\n summary={(\n <>\n <span className=\"dsh-channels-cfg__mono\">{request.targetId}</span>\n {` · ${formatAccessDate(request.createdAt)} · ${fillCopy(PANEL_COPY.accessRequestExpires, { date: formatAccessDate(request.expiresAt) })}`}\n </>\n )}\n control={(\n <div className=\"dsh-channels-cfg__inputRow\">\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\"\n disabled={disabled}\n onClick={() => onResolve(request.token, 'deny')}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessDeny}\n </button>\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\"\n disabled={disabled}\n onClick={() => onResolve(request.token, 'allow')}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAllow}\n </button>\n </div>\n )}\n />\n )\n })}\n </Section>\n )\n}\n","/**\n * 「访问授权」tab:待处理申请(有才显示,置顶)、准入字段(调试模式 /\n * 管理员 / 申请有效期)与群、用户白名单。字段保存走全局 draft,白名单增删\n * 与申请处理是独立即时 mutation。\n *\n * `access.loaded === false`(飞书未连接、插件 storage 域未打开)时白名单与\n * 申请列表是「未知」而不是「空」,因此只渲染连接提示并禁用全部写操作。\n *\n * @module dsh-channels/client/panel/access-tab\n */\n\nimport { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type { AccessWire } from '../../settings/contract.ts'\nimport { Avatar } from '../avatar.tsx'\nimport type { ProfileView } from './profiles.ts'\nimport { FieldSections, NoticeBar, Row } from './shared.tsx'\nimport type { FormView } from './shared.tsx'\nimport { RequestList, Whitelist } from './whitelist.tsx'\n\nexport interface AccessTabProps {\n form: FormView\n profiles: ProfileView\n accessNotice: string | null\n accessError: string | null\n onAddGroup: (chatId: string, name: string) => Promise<boolean>\n onRemoveGroup: (chatId: string) => void\n onAddUser: (userId: string, name: string) => Promise<boolean>\n onRemoveUser: (userId: string) => void\n onResolveRequest: (token: string, decision: 'allow' | 'deny') => void\n}\n\nexport function AccessTab(props: AccessTabProps): JSX.Element {\n const { form, profiles, accessNotice, accessError, onAddGroup, onRemoveGroup, onAddUser, onRemoveUser, onResolveRequest } = props\n const { access } = form.panel\n const working = form.busy === 'access'\n const disabled = form.busy !== null || form.locked || !access.loaded\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {accessNotice === null ? null : <NoticeBar kind=\"ok\" text={accessNotice} />}\n {accessError === null ? null : <NoticeBar kind=\"error\" text={accessError} />}\n {access.loaded && access.mode === 'guarded' && !access.admin.configured\n ? (\n <NoticeBar\n kind=\"error\"\n text={access.admin.ownerLookupError === undefined\n ? PANEL_COPY.accessAdminMissingWarning\n : `${PANEL_COPY.accessAdminMissingWarning}(${access.admin.ownerLookupError})`}\n />\n )\n : null}\n {profiles.warning === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{fillCopy(PANEL_COPY.profilesWarning, { reason: profiles.warning })}</p>}\n {access.loaded && access.requests.length > 0\n ? <RequestList requests={access.requests} profiles={profiles} disabled={disabled} working={working} onResolve={onResolveRequest} />\n : null}\n <FieldSections\n form={form}\n groups={TAB_FIELD_GROUPS.access}\n extra={{ access: <AdminRow admin={access.admin} profiles={profiles} /> }}\n />\n <Whitelist\n title={PANEL_COPY.accessGroupsTitle}\n entries={access.groups.map(group => ({\n id: group.chatId,\n name: profiles.chat(group.chatId)?.name ?? group.name,\n avatarUrl: profiles.chat(group.chatId)?.avatarUrl,\n source: group.source,\n }))}\n avatar=\"群\"\n empty={PANEL_COPY.accessGroupsEmpty}\n loaded={access.loaded}\n addLabel={PANEL_COPY.accessAddGroup}\n idPlaceholder={PANEL_COPY.accessGroupChatIdPlaceholder}\n namePlaceholder={PANEL_COPY.accessGroupNamePlaceholder}\n disabled={disabled}\n working={working}\n onAdd={onAddGroup}\n onRemove={onRemoveGroup}\n />\n <Whitelist\n title={PANEL_COPY.accessUsersTitle}\n entries={access.users.map(user => ({\n id: user.userId,\n name: profiles.user(user.userId)?.name ?? user.name,\n avatarUrl: profiles.user(user.userId)?.avatarUrl,\n source: user.source,\n }))}\n empty={PANEL_COPY.accessUsersEmpty}\n loaded={access.loaded}\n addLabel={PANEL_COPY.accessAddUser}\n idPlaceholder={PANEL_COPY.accessUserIdPlaceholder}\n namePlaceholder={PANEL_COPY.accessUserNamePlaceholder}\n disabled={disabled}\n working={working}\n onAdd={onAddUser}\n onRemove={onRemoveUser}\n />\n </div>\n )\n}\n\n/** The effective administrator with their Feishu name and avatar; nothing while there is none. */\nfunction AdminRow(props: { admin: AccessWire['admin']; profiles: ProfileView }): JSX.Element | null {\n const { admin, profiles } = props\n if (admin.userId === undefined) return null\n const profile = profiles.user(admin.userId)\n const name = profile?.name ?? admin.userId\n const source = admin.source === 'owner' ? PANEL_COPY.accessAdminSourceOwner : PANEL_COPY.accessAdminSourceConfig\n return (\n <Row\n leading={<Avatar text={name} url={profile?.avatarUrl} />}\n label={name}\n summary={<>{PANEL_COPY.accessAdminCurrent} · <span className=\"dsh-channels-cfg__mono\">{admin.userId}</span> · {source}</>}\n />\n )\n}\n","/**\n * 凭据行:值经 Host 的 DSH 凭据服务写入本机凭据库,面板只显示「已配置 /\n * 未配置」,两个方向都不回显已存值。已配置时只显示「更换 / 移除」,点「更换」\n * 才出现输入框。保存动作由页面外壳持有(凭据保存独立于配置 draft)。\n *\n * @module dsh-channels/client/panel/credentials-card\n */\n\nimport { useEffect, useRef, useState } from 'react'\nimport { PANEL_COPY } from '../../settings/contract.ts'\nimport type { CredentialRefWire, FieldIssueWire, PanelWire } from '../../settings/contract.ts'\nimport { Pill, Row, Section } from './shared.tsx'\nimport type { BusyAction, CredentialsView } from './shared.tsx'\n\n/** One credential slot as a setting row with its own input and actions. */\nexport function CredentialRow(props: {\n label: string\n summary?: string\n state: CredentialRefWire\n value: string\n secret: boolean\n placeholder: string\n issue?: FieldIssueWire\n busy: BusyAction | null\n disabled: boolean\n onChange: (value: string) => void\n onSave: () => void\n onClear: () => void\n}): JSX.Element {\n const { label, summary, state, value, secret, placeholder, issue, busy, disabled, onChange, onSave, onClear } = props\n const [editing, setEditing] = useState(false)\n // Close the replace input once a credential save finishes (the shell clears the value).\n const previousBusy = useRef(busy)\n useEffect(() => {\n if (previousBusy.current === 'credentials' && busy === null) setEditing(false)\n previousBusy.current = busy\n }, [busy])\n const showInput = !state.configured || editing || value !== ''\n const source = `${PANEL_COPY.credentialStore} · ${state.ref}${state.source === undefined ? '' : ` · ${state.source}`}`\n return (\n <Row\n label={label}\n tags={(\n <Pill tone={state.configured ? 'ok' : 'muted'}>\n {state.configured ? PANEL_COPY.credentialConfigured : PANEL_COPY.credentialMissing}\n </Pill>\n )}\n summary={summary === undefined ? <span className=\"dsh-channels-cfg__mono\">{source}</span> : summary}\n error={issue?.message ?? (state.writable ? undefined : PANEL_COPY.credentialReadOnly)}\n control={showInput\n ? (\n <div className=\"dsh-channels-cfg__inputRow\">\n <input\n type={secret ? 'password' : 'text'}\n className={`dsh-channels-cfg__input dsh-channels-cfg__input--text${issue === undefined ? '' : ' dsh-channels-cfg__input--invalid'}`}\n value={value}\n placeholder={placeholder}\n autoComplete=\"new-password\"\n aria-label={label}\n disabled={disabled}\n onChange={event => onChange(event.target.value)}\n />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\"\n disabled={disabled || value.trim() === ''}\n onClick={onSave}\n >\n {busy === 'credentials' ? PANEL_COPY.savingCredentials : PANEL_COPY.saveCredentials}\n </button>\n {state.configured\n ? (\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\"\n disabled={busy !== null}\n onClick={() => {\n setEditing(false)\n onChange('')\n }}\n >\n {PANEL_COPY.cancel}\n </button>\n )\n : null}\n </div>\n )\n : (\n <div className=\"dsh-channels-cfg__inputRow\">\n <button type=\"button\" className=\"dsh-channels-cfg__btn\" disabled={disabled} onClick={() => setEditing(true)}>\n {PANEL_COPY.replaceCredential}\n </button>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--remove\" disabled={disabled} onClick={onClear}>\n {busy === 'credentials-clear' ? PANEL_COPY.removingCredential : PANEL_COPY.removeCredential}\n </button>\n </div>\n )}\n />\n )\n}\n\n/** The Feishu application pair (App ID / App Secret). */\nexport function CredentialsSection(props: {\n panel: PanelWire\n credentials: CredentialsView\n}): JSX.Element {\n const { panel, credentials } = props\n const { appId, appSecret, issues, busy, locked, onAppIdChange, onAppSecretChange, onSave, onClear } = credentials\n const appIdState = panel.config.credentials.appId\n const appSecretState = panel.config.credentials.appSecret\n return (\n <Section title={PANEL_COPY.credentialsTitle} footnote={PANEL_COPY.credentialsHint}>\n <CredentialRow\n label={PANEL_COPY.appIdLabel}\n state={appIdState}\n value={appId}\n secret={false}\n placeholder={PANEL_COPY.appIdPlaceholder}\n issue={issues.find(item => item.key === 'appId')}\n busy={busy}\n disabled={busy !== null || locked || !appIdState.writable}\n onChange={onAppIdChange}\n onSave={onSave}\n onClear={() => onClear('appId')}\n />\n <CredentialRow\n label={PANEL_COPY.appSecretLabel}\n state={appSecretState}\n value={appSecret}\n secret\n placeholder={PANEL_COPY.appSecretPlaceholder}\n issue={issues.find(item => item.key === 'appSecret')}\n busy={busy}\n disabled={busy !== null || locked || !appSecretState.writable}\n onChange={onAppSecretChange}\n onSave={onSave}\n onClear={() => onClear('appSecret')}\n />\n </Section>\n )\n}\n","/**\n * 「高级」tab:日志分析接口(监听地址字段 + 访问令牌凭据行)、SDK 日志级别\n * 与 Web 覆盖文件(「恢复 profile 配置」删除整个覆盖层,不走 draft)。\n *\n * @module dsh-channels/client/panel/advanced-tab\n */\n\nimport { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport { CredentialRow } from './credentials-card.tsx'\nimport { FieldSections, Row, Section } from './shared.tsx'\nimport type { AnalyzeTokenView, FormView } from './shared.tsx'\n\nexport function AdvancedTab(props: {\n form: FormView\n analyzeToken: AnalyzeTokenView\n onClear: () => void\n}): JSX.Element {\n const { form, analyzeToken, onClear } = props\n const { panel, busy, locked } = form\n const tokenState = panel.config.credentials.analyzeToken\n const overridden = panel.config.overriddenKeys.length\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n <FieldSections\n form={form}\n groups={TAB_FIELD_GROUPS.advanced}\n extra={{\n analyze: (\n <CredentialRow\n label={PANEL_COPY.analyzeTokenLabel}\n summary={PANEL_COPY.analyzeTokenSummary}\n state={tokenState}\n value={analyzeToken.value}\n secret\n placeholder={PANEL_COPY.analyzeTokenPlaceholder}\n issue={analyzeToken.issues.find(item => item.key === 'analyzeToken')}\n busy={analyzeToken.busy}\n disabled={analyzeToken.busy !== null || analyzeToken.locked || !tokenState.writable}\n onChange={analyzeToken.onChange}\n onSave={analyzeToken.onSave}\n onClear={analyzeToken.onClear}\n />\n ),\n }}\n />\n <Section title={PANEL_COPY.overlayTitle}>\n <Row\n label={PANEL_COPY.overlayPath}\n summary={<span className=\"dsh-channels-cfg__mono\">{panel.status.overlayPath}</span>}\n details={(\n <>\n {PANEL_COPY.overlayPathHelp}\n {' '}\n {overridden === 0 ? PANEL_COPY.noOverrides : fillCopy(PANEL_COPY.overlayCount, { n: overridden })}\n </>\n )}\n control={(\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--danger\"\n onClick={onClear}\n disabled={overridden === 0 || busy !== null || locked}\n >\n {busy === 'clear' ? PANEL_COPY.clearing : PANEL_COPY.clear}\n </button>\n )}\n />\n </Section>\n </div>\n )\n}\n","/**\n * 「连接」tab:按 Host 派生的生命周期阶段渲染。\n *\n * - unconfigured:上手三步 + 应用凭据\n * - idle / error / connecting / connected:状态块(当前阶段的唯一操作 + 运行数字)\n * - 连接后:自检(权限缺口可展开、斜杠指令面板同步结果)\n * - 有需重连才生效的已保存配置:顶部「重启连接」横幅\n *\n * @module dsh-channels/client/panel/connection-tab\n */\n\nimport { useState } from 'react'\nimport { fillCopy, PANEL_COPY } from '../../settings/contract.ts'\nimport type { ChannelStatusWire, PanelWire, ScopeCheckWire, SlashCommandSyncWire } from '../../settings/contract.ts'\nimport { CredentialsSection } from './credentials-card.tsx'\nimport { fieldLabel, phaseBadge, phaseSummary, Pill, Row, Section } from './shared.tsx'\nimport type { BusyAction, CredentialsView } from './shared.tsx'\n\nexport interface ConnectionTabProps {\n panel: PanelWire\n status: ChannelStatusWire\n busy: BusyAction | null\n credentials: CredentialsView\n onReconnect: () => void\n}\n\nexport function ConnectionTab(props: ConnectionTabProps): JSX.Element {\n const { panel, status, busy, credentials, onReconnect } = props\n const checks = status.scopeCheck !== undefined || status.slashCommandSync !== undefined\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {panel.config.restartPendingKeys.length === 0\n ? null\n : <RestartBanner panel={panel} busy={busy} locked={status.busy} onReconnect={onReconnect} />}\n {status.phase === 'unconfigured'\n ? <OnboardingSection status={status} />\n : <StatusBlock status={status} busy={busy} onReconnect={onReconnect} />}\n {checks\n ? (\n <Section title={PANEL_COPY.checksTitle}>\n {status.scopeCheck === undefined ? null : <ScopeCheckRow check={status.scopeCheck} />}\n {status.slashCommandSync === undefined ? null : <SlashCommandSyncRow sync={status.slashCommandSync} />}\n </Section>\n )\n : null}\n <CredentialsSection panel={panel} credentials={credentials} />\n </div>\n )\n}\n\n/** First-run guide: create the app, fill the credentials, save and wait. */\nfunction OnboardingSection(props: { status: ChannelStatusWire }): JSX.Element {\n const { status } = props\n const steps = [\n { title: PANEL_COPY.onboardingStep1Title, text: PANEL_COPY.onboardingStep1Text },\n { title: PANEL_COPY.onboardingStep2Title, text: PANEL_COPY.onboardingStep2Text },\n { title: PANEL_COPY.onboardingStep3Title, text: PANEL_COPY.onboardingStep3Text },\n ]\n return (\n <div className=\"dsh-channels-cfg__hero\">\n <div className=\"dsh-channels-cfg__heroTop\">\n <strong>{PANEL_COPY.onboardingTitle}</strong>\n </div>\n <ol className=\"dsh-channels-cfg__steps\">\n {steps.map((step, index) => (\n <li className=\"dsh-channels-cfg__step\" key={step.title}>\n <span className=\"dsh-channels-cfg__stepIndex\">{index + 1}</span>\n <span className=\"dsh-channels-cfg__stepBody\">\n <span className=\"dsh-channels-cfg__stepTitle\">{step.title}</span>\n <span className=\"dsh-channels-cfg__rowSummary\">{step.text}</span>\n </span>\n </li>\n ))}\n </ol>\n {status.missingConfig.length === 0\n ? null\n : (\n <span className=\"dsh-channels-cfg__rowSummary\">\n {PANEL_COPY.missingConfig}:{status.missingConfig.join('、')}\n </span>\n )}\n </div>\n )\n}\n\n/** Phase, the one lifecycle action available in it, and the runtime counters. */\nfunction StatusBlock(props: {\n status: ChannelStatusWire\n busy: BusyAction | null\n onReconnect: () => void\n}): JSX.Element {\n const { status, busy, onReconnect } = props\n const badge = phaseBadge(status.phase)\n // `status.busy` covers a connection attempt started outside this panel; the\n // local busy flag covers the panel's own action. Either way the button waits.\n const actionBusy = busy !== null || status.busy\n const stats = [\n { label: PANEL_COPY.routeCount, value: String(status.routeCount) },\n { label: PANEL_COPY.liveAgentCount, value: String(status.liveAgentCount) },\n { label: PANEL_COPY.inflightTurnCount, value: String(status.inflightTurnCount) },\n { label: PANEL_COPY.botOpenId, value: status.botOpenIdTail ?? PANEL_COPY.unknownValue, mono: true },\n ]\n let action: JSX.Element | null = null\n if (status.phase === 'connecting') {\n action = <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\" disabled>{PANEL_COPY.connecting}</button>\n } else if (status.phase === 'idle' || status.phase === 'error') {\n action = (\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\" onClick={onReconnect} disabled={actionBusy}>\n {busy === 'reconnect' ? PANEL_COPY.connecting : status.phase === 'idle' ? PANEL_COPY.connect : PANEL_COPY.retry}\n </button>\n )\n } else if (status.phase === 'connected') {\n action = (\n <button type=\"button\" className=\"dsh-channels-cfg__btn\" onClick={onReconnect} disabled={actionBusy}>\n {busy === 'reconnect' ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect}\n </button>\n )\n }\n return (\n <div className=\"dsh-channels-cfg__hero\">\n <div className=\"dsh-channels-cfg__heroTop\">\n <Pill tone={badge.tone} dot>{badge.text}</Pill>\n <strong>{phaseSummary(status.phase)}</strong>\n <span className=\"dsh-channels-cfg__spacer\" />\n {action}\n </div>\n {status.lastStartError === undefined\n ? null\n : (\n <div className=\"dsh-channels-cfg__notice dsh-channels-cfg__notice--error\">\n {PANEL_COPY.lastError}:<span className=\"dsh-channels-cfg__mono\">{status.lastStartError}</span>\n </div>\n )}\n {status.phase === 'connected'\n ? (\n <div className=\"dsh-channels-cfg__stats\">\n {stats.map(stat => (\n <div className=\"dsh-channels-cfg__stat\" key={stat.label}>\n <span>{stat.label}</span>\n <strong className={stat.mono === true ? 'dsh-channels-cfg__mono' : undefined}>{stat.value}</strong>\n </div>\n ))}\n </div>\n )\n : null}\n {status.reasons.length === 0\n ? null\n : (\n <span className={`dsh-channels-cfg__rowSummary${status.level === 'unhealthy' ? ' dsh-channels-cfg__textBad' : ''}`}>\n {PANEL_COPY.healthReasons}:{status.reasons.join(';')}\n </span>\n )}\n </div>\n )\n}\n\n/** Status icon in front of one check row. */\nfunction CheckIcon(props: { tone: 'ok' | 'warn' | 'bad' }): JSX.Element {\n return (\n <span className={`dsh-channels-cfg__checkIcon dsh-channels-cfg__checkIcon--${props.tone}`} aria-hidden=\"true\">\n {props.tone === 'ok' ? '✓' : '!'}\n </span>\n )\n}\n\n/** Post-connect scope self-check: the gap count, expandable to each scope with its console link. */\nfunction ScopeCheckRow(props: { check: ScopeCheckWire }): JSX.Element {\n const { check } = props\n const [open, setOpen] = useState(false)\n if (check.state === 'ok') {\n return <Row leading={<CheckIcon tone=\"ok\" />} label={PANEL_COPY.scopeCheckOk} />\n }\n if (check.state === 'error') {\n return (\n <Row\n leading={<CheckIcon tone=\"bad\" />}\n label={PANEL_COPY.scopeCheckError}\n summary={<span className=\"dsh-channels-cfg__mono\">{check.error}</span>}\n />\n )\n }\n const required = check.missing.filter(item => item.level === 'required').length\n return (\n <>\n <Row\n leading={<CheckIcon tone={required > 0 ? 'bad' : 'warn'} />}\n label={required > 0\n ? fillCopy(PANEL_COPY.scopeCheckMissingRequired, { n: required })\n : fillCopy(PANEL_COPY.scopeCheckMissing, { n: check.missing.length })}\n summary={required > 0 ? PANEL_COPY.scopeCheckPublish : PANEL_COPY.scopeCheckMissingOptional}\n control={(\n <div className=\"dsh-channels-cfg__inputRow\">\n <button type=\"button\" className=\"dsh-channels-cfg__more\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? PANEL_COPY.collapse : PANEL_COPY.expand}\n </button>\n <a className=\"dsh-channels-cfg__btn\" href={check.consoleUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n {PANEL_COPY.scopeCheckOpenAll}\n </a>\n </div>\n )}\n />\n {open\n ? (\n <ul className=\"dsh-channels-cfg__scopeList\">\n {check.missing.map(item => (\n <li key={item.scopes.join(',')}>\n <code className=\"dsh-channels-cfg__mono\">{item.scopes.join(' / ')}</code>\n {item.level === 'required' ? <Pill tone=\"bad\">{PANEL_COPY.scopeCheckRequired}</Pill> : null}\n <span>{item.purpose}</span>\n <a className=\"dsh-channels-cfg__link\" href={item.consoleUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n {PANEL_COPY.scopeCheckOpenOne}\n </a>\n </li>\n ))}\n </ul>\n )\n : null}\n </>\n )\n}\n\n/** Post-connect native slash-command panel sync: the command count, or the Feishu reason. */\nfunction SlashCommandSyncRow(props: { sync: SlashCommandSyncWire }): JSX.Element {\n const { sync } = props\n return sync.state === 'ok'\n ? (\n <Row\n leading={<CheckIcon tone=\"ok\" />}\n label={PANEL_COPY.slashCommandSyncTitle}\n summary={fillCopy(PANEL_COPY.slashCommandSyncOk, { total: sync.total })}\n />\n )\n : (\n <Row\n leading={<CheckIcon tone=\"warn\" />}\n label={PANEL_COPY.slashCommandSyncTitle}\n summary={<>{PANEL_COPY.slashCommandSyncError}:<span className=\"dsh-channels-cfg__mono\">{sync.error}</span></>}\n />\n )\n}\n\n/** Config saved but only applied on the next connect: offer the one-click restart. */\nfunction RestartBanner(props: {\n panel: PanelWire\n busy: BusyAction | null\n locked: boolean\n onReconnect: () => void\n}): JSX.Element {\n const { panel, busy, locked, onReconnect } = props\n const keys = panel.config.restartPendingKeys\n .map(key => fieldLabel(panel.config.fields, key))\n .join('、')\n return (\n <div className=\"dsh-channels-cfg__banner\">\n <span className=\"dsh-channels-cfg__bannerText\">{PANEL_COPY.restartBanner}{keys}</span>\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn\"\n onClick={onReconnect}\n disabled={busy !== null || locked}\n >\n {busy === 'reconnect' ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect}\n </button>\n </div>\n )\n}\n","/**\n * Tabs made only of Host fields (「会话」「模型」「附件与工具」): one titled\n * section per field group the tab owns (`TAB_FIELD_GROUPS`).\n *\n * @module dsh-channels/client/panel/fields-tab\n */\n\nimport type { ReactNode } from 'react'\nimport { PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type { FieldGroup, PanelTabId } from '../../settings/contract.ts'\nimport { FieldSections } from './shared.tsx'\nimport type { FormView } from './shared.tsx'\n\n/** Footnotes under one group's section, per group. */\nconst FOOTNOTES: Partial<Record<FieldGroup, ReactNode>> = {\n model: PANEL_COPY.modelFootnote,\n}\n\nexport function FieldsTab(props: { form: FormView; tab: PanelTabId; note?: string }): JSX.Element {\n const { form, tab, note } = props\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {note === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{note}</p>}\n <FieldSections form={form} groups={TAB_FIELD_GROUPS[tab]} footnotes={FOOTNOTES} />\n </div>\n )\n}\n","/**\n * Live Feishu names and avatars for every user and chat the access and wiki\n * tabs show. The page asks the Host once on entry and again whenever the set\n * of shown ids changes; a failed request keeps the previous answer.\n *\n * @module dsh-channels/client/panel/profiles\n */\n\nimport { useEffect, useMemo, useState } from 'react'\nimport type { AccessWire, FeishuProfilesWire, FeishuProfileWire } from '../../settings/contract.ts'\nimport { fetchFeishuProfiles } from '../api.ts'\n\n/** Resolved profiles plus the reason some could not be read. */\nexport interface ProfileView {\n user: (userId: string) => FeishuProfileWire | undefined\n chat: (chatId: string) => FeishuProfileWire | undefined\n /** Feishu or request failure reason, when one lookup failed. */\n warning?: string\n}\n\n/**\n * The `user_id`s and `chat_id`s one access snapshot shows.\n * @param access - the panel's access snapshot.\n * @returns sorted, distinct ids per kind.\n */\nexport function profileIds(access: AccessWire): { users: string[]; chats: string[] } {\n const users = new Set<string>()\n const chats = new Set<string>()\n if (access.admin.userId !== undefined) users.add(access.admin.userId)\n for (const user of access.users) users.add(user.userId)\n for (const user of access.wikiUsers) users.add(user.userId)\n for (const group of access.groups) chats.add(group.chatId)\n for (const request of access.requests) {\n if (request.kind === 'user') users.add(request.targetId)\n else chats.add(request.targetId)\n if (request.chatId !== undefined) chats.add(request.chatId)\n }\n return { users: [...users].sort(), chats: [...chats].sort() }\n}\n\n/**\n * Profiles for one access snapshot; empty until the panel has loaded.\n * @param access - the panel's access snapshot, null while loading.\n * @returns lookups by id and the latest failure reason.\n */\nexport function useFeishuProfiles(access: AccessWire | null): ProfileView {\n const ids = useMemo(() => (access === null ? { users: [], chats: [] } : profileIds(access)), [access])\n const key = `${ids.users.join(',')}|${ids.chats.join(',')}`\n const [answer, setAnswer] = useState<FeishuProfilesWire>({ users: {}, chats: {} })\n const [requestError, setRequestError] = useState<string | undefined>(undefined)\n useEffect(() => {\n if (ids.users.length === 0 && ids.chats.length === 0) return\n let current = true\n fetchFeishuProfiles(ids.users, ids.chats).then(\n (next) => {\n if (!current) return\n setAnswer(next)\n setRequestError(undefined)\n },\n (error: unknown) => { if (current) setRequestError(error instanceof Error ? error.message : String(error)) },\n )\n return () => { current = false }\n // `key` carries `ids`: a new snapshot with the same ids does not re-ask.\n }, [key])\n return useMemo(() => {\n const warning = requestError ?? answer.warning\n return {\n user: (userId: string) => answer.users[userId],\n chat: (chatId: string) => answer.chats[chatId],\n ...(warning === undefined ? {} : { warning }),\n }\n }, [answer, requestError])\n}\n","/**\n * 「知识库」tab:知识库工具字段(读取 / 编辑开关、可访问范围)+ 知识库\n * 白名单(「可使用的人」)。字段保存走全局 draft,白名单增删是独立即时\n * mutation。\n *\n * @module dsh-channels/client/panel/wiki-tab\n */\n\nimport { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type { ProfileView } from './profiles.ts'\nimport { FieldSections, NoticeBar } from './shared.tsx'\nimport type { FormView } from './shared.tsx'\nimport { Whitelist } from './whitelist.tsx'\n\nexport interface WikiTabProps {\n form: FormView\n profiles: ProfileView\n accessNotice: string | null\n accessError: string | null\n onAddUser: (userId: string, name: string) => Promise<boolean>\n onRemoveUser: (userId: string) => void\n}\n\nexport function WikiTab(props: WikiTabProps): JSX.Element {\n const { form, profiles, accessNotice, accessError, onAddUser, onRemoveUser } = props\n const { access } = form.panel\n const disabled = form.busy !== null || form.locked || !access.loaded\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {accessNotice === null ? null : <NoticeBar kind=\"ok\" text={accessNotice} />}\n {accessError === null ? null : <NoticeBar kind=\"error\" text={accessError} />}\n {profiles.warning === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{fillCopy(PANEL_COPY.profilesWarning, { reason: profiles.warning })}</p>}\n <FieldSections form={form} groups={TAB_FIELD_GROUPS.wiki} />\n <Whitelist\n title={PANEL_COPY.wikiUsersTitle}\n countLabel={fillCopy(PANEL_COPY.wikiUsersAdminPlus, { n: access.wikiUsers.length })}\n entries={access.wikiUsers.map(user => ({\n id: user.userId,\n name: profiles.user(user.userId)?.name ?? user.name,\n avatarUrl: profiles.user(user.userId)?.avatarUrl,\n source: user.source,\n }))}\n empty={PANEL_COPY.wikiUsersEmpty}\n loaded={access.loaded}\n addLabel={PANEL_COPY.accessAddUser}\n idPlaceholder={PANEL_COPY.accessUserIdPlaceholder}\n namePlaceholder={PANEL_COPY.accessUserNamePlaceholder}\n footnote={PANEL_COPY.wikiUsersHint}\n disabled={disabled}\n working={form.busy === 'access'}\n onAdd={onAddUser}\n onRemove={onRemoveUser}\n />\n </div>\n )\n}\n","/**\n * 左侧栏「飞书渠道」主区页面:数据加载、5 秒状态轮询、全局 draft、顶部状态头 /\n * tab 导航与底部保存条的外壳。\n *\n * 所有 Host 交互(panel / status / catalog / config / credentials / access /\n * pick-directory)都集中在这里;tab 组件只接收渲染所需的数据与\n * 回调。页面不 import 任何 DSH 客户端包,只依赖 react 与 `main` 槽位。\n *\n * @module dsh-channels/client/panel\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { KeyboardEvent } from 'react'\nimport { PANEL_COPY, PANEL_TABS, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type {\n AccessMutationResponse,\n AccessWire,\n CatalogWire,\n ChannelStatusWire,\n ConfigValue,\n CredentialField,\n EditableKey,\n EditableValues,\n FieldIssueWire,\n PanelTabId,\n PanelWire,\n} from '../../settings/contract.ts'\nimport {\n addAccessGroup,\n addAccessUser,\n addWikiUser,\n clearConfig,\n clearCredential,\n fetchCatalog,\n fetchPanel,\n fetchStatus,\n pickDirectory as requestDirectoryPicker,\n reconnect,\n removeAccessGroup,\n removeAccessUser,\n removeWikiUser,\n resolveAccessRequest,\n saveConfig,\n saveCredentials,\n} from '../api.ts'\nimport { applyCatalogFields, catalogFromFields } from '../catalog.ts'\nimport { AccessTab } from './access-tab.tsx'\nimport { AdvancedTab } from './advanced-tab.tsx'\nimport { ConnectionTab } from './connection-tab.tsx'\nimport { FieldsTab } from './fields-tab.tsx'\nimport { useFeishuProfiles } from './profiles.ts'\nimport {\n fieldLabel,\n formatUptime,\n messageOf,\n NoticeBar,\n phaseBadge,\n Pill,\n SaveBar,\n validateDraft,\n} from './shared.tsx'\nimport type {\n AnalyzeTokenView,\n BusyAction,\n CredentialsView,\n FormView,\n LoadState,\n Notice,\n} from './shared.tsx'\nimport { WikiTab } from './wiki-tab.tsx'\n\n/**\n * Main-column page for the `main` slot: a full-height scroll region with the\n * centered panel column. The slot passes no owner props.\n * @returns the page.\n */\nexport function FeishuChannelsPage(): JSX.Element {\n return (\n <section className=\"dsh-channels-page\" aria-label={PANEL_COPY.title}>\n <div className=\"dsh-channels-page__content\">\n <FeishuChannelsPanel />\n </div>\n </section>\n )\n}\n\n/** Panel shell: one panel payload, one status poll, one global config draft. */\nfunction FeishuChannelsPanel(): JSX.Element | null {\n const [load, setLoad] = useState<LoadState>({ phase: 'loading' })\n const [activeTab, setActiveTab] = useState<PanelTabId>('connection')\n const [draft, setDraft] = useState<EditableValues>({})\n const [notice, setNotice] = useState<Notice | null>(null)\n const [busy, setBusy] = useState<BusyAction | null>(null)\n const [status, setStatus] = useState<ChannelStatusWire | null>(null)\n const [pollError, setPollError] = useState<string | null>(null)\n const [appIdInput, setAppIdInput] = useState('')\n const [appSecretInput, setAppSecretInput] = useState('')\n const [analyzeTokenInput, setAnalyzeTokenInput] = useState('')\n const [catalog, setCatalog] = useState<CatalogWire | null>(null)\n const [catalogBusy, setCatalogBusy] = useState(false)\n const [accessNotice, setAccessNotice] = useState<string | null>(null)\n const [accessError, setAccessError] = useState<string | null>(null)\n const catalogTicket = useRef(0)\n /** Latest ready panel, read from the poll loop without restarting it. */\n const panelRef = useRef<PanelWire | null>(null)\n const panel = load.phase === 'ready' ? load.panel : null\n /** Host-side lifecycle busy: connecting/restarting blocks every panel write. */\n const hostBusy = (status ?? panel?.status)?.busy === true\n useEffect(() => {\n panelRef.current = panel\n }, [panel])\n\n const loadPanel = useCallback(async (): Promise<void> => {\n setLoad({ phase: 'loading' })\n setNotice(null)\n setAccessNotice(null)\n setAccessError(null)\n try {\n const next = await fetchPanel()\n setLoad({ phase: 'ready', panel: next })\n setStatus(next.status)\n setDraft({})\n setAppIdInput('')\n setAppSecretInput('')\n setAnalyzeTokenInput('')\n } catch (error) {\n setLoad({ phase: 'error', message: messageOf(error) })\n }\n }, [])\n\n useEffect(() => {\n void loadPanel()\n }, [loadPanel])\n\n // The Host panel payload already carries options resolved for the effective\n // route; re-seed the local catalog whenever a fresh panel arrives (load/save).\n useEffect(() => {\n if (panel === null) {\n setCatalog(null)\n return\n }\n setCatalog(catalogFromFields(panel.config.fields))\n setCatalogBusy(false)\n }, [panel])\n\n useEffect(() => {\n if (load.phase !== 'ready') return undefined\n let cancelled = false\n const tick = async (): Promise<void> => {\n try {\n const previousPhase = panelRef.current?.status.phase\n const next = await fetchStatus()\n if (cancelled) return\n setStatus(next)\n setPollError(null)\n // A phase transition changes what the panel payload can offer: the\n // storage domain opens/closes with the connection, so the access\n // whitelists go stale. Re-read the panel but\n // never touch the dirty draft.\n if (previousPhase !== undefined && previousPhase !== next.phase) {\n try {\n const refreshed = await fetchPanel()\n if (!cancelled) {\n setLoad({ phase: 'ready', panel: refreshed })\n setStatus(refreshed.status)\n }\n } catch {\n // Keep the status-only update; the next manual refresh retries.\n }\n }\n } catch (error) {\n if (!cancelled) setPollError(messageOf(error))\n }\n }\n const timer = window.setInterval(() => { void tick() }, 5_000)\n return () => {\n cancelled = true\n window.clearInterval(timer)\n }\n }, [load.phase])\n\n /** Fetch the selector catalog for a draft route (latest request wins). */\n const refreshCatalog = useCallback(async (provider: string, model: string): Promise<void> => {\n catalogTicket.current += 1\n const ticket = catalogTicket.current\n setCatalogBusy(true)\n try {\n const next = await fetchCatalog(provider, model)\n if (catalogTicket.current === ticket) setCatalog(next)\n } catch {\n if (catalogTicket.current !== ticket) return\n // Never keep another route's options: degrade the dependent fields to the\n // free-text controls instead of offering stale values.\n setCatalog(previous => previous === null ? null : {\n provider: previous.provider,\n model: { selectable: false, options: [] },\n reasoningEffort: { selectable: false, options: [] },\n agentPreset: previous.agentPreset,\n })\n setNotice({ kind: 'error', text: PANEL_COPY.catalogFailed })\n } finally {\n if (catalogTicket.current === ticket) setCatalogBusy(false)\n }\n }, [])\n\n /** Current form values, used to keep unlisted configured values visible. */\n const currentValues = useMemo(() => {\n if (panel === null) return {}\n const valueOf = (key: EditableKey): string | undefined => {\n const raw = draft[key] ?? panel.config.effective[key] ?? panel.config.base[key]\n return typeof raw === 'string' && raw !== '' ? raw : undefined\n }\n return {\n provider: valueOf('provider'),\n model: valueOf('model'),\n reasoningEffort: valueOf('reasoningEffort'),\n agentPreset: valueOf('agentPreset'),\n }\n }, [draft, panel])\n\n const renderedFields = useMemo(\n () => applyCatalogFields(panel?.config.fields ?? [], catalog, currentValues),\n [panel, catalog, currentValues],\n )\n\n /** Whether one selector currently renders as a dropdown for this form. */\n const isSelectField = useCallback(\n (key: EditableKey): boolean => renderedFields.some(field => field.key === key && field.kind === 'select'),\n [renderedFields],\n )\n\n /** Draft one field, cascading provider → model → effort like the Host catalog. */\n const updateDraft = useCallback((key: EditableKey, value: ConfigValue) => {\n if (panel === null) return\n const providerNow = String(\n draft.provider ?? panel.config.effective.provider ?? panel.config.base.provider ?? '',\n )\n setNotice(null)\n if (key === 'provider') {\n // Changing the provider invalidates the model/effort below it. When the\n // selected provider is the configured one, keep showing its configured\n // model; otherwise fall back to the inherit choice.\n const nextProvider = String(value ?? '')\n const configuredProvider = String(\n panel.config.effective.provider ?? panel.config.base.provider ?? '',\n )\n const keepModel = nextProvider === configuredProvider\n ? String(panel.config.effective.model ?? panel.config.base.model ?? '')\n : ''\n setDraft(previous => ({ ...previous, provider: value, model: '', reasoningEffort: '' }))\n if (catalog !== null && isSelectField('provider')) void refreshCatalog(nextProvider, keepModel)\n return\n }\n if (key === 'model') {\n setDraft(previous => ({ ...previous, model: value, reasoningEffort: '' }))\n if (catalog !== null && isSelectField('model')) void refreshCatalog(providerNow, String(value ?? ''))\n return\n }\n setDraft(previous => ({ ...previous, [key]: value }))\n }, [catalog, draft.provider, isSelectField, panel, refreshCatalog])\n\n const refreshStatus = useCallback(async (): Promise<void> => {\n try {\n const next = await fetchStatus()\n setStatus(next)\n setPollError(null)\n } catch (error) {\n setPollError(messageOf(error))\n }\n }, [])\n\n /**\n * Manual refresh: reload the whole panel payload so access lists and\n * `restartPendingKeys` catch up with the Host. The dirty draft is\n * deliberately preserved — refreshing never discards user input.\n */\n const onRefresh = useCallback(async (): Promise<void> => {\n try {\n const next = await fetchPanel()\n setLoad({ phase: 'ready', panel: next })\n setStatus(next.status)\n setPollError(null)\n setAccessNotice(null)\n setAccessError(null)\n } catch (error) {\n setPollError(messageOf(error))\n }\n }, [])\n\n /** Submit every dirty field at once; the Host persists and re-validates. */\n const onSave = useCallback(async (): Promise<void> => {\n if (panel === null) return\n const { values, issues } = validateDraft(panel, draft)\n if (issues.length > 0) {\n // Name the offending fields: with one draft shared by every tab, the\n // user may be looking at a different tab than the invalid field.\n const detail = issues\n .map(issue => `${fieldLabel(panel.config.fields, issue.key as EditableKey)} ${issue.message}`)\n .join(';')\n setNotice({ kind: 'error', text: `${PANEL_COPY.saveInvalid}:${detail}`, issues })\n return\n }\n if (Object.keys(values).length === 0) return\n setBusy('save')\n setNotice(null)\n try {\n const result = await saveConfig(values)\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n // Clear only the submitted keys: edits typed while the request was in\n // flight stay in the draft instead of being silently discarded.\n setDraft(previous => {\n const next = { ...previous }\n for (const key of Object.keys(values) as EditableKey[]) delete next[key]\n return next\n })\n setNotice({ kind: 'ok', text: PANEL_COPY.saved })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [draft, panel])\n\n /** Drop the whole draft (nothing is written). */\n const discardDraft = useCallback((): void => {\n if (panel === null) return\n setDraft({})\n setNotice(null)\n catalogTicket.current += 1\n setCatalog(catalogFromFields(panel.config.fields))\n setCatalogBusy(false)\n }, [panel])\n\n /**\n * Open the host's native directory chooser (workspace root「选择目录…」) and\n * stage the picked absolute path as a draft value. Nothing is persisted until\n *「保存更改」; a canceled dialog is a no-op, not an error.\n */\n const onPickDirectory = useCallback(async (key: EditableKey): Promise<void> => {\n setNotice(null)\n try {\n const result = await requestDirectoryPicker()\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error })\n return\n }\n if ('canceled' in result) return\n setDraft(previous => ({ ...previous, [key]: result.path }))\n setNotice({ kind: 'ok', text: `${PANEL_COPY.directoryPicked}:${result.path}` })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n }\n }, [])\n\n /** Drop every Web override and return the refreshed panel. */\n const onClear = useCallback(async (): Promise<void> => {\n const confirmed = typeof window.confirm === 'function'\n ? window.confirm(PANEL_COPY.clearConfirm)\n : true\n if (!confirmed) return\n setBusy('clear')\n setNotice(null)\n try {\n const result = await clearConfig()\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setDraft({})\n setNotice({ kind: 'ok', text: PANEL_COPY.cleared })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [])\n\n const running = (status ?? panel?.status)?.running === true\n\n /**\n * The single connect/reconnect action: open a stopped channel or restart a\n * running one. Only a restart interrupts live replies, so only it confirms.\n */\n const onReconnect = useCallback(async (): Promise<void> => {\n if (running) {\n const confirmed = typeof window.confirm === 'function'\n ? window.confirm(PANEL_COPY.restartWarning)\n : true\n if (!confirmed) return\n }\n setBusy('reconnect')\n setNotice(null)\n try {\n const result = await reconnect()\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error })\n // Incomplete credentials: send the user back to the onboarding form.\n if (result.unconfigured === true) setActiveTab('connection')\n await refreshStatus()\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setNotice({ kind: 'ok', text: running ? PANEL_COPY.reconnectDone : PANEL_COPY.connectDone })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n await refreshStatus()\n } finally {\n setBusy(null)\n }\n }, [refreshStatus, running])\n\n /** Replace the embedded access snapshot after a whitelist mutation. */\n const applyAccess = useCallback((access: AccessWire): void => {\n setLoad(previous => previous.phase === 'ready'\n ? { phase: 'ready', panel: { ...previous.panel, access } }\n : previous)\n }, [])\n\n const onAddAccessGroup = useCallback(async (chatId: string, name: string): Promise<boolean> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await addAccessGroup(chatId, name)\n if (!result.ok) {\n setAccessError(result.error)\n return false\n }\n applyAccess(result.access)\n setAccessNotice(PANEL_COPY.accessGroupAdded)\n return true\n } catch (error) {\n setAccessError(messageOf(error))\n return false\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n const onRemoveAccessGroup = useCallback(async (chatId: string): Promise<void> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await removeAccessGroup(chatId)\n if (!result.ok) {\n setAccessError(result.error)\n return\n }\n applyAccess(result.access)\n setAccessNotice(PANEL_COPY.accessGroupRemoved)\n } catch (error) {\n setAccessError(messageOf(error))\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n /** Add one user to a whitelist; resolves true when the input should clear. */\n const addUser = useCallback(async (\n add: (userId: string, name: string) => Promise<AccessMutationResponse>,\n userId: string,\n name: string,\n notice: string,\n ): Promise<boolean> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await add(userId, name)\n if (!result.ok) {\n setAccessError(result.error)\n return false\n }\n applyAccess(result.access)\n setAccessNotice(notice)\n return true\n } catch (error) {\n setAccessError(messageOf(error))\n return false\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n const removeUser = useCallback(async (\n remove: (userId: string) => Promise<AccessMutationResponse>,\n userId: string,\n notice: string,\n ): Promise<void> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await remove(userId)\n if (!result.ok) {\n setAccessError(result.error)\n return\n }\n applyAccess(result.access)\n setAccessNotice(notice)\n } catch (error) {\n setAccessError(messageOf(error))\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n const onResolveAccessRequest = useCallback(async (token: string, decision: 'allow' | 'deny'): Promise<void> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await resolveAccessRequest(token, decision)\n if (!result.ok) {\n setAccessError(result.error)\n return\n }\n applyAccess(result.access)\n setAccessNotice(decision === 'allow' ? PANEL_COPY.accessRequestAllowed : PANEL_COPY.accessRequestDenied)\n } catch (error) {\n setAccessError(messageOf(error))\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n /** Save provided credential values; the Host reconnects automatically when ready. */\n const onSaveCredentials = useCallback(async (): Promise<void> => {\n const values: { appId?: string; appSecret?: string } = {}\n if (appIdInput.trim() !== '') values.appId = appIdInput\n if (appSecretInput.trim() !== '') values.appSecret = appSecretInput\n if (values.appId === undefined && values.appSecret === undefined) return\n setBusy('credentials')\n setNotice(null)\n try {\n const result = await saveCredentials(values)\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setAppIdInput('')\n setAppSecretInput('')\n const base = result.reconnect === 'pending'\n ? PANEL_COPY.credentialSavedPending\n : result.reconnect === 'auto'\n ? PANEL_COPY.credentialSavedReconnected\n : result.reconnect === 'unconfigured'\n ? PANEL_COPY.credentialSavedUnconfigured\n : result.reconnect === 'failed'\n ? PANEL_COPY.credentialAutoConnectFailed\n : PANEL_COPY.credentialSaved\n setNotice({\n kind: result.reconnect === 'failed' || result.autoReconnectError !== undefined ? 'error' : 'ok',\n text: result.autoReconnectError === undefined\n ? base\n : `${base}:${result.autoReconnectError}`,\n })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [appIdInput, appSecretInput])\n\n /** Save the analyze API token; it applies to the next request, no reconnect. */\n const onSaveAnalyzeToken = useCallback(async (): Promise<void> => {\n if (analyzeTokenInput.trim() === '') return\n setBusy('credentials')\n setNotice(null)\n try {\n const result = await saveCredentials({ analyzeToken: analyzeTokenInput })\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setAnalyzeTokenInput('')\n setNotice({ kind: 'ok', text: PANEL_COPY.analyzeTokenSaved })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [analyzeTokenInput])\n\n const onClearCredential = useCallback(async (field: CredentialField): Promise<void> => {\n const confirmed = typeof window.confirm === 'function'\n ? window.confirm(PANEL_COPY.credentialRemoveConfirm)\n : true\n if (!confirmed) return\n setBusy('credentials-clear')\n setNotice(null)\n try {\n const result = await clearCredential(field)\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setNotice({\n kind: 'ok',\n text: field === 'analyzeToken'\n ? PANEL_COPY.analyzeTokenRemoved\n : result.reconnect === 'pending' ? PANEL_COPY.credentialRemovedPending : PANEL_COPY.credentialRemoved,\n })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [])\n\n const issues = useMemo<FieldIssueWire[]>(\n () => (notice?.kind === 'error' ? notice.issues ?? [] : []),\n [notice],\n )\n const dirtyCount = Object.keys(draft).length\n const restartCount = useMemo(\n () => (panel?.config.fields ?? []).filter(field => field.restartRequired && field.key in draft).length,\n [draft, panel],\n )\n\n /** Which tabs currently own a dirty key (the dot is per tab, not global). */\n const dirtyTabs = useMemo<ReadonlySet<PanelTabId>>(() => {\n const tabs = new Set<PanelTabId>()\n if (panel === null || dirtyCount === 0) return tabs\n const groupOf = new Map(panel.config.fields.map(field => [field.key, field.group]))\n for (const key of Object.keys(draft)) {\n const group = groupOf.get(key as EditableKey)\n if (group === undefined) continue\n for (const tab of PANEL_TABS) {\n if (TAB_FIELD_GROUPS[tab.id].includes(group)) tabs.add(tab.id)\n }\n }\n return tabs\n }, [dirtyCount, draft, panel])\n\n const form = useMemo<FormView | null>(() => {\n if (panel === null) return null\n return {\n panel,\n fields: renderedFields,\n draft,\n issues,\n busy,\n locked: hostBusy,\n onChange: updateDraft,\n onPickDirectory,\n }\n }, [busy, draft, hostBusy, issues, onPickDirectory, panel, renderedFields, updateDraft])\n\n const profiles = useFeishuProfiles(panel?.access ?? null)\n\n const credentials = useMemo<CredentialsView>(() => ({\n appId: appIdInput,\n appSecret: appSecretInput,\n issues,\n busy,\n locked: hostBusy,\n onAppIdChange: setAppIdInput,\n onAppSecretChange: setAppSecretInput,\n onSave: () => { void onSaveCredentials() },\n onClear: (field: CredentialField) => { void onClearCredential(field) },\n }), [appIdInput, appSecretInput, busy, hostBusy, issues, onClearCredential, onSaveCredentials])\n\n const analyzeToken = useMemo<AnalyzeTokenView>(() => ({\n value: analyzeTokenInput,\n issues,\n busy,\n locked: hostBusy,\n onChange: setAnalyzeTokenInput,\n onSave: () => { void onSaveAnalyzeToken() },\n onClear: () => { void onClearCredential('analyzeToken') },\n }), [analyzeTokenInput, busy, hostBusy, issues, onClearCredential, onSaveAnalyzeToken])\n\n if (load.phase === 'loading') {\n return (\n <div className=\"dsh-channels-cfg\">\n <div className=\"dsh-channels-cfg__loading\">{PANEL_COPY.loading}</div>\n </div>\n )\n }\n if (load.phase === 'error') {\n return (\n <div className=\"dsh-channels-cfg\">\n <div className=\"dsh-channels-cfg__error\">\n <strong>{PANEL_COPY.loadFailed}</strong>\n <span>{load.message}</span>\n <div>\n <button type=\"button\" className=\"dsh-channels-cfg__btn\" onClick={() => { void loadPanel() }}>\n {PANEL_COPY.retry}\n </button>\n </div>\n </div>\n </div>\n )\n }\n if (form === null) return null\n const current = status ?? load.panel.status\n\n return (\n <div className=\"dsh-channels-cfg\">\n <PanelHeader\n status={current}\n pollError={pollError}\n busy={busy}\n locked={hostBusy}\n onRefresh={() => { void onRefresh() }}\n />\n <TabNav\n activeTab={activeTab}\n dirtyTabs={dirtyTabs}\n counts={{ access: load.panel.access.loaded ? load.panel.access.requests.length : 0 }}\n onChange={setActiveTab}\n />\n {load.panel.access.mode === 'open' ? <div className=\"dsh-channels-cfg__banner\">{PANEL_COPY.debugModeWarning}</div> : null}\n {notice === null ? null : <NoticeBar kind={notice.kind} text={notice.text} />}\n <div\n role=\"tabpanel\"\n id={`dsh-channels-tabpanel-${activeTab}`}\n aria-labelledby={`dsh-channels-tab-${activeTab}`}\n >\n {activeTab === 'connection'\n ? (\n <ConnectionTab\n panel={form.panel}\n status={current}\n busy={busy}\n credentials={credentials}\n onReconnect={() => { void onReconnect() }}\n />\n )\n : null}\n {activeTab === 'sessions' ? <FieldsTab form={form} tab=\"sessions\" /> : null}\n {activeTab === 'model'\n ? <FieldsTab form={form} tab=\"model\" note={catalogBusy ? PANEL_COPY.catalogLoading : undefined} />\n : null}\n {activeTab === 'attachments' ? <FieldsTab form={form} tab=\"attachments\" /> : null}\n {activeTab === 'access'\n ? (\n <AccessTab\n form={form}\n profiles={profiles}\n accessNotice={accessNotice}\n accessError={accessError}\n onAddGroup={onAddAccessGroup}\n onRemoveGroup={chatId => { void onRemoveAccessGroup(chatId) }}\n onAddUser={(userId, name) => addUser(addAccessUser, userId, name, PANEL_COPY.accessUserAdded)}\n onRemoveUser={userId => { void removeUser(removeAccessUser, userId, PANEL_COPY.accessUserRemoved) }}\n onResolveRequest={(token, decision) => { void onResolveAccessRequest(token, decision) }}\n />\n )\n : null}\n {activeTab === 'wiki'\n ? (\n <WikiTab\n form={form}\n profiles={profiles}\n accessNotice={accessNotice}\n accessError={accessError}\n onAddUser={(userId, name) => addUser(addWikiUser, userId, name, PANEL_COPY.wikiUserAdded)}\n onRemoveUser={userId => { void removeUser(removeWikiUser, userId, PANEL_COPY.wikiUserRemoved) }}\n />\n )\n : null}\n {activeTab === 'advanced'\n ? <AdvancedTab form={form} analyzeToken={analyzeToken} onClear={() => { void onClear() }} />\n : null}\n </div>\n <SaveBar\n dirtyCount={dirtyCount}\n restartCount={restartCount}\n busy={busy}\n locked={hostBusy}\n onSave={() => { void onSave() }}\n onDiscard={discardDraft}\n />\n </div>\n )\n}\n\n/** Page header: title, phase pill, version and uptime, refresh; visible on every tab. */\nfunction PanelHeader(props: {\n status: ChannelStatusWire\n pollError: string | null\n busy: BusyAction | null\n locked: boolean\n onRefresh: () => void\n}): JSX.Element {\n const { status, pollError, busy, locked, onRefresh } = props\n const badge = phaseBadge(status.phase)\n return (\n <header className=\"dsh-channels-cfg__panelHead\">\n <div className=\"dsh-channels-cfg__headRow\">\n <h1 className=\"dsh-channels-cfg__title\">{PANEL_COPY.title}</h1>\n <Pill tone={badge.tone} dot>{badge.text}</Pill>\n <span className=\"dsh-channels-cfg__meta dsh-channels-cfg__mono\">v{status.version}</span>\n {status.uptimeMs === undefined\n ? null\n : <span className=\"dsh-channels-cfg__meta\">· {PANEL_COPY.uptimePrefix} {formatUptime(status.uptimeMs)}</span>}\n <span className=\"dsh-channels-cfg__spacer\" />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\"\n onClick={onRefresh}\n disabled={busy !== null || locked}\n >\n {PANEL_COPY.refresh}\n </button>\n </div>\n {pollError === null\n ? null\n : (\n <span className=\"dsh-channels-cfg__fieldError\" role=\"status\" aria-live=\"polite\">\n {PANEL_COPY.pollFailed}:{pollError}\n </span>\n )}\n </header>\n )\n}\n\n/** Top-level tab navigation; the dirty dot keeps unsaved work visible per tab. */\nfunction TabNav(props: {\n activeTab: PanelTabId\n dirtyTabs: ReadonlySet<PanelTabId>\n /** Items waiting for the operator per tab (pending access requests). */\n counts: Partial<Record<PanelTabId, number>>\n onChange: (tab: PanelTabId) => void\n}): JSX.Element {\n const { activeTab, dirtyTabs, counts, onChange } = props\n const refs = useRef<Array<HTMLButtonElement | null>>([])\n\n /** Roving-focus keyboard navigation between tabs (WAI-ARIA tabs pattern). */\n const onKeyDown = (index: number, event: KeyboardEvent<HTMLButtonElement>): void => {\n const last = PANEL_TABS.length - 1\n let next: number\n switch (event.key) {\n case 'ArrowRight': next = index === last ? 0 : index + 1; break\n case 'ArrowLeft': next = index === 0 ? last : index - 1; break\n case 'Home': next = 0; break\n case 'End': next = last; break\n default: return\n }\n event.preventDefault()\n onChange(PANEL_TABS[next]!.id)\n refs.current[next]?.focus()\n }\n\n return (\n <nav className=\"dsh-channels-cfg__tabs\" role=\"tablist\" aria-label={PANEL_COPY.title}>\n {PANEL_TABS.map((tab, index) => {\n const selected = tab.id === activeTab\n const dirty = dirtyTabs.has(tab.id)\n return (\n <button\n key={tab.id}\n ref={(node) => { refs.current[index] = node }}\n type=\"button\"\n role=\"tab\"\n id={`dsh-channels-tab-${tab.id}`}\n aria-selected={selected}\n aria-controls={selected ? `dsh-channels-tabpanel-${tab.id}` : undefined}\n tabIndex={selected ? 0 : -1}\n className=\"dsh-channels-cfg__tab\"\n onClick={() => onChange(tab.id)}\n onKeyDown={event => onKeyDown(index, event)}\n >\n {tab.label}\n {(counts[tab.id] ?? 0) > 0 ? <span className=\"dsh-channels-cfg__tabCount\">{counts[tab.id]}</span> : null}\n {dirty\n ? (\n <span\n className=\"dsh-channels-cfg__tabDot\"\n aria-hidden=\"true\"\n />\n )\n : null}\n </button>\n )\n })}\n </nav>\n )\n}\n","/**\n * Feishu channel page styling. DSH semantic tokens (`--dsw-alias-*`) with neutral\n * fallbacks, aliased once to local `--dc-*` variables, so the page follows\n * the active theme.\n *\n * @module dsh-channels/client/styles\n */\n\nconst STYLE_ATTRIBUTE = 'data-dsh-channels-css'\n\nconst CSS = `\n.dsh-channels-page{width:100%;height:100%;min-width:0;min-height:0;overflow:auto;scrollbar-gutter:stable;background:var(--dsw-alias-bg-base,#fff)}\n.dsh-channels-page__content{box-sizing:border-box;max-width:816px;margin:0 auto;padding:18px clamp(24px,4vw,48px) 48px}\n[data-platform='darwin'] .dsh-channels-page__content{padding-top:calc(18px + var(--dsh-frame-top-clearance,0px))}\n.dsh-channels-cfg{--dc-fg:var(--dsw-alias-label-primary,#1f2328);--dc-fg2:var(--dsw-alias-label-secondary,#5c6169);--dc-fg3:var(--dsw-alias-label-tertiary,#8a8f98);--dc-dim:var(--dsw-alias-label-dimmed,#b0b4ba);--dc-bg:var(--dsw-alias-bg-base,#fff);--dc-line:var(--dsw-alias-border-l2,rgba(0,0,0,.12));--dc-hover:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05));--dc-accent:var(--dsw-alias-state-business-primary,#0a67d3);--dc-ok:var(--dsw-alias-state-success-primary,#0a7d33);--dc-warn:var(--dsw-alias-state-warn-primary,#a15c00);--dc-bad:var(--dsw-alias-state-error-primary,#c62828);--dc-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;container-type:inline-size;display:flex;flex-direction:column;gap:14px;width:100%;max-width:720px;color:var(--dc-fg);font-size:13px;line-height:20px}\n.dsh-channels-cfg__panelHead{position:sticky;top:0;z-index:2;display:flex;flex-direction:column;gap:4px;padding:10px 0 6px;background:var(--dc-bg)}\n.dsh-channels-cfg__headRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.dsh-channels-cfg__title{margin:0 2px 0 0;font-size:20px;font-weight:500;line-height:28px}\n.dsh-channels-cfg__meta{color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__spacer{flex:1}\n.dsh-channels-cfg__mono{font-family:var(--dc-mono);font-size:12px}\n.dsh-channels-cfg__pill{display:inline-flex;align-items:center;gap:5px;padding:0 8px;border-radius:999px;font-size:12px;line-height:20px;font-weight:400;white-space:nowrap;color:var(--dc-fg3);background:color-mix(in srgb,currentColor 10%,transparent)}\n.dsh-channels-cfg__pill--ok{color:var(--dc-ok)}\n.dsh-channels-cfg__pill--warn{color:var(--dc-warn)}\n.dsh-channels-cfg__pill--bad{color:var(--dc-bad)}\n.dsh-channels-cfg__dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}\n.dsh-channels-cfg__tabs{display:flex;align-items:center;gap:18px;border-bottom:1px solid var(--dc-line);overflow-x:auto;scrollbar-width:none}\n.dsh-channels-cfg__tab{display:inline-flex;align-items:center;gap:5px;min-height:34px;padding:0;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--dc-fg2);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}\n.dsh-channels-cfg__tab:hover{color:var(--dc-fg)}\n.dsh-channels-cfg__tab[aria-selected=\"true\"]{color:var(--dc-fg);border-bottom-color:currentColor;font-weight:600}\n.dsh-channels-cfg__tabCount{padding:0 5px;border-radius:8px;font-size:11px;line-height:16px;font-weight:500;color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 12%,transparent)}\n.dsh-channels-cfg__tabDot{width:6px;height:6px;border-radius:50%;background:var(--dc-accent);flex:none}\n.dsh-channels-cfg__tabBody{display:flex;flex-direction:column;gap:22px;padding:4px 0 8px}\n.dsh-channels-cfg__sec{display:flex;flex-direction:column;gap:6px;margin:0}\n.dsh-channels-cfg__secTitle{margin:0;font-size:12px;font-weight:600;line-height:18px;color:var(--dc-fg3);letter-spacing:.02em}\n.dsh-channels-cfg__secFoot{margin:0;color:var(--dc-fg3);font-size:12px;line-height:18px}\n.dsh-channels-cfg__list{display:flex;flex-direction:column;border:1px solid var(--dc-line);border-radius:10px}\n.dsh-channels-cfg__list>*+*{border-top:1px solid var(--dc-line)}\n.dsh-channels-cfg__list--warn{border-color:color-mix(in srgb,var(--dc-warn) 45%,transparent);background:color-mix(in srgb,var(--dc-warn) 7%,transparent)}\n.dsh-channels-cfg__row{display:flex;align-items:center;gap:16px;padding:11px 14px;min-height:28px}\n.dsh-channels-cfg__row--wide{flex-direction:column;align-items:stretch;gap:8px}\n.dsh-channels-cfg__rowText{display:flex;flex-direction:column;gap:1px;flex:1 1 auto;min-width:0}\n.dsh-channels-cfg__rowLabel{display:flex;align-items:center;gap:6px;flex-wrap:wrap;overflow-wrap:anywhere}\n.dsh-channels-cfg__rowSummary{color:var(--dc-fg3);font-size:12px;line-height:18px;overflow-wrap:anywhere}\n.dsh-channels-cfg__rowCtl{flex:none;display:flex;align-items:center;justify-content:flex-end;min-width:0;max-width:62%}\n.dsh-channels-cfg__row--wide .dsh-channels-cfg__rowCtl{max-width:none;justify-content:stretch}\n.dsh-channels-cfg__row--wide .dsh-channels-cfg__rowCtl>*{flex:1 1 auto}\n.dsh-channels-cfg__details{padding:0 14px 12px;color:var(--dc-fg2);font-size:12px;line-height:19px;overflow-wrap:anywhere}\n.dsh-channels-cfg__rowError{padding:0 14px 10px;color:var(--dc-bad);font-size:12px;line-height:18px}\n.dsh-channels-cfg__list>.dsh-channels-cfg__rowWrap{border-top-width:0}\n.dsh-channels-cfg__list>.dsh-channels-cfg__rowWrap+*{border-top-width:1px}\n.dsh-channels-cfg__tag{display:inline-flex;align-items:center;padding:0 5px;border-radius:4px;font-size:11px;line-height:16px;white-space:nowrap;color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 12%,transparent)}\n.dsh-channels-cfg__more{padding:0;border:0;background:none;color:var(--dc-fg3);font:inherit;font-size:12px;line-height:18px;cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px;white-space:nowrap}\n.dsh-channels-cfg__more:hover{color:var(--dc-fg2)}\n.dsh-channels-cfg__link{padding:0;border:0;background:none;color:var(--dc-accent);font:inherit;font-size:13px;cursor:pointer;text-decoration:none;white-space:nowrap}\n.dsh-channels-cfg__link:disabled{color:var(--dc-dim);cursor:default}\n.dsh-channels-cfg__switch{position:relative;flex:none;width:34px;height:20px;padding:0;border:0;border-radius:10px;background:var(--dc-dim);cursor:pointer;transition:background .15s}\n.dsh-channels-cfg__switch::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}\n.dsh-channels-cfg__switch[aria-checked=\"true\"]{background:var(--dc-accent)}\n.dsh-channels-cfg__switch[aria-checked=\"true\"]::after{transform:translateX(14px)}\n.dsh-channels-cfg__switch:disabled{opacity:.5;cursor:default}\n.dsh-channels-cfg__input{box-sizing:border-box;width:100%;min-width:0;height:30px;padding:0 10px;border:1px solid var(--dc-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,transparent);color:inherit;font:inherit;font-size:13px;outline:none}\n.dsh-channels-cfg__input:focus{border-color:var(--dc-accent)}\n.dsh-channels-cfg__input--invalid{border-color:var(--dc-bad)}\n.dsh-channels-cfg__input--num{width:120px;text-align:right;font-variant-numeric:tabular-nums}\n.dsh-channels-cfg__input--text{width:260px}\n.dsh-channels-cfg__select{width:220px;appearance:auto;cursor:pointer}\n.dsh-channels-cfg__unit{min-width:36px;color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__bytes{color:var(--dc-fg3);font-size:12px;white-space:nowrap}\n.dsh-channels-cfg__inputRow{display:flex;align-items:center;gap:8px;min-width:0}\n.dsh-channels-cfg__inputRow>.dsh-channels-cfg__input:not(.dsh-channels-cfg__input--num){flex:1 1 auto;width:auto}\n.dsh-channels-cfg__stringList{display:flex;flex-direction:column;gap:6px;min-width:0}\n.dsh-channels-cfg__stringListItem{display:flex;flex-direction:column;gap:2px;min-width:0}\n.dsh-channels-cfg__fieldError{color:var(--dc-bad);font-size:12px;line-height:18px}\n.dsh-channels-cfg__textBad{color:var(--dc-bad)}\n.dsh-channels-cfg__btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:28px;padding:0 12px;border:1px solid var(--dc-line);border-radius:7px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;text-decoration:none}\n.dsh-channels-cfg__btn:hover:not(:disabled){background:var(--dc-hover)}\n.dsh-channels-cfg__btn:disabled{color:var(--dc-dim);cursor:default}\n.dsh-channels-cfg__btn--primary{background:var(--dsw-alias-button-primary-fill,var(--dc-accent));border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff)}\n.dsh-channels-cfg__btn--primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,#0b5cba)}\n.dsh-channels-cfg__btn--primary:disabled{opacity:.45;color:var(--dsw-alias-label-primary-foreground,#fff)}\n.dsh-channels-cfg__btn--ghost{border-color:transparent;color:var(--dc-fg2)}\n.dsh-channels-cfg__btn--danger{color:var(--dc-bad)}\n.dsh-channels-cfg__btn--remove{color:var(--dc-bad);border-color:color-mix(in srgb,var(--dc-bad) 55%,transparent);background:color-mix(in srgb,var(--dc-bad) 8%,transparent)}\n.dsh-channels-cfg__btn--remove:hover:not(:disabled){background:color-mix(in srgb,var(--dc-bad) 15%,transparent)}\n.dsh-channels-cfg__btn--remove:disabled{opacity:.5;color:var(--dc-bad)}\n.dsh-channels-cfg__btn:focus-visible,.dsh-channels-cfg__tab:focus-visible,.dsh-channels-cfg__switch:focus-visible,.dsh-channels-cfg__link:focus-visible,.dsh-channels-cfg__more:focus-visible{outline:2px solid var(--dc-accent);outline-offset:2px}\n.dsh-channels-cfg__notice{padding:8px 12px;border-radius:8px;font-size:12px;line-height:18px;overflow-wrap:anywhere}\n.dsh-channels-cfg__notice--ok{color:var(--dc-ok);background:color-mix(in srgb,var(--dc-ok) 9%,transparent)}\n.dsh-channels-cfg__notice--error{color:var(--dc-bad);background:color-mix(in srgb,var(--dc-bad) 8%,transparent)}\n.dsh-channels-cfg__banner{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 12px;border-radius:8px;background:color-mix(in srgb,var(--dc-warn) 12%,transparent);color:var(--dc-fg);font-size:12px;line-height:18px}\n.dsh-channels-cfg__bannerText{flex:1;min-width:0}\n.dsh-channels-cfg__hero{display:flex;flex-direction:column;gap:12px;padding:14px 16px;border:1px solid var(--dc-line);border-radius:10px}\n.dsh-channels-cfg__heroTop{display:flex;align-items:center;gap:10px;flex-wrap:wrap}\n.dsh-channels-cfg__heroTop strong{font-size:14px;font-weight:600}\n.dsh-channels-cfg__stats{display:flex;gap:28px;flex-wrap:wrap}\n.dsh-channels-cfg__stat{display:flex;flex-direction:column}\n.dsh-channels-cfg__stat span{color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__stat strong{font-size:18px;font-weight:600;line-height:26px;font-variant-numeric:tabular-nums}\n.dsh-channels-cfg__stat strong.dsh-channels-cfg__mono{font-size:14px}\n.dsh-channels-cfg__checkIcon{display:grid;place-items:center;flex:none;width:18px;height:18px;border-radius:50%;font-size:11px;font-weight:700;align-self:flex-start;margin-top:1px}\n.dsh-channels-cfg__checkIcon--ok{color:var(--dc-ok);background:color-mix(in srgb,var(--dc-ok) 12%,transparent)}\n.dsh-channels-cfg__checkIcon--warn{color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 14%,transparent)}\n.dsh-channels-cfg__checkIcon--bad{color:var(--dc-bad);background:color-mix(in srgb,var(--dc-bad) 12%,transparent)}\n.dsh-channels-cfg__scopeList{margin:0;padding:0 14px 12px 46px;list-style:none;display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dc-fg2)}\n.dsh-channels-cfg__scopeList li{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}\n.dsh-channels-cfg__scopeList .dsh-channels-cfg__link{font-size:12px}\n.dsh-channels-cfg__list>.dsh-channels-cfg__scopeList{border-top-width:0}\n.dsh-channels-avatar{box-sizing:border-box;display:grid;place-items:center;flex:none;border-radius:50%;object-fit:cover;background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05));color:var(--dsw-alias-label-secondary,#5c6169);line-height:1}\n.dsh-channels-cfg__addRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 14px}\n.dsh-channels-cfg__addRow .dsh-channels-cfg__input{flex:1 1 160px;width:auto}\n.dsh-channels-cfg__emptyRow{padding:11px 14px;color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__saveBar{position:sticky;bottom:0;z-index:2;display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 0;border-top:1px solid var(--dc-line);background:var(--dc-bg)}\n.dsh-channels-cfg__saveMsg{flex:1;min-width:0;color:var(--dc-fg2);font-size:12px}\n.dsh-channels-cfg__steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}\n.dsh-channels-cfg__step{display:flex;align-items:flex-start;gap:10px}\n.dsh-channels-cfg__stepIndex{display:grid;place-items:center;flex:none;width:20px;height:20px;border-radius:50%;color:var(--dc-accent);background:color-mix(in srgb,var(--dc-accent) 12%,transparent);font-size:12px;font-variant-numeric:tabular-nums}\n.dsh-channels-cfg__stepBody{display:flex;flex-direction:column;gap:2px;min-width:0}\n.dsh-channels-cfg__stepTitle{font-weight:500}\n.dsh-channels-cfg__error{padding:10px 12px;border:1px solid var(--dc-bad);border-radius:10px;color:var(--dc-bad);font-size:12px;line-height:18px;display:flex;flex-direction:column;gap:8px}\n.dsh-channels-cfg__loading{padding:16px 0;color:var(--dc-fg3);font-size:12px}\n.dsh-channels-origin-chip{display:inline-flex;align-items:center;gap:4px;max-width:180px;height:24px;padding:0 8px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary,#5c6169);font:inherit;font-size:12px;line-height:24px;cursor:pointer;white-space:nowrap}\n.dsh-channels-origin-chip:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05));color:var(--dsw-alias-label-primary,#1f2328)}\n.dsh-channels-origin-chip>svg{flex:none}\n.dsh-channels-origin__who{display:inline-flex;align-items:center;gap:6px;vertical-align:middle}\n.dsh-channels-origin-chip__text{overflow:hidden;text-overflow:ellipsis}\n.dsh-channels-origin{box-sizing:border-box;height:100%;overflow:auto;padding:16px;color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;line-height:20px}\n.dsh-channels-origin__head{display:flex;align-items:center;gap:8px;margin-bottom:12px}\n.dsh-channels-origin__title{font-size:15px;font-weight:600;line-height:22px;overflow-wrap:anywhere}\n.dsh-channels-origin__list{display:flex;flex-direction:column;margin:0;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:10px}\n.dsh-channels-origin__row{display:flex;gap:12px;padding:9px 12px}\n.dsh-channels-origin__row+.dsh-channels-origin__row{border-top:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12))}\n.dsh-channels-origin__row dt{flex:none;width:64px;color:var(--dsw-alias-label-tertiary,#8a8f98)}\n.dsh-channels-origin__row dd{flex:1;min-width:0;margin:0;overflow-wrap:anywhere}\n.dsh-channels-origin__mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}\n.dsh-channels-origin__muted{color:var(--dsw-alias-label-tertiary,#8a8f98)}\n.dsh-channels-origin__error{display:flex;flex-direction:column;gap:6px;color:var(--dsw-alias-state-error-primary,#c62828);font-size:12px}\n.dsh-channels-origin__actions{display:flex;align-items:center;gap:14px;margin-top:14px}\n.dsh-channels-origin__button{display:inline-flex;align-items:center;height:28px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:7px;color:inherit;font-size:13px;text-decoration:none}\n.dsh-channels-origin__button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05))}\n.dsh-channels-origin__link{padding:0;border:0;background:none;color:var(--dsw-alias-state-business-primary,#0a67d3);font:inherit;font-size:13px;cursor:pointer}\n@media (prefers-reduced-motion:reduce){.dsh-channels-cfg__switch,.dsh-channels-cfg__switch::after{transition:none}}\n@container (max-width:560px){\n.dsh-channels-cfg__row:not(.dsh-channels-cfg__row--wide){flex-wrap:wrap}\n.dsh-channels-cfg__rowCtl{max-width:100%}\n.dsh-channels-cfg__select,.dsh-channels-cfg__input--text{width:100%}\n}\n`\n\n/** Append the panel stylesheet once (idempotent across HMR / remounts). */\nexport function installStyles(): () => void {\n const existing = document.querySelector(`style[${STYLE_ATTRIBUTE}]`)\n if (existing !== null) return () => {}\n const style = document.createElement('style')\n style.setAttribute(STYLE_ATTRIBUTE, '')\n style.textContent = CSS\n document.head.append(style)\n return () => {\n style.remove()\n }\n}\n","/**\n * dsh-channels client half: 左侧栏全局入口「飞书渠道」+ 主区整页设置面板,\n * 以及对话内的「飞书来源」(会话头部入口 + 右侧栏标签页)。\n *\n * 只依赖平台核心的 `slots` 服务与 react(bundle 里 react 走平台模块表,\n * 其余全部内联);右侧栏服务存在时才注册来源标签页。页面数据来自 Host\n * 半侧的 `/dsh-channels/*` 路由,因此不需要 typert 契约,也不 import 任何\n * DSH 客户端包。\n *\n * @module dsh-channels/client\n */\n\nimport { PANEL_COPY } from '../settings/contract.ts'\nimport { FeishuChannelsIcon } from './icon.tsx'\nimport { ORIGIN_TAB_ID, ORIGIN_TAB_KIND, ORIGIN_TAB_TITLE, OriginHeaderAction, OriginTab } from './origin.tsx'\nimport { FeishuChannelsPage } from './panel/index.tsx'\nimport { installStyles } from './styles.ts'\n\n// 平台 client runtime 的最小结构声明:只声明本 bundle 用到的能力。\n// 与安装的 DSH Web 版本(0.2.x)的 slots 运行时 API 一致:list 槽按 `id`\n// 注册,keyed 槽按 `key` 注册;`inject` 回调按会话作用域槽收到 sessionId。\nexport interface ClientSlots {\n inject(slot: string, contribution: () => unknown): void\n register(\n declaration:\n | { name: string; id: string; order?: number; label?: () => string; inject?: (sessionId: string) => object }\n | { name: string; key: string },\n component: (props: never) => unknown,\n ): () => void\n}\n\n/** Static right-sidebar tab type (`ctx.sidebarRightTabs.register`). */\nexport interface SidebarRightTabDefinition {\n id: string\n kind: string\n title: (address: string) => string\n}\n\n/** Right-sidebar services the origin tab needs. */\nexport interface SidebarRightScope extends ClientContext {\n sidebarRightTabs: { register(definition: SidebarRightTabDefinition): () => void }\n sidebarRight: { openTab(kind: string): void }\n}\n\nexport interface ClientContext {\n slots: ClientSlots\n effect?(callback: () => void | (() => void), label?: string): void\n /** Run `callback` in a child scope while every named service is available. */\n inject?(deps: string[], callback: (scope: SidebarRightScope) => void): void\n}\n\n/** Cordis client plugin name. */\nexport const name = 'dsh-channels'\n\n/** Required client services: the Slots registry is part of the web shell core. */\nexport const inject = ['slots']\n\n/**\n * Main panel id: the `sidebar.panellist` row id and the matching `main` slot key.\n * Must not change once released: DSH keys navigation state by it.\n */\nexport const PANEL_ID = 'feishu-channels'\n/** Left-sidebar row order (built-ins: tasks 10; Plugins is pinned last by the sidebar). */\nexport const PANEL_ORDER = 40\n/** Session header action order (after the preset label -10 and subagents 10). */\nexport const ORIGIN_ACTION_ORDER = 30\n\n/**\n * Mount the left-sidebar entry, its main-column page, the stylesheet, and —\n * while the right sidebar is present — the Feishu origin header entry and tab.\n * @param ctx - client plugin context carrying the slots registry.\n */\nexport function apply(ctx: ClientContext): void {\n if (typeof ctx.effect === 'function') {\n ctx.effect(() => installStyles(), 'dsh-channels: panel styles')\n } else {\n installStyles()\n }\n ctx.slots.inject('main', () => ctx.slots.register({\n name: 'main',\n key: PANEL_ID,\n }, FeishuChannelsPage))\n ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: PANEL_ORDER,\n label: () => PANEL_COPY.title,\n }, FeishuChannelsIcon))\n ctx.inject?.(['sidebarRightTabs', 'sidebarRight'], mountOrigin)\n}\n\n/**\n * Register the origin tab type, its body and title, and the header entry that\n * opens it. Everything lives as long as the right-sidebar services do.\n * @param scope - child context holding the right-sidebar services.\n */\nexport function mountOrigin(scope: SidebarRightScope): void {\n const definition: SidebarRightTabDefinition = {\n id: ORIGIN_TAB_ID,\n kind: ORIGIN_TAB_KIND,\n title: () => ORIGIN_TAB_TITLE,\n }\n scope.effect?.(() => scope.sidebarRightTabs.register(definition), 'dsh-channels: origin tab type')\n scope.slots.inject('sidebar.right.pane.tab', () => scope.slots.register({\n name: 'sidebar.right.pane.tab',\n key: ORIGIN_TAB_ID,\n }, OriginTab))\n scope.slots.inject('conversation.session.header.actions', () => scope.slots.register({\n name: 'conversation.session.header.actions',\n id: 'dsh-channels-origin',\n order: ORIGIN_ACTION_ORDER,\n inject: () => ({\n openOrigin: () => { scope.sidebarRight.openTab(ORIGIN_TAB_KIND) },\n }),\n }, OriginHeaderAction))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["messageOf","sourceLabel","requestDirectoryPicker"],"sources":["../src/settings/contract.ts","../src/client/icon.tsx","../src/client/avatar.tsx","../src/client/api.ts","../src/client/origin-cache.ts","../src/client/origin.tsx","../src/client/catalog.ts","../src/client/number-field.ts","../src/feishu/wiki/links.ts","../src/client/string-list.ts","../src/client/panel/shared.tsx","../src/client/panel/whitelist.tsx","../src/client/panel/access-tab.tsx","../src/client/panel/credentials-card.tsx","../src/client/panel/advanced-tab.tsx","../src/client/panel/connection-tab.tsx","../src/client/panel/fields-tab.tsx","../src/client/panel/profiles.ts","../src/client/panel/wiki-tab.tsx","../src/client/panel/index.tsx","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["/**\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/**\n * Settings panel routes: exact DSH `/api` Fetch routes named\n * `/api/dsh-channels.<method>`. Reads are GET; every write is POST, because\n * DSH exact `/api` routes accept only GET, HEAD, and POST.\n */\nexport const STATUS_ROUTE = '/api/dsh-channels.status'\nexport const PANEL_ROUTE = '/api/dsh-channels.panel'\n/** Persist a partial config edit (POST, body `{ values }`). */\nexport const SAVE_CONFIG_ROUTE = '/api/dsh-channels.saveConfig'\n/** Drop every Web override (POST, no body). */\nexport const CLEAR_CONFIG_ROUTE = '/api/dsh-channels.clearConfig'\n/** Store credential values (POST, body `SaveCredentialsRequest`). */\nexport const SAVE_CREDENTIALS_ROUTE = '/api/dsh-channels.saveCredentials'\n/** Remove one stored credential (POST, body `{ field }`). */\nexport const CLEAR_CREDENTIAL_ROUTE = '/api/dsh-channels.clearCredential'\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 = '/api/dsh-channels.reconnect'\n/** 模型/推理档位/预设选择器的宿主目录(provider → model → effort 级联刷新)。 */\nexport const CATALOG_ROUTE = '/api/dsh-channels.catalog'\n/** 访问与授权只读快照(GET):准入模式、管理员、群/用户/知识库白名单、待处理申请。 */\nexport const ACCESS_ROUTE = '/api/dsh-channels.access'\n/** 授权一个群(POST,body `{ chatId, name? }`)。 */\nexport const ADD_ACCESS_GROUP_ROUTE = '/api/dsh-channels.addAccessGroup'\n/** 撤销一个群(POST,body `{ chatId }`)。 */\nexport const REMOVE_ACCESS_GROUP_ROUTE = '/api/dsh-channels.removeAccessGroup'\n/** 用户白名单添加(POST,body `{ userId, name? }`)。 */\nexport const ADD_ACCESS_USER_ROUTE = '/api/dsh-channels.addAccessUser'\n/** 用户白名单移除(POST,body `{ userId }`)。 */\nexport const REMOVE_ACCESS_USER_ROUTE = '/api/dsh-channels.removeAccessUser'\n/** 知识库白名单添加(POST,body `{ userId, name? }`)。 */\nexport const ADD_WIKI_USER_ROUTE = '/api/dsh-channels.addWikiUser'\n/** 知识库白名单移除(POST,body `{ userId }`)。 */\nexport const REMOVE_WIKI_USER_ROUTE = '/api/dsh-channels.removeWikiUser'\n/** 结算一条待处理授权申请(POST,body `{ token, decision }`)。 */\nexport const RESOLVE_ACCESS_REQUEST_ROUTE = '/api/dsh-channels.resolveAccessRequest'\n/**\n * 打开宿主机的系统目录选择框(POST,无请求体),返回绝对路径。\n *\n * 只有宿主挂载了 `directoryPicker` 的 native 后端(桌面/本地 GUI 会话)才\n * 会注册实际选择逻辑;远程/无头会话的 browse 后端不可用,面板不下发\n * [`EditableFieldWire.browse`],保留手写输入。\n */\nexport const PICK_DIRECTORY_ROUTE = '/api/dsh-channels.pickDirectory'\n/** 会话的飞书来源(GET,`?sessionId=`):对话右侧栏「飞书来源」标签与会话头部入口使用。 */\nexport const SESSION_ORIGIN_ROUTE = '/api/dsh-channels.sessionOrigin'\n\n/** Where one Feishu-routed Session came from; names are resolved live and may be absent. */\nexport interface SessionOriginWire {\n sessionId: string\n /** `p2p` = 私聊,`group` = 群聊。 */\n chatType: 'p2p' | 'group'\n /** `thread` = 话题内会话,`chat` = 整个聊天一个会话(话题不可用时的降级)。 */\n kind: 'thread' | 'chat'\n chatId: string\n /** 群名(群聊且能读取群信息时)。 */\n chatName?: string\n /** 群头像 URL(群聊且能读取群信息时)。 */\n chatAvatarUrl?: string\n /** 发起人 user_id(日志平台分析话题为请求人)。 */\n senderUserId: string\n /** 发起人姓名(通讯录或群成员列表能查到时)。 */\n senderName?: string\n /** 发起人头像 URL(通讯录能查到时)。 */\n senderAvatarUrl?: string\n /** 日志平台分析话题:群内成员都可以继续。 */\n shared: boolean\n /** Epoch ms when the Session was bound to its Feishu route. */\n createdAt: number\n /** Whether the Feishu route still points at this Session (false after `/renew`, `/new` or route expiry). */\n bound: boolean\n /** Epoch ms of the route's last activity, while bound. */\n lastActivityAt?: number\n /** 在飞书客户端打开该聊天的链接。 */\n chatLink: string\n}\n\n/**\n * `GET PROFILES_ROUTE?users=<user_id,...>&chats=<chat_id,...>`: live Feishu\n * names and avatars for the users and chats the panel shows. At most\n * {@link PROFILES_MAX_IDS} ids per list.\n */\nexport const PROFILES_ROUTE = '/api/dsh-channels.profiles'\n\n/** Most `user_id`s, and separately most `chat_id`s, one profiles request may name. */\nexport const PROFILES_MAX_IDS = 200\n\n/** Display name and avatar of one Feishu user or chat. */\nexport interface FeishuProfileWire {\n name?: string\n /** `https:` image URL on the Feishu CDN. */\n avatarUrl?: string\n}\n\n/**\n * `GET PROFILES_ROUTE` response. Ids Feishu could not resolve, and ids that\n * are not valid Feishu ids, are absent; `warning` carries the Feishu reason\n * when a lookup failed (typically a missing scope).\n */\nexport interface FeishuProfilesWire {\n users: Record<string, FeishuProfileWire>\n chats: Record<string, FeishuProfileWire>\n warning?: string\n}\n\n/** `GET SESSION_ORIGIN_ROUTE` response: `origin` is null for a Session not started from Feishu. */\nexport interface SessionOriginResponse {\n origin: SessionOriginWire | null\n}\n\n/**\n * 可由 Web 面板覆盖的配置项。顶层键对应 `src/config.ts` 的字段;`feishuTools.*`\n * 是嵌套字段(覆盖层里仍写成 `{ \"feishuTools\": { ... } }` 对象)。覆盖值写进\n * `$DSH_HOME/dsh-channels/settings.json`,优先级高于 profile 的\n * `cordis.patch.yml`(见 README「Web 设置面板」)。\n *\n * 刻意不包含 `appId` / `appSecret`:它们是**凭据引用名**,面板改为直接\n * 编辑引用背后的值(`POST /api/dsh-channels.saveCredentials`,经 DSH 凭据服务写入),\n * 因此不会落进覆盖层文件。\n *\n * `baseDir`(工作区根目录 = 模型/工具产生文件的落点)可覆盖,但面板只接受\n * **绝对路径**(`~` 会展开);已有 Session / 目录不迁移,只影响之后新建的\n * 会话。校验与保存时建目录见 `src/workspaces.ts`。\n *\n * 数组顺序就是面板里同一分组内字段的显示顺序。\n */\nexport const EDITABLE_KEYS = [\n // 会话:触发\n 'dmMode',\n 'requireMention',\n 'threadFallback',\n // 会话:工作区\n 'baseDir',\n 'topicIsolation',\n // 会话:话题与卡片\n 'routeTtlMs',\n 'approvalTimeoutMs',\n 'questionTimeoutMs',\n 'refreshMs',\n 'statusRefreshMs',\n // 会话:飞书侧功能\n 'welcomeOnboarding',\n 'slashCommandPanel',\n 'allowPermissionSwitch',\n // 模型\n 'provider',\n 'model',\n 'reasoningEffort',\n 'maxTokens',\n 'agentPreset',\n // 附件(v2 管线)\n 'acceptAttachments',\n 'attachmentStore',\n 'prefetchImages',\n 'historyBootstrapLimit',\n // 模型工具(`feishuTools.*` 是嵌套配置,覆盖层仍写成嵌套对象)\n 'feishuTools.attachmentFetch',\n 'feishuTools.topicMessages',\n 'feishuTools.sendFile',\n // 限额与超时\n 'prefetchTimeoutMs',\n 'prefetchImageMaxBytes',\n 'prefetchTotalMaxBytes',\n 'feishuTools.attachmentFetchMaxResources',\n 'feishuTools.attachmentFetchMaxBytes',\n 'feishuTools.attachmentFetchTimeoutMs',\n 'feishuTools.topicMessagesTimeoutMs',\n 'promptContextTimeoutMs',\n // 准入与授权(白名单由「访问授权」tab 的列表管理)\n 'accessMode',\n 'dmAllowWhitelist',\n 'adminUserId',\n 'authRequestTtlDays',\n // 知识库(顶层 `wikiRoots` 字符串列表;为空时不挂载任何知识库工具)\n 'feishuTools.wikiRead',\n 'feishuTools.wikiWrite',\n 'wikiRoots',\n // 日志平台分析接口(访问令牌是凭据引用 analyzeToken,值写在 DSH 凭据里)\n 'analyzeEnabled',\n // 日志\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 'slashCommandPanel',\n 'refreshMs',\n 'statusRefreshMs',\n 'loggerLevel',\n] as const satisfies readonly EditableKey[]\n\nexport type RestartKey = (typeof RESTART_KEYS)[number]\n\n/**\n * Render kinds the generic panel form understands. `string-list` renders one\n * single-line input per entry with a remove button each and an add button\n * below; its value is a `readonly string[]`.\n */\nexport type FieldKind = 'boolean' | 'number' | 'text' | 'select' | 'string-list'\n\n/**\n * Per-entry format of a `string-list` field. Host and Client both map the\n * format onto the same entry parser, so an invalid entry is marked before\n * saving and the Host re-validates it on save.\n *\n * - `wiki-root-link`: a Feishu wiki space link or wiki node link (`wikiRoots`).\n */\nexport type StringListItemFormat = 'wiki-root-link'\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 =\n | 'trigger'\n | 'workspace'\n | 'cards'\n | 'features'\n | 'model'\n | 'attachments'\n | 'tools'\n | 'limits'\n | 'access'\n | 'wiki'\n | 'analyze'\n | 'logging'\n\n/** One selectable value rendered by the generic form. */\nexport interface FieldOptionWire {\n value: string\n label: string\n}\n\n/**\n * A two-value `select` rendered as a switch: `on` is the value stored while\n * the switch is on, `off` while it is off. Both are members of `options`.\n */\nexport interface FieldToggleWire {\n on: string\n off: 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 /** One-line description always shown under the label. */\n summary?: string\n /** Longer explanation, collapsed behind the row's「详情」link. */\n help?: string\n options?: readonly FieldOptionWire[]\n /** `select` only: render the two options as a switch. */\n toggle?: FieldToggleWire\n /**\n * `number` only: display unit next to the input. With `scale`, the input\n * shows `stored / scale` and the panel submits `round(input * scale)`;\n * `min` / `max` / `step` stay in stored units.\n */\n unit?: string\n /** `number` only: stored units per displayed unit (e.g. 1000 for 秒 over ms). */\n scale?: number\n /** `number` only: an empty input clears the override instead of failing. */\n optional?: boolean\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 /** Input placeholder; for `string-list` it applies to every entry input. */\n placeholder?: string\n /** `string-list` only: most non-empty entries the Host accepts. */\n maxItems?: number\n /** `string-list` only: format every non-empty entry must satisfy. */\n itemFormat?: StringListItemFormat\n /** Render the control below the label across the row width (paths, lists). */\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 /api/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/**\n * Values the panel may submit. Empty strings for optional text fields clear the\n * override. A `string-list` field submits its entries as typed (untrimmed,\n * empty inputs included) so Host issue indexes match the rendered inputs; the\n * Host trims entries and drops empty ones before persisting.\n */\nexport type ConfigValue = string | number | boolean | readonly string[]\nexport type EditableValues = Partial<Record<EditableKey, ConfigValue>>\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/**\n * The credential slots the panel may write: the Feishu application pair\n * (resolved on connect) and the analyze API token (resolved per request).\n */\nexport type CredentialField = 'appId' | 'appSecret' | 'analyzeToken'\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 scopeCheck?: ScopeCheckWire\n /** 连接后飞书原生斜杠指令面板的同步结果;未连接或同步尚未完成时缺省。 */\n slashCommandSync?: SlashCommandSyncWire\n}\n\n/** One plugin need whose scopes are all missing. */\nexport interface MissingScopeWire {\n /** Scope names, preferred first; granting any one satisfies the need. */\n scopes: string[]\n /** `required` breaks a core path; `optional` only degrades a feature. */\n level: 'required' | 'optional'\n /** What the plugin loses without it. */\n purpose: string\n /** Developer-console permission page filtered to the preferred scope. */\n consoleUrl: string\n}\n\n/** Post-connect scope self-check outcome. */\nexport type ScopeCheckWire =\n /** Every applicable need is granted. */\n | { state: 'ok'; checkedAt: number }\n /** Some needs are missing; `consoleUrl` opens the console filtered to all of them. */\n | { state: 'missing'; checkedAt: number; missing: MissingScopeWire[]; consoleUrl: string }\n /** The grant list could not be read; `error` carries the Feishu reason. */\n | { state: 'error'; checkedAt: number; error: string }\n\n/** Post-connect native slash-command panel sync outcome. */\nexport type SlashCommandSyncWire =\n /** The panel lists exactly the registry commands; counts are this sync's writes. */\n | { state: 'ok'; checkedAt: number; created: number; updated: number; removed: number; total: number }\n /** A Feishu call failed; `error` carries the Feishu reason. */\n | { state: 'error'; checkedAt: number; error: string }\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 credentials: {\n appId: CredentialRefWire\n appSecret: CredentialRefWire\n analyzeToken: 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 `user_id` (global across every authorized group). */\nexport interface AccessUserWire {\n userId: 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, `user_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 /api/dsh-channels.access` payload: admission mode, the administrator\n * reference, the group, user, and wiki whitelists, and the still-pending\n * authorization requests.\n */\nexport interface AccessWire {\n mode: 'open' | 'guarded'\n admin: {\n /** The effective administrator: configured `adminUserId`, else the app owner. */\n userId?: string\n /** Where `userId` came from: explicit config or the app-owner fallback. */\n source?: 'config' | 'owner'\n /** false while guarded mode would refuse every conversation (fail closed). */\n configured: boolean\n /** Why the app-owner fallback is unavailable, when its lookup failed. */\n ownerLookupError?: string\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 /** Wiki whitelist (`/auth wiki add`): topic owners allowed to use the wiki tools. */\n wikiUsers: AccessUserWire[]\n requests: AccessRequestWire[]\n}\n\n/** Write responses for every access mutation route. */\nexport type AccessMutationResponse =\n | { ok: true; access: AccessWire }\n | { ok: false; error: string }\n\n/** `GET /api/dsh-channels.panel` payload. */\nexport interface PanelWire {\n status: ChannelStatusWire\n config: PanelConfigWire\n /** 访问与授权卡片的数据(准入模式、管理员、白名单、待处理申请)。 */\n access: AccessWire\n}\n\n/**\n * `POST /api/dsh-channels.pickDirectory` 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/** `POST /api/dsh-channels.saveConfig` 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 * `string-list` only: zero-based position of the rejected entry in the\n * submitted array (empty inputs included). Absent for a whole-field issue.\n */\n index?: number\n}\n\n/** `POST /api/dsh-channels.saveConfig` response. */\nexport type SaveConfigResponse =\n | { ok: true; panel: PanelWire; clearedKeys: EditableKey[] }\n | { ok: false; error: string; issues?: FieldIssueWire[] }\n\n/** `POST /api/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/** `POST /api/dsh-channels.clearConfig` response. */\nexport type ClearConfigResponse =\n | { ok: true; panel: PanelWire }\n | { ok: false; error: string }\n\n/** `POST /api/dsh-channels.saveCredentials` body: only provided, non-empty values are written. */\nexport interface SaveCredentialsRequest {\n appId?: string\n appSecret?: string\n analyzeToken?: string\n}\n\n/** `POST /api/dsh-channels.clearCredential` 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, or only the analyze API token\n * changed (it applies to the next request), 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: 'attachments', label: '附件与工具' },\n { id: 'access', label: '访问授权' },\n { id: 'wiki', 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, in section order. */\nexport const TAB_FIELD_GROUPS: Readonly<Record<PanelTabId, readonly FieldGroup[]>> = {\n connection: [],\n sessions: ['trigger', 'workspace', 'cards', 'features'],\n model: ['model'],\n attachments: ['attachments', 'tools', 'limits'],\n access: ['access'],\n wiki: ['wiki'],\n advanced: ['analyze', 'logging'],\n}\n\n/** Section heading for one field group inside its tab. */\nexport const FIELD_GROUP_LABELS: Readonly<Record<FieldGroup, string>> = {\n trigger: '触发',\n workspace: '工作区',\n cards: '话题与卡片',\n features: '飞书侧功能',\n model: '模型',\n attachments: '入站附件',\n tools: '模型可用的飞书工具',\n limits: '限额与超时',\n access: '准入',\n wiki: '知识库工具',\n analyze: '日志分析接口',\n logging: '日志',\n}\n\n/** Groups rendered collapsed behind one summary row (rarely changed values). */\nexport const COLLAPSED_FIELD_GROUPS: readonly FieldGroup[] = ['limits']\n\n/** `unit` of byte-count fields; the panel adds a MiB reading next to them. */\nexport const BYTES_UNIT = '字节'\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 // ---- lifecycle / status -------------------------------------------------\n phaseUnconfigured: '待配置',\n phaseIdle: '未连接',\n phaseConnecting: '连接中',\n phaseConnected: '已连接',\n phaseError: '连接失败',\n statusSummaryConnected: '机器人在线,可以收发消息',\n statusSummaryUnconfigured: '还没有填写飞书应用凭据,插件不会连接飞书',\n statusSummaryIdle: '凭据已就绪,还没有建立连接',\n statusSummaryConnecting: '正在连接飞书…',\n statusSummaryError: '连接飞书失败,请检查凭据与网络后重试',\n uptimePrefix: '运行',\n lastError: '最近一次连接失败',\n checksTitle: '自检',\n scopeCheckOk: '插件需要的飞书权限都已开通',\n scopeCheckMissing: '{n} 项权限未开通',\n scopeCheckMissingRequired: '{n} 项必需权限未开通,相关功能无法使用',\n scopeCheckMissingOptional: '不影响收发消息,只有相关功能不可用。开通后需在开发者后台发布新版本。',\n scopeCheckPublish: '开通后需在开发者后台发布新版本。',\n scopeCheckOpenAll: '全部开通',\n scopeCheckOpenOne: '去开通',\n scopeCheckRequired: '必需',\n scopeCheckError: '无法读取权限开通情况',\n slashCommandSyncTitle: '斜杠指令面板',\n slashCommandSyncOk: '已同步 {total} 条命令,飞书客户端约 5 分钟后刷新',\n slashCommandSyncError: '同步失败,直接输入命令不受影响',\n missingConfig: '缺少配置',\n routeCount: '话题路由',\n liveAgentCount: '活动会话',\n inflightTurnCount: '在途回复',\n botOpenId: 'Bot open_id',\n healthReasons: '健康判定',\n unknownValue: '—',\n connect: '连接飞书',\n connecting: '连接中…',\n // ---- onboarding ---------------------------------------------------------\n onboardingTitle: '开始使用飞书渠道',\n onboardingStep1Title: '创建飞书自建应用',\n onboardingStep1Text: '在 open.feishu.cn/app 创建企业自建应用,开启机器人能力;事件订阅选「长连接」,订阅 im.message.receive_v1。',\n onboardingStep2Title: '填写应用凭据',\n onboardingStep2Text: '把 App ID(cli_ 开头)和 App Secret 填到下方「应用凭据」。',\n onboardingStep3Title: '保存后自动连接',\n onboardingStep3Text: '两项都保存后插件会自动连接,状态变成「已连接」就能在飞书里私聊机器人。',\n // ---- credentials --------------------------------------------------------\n credentialsTitle: '应用凭据',\n credentialsHint: '值只存在本机凭据库,面板不回显。',\n appIdLabel: 'App ID',\n appSecretLabel: 'App Secret',\n credentialStore: '凭据库',\n saveCredentials: '保存凭据',\n savingCredentials: '保存中…',\n credentialSaved: '凭据已保存',\n credentialSavedReconnected: '凭据已保存,飞书已连接',\n credentialSavedPending: '凭据已保存,重启飞书连接后生效',\n credentialSavedUnconfigured: '凭据已保存;还缺少另一项凭据,补齐后会自动连接',\n credentialAutoConnectFailed: '凭据已保存,但自动连接失败',\n credentialRemoved: '已移除存储的凭据',\n credentialRemovedPending: '已移除存储的凭据,重启飞书连接后生效',\n replaceCredential: '更换',\n setCredential: '设置',\n cancel: '取消',\n removeCredential: '移除',\n removingCredential: '移除中…',\n credentialConfigured: '已配置',\n credentialMissing: '未配置',\n credentialReadOnly: '当前凭据存储不可写',\n appIdPlaceholder: '输入 App ID(cli_…)',\n appSecretPlaceholder: '输入 App Secret',\n credentialRemoveConfirm: '确定移除已存储的凭据值?移除后不会回显,需要时须重新填写。',\n // ---- forms --------------------------------------------------------------\n save: '保存',\n saving: '保存中…',\n discard: '放弃',\n dirtyCount: '{n} 项未保存',\n dirtyRestart: ',其中 {n} 项需要重连后生效',\n details: '详情',\n expand: '展开',\n collapse: '收起',\n collapsedCount: '{n} 项',\n collapsedClean: '一般不用改',\n collapsedChanged: '{n} 项已在面板修改',\n restartTag: '需重连',\n listAdd: '添加',\n listRemove: '移除',\n listEntry: '第 {n} 项',\n listTooMany: '最多 {max} 项(空输入框不计)',\n listNotArray: '必须是字符串列表',\n listEntryNotString: '必须是字符串',\n browseDirectory: '选择目录…',\n browsingDirectory: '选择中…',\n directoryPicked: '已选择目录',\n directoryPickFailed: '无法打开系统目录选择器',\n saved: '已保存',\n saveInvalid: '请先修正标红的配置项',\n numberInvalid: '请输入数字',\n numberMin: '不得小于 {n}',\n numberMax: '不得大于 {n}',\n modelFootnote: '对之后新建的会话生效。',\n catalogFailed: '模型目录加载失败,已退回手填。',\n catalogLoading: '正在读取宿主机选项…',\n catalogUnavailable: '宿主机未提供可选项,请手填。',\n // ---- restart ------------------------------------------------------------\n restartBanner: '以下配置要重启飞书连接后生效:',\n restartWarning: '重启会断开当前飞书连接并中断进行中的回复,确定现在重启?',\n reconnect: '重启连接',\n reconnecting: '重连中…',\n refresh: '刷新',\n retry: '重试',\n pollFailed: '状态刷新失败',\n connectDone: '已连接飞书',\n reconnectDone: '已重启飞书连接',\n loading: '加载中…',\n loadFailed: '设置面板加载失败',\n // ---- advanced -----------------------------------------------------------\n analyzeTokenLabel: '访问令牌',\n analyzeTokenSummary: '请求头 Authorization: Bearer 的值,保存后立即生效;未配置时接口拒绝所有请求',\n analyzeTokenPlaceholder: '填写新令牌(建议 32 位以上随机串)',\n analyzeTokenSaved: '令牌已保存,下一次请求生效',\n analyzeTokenRemoved: '令牌已移除,接口将拒绝所有请求',\n overlayTitle: 'Web 覆盖',\n overlayPath: '覆盖文件',\n overlayCount: '{n} 项配置由面板覆盖',\n noOverrides: '当前没有面板覆盖的配置',\n overlayPathHelp: '面板保存的配置写在这个 JSON 文件里,优先级高于 profile。可以直接编辑;面板保存会整体重写它。',\n clear: '恢复 profile 配置',\n clearing: '恢复中…',\n clearConfirm: '确定删除所有面板覆盖,恢复为 profile 配置?',\n cleared: '已恢复为 profile 配置',\n // ---- access -------------------------------------------------------------\n debugModeWarning: '调试模式已打开:应用可用范围内的任何人都能和机器人对话。用完请在「访问授权」关闭。',\n accessAdminMissingWarning: '没有可用的管理员,所有对话都会被拒绝。请在下方填写管理员 user_id(私聊机器人发送 /userid 获取)。',\n accessAdminCurrent: '当前管理员',\n accessAdminSourceConfig: '已配置',\n accessAdminSourceOwner: '应用所有者',\n profilesWarning: '部分飞书名字和头像未能读取:{reason}',\n accessNotLoaded: '飞书连接建立后加载授权列表',\n accessRequestsTitle: '待处理申请',\n accessGroupsTitle: '已授权群',\n accessUsersTitle: '已授权用户',\n accessGroupsEmpty: '还没有授权任何群',\n accessUsersEmpty: '还没有授权任何用户',\n accessGroupChatIdPlaceholder: 'chat_id(oc_…,群里发 /groupid 获取)',\n accessGroupNamePlaceholder: '群名(可选)',\n accessUserIdPlaceholder: 'user_id(私聊机器人发 /userid 获取)',\n accessUserNamePlaceholder: '昵称(可选)',\n accessAddGroup: '添加群',\n accessAddUser: '添加用户',\n accessAdd: '添加',\n accessRemove: '移除',\n accessAllow: '授权',\n accessDeny: '忽略',\n accessWorking: '处理中…',\n accessSourceCommand: '对话命令',\n accessSourceCard: '授权卡',\n accessSourcePanel: '设置面板',\n accessRequestGroup: '申请授权群',\n accessRequestUser: '申请使用',\n accessRequestIn: '在「{group}」',\n accessRequestExpires: '{date} 前有效',\n accessGroupAdded: '已添加群授权',\n accessGroupRemoved: '已移除群授权',\n accessUserAdded: '已添加用户授权',\n accessUserRemoved: '已移除用户授权',\n accessRequestAllowed: '已授权',\n accessRequestDenied: '已忽略',\n // ---- wiki ---------------------------------------------------------------\n wikiUsersTitle: '可使用的人',\n wikiUsersAdminPlus: '管理员 + {n}',\n wikiUsersEmpty: '目前只有管理员发起的话题能用知识库工具',\n wikiUsersHint: '也可以在飞书发 /auth wiki add @用户',\n wikiUserAdded: '已加入知识库白名单',\n wikiUserRemoved: '已移出知识库白名单',\n} as const\n\n/**\n * Fill `{name}` placeholders of one `PANEL_COPY` template.\n *\n * @param template - Copy containing `{name}` placeholders.\n * @param values - Replacement per placeholder name.\n * @returns The filled string; unknown placeholders stay as written.\n */\nexport function fillCopy(template: string, values: Readonly<Record<string, string | number>>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) => {\n const value = values[name]\n return value === undefined ? match : String(value)\n })\n}\n","/**\n * 左侧栏「飞书渠道」行的装饰图标;行本身负责可访问名称(列表注册的 label)。\n *\n * @module dsh-channels/client/icon\n */\n\n/** Owner share of `sidebar.panellist`: requested square edge and selection state. */\nexport interface FeishuChannelsIconProps {\n size: number\n active: boolean\n}\n\n/**\n * Paper-plane glyph drawn in `currentColor`, so it follows the row's text color.\n * @param props - the sidebar's icon share.\n * @returns decorative icon.\n */\nexport function FeishuChannelsIcon({ size }: FeishuChannelsIconProps): JSX.Element {\n return (\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.6} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M21 3 3.5 10.2a.6.6 0 0 0 0 1.1l6.6 2.6 2.6 6.6a.6.6 0 0 0 1.1 0L21 3Z\" />\n <path d=\"m10.1 13.9 4.4-4.4\" />\n </svg>\n )\n}\n","/**\n * Round Feishu avatar: the user or chat image when Feishu returned one,\n * otherwise (and when the image fails to load) the first character of the\n * display text.\n *\n * @module dsh-channels/client/avatar\n */\n\nimport { useEffect, useState } from 'react'\n\n/**\n * One avatar badge. Decorative: the adjacent label names the user or chat.\n * @param props - `text` supplies the fallback initial, `url` the image, `size` the diameter in px (default 26).\n * @returns the badge.\n */\nexport function Avatar(props: { text: string; url?: string; size?: number }): JSX.Element {\n const { text, url, size = 26 } = props\n const [failed, setFailed] = useState(false)\n useEffect(() => { setFailed(false) }, [url])\n const style = { width: size, height: size, fontSize: Math.round(size * 0.46) }\n if (url !== undefined && !failed) {\n return (\n <img\n className=\"dsh-channels-avatar\"\n style={style}\n src={url}\n alt=\"\"\n aria-hidden=\"true\"\n referrerPolicy=\"no-referrer\"\n loading=\"lazy\"\n onError={() => setFailed(true)}\n />\n )\n }\n return <span className=\"dsh-channels-avatar\" style={style} aria-hidden=\"true\">{Array.from(text)[0] ?? '?'}</span>\n}\n","/**\n * Same-origin HTTP client for the settings panel. No typert contract: the\n * Host half registers exact DSH `/api` Fetch routes and this module fetches them.\n *\n * @module dsh-channels/client/api\n */\n\nimport {\n ACCESS_ROUTE,\n ADD_ACCESS_GROUP_ROUTE,\n ADD_ACCESS_USER_ROUTE,\n ADD_WIKI_USER_ROUTE,\n CATALOG_ROUTE,\n CLEAR_CONFIG_ROUTE,\n CLEAR_CREDENTIAL_ROUTE,\n PANEL_ROUTE,\n PICK_DIRECTORY_ROUTE,\n PROFILES_MAX_IDS,\n PROFILES_ROUTE,\n RECONNECT_ROUTE,\n REMOVE_ACCESS_GROUP_ROUTE,\n REMOVE_ACCESS_USER_ROUTE,\n REMOVE_WIKI_USER_ROUTE,\n RESOLVE_ACCESS_REQUEST_ROUTE,\n SAVE_CONFIG_ROUTE,\n SAVE_CREDENTIALS_ROUTE,\n SESSION_ORIGIN_ROUTE,\n STATUS_ROUTE,\n} from '../settings/contract.ts'\nimport type {\n AccessMutationResponse,\n AccessWire,\n CatalogWire,\n ChannelStatusWire,\n ClearConfigResponse,\n ClearCredentialResponse,\n CredentialField,\n EditableValues,\n FeishuProfilesWire,\n PanelWire,\n PickDirectoryResponse,\n ReconnectResponse,\n SaveConfigResponse,\n SaveCredentialsRequest,\n SaveCredentialsResponse,\n SessionOriginResponse,\n} from '../settings/contract.ts'\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Pull the JSON `error` field out of a failed response body. */\nfunction errorFromBody(status: number, text: string): string {\n try {\n const parsed = JSON.parse(text) as { error?: unknown }\n if (typeof parsed.error === 'string' && parsed.error !== '') return parsed.error\n } catch {\n // Not JSON — fall through to the HTTP status.\n }\n return `HTTP ${status}`\n}\n\n/**\n * Fetch one Host route. Routes are registered as absolute pathnames, but the\n * browser addresses them relative to the DSH document (`<base href=\"./\">`), so\n * a reverse proxy that mounts DSH under a prefix such as `/dsh/` still routes\n * them to DSH with the login cookie it scoped to that prefix.\n */\nasync function request(route: string, init?: RequestInit): Promise<Response> {\n try {\n return await fetch(route.slice(1), {\n cache: 'no-store',\n credentials: 'same-origin',\n ...init,\n headers: { accept: 'application/json', ...(init?.headers ?? {}) },\n })\n } catch (error) {\n throw new Error(`无法连接设置接口:${messageOf(error)}`)\n }\n}\n\nasync function readJson<T>(response: Response): Promise<T> {\n const text = await response.text()\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as T\n}\n\n/** Full panel payload (status + editable config + read-only facts). */\nexport async function fetchPanel(): Promise<PanelWire> {\n return readJson<PanelWire>(await request(PANEL_ROUTE))\n}\n\n/** Status-only payload polled while the page is open. */\nexport async function fetchStatus(): Promise<ChannelStatusWire> {\n return readJson<ChannelStatusWire>(await request(STATUS_ROUTE))\n}\n\n/** Feishu origin of one Session (`origin: null` when Feishu never routed it). */\nexport async function fetchSessionOrigin(sessionId: string): Promise<SessionOriginResponse> {\n const query = new URLSearchParams({ sessionId }).toString()\n return readJson<SessionOriginResponse>(await request(`${SESSION_ORIGIN_ROUTE}?${query}`))\n}\n\n/**\n * Live Feishu names and avatars of the given users and chats, split into\n * requests of at most {@link PROFILES_MAX_IDS} ids per list and merged.\n * @param userIds - user `user_id`s.\n * @param chatIds - chat `chat_id`s.\n * @returns the profiles Feishu resolved, by id, with every request's warning.\n */\nexport async function fetchFeishuProfiles(userIds: readonly string[], chatIds: readonly string[]): Promise<FeishuProfilesWire> {\n const requests: Array<Promise<FeishuProfilesWire>> = []\n const rounds = Math.max(1, Math.ceil(userIds.length / PROFILES_MAX_IDS), Math.ceil(chatIds.length / PROFILES_MAX_IDS))\n for (let round = 0; round < rounds; round += 1) {\n const window = (ids: readonly string[]): string => ids.slice(round * PROFILES_MAX_IDS, (round + 1) * PROFILES_MAX_IDS).join(',')\n const query = new URLSearchParams({ users: window(userIds), chats: window(chatIds) }).toString()\n requests.push(request(`${PROFILES_ROUTE}?${query}`).then(response => readJson<FeishuProfilesWire>(response)))\n }\n const merged: FeishuProfilesWire = { users: {}, chats: {} }\n const warnings = new Set<string>()\n for (const part of await Promise.all(requests)) {\n Object.assign(merged.users, part.users)\n Object.assign(merged.chats, part.chats)\n if (part.warning !== undefined) warnings.add(part.warning)\n }\n if (warnings.size > 0) merged.warning = [...warnings].join(';')\n return merged\n}\n\n/**\n * Model-selector catalog for one route: providers, the provider's models, the\n * model's reasoning efforts, and the preset roster. The client re-queries it\n * whenever the draft provider/model changes.\n */\nexport async function fetchCatalog(provider?: string, model?: string): Promise<CatalogWire> {\n const params = new URLSearchParams()\n if (provider !== undefined && provider !== '') params.set('provider', provider)\n if (model !== undefined && model !== '') params.set('model', model)\n const query = params.toString()\n return readJson<CatalogWire>(await request(query === '' ? CATALOG_ROUTE : `${CATALOG_ROUTE}?${query}`))\n}\n\n/** Persist a partial edit. Validation failures come back as `{ok:false}`. */\nexport async function saveConfig(values: EditableValues): Promise<SaveConfigResponse> {\n const response = await request(SAVE_CONFIG_ROUTE, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ values }),\n })\n const text = await response.text()\n if (response.status === 400) {\n // Validation/IO refusal: the body is already the SaveConfigResponse.\n try {\n return JSON.parse(text) as SaveConfigResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as SaveConfigResponse\n}\n\n/** Drop every Web override and return the refreshed panel. */\nexport async function clearConfig(): Promise<ClearConfigResponse> {\n const response = await request(CLEAR_CONFIG_ROUTE, { method: 'POST' })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as ClearConfigResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as ClearConfigResponse\n}\n\n/** Reopen the Feishu long connection (interrupts in-flight replies). */\nexport async function reconnect(): Promise<ReconnectResponse> {\n const response = await request(RECONNECT_ROUTE, { method: 'POST' })\n const text = await response.text()\n if (!response.ok) {\n try {\n return JSON.parse(text) as ReconnectResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n return JSON.parse(text) as ReconnectResponse\n}\n\n/**\n * Ask the Host to open its native directory chooser (the panel's「选择目录…」\n * button). The request stays open while the OS dialog does; a cancel comes back\n * as `{ ok: true, canceled: true }`, not as an error.\n */\nexport async function pickDirectory(): Promise<PickDirectoryResponse> {\n const response = await request(PICK_DIRECTORY_ROUTE, { method: 'POST' })\n const text = await response.text()\n if (!response.ok) {\n try {\n return JSON.parse(text) as PickDirectoryResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n return JSON.parse(text) as PickDirectoryResponse\n}\n\n/** Store provided credential values (the request/response never carry stored values). */\nexport async function saveCredentials(values: SaveCredentialsRequest): Promise<SaveCredentialsResponse> {\n const response = await request(SAVE_CREDENTIALS_ROUTE, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(values),\n })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as SaveCredentialsResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as SaveCredentialsResponse\n}\n\n/** Remove the stored value behind one credential reference. */\nexport async function clearCredential(field: CredentialField): Promise<ClearCredentialResponse> {\n const response = await request(CLEAR_CREDENTIAL_ROUTE, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ field }),\n })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as ClearCredentialResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as ClearCredentialResponse\n}\n\n/** Admission snapshot: mode, administrator, whitelists, pending requests. */\nexport async function fetchAccess(): Promise<AccessWire> {\n return readJson<AccessWire>(await request(ACCESS_ROUTE))\n}\n\n/** One access write: a 400 body already is the wire union; others throw. */\nasync function mutateAccess(path: string, body: unknown): Promise<AccessMutationResponse> {\n const response = await request(path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n const text = await response.text()\n if (response.status === 400) {\n try {\n return JSON.parse(text) as AccessMutationResponse\n } catch {\n return { ok: false, error: errorFromBody(response.status, text) }\n }\n }\n if (!response.ok) throw new Error(errorFromBody(response.status, text))\n return JSON.parse(text) as AccessMutationResponse\n}\n\n/** Authorize one group; resolves with the refreshed access snapshot. */\nexport async function addAccessGroup(chatId: string, name?: string): Promise<AccessMutationResponse> {\n return mutateAccess(\n ADD_ACCESS_GROUP_ROUTE,\n name === undefined || name === '' ? { chatId } : { chatId, name },\n )\n}\n\n/** Revoke one group authorization (takes effect on the next message). */\nexport async function removeAccessGroup(chatId: string): Promise<AccessMutationResponse> {\n return mutateAccess(REMOVE_ACCESS_GROUP_ROUTE, { chatId })\n}\n\n/** Authorize one user `user_id` (global across authorized groups). */\nexport async function addAccessUser(userId: string, name?: string): Promise<AccessMutationResponse> {\n return mutateAccess(\n ADD_ACCESS_USER_ROUTE,\n name === undefined || name === '' ? { userId } : { userId, name },\n )\n}\n\n/** Revoke one user authorization (takes effect on the next message). */\nexport async function removeAccessUser(userId: string): Promise<AccessMutationResponse> {\n return mutateAccess(REMOVE_ACCESS_USER_ROUTE, { userId })\n}\n\n/** Add one user `user_id` to the wiki whitelist. */\nexport async function addWikiUser(userId: string, name?: string): Promise<AccessMutationResponse> {\n return mutateAccess(\n ADD_WIKI_USER_ROUTE,\n name === undefined || name === '' ? { userId } : { userId, name },\n )\n}\n\n/** Remove one user from the wiki whitelist (takes effect on the next wiki tool call). */\nexport async function removeWikiUser(userId: string): Promise<AccessMutationResponse> {\n return mutateAccess(REMOVE_WIKI_USER_ROUTE, { userId })\n}\n\n/** Settle one pending authorization request (same resolver as the Feishu card). */\nexport async function resolveAccessRequest(\n token: string,\n decision: 'allow' | 'deny',\n): Promise<AccessMutationResponse> {\n return mutateAccess(RESOLVE_ACCESS_REQUEST_ROUTE, { token, decision })\n}\n","/**\n * 按会话缓存的飞书来源查询,会话头部入口与右侧栏标签页共用。\n *\n * @module dsh-channels/client/origin-cache\n */\n\nimport type { SessionOriginWire } from '../settings/contract.ts'\nimport { fetchSessionOrigin } from './api.ts'\n\n/**\n * How long a \"not from Feishu\" answer is reused. A Session shows up in the\n * Web list before its Feishu route is written, so an early null must expire.\n */\nconst NULL_ORIGIN_TTL_MS = 15_000\n\ninterface Lookup {\n pending: Promise<SessionOriginWire | null>\n /** Epoch ms when the Host answered null; absent while pending or for an origin. */\n nullAt?: number\n}\n\n/** One settled or pending origin lookup per Session; failures are not kept. */\nconst lookups = new Map<string, Lookup>()\n\n/**\n * Origin of one Session through the shared cache. An origin is kept for the\n * page's lifetime; a null answer only for {@link NULL_ORIGIN_TTL_MS}.\n * @param sessionId - DSH session id.\n * @param refresh - drop the cached answer and ask the Host again.\n * @returns the origin, or null for a Session Feishu never routed.\n */\nexport function loadSessionOrigin(sessionId: string, refresh = false): Promise<SessionOriginWire | null> {\n const cached = refresh ? undefined : lookups.get(sessionId)\n if (cached !== undefined && (cached.nullAt === undefined || Date.now() - cached.nullAt < NULL_ORIGIN_TTL_MS)) {\n return cached.pending\n }\n const pending = fetchSessionOrigin(sessionId).then(response => response.origin)\n const lookup: Lookup = { pending }\n lookups.set(sessionId, lookup)\n pending.then(\n (origin) => { if (origin === null) lookup.nullAt = Date.now() },\n () => { if (lookups.get(sessionId) === lookup) lookups.delete(sessionId) },\n )\n return pending\n}\n","/**\n * 对话里的「飞书来源」:会话头部的入口按钮(只在飞书发起的会话里出现)与\n * 右侧栏的来源标签页。两者共用一份按会话缓存的来源查询。\n *\n * @module dsh-channels/client/origin\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { SessionOriginWire } from '../settings/contract.ts'\nimport { Avatar } from './avatar.tsx'\nimport { loadSessionOrigin } from './origin-cache.ts'\nimport { FeishuChannelsIcon } from './icon.tsx'\n\n/** Right-sidebar tab kind opened by the header button. */\nexport const ORIGIN_TAB_KIND = 'feishu-origin'\n/** Tab-type implementation id and keyed body/title slot key. */\nexport const ORIGIN_TAB_ID = '@modesto_ma/dsh-channels/origin'\n/** Tab chip text. */\nexport const ORIGIN_TAB_TITLE = '飞书来源'\n\ntype OriginState =\n | { phase: 'loading' }\n | { phase: 'ready'; origin: SessionOriginWire | null }\n | { phase: 'error'; message: string }\n\n/**\n * Load one Session's origin; `reload` asks the Host again. `fresh` skips the\n * shared cache on the first load too, so names and avatars are current.\n */\nfunction useSessionOrigin(sessionId: string, fresh = false): [OriginState, () => void] {\n const [state, setState] = useState<OriginState>({ phase: 'loading' })\n // A reload is tied to the Session it was asked for, so switching Sessions\n // never shows the previous Session's origin while the new one loads.\n const [reloadOf, setReloadOf] = useState<{ sessionId: string; ticket: number }>({ sessionId, ticket: 0 })\n const ticket = reloadOf.sessionId === sessionId ? reloadOf.ticket : 0\n useEffect(() => {\n let live = true\n setState(current => (current.phase === 'ready' && ticket > 0 ? current : { phase: 'loading' }))\n loadSessionOrigin(sessionId, fresh || ticket > 0).then(\n origin => { if (live) setState({ phase: 'ready', origin }) },\n (error: unknown) => {\n if (live) setState({ phase: 'error', message: error instanceof Error ? error.message : String(error) })\n },\n )\n return () => { live = false }\n }, [fresh, sessionId, ticket])\n const reload = useCallback(() => {\n setReloadOf(current => ({ sessionId, ticket: current.sessionId === sessionId ? current.ticket + 1 : 1 }))\n }, [sessionId])\n return [state, reload]\n}\n\n/** Header chip text: group name, or 私聊 with the peer's name. */\nfunction sourceLabel(origin: SessionOriginWire): string {\n if (origin.chatType === 'p2p') return origin.senderName === undefined ? '飞书私聊' : `私聊 · ${origin.senderName}`\n return origin.chatName === undefined ? '飞书群聊' : origin.chatName\n}\n\n/** Tab heading: the group, or the private-chat peer, with its avatar. */\nfunction originHeading(origin: SessionOriginWire): { title: string; avatarUrl?: string } {\n if (origin.chatType === 'p2p') {\n return { title: origin.senderName ?? '飞书私聊', ...(origin.senderAvatarUrl === undefined ? {} : { avatarUrl: origin.senderAvatarUrl }) }\n }\n return { title: origin.chatName ?? '飞书群聊', ...(origin.chatAvatarUrl === undefined ? {} : { avatarUrl: origin.chatAvatarUrl }) }\n}\n\n/** Times a mounted header re-asks after a null answer, and the delay between asks. */\nconst HEADER_NULL_RETRIES = 3\nconst HEADER_NULL_RETRY_MS = 5_000\n\n/** Props of the header entry: the session standard kit's `sessionId`. */\nexport interface OriginHeaderActionProps {\n sessionId: string\n /** Open the origin tab in this Session's right sidebar. */\n openOrigin: () => void\n}\n\n/**\n * Header button shown only for Sessions Feishu routed; renders nothing for\n * every other Session and while the lookup is pending or failed.\n * @param props - session id and the tab opener.\n * @returns the button or null.\n */\nexport function OriginHeaderAction({ sessionId, openOrigin }: OriginHeaderActionProps): JSX.Element | null {\n const [state, reload] = useSessionOrigin(sessionId)\n const [retries, setRetries] = useState(0)\n useEffect(() => { setRetries(0) }, [sessionId])\n // A Session opened while Feishu is still creating it answers null until its\n // route is written; look again a few times while the header stays mounted.\n const waiting = state.phase === 'ready' && state.origin === null && retries < HEADER_NULL_RETRIES\n useEffect(() => {\n if (!waiting) return\n const timer = setTimeout(() => {\n setRetries(value => value + 1)\n reload()\n }, HEADER_NULL_RETRY_MS)\n return () => { clearTimeout(timer) }\n }, [waiting, reload])\n if (state.phase !== 'ready' || state.origin === null) return null\n const label = sourceLabel(state.origin)\n return (\n <button\n type=\"button\"\n className=\"dsh-channels-origin-chip\"\n title={`来自${label},点击查看飞书来源`}\n aria-label={`飞书来源:${label}`}\n onClick={openOrigin}\n >\n <FeishuChannelsIcon size={13} active={false} />\n <span className=\"dsh-channels-origin-chip__text\">{label}</span>\n </button>\n )\n}\n\n/** Props of the tab body: the session standard kit's `sessionId`. */\nexport interface OriginTabProps {\n sessionId: string\n}\n\nfunction formatTime(ms: number): string {\n const date = new Date(ms)\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\n}\n\n/**\n * Right-sidebar body: where this Session came from in Feishu.\n * @param props - the tab's Session.\n * @returns the origin page.\n */\nexport function OriginTab({ sessionId }: OriginTabProps): JSX.Element {\n const [state, reload] = useSessionOrigin(sessionId, true)\n return (\n <div className=\"dsh-channels-origin\">\n {state.phase === 'loading' ? <div className=\"dsh-channels-origin__muted\">正在读取飞书来源…</div> : null}\n {state.phase === 'error'\n ? (\n <div className=\"dsh-channels-origin__error\">\n <span>读取飞书来源失败:{state.message}</span>\n <button type=\"button\" className=\"dsh-channels-origin__link\" onClick={reload}>重试</button>\n </div>\n )\n : null}\n {state.phase === 'ready' && state.origin === null\n ? (\n <div className=\"dsh-channels-origin__muted\">\n 这个会话不是从飞书发起的。\n <button type=\"button\" className=\"dsh-channels-origin__link\" onClick={reload}>刷新</button>\n </div>\n )\n : null}\n {state.phase === 'ready' && state.origin !== null ? <OriginDetails origin={state.origin} onRefresh={reload} /> : null}\n </div>\n )\n}\n\nfunction OriginDetails({ origin, onRefresh }: { origin: SessionOriginWire; onRefresh: () => void }): JSX.Element {\n const p2p = origin.chatType === 'p2p'\n const rows: Array<[string, JSX.Element | string]> = [\n ['来源', p2p ? '私聊' : (origin.kind === 'thread' ? '群聊 · 话题' : '群聊')],\n ]\n // The heading already names a resolved group; an unnamed one shows its id.\n if (!p2p && origin.chatName === undefined) {\n rows.push(['群 ID', <span className=\"dsh-channels-origin__mono\">{origin.chatId}</span>])\n }\n rows.push([origin.shared ? '请求人' : '发起人', (\n <span className=\"dsh-channels-origin__who\">\n <Avatar text={origin.senderName ?? origin.senderUserId} url={origin.senderAvatarUrl} size={20} />\n {origin.senderName === undefined ? null : <span>{origin.senderName}</span>}\n <span className=\"dsh-channels-origin__mono dsh-channels-origin__muted\">{origin.senderUserId}</span>\n </span>\n )])\n const heading = originHeading(origin)\n if (origin.shared) rows.push(['说明', '日志平台分析话题,群内成员都可以继续'])\n rows.push(['开始于', formatTime(origin.createdAt)])\n rows.push(origin.bound && origin.lastActivityAt !== undefined\n ? ['最近活跃', formatTime(origin.lastActivityAt)]\n : ['状态', '飞书侧已切换到新会话或已过期'])\n return (\n <>\n <div className=\"dsh-channels-origin__head\">\n {heading.avatarUrl === undefined\n ? <FeishuChannelsIcon size={16} active={false} />\n : <Avatar text={heading.title} url={heading.avatarUrl} size={28} />}\n <span className=\"dsh-channels-origin__title\">{heading.title}</span>\n </div>\n <dl className=\"dsh-channels-origin__list\">\n {rows.map(([label, value]) => (\n <div key={label} className=\"dsh-channels-origin__row\">\n <dt>{label}</dt>\n <dd>{value}</dd>\n </div>\n ))}\n </dl>\n <div className=\"dsh-channels-origin__actions\">\n <a className=\"dsh-channels-origin__button\" href={origin.chatLink} target=\"_blank\" rel=\"noreferrer\">在飞书中打开</a>\n <button type=\"button\" className=\"dsh-channels-origin__link\" onClick={onRefresh}>刷新</button>\n </div>\n </>\n )\n}\n","/**\n * 浏览器半区的设置面板选择器目录折叠:把 Host 下发的字段描述与实时目录\n * (`/api/dsh-channels.catalog`)合成通用表单需要的 select 描述。\n *\n * 拆成独立模块(不含 JSX/React)以便单测;`panel/index.tsx` 只负责串起状态与渲染。\n *\n * @module dsh-channels/client/catalog\n */\n\nimport { PANEL_COPY } from '../settings/contract.ts'\nimport type {\n CatalogWire,\n EditableFieldWire,\n EditableKey,\n FieldOptionWire,\n SelectCatalogWire,\n} from '../settings/contract.ts'\n\n/** Keys whose options come from the Host selector catalog. */\nexport const CATALOG_KEYS = ['provider', 'model', 'reasoningEffort', 'agentPreset'] as const\nexport type CatalogKey = (typeof CATALOG_KEYS)[number]\n\nexport function isCatalogKey(key: EditableKey): key is CatalogKey {\n return (CATALOG_KEYS as readonly string[]).includes(key)\n}\n\n/** Read one selector group back out of the Host-resolved panel fields. */\nexport function selectorOf(field: EditableFieldWire | undefined): SelectCatalogWire {\n if (field?.kind !== 'select') return { selectable: false, options: [] }\n return {\n selectable: true,\n options: field.options === undefined ? [] : [...field.options],\n ...(field.disabled === true ? { disabled: true } : {}),\n ...(field.help === undefined ? {} : { help: field.help }),\n }\n}\n\n/** Seed the client catalog from the options the Host already resolved for the panel. */\nexport function catalogFromFields(fields: readonly EditableFieldWire[]): CatalogWire | null {\n const byKey = new Map(fields.map(field => [field.key, field]))\n const catalog: CatalogWire = {\n provider: selectorOf(byKey.get('provider')),\n model: selectorOf(byKey.get('model')),\n reasoningEffort: selectorOf(byKey.get('reasoningEffort')),\n agentPreset: selectorOf(byKey.get('agentPreset')),\n }\n return CATALOG_KEYS.every(key => !catalog[key].selectable) ? null : catalog\n}\n\n/** Current-value option kept when a saved value is missing from the live list. */\nfunction withCurrentOption(options: readonly FieldOptionWire[], value: string): 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 * Layer the live catalog over the Host panel fields. A group the Host cannot\n * enumerate falls back to the free-text control; the rest render as selects.\n *\n * `disabled` must be cleared explicitly: the Host's initial panel descriptor\n * carries `disabled: true` for dependent selectors (model without a provider,\n * effort without a model), and a fetched catalog that resolved the dependency\n * has to re-enable them. Forgetting this is exactly what made the model and\n * reasoning selects permanently unusable after choosing a provider/model.\n */\nexport function applyCatalogFields(\n fields: readonly EditableFieldWire[],\n catalog: CatalogWire | null,\n current: Partial<Record<CatalogKey, string | undefined>> = {},\n): EditableFieldWire[] {\n if (catalog === null) return [...fields]\n return fields.map((field) => {\n if (!isCatalogKey(field.key)) return field\n const selector = catalog[field.key]\n if (!selector.selectable) {\n return field.kind === 'select'\n ? { ...field, kind: 'text', options: undefined, help: PANEL_COPY.catalogUnavailable, disabled: undefined }\n : field\n }\n const value = current[field.key] ?? ''\n const options = value !== '' && !selector.options.some(option => option.value === value)\n ? withCurrentOption(selector.options, value)\n : [...selector.options]\n const next: EditableFieldWire = { ...field, kind: 'select', options }\n if (selector.disabled === true) next.disabled = true\n else delete next.disabled\n if (selector.help === undefined) delete next.help\n else next.help = selector.help\n return next\n })\n}","/**\n * 浏览器半区的 `number` 字段辅助:按 Host 描述的 `scale` 在显示单位与存储\n * 单位之间换算、保存前校验上下界,以及字节数的 MiB 读数。\n *\n * 不含 JSX/React,便于单测。Host 保存时按 config schema 复核。\n *\n * @module dsh-channels/client/number-field\n */\n\nimport { fillCopy, PANEL_COPY } from '../settings/contract.ts'\nimport type { ConfigValue, EditableFieldWire } from '../settings/contract.ts'\n\n/** Drop float noise from `stored / scale` (e.g. 0.30000000000000004). */\nfunction tidy(value: number): number {\n return Number(value.toPrecision(12))\n}\n\n/**\n * Value the input shows for one field.\n *\n * @param field - The Host descriptor (`kind: 'number'`).\n * @param value - Draft (the typed string) or effective value (stored units).\n * @returns The input text.\n */\nexport function numberInputValue(field: EditableFieldWire, value: ConfigValue | undefined): string {\n if (typeof value === 'string') return value\n if (typeof value !== 'number') return ''\n return String(field.scale === undefined ? value : tidy(value / field.scale))\n}\n\n/**\n * Input `step` in display units.\n *\n * @param field - The Host descriptor (`kind: 'number'`).\n * @returns The step, or `undefined` when the Host gives none.\n */\nexport function numberInputStep(field: EditableFieldWire): number | undefined {\n if (field.step === undefined) return undefined\n return field.scale === undefined ? field.step : tidy(field.step / field.scale)\n}\n\n/** One bound in display units with the field's unit appended. */\nfunction boundLabel(field: EditableFieldWire, stored: number): string {\n const shown = field.scale === undefined ? stored : tidy(stored / field.scale)\n return field.unit === undefined ? String(shown) : `${shown} ${field.unit}`\n}\n\n/**\n * Turn one number draft into its save payload, checking the Host bounds.\n *\n * @param field - The Host descriptor (`kind: 'number'`).\n * @param raw - The draft: the typed string, or a stored-unit number.\n * @returns The stored-unit value (`''` clears an optional field), or the reason it is rejected.\n */\nexport function numberDraftValue(\n field: EditableFieldWire,\n raw: ConfigValue | undefined,\n): { ok: true; value: number | '' } | { ok: false; message: string } {\n let stored: number\n if (typeof raw === 'number') {\n stored = raw\n } else {\n const text = typeof raw === 'string' ? raw.trim() : ''\n if (text === '') {\n return field.optional === true ? { ok: true, value: '' } : { ok: false, message: PANEL_COPY.numberInvalid }\n }\n const parsed = Number(text)\n if (!Number.isFinite(parsed)) return { ok: false, message: PANEL_COPY.numberInvalid }\n stored = field.scale === undefined ? parsed : Math.round(parsed * field.scale)\n }\n if (field.min !== undefined && stored < field.min) {\n return { ok: false, message: fillCopy(PANEL_COPY.numberMin, { n: boundLabel(field, field.min) }) }\n }\n if (field.max !== undefined && stored > field.max) {\n return { ok: false, message: fillCopy(PANEL_COPY.numberMax, { n: boundLabel(field, field.max) }) }\n }\n return { ok: true, value: stored }\n}\n\n/**\n * Human reading of a byte count, e.g. `20 MiB`.\n *\n * @param text - The input text (bytes).\n * @returns The reading, or `undefined` when the text is not a positive number.\n */\nexport function bytesReading(text: string): string | undefined {\n const bytes = Number(text)\n if (text.trim() === '' || !Number.isFinite(bytes) || bytes <= 0) return undefined\n const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']\n let value = bytes\n let unit = 0\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit += 1\n }\n return `${Number(value.toFixed(value >= 100 || unit === 0 ? 0 : 1))} ${units[unit]}`\n}\n","/**\n * Feishu wiki (知识库) link parsing: the `wikiRoots` allow-list entries and\n * the document references the model passes to the wiki tools.\n *\n * Pure module: no I/O. Hosts are not checked because tenants use their own\n * subdomains (`<tenant>.feishu.cn`) and the API never uses the link host.\n *\n * @module dsh-channels/feishu/wiki/links\n */\n\n/** Feishu wiki space id: decimal digits. */\nconst SPACE_ID_PATTERN = /^\\d{1,32}$/u\n\n/** Feishu wiki node / docx token: letters and digits. */\nconst TOKEN_PATTERN = /^[A-Za-z0-9]{8,64}$/u\n\n/** One configured `wikiRoots` entry. */\nexport type WikiRoot =\n | {\n /** A whole wiki space (`/wiki/space/<space_id>`). */\n readonly kind: 'space'\n readonly spaceId: string\n /** The link as configured. */\n readonly link: string\n }\n | {\n /** One wiki node and every node below it (`/wiki/<node_token>`). */\n readonly kind: 'node'\n readonly nodeToken: string\n /** The link as configured. */\n readonly link: string\n }\n\n/** Why a `wikiRoots` entry was rejected. */\nexport type WikiRootLinkError =\n | 'not-a-url'\n | 'not-https'\n | 'not-a-wiki-link'\n\n/** Readable reasons for {@link WikiRootLinkError}. */\nexport const WIKI_ROOT_LINK_ERRORS: Readonly<Record<WikiRootLinkError, string>> = {\n 'not-a-url': '不是有效的链接',\n 'not-https': '必须是 https 链接',\n 'not-a-wiki-link': '只支持知识空间链接 https://<租户>.feishu.cn/wiki/space/<space_id> 或节点链接 https://<租户>.feishu.cn/wiki/<node_token>',\n}\n\n/**\n * Parse one `wikiRoots` entry.\n *\n * @param link Link as configured (surrounding whitespace is ignored).\n * @returns The root, or the reason it is invalid.\n */\nexport function parseWikiRootLink(link: string): { ok: true; root: WikiRoot } | { ok: false; error: WikiRootLinkError } {\n const trimmed = link.trim()\n let url: URL\n try {\n url = new URL(trimmed)\n } catch {\n // Unparseable input: reported as a configuration error below.\n return { ok: false, error: 'not-a-url' }\n }\n if (url.protocol !== 'https:') return { ok: false, error: 'not-https' }\n const segments = url.pathname.split('/').filter(segment => segment !== '')\n if (segments[0] !== 'wiki') return { ok: false, error: 'not-a-wiki-link' }\n if (segments.length === 3 && segments[1] === 'space' && SPACE_ID_PATTERN.test(segments[2]!)) {\n return { ok: true, root: { kind: 'space', spaceId: segments[2]!, link: trimmed } }\n }\n if (segments.length === 2 && segments[1] !== 'space' && TOKEN_PATTERN.test(segments[1]!)) {\n return { ok: true, root: { kind: 'node', nodeToken: segments[1]!, link: trimmed } }\n }\n return { ok: false, error: 'not-a-wiki-link' }\n}\n\n/**\n * Parse every configured root. Callers validate the config first, so an\n * invalid entry here is a programming error.\n *\n * @param links Validated `wikiRoots` entries.\n * @returns The parsed roots, in configuration order.\n */\nexport function parseWikiRoots(links: readonly string[]): WikiRoot[] {\n return links.map((link) => {\n const parsed = parseWikiRootLink(link)\n if (!parsed.ok) throw new Error(`dsh-channels: invalid wikiRoots entry ${link}: ${WIKI_ROOT_LINK_ERRORS[parsed.error]}`)\n return parsed.root\n })\n}\n\n/** A document or node the model names: a wiki node, a docx, or a whole space. */\nexport type WikiRef =\n | { readonly kind: 'node'; readonly token: string }\n | { readonly kind: 'docx'; readonly token: string }\n | { readonly kind: 'space'; readonly spaceId: string }\n\n/**\n * Parse a model-supplied reference: a wiki node link, a docx link, a space\n * link, or a bare node token.\n *\n * @param input Raw tool argument.\n * @returns The reference, or `undefined` when it names nothing recognizable.\n */\nexport function parseWikiRef(input: string): WikiRef | undefined {\n const trimmed = input.trim()\n if (TOKEN_PATTERN.test(trimmed)) return { kind: 'node', token: trimmed }\n let url: URL\n try {\n url = new URL(trimmed)\n } catch {\n // Neither a token nor a URL.\n return undefined\n }\n if (url.protocol !== 'https:') return undefined\n const segments = url.pathname.split('/').filter(segment => segment !== '')\n if (segments[0] === 'wiki' && segments.length === 3 && segments[1] === 'space' && SPACE_ID_PATTERN.test(segments[2]!)) {\n return { kind: 'space', spaceId: segments[2]! }\n }\n if (segments.length === 2 && TOKEN_PATTERN.test(segments[1]!)) {\n if (segments[0] === 'wiki' && segments[1] !== 'space') return { kind: 'node', token: segments[1]! }\n if (segments[0] === 'docx') return { kind: 'docx', token: segments[1]! }\n }\n return undefined\n}\n","/**\n * 浏览器半区的 `string-list` 字段辅助:把草稿值读成条目数组,并在保存前\n * 逐项校验,供面板即时标红。Host 保存时按同一规则复核(见\n * `settings/surface.ts` 的 `normalizeStringList`)。\n *\n * 不含 JSX/React,便于单测。条目格式校验直接复用纯函数模块\n * `feishu/wiki/links.ts`(无运行时依赖,可内联进 Client bundle)。\n *\n * @module dsh-channels/client/string-list\n */\n\nimport { parseWikiRootLink, WIKI_ROOT_LINK_ERRORS } from '../feishu/wiki/links.ts'\nimport { PANEL_COPY } from '../settings/contract.ts'\nimport type {\n ConfigValue,\n EditableFieldWire,\n FieldIssueWire,\n StringListItemFormat,\n} from '../settings/contract.ts'\n\n/** Entry checks per item format: the readable reason, or `undefined` when valid. */\nconst ITEM_FORMAT_CHECKS: Readonly<Record<StringListItemFormat, (entry: string) => string | undefined>> = {\n 'wiki-root-link': (entry) => {\n const parsed = parseWikiRootLink(entry)\n return parsed.ok ? undefined : WIKI_ROOT_LINK_ERRORS[parsed.error]\n },\n}\n\n/**\n * Entries of a `string-list` value; any other value reads as no entries.\n *\n * @param value - Draft or effective value of a `string-list` field.\n * @returns The entries as stored (untrimmed).\n */\nexport function listItems(value: ConfigValue | undefined): readonly string[] {\n return typeof value === 'object' ? value : []\n}\n\n/**\n * Validate the entries of one `string-list` draft the way the Host will:\n * empty inputs are ignored, every other entry is trimmed and checked against\n * the field's `itemFormat`, and the non-empty count is bounded by `maxItems`.\n *\n * @param field - The Host descriptor (`kind: 'string-list'`).\n * @param items - The draft entries, empty inputs included.\n * @returns Per-entry issues (with `index` into `items`) and a whole-field issue when over `maxItems`.\n */\nexport function stringListIssues(field: EditableFieldWire, items: readonly string[]): FieldIssueWire[] {\n const issues: FieldIssueWire[] = []\n const check = field.itemFormat === undefined ? undefined : ITEM_FORMAT_CHECKS[field.itemFormat]\n let count = 0\n items.forEach((item, index) => {\n const entry = item.trim()\n if (entry === '') return\n count += 1\n const reason = check?.(entry)\n if (reason === undefined) return\n const position = PANEL_COPY.listEntry.replace('{n}', String(index + 1))\n issues.push({ key: field.key, index, message: `${position}「${entry}」:${reason}` })\n })\n if (field.maxItems !== undefined && count > field.maxItems) {\n issues.push({ key: field.key, message: PANEL_COPY.listTooMany.replace('{max}', String(field.maxItems)) })\n }\n return issues\n}\n\n/**\n * Turn one `string-list` draft into its save payload. Entries are submitted as\n * typed (untrimmed, empty inputs included) so Host issue indexes point at the\n * same rendered inputs; the Host trims and drops empty inputs.\n *\n * @param field - The Host descriptor (`kind: 'string-list'`).\n * @param raw - The draft value.\n * @returns The payload entries, or the issues that block the save.\n */\nexport function stringListDraftValue(\n field: EditableFieldWire,\n raw: ConfigValue | undefined,\n): { ok: true; value: string[] } | { ok: false; issues: FieldIssueWire[] } {\n const items = listItems(raw)\n const issues = stringListIssues(field, items)\n return issues.length > 0 ? { ok: false, issues } : { ok: true, value: [...items] }\n}\n\n/**\n * Inputs the list control renders: one per entry, or one blank input for an\n * empty list so the first entry can be typed directly.\n *\n * @param value - Draft or effective value.\n * @returns The input values, in order.\n */\nexport function listRows(value: ConfigValue | undefined): readonly string[] {\n const items = listItems(value)\n return items.length === 0 ? [''] : items\n}\n\n/**\n * Replace the entry at one input position.\n *\n * @param rows - Rendered inputs ({@link listRows}).\n * @param index - Position of the edited input.\n * @param entry - The typed value.\n * @returns The next draft entries.\n */\nexport function withListEntry(rows: readonly string[], index: number, entry: string): string[] {\n return rows.map((item, position) => (position === index ? entry : item))\n}\n\n/**\n * Drop the entry at one input position.\n *\n * @param rows - Rendered inputs ({@link listRows}).\n * @param index - Position of the removed input.\n * @returns The next draft entries.\n */\nexport function withoutListEntry(rows: readonly string[], index: number): string[] {\n return rows.filter((_, position) => position !== index)\n}\n\n/**\n * Append one blank input.\n *\n * @param rows - Rendered inputs ({@link listRows}).\n * @returns The next draft entries.\n */\nexport function withBlankListEntry(rows: readonly string[]): string[] {\n return [...rows, '']\n}\n\n/**\n * Whether the add button is disabled: the inputs already reach `maxItems`.\n *\n * @param field - The Host descriptor.\n * @param rows - Rendered inputs ({@link listRows}).\n * @returns `true` when no further input may be added.\n */\nexport function listIsFull(field: EditableFieldWire, rows: readonly string[]): boolean {\n return field.maxItems !== undefined && rows.length >= field.maxItems\n}\n","/**\n * 设置面板共享层:页面状态类型、Host 字段渲染原语(SettingRow /\n * FieldSections / SaveBar)、分区与列表原语、生命周期徽标与纯格式化工具。\n *\n * 这一层不发请求、不持有页面状态,只把 Host 下发的 wire 数据渲染成控件;\n * 页面外壳在 `index.tsx`,各 tab 只组合这些原语。\n *\n * @module dsh-channels/client/panel/shared\n */\n\nimport { useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n BYTES_UNIT,\n COLLAPSED_FIELD_GROUPS,\n FIELD_GROUP_LABELS,\n fillCopy,\n PANEL_COPY,\n} from '../../settings/contract.ts'\nimport type {\n ChannelPhaseWire,\n ConfigValue,\n CredentialField,\n EditableFieldWire,\n EditableKey,\n EditableValues,\n FieldGroup,\n FieldIssueWire,\n PanelWire,\n} from '../../settings/contract.ts'\nimport { bytesReading, numberDraftValue, numberInputStep, numberInputValue } from '../number-field.ts'\nimport {\n listIsFull,\n listItems,\n listRows,\n stringListDraftValue,\n withBlankListEntry,\n withListEntry,\n withoutListEntry,\n} from '../string-list.ts'\n\n/** Initial panel load: one request, retried from the error card. */\nexport type LoadState =\n | { phase: 'loading' }\n | { phase: 'error'; message: string }\n | { phase: 'ready'; panel: PanelWire }\n\n/** One transient banner above the tab body (save/credentials/reconnect/catalog). */\nexport interface Notice {\n kind: 'ok' | 'error'\n text: string\n /** Per-field validation failures rendered inline by SettingRow. */\n issues?: FieldIssueWire[]\n}\n\n/** One in-flight panel mutation (only one action runs at a time). */\nexport type BusyAction =\n | 'save'\n | 'clear'\n | 'reconnect'\n | 'credentials'\n | 'credentials-clear'\n | 'access'\n\n/** Tone of a small pill; maps onto `dsh-channels-cfg__pill--*` classes. */\nexport type BadgeTone = 'ok' | 'warn' | 'bad' | 'muted'\n\nexport function messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Compact duration for the page header. */\nexport function formatUptime(ms: number | undefined): string {\n if (ms === undefined || !Number.isFinite(ms)) return PANEL_COPY.unknownValue\n const totalSeconds = Math.max(0, Math.floor(ms / 1_000))\n const days = Math.floor(totalSeconds / 86_400)\n const hours = Math.floor((totalSeconds % 86_400) / 3_600)\n const minutes = Math.floor((totalSeconds % 3_600) / 60)\n if (days > 0) return `${days} 天 ${hours} 小时`\n if (hours > 0) return `${hours} 小时 ${minutes} 分`\n if (minutes > 0) return `${minutes} 分 ${totalSeconds % 60} 秒`\n return `${totalSeconds} 秒`\n}\n\n/** Effective value for one key (falling back to the profile base). */\nexport function effectiveValue(panel: PanelWire, key: EditableKey): ConfigValue | undefined {\n return panel.config.effective[key] ?? panel.config.base[key]\n}\n\n/** Host label for one editable key; the raw key is the technical fallback. */\nexport function fieldLabel(fields: readonly EditableFieldWire[], key: EditableKey): string {\n return fields.find(field => field.key === key)?.label ?? key\n}\n\n/**\n * Turn the dirty draft into the wire payload, keeping the Host-descriptor\n * bounds check client-side (the Host re-validates every value on save).\n */\nexport function validateDraft(\n panel: PanelWire,\n draft: EditableValues,\n): { values: EditableValues; issues: FieldIssueWire[] } {\n const values: EditableValues = {}\n const issues: FieldIssueWire[] = []\n for (const field of panel.config.fields) {\n if (!(field.key in draft)) continue\n const raw = draft[field.key]\n if (field.kind === 'number') {\n const parsed = numberDraftValue(field, raw)\n if (parsed.ok) values[field.key] = parsed.value\n else issues.push({ key: field.key, message: parsed.message })\n continue\n }\n if (field.kind === 'boolean') {\n values[field.key] = raw === true\n continue\n }\n if (field.kind === 'string-list') {\n const list = stringListDraftValue(field, raw)\n if (list.ok) values[field.key] = list.value\n else issues.push(...list.issues)\n continue\n }\n values[field.key] = String(raw ?? '')\n }\n return { values, issues }\n}\n\n/** Header pill for the Host-derived lifecycle phase. */\nexport function phaseBadge(phase: ChannelPhaseWire): { text: string; tone: BadgeTone } {\n switch (phase) {\n case 'connected': return { text: PANEL_COPY.phaseConnected, tone: 'ok' }\n case 'connecting': return { text: PANEL_COPY.phaseConnecting, tone: 'warn' }\n case 'error': return { text: PANEL_COPY.phaseError, tone: 'bad' }\n case 'idle': return { text: PANEL_COPY.phaseIdle, tone: 'muted' }\n default: return { text: PANEL_COPY.phaseUnconfigured, tone: 'muted' }\n }\n}\n\n/** One-line explanation of the current phase (connection tab status block). */\nexport function phaseSummary(phase: ChannelPhaseWire): string {\n switch (phase) {\n case 'connected': return PANEL_COPY.statusSummaryConnected\n case 'connecting': return PANEL_COPY.statusSummaryConnecting\n case 'error': return PANEL_COPY.statusSummaryError\n case 'idle': return PANEL_COPY.statusSummaryIdle\n default: return PANEL_COPY.statusSummaryUnconfigured\n }\n}\n\n/** Credential editor data owned by the page shell, rendered by the connection tab. */\nexport interface CredentialsView {\n appId: string\n appSecret: string\n issues: FieldIssueWire[]\n busy: BusyAction | null\n /** Host-side lifecycle busy (connecting/restarting): block writes. */\n locked: boolean\n onAppIdChange: (value: string) => void\n onAppSecretChange: (value: string) => void\n onSave: () => void\n onClear: (field: CredentialField) => void\n}\n\n/** Analyze API token editor data owned by the page shell, rendered by the advanced tab. */\nexport interface AnalyzeTokenView {\n value: string\n issues: FieldIssueWire[]\n busy: BusyAction | null\n /** Host-side lifecycle busy (connecting/restarting): block writes. */\n locked: boolean\n onChange: (value: string) => void\n onSave: () => void\n onClear: () => void\n}\n\n/** Everything a field-bearing tab needs from the page shell's single draft. */\nexport interface FormView {\n panel: PanelWire\n /** Host descriptors after the live selector catalog has been layered on. */\n fields: readonly EditableFieldWire[]\n draft: EditableValues\n issues: FieldIssueWire[]\n busy: BusyAction | null\n /** Host-side lifecycle busy (connecting/restarting): block writes. */\n locked: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n /**\n * Open the Host's native directory chooser and write the picked absolute\n * path into the draft (not saved until「保存」). Absent when the Host\n * cannot offer a native chooser.\n */\n onPickDirectory?: (key: EditableKey) => Promise<void>\n}\n\n/** Small rounded status pill. */\nexport function Pill(props: { tone: BadgeTone; dot?: boolean; children: ReactNode }): JSX.Element {\n return (\n <span className={`dsh-channels-cfg__pill dsh-channels-cfg__pill--${props.tone}`}>\n {props.dot === true ? <span className=\"dsh-channels-cfg__dot\" /> : null}\n {props.children}\n </span>\n )\n}\n\n/** One titled section: a muted heading above a bordered row list. */\nexport function Section(props: {\n title: ReactNode\n footnote?: ReactNode\n tone?: 'warn'\n children: ReactNode\n}): JSX.Element {\n const { title, footnote, tone, children } = props\n return (\n <section className=\"dsh-channels-cfg__sec\">\n <h3 className=\"dsh-channels-cfg__secTitle\">{title}</h3>\n <div className={`dsh-channels-cfg__list${tone === 'warn' ? ' dsh-channels-cfg__list--warn' : ''}`}>\n {children}\n </div>\n {footnote === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{footnote}</p>}\n </section>\n )\n}\n\n/**\n * One setting row: label (with tags and the「详情」toggle) and a one-line\n * summary on the left, the control on the right. `details` renders below the\n * row once expanded; `wide` puts the control under the text.\n */\nexport function Row(props: {\n label: ReactNode\n tags?: ReactNode\n summary?: ReactNode\n details?: ReactNode\n control?: ReactNode\n error?: ReactNode\n wide?: boolean\n leading?: ReactNode\n}): JSX.Element {\n const { label, tags, summary, details, control, error, wide = false, leading } = props\n const [open, setOpen] = useState(false)\n return (\n <div className=\"dsh-channels-cfg__rowWrap\">\n <div className={`dsh-channels-cfg__row${wide ? ' dsh-channels-cfg__row--wide' : ''}`}>\n {leading}\n <div className=\"dsh-channels-cfg__rowText\">\n <div className=\"dsh-channels-cfg__rowLabel\">\n <span>{label}</span>\n {tags}\n {details === undefined\n ? null\n : (\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__more\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n {PANEL_COPY.details}\n </button>\n )}\n </div>\n {summary === undefined ? null : <div className=\"dsh-channels-cfg__rowSummary\">{summary}</div>}\n </div>\n {control === undefined ? null : <div className=\"dsh-channels-cfg__rowCtl\">{control}</div>}\n </div>\n {open && details !== undefined ? <div className=\"dsh-channels-cfg__details\">{details}</div> : null}\n {error === undefined || error === null ? null : <div className=\"dsh-channels-cfg__rowError\">{error}</div>}\n </div>\n )\n}\n\n/** Accessible on/off switch. */\nexport function Switch(props: {\n checked: boolean\n label: string\n disabled?: boolean\n onChange: (next: boolean) => void\n}): JSX.Element {\n const { checked, label, disabled = false, onChange } = props\n return (\n <button\n type=\"button\"\n role=\"switch\"\n className=\"dsh-channels-cfg__switch\"\n aria-checked={checked}\n aria-label={label}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n />\n )\n}\n\n/**\n * Text input paired with the host-native「选择目录…」button. The button owns\n * its own busy state: the OS dialog can stay open for a long time, so the\n * rest of the form stays usable while the operator browses.\n */\nfunction DirectoryTextInput(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n inputClass: string\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n onPick: () => Promise<void>\n}): JSX.Element {\n const { field, value, inputClass, disabled, onChange, onPick } = props\n const [picking, setPicking] = useState(false)\n const pick = async (): Promise<void> => {\n setPicking(true)\n try {\n await onPick()\n } finally {\n setPicking(false)\n }\n }\n return (\n <div className=\"dsh-channels-cfg__inputRow\">\n <input\n type=\"text\"\n className={inputClass}\n value={String(value ?? '')}\n placeholder={field.placeholder}\n aria-label={field.label}\n disabled={disabled || picking}\n onChange={event => onChange(field.key, event.target.value)}\n />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn\"\n disabled={disabled || picking}\n onClick={() => { void pick() }}\n >\n {picking ? PANEL_COPY.browsingDirectory : PANEL_COPY.browseDirectory}\n </button>\n </div>\n )\n}\n\n/**\n * `string-list` control: one single-line input per entry, each with a remove\n * button, and an add link below (disabled at `maxItems` inputs). An empty\n * list renders one blank input so the first entry can be typed directly.\n * Issues carrying an `index` mark that input and show the reason under it.\n */\nfunction StringListInput(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n issues: readonly FieldIssueWire[]\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n}): JSX.Element {\n const { field, value, issues, disabled, onChange } = props\n const empty = listItems(value).length === 0\n const rows = listRows(value)\n return (\n <div className=\"dsh-channels-cfg__stringList\">\n {rows.map((item, index) => {\n const issue = issues.find(entry => entry.index === index)\n const position = PANEL_COPY.listEntry.replace('{n}', String(index + 1))\n // Entries have no identity beyond their position, so the index is the key.\n return (\n <div className=\"dsh-channels-cfg__stringListItem\" key={index}>\n <div className=\"dsh-channels-cfg__inputRow\">\n <input\n type=\"text\"\n className={`dsh-channels-cfg__input${issue === undefined ? '' : ' dsh-channels-cfg__input--invalid'}`}\n value={item}\n placeholder={field.placeholder}\n aria-label={`${field.label} ${position}`}\n aria-invalid={issue === undefined ? undefined : true}\n disabled={disabled}\n onChange={event => onChange(field.key, withListEntry(rows, index, event.target.value))}\n />\n {empty\n ? null\n : (\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--remove\"\n aria-label={`${PANEL_COPY.listRemove} ${position}`}\n disabled={disabled}\n onClick={() => onChange(field.key, withoutListEntry(rows, index))}\n >\n {PANEL_COPY.listRemove}\n </button>\n )}\n </div>\n {issue === undefined ? null : <span className=\"dsh-channels-cfg__fieldError\">{issue.message}</span>}\n </div>\n )\n })}\n <div>\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__link\"\n disabled={disabled || listIsFull(field, rows)}\n onClick={() => onChange(field.key, withBlankListEntry(rows))}\n >\n + {PANEL_COPY.listAdd}\n </button>\n </div>\n </div>\n )\n}\n\n/** Number input with its unit (and a MiB reading for byte counts). */\nfunction NumberInput(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n inputClass: string\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n}): JSX.Element {\n const { field, value, inputClass, disabled, onChange } = props\n const text = numberInputValue(field, value)\n const reading = field.unit === BYTES_UNIT ? bytesReading(text) : undefined\n return (\n <div className=\"dsh-channels-cfg__inputRow\">\n {reading === undefined ? null : <span className=\"dsh-channels-cfg__bytes\">{reading}</span>}\n <input\n type=\"number\"\n className={`${inputClass} dsh-channels-cfg__input--num`}\n value={text}\n placeholder={field.placeholder}\n step={numberInputStep(field)}\n aria-label={field.label}\n disabled={disabled}\n onChange={event => onChange(field.key, event.target.value)}\n />\n {field.unit === undefined ? null : <span className=\"dsh-channels-cfg__unit\">{field.unit}</span>}\n </div>\n )\n}\n\n/** The control for one Host field descriptor. */\nfunction FieldControl(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n issues: readonly FieldIssueWire[]\n invalid: boolean\n disabled: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n onPickDirectory?: () => Promise<void>\n}): JSX.Element | null {\n const { field, value, issues, invalid, disabled, onChange, onPickDirectory } = props\n const inputClass = `dsh-channels-cfg__input${invalid ? ' dsh-channels-cfg__input--invalid' : ''}`\n switch (field.kind) {\n case 'boolean':\n return <Switch checked={value === true} label={field.label} disabled={disabled} onChange={next => onChange(field.key, next)} />\n case 'select': {\n const toggle = field.toggle\n if (toggle !== undefined) {\n return (\n <Switch\n checked={value === toggle.on}\n label={field.label}\n disabled={disabled}\n onChange={next => onChange(field.key, next ? toggle.on : toggle.off)}\n />\n )\n }\n return (\n <select\n className={`${inputClass} dsh-channels-cfg__select`}\n value={String(value ?? '')}\n disabled={disabled || field.disabled === true}\n aria-label={field.label}\n onChange={event => onChange(field.key, event.target.value)}\n >\n {(field.options ?? []).map(option => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n )\n }\n case 'number':\n return <NumberInput field={field} value={value} inputClass={inputClass} disabled={disabled} onChange={onChange} />\n case 'text':\n return field.browse === 'directory' && onPickDirectory !== undefined\n ? (\n <DirectoryTextInput\n field={field}\n value={value}\n inputClass={inputClass}\n disabled={disabled}\n onChange={onChange}\n onPick={onPickDirectory}\n />\n )\n : (\n <input\n type=\"text\"\n className={`${inputClass}${field.wide === true ? '' : ' dsh-channels-cfg__input--text'}`}\n value={String(value ?? '')}\n placeholder={field.placeholder}\n aria-label={field.label}\n disabled={disabled}\n onChange={event => onChange(field.key, event.target.value)}\n />\n )\n case 'string-list':\n return <StringListInput field={field} value={value} issues={issues} disabled={disabled} onChange={onChange} />\n default:\n // Unknown kinds come from a newer Host; render nothing instead of a broken control.\n return null\n }\n}\n\n/** One Host field rendered as a setting row. */\nexport function SettingRow(props: {\n field: EditableFieldWire\n value: ConfigValue | undefined\n issues: readonly FieldIssueWire[]\n disabled?: boolean\n onChange: (key: EditableKey, value: ConfigValue) => void\n /** Present only for `field.browse === 'directory'` rows the page can serve. */\n onPickDirectory?: () => Promise<void>\n}): JSX.Element {\n const { field, value, issues, disabled = false, onChange, onPickDirectory } = props\n // Entry issues (`index` set) are rendered next to their input by StringListInput.\n const issue = issues.find(item => item.key === field.key && item.index === undefined)\n const entryIssues = issues.filter(item => item.key === field.key && item.index !== undefined)\n return (\n <Row\n label={field.label}\n tags={field.restartRequired ? <span className=\"dsh-channels-cfg__tag\">{PANEL_COPY.restartTag}</span> : undefined}\n summary={field.summary}\n details={field.help}\n wide={field.wide === true}\n error={issue?.message}\n control={(\n <FieldControl\n field={field}\n value={value}\n issues={entryIssues}\n invalid={issue !== undefined}\n disabled={disabled}\n onChange={onChange}\n onPickDirectory={onPickDirectory}\n />\n )}\n />\n )\n}\n\n/** Rows for every field of one group. */\nfunction GroupRows(props: { form: FormView; fields: readonly EditableFieldWire[] }): JSX.Element {\n const { form, fields } = props\n return (\n <>\n {fields.map((field) => {\n const onPick = form.onPickDirectory\n return (\n <SettingRow\n key={field.key}\n field={field}\n value={form.draft[field.key] ?? effectiveValue(form.panel, field.key)}\n issues={form.issues}\n disabled={form.locked}\n onChange={form.onChange}\n onPickDirectory={onPick === undefined ? undefined : () => onPick(field.key)}\n />\n )\n })}\n </>\n )\n}\n\n/**\n * A group of rarely changed fields behind one summary row. It opens on its\n * own when one of its fields has a validation issue.\n */\nfunction CollapsedGroup(props: { form: FormView; fields: readonly EditableFieldWire[] }): JSX.Element {\n const { form, fields } = props\n const [open, setOpen] = useState(false)\n const keys = new Set<string>(fields.map(field => field.key))\n const changed = fields.filter(field =>\n form.panel.config.overriddenKeys.includes(field.key) || field.key in form.draft).length\n const expanded = open || form.issues.some(issue => keys.has(issue.key))\n return (\n <>\n <Row\n label={fillCopy(PANEL_COPY.collapsedCount, { n: fields.length })}\n summary={changed === 0 ? PANEL_COPY.collapsedClean : fillCopy(PANEL_COPY.collapsedChanged, { n: changed })}\n control={(\n <button type=\"button\" className=\"dsh-channels-cfg__more\" aria-expanded={expanded} onClick={() => setOpen(!expanded)}>\n {expanded ? PANEL_COPY.collapse : PANEL_COPY.expand}\n </button>\n )}\n />\n {expanded ? <GroupRows form={form} fields={fields} /> : null}\n </>\n )\n}\n\n/**\n * One tab's editable fields as titled sections, one per Host field group, in\n * the given order. Empty groups render nothing, so a Host that moves every key\n * out of a group does not leave a stray heading behind. `extra` appends rows\n * that are not Host fields (e.g. a credential) to one group's list.\n */\nexport function FieldSections(props: {\n form: FormView\n groups: readonly FieldGroup[]\n extra?: Partial<Record<FieldGroup, ReactNode>>\n footnotes?: Partial<Record<FieldGroup, ReactNode>>\n}): JSX.Element {\n const { form, groups, extra = {}, footnotes = {} } = props\n return (\n <>\n {groups.map((group) => {\n const fields = form.fields.filter(field => field.group === group)\n const more = extra[group]\n if (fields.length === 0 && more === undefined) return null\n return (\n <Section key={group} title={FIELD_GROUP_LABELS[group]} footnote={footnotes[group]}>\n {COLLAPSED_FIELD_GROUPS.includes(group)\n ? <CollapsedGroup form={form} fields={fields} />\n : <GroupRows form={form} fields={fields} />}\n {more}\n </Section>\n )\n })}\n </>\n )\n}\n\n/**\n * Sticky bar shown only while the draft has unsaved keys. Saving submits the\n * whole draft (every tab); discarding drops it.\n */\nexport function SaveBar(props: {\n dirtyCount: number\n restartCount: number\n busy: BusyAction | null\n locked: boolean\n onSave: () => void\n onDiscard: () => void\n}): JSX.Element | null {\n const { dirtyCount, restartCount, busy, locked, onSave, onDiscard } = props\n if (dirtyCount === 0) return null\n const disabled = busy !== null || locked\n return (\n <div className=\"dsh-channels-cfg__saveBar\" role=\"region\" aria-label={PANEL_COPY.save}>\n <span className=\"dsh-channels-cfg__saveMsg\">\n {fillCopy(PANEL_COPY.dirtyCount, { n: dirtyCount })}\n {restartCount === 0 ? '' : fillCopy(PANEL_COPY.dirtyRestart, { n: restartCount })}\n </span>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\" onClick={onDiscard} disabled={disabled}>\n {PANEL_COPY.discard}\n </button>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\" onClick={onSave} disabled={disabled}>\n {busy === 'save' ? PANEL_COPY.saving : PANEL_COPY.save}\n </button>\n </div>\n )\n}\n\n/** Transient banner (save / credentials / reconnect / catalog / access). */\nexport function NoticeBar(props: { kind: 'ok' | 'error'; text: string }): JSX.Element {\n const { kind, text } = props\n return (\n <div\n className={`dsh-channels-cfg__notice dsh-channels-cfg__notice--${kind}`}\n role={kind === 'error' ? 'alert' : 'status'}\n aria-live=\"polite\"\n >\n {text}\n </div>\n )\n}\n\n/** Muted row inside a list (empty or not-yet-loaded state). */\nexport function EmptyRow(props: { text: string }): JSX.Element {\n return <div className=\"dsh-channels-cfg__emptyRow\">{props.text}</div>\n}\n","/**\n * 白名单列表:群白名单、用户白名单与知识库白名单共用的条目行、添加表单\n * (点「+ 添加」才展开)与待处理申请行。\n *\n * @module dsh-channels/client/panel/whitelist\n */\n\nimport { useState } from 'react'\nimport { fillCopy, PANEL_COPY } from '../../settings/contract.ts'\nimport type { AccessGroupWire, AccessRequestWire } from '../../settings/contract.ts'\nimport { Avatar } from '../avatar.tsx'\nimport type { ProfileView } from './profiles.ts'\nimport { EmptyRow, Row, Section } from './shared.tsx'\n\n/** Local `MM-DD HH:mm` rendering for authorization timestamps. */\nexport function formatAccessDate(ms: number): string {\n if (!Number.isFinite(ms) || ms <= 0) return PANEL_COPY.unknownValue\n const date = new Date(ms)\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\n}\n\n/** Which authorization entry produced one grant. */\nfunction sourceLabel(source: AccessGroupWire['source']): string {\n switch (source) {\n case 'command': return PANEL_COPY.accessSourceCommand\n case 'card': return PANEL_COPY.accessSourceCard\n default: return PANEL_COPY.accessSourcePanel\n }\n}\n\n/** One whitelist entry: a group `chat_id` or a user `user_id`. */\nexport interface WhitelistEntry {\n id: string\n /** Live Feishu name, else the name captured at authorization time. */\n name?: string\n /** Live Feishu avatar. */\n avatarUrl?: string\n source: AccessGroupWire['source']\n}\n\n/**\n * One titled whitelist with remove buttons and a collapsed add form.\n * `onAdd` resolves true when the entry was added (the form then closes).\n */\nexport function Whitelist(props: {\n title: string\n /** Shown after the title once loaded; defaults to the entry count. */\n countLabel?: string\n entries: readonly WhitelistEntry[]\n /** Fallback avatar text for entries without an image; defaults to the entry name. */\n avatar?: string\n empty: string\n loaded: boolean\n addLabel: string\n idPlaceholder: string\n namePlaceholder: string\n footnote?: string\n disabled: boolean\n working: boolean\n onAdd: (id: string, name: string) => Promise<boolean>\n onRemove: (id: string) => void\n}): JSX.Element {\n const { title, countLabel, entries, avatar, empty, loaded, addLabel, idPlaceholder, namePlaceholder, footnote, disabled, working, onAdd, onRemove } = props\n const [adding, setAdding] = useState(false)\n const [id, setId] = useState('')\n const [name, setName] = useState('')\n\n const submit = async (): Promise<void> => {\n if (id.trim() === '') return\n if (await onAdd(id, name)) {\n setId('')\n setName('')\n setAdding(false)\n }\n }\n\n return (\n <Section title={loaded ? `${title} · ${countLabel ?? entries.length}` : title} footnote={footnote}>\n {!loaded ? <EmptyRow text={PANEL_COPY.accessNotLoaded} /> : null}\n {loaded && entries.length === 0 ? <EmptyRow text={empty} /> : null}\n {entries.map(entry => (\n <Row\n key={entry.id}\n leading={<Avatar text={avatar ?? entry.name ?? entry.id} url={entry.avatarUrl} />}\n label={entry.name ?? entry.id}\n summary={<><span className=\"dsh-channels-cfg__mono\">{entry.id}</span> · {sourceLabel(entry.source)}</>}\n control={(\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--remove\"\n disabled={disabled}\n onClick={() => onRemove(entry.id)}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove}\n </button>\n )}\n />\n ))}\n {!loaded\n ? null\n : adding\n ? (\n <div className=\"dsh-channels-cfg__addRow\">\n <input\n type=\"text\"\n className=\"dsh-channels-cfg__input\"\n value={id}\n placeholder={idPlaceholder}\n aria-label={idPlaceholder}\n disabled={disabled}\n onChange={event => setId(event.target.value)}\n />\n <input\n type=\"text\"\n className=\"dsh-channels-cfg__input\"\n value={name}\n placeholder={namePlaceholder}\n aria-label={namePlaceholder}\n disabled={disabled}\n onChange={event => setName(event.target.value)}\n />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\"\n disabled={disabled || id.trim() === ''}\n onClick={() => { void submit() }}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAdd}\n </button>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\" onClick={() => setAdding(false)}>\n {PANEL_COPY.cancel}\n </button>\n </div>\n )\n : (\n <div className=\"dsh-channels-cfg__addRow\">\n <button type=\"button\" className=\"dsh-channels-cfg__link\" disabled={disabled} onClick={() => setAdding(true)}>\n + {addLabel}\n </button>\n </div>\n )}\n </Section>\n )\n}\n\n/** Pending authorization requests, settled with the same resolver as the DM card. */\nexport function RequestList(props: {\n requests: readonly AccessRequestWire[]\n profiles: ProfileView\n disabled: boolean\n working: boolean\n onResolve: (token: string, decision: 'allow' | 'deny') => void\n}): JSX.Element {\n const { requests, profiles, disabled, working, onResolve } = props\n return (\n <Section title={`${PANEL_COPY.accessRequestsTitle} · ${requests.length}`} tone=\"warn\">\n {requests.map((request) => {\n const profile = request.kind === 'user' ? profiles.user(request.targetId) : profiles.chat(request.targetId)\n const target = profile?.name ?? request.name ?? request.targetId\n const group = request.chatId === undefined\n ? request.groupName\n : profiles.chat(request.chatId)?.name ?? request.groupName ?? request.chatId\n const where = request.kind === 'user' && group !== undefined\n ? fillCopy(PANEL_COPY.accessRequestIn, { group })\n : ''\n return (\n <Row\n key={request.token}\n leading={<Avatar text={target} url={profile?.avatarUrl} />}\n label={`${target} ${where}${request.kind === 'group' ? PANEL_COPY.accessRequestGroup : PANEL_COPY.accessRequestUser}`}\n summary={(\n <>\n <span className=\"dsh-channels-cfg__mono\">{request.targetId}</span>\n {` · ${formatAccessDate(request.createdAt)} · ${fillCopy(PANEL_COPY.accessRequestExpires, { date: formatAccessDate(request.expiresAt) })}`}\n </>\n )}\n control={(\n <div className=\"dsh-channels-cfg__inputRow\">\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\"\n disabled={disabled}\n onClick={() => onResolve(request.token, 'deny')}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessDeny}\n </button>\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\"\n disabled={disabled}\n onClick={() => onResolve(request.token, 'allow')}\n >\n {working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAllow}\n </button>\n </div>\n )}\n />\n )\n })}\n </Section>\n )\n}\n","/**\n * 「访问授权」tab:待处理申请(有才显示,置顶)、准入字段(调试模式 /\n * 管理员 / 申请有效期)与群、用户白名单。字段保存走全局 draft,白名单增删\n * 与申请处理是独立即时 mutation。\n *\n * `access.loaded === false`(飞书未连接、插件 storage 域未打开)时白名单与\n * 申请列表是「未知」而不是「空」,因此只渲染连接提示并禁用全部写操作。\n *\n * @module dsh-channels/client/panel/access-tab\n */\n\nimport { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type { AccessWire } from '../../settings/contract.ts'\nimport { Avatar } from '../avatar.tsx'\nimport type { ProfileView } from './profiles.ts'\nimport { FieldSections, NoticeBar, Row } from './shared.tsx'\nimport type { FormView } from './shared.tsx'\nimport { RequestList, Whitelist } from './whitelist.tsx'\n\nexport interface AccessTabProps {\n form: FormView\n profiles: ProfileView\n accessNotice: string | null\n accessError: string | null\n onAddGroup: (chatId: string, name: string) => Promise<boolean>\n onRemoveGroup: (chatId: string) => void\n onAddUser: (userId: string, name: string) => Promise<boolean>\n onRemoveUser: (userId: string) => void\n onResolveRequest: (token: string, decision: 'allow' | 'deny') => void\n}\n\nexport function AccessTab(props: AccessTabProps): JSX.Element {\n const { form, profiles, accessNotice, accessError, onAddGroup, onRemoveGroup, onAddUser, onRemoveUser, onResolveRequest } = props\n const { access } = form.panel\n const working = form.busy === 'access'\n const disabled = form.busy !== null || form.locked || !access.loaded\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {accessNotice === null ? null : <NoticeBar kind=\"ok\" text={accessNotice} />}\n {accessError === null ? null : <NoticeBar kind=\"error\" text={accessError} />}\n {access.loaded && access.mode === 'guarded' && !access.admin.configured\n ? (\n <NoticeBar\n kind=\"error\"\n text={access.admin.ownerLookupError === undefined\n ? PANEL_COPY.accessAdminMissingWarning\n : `${PANEL_COPY.accessAdminMissingWarning}(${access.admin.ownerLookupError})`}\n />\n )\n : null}\n {profiles.warning === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{fillCopy(PANEL_COPY.profilesWarning, { reason: profiles.warning })}</p>}\n {access.loaded && access.requests.length > 0\n ? <RequestList requests={access.requests} profiles={profiles} disabled={disabled} working={working} onResolve={onResolveRequest} />\n : null}\n <FieldSections\n form={form}\n groups={TAB_FIELD_GROUPS.access}\n extra={{ access: <AdminRow admin={access.admin} profiles={profiles} /> }}\n />\n <Whitelist\n title={PANEL_COPY.accessGroupsTitle}\n entries={access.groups.map(group => ({\n id: group.chatId,\n name: profiles.chat(group.chatId)?.name ?? group.name,\n avatarUrl: profiles.chat(group.chatId)?.avatarUrl,\n source: group.source,\n }))}\n avatar=\"群\"\n empty={PANEL_COPY.accessGroupsEmpty}\n loaded={access.loaded}\n addLabel={PANEL_COPY.accessAddGroup}\n idPlaceholder={PANEL_COPY.accessGroupChatIdPlaceholder}\n namePlaceholder={PANEL_COPY.accessGroupNamePlaceholder}\n disabled={disabled}\n working={working}\n onAdd={onAddGroup}\n onRemove={onRemoveGroup}\n />\n <Whitelist\n title={PANEL_COPY.accessUsersTitle}\n entries={access.users.map(user => ({\n id: user.userId,\n name: profiles.user(user.userId)?.name ?? user.name,\n avatarUrl: profiles.user(user.userId)?.avatarUrl,\n source: user.source,\n }))}\n empty={PANEL_COPY.accessUsersEmpty}\n loaded={access.loaded}\n addLabel={PANEL_COPY.accessAddUser}\n idPlaceholder={PANEL_COPY.accessUserIdPlaceholder}\n namePlaceholder={PANEL_COPY.accessUserNamePlaceholder}\n disabled={disabled}\n working={working}\n onAdd={onAddUser}\n onRemove={onRemoveUser}\n />\n </div>\n )\n}\n\n/** The effective administrator with their Feishu name and avatar; nothing while there is none. */\nfunction AdminRow(props: { admin: AccessWire['admin']; profiles: ProfileView }): JSX.Element | null {\n const { admin, profiles } = props\n if (admin.userId === undefined) return null\n const profile = profiles.user(admin.userId)\n const name = profile?.name ?? admin.userId\n const source = admin.source === 'owner' ? PANEL_COPY.accessAdminSourceOwner : PANEL_COPY.accessAdminSourceConfig\n return (\n <Row\n leading={<Avatar text={name} url={profile?.avatarUrl} />}\n label={name}\n summary={<>{PANEL_COPY.accessAdminCurrent} · <span className=\"dsh-channels-cfg__mono\">{admin.userId}</span> · {source}</>}\n />\n )\n}\n","/**\n * 凭据行:值经 Host 的 DSH 凭据服务写入本机凭据库,面板只显示「已配置 /\n * 未配置」,两个方向都不回显已存值。已配置时只显示「更换 / 移除」,点「更换」\n * 才出现输入框。保存动作由页面外壳持有(凭据保存独立于配置 draft)。\n *\n * @module dsh-channels/client/panel/credentials-card\n */\n\nimport { useEffect, useRef, useState } from 'react'\nimport { PANEL_COPY } from '../../settings/contract.ts'\nimport type { CredentialRefWire, FieldIssueWire, PanelWire } from '../../settings/contract.ts'\nimport { Pill, Row, Section } from './shared.tsx'\nimport type { BusyAction, CredentialsView } from './shared.tsx'\n\n/** One credential slot as a setting row with its own input and actions. */\nexport function CredentialRow(props: {\n label: string\n summary?: string\n state: CredentialRefWire\n value: string\n secret: boolean\n placeholder: string\n issue?: FieldIssueWire\n busy: BusyAction | null\n disabled: boolean\n onChange: (value: string) => void\n onSave: () => void\n onClear: () => void\n}): JSX.Element {\n const { label, summary, state, value, secret, placeholder, issue, busy, disabled, onChange, onSave, onClear } = props\n const [editing, setEditing] = useState(false)\n // Close the replace input once a credential save finishes (the shell clears the value).\n const previousBusy = useRef(busy)\n useEffect(() => {\n if (previousBusy.current === 'credentials' && busy === null) setEditing(false)\n previousBusy.current = busy\n }, [busy])\n const showInput = !state.configured || editing || value !== ''\n const source = `${PANEL_COPY.credentialStore} · ${state.ref}${state.source === undefined ? '' : ` · ${state.source}`}`\n return (\n <Row\n label={label}\n tags={(\n <Pill tone={state.configured ? 'ok' : 'muted'}>\n {state.configured ? PANEL_COPY.credentialConfigured : PANEL_COPY.credentialMissing}\n </Pill>\n )}\n summary={summary === undefined ? <span className=\"dsh-channels-cfg__mono\">{source}</span> : summary}\n error={issue?.message ?? (state.writable ? undefined : PANEL_COPY.credentialReadOnly)}\n control={showInput\n ? (\n <div className=\"dsh-channels-cfg__inputRow\">\n <input\n type={secret ? 'password' : 'text'}\n className={`dsh-channels-cfg__input dsh-channels-cfg__input--text${issue === undefined ? '' : ' dsh-channels-cfg__input--invalid'}`}\n value={value}\n placeholder={placeholder}\n autoComplete=\"new-password\"\n aria-label={label}\n disabled={disabled}\n onChange={event => onChange(event.target.value)}\n />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\"\n disabled={disabled || value.trim() === ''}\n onClick={onSave}\n >\n {busy === 'credentials' ? PANEL_COPY.savingCredentials : PANEL_COPY.saveCredentials}\n </button>\n {state.configured\n ? (\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\"\n disabled={busy !== null}\n onClick={() => {\n setEditing(false)\n onChange('')\n }}\n >\n {PANEL_COPY.cancel}\n </button>\n )\n : null}\n </div>\n )\n : (\n <div className=\"dsh-channels-cfg__inputRow\">\n <button type=\"button\" className=\"dsh-channels-cfg__btn\" disabled={disabled} onClick={() => setEditing(true)}>\n {PANEL_COPY.replaceCredential}\n </button>\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--remove\" disabled={disabled} onClick={onClear}>\n {busy === 'credentials-clear' ? PANEL_COPY.removingCredential : PANEL_COPY.removeCredential}\n </button>\n </div>\n )}\n />\n )\n}\n\n/** The Feishu application pair (App ID / App Secret). */\nexport function CredentialsSection(props: {\n panel: PanelWire\n credentials: CredentialsView\n}): JSX.Element {\n const { panel, credentials } = props\n const { appId, appSecret, issues, busy, locked, onAppIdChange, onAppSecretChange, onSave, onClear } = credentials\n const appIdState = panel.config.credentials.appId\n const appSecretState = panel.config.credentials.appSecret\n return (\n <Section title={PANEL_COPY.credentialsTitle} footnote={PANEL_COPY.credentialsHint}>\n <CredentialRow\n label={PANEL_COPY.appIdLabel}\n state={appIdState}\n value={appId}\n secret={false}\n placeholder={PANEL_COPY.appIdPlaceholder}\n issue={issues.find(item => item.key === 'appId')}\n busy={busy}\n disabled={busy !== null || locked || !appIdState.writable}\n onChange={onAppIdChange}\n onSave={onSave}\n onClear={() => onClear('appId')}\n />\n <CredentialRow\n label={PANEL_COPY.appSecretLabel}\n state={appSecretState}\n value={appSecret}\n secret\n placeholder={PANEL_COPY.appSecretPlaceholder}\n issue={issues.find(item => item.key === 'appSecret')}\n busy={busy}\n disabled={busy !== null || locked || !appSecretState.writable}\n onChange={onAppSecretChange}\n onSave={onSave}\n onClear={() => onClear('appSecret')}\n />\n </Section>\n )\n}\n","/**\n * 「高级」tab:日志分析接口(开关 + 访问令牌凭据行)、SDK 日志级别\n * 与 Web 覆盖文件(「恢复 profile 配置」删除整个覆盖层,不走 draft)。\n *\n * @module dsh-channels/client/panel/advanced-tab\n */\n\nimport { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport { CredentialRow } from './credentials-card.tsx'\nimport { FieldSections, Row, Section } from './shared.tsx'\nimport type { AnalyzeTokenView, FormView } from './shared.tsx'\n\nexport function AdvancedTab(props: {\n form: FormView\n analyzeToken: AnalyzeTokenView\n onClear: () => void\n}): JSX.Element {\n const { form, analyzeToken, onClear } = props\n const { panel, busy, locked } = form\n const tokenState = panel.config.credentials.analyzeToken\n const overridden = panel.config.overriddenKeys.length\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n <FieldSections\n form={form}\n groups={TAB_FIELD_GROUPS.advanced}\n extra={{\n analyze: (\n <CredentialRow\n label={PANEL_COPY.analyzeTokenLabel}\n summary={PANEL_COPY.analyzeTokenSummary}\n state={tokenState}\n value={analyzeToken.value}\n secret\n placeholder={PANEL_COPY.analyzeTokenPlaceholder}\n issue={analyzeToken.issues.find(item => item.key === 'analyzeToken')}\n busy={analyzeToken.busy}\n disabled={analyzeToken.busy !== null || analyzeToken.locked || !tokenState.writable}\n onChange={analyzeToken.onChange}\n onSave={analyzeToken.onSave}\n onClear={analyzeToken.onClear}\n />\n ),\n }}\n />\n <Section title={PANEL_COPY.overlayTitle}>\n <Row\n label={PANEL_COPY.overlayPath}\n summary={<span className=\"dsh-channels-cfg__mono\">{panel.status.overlayPath}</span>}\n details={(\n <>\n {PANEL_COPY.overlayPathHelp}\n {' '}\n {overridden === 0 ? PANEL_COPY.noOverrides : fillCopy(PANEL_COPY.overlayCount, { n: overridden })}\n </>\n )}\n control={(\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--danger\"\n onClick={onClear}\n disabled={overridden === 0 || busy !== null || locked}\n >\n {busy === 'clear' ? PANEL_COPY.clearing : PANEL_COPY.clear}\n </button>\n )}\n />\n </Section>\n </div>\n )\n}\n","/**\n * 「连接」tab:按 Host 派生的生命周期阶段渲染。\n *\n * - unconfigured:上手三步 + 应用凭据\n * - idle / error / connecting / connected:状态块(当前阶段的唯一操作 + 运行数字)\n * - 连接后:自检(权限缺口可展开、斜杠指令面板同步结果)\n * - 有需重连才生效的已保存配置:顶部「重启连接」横幅\n *\n * @module dsh-channels/client/panel/connection-tab\n */\n\nimport { useState } from 'react'\nimport { fillCopy, PANEL_COPY } from '../../settings/contract.ts'\nimport type { ChannelStatusWire, PanelWire, ScopeCheckWire, SlashCommandSyncWire } from '../../settings/contract.ts'\nimport { CredentialsSection } from './credentials-card.tsx'\nimport { fieldLabel, phaseBadge, phaseSummary, Pill, Row, Section } from './shared.tsx'\nimport type { BusyAction, CredentialsView } from './shared.tsx'\n\nexport interface ConnectionTabProps {\n panel: PanelWire\n status: ChannelStatusWire\n busy: BusyAction | null\n credentials: CredentialsView\n onReconnect: () => void\n}\n\nexport function ConnectionTab(props: ConnectionTabProps): JSX.Element {\n const { panel, status, busy, credentials, onReconnect } = props\n const checks = status.scopeCheck !== undefined || status.slashCommandSync !== undefined\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {panel.config.restartPendingKeys.length === 0\n ? null\n : <RestartBanner panel={panel} busy={busy} locked={status.busy} onReconnect={onReconnect} />}\n {status.phase === 'unconfigured'\n ? <OnboardingSection status={status} />\n : <StatusBlock status={status} busy={busy} onReconnect={onReconnect} />}\n {checks\n ? (\n <Section title={PANEL_COPY.checksTitle}>\n {status.scopeCheck === undefined ? null : <ScopeCheckRow check={status.scopeCheck} />}\n {status.slashCommandSync === undefined ? null : <SlashCommandSyncRow sync={status.slashCommandSync} />}\n </Section>\n )\n : null}\n <CredentialsSection panel={panel} credentials={credentials} />\n </div>\n )\n}\n\n/** First-run guide: create the app, fill the credentials, save and wait. */\nfunction OnboardingSection(props: { status: ChannelStatusWire }): JSX.Element {\n const { status } = props\n const steps = [\n { title: PANEL_COPY.onboardingStep1Title, text: PANEL_COPY.onboardingStep1Text },\n { title: PANEL_COPY.onboardingStep2Title, text: PANEL_COPY.onboardingStep2Text },\n { title: PANEL_COPY.onboardingStep3Title, text: PANEL_COPY.onboardingStep3Text },\n ]\n return (\n <div className=\"dsh-channels-cfg__hero\">\n <div className=\"dsh-channels-cfg__heroTop\">\n <strong>{PANEL_COPY.onboardingTitle}</strong>\n </div>\n <ol className=\"dsh-channels-cfg__steps\">\n {steps.map((step, index) => (\n <li className=\"dsh-channels-cfg__step\" key={step.title}>\n <span className=\"dsh-channels-cfg__stepIndex\">{index + 1}</span>\n <span className=\"dsh-channels-cfg__stepBody\">\n <span className=\"dsh-channels-cfg__stepTitle\">{step.title}</span>\n <span className=\"dsh-channels-cfg__rowSummary\">{step.text}</span>\n </span>\n </li>\n ))}\n </ol>\n {status.missingConfig.length === 0\n ? null\n : (\n <span className=\"dsh-channels-cfg__rowSummary\">\n {PANEL_COPY.missingConfig}:{status.missingConfig.join('、')}\n </span>\n )}\n </div>\n )\n}\n\n/** Phase, the one lifecycle action available in it, and the runtime counters. */\nfunction StatusBlock(props: {\n status: ChannelStatusWire\n busy: BusyAction | null\n onReconnect: () => void\n}): JSX.Element {\n const { status, busy, onReconnect } = props\n const badge = phaseBadge(status.phase)\n // `status.busy` covers a connection attempt started outside this panel; the\n // local busy flag covers the panel's own action. Either way the button waits.\n const actionBusy = busy !== null || status.busy\n const stats = [\n { label: PANEL_COPY.routeCount, value: String(status.routeCount) },\n { label: PANEL_COPY.liveAgentCount, value: String(status.liveAgentCount) },\n { label: PANEL_COPY.inflightTurnCount, value: String(status.inflightTurnCount) },\n { label: PANEL_COPY.botOpenId, value: status.botOpenIdTail ?? PANEL_COPY.unknownValue, mono: true },\n ]\n let action: JSX.Element | null = null\n if (status.phase === 'connecting') {\n action = <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\" disabled>{PANEL_COPY.connecting}</button>\n } else if (status.phase === 'idle' || status.phase === 'error') {\n action = (\n <button type=\"button\" className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--primary\" onClick={onReconnect} disabled={actionBusy}>\n {busy === 'reconnect' ? PANEL_COPY.connecting : status.phase === 'idle' ? PANEL_COPY.connect : PANEL_COPY.retry}\n </button>\n )\n } else if (status.phase === 'connected') {\n action = (\n <button type=\"button\" className=\"dsh-channels-cfg__btn\" onClick={onReconnect} disabled={actionBusy}>\n {busy === 'reconnect' ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect}\n </button>\n )\n }\n return (\n <div className=\"dsh-channels-cfg__hero\">\n <div className=\"dsh-channels-cfg__heroTop\">\n <Pill tone={badge.tone} dot>{badge.text}</Pill>\n <strong>{phaseSummary(status.phase)}</strong>\n <span className=\"dsh-channels-cfg__spacer\" />\n {action}\n </div>\n {status.lastStartError === undefined\n ? null\n : (\n <div className=\"dsh-channels-cfg__notice dsh-channels-cfg__notice--error\">\n {PANEL_COPY.lastError}:<span className=\"dsh-channels-cfg__mono\">{status.lastStartError}</span>\n </div>\n )}\n {status.phase === 'connected'\n ? (\n <div className=\"dsh-channels-cfg__stats\">\n {stats.map(stat => (\n <div className=\"dsh-channels-cfg__stat\" key={stat.label}>\n <span>{stat.label}</span>\n <strong className={stat.mono === true ? 'dsh-channels-cfg__mono' : undefined}>{stat.value}</strong>\n </div>\n ))}\n </div>\n )\n : null}\n {status.reasons.length === 0\n ? null\n : (\n <span className={`dsh-channels-cfg__rowSummary${status.level === 'unhealthy' ? ' dsh-channels-cfg__textBad' : ''}`}>\n {PANEL_COPY.healthReasons}:{status.reasons.join(';')}\n </span>\n )}\n </div>\n )\n}\n\n/** Status icon in front of one check row. */\nfunction CheckIcon(props: { tone: 'ok' | 'warn' | 'bad' }): JSX.Element {\n return (\n <span className={`dsh-channels-cfg__checkIcon dsh-channels-cfg__checkIcon--${props.tone}`} aria-hidden=\"true\">\n {props.tone === 'ok' ? '✓' : '!'}\n </span>\n )\n}\n\n/** Post-connect scope self-check: the gap count, expandable to each scope with its console link. */\nfunction ScopeCheckRow(props: { check: ScopeCheckWire }): JSX.Element {\n const { check } = props\n const [open, setOpen] = useState(false)\n if (check.state === 'ok') {\n return <Row leading={<CheckIcon tone=\"ok\" />} label={PANEL_COPY.scopeCheckOk} />\n }\n if (check.state === 'error') {\n return (\n <Row\n leading={<CheckIcon tone=\"bad\" />}\n label={PANEL_COPY.scopeCheckError}\n summary={<span className=\"dsh-channels-cfg__mono\">{check.error}</span>}\n />\n )\n }\n const required = check.missing.filter(item => item.level === 'required').length\n return (\n <>\n <Row\n leading={<CheckIcon tone={required > 0 ? 'bad' : 'warn'} />}\n label={required > 0\n ? fillCopy(PANEL_COPY.scopeCheckMissingRequired, { n: required })\n : fillCopy(PANEL_COPY.scopeCheckMissing, { n: check.missing.length })}\n summary={required > 0 ? PANEL_COPY.scopeCheckPublish : PANEL_COPY.scopeCheckMissingOptional}\n control={(\n <div className=\"dsh-channels-cfg__inputRow\">\n <button type=\"button\" className=\"dsh-channels-cfg__more\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? PANEL_COPY.collapse : PANEL_COPY.expand}\n </button>\n <a className=\"dsh-channels-cfg__btn\" href={check.consoleUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n {PANEL_COPY.scopeCheckOpenAll}\n </a>\n </div>\n )}\n />\n {open\n ? (\n <ul className=\"dsh-channels-cfg__scopeList\">\n {check.missing.map(item => (\n <li key={item.scopes.join(',')}>\n <code className=\"dsh-channels-cfg__mono\">{item.scopes.join(' / ')}</code>\n {item.level === 'required' ? <Pill tone=\"bad\">{PANEL_COPY.scopeCheckRequired}</Pill> : null}\n <span>{item.purpose}</span>\n <a className=\"dsh-channels-cfg__link\" href={item.consoleUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n {PANEL_COPY.scopeCheckOpenOne}\n </a>\n </li>\n ))}\n </ul>\n )\n : null}\n </>\n )\n}\n\n/** Post-connect native slash-command panel sync: the command count, or the Feishu reason. */\nfunction SlashCommandSyncRow(props: { sync: SlashCommandSyncWire }): JSX.Element {\n const { sync } = props\n return sync.state === 'ok'\n ? (\n <Row\n leading={<CheckIcon tone=\"ok\" />}\n label={PANEL_COPY.slashCommandSyncTitle}\n summary={fillCopy(PANEL_COPY.slashCommandSyncOk, { total: sync.total })}\n />\n )\n : (\n <Row\n leading={<CheckIcon tone=\"warn\" />}\n label={PANEL_COPY.slashCommandSyncTitle}\n summary={<>{PANEL_COPY.slashCommandSyncError}:<span className=\"dsh-channels-cfg__mono\">{sync.error}</span></>}\n />\n )\n}\n\n/** Config saved but only applied on the next connect: offer the one-click restart. */\nfunction RestartBanner(props: {\n panel: PanelWire\n busy: BusyAction | null\n locked: boolean\n onReconnect: () => void\n}): JSX.Element {\n const { panel, busy, locked, onReconnect } = props\n const keys = panel.config.restartPendingKeys\n .map(key => fieldLabel(panel.config.fields, key))\n .join('、')\n return (\n <div className=\"dsh-channels-cfg__banner\">\n <span className=\"dsh-channels-cfg__bannerText\">{PANEL_COPY.restartBanner}{keys}</span>\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn\"\n onClick={onReconnect}\n disabled={busy !== null || locked}\n >\n {busy === 'reconnect' ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect}\n </button>\n </div>\n )\n}\n","/**\n * Tabs made only of Host fields (「会话」「模型」「附件与工具」): one titled\n * section per field group the tab owns (`TAB_FIELD_GROUPS`).\n *\n * @module dsh-channels/client/panel/fields-tab\n */\n\nimport type { ReactNode } from 'react'\nimport { PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type { FieldGroup, PanelTabId } from '../../settings/contract.ts'\nimport { FieldSections } from './shared.tsx'\nimport type { FormView } from './shared.tsx'\n\n/** Footnotes under one group's section, per group. */\nconst FOOTNOTES: Partial<Record<FieldGroup, ReactNode>> = {\n model: PANEL_COPY.modelFootnote,\n}\n\nexport function FieldsTab(props: { form: FormView; tab: PanelTabId; note?: string }): JSX.Element {\n const { form, tab, note } = props\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {note === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{note}</p>}\n <FieldSections form={form} groups={TAB_FIELD_GROUPS[tab]} footnotes={FOOTNOTES} />\n </div>\n )\n}\n","/**\n * Live Feishu names and avatars for every user and chat the access and wiki\n * tabs show. The page asks the Host once on entry and again whenever the set\n * of shown ids changes; a failed request keeps the previous answer.\n *\n * @module dsh-channels/client/panel/profiles\n */\n\nimport { useEffect, useMemo, useState } from 'react'\nimport type { AccessWire, FeishuProfilesWire, FeishuProfileWire } from '../../settings/contract.ts'\nimport { fetchFeishuProfiles } from '../api.ts'\n\n/** Resolved profiles plus the reason some could not be read. */\nexport interface ProfileView {\n user: (userId: string) => FeishuProfileWire | undefined\n chat: (chatId: string) => FeishuProfileWire | undefined\n /** Feishu or request failure reason, when one lookup failed. */\n warning?: string\n}\n\n/**\n * The `user_id`s and `chat_id`s one access snapshot shows.\n * @param access - the panel's access snapshot.\n * @returns sorted, distinct ids per kind.\n */\nexport function profileIds(access: AccessWire): { users: string[]; chats: string[] } {\n const users = new Set<string>()\n const chats = new Set<string>()\n if (access.admin.userId !== undefined) users.add(access.admin.userId)\n for (const user of access.users) users.add(user.userId)\n for (const user of access.wikiUsers) users.add(user.userId)\n for (const group of access.groups) chats.add(group.chatId)\n for (const request of access.requests) {\n if (request.kind === 'user') users.add(request.targetId)\n else chats.add(request.targetId)\n if (request.chatId !== undefined) chats.add(request.chatId)\n }\n return { users: [...users].sort(), chats: [...chats].sort() }\n}\n\n/**\n * Profiles for one access snapshot; empty until the panel has loaded.\n * @param access - the panel's access snapshot, null while loading.\n * @returns lookups by id and the latest failure reason.\n */\nexport function useFeishuProfiles(access: AccessWire | null): ProfileView {\n const ids = useMemo(() => (access === null ? { users: [], chats: [] } : profileIds(access)), [access])\n const key = `${ids.users.join(',')}|${ids.chats.join(',')}`\n const [answer, setAnswer] = useState<FeishuProfilesWire>({ users: {}, chats: {} })\n const [requestError, setRequestError] = useState<string | undefined>(undefined)\n useEffect(() => {\n if (ids.users.length === 0 && ids.chats.length === 0) return\n let current = true\n fetchFeishuProfiles(ids.users, ids.chats).then(\n (next) => {\n if (!current) return\n setAnswer(next)\n setRequestError(undefined)\n },\n (error: unknown) => { if (current) setRequestError(error instanceof Error ? error.message : String(error)) },\n )\n return () => { current = false }\n // `key` carries `ids`: a new snapshot with the same ids does not re-ask.\n }, [key])\n return useMemo(() => {\n const warning = requestError ?? answer.warning\n return {\n user: (userId: string) => answer.users[userId],\n chat: (chatId: string) => answer.chats[chatId],\n ...(warning === undefined ? {} : { warning }),\n }\n }, [answer, requestError])\n}\n","/**\n * 「知识库」tab:知识库工具字段(读取 / 编辑开关、可访问范围)+ 知识库\n * 白名单(「可使用的人」)。字段保存走全局 draft,白名单增删是独立即时\n * mutation。\n *\n * @module dsh-channels/client/panel/wiki-tab\n */\n\nimport { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type { ProfileView } from './profiles.ts'\nimport { FieldSections, NoticeBar } from './shared.tsx'\nimport type { FormView } from './shared.tsx'\nimport { Whitelist } from './whitelist.tsx'\n\nexport interface WikiTabProps {\n form: FormView\n profiles: ProfileView\n accessNotice: string | null\n accessError: string | null\n onAddUser: (userId: string, name: string) => Promise<boolean>\n onRemoveUser: (userId: string) => void\n}\n\nexport function WikiTab(props: WikiTabProps): JSX.Element {\n const { form, profiles, accessNotice, accessError, onAddUser, onRemoveUser } = props\n const { access } = form.panel\n const disabled = form.busy !== null || form.locked || !access.loaded\n return (\n <div className=\"dsh-channels-cfg__tabBody\">\n {accessNotice === null ? null : <NoticeBar kind=\"ok\" text={accessNotice} />}\n {accessError === null ? null : <NoticeBar kind=\"error\" text={accessError} />}\n {profiles.warning === undefined ? null : <p className=\"dsh-channels-cfg__secFoot\">{fillCopy(PANEL_COPY.profilesWarning, { reason: profiles.warning })}</p>}\n <FieldSections form={form} groups={TAB_FIELD_GROUPS.wiki} />\n <Whitelist\n title={PANEL_COPY.wikiUsersTitle}\n countLabel={fillCopy(PANEL_COPY.wikiUsersAdminPlus, { n: access.wikiUsers.length })}\n entries={access.wikiUsers.map(user => ({\n id: user.userId,\n name: profiles.user(user.userId)?.name ?? user.name,\n avatarUrl: profiles.user(user.userId)?.avatarUrl,\n source: user.source,\n }))}\n empty={PANEL_COPY.wikiUsersEmpty}\n loaded={access.loaded}\n addLabel={PANEL_COPY.accessAddUser}\n idPlaceholder={PANEL_COPY.accessUserIdPlaceholder}\n namePlaceholder={PANEL_COPY.accessUserNamePlaceholder}\n footnote={PANEL_COPY.wikiUsersHint}\n disabled={disabled}\n working={form.busy === 'access'}\n onAdd={onAddUser}\n onRemove={onRemoveUser}\n />\n </div>\n )\n}\n","/**\n * 左侧栏「飞书渠道」主区页面:数据加载、5 秒状态轮询、全局 draft、顶部状态头 /\n * tab 导航与底部保存条的外壳。\n *\n * 所有 Host 交互(panel / status / catalog / config / credentials / access /\n * pick-directory)都集中在这里;tab 组件只接收渲染所需的数据与\n * 回调。页面不 import 任何 DSH 客户端包,只依赖 react 与 `main` 槽位。\n *\n * @module dsh-channels/client/panel\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { KeyboardEvent } from 'react'\nimport { PANEL_COPY, PANEL_TABS, TAB_FIELD_GROUPS } from '../../settings/contract.ts'\nimport type {\n AccessMutationResponse,\n AccessWire,\n CatalogWire,\n ChannelStatusWire,\n ConfigValue,\n CredentialField,\n EditableKey,\n EditableValues,\n FieldIssueWire,\n PanelTabId,\n PanelWire,\n} from '../../settings/contract.ts'\nimport {\n addAccessGroup,\n addAccessUser,\n addWikiUser,\n clearConfig,\n clearCredential,\n fetchCatalog,\n fetchPanel,\n fetchStatus,\n pickDirectory as requestDirectoryPicker,\n reconnect,\n removeAccessGroup,\n removeAccessUser,\n removeWikiUser,\n resolveAccessRequest,\n saveConfig,\n saveCredentials,\n} from '../api.ts'\nimport { applyCatalogFields, catalogFromFields } from '../catalog.ts'\nimport { AccessTab } from './access-tab.tsx'\nimport { AdvancedTab } from './advanced-tab.tsx'\nimport { ConnectionTab } from './connection-tab.tsx'\nimport { FieldsTab } from './fields-tab.tsx'\nimport { useFeishuProfiles } from './profiles.ts'\nimport {\n fieldLabel,\n formatUptime,\n messageOf,\n NoticeBar,\n phaseBadge,\n Pill,\n SaveBar,\n validateDraft,\n} from './shared.tsx'\nimport type {\n AnalyzeTokenView,\n BusyAction,\n CredentialsView,\n FormView,\n LoadState,\n Notice,\n} from './shared.tsx'\nimport { WikiTab } from './wiki-tab.tsx'\n\n/**\n * Main-column page for the `main` slot: a full-height scroll region with the\n * centered panel column. The slot passes no owner props.\n * @returns the page.\n */\nexport function FeishuChannelsPage(): JSX.Element {\n return (\n <section className=\"dsh-channels-page\" aria-label={PANEL_COPY.title}>\n <div className=\"dsh-channels-page__content\">\n <FeishuChannelsPanel />\n </div>\n </section>\n )\n}\n\n/** Panel shell: one panel payload, one status poll, one global config draft. */\nfunction FeishuChannelsPanel(): JSX.Element | null {\n const [load, setLoad] = useState<LoadState>({ phase: 'loading' })\n const [activeTab, setActiveTab] = useState<PanelTabId>('connection')\n const [draft, setDraft] = useState<EditableValues>({})\n const [notice, setNotice] = useState<Notice | null>(null)\n const [busy, setBusy] = useState<BusyAction | null>(null)\n const [status, setStatus] = useState<ChannelStatusWire | null>(null)\n const [pollError, setPollError] = useState<string | null>(null)\n const [appIdInput, setAppIdInput] = useState('')\n const [appSecretInput, setAppSecretInput] = useState('')\n const [analyzeTokenInput, setAnalyzeTokenInput] = useState('')\n const [catalog, setCatalog] = useState<CatalogWire | null>(null)\n const [catalogBusy, setCatalogBusy] = useState(false)\n const [accessNotice, setAccessNotice] = useState<string | null>(null)\n const [accessError, setAccessError] = useState<string | null>(null)\n const catalogTicket = useRef(0)\n /** Latest ready panel, read from the poll loop without restarting it. */\n const panelRef = useRef<PanelWire | null>(null)\n const panel = load.phase === 'ready' ? load.panel : null\n /** Host-side lifecycle busy: connecting/restarting blocks every panel write. */\n const hostBusy = (status ?? panel?.status)?.busy === true\n useEffect(() => {\n panelRef.current = panel\n }, [panel])\n\n const loadPanel = useCallback(async (): Promise<void> => {\n setLoad({ phase: 'loading' })\n setNotice(null)\n setAccessNotice(null)\n setAccessError(null)\n try {\n const next = await fetchPanel()\n setLoad({ phase: 'ready', panel: next })\n setStatus(next.status)\n setDraft({})\n setAppIdInput('')\n setAppSecretInput('')\n setAnalyzeTokenInput('')\n } catch (error) {\n setLoad({ phase: 'error', message: messageOf(error) })\n }\n }, [])\n\n useEffect(() => {\n void loadPanel()\n }, [loadPanel])\n\n // The Host panel payload already carries options resolved for the effective\n // route; re-seed the local catalog whenever a fresh panel arrives (load/save).\n useEffect(() => {\n if (panel === null) {\n setCatalog(null)\n return\n }\n setCatalog(catalogFromFields(panel.config.fields))\n setCatalogBusy(false)\n }, [panel])\n\n useEffect(() => {\n if (load.phase !== 'ready') return undefined\n let cancelled = false\n const tick = async (): Promise<void> => {\n try {\n const previousPhase = panelRef.current?.status.phase\n const next = await fetchStatus()\n if (cancelled) return\n setStatus(next)\n setPollError(null)\n // A phase transition changes what the panel payload can offer: the\n // storage domain opens/closes with the connection, so the access\n // whitelists go stale. Re-read the panel but\n // never touch the dirty draft.\n if (previousPhase !== undefined && previousPhase !== next.phase) {\n try {\n const refreshed = await fetchPanel()\n if (!cancelled) {\n setLoad({ phase: 'ready', panel: refreshed })\n setStatus(refreshed.status)\n }\n } catch {\n // Keep the status-only update; the next manual refresh retries.\n }\n }\n } catch (error) {\n if (!cancelled) setPollError(messageOf(error))\n }\n }\n const timer = window.setInterval(() => { void tick() }, 5_000)\n return () => {\n cancelled = true\n window.clearInterval(timer)\n }\n }, [load.phase])\n\n /** Fetch the selector catalog for a draft route (latest request wins). */\n const refreshCatalog = useCallback(async (provider: string, model: string): Promise<void> => {\n catalogTicket.current += 1\n const ticket = catalogTicket.current\n setCatalogBusy(true)\n try {\n const next = await fetchCatalog(provider, model)\n if (catalogTicket.current === ticket) setCatalog(next)\n } catch {\n if (catalogTicket.current !== ticket) return\n // Never keep another route's options: degrade the dependent fields to the\n // free-text controls instead of offering stale values.\n setCatalog(previous => previous === null ? null : {\n provider: previous.provider,\n model: { selectable: false, options: [] },\n reasoningEffort: { selectable: false, options: [] },\n agentPreset: previous.agentPreset,\n })\n setNotice({ kind: 'error', text: PANEL_COPY.catalogFailed })\n } finally {\n if (catalogTicket.current === ticket) setCatalogBusy(false)\n }\n }, [])\n\n /** Current form values, used to keep unlisted configured values visible. */\n const currentValues = useMemo(() => {\n if (panel === null) return {}\n const valueOf = (key: EditableKey): string | undefined => {\n const raw = draft[key] ?? panel.config.effective[key] ?? panel.config.base[key]\n return typeof raw === 'string' && raw !== '' ? raw : undefined\n }\n return {\n provider: valueOf('provider'),\n model: valueOf('model'),\n reasoningEffort: valueOf('reasoningEffort'),\n agentPreset: valueOf('agentPreset'),\n }\n }, [draft, panel])\n\n const renderedFields = useMemo(\n () => applyCatalogFields(panel?.config.fields ?? [], catalog, currentValues),\n [panel, catalog, currentValues],\n )\n\n /** Whether one selector currently renders as a dropdown for this form. */\n const isSelectField = useCallback(\n (key: EditableKey): boolean => renderedFields.some(field => field.key === key && field.kind === 'select'),\n [renderedFields],\n )\n\n /** Draft one field, cascading provider → model → effort like the Host catalog. */\n const updateDraft = useCallback((key: EditableKey, value: ConfigValue) => {\n if (panel === null) return\n const providerNow = String(\n draft.provider ?? panel.config.effective.provider ?? panel.config.base.provider ?? '',\n )\n setNotice(null)\n if (key === 'provider') {\n // Changing the provider invalidates the model/effort below it. When the\n // selected provider is the configured one, keep showing its configured\n // model; otherwise fall back to the inherit choice.\n const nextProvider = String(value ?? '')\n const configuredProvider = String(\n panel.config.effective.provider ?? panel.config.base.provider ?? '',\n )\n const keepModel = nextProvider === configuredProvider\n ? String(panel.config.effective.model ?? panel.config.base.model ?? '')\n : ''\n setDraft(previous => ({ ...previous, provider: value, model: '', reasoningEffort: '' }))\n if (catalog !== null && isSelectField('provider')) void refreshCatalog(nextProvider, keepModel)\n return\n }\n if (key === 'model') {\n setDraft(previous => ({ ...previous, model: value, reasoningEffort: '' }))\n if (catalog !== null && isSelectField('model')) void refreshCatalog(providerNow, String(value ?? ''))\n return\n }\n setDraft(previous => ({ ...previous, [key]: value }))\n }, [catalog, draft.provider, isSelectField, panel, refreshCatalog])\n\n const refreshStatus = useCallback(async (): Promise<void> => {\n try {\n const next = await fetchStatus()\n setStatus(next)\n setPollError(null)\n } catch (error) {\n setPollError(messageOf(error))\n }\n }, [])\n\n /**\n * Manual refresh: reload the whole panel payload so access lists and\n * `restartPendingKeys` catch up with the Host. The dirty draft is\n * deliberately preserved — refreshing never discards user input.\n */\n const onRefresh = useCallback(async (): Promise<void> => {\n try {\n const next = await fetchPanel()\n setLoad({ phase: 'ready', panel: next })\n setStatus(next.status)\n setPollError(null)\n setAccessNotice(null)\n setAccessError(null)\n } catch (error) {\n setPollError(messageOf(error))\n }\n }, [])\n\n /** Submit every dirty field at once; the Host persists and re-validates. */\n const onSave = useCallback(async (): Promise<void> => {\n if (panel === null) return\n const { values, issues } = validateDraft(panel, draft)\n if (issues.length > 0) {\n // Name the offending fields: with one draft shared by every tab, the\n // user may be looking at a different tab than the invalid field.\n const detail = issues\n .map(issue => `${fieldLabel(panel.config.fields, issue.key as EditableKey)} ${issue.message}`)\n .join(';')\n setNotice({ kind: 'error', text: `${PANEL_COPY.saveInvalid}:${detail}`, issues })\n return\n }\n if (Object.keys(values).length === 0) return\n setBusy('save')\n setNotice(null)\n try {\n const result = await saveConfig(values)\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n // Clear only the submitted keys: edits typed while the request was in\n // flight stay in the draft instead of being silently discarded.\n setDraft(previous => {\n const next = { ...previous }\n for (const key of Object.keys(values) as EditableKey[]) delete next[key]\n return next\n })\n setNotice({ kind: 'ok', text: PANEL_COPY.saved })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [draft, panel])\n\n /** Drop the whole draft (nothing is written). */\n const discardDraft = useCallback((): void => {\n if (panel === null) return\n setDraft({})\n setNotice(null)\n catalogTicket.current += 1\n setCatalog(catalogFromFields(panel.config.fields))\n setCatalogBusy(false)\n }, [panel])\n\n /**\n * Open the host's native directory chooser (workspace root「选择目录…」) and\n * stage the picked absolute path as a draft value. Nothing is persisted until\n *「保存更改」; a canceled dialog is a no-op, not an error.\n */\n const onPickDirectory = useCallback(async (key: EditableKey): Promise<void> => {\n setNotice(null)\n try {\n const result = await requestDirectoryPicker()\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error })\n return\n }\n if ('canceled' in result) return\n setDraft(previous => ({ ...previous, [key]: result.path }))\n setNotice({ kind: 'ok', text: `${PANEL_COPY.directoryPicked}:${result.path}` })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n }\n }, [])\n\n /** Drop every Web override and return the refreshed panel. */\n const onClear = useCallback(async (): Promise<void> => {\n const confirmed = typeof window.confirm === 'function'\n ? window.confirm(PANEL_COPY.clearConfirm)\n : true\n if (!confirmed) return\n setBusy('clear')\n setNotice(null)\n try {\n const result = await clearConfig()\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setDraft({})\n setNotice({ kind: 'ok', text: PANEL_COPY.cleared })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [])\n\n const running = (status ?? panel?.status)?.running === true\n\n /**\n * The single connect/reconnect action: open a stopped channel or restart a\n * running one. Only a restart interrupts live replies, so only it confirms.\n */\n const onReconnect = useCallback(async (): Promise<void> => {\n if (running) {\n const confirmed = typeof window.confirm === 'function'\n ? window.confirm(PANEL_COPY.restartWarning)\n : true\n if (!confirmed) return\n }\n setBusy('reconnect')\n setNotice(null)\n try {\n const result = await reconnect()\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error })\n // Incomplete credentials: send the user back to the onboarding form.\n if (result.unconfigured === true) setActiveTab('connection')\n await refreshStatus()\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setNotice({ kind: 'ok', text: running ? PANEL_COPY.reconnectDone : PANEL_COPY.connectDone })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n await refreshStatus()\n } finally {\n setBusy(null)\n }\n }, [refreshStatus, running])\n\n /** Replace the embedded access snapshot after a whitelist mutation. */\n const applyAccess = useCallback((access: AccessWire): void => {\n setLoad(previous => previous.phase === 'ready'\n ? { phase: 'ready', panel: { ...previous.panel, access } }\n : previous)\n }, [])\n\n const onAddAccessGroup = useCallback(async (chatId: string, name: string): Promise<boolean> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await addAccessGroup(chatId, name)\n if (!result.ok) {\n setAccessError(result.error)\n return false\n }\n applyAccess(result.access)\n setAccessNotice(PANEL_COPY.accessGroupAdded)\n return true\n } catch (error) {\n setAccessError(messageOf(error))\n return false\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n const onRemoveAccessGroup = useCallback(async (chatId: string): Promise<void> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await removeAccessGroup(chatId)\n if (!result.ok) {\n setAccessError(result.error)\n return\n }\n applyAccess(result.access)\n setAccessNotice(PANEL_COPY.accessGroupRemoved)\n } catch (error) {\n setAccessError(messageOf(error))\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n /** Add one user to a whitelist; resolves true when the input should clear. */\n const addUser = useCallback(async (\n add: (userId: string, name: string) => Promise<AccessMutationResponse>,\n userId: string,\n name: string,\n notice: string,\n ): Promise<boolean> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await add(userId, name)\n if (!result.ok) {\n setAccessError(result.error)\n return false\n }\n applyAccess(result.access)\n setAccessNotice(notice)\n return true\n } catch (error) {\n setAccessError(messageOf(error))\n return false\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n const removeUser = useCallback(async (\n remove: (userId: string) => Promise<AccessMutationResponse>,\n userId: string,\n notice: string,\n ): Promise<void> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await remove(userId)\n if (!result.ok) {\n setAccessError(result.error)\n return\n }\n applyAccess(result.access)\n setAccessNotice(notice)\n } catch (error) {\n setAccessError(messageOf(error))\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n const onResolveAccessRequest = useCallback(async (token: string, decision: 'allow' | 'deny'): Promise<void> => {\n setBusy('access')\n setAccessError(null)\n setAccessNotice(null)\n try {\n const result = await resolveAccessRequest(token, decision)\n if (!result.ok) {\n setAccessError(result.error)\n return\n }\n applyAccess(result.access)\n setAccessNotice(decision === 'allow' ? PANEL_COPY.accessRequestAllowed : PANEL_COPY.accessRequestDenied)\n } catch (error) {\n setAccessError(messageOf(error))\n } finally {\n setBusy(null)\n }\n }, [applyAccess])\n\n /** Save provided credential values; the Host reconnects automatically when ready. */\n const onSaveCredentials = useCallback(async (): Promise<void> => {\n const values: { appId?: string; appSecret?: string } = {}\n if (appIdInput.trim() !== '') values.appId = appIdInput\n if (appSecretInput.trim() !== '') values.appSecret = appSecretInput\n if (values.appId === undefined && values.appSecret === undefined) return\n setBusy('credentials')\n setNotice(null)\n try {\n const result = await saveCredentials(values)\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setAppIdInput('')\n setAppSecretInput('')\n const base = result.reconnect === 'pending'\n ? PANEL_COPY.credentialSavedPending\n : result.reconnect === 'auto'\n ? PANEL_COPY.credentialSavedReconnected\n : result.reconnect === 'unconfigured'\n ? PANEL_COPY.credentialSavedUnconfigured\n : result.reconnect === 'failed'\n ? PANEL_COPY.credentialAutoConnectFailed\n : PANEL_COPY.credentialSaved\n setNotice({\n kind: result.reconnect === 'failed' || result.autoReconnectError !== undefined ? 'error' : 'ok',\n text: result.autoReconnectError === undefined\n ? base\n : `${base}:${result.autoReconnectError}`,\n })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [appIdInput, appSecretInput])\n\n /** Save the analyze API token; it applies to the next request, no reconnect. */\n const onSaveAnalyzeToken = useCallback(async (): Promise<void> => {\n if (analyzeTokenInput.trim() === '') return\n setBusy('credentials')\n setNotice(null)\n try {\n const result = await saveCredentials({ analyzeToken: analyzeTokenInput })\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setAnalyzeTokenInput('')\n setNotice({ kind: 'ok', text: PANEL_COPY.analyzeTokenSaved })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [analyzeTokenInput])\n\n const onClearCredential = useCallback(async (field: CredentialField): Promise<void> => {\n const confirmed = typeof window.confirm === 'function'\n ? window.confirm(PANEL_COPY.credentialRemoveConfirm)\n : true\n if (!confirmed) return\n setBusy('credentials-clear')\n setNotice(null)\n try {\n const result = await clearCredential(field)\n if (!result.ok) {\n setNotice({ kind: 'error', text: result.error, issues: result.issues })\n return\n }\n setLoad({ phase: 'ready', panel: result.panel })\n setStatus(result.panel.status)\n setNotice({\n kind: 'ok',\n text: field === 'analyzeToken'\n ? PANEL_COPY.analyzeTokenRemoved\n : result.reconnect === 'pending' ? PANEL_COPY.credentialRemovedPending : PANEL_COPY.credentialRemoved,\n })\n } catch (error) {\n setNotice({ kind: 'error', text: messageOf(error) })\n } finally {\n setBusy(null)\n }\n }, [])\n\n const issues = useMemo<FieldIssueWire[]>(\n () => (notice?.kind === 'error' ? notice.issues ?? [] : []),\n [notice],\n )\n const dirtyCount = Object.keys(draft).length\n const restartCount = useMemo(\n () => (panel?.config.fields ?? []).filter(field => field.restartRequired && field.key in draft).length,\n [draft, panel],\n )\n\n /** Which tabs currently own a dirty key (the dot is per tab, not global). */\n const dirtyTabs = useMemo<ReadonlySet<PanelTabId>>(() => {\n const tabs = new Set<PanelTabId>()\n if (panel === null || dirtyCount === 0) return tabs\n const groupOf = new Map(panel.config.fields.map(field => [field.key, field.group]))\n for (const key of Object.keys(draft)) {\n const group = groupOf.get(key as EditableKey)\n if (group === undefined) continue\n for (const tab of PANEL_TABS) {\n if (TAB_FIELD_GROUPS[tab.id].includes(group)) tabs.add(tab.id)\n }\n }\n return tabs\n }, [dirtyCount, draft, panel])\n\n const form = useMemo<FormView | null>(() => {\n if (panel === null) return null\n return {\n panel,\n fields: renderedFields,\n draft,\n issues,\n busy,\n locked: hostBusy,\n onChange: updateDraft,\n onPickDirectory,\n }\n }, [busy, draft, hostBusy, issues, onPickDirectory, panel, renderedFields, updateDraft])\n\n const profiles = useFeishuProfiles(panel?.access ?? null)\n\n const credentials = useMemo<CredentialsView>(() => ({\n appId: appIdInput,\n appSecret: appSecretInput,\n issues,\n busy,\n locked: hostBusy,\n onAppIdChange: setAppIdInput,\n onAppSecretChange: setAppSecretInput,\n onSave: () => { void onSaveCredentials() },\n onClear: (field: CredentialField) => { void onClearCredential(field) },\n }), [appIdInput, appSecretInput, busy, hostBusy, issues, onClearCredential, onSaveCredentials])\n\n const analyzeToken = useMemo<AnalyzeTokenView>(() => ({\n value: analyzeTokenInput,\n issues,\n busy,\n locked: hostBusy,\n onChange: setAnalyzeTokenInput,\n onSave: () => { void onSaveAnalyzeToken() },\n onClear: () => { void onClearCredential('analyzeToken') },\n }), [analyzeTokenInput, busy, hostBusy, issues, onClearCredential, onSaveAnalyzeToken])\n\n if (load.phase === 'loading') {\n return (\n <div className=\"dsh-channels-cfg\">\n <div className=\"dsh-channels-cfg__loading\">{PANEL_COPY.loading}</div>\n </div>\n )\n }\n if (load.phase === 'error') {\n return (\n <div className=\"dsh-channels-cfg\">\n <div className=\"dsh-channels-cfg__error\">\n <strong>{PANEL_COPY.loadFailed}</strong>\n <span>{load.message}</span>\n <div>\n <button type=\"button\" className=\"dsh-channels-cfg__btn\" onClick={() => { void loadPanel() }}>\n {PANEL_COPY.retry}\n </button>\n </div>\n </div>\n </div>\n )\n }\n if (form === null) return null\n const current = status ?? load.panel.status\n\n return (\n <div className=\"dsh-channels-cfg\">\n <PanelHeader\n status={current}\n pollError={pollError}\n busy={busy}\n locked={hostBusy}\n onRefresh={() => { void onRefresh() }}\n />\n <TabNav\n activeTab={activeTab}\n dirtyTabs={dirtyTabs}\n counts={{ access: load.panel.access.loaded ? load.panel.access.requests.length : 0 }}\n onChange={setActiveTab}\n />\n {load.panel.access.mode === 'open' ? <div className=\"dsh-channels-cfg__banner\">{PANEL_COPY.debugModeWarning}</div> : null}\n {notice === null ? null : <NoticeBar kind={notice.kind} text={notice.text} />}\n <div\n role=\"tabpanel\"\n id={`dsh-channels-tabpanel-${activeTab}`}\n aria-labelledby={`dsh-channels-tab-${activeTab}`}\n >\n {activeTab === 'connection'\n ? (\n <ConnectionTab\n panel={form.panel}\n status={current}\n busy={busy}\n credentials={credentials}\n onReconnect={() => { void onReconnect() }}\n />\n )\n : null}\n {activeTab === 'sessions' ? <FieldsTab form={form} tab=\"sessions\" /> : null}\n {activeTab === 'model'\n ? <FieldsTab form={form} tab=\"model\" note={catalogBusy ? PANEL_COPY.catalogLoading : undefined} />\n : null}\n {activeTab === 'attachments' ? <FieldsTab form={form} tab=\"attachments\" /> : null}\n {activeTab === 'access'\n ? (\n <AccessTab\n form={form}\n profiles={profiles}\n accessNotice={accessNotice}\n accessError={accessError}\n onAddGroup={onAddAccessGroup}\n onRemoveGroup={chatId => { void onRemoveAccessGroup(chatId) }}\n onAddUser={(userId, name) => addUser(addAccessUser, userId, name, PANEL_COPY.accessUserAdded)}\n onRemoveUser={userId => { void removeUser(removeAccessUser, userId, PANEL_COPY.accessUserRemoved) }}\n onResolveRequest={(token, decision) => { void onResolveAccessRequest(token, decision) }}\n />\n )\n : null}\n {activeTab === 'wiki'\n ? (\n <WikiTab\n form={form}\n profiles={profiles}\n accessNotice={accessNotice}\n accessError={accessError}\n onAddUser={(userId, name) => addUser(addWikiUser, userId, name, PANEL_COPY.wikiUserAdded)}\n onRemoveUser={userId => { void removeUser(removeWikiUser, userId, PANEL_COPY.wikiUserRemoved) }}\n />\n )\n : null}\n {activeTab === 'advanced'\n ? <AdvancedTab form={form} analyzeToken={analyzeToken} onClear={() => { void onClear() }} />\n : null}\n </div>\n <SaveBar\n dirtyCount={dirtyCount}\n restartCount={restartCount}\n busy={busy}\n locked={hostBusy}\n onSave={() => { void onSave() }}\n onDiscard={discardDraft}\n />\n </div>\n )\n}\n\n/** Page header: title, phase pill, version and uptime, refresh; visible on every tab. */\nfunction PanelHeader(props: {\n status: ChannelStatusWire\n pollError: string | null\n busy: BusyAction | null\n locked: boolean\n onRefresh: () => void\n}): JSX.Element {\n const { status, pollError, busy, locked, onRefresh } = props\n const badge = phaseBadge(status.phase)\n return (\n <header className=\"dsh-channels-cfg__panelHead\">\n <div className=\"dsh-channels-cfg__headRow\">\n <h1 className=\"dsh-channels-cfg__title\">{PANEL_COPY.title}</h1>\n <Pill tone={badge.tone} dot>{badge.text}</Pill>\n <span className=\"dsh-channels-cfg__meta dsh-channels-cfg__mono\">v{status.version}</span>\n {status.uptimeMs === undefined\n ? null\n : <span className=\"dsh-channels-cfg__meta\">· {PANEL_COPY.uptimePrefix} {formatUptime(status.uptimeMs)}</span>}\n <span className=\"dsh-channels-cfg__spacer\" />\n <button\n type=\"button\"\n className=\"dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost\"\n onClick={onRefresh}\n disabled={busy !== null || locked}\n >\n {PANEL_COPY.refresh}\n </button>\n </div>\n {pollError === null\n ? null\n : (\n <span className=\"dsh-channels-cfg__fieldError\" role=\"status\" aria-live=\"polite\">\n {PANEL_COPY.pollFailed}:{pollError}\n </span>\n )}\n </header>\n )\n}\n\n/** Top-level tab navigation; the dirty dot keeps unsaved work visible per tab. */\nfunction TabNav(props: {\n activeTab: PanelTabId\n dirtyTabs: ReadonlySet<PanelTabId>\n /** Items waiting for the operator per tab (pending access requests). */\n counts: Partial<Record<PanelTabId, number>>\n onChange: (tab: PanelTabId) => void\n}): JSX.Element {\n const { activeTab, dirtyTabs, counts, onChange } = props\n const refs = useRef<Array<HTMLButtonElement | null>>([])\n\n /** Roving-focus keyboard navigation between tabs (WAI-ARIA tabs pattern). */\n const onKeyDown = (index: number, event: KeyboardEvent<HTMLButtonElement>): void => {\n const last = PANEL_TABS.length - 1\n let next: number\n switch (event.key) {\n case 'ArrowRight': next = index === last ? 0 : index + 1; break\n case 'ArrowLeft': next = index === 0 ? last : index - 1; break\n case 'Home': next = 0; break\n case 'End': next = last; break\n default: return\n }\n event.preventDefault()\n onChange(PANEL_TABS[next]!.id)\n refs.current[next]?.focus()\n }\n\n return (\n <nav className=\"dsh-channels-cfg__tabs\" role=\"tablist\" aria-label={PANEL_COPY.title}>\n {PANEL_TABS.map((tab, index) => {\n const selected = tab.id === activeTab\n const dirty = dirtyTabs.has(tab.id)\n return (\n <button\n key={tab.id}\n ref={(node) => { refs.current[index] = node }}\n type=\"button\"\n role=\"tab\"\n id={`dsh-channels-tab-${tab.id}`}\n aria-selected={selected}\n aria-controls={selected ? `dsh-channels-tabpanel-${tab.id}` : undefined}\n tabIndex={selected ? 0 : -1}\n className=\"dsh-channels-cfg__tab\"\n onClick={() => onChange(tab.id)}\n onKeyDown={event => onKeyDown(index, event)}\n >\n {tab.label}\n {(counts[tab.id] ?? 0) > 0 ? <span className=\"dsh-channels-cfg__tabCount\">{counts[tab.id]}</span> : null}\n {dirty\n ? (\n <span\n className=\"dsh-channels-cfg__tabDot\"\n aria-hidden=\"true\"\n />\n )\n : null}\n </button>\n )\n })}\n </nav>\n )\n}\n","/**\n * Feishu channel page styling. DSH semantic tokens (`--dsw-alias-*`) with neutral\n * fallbacks, aliased once to local `--dc-*` variables, so the page follows\n * the active theme.\n *\n * @module dsh-channels/client/styles\n */\n\nconst STYLE_ATTRIBUTE = 'data-dsh-channels-css'\n\nconst CSS = `\n.dsh-channels-page{width:100%;height:100%;min-width:0;min-height:0;overflow:auto;scrollbar-gutter:stable;background:var(--dsw-alias-bg-base,#fff)}\n.dsh-channels-page__content{box-sizing:border-box;max-width:816px;margin:0 auto;padding:18px clamp(24px,4vw,48px) 48px}\n[data-platform='darwin'] .dsh-channels-page__content{padding-top:calc(18px + var(--dsh-frame-top-clearance,0px))}\n.dsh-channels-cfg{--dc-fg:var(--dsw-alias-label-primary,#1f2328);--dc-fg2:var(--dsw-alias-label-secondary,#5c6169);--dc-fg3:var(--dsw-alias-label-tertiary,#8a8f98);--dc-dim:var(--dsw-alias-label-dimmed,#b0b4ba);--dc-bg:var(--dsw-alias-bg-base,#fff);--dc-line:var(--dsw-alias-border-l2,rgba(0,0,0,.12));--dc-hover:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05));--dc-accent:var(--dsw-alias-state-business-primary,#0a67d3);--dc-ok:var(--dsw-alias-state-success-primary,#0a7d33);--dc-warn:var(--dsw-alias-state-warn-primary,#a15c00);--dc-bad:var(--dsw-alias-state-error-primary,#c62828);--dc-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;container-type:inline-size;display:flex;flex-direction:column;gap:14px;width:100%;max-width:720px;color:var(--dc-fg);font-size:13px;line-height:20px}\n.dsh-channels-cfg__panelHead{position:sticky;top:0;z-index:2;display:flex;flex-direction:column;gap:4px;padding:10px 0 6px;background:var(--dc-bg)}\n.dsh-channels-cfg__headRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.dsh-channels-cfg__title{margin:0 2px 0 0;font-size:20px;font-weight:500;line-height:28px}\n.dsh-channels-cfg__meta{color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__spacer{flex:1}\n.dsh-channels-cfg__mono{font-family:var(--dc-mono);font-size:12px}\n.dsh-channels-cfg__pill{display:inline-flex;align-items:center;gap:5px;padding:0 8px;border-radius:999px;font-size:12px;line-height:20px;font-weight:400;white-space:nowrap;color:var(--dc-fg3);background:color-mix(in srgb,currentColor 10%,transparent)}\n.dsh-channels-cfg__pill--ok{color:var(--dc-ok)}\n.dsh-channels-cfg__pill--warn{color:var(--dc-warn)}\n.dsh-channels-cfg__pill--bad{color:var(--dc-bad)}\n.dsh-channels-cfg__dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}\n.dsh-channels-cfg__tabs{display:flex;align-items:center;gap:18px;border-bottom:1px solid var(--dc-line);overflow-x:auto;scrollbar-width:none}\n.dsh-channels-cfg__tab{display:inline-flex;align-items:center;gap:5px;min-height:34px;padding:0;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--dc-fg2);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}\n.dsh-channels-cfg__tab:hover{color:var(--dc-fg)}\n.dsh-channels-cfg__tab[aria-selected=\"true\"]{color:var(--dc-fg);border-bottom-color:currentColor;font-weight:600}\n.dsh-channels-cfg__tabCount{padding:0 5px;border-radius:8px;font-size:11px;line-height:16px;font-weight:500;color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 12%,transparent)}\n.dsh-channels-cfg__tabDot{width:6px;height:6px;border-radius:50%;background:var(--dc-accent);flex:none}\n.dsh-channels-cfg__tabBody{display:flex;flex-direction:column;gap:22px;padding:4px 0 8px}\n.dsh-channels-cfg__sec{display:flex;flex-direction:column;gap:6px;margin:0}\n.dsh-channels-cfg__secTitle{margin:0;font-size:12px;font-weight:600;line-height:18px;color:var(--dc-fg3);letter-spacing:.02em}\n.dsh-channels-cfg__secFoot{margin:0;color:var(--dc-fg3);font-size:12px;line-height:18px}\n.dsh-channels-cfg__list{display:flex;flex-direction:column;border:1px solid var(--dc-line);border-radius:10px}\n.dsh-channels-cfg__list>*+*{border-top:1px solid var(--dc-line)}\n.dsh-channels-cfg__list--warn{border-color:color-mix(in srgb,var(--dc-warn) 45%,transparent);background:color-mix(in srgb,var(--dc-warn) 7%,transparent)}\n.dsh-channels-cfg__row{display:flex;align-items:center;gap:16px;padding:11px 14px;min-height:28px}\n.dsh-channels-cfg__row--wide{flex-direction:column;align-items:stretch;gap:8px}\n.dsh-channels-cfg__rowText{display:flex;flex-direction:column;gap:1px;flex:1 1 auto;min-width:0}\n.dsh-channels-cfg__rowLabel{display:flex;align-items:center;gap:6px;flex-wrap:wrap;overflow-wrap:anywhere}\n.dsh-channels-cfg__rowSummary{color:var(--dc-fg3);font-size:12px;line-height:18px;overflow-wrap:anywhere}\n.dsh-channels-cfg__rowCtl{flex:none;display:flex;align-items:center;justify-content:flex-end;min-width:0;max-width:62%}\n.dsh-channels-cfg__row--wide .dsh-channels-cfg__rowCtl{max-width:none;justify-content:stretch}\n.dsh-channels-cfg__row--wide .dsh-channels-cfg__rowCtl>*{flex:1 1 auto}\n.dsh-channels-cfg__details{padding:0 14px 12px;color:var(--dc-fg2);font-size:12px;line-height:19px;overflow-wrap:anywhere}\n.dsh-channels-cfg__rowError{padding:0 14px 10px;color:var(--dc-bad);font-size:12px;line-height:18px}\n.dsh-channels-cfg__list>.dsh-channels-cfg__rowWrap{border-top-width:0}\n.dsh-channels-cfg__list>.dsh-channels-cfg__rowWrap+*{border-top-width:1px}\n.dsh-channels-cfg__tag{display:inline-flex;align-items:center;padding:0 5px;border-radius:4px;font-size:11px;line-height:16px;white-space:nowrap;color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 12%,transparent)}\n.dsh-channels-cfg__more{padding:0;border:0;background:none;color:var(--dc-fg3);font:inherit;font-size:12px;line-height:18px;cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px;white-space:nowrap}\n.dsh-channels-cfg__more:hover{color:var(--dc-fg2)}\n.dsh-channels-cfg__link{padding:0;border:0;background:none;color:var(--dc-accent);font:inherit;font-size:13px;cursor:pointer;text-decoration:none;white-space:nowrap}\n.dsh-channels-cfg__link:disabled{color:var(--dc-dim);cursor:default}\n.dsh-channels-cfg__switch{position:relative;flex:none;width:34px;height:20px;padding:0;border:0;border-radius:10px;background:var(--dc-dim);cursor:pointer;transition:background .15s}\n.dsh-channels-cfg__switch::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}\n.dsh-channels-cfg__switch[aria-checked=\"true\"]{background:var(--dc-accent)}\n.dsh-channels-cfg__switch[aria-checked=\"true\"]::after{transform:translateX(14px)}\n.dsh-channels-cfg__switch:disabled{opacity:.5;cursor:default}\n.dsh-channels-cfg__input{box-sizing:border-box;width:100%;min-width:0;height:30px;padding:0 10px;border:1px solid var(--dc-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,transparent);color:inherit;font:inherit;font-size:13px;outline:none}\n.dsh-channels-cfg__input:focus{border-color:var(--dc-accent)}\n.dsh-channels-cfg__input--invalid{border-color:var(--dc-bad)}\n.dsh-channels-cfg__input--num{width:120px;text-align:right;font-variant-numeric:tabular-nums}\n.dsh-channels-cfg__input--text{width:260px}\n.dsh-channels-cfg__select{width:220px;appearance:auto;cursor:pointer}\n.dsh-channels-cfg__unit{min-width:36px;color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__bytes{color:var(--dc-fg3);font-size:12px;white-space:nowrap}\n.dsh-channels-cfg__inputRow{display:flex;align-items:center;gap:8px;min-width:0}\n.dsh-channels-cfg__inputRow>.dsh-channels-cfg__input:not(.dsh-channels-cfg__input--num){flex:1 1 auto;width:auto}\n.dsh-channels-cfg__stringList{display:flex;flex-direction:column;gap:6px;min-width:0}\n.dsh-channels-cfg__stringListItem{display:flex;flex-direction:column;gap:2px;min-width:0}\n.dsh-channels-cfg__fieldError{color:var(--dc-bad);font-size:12px;line-height:18px}\n.dsh-channels-cfg__textBad{color:var(--dc-bad)}\n.dsh-channels-cfg__btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:28px;padding:0 12px;border:1px solid var(--dc-line);border-radius:7px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;text-decoration:none}\n.dsh-channels-cfg__btn:hover:not(:disabled){background:var(--dc-hover)}\n.dsh-channels-cfg__btn:disabled{color:var(--dc-dim);cursor:default}\n.dsh-channels-cfg__btn--primary{background:var(--dsw-alias-button-primary-fill,var(--dc-accent));border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff)}\n.dsh-channels-cfg__btn--primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,#0b5cba)}\n.dsh-channels-cfg__btn--primary:disabled{opacity:.45;color:var(--dsw-alias-label-primary-foreground,#fff)}\n.dsh-channels-cfg__btn--ghost{border-color:transparent;color:var(--dc-fg2)}\n.dsh-channels-cfg__btn--danger{color:var(--dc-bad)}\n.dsh-channels-cfg__btn--remove{color:var(--dc-bad);border-color:color-mix(in srgb,var(--dc-bad) 55%,transparent);background:color-mix(in srgb,var(--dc-bad) 8%,transparent)}\n.dsh-channels-cfg__btn--remove:hover:not(:disabled){background:color-mix(in srgb,var(--dc-bad) 15%,transparent)}\n.dsh-channels-cfg__btn--remove:disabled{opacity:.5;color:var(--dc-bad)}\n.dsh-channels-cfg__btn:focus-visible,.dsh-channels-cfg__tab:focus-visible,.dsh-channels-cfg__switch:focus-visible,.dsh-channels-cfg__link:focus-visible,.dsh-channels-cfg__more:focus-visible{outline:2px solid var(--dc-accent);outline-offset:2px}\n.dsh-channels-cfg__notice{padding:8px 12px;border-radius:8px;font-size:12px;line-height:18px;overflow-wrap:anywhere}\n.dsh-channels-cfg__notice--ok{color:var(--dc-ok);background:color-mix(in srgb,var(--dc-ok) 9%,transparent)}\n.dsh-channels-cfg__notice--error{color:var(--dc-bad);background:color-mix(in srgb,var(--dc-bad) 8%,transparent)}\n.dsh-channels-cfg__banner{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 12px;border-radius:8px;background:color-mix(in srgb,var(--dc-warn) 12%,transparent);color:var(--dc-fg);font-size:12px;line-height:18px}\n.dsh-channels-cfg__bannerText{flex:1;min-width:0}\n.dsh-channels-cfg__hero{display:flex;flex-direction:column;gap:12px;padding:14px 16px;border:1px solid var(--dc-line);border-radius:10px}\n.dsh-channels-cfg__heroTop{display:flex;align-items:center;gap:10px;flex-wrap:wrap}\n.dsh-channels-cfg__heroTop strong{font-size:14px;font-weight:600}\n.dsh-channels-cfg__stats{display:flex;gap:28px;flex-wrap:wrap}\n.dsh-channels-cfg__stat{display:flex;flex-direction:column}\n.dsh-channels-cfg__stat span{color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__stat strong{font-size:18px;font-weight:600;line-height:26px;font-variant-numeric:tabular-nums}\n.dsh-channels-cfg__stat strong.dsh-channels-cfg__mono{font-size:14px}\n.dsh-channels-cfg__checkIcon{display:grid;place-items:center;flex:none;width:18px;height:18px;border-radius:50%;font-size:11px;font-weight:700;align-self:flex-start;margin-top:1px}\n.dsh-channels-cfg__checkIcon--ok{color:var(--dc-ok);background:color-mix(in srgb,var(--dc-ok) 12%,transparent)}\n.dsh-channels-cfg__checkIcon--warn{color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 14%,transparent)}\n.dsh-channels-cfg__checkIcon--bad{color:var(--dc-bad);background:color-mix(in srgb,var(--dc-bad) 12%,transparent)}\n.dsh-channels-cfg__scopeList{margin:0;padding:0 14px 12px 46px;list-style:none;display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dc-fg2)}\n.dsh-channels-cfg__scopeList li{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}\n.dsh-channels-cfg__scopeList .dsh-channels-cfg__link{font-size:12px}\n.dsh-channels-cfg__list>.dsh-channels-cfg__scopeList{border-top-width:0}\n.dsh-channels-avatar{box-sizing:border-box;display:grid;place-items:center;flex:none;border-radius:50%;object-fit:cover;background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05));color:var(--dsw-alias-label-secondary,#5c6169);line-height:1}\n.dsh-channels-cfg__addRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 14px}\n.dsh-channels-cfg__addRow .dsh-channels-cfg__input{flex:1 1 160px;width:auto}\n.dsh-channels-cfg__emptyRow{padding:11px 14px;color:var(--dc-fg3);font-size:12px}\n.dsh-channels-cfg__saveBar{position:sticky;bottom:0;z-index:2;display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 0;border-top:1px solid var(--dc-line);background:var(--dc-bg)}\n.dsh-channels-cfg__saveMsg{flex:1;min-width:0;color:var(--dc-fg2);font-size:12px}\n.dsh-channels-cfg__steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}\n.dsh-channels-cfg__step{display:flex;align-items:flex-start;gap:10px}\n.dsh-channels-cfg__stepIndex{display:grid;place-items:center;flex:none;width:20px;height:20px;border-radius:50%;color:var(--dc-accent);background:color-mix(in srgb,var(--dc-accent) 12%,transparent);font-size:12px;font-variant-numeric:tabular-nums}\n.dsh-channels-cfg__stepBody{display:flex;flex-direction:column;gap:2px;min-width:0}\n.dsh-channels-cfg__stepTitle{font-weight:500}\n.dsh-channels-cfg__error{padding:10px 12px;border:1px solid var(--dc-bad);border-radius:10px;color:var(--dc-bad);font-size:12px;line-height:18px;display:flex;flex-direction:column;gap:8px}\n.dsh-channels-cfg__loading{padding:16px 0;color:var(--dc-fg3);font-size:12px}\n.dsh-channels-origin-chip{display:inline-flex;align-items:center;gap:4px;max-width:180px;height:24px;padding:0 8px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary,#5c6169);font:inherit;font-size:12px;line-height:24px;cursor:pointer;white-space:nowrap}\n.dsh-channels-origin-chip:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05));color:var(--dsw-alias-label-primary,#1f2328)}\n.dsh-channels-origin-chip>svg{flex:none}\n.dsh-channels-origin__who{display:inline-flex;align-items:center;gap:6px;vertical-align:middle}\n.dsh-channels-origin-chip__text{overflow:hidden;text-overflow:ellipsis}\n.dsh-channels-origin{box-sizing:border-box;height:100%;overflow:auto;padding:16px;color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;line-height:20px}\n.dsh-channels-origin__head{display:flex;align-items:center;gap:8px;margin-bottom:12px}\n.dsh-channels-origin__title{font-size:15px;font-weight:600;line-height:22px;overflow-wrap:anywhere}\n.dsh-channels-origin__list{display:flex;flex-direction:column;margin:0;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:10px}\n.dsh-channels-origin__row{display:flex;gap:12px;padding:9px 12px}\n.dsh-channels-origin__row+.dsh-channels-origin__row{border-top:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12))}\n.dsh-channels-origin__row dt{flex:none;width:64px;color:var(--dsw-alias-label-tertiary,#8a8f98)}\n.dsh-channels-origin__row dd{flex:1;min-width:0;margin:0;overflow-wrap:anywhere}\n.dsh-channels-origin__mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}\n.dsh-channels-origin__muted{color:var(--dsw-alias-label-tertiary,#8a8f98)}\n.dsh-channels-origin__error{display:flex;flex-direction:column;gap:6px;color:var(--dsw-alias-state-error-primary,#c62828);font-size:12px}\n.dsh-channels-origin__actions{display:flex;align-items:center;gap:14px;margin-top:14px}\n.dsh-channels-origin__button{display:inline-flex;align-items:center;height:28px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:7px;color:inherit;font-size:13px;text-decoration:none}\n.dsh-channels-origin__button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05))}\n.dsh-channels-origin__link{padding:0;border:0;background:none;color:var(--dsw-alias-state-business-primary,#0a67d3);font:inherit;font-size:13px;cursor:pointer}\n@media (prefers-reduced-motion:reduce){.dsh-channels-cfg__switch,.dsh-channels-cfg__switch::after{transition:none}}\n@container (max-width:560px){\n.dsh-channels-cfg__row:not(.dsh-channels-cfg__row--wide){flex-wrap:wrap}\n.dsh-channels-cfg__rowCtl{max-width:100%}\n.dsh-channels-cfg__select,.dsh-channels-cfg__input--text{width:100%}\n}\n`\n\n/** Append the panel stylesheet once (idempotent across HMR / remounts). */\nexport function installStyles(): () => void {\n const existing = document.querySelector(`style[${STYLE_ATTRIBUTE}]`)\n if (existing !== null) return () => {}\n const style = document.createElement('style')\n style.setAttribute(STYLE_ATTRIBUTE, '')\n style.textContent = CSS\n document.head.append(style)\n return () => {\n style.remove()\n }\n}\n","/**\n * dsh-channels client half: 左侧栏全局入口「飞书渠道」+ 主区整页设置面板,\n * 以及对话内的「飞书来源」(会话头部入口 + 右侧栏标签页)。\n *\n * 只依赖平台核心的 `slots` 服务与 react(bundle 里 react 走平台模块表,\n * 其余全部内联);右侧栏服务存在时才注册来源标签页。页面数据来自 Host\n * 半侧的 `/dsh-channels/*` 路由,因此不需要 typert 契约,也不 import 任何\n * DSH 客户端包。\n *\n * @module dsh-channels/client\n */\n\nimport { PANEL_COPY } from '../settings/contract.ts'\nimport { FeishuChannelsIcon } from './icon.tsx'\nimport { ORIGIN_TAB_ID, ORIGIN_TAB_KIND, ORIGIN_TAB_TITLE, OriginHeaderAction, OriginTab } from './origin.tsx'\nimport { FeishuChannelsPage } from './panel/index.tsx'\nimport { installStyles } from './styles.ts'\n\n// 平台 client runtime 的最小结构声明:只声明本 bundle 用到的能力。\n// 与安装的 DSH Web 版本(0.2.x)的 slots 运行时 API 一致:list 槽按 `id`\n// 注册,keyed 槽按 `key` 注册;`inject` 回调按会话作用域槽收到 sessionId。\nexport interface ClientSlots {\n inject(slot: string, contribution: () => unknown): void\n register(\n declaration:\n | { name: string; id: string; order?: number; label?: () => string; inject?: (sessionId: string) => object }\n | { name: string; key: string },\n component: (props: never) => unknown,\n ): () => void\n}\n\n/** Static right-sidebar tab type (`ctx.sidebarRightTabs.register`). */\nexport interface SidebarRightTabDefinition {\n id: string\n kind: string\n title: (address: string) => string\n}\n\n/** Right-sidebar services the origin tab needs. */\nexport interface SidebarRightScope extends ClientContext {\n sidebarRightTabs: { register(definition: SidebarRightTabDefinition): () => void }\n sidebarRight: { openTab(kind: string): void }\n}\n\nexport interface ClientContext {\n slots: ClientSlots\n effect?(callback: () => void | (() => void), label?: string): void\n /** Run `callback` in a child scope while every named service is available. */\n inject?(deps: string[], callback: (scope: SidebarRightScope) => void): void\n}\n\n/** Cordis client plugin name. */\nexport const name = 'dsh-channels'\n\n/** Required client services: the Slots registry is part of the web shell core. */\nexport const inject = ['slots']\n\n/**\n * Main panel id: the `sidebar.panellist` row id and the matching `main` slot key.\n * Must not change once released: DSH keys navigation state by it.\n */\nexport const PANEL_ID = 'feishu-channels'\n/** Left-sidebar row order (built-ins: tasks 10; Plugins is pinned last by the sidebar). */\nexport const PANEL_ORDER = 40\n/** Session header action order (after the preset label -10 and subagents 10). */\nexport const ORIGIN_ACTION_ORDER = 30\n\n/**\n * Mount the left-sidebar entry, its main-column page, the stylesheet, and —\n * while the right sidebar is present — the Feishu origin header entry and tab.\n * @param ctx - client plugin context carrying the slots registry.\n */\nexport function apply(ctx: ClientContext): void {\n if (typeof ctx.effect === 'function') {\n ctx.effect(() => installStyles(), 'dsh-channels: panel styles')\n } else {\n installStyles()\n }\n ctx.slots.inject('main', () => ctx.slots.register({\n name: 'main',\n key: PANEL_ID,\n }, FeishuChannelsPage))\n ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: PANEL_ORDER,\n label: () => PANEL_COPY.title,\n }, FeishuChannelsIcon))\n ctx.inject?.(['sidebarRightTabs', 'sidebarRight'], mountOrigin)\n}\n\n/**\n * Register the origin tab type, its body and title, and the header entry that\n * opens it. Everything lives as long as the right-sidebar services do.\n * @param scope - child context holding the right-sidebar services.\n */\nexport function mountOrigin(scope: SidebarRightScope): void {\n const definition: SidebarRightTabDefinition = {\n id: ORIGIN_TAB_ID,\n kind: ORIGIN_TAB_KIND,\n title: () => ORIGIN_TAB_TITLE,\n }\n scope.effect?.(() => scope.sidebarRightTabs.register(definition), 'dsh-channels: origin tab type')\n scope.slots.inject('sidebar.right.pane.tab', () => scope.slots.register({\n name: 'sidebar.right.pane.tab',\n key: ORIGIN_TAB_ID,\n }, OriginTab))\n scope.slots.inject('conversation.session.header.actions', () => scope.slots.register({\n name: 'conversation.session.header.actions',\n id: 'dsh-channels-origin',\n order: ORIGIN_ACTION_ORDER,\n inject: () => ({\n openOrigin: () => { scope.sidebarRight.openTab(ORIGIN_TAB_KIND) },\n }),\n }, OriginHeaderAction))\n}\n"],"mappings":"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"}
|