import { type OncallChat, type VcMeetingAgentConfig } from './bot-registry.js'; import type { Session } from './types.js'; import type { DaemonToWorker, LarkMessage } from './types.js'; export type { DaemonSession } from './core/types.js'; import type { DaemonSession } from './core/types.js'; import { type QueuedActivationTailReservation, rollbackRejectedSessionAndGetWinner, type WorkerSessionReplyOptions } from './core/worker-pool.js'; import { type PersistentBackendType } from './core/persistent-backend.js'; import type { PersistentBackendTarget } from './adapters/backend/types.js'; import type { CardActionData } from './im/lark/card-handler.js'; import { type CodexTaskCompletedEvent } from './features/codex-notifier/index.js'; import { type RoutingContext, type DocCommentContext } from './im/lark/event-dispatcher.js'; import { getDocSubscription, putDocSubscription, type DocSubscription } from './services/doc-subs-store.js'; import { RealtimeVoiceSession } from './vc-agent/realtime/index.js'; import { type VcMeetingOutputPolicy, type VcMeetingRuntimeSelectedAgent } from './services/vc-meeting-runtime-store.js'; import { type VcMeetingPreparationQaMode } from './services/vc-meeting-preparations-store.js'; import type { NormalizedVcMeetingItem, VcMeetingPushContext, VcMeetingSessionState } from './vc-agent/types.js'; import { type VcMeetingAmbiguousReceiptRef } from './services/vc-meeting-delivery-store.js'; import { type VcMeetingDeliveryRequest, type VcMeetingDeliveryGap } from './services/vc-meeting-delivery-protocol.js'; import { type VcMeetingCanonicalFeedItem } from './services/vc-meeting-delivery-feed.js'; type VcMeetingDeliveryScope = { receiverSessionId: string; listenerAppId: string; meetingId: string; memberId: string; memberEpoch: number; }; export declare const __testOnly_vcMeetingReceiverRecovery: { start(sessionId: string, turnId: string, dispatchAttempt: number, scope?: Partial>): string; acknowledge(sessionId: string, turnId: string, dispatchAttempt: number): void; finishScheduling(): void; snapshot(key: string): { ready: boolean; pending: boolean; timerArmed: boolean; }; isBlocked(request: VcMeetingDeliveryRequest): boolean; setBackingMissingProbe(probe: (sessionId: string, destroy: boolean) => boolean): void; setDispatchRetirement(retire: (sessionId: string, turnId: string, dispatchAttempt: number) => boolean): void; reset(): void; }; type VcMeetingRuntimeLeaseFencePhase = 'awaiting_ack' | 'escalating' | 'blocked'; type VcMeetingRuntimePersistentScope = PersistentBackendType | 'none' | 'unknown'; type VcMeetingRuntimeLeaseRecoveryDeps = { findSession: (sessionId: string) => DaemonSession | undefined; sendExpiry: (ds: DaemonSession, message: Extract) => void; killWorker: (ds: DaemonSession) => void; /** Worker processes still dying for this session id even though the registry * no longer resolves a ds (process-only retirements). The producer-liveness * gate must keep seeing them (fifth-round review, A1: a fence armed AFTER * the registry delete had no worker reference at all). */ resolveRetiringWorkers?: (sessionId: string) => readonly (DaemonSession['worker'])[]; resolvePersistentScope: (ds: DaemonSession) => VcMeetingRuntimePersistentScope; resolveMissingPersistentScope: (sessionId: string) => VcMeetingRuntimePersistentScope; resolvePersistentTarget?: (sessionId: string, ds?: DaemonSession) => PersistentBackendTarget | undefined; backendAvailable: (backendType: PersistentBackendType) => boolean; /** `sessionId` is mandatory: ZMX destruction is identity-verified and refuses * a name-only kill. `target` carries Herdr's agent-scoped addressing. */ killPersistent: (backendType: PersistentBackendType, sessionName: string, sessionId: string, target?: PersistentBackendTarget) => void; probePersistent: (backendType: PersistentBackendType, sessionName: string, target?: PersistentBackendTarget) => 'exists' | 'missing' | 'unknown'; retireDispatch: (sessionId: string, turnId: string, dispatchAttempt: number) => boolean; warn: (message: string) => void; error: (message: string) => void; }; declare function createVcMeetingRuntimeLeaseRecovery(deps: VcMeetingRuntimeLeaseRecoveryDeps): { arm: (ref: VcMeetingAmbiguousReceiptRef, agentAppId: string) => void; acknowledge: (context: { sessionId: string; turnId: string; dispatchAttempt: number; workerGeneration: number; disposition: "queued_removed" | "cli_fenced"; }) => boolean; isBlocked(request: VcMeetingDeliveryRequest): boolean; snapshot(): Array<{ receiverSessionId: string; deliveryKey: string; dispatchAttempt: number; phase: VcMeetingRuntimeLeaseFencePhase; timerArmed: boolean; }>; reset(): void; }; export declare const __testOnly_createVcMeetingRuntimeLeaseRecovery: typeof createVcMeetingRuntimeLeaseRecovery; /** * Identity captured before a document-comment path crosses an async boundary. * A DaemonSession object can outlive its routing ownership: close, relay, repo * switch, or another creator may replace either the map occupant or its Session * object while an awaited Lark lookup is in flight. Both identities and the * original routing key therefore form one generation. */ interface RoutingGeneration { key: string; ds: DaemonSession; session: Session; } declare function captureRoutingGeneration(ds: DaemonSession): RoutingGeneration; declare function isCurrentRoutingGeneration(generation: RoutingGeneration): boolean; type DocSessionRollback = typeof rollbackRejectedSessionAndGetWinner; /** Register one freshly-created doc-native session without overwriting a * concurrent winner. The rejected row is closed before the winner is returned, * so its persisted record cannot survive as a ghost-active session. */ declare function registerDocSessionCandidate(key: string, candidate: DaemonSession, rollback?: DocSessionRollback): Promise; interface DocBindingPersistenceDeps { dataDir?: string; read?: typeof getDocSubscription; write?: typeof putDocSubscription; } /** * Move one subscription onto the authoritative route owned by `selected`. * * The store is re-read immediately before the synchronous write so cursor and * metadata updates made from another event are retained. A newer explicit * rebind is never overwritten: the only route change accepted since `sub` was * read is the same selected route (the normal concurrent auto-create winner * case). Mutating the caller's snapshot keeps the current comment turn on the * same route as the persisted subscription. */ declare function persistDocBindingToSession(sub: DocSubscription, larkAppId: string, selected: DaemonSession, deps?: DocBindingPersistenceDeps): void; type PersistDocBinding = typeof persistDocBindingToSession; type RollbackDocCandidate = (sessionId: string) => Promise; /** Persist the route selected by registration. A candidate that won the CAS * is now daemon-owned, so a failed subscription write must close only that * exact occupant. A rejected candidate must never close the concurrent * winner whose route it received from registerDocSessionCandidate(). */ declare function persistSelectedDocBinding(routingKey: string, sub: DocSubscription, larkAppId: string, selected: DaemonSession, candidate: DaemonSession, persist?: PersistDocBinding, rollback?: RollbackDocCandidate): Promise; /** Resolve a binding without ever handing a stale anchor's replacement the * comment. If the subscribed session was relayed, find it by stable sessionId * and lazily migrate the subscription to its new authoritative route. */ declare function resolveBoundDocSession(sub: DocSubscription, larkAppId: string, persist?: PersistDocBinding): DaemonSession | undefined; export declare const __testOnly_captureRoutingGeneration: typeof captureRoutingGeneration; export declare const __testOnly_isCurrentRoutingGeneration: typeof isCurrentRoutingGeneration; export declare const __testOnly_registerDocSessionCandidate: typeof registerDocSessionCandidate; export declare const __testOnly_persistDocBindingToSession: typeof persistDocBindingToSession; export declare const __testOnly_persistSelectedDocBinding: typeof persistSelectedDocBinding; export declare const __testOnly_resolveBoundDocSession: typeof resolveBoundDocSession; type VcMeetingDaemonSession = { larkAppId: string; state: VcMeetingSessionState; createdAt: number; lastActivityAt: number; ended: boolean; joined: boolean; monitoringStarted: boolean; /** True after this listener bot's own participant_left event. Generic * activity must not infer presence again; only an explicit own join or a * successful forced join clears the fence. */ listenerPresenceStale?: boolean; listenerPresenceChangedAtMs?: number; listenerPresenceGeneration?: number; listenerRejoinNonce?: string; listenerRejoinCardMessageId?: string; listenerRejoinApplying?: boolean; listenerChatId?: string; pendingItems: NormalizedVcMeetingItem[]; flushTimer?: ReturnType; restoreTickTimer?: ReturnType; flushing: boolean; flushPromise?: Promise; startPromise?: Promise; realtimeVoice?: RealtimeVoiceSession; realtimeVoiceTestUtteranceSent?: boolean; consumerMode?: 'pending' | 'listenOnly' | 'agent'; selectedAgentAppId?: string; selectedAgentLabel?: string; /** MA-P1 committed profile selection. Singular aliases above remain only * for the legacy path and are derived on persistence when this has one row. */ selectedAgents: VcMeetingRuntimeSelectedAgent[]; /** undefined = no staged profile edit; [] = explicitly staged listen-only. */ consumerPendingProfileIds?: string[]; consumerMemberStates: Record; /** A restored profile meeting must move every current projection to the * current daemon boot as one barrier before any member mutates or delivers. */ consumerProfileOwnerBootReady?: boolean; consumerPaused?: boolean; textOutputPolicy: VcMeetingOutputPolicy; voiceOutputPolicy: VcMeetingOutputPolicy; syncIntervalMs?: number; consumerSelectionExpiresAt?: number; consumerSelectionNonce?: string; consumerCardMessageId?: string; consumerSelectionTimer?: ReturnType; consumerSelectionApplying?: boolean; consumerSelectionPromise?: Promise; consumerClosingRequested?: boolean; pendingOutputRequests: Partial>; outputSubmitPromises?: Partial>>; /** Canonical listener feed entries waiting for the selected member. Each * item receives one ingestSeq before any per-member filtering/deliverySeq. */ consumerPendingItems: VcMeetingCanonicalFeedItem[]; /** Exact semantic envelope retained while the receiver owns the stream * head. The metadata-only hub store is authoritative across restarts. */ consumerFrozenDelivery?: { request: VcMeetingDeliveryRequest; deliveryKey: string; inputHash: string; }; consumerOverflowNotified?: boolean; consumerMembershipPausePromise?: Promise; consumerRecoveryCardRequired?: boolean; consumerRecoveryCardPromise?: Promise; consumerProfileRecoveryCardSent?: boolean; /** Active-session restore can be blocked by an unrecoverable frozen body. * This card is a listener-authorized exit: retry catch-up, or retire the old * epoch and resume the same agent from-now. */ consumerActiveRecoveryNonce?: string; consumerActiveRecoveryCardMessageId?: string; consumerActiveRecoveryCardRequired?: boolean; consumerActiveRecoveryCardPromise?: Promise; consumerActiveRecoveryApplying?: boolean; consumerRestoreSelectionCardRequired?: boolean; consumerRestoreSelectionCardPromise?: Promise; consumerClosePhase?: 'data_closing' | 'finalizing'; consumerFinalizationDeadlineAt?: number; consumerCloseResolutionDeadlineAt?: number; /** Closing sessions restored after daemon restart must rehydrate raw bodies * from the bounded VC event source before a frozen hash-only envelope can be * replayed exactly. */ consumerRestoreCatchUpRequired?: boolean; consumerRecoveryGapNotified?: boolean; /** De-dupes the owner DM sent when a meeting invite fails to join. */ inviteFailureNotified?: boolean; consumerRecoveryGap?: VcMeetingDeliveryGap; consumerTranscriptRevisions: Record; consumerLastInjectedAtMs?: number; consumerFullInstructionSent?: boolean; consumerPendingChoice?: { mode: 'agent'; agentAppId: string; } | { mode: 'listenOnly'; }; consumerPendingIntervalMs?: number; /** Authenticated operator context submitted with the current profile form. * Staged only: activation freezes it into each newly joined member's trusted * instruction snapshot, so it never mutates the reusable preset. */ consumerPendingActivationContext?: string; consumerInjectTimer?: ReturnType; consumerInjectPromise?: Promise; actorNamesByOpenId: Record; actorNamesByUnionId: Record; actorUnionIdsByOpenId: Record; actorOpenIdsByUnionId: Record; temporaryInstructionOpenIds: Record; temporaryInstructionUnionIds: Record; preparationMeetingNo?: string; qaMode?: VcMeetingPreparationQaMode; qaAgentAppId?: string; qaRecentOutputHashes: string[]; qaQueueTail?: Promise; qaPendingCount: number; }; type VcMeetingConsumerMemberVolatileState = { frozenDelivery?: { request: VcMeetingDeliveryRequest; deliveryKey: string; inputHash: string; }; injectPromise?: Promise; lastInjectedAtMs?: number; fullInstructionSent?: boolean; overflowNotified?: boolean; restoreBlocked?: boolean; /** Member-scoped terminal replacement for bodies that remained unassigned * through the close recovery horizon. Once frozen, the durable assignment * itself carries this gap across later restarts. */ recoveryGap?: VcMeetingDeliveryGap; activeRecoveryNonce?: string; activeRecoveryCardMessageId?: string; activeRecoveryApplying?: boolean; }; type VcMeetingListenerFlushResult = { ok: boolean; sent: number; error?: string; }; type VcMeetingStartResult = { ok: true; meeting: VcMeetingPushContext['meeting']; listenerChatId: string; key: string; } | { ok: false; meeting: VcMeetingPushContext['meeting']; error: string; key: string; }; type VcMeetingConsumerInjectResult = { ok: boolean; injected: number; error?: string; }; type VcMeetingOutputChannel = 'text' | 'voice'; type VcMeetingOutputDecision = 'approve_voice' | 'allow_voice_and_approve' | 'send_text' | 'allow_text_and_send' | 'reject'; type VcMeetingOutputSubmitResult = { ok: true; status: 'sent' | 'pending'; requestId?: string; merged?: boolean; } | { ok: false; error: string; }; type VcMeetingOutputTextSender = (session: VcMeetingDaemonSession, req: VcMeetingPendingOutputRequest) => Promise; type VcMeetingPendingOutputRequest = { id: string; channel: VcMeetingOutputChannel; nonce: string; agentAppId: string; content: string; contentParts?: string[]; reason?: string; reasonParts?: string[]; fallbackText?: string; fallbackTextParts?: string[]; createdAt: number; expiresAt: number; cardMessageId?: string; applying?: boolean; timer?: ReturnType; /** Present only for the durable MA-P0 action-gate path. */ managedAction?: { listenerAppId: string; meetingId: string; actionId: string; inputHash: string; providerKey: string; }; }; export type VcMeetingJoinProfileResult = { ok: true; profile: string; created: boolean; } | { ok: false; reason: 'no_profile' | 'missing_secret' | 'add_failed'; profile?: string; error: string; }; export declare function noteTurnReceived(ds: DaemonSession, triggerMessageId: string, _prompt?: string, _sender?: { name?: string; }, _turnId?: string, receivedReactionEmoji?: string): Promise; declare function sessionReply(anchor: string, content: string, msgType?: string, larkAppId?: string, turnId?: string, opts?: WorkerSessionReplyOptions): Promise; export declare const __testOnly_sessionReply: typeof sessionReply; export declare const __testOnly_activeSessions: Map; export declare function enforceMessageQuotaForCliInput(larkAppId: string, chatId: string, senderOpenId: string | undefined, messageId: string, anchor: string, senderUnionId?: string, memberUnionId?: string, chatType?: 'group' | 'p2p', botSender?: boolean, opts?: { listenerAuthorized?: boolean; skipCharge?: boolean; alreadyAuthorizedAndCharged?: boolean; }): Promise; export declare function grantRestrictedCommandText(larkAppId: string, chatId: string | undefined, senderOpenId: string | undefined, cmd: string): string | undefined; export declare function grantRestrictedSlashCommandText(larkAppId: string, chatId: string | undefined, senderOpenId: string | undefined, cmd: string): string | undefined; declare function prewarmDocCommentSession(ds: DaemonSession, sub: DocSubscription): Promise; export declare const __testOnly_prewarmDocCommentSession: typeof prewarmDocCommentSession; /** * P1 revalidation for the Codex-notifier「继续处理」takeover. After the dynamic * import + AbortSignal await inside adoptCodexNotifierEvent, a concurrent path may * have moved the ground: a `/relay` transfer opened its input gate (the starter * would then fail-closed with a bare `return` our no-op sessionReply swallows, * and the outer handler would render a bogus green「已接管」over a half-rewritten * session), or the session was /close'd / swapped / re-created under this key. * Returns true when the takeover MUST abort before mutating any state — so the * caller can throw with zero side effects and no buffered input dropped. */ declare function notifierAdoptStaleOrTransferring(ds: DaemonSession, sessions: Map, activeKey: string, genSessionId: string): boolean; /** * P2 predicate: would clearPendingRepoStateForNotifierAdopt drop user input that * was accepted but not yet delivered to the CLI? Two DISTINCT windows with * different gating — do NOT collapse them: * * (1) repo-select pending buffer — ONLY meaningful while pendingRepo===true. * pendingPrompt / pendingCodexAppText / pendingAttachments / pendingFollowUps * are the message stashed for the deferred post-repo-selection fork. But the * pinned/defaultWorkingDir immediate-launch path (daemon.ts ~16031/17325) * ALSO seeds these same mirror fields, then forks and clears only * pendingTurnId (~16097/17387) — leaving pendingPrompt/etc behind on an * already-delivered session with pendingRepo=false. Gating on pendingRepo * here is what stops us from warning "message not delivered, resend" about * a prompt the worker already ran (which would make the user re-run a * non-idempotent command). * * (2) just-committed launch window — INDEPENDENT of pendingRepo. commitRepoSelection * set pendingRepo=false and forked, but pendingRawInput / pendingFollowUpInput * still wait on the new worker's prompt_ready to actually send. These are the * only fields that legitimately represent undelivered input while * pendingRepo=false, so they are checked unconditionally. (The immediate-launch * path never populates them — it forks with the prompt as a direct argument.) */ declare function notifierAdoptWouldDropInput(ds: DaemonSession): boolean; /** * Fully retire an in-memory repo-select placeholder when the Codex-notifier * 「继续处理」callback takes over the DM session. The legacy inline clear only * reset six fields and left `repoCardMessageId`, `pendingFollowUps`, * `pendingRepoCommitInFlight`, `worktreeCreating`, attachments, mentions, raw * input, etc. behind — a stale repo-card click or a late auto-worktree * completion could then act on the just-adopted session. Clear the whole * pending-repo surface so no residue survives the takeover. Buffered/undelivered * user input (repo-select buffer AND the pendingRawInput/pendingFollowUpInput a * just-committed session is still waiting to send on prompt_ready) is dropped * here — the drop is terminal (never rolled back, since concurrent background * tasks may already have observed pendingRepo=false); the caller tells the user * their input was cancelled on both the success and failure card. */ declare function clearPendingRepoStateForNotifierAdopt(ds: DaemonSession): void; declare function adoptCodexNotifierEvent(larkAppId: string, event: CodexTaskCompletedEvent, cardMessageId: string, ownerOpenId: string, signal: AbortSignal, deadlineAt: number): Promise>; export declare const __testOnly_notifierAdoptStaleOrTransferring: typeof notifierAdoptStaleOrTransferring; export declare const __testOnly_notifierAdoptWouldDropInput: typeof notifierAdoptWouldDropInput; export declare const __testOnly_clearPendingRepoStateForNotifierAdopt: typeof clearPendingRepoStateForNotifierAdopt; export declare const __testOnly_adoptCodexNotifierEvent: typeof adoptCodexNotifierEvent; /** * 按 (cliId, cliSessionId) 在活跃会话里反查 CLI 原生会话绑定。 * * 反查 identity 必须是组合键:OpenCode V1→V2 迁移会原样保留 ses_* id(同一 * id 同时存在于 V1 session 与 V2 session_v2),只按 cliSessionId 匹配会把 * V1 会话(cliId=opencode)当成唯一 hit,错投到 V1 话题。本查询只服务 * opencode2 共享托管 service,命中必须恰好一个:0 个 → miss,≥2 个 * (两个话题/机器人并发导入同一外部会话的重复绑定)→ conflict。 */ export declare function matchCliSession(activeSessions: Iterable, cliId: string, cliSessionId: string): { kind: 'hit'; session: DaemonSession; } | { kind: 'miss'; } | { kind: 'conflict'; }; declare function handleVcMeetingCardAction(data: CardActionData, larkAppId: string): Promise; declare function handleVcMeetingPush(ctx: VcMeetingPushContext): Promise; export declare const __vcMeetingAgentTest: { handlePush: typeof handleVcMeetingPush; handleCardAction: typeof handleVcMeetingCardAction; receiverSessionSnapshot: (sessionId: string) => { sessionId: string; larkAppId: string; chatId: string; rootMessageId: string; scope: "chat" | "thread"; sandbox: boolean | undefined; backendType: import("./adapters/backend/types.js").BackendType | undefined; vcMeetingReceiver: { listenerAppId: string; meetingId: string; memberId: string; memberEpoch: number; } | undefined; activeKey: string; ordinaryChatKey: string; } | undefined; sessionCount: () => number; hasSession: (larkAppId: string, meetingId: string) => boolean; sessionState: (larkAppId: string, meetingId: string) => VcMeetingSessionState | undefined; flushListener: (larkAppId: string, meetingId: string) => Promise; injectConsumer: (larkAppId: string, meetingId: string, opts?: { final?: boolean; force?: boolean; }) => Promise; catchUpConsumerBeforeTurn: (larkAppId: string, listenerChatId: string) => Promise; routeConsumerBeforeTurnForTest: (larkAppId: string, listenerChatId: string, content?: string) => Promise<{ result: void | { anchorOverride?: string; block?: boolean; }; ctx: RoutingContext; }>; handleTemporaryAuthCommand: (input: { larkAppId: string; chatId: string; anchor?: string; commandContent: string; mentions?: LarkMessage["mentions"]; senderOpenId?: string; senderUnionId?: string; }) => Promise; submitOutput: (input: { larkAppId: string; meetingId: string; channel: VcMeetingOutputChannel; content: string; reason?: string; fallbackText?: string; }) => Promise; submitManagedOutput: (input: { agentAppId: string; receiverSessionId: string; stableTurnId: string; dispatchAttempt: number; channel: VcMeetingOutputChannel; content: string; reason?: string; fallbackText?: string; }) => Promise<{ status: number; body: unknown; }>; submitManagedImOutput: (input: { origin: NonNullable; channel: VcMeetingOutputChannel; content: string; reason?: string; fallbackText?: string; }) => Promise<{ status: number; body: unknown; }>; reviewOutput: (input: { larkAppId: string; meetingId: string; requestId: string; nonce: string; decision: VcMeetingOutputDecision; operatorOpenId?: string; }) => Promise; reconcileManagedActions: (listenerAppId: string) => Promise; pendingOutput: (larkAppId: string, meetingId: string, channel: VcMeetingOutputChannel) => { id: string; channel: VcMeetingOutputChannel; nonce: string; agentAppId: string; content: string; contentParts?: string[]; reason?: string; reasonParts?: string[]; fallbackText?: string; fallbackTextParts?: string[]; createdAt: number; expiresAt: number; cardMessageId?: string; applying?: boolean; /** Present only for the durable MA-P0 action-gate path. */ managedAction?: { listenerAppId: string; meetingId: string; actionId: string; inputHash: string; providerKey: string; }; } | undefined; dropPendingOutputForTest: (larkAppId: string, meetingId: string, channel: VcMeetingOutputChannel) => void; setOutputTextSenderForTest: (sender?: VcMeetingOutputTextSender) => void; setOutputTextAvailableForTest: (available?: boolean) => void; setOutputPolicyForTest: (larkAppId: string, meetingId: string, channel: VcMeetingOutputChannel, policy: VcMeetingOutputPolicy) => void; setGlobalVcMeetingAgentEnabledForTest: (enabled?: boolean) => void; setGlobalVcMeetingListenerBotAppIdForTest: (appId?: string | null) => void; setCrossAppLocalReceiverForTest: (enabled: boolean) => void; setSelfDaemonLarkAppIdForTest: (larkAppId?: string) => void; setConsumerPendingItemLimitForTest: (limit?: number) => void; setConsumerDeliveryCapsForTest: (caps?: { maxItems: number; maxRenderedChars: number; }) => void; setConsumerCloseTimingForTest: (timing?: { retryMs: number; horizonMs: number; slowRetryMs: number; resolutionGraceMs?: number; }) => void; consumerPendingCount: (larkAppId: string, meetingId: string) => number; closingConsumerCount: () => number; closingConsumerFrozenRequest: (larkAppId: string, meetingId: string) => VcMeetingDeliveryRequest | undefined; consumerFrozenRequest: (larkAppId: string, meetingId: string) => VcMeetingDeliveryRequest | undefined; beginCloseIntentForTest: (larkAppId: string, meetingId: string) => number | undefined; waitQaQueue: (larkAppId: string, meetingId: string) => Promise; restoreRuntimeSessions: (larkAppId: string) => void; /** 测试用:读某个 bot 的有效 VC 配置(含共享目录绑定 + larkCliProfile 默认值)。 */ effectiveConfig: (larkAppId: string) => VcMeetingAgentConfig | undefined; reset: () => void; }; /** * Resolve the pinned working dir for a brand-new topic via the layered lookup: * 1) this bot's OWN oncall binding (per-bot: another bot's binding never pins * this bot — cross-bot dir alignment is handled by layer 4 inherit-peer) * 2) this bot's defaultOncall — auto-binds a brand-new chat when the flag is on * (this WRITES state, so it must run identically on every spawn path) * 3) when auto-worktree is enabled, a sibling session's workingDir if * "bot@bot 同目录拉起" is on. Reusing the already-running collaborator's * exact dir must win over creating a second, unrelated worktree. * 4) this bot's OWN effective `defaultWorkingDir` (legacy `defaultWorkingDir`, * or the `defaultOncall.workingDir` all-sessions fallback — see * {@link resolveBotDefaultWorkingDir}). An explicit per-bot config is the * bot's own intent and normally OUTRANKS cross-bot inheritance. The sole * exception is the auto-worktree conflict described in layer 3. * 5) a sibling session's workingDir (cross-bot / chat-scope inheritance) — * last-resort convenience so a freshly @mentioned collaborator bot with no * dir of its own follows the topic instead of bouncing through a repo card. * Returns the dir plus the oncall / inherited source so callers can log the reason. * Shared by the normal spawn path and the first-message `/repo` command branch so * both honor the defaultOncall auto-bind the same way. */ declare function resolvePinnedWorkingDir(ctx: { scope: 'thread' | 'chat'; anchor: string; chatId: string; chatType: 'group' | 'p2p'; larkAppId: string; listenerWorkingDir?: string; }): Promise<{ pinnedWorkingDir: string | undefined; oncallEntry: OncallChat | undefined; inheritedFrom: import("./core/inherit-peer.js").InheritedPeer | null; pinnedFromBotDefault: boolean; }>; export declare const __testOnly_resolvePinnedWorkingDir: typeof resolvePinnedWorkingDir; export declare const __testOnly_handleNewTopic: (data: any, ctx: RoutingContext) => Promise; export declare const __testOnly_handleThreadReply: (data: any, ctx: RoutingContext) => Promise; export declare const __testOnly_computeCodexAppSteerable: typeof computeCodexAppSteerable; type NewDaemonSessionClaim = { accepted: true; key: string; owner: DaemonSession; } | { accepted: false; key: string; owner: DaemonSession; reason: 'existing_owner'; closedIncomingSessionId: string; } | { accepted: false; key: string; owner: DaemonSession; reason: 'both_pending' | 'incoming_pending'; preservedIncomingSessionId: string; }; export declare const __testOnly_claimNewDaemonSession: (map: Map, incoming: DaemonSession) => Promise; /** Fence an arrival before any sender/prompt await. Besides reserving durable * FIFO order, keep the opening route owned until this reservation either lands * in the durable tail or fails. */ declare function reserveAsyncQueuedActivationTailAdmission(ds: DaemonSession): QueuedActivationTailReservation; /** Complete one async reservation. If the predecessor ACK arrived during its * await, the final settler performs the deferred handoff immediately. */ declare function settleAsyncQueuedActivationTailAdmission(ds: DaemonSession): void; export declare const __testOnly_reserveAsyncQueuedActivationTailAdmission: typeof reserveAsyncQueuedActivationTailAdmission; export declare const __testOnly_settleAsyncQueuedActivationTailAdmission: typeof settleAsyncQueuedActivationTailAdmission; /** Release a queued activation's runtime route reservation only after the * worker ACKs actual adapter submission. Turns that arrived meanwhile are sent * as one ordered follow-up, never allowed to overtake the opening item. */ declare function releaseQueuedActivationReservation(ds: DaemonSession, acknowledgedToken?: string): boolean; export declare const __testOnly_releaseQueuedActivationReservation: typeof releaseQueuedActivationReservation; /** Exact production callback passed to initWorkerPool. Keep the boolean return: * false means the worker did not accept the staged FIFO head and the pool must * retry instead of silently dropping the activation reservation. */ declare function onQueuedActivationSubmitted(ds: DaemonSession, activationToken?: string): boolean; export declare const __testOnly_onQueuedActivationSubmitted: typeof onQueuedActivationSubmitted; /** Preserve the established mid-session passthrough semantics when a cold-start * scratch loses its registration race to a concurrently-created real session. */ declare function deliverPassthroughToExistingSession(ds: DaemonSession, cmd: string, commandContent: string, anchor: string, larkAppId: string, turn: { messageId: string; replyRootId?: string; senderOpenId?: string; senderIsBot: boolean; substitute: boolean; /** raw input 已写入 worker 后回调(worker 不在线的拒绝分支不触发),供 ingress * 调用方打接纳标——其后同步收尾(落盘/事件派发)抛错不得再诱导重发,否则 * /compact 这类非幂等 passthrough 会被重发重复执行。 */ onDelivered?: () => void; }): void; export declare const __testOnly_deliverPassthroughToExistingSession: typeof deliverPassthroughToExistingSession; /** * Codex App steer authorization (Blocking 1, decision A) from INBOUND SOURCE * FACTS only — the single source of truth shared by both admission twins * (handleNewTopicAdmitted + handleThreadReplyAdmitted), so a plain-human turn is * authorized identically whether it opens a new topic or continues one (R6/R7-B1). * * FAIL-CLOSED by construction (R7-B1): authorization requires either a POSITIVE * `humanSender` (senderType === 'user' AND not a known peer bot), or a known peer * bot carrying the explicit `@steer` directive. Both lanes must also have no * control-rewrite / dedicated-receiver signal. Anything not positively matched * by one of those lanes stays forced-serial. */ declare function computeCodexAppSteerable(facts: { humanSender: boolean; adopted: boolean; isForeignBot: boolean; isBotSenderType: boolean; explicitBotSteer: boolean; substituteTrigger: boolean; controlRewrite: boolean; messageListener: boolean; vcMeetingReceiver: boolean; vcMeetingImTurnOrigin: boolean; }): boolean; export declare function __testOnly_setAutoStartJoinReadyMaxWaitMs(value?: number): void; /** * 主动开工 — 场景①: the bot was added to a chat. Auto-start a session when * (1) the bot opted in via `autoStartOnGroupJoin`, and (2) at least one of its * allowedUsers is a member of the chat (D7). Working dir per D6: the bot's * default working dir, else degrade to the repo-selection card. The first-turn * prompt is the configured prompt, or empty (the role/identity envelope still * makes it a non-empty CLI turn — the bot reads the group context itself, D8). * * Scope is mode-aware: a 普通群 keeps a chat-scope session anchored at chatId. * When its reply mode is shared, a top-level seed supplies the visible topic * root while every turn still reuses that chat-scope session. A 话题群 has no * thread to attach to yet, so it also seeds a fresh topic, but runs a * thread-scope session anchored at that seed — otherwise a chat-scope session * in a 话题群 is the known stale-session bug (every reply would wrap into a new * topic, and later messages route elsewhere). */ declare function handleBotAdded(chatId: string, operatorOpenId: string | undefined, larkAppId: string, opts?: { /** * 强制开工并指定首轮 prompt(Issue Board 领取用)。 * * 复用这里而不是另写一条建会话路径:下面那套 CAS 注册 + ready 屏障 + 接管检测是 * 有状态的竞态协议,平行实现迟早对不齐。带上它就绕过 `autoStartOnGroupJoin` 开关 * ——领取是人明确点出来的动作,不该再受"主动开工"这个全局偏好左右。 * D7(群里必须有 allowedUser)等其余前置检查一律照常跑。 */ forcePrompt?: string; }): Promise; export declare const __testOnly_handleBotAdded: typeof handleBotAdded; declare function runClaimedDocCommentTurn(claimKey: string, work: () => Promise, onOwnerFailure?: (error: unknown) => Promise): Promise; export declare const __testOnly_runClaimedDocCommentTurn: typeof runClaimedDocCommentTurn; export declare function __testOnly_resetDocCommentClaims(): void; export declare const __testOnly_handleDocComment: (ctx: DocCommentContext) => Promise; /** Preserve accepted/pending work when a normal group is converted to topic * mode. The converted event is routed to a new thread key, so keeping the old * chat owner does not block the new session; it only keeps the durable owner * addressable for completion or explicit close. */ declare function handleChatModeConverted(chatId: string, larkAppId: string): boolean; export declare const __testOnly_handleChatModeConverted: typeof handleChatModeConverted; export declare function startDaemon(botIndex?: number): Promise; //# sourceMappingURL=daemon.d.ts.map