import { type RegisterAppOptions, type RegisterAppResult } from '../setup/register-app.js'; import { type CredentialValidation, type CriticalScopeReadbackResult, type RemainingStep } from '../setup/verify-permissions.js'; import { type CreateFeishuOpenPlatformAppOptions, type CreateFeishuOpenPlatformAppResult, type FeishuOpenPlatformSessionInspectionResult, type FeishuWebSessionIdentity, type OpenPlatformAutomationOptions, type OpenPlatformAutomationResult } from '../setup/open-platform-automation.js'; import type { CliId } from '../adapters/cli/types.js'; export type BotOnboardingStatus = 'starting' | 'waiting_for_scan' | 'verifying' | 'configuring_permissions' | 'waiting_for_platform_scan' | 'needs_owner' | 'completed' | 'failed'; /** 开放平台权限自动配置结果, 供前端展示成功摘要或手动兜底步骤. */ export interface BotOnboardingPermission { ok: boolean; /** 成功导入的权限数 */ scopeCount?: number; /** 当前租户目录里没有、被跳过的权限数 */ skippedScopeCount?: number; /** 已提交发布的版本号 */ versionId?: string; /** 部分权限注册失败的告警 */ scopeWarning?: string; /** Exact same-session event mode ACK for MOSA-managed activation. */ eventMode?: number; /** Exact baseline event + callback count ACK for MOSA-managed activation. */ verifiedEventCount?: number; /** 失败原因 / 信息 (失败时给出手动步骤) */ reason?: string; message?: string; } export interface BotOnboardingSnapshot { id: string; status: BotOnboardingStatus; createdAt: number; updatedAt: number; qrUrl?: string; qrDataUrl?: string; expireAt?: number; platformQrDataUrl?: string; /** Exact second Open Platform QR was observed as scanned by Feishu polling. */ platformQrScanConfirmedAt?: number; /** 自动配置进度文案 (来自 automation onStatus) */ permissionStatusMsg?: string; appId?: string; appName?: string; registrationMode?: 'web' | 'compat'; /** Existing onboarding job whose exact App permission setup is being resumed. */ recoveryOfJobId?: string; /** Monotonic permission-recovery attempt within the same immutable target lineage. */ recoveryAttempt?: number; /** Exact terminal/interrupted recovery attempt that authorized this fresh QR. */ previousRecoveryJobId?: string; brand?: 'feishu' | 'lark'; cliId?: string; workingDir?: string; addedBotIndex?: number; /** * 新 bot 是否已自动上线(`botmux start-bot`,无需整组 botmux restart)。 * true = 已拉起单个 daemon 进程并开始收飞书消息;false = 尝试失败(回退到 * 「请重启」提示);undefined = 未尝试(无 startBotLive 注入,如单测)。 */ liveStarted?: boolean; /** 自动上线的诊断信息(成功给进程名,失败给原因),供前端提示。 */ liveStartMessage?: string; /** Managed recovery stopped the exact existing daemon before issuing a fresh owner QR. */ liveStopped?: boolean; /** Exact single-bot stop diagnostic. */ liveStopMessage?: string; /** * MOSA-managed onboarding only: the bot remains present in the private * configuration for exact permission recovery but is excluded from daemon * registration until every critical scope is observable. */ activationPending?: boolean; /** A durable exact-daemon stop is required before recovery can proceed. */ activationDeactivating?: boolean; /** A durable activation ledger exists but the config marker is not yet final. */ activationCommitting?: boolean; /** The caller explicitly requires the critical-scope activation gate. */ criticalScopeActivationRequired?: boolean; permission?: BotOnboardingPermission; /** 自动配置失败时的手动权限步骤 (深链) */ remainingSteps?: RemainingStep[]; /** * needs_owner 时的预填建议:创建应用所用 Web session 的账号邮箱。前端已在表单 * 顶部展示过该邮箱,不算新增暴露;仅当自动确认失败需要用户复核时给出。 */ suggestedOwner?: string; error?: string; message?: string; } /** 调用方 (dashboard) 已校验过的表单输入: CLI / 工作目录 / model. */ export interface BotOnboardingInput { /** 飞书应用名称;留空时按待追加的 bots.json 行号生成 botmux-N。 */ appName?: string; /** 默认 Feishu 单码主路径;compat 是用户明确确认过的 SDK 兼容模式。 */ registrationMode?: 'web' | 'compat'; /** * reuse: 使用表单已展示并确认的身份,缓存失效时不静默弹码; * qr: 用户明确选择首次登录/更换账号,强制生成新二维码。 */ sessionMode?: 'reuse' | 'qr'; expectedIdentity?: Pick; cliId?: CliId; /** 通用启动前缀(如 "aiden x claude");aiden×* 选项解析所得,普通 CLI 为空。 */ wrapperCli?: string; workingDir?: string; /** * 新话题工作目录模式:'fixed' → 落 defaultWorkingDir(直接启动、不弹卡片); * 'card' → 落 workingDir(仓库选择卡片的扫描根)。缺省按 'card' 处理—— * 老前端 / 脚本不带该字段时行为不变;新 Web 表单默认发 'fixed'(推荐)。 */ dirMode?: 'fixed' | 'card'; model?: string; /** * MOSA-managed onboarding only. When true, a bot with incomplete critical * scope readback is persisted as activation-pending and cannot be loaded by * a daemon. The exact permission-recovery job clears the marker and starts * the bot only after all critical scopes are readable. */ requireCriticalScopesBeforeActivation?: boolean; } type RegisterAppFn = (opts?: RegisterAppOptions) => Promise; type CreateAppFn = (opts: CreateFeishuOpenPlatformAppOptions) => Promise; type InspectSessionFn = () => Promise; type ValidateCredentialsFn = (appId: string, appSecret: string, brand?: 'feishu' | 'lark') => Promise; type AutomateOpenPlatformFn = (opts: OpenPlatformAutomationOptions) => Promise; type VerifyCriticalScopesFn = (appId: string, appSecret: string, brand: 'feishu' | 'lark') => Promise; export interface BotOnboardingManagerOptions { botsJsonPath: string; /** * needs_owner 的私有恢复文件。默认与 bots.json 同目录,权限固定 0600;仅用于 * Dashboard 进程重启后继续完成已经创建、但尚未写入 bots.json 的应用。 */ pendingStorePath?: string; /** Private, secret-free recovery lineage used to turn restart into a fresh owner QR. */ permissionRecoveryStorePath?: string; /** 单次 Feishu Web 登录建应用主路径;测试可注入。 */ createApp?: CreateAppFn; inspectSession?: InspectSessionFn; /** SDK device flow fallback;显式只注入 registerApp 时保留旧测试路径。 */ registerApp?: RegisterAppFn; validateCredentials?: ValidateCredentialsFn; automateOpenPlatform?: AutomateOpenPlatformFn; verifyCriticalScopes?: VerifyCriticalScopesFn; /** * A single complete application-info response is not an activation ACK: * Feishu permission propagation can briefly expose an incomplete or stale * view after the owner scan. Require consecutive complete observations * before removing activationPending. */ criticalScopeStableReads?: number; /** Maximum bounded application-info observations for the stable gate. */ criticalScopeMaxAttempts?: number; /** Delay between stable-gate observations. Tests may set this to zero. */ criticalScopePollIntervalMs?: number; renderQrDataUrl?: (url: string) => string; now?: () => number; /** * Bring the just-persisted bot online without a fleet-wide restart. Wired in * the dashboard to spawn `botmux start-bot `: the new daemon * self-registers, opens its Feishu WSClient long-connection, and publishes a * descriptor the dashboard auto-discovers — so a newly added bot works with no * `botmux restart`. Best-effort: a rejection/`ok:false` just falls back to the * restart hint. Omitted in tests → onboarding behaves as before (persist only, * `liveStarted` stays undefined). */ startBotLive?: (appId: string) => Promise<{ ok: boolean; message?: string; }>; /** Stop only the exact existing bot before a managed permission recovery QR. */ stopBotLive?: (appId: string) => Promise<{ ok: boolean; message?: string; }>; } export interface BotOnboardingJob { id: string; done: Promise; } export type StartPermissionRecoveryResult = { ok: true; job: BotOnboardingJob; } | { ok: false; error: 'permission_recovery_target_missing' | 'permission_recovery_target_ambiguous' | 'permission_recovery_target_invalid' | 'permission_recovery_state_unavailable'; }; export type CompleteScopePropagationResult = { ok: true; } | { ok: false; error: 'permission_recovery_target_missing' | 'permission_recovery_target_ambiguous' | 'permission_recovery_target_invalid' | 'permission_recovery_scopes_pending' | 'permission_recovery_activation_failed' | 'permission_recovery_state_unavailable'; }; export type BotOnboardingSessionStatus = { status: 'ready'; source: string; identity: FeishuWebSessionIdentity; } | { status: 'scan_required'; reason?: string; }; export declare function renderQrSvgDataUrl(value: string): string; export declare class BotOnboardingManager { private readonly opts; private readonly jobs; private readonly pendingBots; private readonly createApp?; private readonly inspectSession; private readonly registerApp; private readonly validateCredentials; private readonly automateOpenPlatform; private readonly verifyCriticalScopes; private readonly criticalScopeStableReads; private readonly criticalScopeMaxAttempts; private readonly criticalScopePollIntervalMs; private readonly renderQrDataUrl; private readonly now; private readonly startBotLive?; private readonly pendingStorePath; private readonly stopBotLive?; private readonly permissionRecoveryStorePath; private readonly scopePropagationFlights; private activationStartupReconciliation; private permissionRecoveryStateError?; constructor(opts: BotOnboardingManagerOptions); /** * 恢复 owner 待确认任务。凭证只存在 0600 私有文件和内存中,公开 job snapshot * 仍不包含 secret;bot 也仍未进入 bots.json,因此重启不会把空 allowlist bot * 启起来。若上次进程在写入 bots.json 后、清理恢复文件前退出,则把该 job 恢复 * 为 completed,避免前端得到 unknown_onboarding_job。 */ private restorePendingJobs; /** 原子保存所有 needs_owner 任务;文件不为空时始终是 0600。 */ private savePendingJobs; private restorePermissionRecoveryJobs; private persistPermissionRecoveryJobs; private savePermissionRecoveryJobsBestEffort; /** * A managed initial ACK is restart authority, not optional UI history. * Refuse activation when its first durable write cannot be proven. */ private requireDurableManagedInitialJobs; /** * Best-effort auto-start of the just-persisted bot's daemon (no fleet restart). * Records the outcome on the job snapshot so the frontend shows "已自动上线" * instead of the restart hint. Never throws. */ private runLiveStart; private runLiveStop; private stableCriticalScopeReadback; private activateRecoveredBot; private beginManagedActivation; private commitManagedActivation; private restoreManagedActivationPending; private completeManagedActivation; private clearManagedActivationCommitted; private reconcileInterruptedManagedActivations; private holdRecoveryActivation; private markManagedDeactivated; start(input?: BotOnboardingInput): BotOnboardingJob; /** * Resume only Open Platform authorization for one already-persisted Space * Agent. The exact neutral working directory is the durable target anchor: * zero or multiple matching bots fail closed, and this path never creates an * App or registers another Bot. A managed caller may place the exact existing * row behind the critical-scope activation marker before the fresh QR. */ startPermissionRecovery(input: { workingDir: string; predecessorJobId: string; expectedAppId: string; priorRecoveryJobId?: string; requireCriticalScopesBeforeActivation?: boolean; }): StartPermissionRecoveryResult; /** * Finish only the exact activation-pending tail of an already-scanned * managed initial/recovery job. This never opens SSO, creates an App, or * issues another owner QR. botmux itself re-reads stable scopes, removes * activationPending, and requires an idempotent exact-daemon start ACK * before returning success. */ completeScopePropagation(input: { jobId: string; workingDir: string; expectedAppId: string; }): Promise; private finishScopePropagation; get(id: string): BotOnboardingSnapshot | undefined; suggestedAppName(): string; sessionStatus(): Promise; private patch; private confirmPlatformQrScan; private run; private registerWithSdk; /** 把 bot append/更新进 bots.json(按 larkAppId upsert, 幂等),返回它的行号。 */ private persistBot; /** * 用户在 needs_owner 状态下手动提交 owner。先做格式校验, 再用新 app 凭证 best-effort * 校验「填的身份在本应用里是否可用」:只对能确凿判定的错误 (跨 app 的 ou_ / 不在本 * 企业的邮箱) 拒绝;scope 未生效 / 权限不足 / 网络错误等无法证伪的情况不拦截, 避免把 * 用户永久卡在 needs_owner。校验通过才落盘 allowedUsers 并进入 completed。 */ submitOwner(id: string, rawEntries: string[]): Promise<{ ok: boolean; error?: string; message?: string; }>; /** * 跑开放平台权限自动配置 (复用 setup 的 automateOpenPlatformSetup)。只负责把进度 * 推给前端 (configuring_permissions / waiting_for_platform_scan) 并返回结果——终态 * 由调用方在 owner 落盘后统一决定 (见 finalizePermissions)。 */ private runPermissionAutomation; private runPermissionRecovery; private runOwnerPermissionAutomation; /** * 统一落终态:completed (已落盘 + 有 owner) 或 needs_owner (尚未落盘、待用户手动填)。 * needs_owner 时 addedBotIndex 为 undefined——bot 还没进 bots.json, 没有行号。 */ private finalizePermissions; } export {}; //# sourceMappingURL=bot-onboarding.d.ts.map