/** * @license * Copyright 2026 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ import { type PeerDeliveryStatus, type PeerDropReason, type PeerMessagePriority, type PeerModeClass } from './frames.js'; /** * Sends remembered for their receipts. The oldest is forgotten first, so an * unanswered send stops being tracked after this many later ones. */ export declare const MAX_TRACKED_SENDS = 200; /** * Received message ids remembered with the answer they got, so a sender * that retries an id is told the same thing again rather than having the * message handled twice. */ export declare const MAX_REMEMBERED_MESSAGES = 200; export interface PeerEndpointOptions { /** * What sessions call this program. Flattened to one line and cut to 40 * characters; it need not be unique. */ name: string; /** What registered, for listings. Default `external`. */ kind?: string; /** The working directory to record. Default `process.cwd()`. */ cwd?: string; /** A stable id for this endpoint. Default: a fresh UUID. */ sessionId?: string; /** Free text recorded as the writer's version. */ version?: string; /** The Qwen home to join. Default `QWEN_HOME`, else `~/.qwen`. */ qwenHome?: string; /** * Bind the inbox here instead of choosing a path. If something live * already answers there, a sibling `-<8 hex>.sock` is bound instead; * {@link PeerEndpoint.ipcPath} is the address actually published. */ socketPath?: string; /** * A controller token minted with `qwen sessions controllers add`. * * Presented only on sends marked `controller: true`. The token works * against every session in the Qwen home, and addresses are resolved from * records any program running as this user can write, so a send presents * it to whichever process's record answers to the address. Mark only the * sends meant to direct a session. * * A controller send is delivered without review unless the receiving * session's own `agents.crossSessionInbound` setting says `hold` or * `refuse` — that outranks the grant. It is for Qwen Code sessions: * another peer endpoint's inbox accepts only its own token, so a * controller send to one is dropped unread. */ controllerToken?: string; /** * Called for each message a session sends here. Without it, the endpoint * answers every message `refused`. */ onMessage?: (message: PeerInboundMessage) => void | Promise; /** Called when a receipt moves one of this endpoint's sends. */ onReceipt?: (receipt: PeerReceipt) => void; /** Called when `onMessage` or `onReceipt` throws or rejects. */ onError?: (error: Error) => void; /** Keep the process running while the endpoint is open. Default true. */ keepAlive?: boolean; /** * Remove the record and the socket when the process exits through * `process.exit` or an empty event loop. Default true. A process killed * by a signal it does not handle leaves both behind; the next Qwen Code * session that lists the directory clears the record once the start * token proves this process is gone. */ closeOnExit?: boolean; } export interface PeerInboundMessage { msgId: string; content: string; priority: PeerMessagePriority; /** The sender's inbox path. */ from?: string; /** The name the sender gave. A claim. */ fromName?: string; /** The review class the sender asserted. A claim. */ fromMode?: PeerModeClass; } export interface PeerReceipt { msgId: string; /** The address the message was sent to, as {@link PeerEndpoint.send} took it. */ address: string; status: PeerDeliveryStatus; previous: PeerDeliveryStatus | 'pending'; /** * Text the recipient wrote for a person, flattened to one line and * bounded like every other label. Never parse it. */ reason?: string; dropReason?: PeerDropReason; } /** A running session this endpoint can send to. */ export interface PeerSessionSummary { sessionId: string; name: string; ref: string; /** The shortest address that selects exactly this session. */ address: string; cwd: string; pid: number; kind: string; startedAt: number; } export interface PeerSendOptions { /** `name`, `name [ref]`, `[ref]` or a bare ref. */ to: string; content: string; priority?: PeerMessagePriority; /** * Present this endpoint's controller token instead of the recipient's own * token; see {@link PeerEndpointOptions.controllerToken}. While marked, an * address that two records answer to — one session id and name published * twice — is ambiguous rather than resolved to the newer record, so a * copied record cannot quietly take the send. */ controller?: boolean; /** * The review class to assert. The recipient compares it with its own and * delivers without review when they match, and nothing authenticates the * claim. A program that is not a coding session has no honest value to * give; leave it out. */ fromMode?: PeerModeClass; } export type PeerSendResult = { kind: 'sent'; msgId: string; peer: PeerSessionSummary; } /** The address names this endpoint. */ | { kind: 'self'; } | { kind: 'not-found'; suggestions: string[]; } /** * The address could mean several sessions; retry with one of these. A * session no address can select on its own is left out, so the list is * empty when nothing tells the candidates apart. */ | { kind: 'ambiguous'; matches: string[]; } | { kind: 'failed'; peer: PeerSessionSummary; /** The errno behind the failure, when there is one. */ code?: string; reason: string; /** * Present when the frame may still be read (the recipient accepted the * connection and stopped reading), so a receipt can still arrive. */ msgId?: string; }; export interface AwaitReceiptOptions { /** Default 30 seconds. */ timeoutMs?: number; /** * Wait past `held` for a decision. Without it, the first receipt of any * kind resolves the wait. */ final?: boolean; } /** Turn a send failure into something a caller can act on. */ export declare function describeSendFailure(error: unknown): string; export declare class PeerEndpoint { readonly sessionId: string; readonly name: string; readonly ref: string; readonly kind: string; readonly cwd: string; /** * The token this endpoint's inbox requires. Anyone who can read the * registry can read it; never print it where a model or a log sees it. */ readonly ipcToken: string; /** Where this endpoint reads and writes session records. */ readonly registryDir: string; readonly startedAt: number; private readonly options; private inbox; private recordFile; private exitHook; private closed; private readonly sends; private readonly answered; private constructor(); /** * Bind an inbox and publish this endpoint's record. * * The inbox is bound first: a record is listed the moment it lands, and * one that named no inbox yet would be a session nobody can reach. If the * record cannot be written, the inbox is closed again and nothing is left * behind. */ static start(options: PeerEndpointOptions): Promise; /** The socket this endpoint listens on. */ get ipcPath(): string; /** The record file this endpoint published. */ get recordPath(): string; /** * The sessions this endpoint can send to right now: live, answering a * dial, and not this endpoint. A session whose `agents.crossSessionMessaging` * is off has no inbox and is not listed. */ list(): Promise; /** * Send `content` to the session `to` names. * * `sent` means the frame was written, not that anyone read it; the * receipts that follow say what became of it (see * {@link PeerEndpoint.awaitReceipt}). */ send(options: PeerSendOptions): Promise; /** * The receipt for a message this endpoint sent. * * Resolves at once when one has already arrived. With `final`, waits past * `held` for the decision. Resolves `undefined` on timeout, when the id is * not one this endpoint is tracking, and when the endpoint closes. */ awaitReceipt(msgId: string, options?: AwaitReceiptOptions): Promise; /** * Stop listening and remove this endpoint's record and socket. Pending * {@link PeerEndpoint.awaitReceipt} calls resolve `undefined`. Safe to * call more than once. */ close(): Promise; private closeSync; private beginClose; private assertOpen; private directory; /** * Everyone but this endpoint. Excluded by session id, so another process * running with the same id — which an endpoint given a stable * `sessionId` can be — is excluded too, rather than messaged as a peer. */ private othersIn; private selfEntry; private track; private forget; private handleFrame; private applyReceipt; private settle; private receive; /** * Best-effort: a sender that has exited, or that gave no address, simply * never hears back. */ private reply; private invoke; private reportError; }