/**
 * Federation SPOKE endpoints, mounted INSIDE the dashboard's token gate (these
 * are owner actions — the dashboard token already proves the owner). The spoke
 * makes OUTBOUND calls to a hub; it never needs to expose anything inbound.
 *   - POST /api/team/join-remote   { hubUrl, inviteCode }
 *   - GET  /api/team/remote-roster
 *   - POST /api/team/sync-remote
 *   - POST /api/team/leave-remote  { hubUrl, teamId }
 *
 * The long-lived syncToken is sent in the `Authorization: Bearer` header (never
 * in a URL, so it stays out of access/proxy logs). All hub calls have a timeout.
 * See docs/federation-design.md.
 */
import type { IncomingMessage, ServerResponse } from 'node:http';
import { type LiveBot } from '../services/team-roster.js';
import { type BotConfig } from '../bot-registry.js';
import { type Fetcher } from './federated-group-core.js';
export interface OwnerCandidate {
    unionId: string;
    name: string;
}
/** Injectable seams for the owner resolver (defaults hit Feishu via the bot's
 *  own credentials). Lets the resolver's registry/iteration logic be unit-tested
 *  without network. */
interface OwnerResolveDeps {
    configs?: () => BotConfig[];
    /** Ensure a usable Lark client exists for cfg. The DASHBOARD process has no bot
     *  registry (it proxies to daemons), so getBotClient() would throw — register
     *  the cfg on demand (it carries the app secret from bots.json). */
    ensureClient?: (cfg: BotConfig) => void;
    resolveAllowed?: (larkAppId: string, allowed: string[]) => Promise<string[]>;
    resolveUnion?: (larkAppId: string, openId: string) => Promise<{
        unionId?: string;
        name?: string;
    }>;
}
/** Resolve this deployment's owner identity from bots.json `allowedUsers` using
 *  each bot's OWN app credentials (no /pair, no shared pairings.json — immune to
 *  the dataDir-split that broke /pair). Iterates ALL bots with non-empty
 *  allowedUsers, resolves each independently, then aggregates and deduplicates by
 *  union_id across all bots. The UI auto-binds when there's exactly one candidate,
 *  else lets the owner pick. A bot failing (no scope / API error) is skipped so
 *  one mis-config doesn't hide the rest.
 *
 *  allowedUsers 支持三种格式：
 *  - `on_xxx` (union_id)  — 跨应用稳定 ID，直接使用，无需 API 解析（推荐）
 *  - 邮箱                 — 通过 bot 凭证查询 open_id 再转 union_id
 *  - `ou_xxx` (open_id)   — 仅对签发该 open_id 的同一应用有效；跨应用会报 99992361 */
export declare function resolveOwnerCandidatesFromAllowedUsers(d?: OwnerResolveDeps): Promise<OwnerCandidate[]>;
/** 上报给团队看板的会话裁剪行所需的最小字段（来源 SessionRow）。 */
export interface TeamSessionRowLike {
    sessionId: string;
    botName?: string;
    cliId?: string;
    status?: string;
    title?: string;
    chatId: string;
    scope?: string;
    adopt?: boolean;
    lastMessageAt?: number;
}
/** Push this deployment's current bots to every joined hub. Best-effort.
 *  传 sessionsProvider 时顺带上报团队看板的会话裁剪行：hub 在 sync 响应里下发
 *  该团队的协作群清单，按 chatId 过滤本地会话 POST 回 hub（活跃优先，截 200）。 */
export declare function syncAllMemberships(dataDir: string, fetcher?: Fetcher, live?: LiveBot[], sessionsProvider?: () => TeamSessionRowLike[]): Promise<{
    synced: number;
    failed: number;
}>;
export interface FederationSpokeDeps {
    dataDir?: string;
    fetcher?: Fetcher;
    /** Live daemon-registry bots (injected by dashboard.ts) — authoritative source
     *  for THIS deployment's bots, so an empty/stale bots-info.json never hides
     *  running bots from the team roster / federation sync. */
    liveBots?: () => LiveBot[];
    /** Injected by dashboard.ts — picks a local online creator + proxies to its
     *  daemon's /api/groups/create (federated bots are added by larkAppId). */
    createTeamGroup?: (args: {
        name: string;
        larkAppIds: string[];
        ownerUnionIds?: string[];
    }) => Promise<{
        ok: boolean;
        chatId?: string;
        shareLink?: string;
        invalidBotIds?: string[];
        invalidOwnerUnionIds?: string[];
        error?: string;
    }>;
    /** Test seam: resolve owner candidates from allowedUsers (defaults to the real
     *  Feishu-backed resolver). */
    ownerCandidates?: () => Promise<OwnerCandidate[]>;
}
export declare function handleFederationSpokeApi(req: IncomingMessage, res: ServerResponse, url: URL, deps?: FederationSpokeDeps): Promise<boolean>;
export {};
//# sourceMappingURL=federation-spoke-api.d.ts.map