/** * Dashboard 私有 API:「会议角色预设」(VC meeting consumer profiles)。 * * 2026-08 起预设目录是**全 fleet 共享**的一份(`~/.botmux/config.json` 的 * `vcMeetingAgent.consumerCatalog`),不再按 bot 分开配置: * * - 用户不用再手选「会议事件接收 Bot」——每个开着 VC 的 bot 都处理自己收到的 * 会议事件,被拉进会的是谁就由谁执行; * - 预设条目**不带** `agentAppId`。执行方在读路径合并时才绑定为收到事件的那个 * bot(services/vc-meeting-shared-consumer-catalog.ts),所以「拉 A 进会却把 * B 拉进监听群」在数据模型层面就不可表达。 * * 职责边界:本层只做 用户 DTO ↔ canonical 配置 的映射与 HTTP 语义包装; * revision 乐观并发 / 字段校验的权威在 * `services/vc-meeting-shared-consumer-catalog-store.ts`,运行时冲突裁决的权威 * 在 bot-registry resolver——Dashboard 校验只是提前反馈。 * * permissionPreset 是纯 UI 概念,不持久化:保存时映射成 canonical * capabilities/ownedSinks 原语;`custom` 只允许复用同 id 既有 policy, * 浏览器不能构造 raw capability。 */ import type { BotConfig } from '../bot-registry.js'; import type { VcMeetingConsumerProfileConfig, VcMeetingConsumerResponseMode, VcMeetingListenerOutputPlacement } from '../types.js'; import type { VcMeetingSharedConsumerProfile } from '../global-config.js'; import type { UpdateVcMeetingSharedConsumerCatalogResult, VcMeetingSharedConsumerCatalogFieldError, VcMeetingSharedConsumerCatalogSnapshot } from '../services/vc-meeting-shared-consumer-catalog-store.js'; import { type VcMeetingConsumerProfileTemplateCatalog, type VcMeetingTemplatePermissionPreset } from '../services/vc-meeting-consumer-profile-templates.js'; export type VcMeetingPermissionPreset = VcMeetingTemplatePermissionPreset | 'custom'; /** 字段级错误的形状与 per-bot 时代一致,路径前缀也保持 `profiles[i].*`。 */ export type VcMeetingConsumerProfileFieldError = VcMeetingSharedConsumerCatalogFieldError; export interface VcMeetingConsumerProfileDto { id: string; label?: string; instructions?: string; activityTypes?: string[]; responseMode: VcMeetingConsumerResponseMode; /** Omitted by pre-feature clients and normalized to `auto`. */ listenerPlacement?: VcMeetingListenerOutputPlacement; permissionPreset: VcMeetingPermissionPreset; } export interface VcMeetingAgentOptionDto { appId: string; label: string; cliId?: string; online: boolean; workingDirReady: boolean; reliableTurnTerminal: boolean; /** May this bot be selected as a meeting consumer at all (plan B: true unless * an explicit sandbox request is undeliverable on this platform/backend). */ managedSideEffectEligible: boolean; /** Is the managed sandbox boundary actually in force? false ⇒ the bot's Lark * credential is exposed to untrusted meeting input (informed opt-out). */ sandboxIsolated: boolean; /** 这个 bot 是否接收会议事件(bots.json vcMeetingAgent.enabled)。缺省视为 * 开启——VC 对每个连着飞书的 bot 默认可用,`enabled: false` 是显式退出。 */ vcEnabled: boolean; /** apiOnly(无飞书连接)的 bot 结构上不可能收会议事件,UI 需要禁用它的开关。 */ vcEligible: boolean; /** Configured per-bot in-meeting output policies (bots.json * vcMeetingAgent.meetingConsumer.*). null = unset → daemon default. */ textOutputPolicy: VcMeetingOutputPolicyValue | null; voiceOutputPolicy: VcMeetingOutputPolicyValue | null; /** vcMeetingAgent.realtimeVoice.enabled — hard gate for in-meeting voice. */ realtimeVoiceEnabled: boolean; /** per-bot 从共享目录挑的默认角色 id(vcMeetingAgent.meetingConsumer. * catalogDefaultConsumerId)。null = 未挑 → 跟随共享目录全局默认。 */ catalogDefaultConsumerId: string | null; /** Effective values after daemon defaults (kept in sync with * defaultVcMeetingTextOutputPolicy / defaultVcMeetingVoiceOutputPolicy). */ effectiveTextOutputPolicy: VcMeetingOutputPolicyValue; effectiveVoiceOutputPolicy: VcMeetingOutputPolicyValue; } export type VcMeetingOutputPolicyValue = 'allow' | 'approval' | 'deny'; export interface VcMeetingBotOutputPolicyPatch { appId: string; /** 会议事件接收开关(vcMeetingAgent.enabled)。 */ vcEnabled: boolean; /** null clears the override back to the daemon default. */ textOutputPolicy: VcMeetingOutputPolicyValue | null; voiceOutputPolicy: VcMeetingOutputPolicyValue | null; realtimeVoiceEnabled: boolean; /** per-bot 从共享目录挑的默认角色 id。null(或空串)= 跟随全局默认。 * 写到 vcMeetingAgent.meetingConsumer.catalogDefaultConsumerId。 */ catalogDefaultConsumerId: string | null; } export interface VcMeetingConsumerProfilesGetBody { ok: true; revision: string; catalogState: VcMeetingSharedConsumerCatalogSnapshot['catalogState']; defaultMode: 'listenOnly' | 'agents'; defaultConsumerIds: string[]; profiles: VcMeetingConsumerProfileDto[]; agentOptions: VcMeetingAgentOptionDto[]; /** Versioned, read-only templates. Applying one creates a detached editable profile. */ templateCatalog: VcMeetingConsumerProfileTemplateCatalog; } export interface VcMeetingConsumerProfilesPutRequest { expectedRevision: string; defaultMode: 'listenOnly' | 'agents'; defaultConsumerIds: string[]; profiles: VcMeetingConsumerProfileDto[]; /** Optional per-bot patches applied to bots.json via the locked * read-modify-write path after the shared catalog update succeeds. */ botOutputPolicies?: VcMeetingBotOutputPolicyPatch[]; } export type VcMeetingConsumerProfilesApiResult = { status: 200; body: VcMeetingConsumerProfilesGetBody; } | { status: 400 | 404 | 409 | 422 | 503; body: { ok: false; error: string; fieldErrors?: VcMeetingConsumerProfileFieldError[]; }; }; export interface VcMeetingConsumerProfilesApiDeps { /** 读全局共享目录。同步实现也可以——签名允许两者。 */ readCatalog(): VcMeetingSharedConsumerCatalogSnapshot | Promise; updateCatalog(input: { expectedRevision: string; defaultMode: 'listenOnly' | 'agents'; defaultConsumerIds: string[]; profiles: VcMeetingSharedConsumerProfile[]; }): UpdateVcMeetingSharedConsumerCatalogResult | Promise; loadBotConfigs(): BotConfig[]; effectiveDefaultWorkingDir(cfg: BotConfig): string | undefined; /** Online DaemonInfo botName lookup; undefined when the daemon is offline. */ onlineBotName(appId: string): string | undefined; isOnline(appId: string): boolean; adapterReliableTurnTerminal(cliId: string | undefined, cliPathOverride?: string): boolean; managedSideEffectEligible(bot: BotConfig): boolean; sandboxIsolated(bot: BotConfig): boolean; /** Called after a successful PUT so the live daemon reloads changed bots.json. */ reloadDaemons(appIds: string[]): Promise; /** Locked read-modify-write of one bot's VC switches in bots.json * (vcMeetingAgent.enabled + meetingConsumer.* + realtimeVoice.enabled). */ applyBotOutputPolicy(patch: VcMeetingBotOutputPolicyPatch): Promise<{ ok: boolean; reason?: string; }>; } /** UI 下拉与 DTO 预检共用;权威列表在 bot-registry 严格校验里。 */ export declare const VC_MEETING_PROFILE_ACTIVITY_TYPES: readonly ["transcript_received", "chat_received", "participant_joined", "participant_left", "magic_share_started", "magic_share_ended"]; /** canonical policy → 预设档位;对不上任何档位的既有 policy 显示为 custom。 */ export declare function deriveVcMeetingPermissionPreset(profile: Pick): VcMeetingPermissionPreset; export declare function vcMeetingConsumerProfileToDto(profile: VcMeetingSharedConsumerProfile): VcMeetingConsumerProfileDto; type DtoValidation = { ok: true; profiles: VcMeetingSharedConsumerProfile[]; } | { ok: false; fieldErrors: VcMeetingConsumerProfileFieldError[]; }; /** * DTO → canonical。`role` 不在用户 DTO 里:同 id 沿用既有 canonical role * (role 参与 profileHash,改写会造成不必要的 epoch 变更),新 id 用 id 作 * role。custom 档只复用同 id 既有 capabilities/ownedSinks,新 id 无可复用 * policy → fieldError。 * * 输出**不含** `agentAppId`:共享目录不绑定执行方。 */ export declare function vcMeetingConsumerProfilesFromDtos(dtos: readonly VcMeetingConsumerProfileDto[], existing: readonly VcMeetingSharedConsumerProfile[]): DtoValidation; export declare function buildVcMeetingAgentOptions(deps: Pick): VcMeetingAgentOptionDto[]; export declare function handleVcMeetingConsumerProfilesGet(deps: VcMeetingConsumerProfilesApiDeps): Promise; export declare function handleVcMeetingConsumerProfilesPut(payload: unknown, deps: VcMeetingConsumerProfilesApiDeps): Promise; export {}; //# sourceMappingURL=vc-consumer-profiles-api.d.ts.map