export declare const BRIDGE_NOTHING_TO_SEND_SENTINEL = "BOTMUX_NOTHING_TO_SEND"; /** Superseded token name. Instructions no longer teach it, but the matcher * below still accepts it: during a rollout (and after a restart that restores * sessions spawned before the rename) in-flight turns still carry the old * token in their captured system prompt, and dropping recognition would leak * that literal sentinel line into Lark. The reader stays liberal; only the * instruction surface moved to the new name. */ export declare const BRIDGE_NO_REPLY_SENTINEL_LEGACY = "BOTMUX_NO_REPLY"; /** Remove TraeX/Codex's internal memory-attribution envelope when it is a * complete suffix of an outbound answer. The rollout keeps the block in its * source transcript; only the copy headed to a user-facing surface is cleaned. * * Deliberately conservative: * - the block must begin at a line boundary and be the final non-whitespace * content; * - both required child sections and every closing tag must be present; * - inline/mid-body mentions, malformed blocks, and fenced examples (whose * closing fence follows the XML) are preserved verbatim. */ export declare function stripTrailingOaiMemoryCitation(text: string): string; export declare function isBridgeNothingToSendFinal(finalText: string | undefined): boolean; /** Remove the trailing RUN of standalone sentinel lines (current or legacy * token, mixable) plus interleaved blank lines from `finalText`, returning the * text that should actually reach Lark. * * Stripping the whole trailing run — not just one line — is required to keep the * "literal token never reaches Lark" guarantee: a model can emit the sentinel * more than once (`prose\nTOKEN\nTOKEN`, or a bare `TOKEN\nTOKEN`). A one-line * strip would leave a surviving token to leak, and a bare multi-token final * would be misjudged as "not silence" and post a literal token — a regression * vs the old whole-turn suppression. We peel blank + sentinel lines off the end * until the last remaining line is real prose (or nothing is left). * * - `BOTMUX_NOTHING_TO_SEND` → "" (silence) * - `TOKEN\nTOKEN` / `TOKEN\n\nTOKEN` → "" (silence — all tokens peeled) * - `\n\nBOTMUX_NOTHING_TO_SEND` → `` (the real answer) * - `\nTOKEN\nTOKEN` → `` (both tokens peeled) * - `` → unchanged (token inline) * - `TOKEN\n\n` → unchanged (token not trailing) * When the last non-empty line is NOT a sentinel, the input is returned as-is; * leading content is untouched. Mixed current/legacy tokens in the run all peel. */ export declare function stripTrailingBridgeSentinelLine(finalText: string): string; /** The text a transcript-drain emit path should actually post for `finalText`. * * NON-ADOPT: strip a trailing sentinel line so the literal token never reaches * Lark (prose+sentinel = the "did work, forgot to send" shape → post the prose). * * ADOPT: preserve sentinel text verbatim. The adopted CLI is botmux-unaware, * transcript drain is its only channel to Lark, and it may legitimately output * that literal sentinel as content. Internal memory-citation metadata is still * removed in both modes because it is never user-facing answer content. * * Shared by emitReadyTurns and emitReadyCodexTurns so the per-mode rule lives in * one place and is unit-tested directly. codex-app does not use adopt and drives * its own strip on the deliverable content path. */ export declare function bridgePostText(finalText: string, adoptMode: boolean): string; export interface BridgeSendMarker { sentAtMs: number; messageId?: string; contentLength?: number; /** Bounded, whitespace-compacted copy for dashboard session previews. * The fallback gate still uses contentLength only. */ previewText?: string; } export interface BridgeGateInput { /** When the user message was queued — defines the lower bound of the * send window. Undefined for legacy turns; the gate degrades to * "never suppress" in that case. */ markTimeMs: number | undefined; /** Whether the queue synthesised this turn from a local-terminal event * (no fingerprint match for a Lark message). */ isLocal: boolean | undefined; /** Transcript final text for this turn, when available. Lets structured * send markers distinguish final-answer sends from earlier progress sends. */ finalText?: string; /** Explicit transcript terminal semantics. Undefined preserves the * historical "assistant_final means completed" behavior. */ terminalStatus?: 'completed' | 'failed' | 'ambiguous'; } /** Bounded, newline-preserving copy of a `botmux send` body for dashboard * previews. Unlike the fingerprint normaliser (which collapses ALL whitespace * incl. newlines into single spaces — right for dedup, wrong for display), this * keeps line breaks so the dashboard can render the reply's Markdown structure * (paragraphs / lists / code blocks). Horizontal runs of spaces/tabs within a * line are collapsed and trailing spaces trimmed to keep the stored copy tidy; * blank-line runs are capped at one to bound size without flattening structure. */ export declare function buildBridgeSendPreviewText(content: string): string | undefined; export declare function buildBridgeSendMarkerContent(content: string): Pick | undefined; export declare function shouldSuppressBridgeEmit(turn: BridgeGateInput, nextBoundaryMs: number | undefined, markers: readonly BridgeSendMarker[], adoptMode: boolean): boolean; /** Some structured CLIs can report a durable completed turn while their * terminal event carries no final text. If there was no explicit `botmux send` * in that turn window, silently completing leaves the Lark thread with no * visible outcome. Emit a diagnostic fallback only for that narrow case. * * Scope note (shared path): this gate feeds worker.ts:emitReadyCodexTurns, * which is shared by every structured-bridge CLI (Codex / Traex / Cursor / Pi / * Grok / Hermes / Mtr / Coco). In practice only two of them can produce an * empty-finalText `assistant_final` that reaches here: * - Traex — `task_complete` with an empty `last_agent_message` * (terminalStatus undefined → treated as completed below); * - Grok — `turn_completed` + stop_reason `end_turn` where the post-tool * buffer is empty (terminalStatus 'completed'). * The other six drainers drop empty text before enqueue (`if (!text) continue`), * so the fallback is unreachable for them. * * terminalStatus dependency: `undefined` is admitted as "completed" for * back-compat with legacy assistant_final events. This relies on Traex encoding * a cancel/abort as `turn_aborted` (terminalStatus 'ambiguous', excluded here) * rather than as an empty `task_complete`. If that fork contract ever changes, * a cancelled turn could surface a spurious "completed but empty" diagnostic. * * Marker caveat: `shouldSuppressBridgeEmit` only sees `botmux send` markers, and * detoured sends (`--top-level` / `--into` / `--override-chat`) intentionally * write no marker (cli.ts shouldRecordBridgeMarker). A turn whose only visible * reply went out via such a send therefore still trips this diagnostic; the * user-facing string (i18n `worker.empty_final_completed`) is worded to account * for that case rather than asserting no send happened. */ export declare function shouldEmitEmptyCompletedBridgeFallback(turn: BridgeGateInput, nextBoundaryMs: number | undefined, markers: readonly BridgeSendMarker[], adoptMode: boolean): boolean; /** 结构化失败回合补发可见错误;部分回答不能替代失败原因。 */ export declare function shouldEmitFailedBridgeFallback(turn: BridgeGateInput, nextBoundaryMs: number | undefined, markers: readonly BridgeSendMarker[], adoptMode: boolean): boolean; /** Which fallback content the worker should post for a ready structured turn. * Extracted from emitReadyCodexTurns so the rate-limit skip — which depends * on whether the CLI owns a dedicated structured rate-limit notification * chain (Codex only today) — is testable without a live worker. * * Rate-limit contract: a `codex_rate_limited` terminal is handed to the * CLI's dedicated chain when one exists, so the generic failed fallback is * skipped to avoid double-posting. A CLI WITHOUT the chain (e.g. TRAE) must * fall through to the generic failed fallback — otherwise a 429 turn posts * nothing at all, regressing "misleading but visible" into "silent". */ export type StructuredFallbackKind = 'failed' | 'final' | 'empty_completed' | 'none'; export declare function structuredFallbackKind(turn: BridgeGateInput & { terminalErrorCode?: string; }, nextBoundaryMs: number | undefined, markers: readonly BridgeSendMarker[], adoptMode: boolean, hasDedicatedRateLimitChain: boolean): StructuredFallbackKind; //# sourceMappingURL=bridge-fallback-gate.d.ts.map