/** * What an agent's message queue needs to know about a message beyond its * text: whether the owner wrote it (it goes ahead of system traffic), which * conversation and thread it belongs to (for a colleague's message or a * reminder, a reply there after it was queued means it was answered — never * for the owner's own message), the owner message it carries (so a second * copy is recognised), and which earlier reminder it replaces. * * Callers that know this (the chat dispatcher for an owner's message, the * decision service for an owner's answer, the owner-message watchdog for its * reminder, the open-items service for a "promised work is ready" reminder) * run their delivery inside {@link withQueueMeta}. If the agent is busy and * the delivery path queues the message, {@link SubAgentMessageQueue.enqueue} * picks the meta up from the async context — no delivery signature changes. * * Case that motivated it (2026-10-05, #education, D-270): the owner's answer * to Atlas's decision card was queued at position 7 behind stale reminders, * handed over one per idle moment oldest-first, and reached Atlas after * ~18 minutes; a duplicate of an owner message Atlas had already answered sat * in front of it. * * @module services/messaging/queue-priority */ /** Where a queued message's answer belongs. */ export interface QueueConversation { /** chat-v2 channel the message was recorded in */ chatChannelId?: string; /** chat-v2 thread the answer belongs in (huddles) */ chatThreadId?: string; /** Slack channel / DM */ slackChannelId?: string; /** Slack thread root */ threadTs?: string; } /** Metadata stored with a queued message. */ export interface QueueMessageMeta { /** The owner wrote it (or it carries the owner's words / answer): goes ahead of everything else */ owner?: boolean; /** Identity of the owner message it carries (`slack::`, `chat::`, `decision:`); a later queued copy is a duplicate */ ref?: string; /** Conversation (and thread) the answer belongs in */ where?: QueueConversation; /** A newer queued message with the same key replaces an older one (a reminder about the same promise) */ supersedeKey?: string; /** * A harness reminder that carries the owner's words (the watchdog's nudge): * owner priority, but — unlike the owner's own message — dropped once the * agent has answered in its thread. */ reminder?: boolean; } /** * Run a delivery with queue metadata for one message to one agent. If the * delivery queues that message for that agent, the queued copy carries the * metadata. * * @param session - The agent the delivery is for * @param data - The exact text being delivered * @param meta - The metadata * @param fn - The delivery * @returns What `fn` returns */ export declare function withQueueMeta(session: string, data: string, meta: QueueMessageMeta, fn: () => T): T; /** * The metadata of the delivery running now, if it is this message for this agent. * * @param session - The agent a message is being queued for * @param data - The message being queued * @returns The metadata, or undefined */ export declare function currentQueueMeta(session: string, data: string): QueueMessageMeta | undefined; /** * The agents' recent posts, by agent. Fed by the chat-v2 turn stream and the * `reply` endpoint; read by the queue when it flushes. */ export declare class AgentPostLog { private static instance; private readonly posts; /** @returns The process-wide log */ static getInstance(): AgentPostLog; /** Reset (tests). */ static resetInstance(): void; /** * Record that an agent posted (a real answer, not an interim "working on it"). * * @param session - The agent * @param where - Where it posted * @param at - When (default now) */ note(session: string, where: QueueConversation, at?: number): void; /** * Whether the agent posted in a conversation (and thread) after a moment. * * Matched by thread only (see {@link sameConversation}). * * @param session - The agent * @param where - The queued message's conversation * @param since - When it was queued * @returns True when the agent spoke there after `since` */ postedSince(session: string, where: QueueConversation, since: number): boolean; /** * Whether the agent answered in a conversation after it was explicitly * shown a message from there (an owner message surfaced at a tool * boundary). Unlike {@link postedSince}, a top-level DM counts: the agent * was told to answer that message first, so its next top-level post there * is taken as the answer (see {@link answersConversation}). * * @param session - The agent * @param where - The message's conversation * @param since - When the agent was shown it * @returns True when the agent posted there after `since` */ answeredSince(session: string, where: QueueConversation, since: number): boolean; } /** * Whether a post answers a message the agent was explicitly shown: the same * thread when the message sits in a thread; otherwise a top-level post in the * same chat channel (or Slack channel). A post in some other thread of the * channel does not count. * * @param post - Where the agent posted * @param message - Where the message's answer belongs * @returns True on a match */ export declare function answersConversation(post: QueueConversation, message: QueueConversation): boolean; /** * Whether a post landed in the conversation (and thread) a queued message belongs to. * * @param post - Where the agent posted * @param queued - Where the queued message's answer belongs * @returns True on a match */ export declare function sameConversation(post: QueueConversation, queued: QueueConversation): boolean; /** * Feed a recorded chat-v2 turn to the post log: an agent's real answer (not * an interim "working on it") in a conversation, and the Slack thread it * mirrors to when the turn says so. * * @param dto - The turn (sender, channel, thread, metadata) * @param log - The post log (default: the process-wide one) */ export declare function noteAgentChatTurn(dto: { senderType: string; senderId: string; channelId: string; threadId?: string | null; createdAt?: number; metadata?: Record; }, log?: AgentPostLog): void; //# sourceMappingURL=queue-priority.d.ts.map