import type { Logger } from '../types/logger.js'; import type { Mailbox, MailboxAgentStatus, MailboxMessage } from '../coordination/mailbox-types.js'; import { type HqMailboxEventAction, type HqMailboxSnapshotOptions } from './mailbox-mapper.js'; import { type HqClientCapability, type HqClientIdentity, type HqEventEnvelope, type HqEventType, type HqFleetSnapshotPayload, type HqMailboxEventPayload, type HqMailboxSnapshotPayload, type HqKanbanSnapshotPayload, type HqProjectIdentity, type HqQueuedCommand, type HqRedactionPolicy, type HqSessionEndedPayload, type HqSessionSnapshotPayload, type HqTranscriptAppendPayload } from './protocol.js'; export interface HqSocketLike { readyState: number; send(data: string): void; close(code?: number, reason?: string): void; addEventListener?(type: 'open' | 'close' | 'error' | 'message', listener: (event: unknown) => void): void; removeEventListener?(type: 'open' | 'close' | 'error' | 'message', listener: (event: unknown) => void): void; on?(type: 'open' | 'close' | 'error' | 'message', listener: (event: unknown) => void): void; off?(type: 'open' | 'close' | 'error' | 'message', listener: (event: unknown) => void): void; } export type HqSocketFactory = (url: string, init: { token?: string; }) => HqSocketLike; export interface HqPublisherCommandResult { commandId: string; status: 'accepted' | 'completed' | 'failed' | 'rejected'; message?: string; } export type HqPublisherCommandHandler = (command: HqQueuedCommand) => void | HqPublisherCommandResult | Promise; export interface HqPublisherOptions { url: string; token?: string; client: HqClientIdentity; project: HqProjectIdentity; capabilities?: readonly HqClientCapability[]; socketFactory?: HqSocketFactory; now?: () => string; idFactory?: () => string; reconnect?: boolean; reconnectBaseMs?: number; reconnectMaxMs?: number; maxQueuedMessages?: number; redactionPolicy?: Partial; commandPollIntervalMs?: number; commandPollLimit?: number; onCommand?: HqPublisherCommandHandler; /** Called whenever HQ sends the merged Kanban state for this project. */ onKanbanSnapshot?: (snapshot: HqKanbanSnapshotPayload) => void | Promise; /** * Local auto-discovery hook: re-resolve the HQ endpoint before EVERY * connect attempt (e.g. by reading `/runtime.json`). Returning * `undefined` means no HQ is currently discoverable — the publisher stays * dormant (events keep queueing, bounded) and re-checks every * {@link HqPublisherOptions.discoveryPollMs}. Because the endpoint is * re-resolved per attempt, an HQ started AFTER this client — or restarted * on a different port, or one that minted its first client token on boot — * is picked up automatically. */ resolveEndpoint?: () => { url: string; token?: string | undefined; } | undefined; /** Dormant re-check interval while `resolveEndpoint` yields nothing. Default 5s. */ discoveryPollMs?: number; /** * Application-level heartbeat interval for the `/ws/client` connection. * The HQ server evicts silent sockets, so this must be comfortably below * the server-side client TTL even when no command handler is installed. */ heartbeatIntervalMs?: number; /** * Diagnostic sink for the one-time consecutive-connect-failure warning. * The publisher is otherwise deliberately silent (dormant queueing), which * made auth failures invisible: a token the server rejects — e.g. a wrong * `WRONGSTACK_HQ_TOKEN` against a remote HQ — looked exactly like "HQ not * running". Defaults to a single structured `console.warn` line. */ warn?: (message: string) => void; /** Logger for structured events. When provided, the `warn` callback is derived from it. */ logger?: Logger | undefined; } export interface HqPublishEventOptions { type: HqEventType | (string & {}); payload: unknown; sessionId?: string; runId?: string; timestamp?: string; /** * Per-string redaction cap override. Defaults to the generic 500-char * summary cap; chat-transcript event types (`session.transcript`, * `agent.message`) automatically get {@link HQ_TRANSCRIPT_TEXT_CAP} so * full chat history survives when `rawContent` is enabled. */ maxSummaryLength?: number; } export declare class HqPublisher { private readonly options; private readonly socketFactory; private readonly now; private readonly idFactory; private readonly capabilities; private readonly reconnect; private readonly reconnectBaseMs; private readonly reconnectMaxMs; private readonly maxQueuedMessages; private readonly resolvedRedactionPolicy; private readonly logger; private socket; private seq; private queue; private stopped; private reconnectAttempt; private connectWarningEmitted; private lastAttempt; private reconnectTimer; private commandPollTimer; private heartbeatTimer; private lastCommandId; constructor(options: HqPublisherOptions); connect(): void; close(): void; publishEvent(options: HqPublishEventOptions & { payload: TPayload; }): HqEventEnvelope; publishMailboxSnapshot(mailbox: Pick, options: Omit & { sessionId?: string; timestamp?: string; }): Promise>; publishMailboxEvent(input: { mailboxId: string; action: HqMailboxEventAction; message?: MailboxMessage; agent?: MailboxAgentStatus; summary?: string; previewLength?: number; sessionId?: string; timestamp?: string; }): HqEventEnvelope; /** The client identity this publisher announced (clientId, kind, machineId, …). */ get identity(): HqClientIdentity; /** The project identity this publisher is bound to. */ get project(): HqProjectIdentity; /** Effective publisher-side policy applied before any event leaves this process. */ get redactionPolicy(): HqRedactionPolicy; /** Publish a live session/terminal snapshot (state + agents). */ publishSessionSnapshot(payload: HqSessionSnapshotPayload, opts?: { timestamp?: string; }): HqEventEnvelope; /** Publish an incremental batch of transcript turns for a session. */ publishTranscriptAppend(payload: HqTranscriptAppendPayload, opts?: { timestamp?: string; }): HqEventEnvelope; /** Mark a session/terminal as ended. */ publishSessionEnded(payload: HqSessionEndedPayload, opts?: { timestamp?: string; }): HqEventEnvelope; /** Publish a fleet (multi-agent coordinator) snapshot. */ publishFleetSnapshot(payload: HqFleetSnapshotPayload, opts?: { sessionId?: string; timestamp?: string; }): HqEventEnvelope; pollCommands(): void; ackCommand(result: HqPublisherCommandResult): void; private createHelloFrame; private sendHelloNow; private sendFrame; private enqueue; /** Batch-dequeue up to 50 frames at a time, yielding to the microtask * queue between batches so we don't starve the event loop on reconnect. */ private flushQueue; private startCommandPolling; private stopCommandPolling; private startHeartbeat; private stopHeartbeat; private publishHeartbeat; private handleServerMessage; private parseServerMessage; private extractMessageData; private handleCommandBatch; private scheduleReconnect; private emitConnectWarning; /** * Dormant re-check while no HQ endpoint is discoverable. Uses a FIXED * interval (not the exponential reconnect backoff): the check is a cheap * local file read, and backing off would delay attaching to an HQ the * user just started — the whole point of auto-discovery. */ private scheduleDiscoveryPoll; } //# sourceMappingURL=publisher.d.ts.map