/** * ipc.ts — Shared IpcProtocol module for @ericzhaozzz/wechat-channel. * * Provides typed message definitions, line-delimited JSON encoding/decoding, * backpressure management, and Promise-based ack tracking. * * Both the Extension and the Worker use this module through the same * IpcProtocol class, configured with a transport that wraps their * respective stdin/stdout. */ export interface Transport { write(data: string): boolean; onData(handler: (chunk: string) => void): void; offData(handler: (chunk: string) => void): void; destroy(): void; } export interface MediaInfo { type: 'image' | 'voice' | 'video' | 'file'; filePath: string; fileName: string; format?: string; durationMs?: number; } export interface QuotedInfo { title: string; text: string; type: string; } /** * Markers a failed ack carries so the sender can tell whether retrying is * safe. A reply is chunked, so "it failed" is not one outcome: nothing * may have reached WeChat, or some chunks may already be on the user's * screen — in which case resending the whole text would duplicate it. */ export declare const AckError: { /** Nothing was delivered; the command can be retried verbatim. */ readonly Undelivered: "E_UNDELIVERED"; /** Some chunks landed. Retrying would duplicate text. */ readonly Partial: "E_PARTIAL"; }; /** The marker strings themselves — see the note in policy.ts. */ export type AckErrorCode = (typeof AckError)[keyof typeof AckError]; export type ExtToWorkerCmd = { type: 'login'; force?: boolean; } | { type: 'reply'; userId: string; text: string; } | { type: 'notice'; userId: string; text: string; } | { type: 'send_media'; userId: string; filePath?: string; data?: string; url?: string; fileName?: string; caption?: string; } | { type: 'send_typing'; userId: string; } | { type: 'stop_typing'; userId: string; } | { type: 'ping'; } | { type: 'shutdown'; }; export type WorkerToExtMsg = { type: 'message'; userId: string; text: string; msgId: string; timestamp: string; hasMedia: boolean; media: MediaInfo | null; voiceText: string; quoted: QuotedInfo | null; attachmentCount?: number; } | { type: 'login_ready'; qrUrl: string; } | { type: 'login_success'; accountId: string; } | { type: 'login_failed'; msg: string; } | { type: 'login_expired'; reason: 'session_expired' | 'qr_expired'; msg: string; } | { type: 'status'; msg: string; } | { type: 'pong'; } | { type: 'error'; msg: string; }; /** Max tracked commands in flight before `send()` rejects as backpressure. */ export declare const MAX_PENDING_COMMANDS = 20; /** * How long a tracked command waits for its ack. Long on purpose: a * multi-chunk reply is paced at 300 ms per chunk, and a timeout is treated * as an *unknown* outcome (never a resend), so cutting it short turns slow * deliveries into dropped answers. */ export declare const ACK_TIMEOUT_MS = 120000; export interface IpcProtocolConfig { transport: Transport; /** Max tracked commands in flight (default: `MAX_PENDING_COMMANDS`). */ maxPendingCommands?: number; /** * Timeout per tracked command in ms (default: `ACK_TIMEOUT_MS`). * 0 = no timeout (use with caution — leaks on lost ack). */ ackTimeoutMs?: number; /** * Where protocol-level failures go. Required on the extension side: * this class also runs inside the pi process, where the default * (stderr) would be drawn into the TUI. The worker can omit it. */ onError?: (message: string) => void; } export declare class IpcProtocol { private transport; private buffer; private inflightCommands; private nextId; private messageHandler; private commandHandler; private maxPendingCommands; private ackTimeoutMs; private onError; private closed; constructor(config: IpcProtocolConfig); /** * Send a tracked command. Returns a Promise that resolves when the * worker acks this command by id. Rejects on backpressure, write * failure, timeout, or a failed ack (the worker processed the command * and it threw) — so a caller that cares about delivery, and not just * about having written to stdin, can await this and retry. * * The command id is assigned internally; the caller never sees it. */ send(cmd: ExtToWorkerCmd, opts?: { timeoutMs?: number; }): Promise; /** * Send a command without tracking or backpressure. Returns true if * the write succeeded. The command is assigned an id for wire * protocol consistency but no ack is expected. */ sendUntracked(cmd: ExtToWorkerCmd): boolean; /** * Number of tracked commands awaiting ack. */ get inflightCount(): number; /** * Register a handler for incoming commands from the extension. * The handler is wrapped: after it resolves (or rejects), an ack * is automatically sent to the extension so its backpressure slot * is always released. */ onCommand(handler: (cmd: ExtToWorkerCmd & { id: string; }) => Promise): void; /** * Register a handler for incoming messages from the worker. */ onMessage(handler: (msg: WorkerToExtMsg) => void): void; /** * Send an event from the worker to the extension (no ack expected). * Equivalent to sendUntracked but typed for WorkerToExt messages. */ sendEvent(msg: WorkerToExtMsg): boolean; /** * Send an ack for a processed command. Pass `error` when the command * was received but failed — the ack still frees the sender's * backpressure slot, but rejects its promise instead of resolving it, * so "the worker never got it" and "the worker could not do it" are * distinguishable at the call site. */ ack(id: string, cmdType: string, error?: string): boolean; /** * Close the protocol. Rejects all pending sends and destroys the * transport. Safe to call multiple times. */ close(): void; get isClosed(): boolean; private _sendRaw; private _onData; private _handleMessage; } //# sourceMappingURL=ipc.d.ts.map