/** * Deliberate silence — the shared half of the `no_reply` verb. * * A turn that calls `no_reply` must end without posting anything: the trigger * gate is message-driven, so no message means no other agent is woken. Every * host wires its own teardown, but they all ask the same question here. */ /** * The model-facing closure sentence, re-exported so a runtime that answers * `no_reply` locally reads it from the same definition the server sends. It is * DEFINED in @canonmsg/backend-contracts because that is the only package both * the server and the hosts depend on. */ export { NO_REPLY_ACK_NOTE } from '@canonmsg/backend-contracts'; import { type CanonVerbWireRequest } from '@canonmsg/backend-contracts'; /** The one client capability the outcome report needs. */ export interface NoReplyReportClient { executeVerbWire(wire: CanonVerbWireRequest): Promise; } /** * What `reportNoReplyOutcome` puts on the wire in place of any caller-supplied * reason text. The server stores only `hasReason` and discards the text, so * transmitting the model- or handler-authored sentence would be pure exposure: * the sentinel preserves the server's `hasReason` signal without moving the * text. The reason a binding was handed never leaves its process. */ export declare const NO_REPLY_REASON_SENTINEL = "given"; /** * Report a locally-answered `no_reply` to the server, fire-and-forget. * * Four of the five runtimes answer the silence tool inside their own process * and never reach `POST /agent/verbs/no_reply`, so Canon's durable record of * deliberate silence (`/runtime-silence/{convoId}/{agentId}`) would otherwise * cover Claude turns only. This is telemetry, not behavior: the silence flag * is already set locally before this is called, so a failed or unreachable * report degrades to exactly the pre-report behavior — never to a posted * message and never to a failed turn. * * `params.reason` is read only for its presence: the wire carries * `NO_REPLY_REASON_SENTINEL` in its place, never the caller's text. * * The Claude host must NOT call this helper: its binding already POSTs the * verb via agent-tools, so calling both would double-record the silence. */ export declare function reportNoReplyOutcome(client: NoReplyReportClient, params: { conversationId: string; messageId?: string; reason?: string; now?: number; }): void; /** * Owner ruling 2026-07-31 (docs/design/group-behavior-plan.md), as * implemented. `strict` (default): a no_reply call suppresses the final * unconditionally. `advisory`: non-empty final text overrides the sentinel. * ONE switch, so the ruling can be flipped without touching five hosts. */ export type SilentTurnPrecedence = 'strict' | 'advisory'; export declare const DEFAULT_SILENT_TURN_PRECEDENCE: SilentTurnPrecedence; export declare function resolveSilentTurnDelivery(input: { silenced: boolean; finalText: string | null | undefined; precedence?: SilentTurnPrecedence; }): 'deliver' | 'suppress'; /** * Whether SILENCE is what withholds this turn's own output — the single * question every consumer of the ruling asks, so they cannot disagree. * * `resolveSilentTurnDelivery` answers "is there anything to deliver", which * also returns `suppress` for an ordinary turn that simply produced no text. * A caller deciding what ELSE a silenced turn withholds — its workspace * artifacts, its plan card — needs the narrower question, and deriving it by * hand (`silenced && ...`) is how a second, independent reading of * `DEFAULT_SILENT_TURN_PRECEDENCE` creeps in. Flipping that constant to * `advisory` must move a turn's text and its artifacts together: a turn that * talks about the chart it made and then ships no chart is worse than either * consistent behaviour. * * `finalText` is the MODEL's own text for the turn — never Canon's failure * notice, which is a host diagnostic rather than something the model said, and * which is delivered whether the turn went silent or not. */ export declare function isSilentTurnSuppressed(input: { silenced: boolean; finalText: string | null | undefined; precedence?: SilentTurnPrecedence; }): boolean;