/** * Pure decision helpers for `botmux send` (extracted from cmdSend so they can * be unit-tested without process.exit / Lark I/O). * * Two policies live here: * - resolveQuoteTarget: which message a chat-scope send should quote (reply * to), so 普通群 messages render Lark's 引用 chain. Thread-scope and * --top-level never quote. * - validateMentionDecision: the @ hard-gate — every model-initiated reply * must explicitly choose --mention / --mention-back / --no-mention. */ import type { TurnParticipant } from '../types.js'; export interface QuoteTargetArgs { /** session.scope === 'chat' */ isChatScope: boolean; /** --top-level publish mode */ sendTopLevel: boolean; /** --no-quote: force a plain (un-quoted) send */ noQuote: boolean; /** --quote explicit override */ explicitQuote?: string; /** session.quoteTargetId — the latest inbound message this turn responds to */ sessionQuoteTargetId?: string; } /** * Resolve the message id a send should quote, or null for a plain send. * Priority: --quote > session.quoteTargetId. Only chat-scope, non-top-level, * non-`--no-quote` sends quote. */ export declare function resolveQuoteTarget(args: QuoteTargetArgs): string | null; export interface ManagedVcQuoteArgs { managed: boolean; durableDelivery: boolean; explicitImMessageId?: string; explicitQuote?: string; } /** A quote message id is a routing primitive: Lark's reply API derives the * destination chat from that id, not from the separately supplied chat id. * Managed deliveries therefore cannot choose one, while an explicit IM turn * may quote only the exact Lark message frozen in its origin snapshot. */ export declare function managedVcQuoteError(args: ManagedVcQuoteArgs): string | null; /** Managed VC output must stay within botmux-owned message shapes. Even though * ordinary custom cards are scanned for known callback controls, treating an * evolving third-party card schema as an exhaustive privilege boundary is not * safe for meeting-derived (untrusted) model output. */ export declare function managedVcCustomCardError(managed: boolean, customCardRequested: boolean): string | null; export interface ManagedVcSendControlArgs { managed: boolean; sendTopLevel: boolean; overrideChatId?: string; sendInto?: string; attentionRequested: boolean; explicitMentionCount: number; mentionBack: boolean; noMention: boolean; } /** Freeze every managed reply to the listener-thread route and a no-mention * addressing mode. Routing/mention/attention are independent side effects that * are not represented by the primary VC action identity. */ export declare function managedVcSendControlError(args: ManagedVcSendControlArgs): string | null; export interface ManagedVcSendPayloadArgs { managed: boolean; asVoice: boolean; hasBodyText: boolean; imageCount: number; fileCount: number; videoCount: number; containsNativeAtTag: boolean; } /** A dedicated receiver may emit only one botmux-owned text card. Provider * uploads (image/file/video/audio) happen before a Lark message UUID can be * reconciled, so allowing them would give retries or repeated commands an * unledgered resource-creation channel even when the visible message dedupes. */ export declare function managedVcSendPayloadError(args: ManagedVcSendPayloadArgs): string | null; export declare function containsLarkAtTag(content: string): boolean; /** Render model-authored native Lark mention tags inert before placing the * text in a botmux-owned card. Full-width angle brackets are intentional: * unlike an HTML entity, they cannot be decoded and re-interpreted as a * second-pass `` control by the card renderer. */ export declare function neutralizeLarkAtTags(content: string): string; export interface MentionDecisionArgs { /** config.send.requireMentionDecision */ enabled: boolean; /** --top-level publish is exempt from the gate */ sendTopLevel: boolean; /** at least one --mention given */ hasMentionArgs: boolean; /** --mention-back given */ mentionBack: boolean; /** --no-mention given */ noMention: boolean; /** whether the session knows who sent the message being replied to */ hasQuoteTargetSender: boolean; } export interface MentionDecisionResult { ok: boolean; /** present when !ok — the message to print before exit(2) */ error?: string; } /** * Enforce that the model made an explicit @ decision before sending. * Returns ok:false with a context-aware error when no decision was made or * the flags contradict each other. */ export declare function validateMentionDecision(args: MentionDecisionArgs): MentionDecisionResult; export interface MentionBackAmbiguityArgs { /** Session chat type — a p2p DM is inherently 1v1, never ambiguous. */ chatType?: 'group' | 'p2p'; /** Turn-window counterparts (executable open_id candidates; sender + @-mentions * across folded/type-ahead messages, self bot already excluded, deduped). */ participants: TurnParticipant[]; /** True when the window may be under-counted (an unresolved non-open_id @, a * pruned sibling, or no window at all). Forces ambiguous regardless of count * so the model must make an explicit decision. */ incomplete?: boolean; } export interface MentionBackAmbiguityResult { /** True when --mention-back is ambiguous and must be replaced by an explicit * --mention / --no-mention (2+ distinct counterparts, or an incomplete * window that could hide additional counterparts). */ ambiguous: boolean; /** The known distinct counterparts to offer as explicit --mention candidates. * May be shorter than the true set when `incomplete` is true. */ candidates: TurnParticipant[]; /** Propagated from args: the candidate list is known-incomplete. */ incomplete: boolean; } /** * Is `--mention-back` ambiguous for THIS turn? --mention-back means "@ back the * one counterpart who triggered this turn". That is unambiguous only when the * turn's window provably had a single counterpart. It becomes ambiguous when: * - two or more distinct people/bots took part (a human + a peer bot, two * humans, the triggerer plus someone they @-ed, a type-ahead follow-up from * a third party, …); OR * - the window is INCOMPLETE (an @ we couldn't resolve to an open_id, a * pruned sibling, or no window record at all) — a hidden counterpart may * exist, so we must not assume the lone visible one is the only target. * In either case we ask the model to pick an explicit `--mention ` * (from the known candidates) or `--no-mention`, rather than auto-@-ing. * * NOT symmetric on human-vs-bot: a bot→bot handoff in a provably 1v1 window * stays unambiguous (allowed); a lone human likewise. p2p short-circuits to * not-ambiguous. Fail-safe: uncertainty always resolves to ambiguous. */ export declare function mentionBackAmbiguity(args: MentionBackAmbiguityArgs): MentionBackAmbiguityResult; /** Render the blocked-`--mention-back` error: explains the ambiguity and lists * every KNOWN candidate's open_id + name + person/bot/unknown so the model can * `--mention ` the right one instead of guessing. When the window is * incomplete, says so (there may be participants without a listable open_id). */ export declare function mentionBackAmbiguityError(candidates: TurnParticipant[], incomplete?: boolean): string; /** * Agent "raise-hand" attention flag for `botmux send --attention[=kind]`. * * `--attention` → boolean raise, kind defaults to 'blocked'. * `--attention=` → raise with an explicit kind. * Unknown kinds fall back to 'blocked' (lenient: never fail the send over a * typo'd category — the reason text carries the real meaning). * * MUST be parsed here, not via argValue('--attention'), because a bare * `--attention "我卡住了"` would otherwise eat the message as the flag value. * Callers must also add '--attention' to positionals()' booleanFlags so the * body isn't swallowed. */ export declare const ATTENTION_KINDS: readonly ["authz", "decision", "blocked", "help"]; export declare function parseAttentionFlag(args: string[]): { requested: boolean; kind: string; }; export interface AttentionUsageArgs { requested: boolean; /** --top-level */ sendTopLevel: boolean; /** --chat-id */ overrideChatId?: string; /** --into */ sendInto?: string; /** --voice */ asVoice?: boolean; /** message body has non-empty text */ hasText: boolean; } /** * Guard `--attention` usage. Returns an error string, or null if OK. * `--attention` only makes sense replying into the CURRENT session: clear-on-reply * binds to this session's anchor, so routing the message elsewhere (--top-level / * --chat-id / --into) would leave the needs-you signal un-clearable. And the * dashboard needs a text reason, so an image/file-only send can't raise. */ export declare function attentionUsageError(args: AttentionUsageArgs): string | null; //# sourceMappingURL=send-policy.d.ts.map