import * as z from 'zod'; import { z as z$1 } from 'zod'; import { EventEmitter } from 'node:events'; import { Socket } from 'socket.io-client'; import { ExpoPushMessage } from 'expo-server-sdk'; declare const SessionTextDeltaSchema: z.ZodObject<{ type: z.ZodLiteral<"text-delta">; turnId: z.ZodString; itemId: z.ZodString; delta: z.ZodString; text: z.ZodString; }, z.core.$strip>; type SessionTextDelta = z.infer; declare const sessionTurnEndStatusSchema: z.ZodEnum<{ completed: "completed"; failed: "failed"; cancelled: "cancelled"; }>; type SessionTurnEndStatus = z.infer; declare const sessionEnvelopeSchema: z.ZodObject<{ id: z.ZodString; time: z.ZodNumber; role: z.ZodEnum<{ user: "user"; agent: "agent"; }>; turn: z.ZodOptional; subagent: z.ZodOptional; claudeUuid: z.ZodOptional; codexItemId: z.ZodOptional; ev: z.ZodDiscriminatedUnion<[z.ZodObject<{ t: z.ZodLiteral<"text">; text: z.ZodString; thinking: z.ZodOptional; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"service">; text: z.ZodString; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"tool-call-start">; call: z.ZodString; name: z.ZodString; title: z.ZodString; description: z.ZodString; args: z.ZodRecord; mcpApp: z.ZodOptional; server: z.ZodString; resourceUri: z.ZodString; appName: z.ZodOptional; actionName: z.ZodOptional; }, z.core.$strip>>; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"tool-call-end">; call: z.ZodString; status: z.ZodOptional>; error: z.ZodOptional; summary: z.ZodString; detail: z.ZodOptional; }, z.core.$strip>>; mcpAppResult: z.ZodOptional; state: z.ZodLiteral<"available">; content: z.ZodArray; structuredContent: z.ZodOptional; _meta: z.ZodOptional; }, z.core.$strip>, z.ZodObject<{ version: z.ZodLiteral<1>; state: z.ZodLiteral<"unavailable">; code: z.ZodLiteral<"MCP_APP_RESULT_TOO_LARGE">; }, z.core.$strip>], "state">>; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"file">; ref: z.ZodString; name: z.ZodString; size: z.ZodNumber; mimeType: z.ZodOptional; kind: z.ZodOptional>; encrypted: z.ZodOptional; source: z.ZodOptional>; browserStep: z.ZodOptional; skillName: z.ZodOptional>; }, z.core.$strip>>; prompt: z.ZodOptional; batchId: z.ZodOptional; localPath: z.ZodOptional; motionPhoto: z.ZodOptional; }, z.core.$strip>>; image: z.ZodOptional>; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"turn-start">; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"start">; title: z.ZodOptional; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"turn-end">; status: z.ZodEnum<{ completed: "completed"; failed: "failed"; cancelled: "cancelled"; }>; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"stop">; status: z.ZodOptional>; }, z.core.$strip>, z.ZodObject<{ t: z.ZodLiteral<"interactive-preview">; preview: z.ZodObject<{ version: z.ZodLiteral<1>; id: z.ZodString; title: z.ZodString; state: z.ZodEnum<{ failed: "failed"; publishing: "publishing"; ready: "ready"; expired: "expired"; }>; provider: z.ZodOptional>; mode: z.ZodOptional>; url: z.ZodOptional; publishedAt: z.ZodOptional; expiresAt: z.ZodOptional; errorCode: z.ZodOptional; }, z.core.$strict>; }, z.core.$strip>], "t">; }, z.core.$strip>; type SessionEnvelope = z.infer; type MotionPhotoVideo = { videoOffset: number; videoLength: number; mimeType: 'video/mp4'; }; declare const interactivePreviewManifestSchema: z.ZodObject<{ version: z.ZodLiteral<1>; previewId: z.ZodString; title: z.ZodString; assets: z.ZodArray>; }, z.core.$strict>; type InteractivePreviewManifest = z.infer; declare const interactivePreviewEventSchema: z.ZodObject<{ version: z.ZodLiteral<1>; id: z.ZodString; title: z.ZodString; state: z.ZodEnum<{ failed: "failed"; publishing: "publishing"; ready: "ready"; expired: "expired"; }>; provider: z.ZodOptional>; mode: z.ZodOptional>; url: z.ZodOptional; publishedAt: z.ZodOptional; expiresAt: z.ZodOptional; errorCode: z.ZodOptional; }, z.core.$strict>; type InteractivePreviewEvent = z.infer; /** * Simplified schema that only validates fields actually used in the codebase * while preserving all other fields through passthrough() */ declare const UsageSchema: z$1.ZodObject<{ input_tokens: z$1.ZodNumber; cache_creation_input_tokens: z$1.ZodOptional; cache_read_input_tokens: z$1.ZodOptional; output_tokens: z$1.ZodNumber; service_tier: z$1.ZodOptional; }, z$1.core.$loose>; declare const RawJSONLinesSchema: z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{ type: z$1.ZodLiteral<"user">; isSidechain: z$1.ZodOptional; isMeta: z$1.ZodOptional; uuid: z$1.ZodString; message: z$1.ZodObject<{ content: z$1.ZodUnion; }, z$1.core.$loose>; }, z$1.core.$loose>, z$1.ZodObject<{ uuid: z$1.ZodString; type: z$1.ZodLiteral<"assistant">; message: z$1.ZodOptional; cache_read_input_tokens: z$1.ZodOptional; output_tokens: z$1.ZodNumber; service_tier: z$1.ZodOptional; }, z$1.core.$loose>>; model: z$1.ZodOptional; }, z$1.core.$loose>>; }, z$1.core.$loose>, z$1.ZodObject<{ type: z$1.ZodLiteral<"summary">; summary: z$1.ZodString; leafUuid: z$1.ZodString; }, z$1.core.$loose>, z$1.ZodObject<{ type: z$1.ZodLiteral<"system">; uuid: z$1.ZodString; }, z$1.core.$loose>], "type">; type RawJSONLines = z$1.infer; /** * Minimal persistence functions for happy CLI * * Handles settings and private key storage in ~/.happy/ or local .happy/ */ declare const SandboxConfigSchema: z.ZodObject<{ enabled: z.ZodDefault; workspaceRoot: z.ZodOptional; sessionIsolation: z.ZodDefault>; customWritePaths: z.ZodDefault>; denyReadPaths: z.ZodDefault>; extraWritePaths: z.ZodDefault>; denyWritePaths: z.ZodDefault>; networkMode: z.ZodDefault>; allowedDomains: z.ZodDefault>; deniedDomains: z.ZodDefault>; allowLocalBinding: z.ZodDefault; }, z.core.$strip>; type SandboxConfig = z.infer; type Credentials = { token: string; encryption: { type: 'legacy'; secret: Uint8Array; } | { type: 'dataKey'; publicKey: Uint8Array; machineKey: Uint8Array; }; }; /** * Permission mode type - includes both Claude and Codex modes * Must match MessageMetaSchema.permissionMode enum values * * Claude modes: default, acceptEdits, bypassPermissions, plan * Codex modes: read-only, safe-yolo, yolo * * When calling Claude SDK, Codex modes are mapped at the SDK boundary: * - yolo → bypassPermissions * - safe-yolo → default * - read-only → default */ type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'read-only' | 'safe-yolo' | 'yolo'; /** * Usage data type from Claude */ type Usage = z$1.infer; /** * Session information */ type Session = { id: string; seq: number; encryptionKey: Uint8Array; encryptionVariant: 'legacy' | 'dataKey'; metadata: Metadata; metadataVersion: number; agentState: AgentState | null; agentStateVersion: number; }; /** * Machine metadata - static information (rarely changes) */ declare const MachineMetadataSchema: z$1.ZodObject<{ host: z$1.ZodString; platform: z$1.ZodString; happyCliVersion: z$1.ZodString; homeDir: z$1.ZodString; happyHomeDir: z$1.ZodString; happyLibDir: z$1.ZodString; cliAvailability: z$1.ZodOptional; claude: z$1.ZodBoolean; codex: z$1.ZodBoolean; gemini: z$1.ZodBoolean; opencode: z$1.ZodBoolean; openclaw: z$1.ZodBoolean; detectedAt: z$1.ZodNumber; }, z$1.core.$strip>>; resumeSupport: z$1.ZodOptional>; }, z$1.core.$strip>; type MachineMetadata = z$1.infer; /** * Daemon state - dynamic runtime information (frequently updated) */ declare const DaemonStateSchema: z$1.ZodObject<{ status: z$1.ZodUnion, z$1.ZodString]>; pid: z$1.ZodOptional; httpPort: z$1.ZodOptional; startedAt: z$1.ZodOptional; shutdownRequestedAt: z$1.ZodOptional; shutdownSource: z$1.ZodOptional, z$1.ZodString]>>; codexUsage: z$1.ZodOptional; codexHome: z$1.ZodString; sessionsDir: z$1.ZodString; timeZone: z$1.ZodString; scannedAt: z$1.ZodNumber; today: z$1.ZodNullable>; yesterday: z$1.ZodNullable>; days: z$1.ZodArray>; latestEvent: z$1.ZodNullable; sessionTotalTokenUsage: z$1.ZodOptional>; rateLimits: z$1.ZodOptional; primary: z$1.ZodOptional; windowMinutes: z$1.ZodOptional; resetsAt: z$1.ZodOptional; }, z$1.core.$strip>>; secondary: z$1.ZodOptional; windowMinutes: z$1.ZodOptional; resetsAt: z$1.ZodOptional; }, z$1.core.$strip>>; rateLimitReachedType: z$1.ZodOptional>; }, z$1.core.$strip>>; rateLimitsTimestamp: z$1.ZodOptional; }, z$1.core.$strip>>; warnings: z$1.ZodArray; }, z$1.core.$strip>>; codexAccountUsage: z$1.ZodOptional; codexHome: z$1.ZodString; sessionsDir: z$1.ZodString; timeZone: z$1.ZodString; scannedAt: z$1.ZodNumber; today: z$1.ZodNullable>; yesterday: z$1.ZodNullable>; days: z$1.ZodArray>; latestEvent: z$1.ZodNullable; sessionTotalTokenUsage: z$1.ZodOptional>; rateLimits: z$1.ZodOptional; primary: z$1.ZodOptional; windowMinutes: z$1.ZodOptional; resetsAt: z$1.ZodOptional; }, z$1.core.$strip>>; secondary: z$1.ZodOptional; windowMinutes: z$1.ZodOptional; resetsAt: z$1.ZodOptional; }, z$1.core.$strip>>; rateLimitReachedType: z$1.ZodOptional>; }, z$1.core.$strip>>; rateLimitsTimestamp: z$1.ZodOptional; }, z$1.core.$strip>>; warnings: z$1.ZodArray; }, z$1.core.$strip>; }, z$1.core.$strip>>>; }, z$1.core.$strip>; type DaemonState = z$1.infer; type Machine = { id: string; encryptionKey: Uint8Array; encryptionVariant: 'legacy' | 'dataKey'; metadata: MachineMetadata; metadataVersion: number; daemonState: DaemonState | null; daemonStateVersion: number; }; declare const UserMessageSchema: z$1.ZodObject<{ role: z$1.ZodLiteral<"user">; content: z$1.ZodObject<{ type: z$1.ZodLiteral<"text">; text: z$1.ZodString; }, z$1.core.$strip>; localKey: z$1.ZodOptional; meta: z$1.ZodOptional; permissionMode: z$1.ZodOptional>; permissionModeExplicit: z$1.ZodOptional; model: z$1.ZodOptional>; effort: z$1.ZodOptional>; fast: z$1.ZodOptional; fallbackModel: z$1.ZodOptional>; customSystemPrompt: z$1.ZodOptional>; appendSystemPrompt: z$1.ZodOptional>; allowedTools: z$1.ZodOptional>>; disallowedTools: z$1.ZodOptional>>; editedFromMessageId: z$1.ZodOptional; }, z$1.core.$strip>>; }, z$1.core.$strip>; type UserMessage = z$1.infer; /** * File event message — sent by the app as a session envelope before the text message. * Contains a ref pointing to the encrypted blob on the server. */ declare const FileEventMessageSchema: z$1.ZodObject<{ role: z$1.ZodLiteral<"session">; content: z$1.ZodObject<{ type: z$1.ZodLiteral<"session">; data: z$1.ZodObject<{ id: z$1.ZodString; time: z$1.ZodNumber; role: z$1.ZodLiteral<"user">; ev: z$1.ZodObject<{ t: z$1.ZodLiteral<"file">; ref: z$1.ZodString; name: z$1.ZodString; size: z$1.ZodNumber; mimeType: z$1.ZodOptional; kind: z$1.ZodOptional>; encrypted: z$1.ZodOptional; source: z$1.ZodOptional>; browserStep: z$1.ZodOptional; skillName: z$1.ZodOptional>; }, z$1.core.$strip>>; prompt: z$1.ZodOptional; batchId: z$1.ZodOptional; localPath: z$1.ZodOptional; motionPhoto: z$1.ZodOptional; }, z$1.core.$strip>>; image: z$1.ZodOptional; }, z$1.core.$strip>>; }, z$1.core.$strip>; }, z$1.core.$strip>; }, z$1.core.$strip>; }, z$1.core.$strip>; type FileEventMessage = z$1.infer; type Metadata = { /** * ACP session config option value (normalized for UI metadata consumers). */ models?: Array<{ code: string; value: string; description?: string | null; serviceTiers?: Array<{ id: string; name: string; description?: string | null; }>; }>; currentModelCode?: string; operatingModes?: Array<{ code: string; value: string; description?: string | null; }>; currentOperatingModeCode?: string; thoughtLevels?: Array<{ code: string; value: string; description?: string | null; }>; currentThoughtLevelCode?: string; path: string; host: string; version?: string; name?: string; os?: string; summary?: { text: string; updatedAt: number; }; capabilities?: { regenerateTitle?: boolean; }; machineId?: string; claudeSessionId?: string; codexThreadId?: string; codexAccountProfileId?: string; codexAccountCredentialVersion?: number; /** Last Codex turn mirrored into this Paws session, used for reconnect catch-up. */ codexSyncCursor?: { threadId: string; turnId: string; }; /** Durable marker for an acknowledged, idempotent Codex history replay. */ codexHistoryReplay?: { threadId: string; startedAt: number; }; /** Opaque key used to recognize this Paws session's user turns in a shared Codex Thread. */ codexPawsOriginToken?: string; tools?: string[]; slashCommands?: string[]; mcpServers?: Array<{ name: string; status: string; }>; skills?: string[]; homeDir: string; happyHomeDir: string; happyLibDir: string; happyToolsDir: string; startedFromDaemon?: boolean; hostPid?: number; startedBy?: 'daemon' | 'terminal'; lifecycleState?: 'running' | 'archiveRequested' | 'archived' | string; lifecycleStateSince?: number; archivedBy?: string; archiveReason?: string; flavor?: string; sandbox?: SandboxConfig | null; dangerouslySkipPermissions?: boolean | null; /** Lineage for sessions created via the fork / duplicate flow. */ parentSessionId?: string; forkedFromMessageId?: string; }; type AgentState = { controlledByUser?: boolean | null | undefined; queuedMessages?: number; turnStatus?: { status: 'running' | 'completed' | 'failed' | 'cancelled'; updatedAt: number; turnId?: string; }; requests?: { [id: string]: { tool: string; arguments: any; createdAt: number; }; }; completedRequests?: { [id: string]: { tool: string; arguments: any; createdAt: number; completedAt: number; status: 'canceled' | 'denied' | 'approved'; reason?: string; mode?: PermissionMode; decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort'; allowTools?: string[]; }; }; }; /** * Common RPC types and interfaces for both session and machine clients */ /** * Generic RPC handler function type * @template TRequest - The request data type * @template TResponse - The response data type */ type RpcHandler = (data: TRequest) => TResponse | Promise; /** * RPC request data from server */ interface RpcRequest { method: string; params: string; } /** * Configuration for RPC handler manager */ interface RpcHandlerConfig { scopePrefix: string; encryptionKey: Uint8Array; encryptionVariant: 'legacy' | 'dataKey'; logger?: (message: string, data?: any) => void; } /** * Generic RPC handler manager for session and machine clients * Manages RPC method registration, encryption/decryption, and handler execution */ declare class RpcHandlerManager { private handlers; private readonly scopePrefix; private readonly encryptionKey; private readonly encryptionVariant; private readonly logger; private socket; constructor(config: RpcHandlerConfig); /** * Register an RPC handler for a specific method * @param method - The method name (without prefix) * @param handler - The handler function */ registerHandler(method: string, handler: RpcHandler): void; unregisterHandler(method: string): void; /** * Handle an incoming RPC request * @param request - The RPC request data * @param callback - The response callback */ handleRequest(request: RpcRequest): Promise; onSocketConnect(socket: Socket): void; onSocketDisconnect(): void; /** * Get the number of registered handlers */ getHandlerCount(): number; /** * Check if a handler is registered * @param method - The method name (without prefix) */ hasHandler(method: string): boolean; /** * Clear all handlers */ clearHandlers(): void; /** * Get the prefixed method name * @param method - The method name */ private getPrefixedMethod; } /** * A user-uploaded attachment claimed by a message. Two lanes: * - image: E2E-encrypted, decrypted to `data` bytes in memory (small, ≤50MB), * fed to the model as base64 (Claude) / localImage (Codex). * - file/audio/video: staged to `localPath` on disk. PDF files stay E2E * encrypted in transit, while audio/video may use the plaintext streaming * lane. The model receives the exact path as text. */ type ImageAttachment = { kind?: 'image'; data: Uint8Array; mimeType: string; name: string; }; type MediaAttachment = { kind: 'audio' | 'video' | 'file'; localPath: string; size: number; mimeType: string; name: string; }; type PendingAttachment = ImageAttachment | MediaAttachment; type StartupTraceWriter = (label: string, event: Record) => void; declare class WorkerSessionStartupLifecycle { private readonly write; private readonly now; private readonly wallNow; private readonly traceId; private readonly startedAt; private lastStageAt; private boundSessionId; private socketReadyLogged; private entryStartedLogged; private authReadyLogged; private machineReadyLogged; private processorStartingLogged; private processorReadyLogged; constructor(traceId: unknown, write?: StartupTraceWriter, now?: () => number, wallNow?: () => number); entryStarted(): boolean; authReady(): boolean; machineReady(machineId?: string): boolean; bindCreatedSession(sessionId: unknown, machineId?: string): boolean; socketReady(sessionId: unknown, machineId?: string): boolean; processorStarting(sessionId: unknown, machineId?: string): boolean; processorReady(sessionId: unknown, machineId?: string): boolean; private logStage; private log; } type ResolvedPreviewWorkspace = { manifest: InteractivePreviewManifest; files: Array<{ assetId: string; absolutePath: string; }>; }; /** * ACP (Agent Communication Protocol) message data types. * This is the unified format for all agent messages - CLI adapts each provider's format to ACP. */ type ACPMessageData = { type: 'message'; message: string; } | { type: 'reasoning'; message: string; } | { type: 'thinking'; text: string; } | { type: 'tool-call'; callId: string; name: string; input: unknown; id: string; } | { type: 'tool-result'; callId: string; output: unknown; id: string; isError?: boolean; } | { type: 'file-edit'; description: string; filePath: string; diff?: string; oldContent?: string; newContent?: string; id: string; } | { type: 'terminal-output'; data: string; callId: string; } | { type: 'task_started'; id: string; } | { type: 'task_complete'; id: string; } | { type: 'turn_aborted'; id: string; status?: 'failed' | 'cancelled'; } | { type: 'permission-request'; permissionId: string; toolName: string; description: string; options?: unknown; } | { type: 'token_count'; [key: string]: unknown; }; declare class ApiSessionClient extends EventEmitter { private readonly startupLifecycle; private readonly token; readonly sessionId: string; private metadata; private metadataVersion; private agentState; private agentStateVersion; private socket; private pendingInputMessages; private pendingMessageCallback; private pendingFileEventCallback; private blobKey; /** * In-flight attachment download promises that belong to the *current* * (not-yet-drained) batch. Each promise resolves to the decoded blob (or * null on failure), so per-message ownership is intrinsic — there is no * shared push-array between batches that a late download could leak into. */ private pendingDownloads; readonly rpcHandlerManager: RpcHandlerManager; private agentStateLock; private metadataLock; private outboxLock; private encryptionKey; private encryptionVariant; private reconnectInterval; private ignoreArchiveSignal; private skipInitialMessages; private hasObservedRootTurnLifecycle; private claudeSessionProtocolState; private lastSeq; private pendingOutbox; private readonly sendSync; private readonly receiveSync; constructor(token: string, session: Session, startupLifecycle?: WorkerSessionStartupLifecycle); processorStarting(): boolean; processorReady(): boolean; onUserMessage(callback: (data: UserMessage) => void, onFileEvent?: (data: FileEventMessage) => void): void; onFileEvent(callback: (data: FileEventMessage) => void): void; private flushPendingInputMessages; /** * Derive (and cache) the blob decryption key for this session. * Legacy sessions use deriveKey(masterSecret, 'Happy Blobs', ['master']). * DataKey sessions use deriveKey(dataKey, 'Happy Blobs', ['session']). */ getBlobKey(): Promise; /** * Download an encrypted attachment blob via the request-download flow: * POST /request-download → { downloadUrl } → GET downloadUrl. Local mode * downloadUrl points back at our server (Bearer required); S3 mode is a * presigned URL that does not accept extra headers. */ downloadAttachment(ref: string): Promise; /** * Resolve a ref to a fetched, ok Response ready to read/stream. * * POST /request-download → { downloadUrl }, then GET it. Local-storage * downloadUrls point back at our own happy server and need the Bearer * token; presigned S3 URLs carry auth in the query string and reject extra * headers, so only those go out unauthenticated. The serverUrl may be a * loopback address while the server builds the URL from its PUBLIC_URL * host, so an exact prefix match misses — detect presigned S3 by the * X-Amz-* / Signature query params instead. */ private openAttachmentDownload; /** * Stream a plaintext (unencrypted) attachment straight to disk without ever * holding the whole file in memory. Used for the audio/video lane, where a * blob can be up to 500MB — an arrayBuffer() there would OOM the daemon, * especially with several sessions downloading concurrently. On any failure * the half-written file is removed so callers never see a truncated file. */ streamAttachmentToDisk(ref: string, destPath: string): Promise; /** * Download and decrypt an attachment blob. * Returns the decrypted binary data or null if decryption fails. */ downloadAndDecryptAttachment(ref: string): Promise; /** * Encrypt + upload a local image file via the attachment channel, returning the * server ref. Reuses getBlobKey() so the app can decrypt with the same session * blob key. Throws on read/encrypt/upload failure. */ uploadImageAttachment(filePath: string): Promise<{ ref: string; name: string; size: number; dims: { width: number; height: number; } | null; motionPhoto: MotionPhotoVideo | null; }>; /** Stream a locally generated media artifact through the plaintext lane. */ uploadMediaAttachment(filePath: string, requestedMimeType?: string): Promise<{ ref: string; name: string; size: number; kind: 'audio' | 'video'; mimeType: string; }>; /** * Emit a file event so the app renders the uploaded attachment inline (FileView). * When dims are provided we include an image{} block carrying the real width/height * so the app renders at the true aspect ratio. The wire schema requires * image.thumbhash; we don't compute a real one, so we pass thumbhash:'' — the app's * FileView treats a falsy thumbhash as "no placeholder" but still uses width/height. * When dims is null/undefined (non-image or unparseable) we omit image{} and the app * falls back to a 4:3 inline render. Use role 'user' to match the proven path. */ sendFileEvent(ref: string, name: string, size: number, dims?: { width: number; height: number; } | null, options?: { source?: 'user' | 'generated' | 'browser_step'; kind?: 'audio' | 'video'; mimeType?: string; encrypted?: boolean; prompt?: string; batchId?: string; localPath?: string; motionPhoto?: MotionPhotoVideo; browserStep?: { label: string; runId?: string; skillName?: 'ego-browser' | 'ego-ops'; }; }): void; /** * Track an attachment download whose promise resolves to the decoded blob * (or null on failure). The download stays in the current batch until the * next drainAttachmentsForUserMessage call swaps the bucket out — file * events that arrive after the swap go into a fresh bucket bound to the * next user-text message. */ trackAttachmentDownload(promise: Promise): void; /** * Atomically claim every download started before this call, wait for them * to resolve, and return the successful ones. The swap-then-await order * guarantees that a late-arriving file event cannot leak into this batch. */ drainAttachmentsForUserMessage(): Promise; private authHeaders; private routeIncomingMessage; private fetchMessages; private static readonly MAX_OUTBOX_BATCH_SIZE; private static readonly MAX_HISTORY_BATCH_SIZE; private flushOutbox; private postOutboxBatch; private encodeOutboxMessage; private enqueueMessage; /** * Send message to session * @param body - Message body (can be MessageContent or raw content for agent messages) */ sendClaudeSessionMessage(body: RawJSONLines): void; closeClaudeSessionTurn(status?: SessionTurnEndStatus): void; /** Best-effort encrypted preview; never enqueue or replay across disconnects. */ sendSessionTextDelta(event: SessionTextDelta): void; sendCodexMessage(body: any): void; private enqueueSessionProtocolEnvelope; private createSessionProtocolContent; private observeSessionProtocolEnvelope; private persistFinalSessionProtocolHistoryStatus; sendSessionProtocolMessage(envelope: SessionEnvelope): void; reportInteractivePreview(preview: InteractivePreviewEvent): void; publishInteractivePreview(workspace: ResolvedPreviewWorkspace): Promise<{ version: 1; id: string; title: string; state: "failed" | "publishing" | "ready" | "expired"; provider?: "vercel" | "cloudflare" | undefined; mode?: "tunnel" | "hosted" | undefined; url?: string | undefined; publishedAt?: number | undefined; expiresAt?: number | undefined; errorCode?: string | undefined; }>; /** * Persist a reconstructed transcript in chronological order using larger * history batches. Server seq is also the pagination cursor, so replay and * live delivery both preserve producer order. */ sendSessionProtocolHistoryAndAwait(envelopes: readonly SessionEnvelope[], timeoutMs?: number): Promise; /** * Send a generic agent message to the session using ACP (Agent Communication Protocol) format. * Works for any agent type (Gemini, Codex, Claude, etc.) - CLI normalizes to unified ACP format. * * @param provider - The agent provider sending the message (e.g., 'gemini', 'codex', 'claude') * @param body - The message payload (type: 'message' | 'reasoning' | 'tool-call' | 'tool-result') */ sendAgentMessage(provider: 'gemini' | 'codex' | 'claude' | 'opencode' | 'openclaw', body: ACPMessageData): void; sendSessionEvent(event: { type: 'switch'; mode: 'local' | 'remote'; } | { type: 'message'; message: string; } | { type: 'permission-mode-changed'; mode: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'; } | { type: 'ready'; }, id?: string): void; private persistTurnStatus; /** * Send a ping message to keep the connection alive */ keepAlive(thinking: boolean, mode: 'local' | 'remote'): void; /** * Send session death message */ sendSessionDeath(): void; /** * Send usage data to the server */ sendUsageData(usage: Usage, model?: string): void; /** * Returns the latest session metadata known to the client. */ getMetadata(): Metadata | null; /** * Update session metadata * @param handler - Handler function that returns the updated metadata */ suppressNextArchiveSignal(): void; skipExistingMessages(): void; updateMetadata(handler: (metadata: Metadata) => Metadata): void; updateMetadataAndAwait(handler: (metadata: Metadata) => Metadata): Promise; /** * Update session agent state * @param handler - Handler function that returns the updated agent state */ updateAgentState(handler: (metadata: AgentState) => AgentState): Promise; /** * Wait for socket buffer to flush */ flush(): Promise; /** * Wait until every queued message has been acknowledged by the v3 API. * Unlike flush(), timing out rejects: callers must not commit a replay * cursor when the outbox may still contain undelivered envelopes. */ flushOutboxAndAwait(timeoutMs?: number): Promise; close(): Promise; private startSmartReconnect; } interface SpawnSessionOptions { machineId?: string; directory: string; sessionId?: string; approvedNewDirectoryCreation?: boolean; agent?: 'ask' | 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw'; environmentVariables?: Record; token?: string; /** * Opaque, single-use server grant for an explicitly bound Codex account. * When absent, the daemon uses this machine's own local Codex login. */ codexSessionGrant?: string; /** * If set, the daemon spawns the agent with `--resume ` so the new * Happy session continues from an existing Claude conversation file. * Used by the session fork / duplicate flow: the fork RPC produces a * new Claude JSONL on disk, the spawn RPC then attaches a fresh Happy * session to it. */ resumeClaudeSessionId?: string; /** * If set, the daemon spawns Codex with `--resume ` so a fresh Happy * session attaches to a forked Codex app-server thread. */ resumeCodexThreadId?: string; /** Happy session id this fork was branched from (lineage). */ parentSessionId?: string; /** Happy message id used as the rewind point (only set for "duplicate"). */ forkedFromMessageId?: string; } type SpawnSessionResult = { type: 'success'; sessionId: string; } | { type: 'requestToApproveDirectoryCreation'; directory: string; } | { type: 'error'; errorMessage: string; }; /** * WebSocket client for machine/daemon communication with Happy server * Similar to ApiSessionClient but for machine-scoped connections */ type TracedSpawnSessionOptions = SpawnSessionOptions & { traceId?: string; }; type MachineRpcHandlers = { spawnSession: (options: TracedSpawnSessionOptions) => Promise; resumeSession?: (sessionId: string, options?: { model?: string; permissionMode?: string; effort?: string | null; codexSessionGrant?: string; }) => Promise; stopSession: (sessionId: string) => boolean; requestShutdown: () => void; refreshCodexUsage?: () => Promise; refreshCodexAccountQuota?: (grant: string) => Promise<{ type: 'success'; accepted: boolean; } | { type: 'error'; errorMessage: string; }>; }; declare class ApiMachineClient { private token; private machine; private socket; private keepAliveInterval; private lastKnownCLIAvailability; private lastKnownResumeSupport; private rpcHandlerManager; private resumeSessionHandler; private reconnectInterval; private readonly codexAttachCandidates; constructor(token: string, machine: Machine); setRPCHandlers({ spawnSession, resumeSession, stopSession, requestShutdown, refreshCodexUsage, refreshCodexAccountQuota, }: MachineRpcHandlers): void; private syncResumeSessionRpcRegistration; /** * Update machine metadata * Currently unused, changes from the mobile client are more likely * for example to set a custom name. */ updateMachineMetadata(handler: (metadata: MachineMetadata | null) => MachineMetadata): Promise; /** * Update daemon state (runtime info) - similar to session updateAgentState * Simplified without lock - relies on backoff for retry */ updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise; connect(): void; private startKeepAlive; isConnected(): boolean; private startSmartReconnect; private stopKeepAlive; shutdown(): void; } interface PushToken { id: string; token: string; createdAt: number; updatedAt: number; } type SessionNotificationKind = 'done' | 'permission' | 'question'; declare class PushNotificationClient { private readonly token; private readonly baseUrl; private readonly expo; constructor(token: string, baseUrl?: string); /** * Fetch all push tokens for the authenticated user. * Retries up to 3 times with exponential backoff on transient errors. */ fetchPushTokens(): Promise; /** * Send push notification via Expo Push API with retry * @param messages - Array of push messages to send */ sendPushNotifications(messages: ExpoPushMessage[]): Promise; /** * Send a push notification to all registered devices for the user * @param title - Notification title * @param body - Notification body * @param data - Additional data to send with the notification */ sendToAllDevices(title: string, body?: string, data?: Record): void; /** * Routes session-event pushes through the server so it can apply * presence-based suppression (active desktop/web, mobile foreground). * Falls back to direct Expo send only when sessionId is missing — that * shouldn't happen for session notifications but guards against regressions. */ sendSessionNotification(params: { kind: SessionNotificationKind; metadata: Metadata | null | undefined; data?: Record; }): void; } declare const codexAccountAuthSchema: z$1.ZodObject<{ auth_mode: z$1.ZodOptional>; OPENAI_API_KEY: z$1.ZodOptional; tokens: z$1.ZodObject<{ id_token: z$1.ZodString; access_token: z$1.ZodString; refresh_token: z$1.ZodString; account_id: z$1.ZodString; }, z$1.core.$strict>; last_refresh: z$1.ZodOptional>; }, z$1.core.$strict>; type CodexAccountAuth = z$1.infer; interface CodexAccountProfile { id: string; displayName: string; credentialVersion: number; status: 'available' | 'needs-refresh' | 'invalid'; } interface CodexGrantRedemption { auth: CodexAccountAuth; launchId: string; profile: Pick; } interface CodexLaunchAttribution { machineId: string; launchId: string; } interface CodexQuotaReport extends CodexLaunchAttribution { sourceSessionId: string; credentialVersion: number; weeklyUsedPercent: number; weeklyResetsAt: string; observedAt: string; } interface CodexQuotaProbeReport extends CodexLaunchAttribution { credentialVersion: number; weeklyUsedPercent: number; weeklyResetsAt: string; observedAt: string; } declare class ApiClient { static create(credential: Credentials, startupLifecycle?: WorkerSessionStartupLifecycle): Promise; private readonly credential; private readonly pushClient; private readonly startupLifecycle; private constructor(); /** * Create a new session or load existing one with the given tag */ getOrCreateSession(opts: { tag: string; metadata: Metadata; state: AgentState | null; }): Promise; /** * Register or update machine with the server * Returns the current machine state from the server with decrypted metadata and daemonState */ getOrCreateMachine(opts: { machineId: string; metadata: MachineMetadata; daemonState?: DaemonState; }): Promise; sessionSyncClient(session: Session): ApiSessionClient; machineSyncClient(machine: Machine): ApiMachineClient; push(): PushNotificationClient; private codexAccountRequest; uploadCodexAccount(auth: CodexAccountAuth): Promise<{ profile: CodexAccountProfile; }>; redeemCodexSessionGrant(request: { machineId: string; grant: string; }): Promise; attachCodexSession(launchId: string, request: { machineId: string; sourceSessionId: string; }): Promise<{ success: true; }>; updateCodexAccountCredential(profileId: string, request: CodexLaunchAttribution & { expectedVersion: number; auth: CodexAccountAuth; }): Promise<{ profile: CodexAccountProfile; }>; reportCodexAccountQuota(profileId: string, request: CodexQuotaReport): Promise<{ accepted: boolean; }>; reportCodexAccountQuotaProbe(profileId: string, request: CodexQuotaProbeReport): Promise<{ accepted: boolean; }>; reportCodexAccountStatus(profileId: string, request: CodexLaunchAttribution & { credentialVersion: number; status: 'needs-refresh' | 'invalid'; }): Promise<{ profile: CodexAccountProfile; }>; /** * Register a vendor API token with the server * The token is sent as a JSON string - server handles encryption */ registerVendorToken(vendor: 'openai' | 'anthropic' | 'gemini', apiKey: any): Promise; /** * Get vendor API token from the server * Returns the token if it exists, null otherwise */ getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini'): Promise; /** * Mark a session as inactive on the server (active=false). Does NOT * change `lifecycleState`, so the session remains visible in the app * and resumable — same effect as the in-app "Archive" button hitting * the /archive endpoint, but without the extra metadata. * * Used during graceful shutdown (Ctrl-C / SIGTERM) as a synchronous * fallback for the socket-based session-end signal: even if the * socket emit doesn't drain before the process exits, the HTTP * response confirms the deactivate landed. */ deactivateSession(sessionId: string): Promise; } /** * Design decisions: * - Logging should be done only through file for debugging, otherwise we might disturb the claude session when in interactive mode * - Use info for logs that are useful to the user - this is our UI * - File output location: ~/.handy/logs/.log */ declare class Logger { readonly logFilePath: string; private dangerouslyUnencryptedServerLoggingUrl; constructor(logFilePath?: string); localTimezoneTimestamp(): string; debug(message: string, ...args: unknown[]): void; debugLargeJson(message: string, object: unknown, maxStringLength?: number, maxArrayLength?: number): void; info(message: string, ...args: unknown[]): void; infoDeveloper(message: string, ...args: unknown[]): void; warn(message: string, ...args: unknown[]): void; getLogPath(): string; private logToConsole; private sendToRemoteServer; private logToFile; } declare let logger: Logger; /** * Global configuration for happy CLI * * Centralizes all configuration including environment variables and paths * Environment files should be loaded using Node's --env-file flag */ declare class Configuration { readonly serverUrl: string; readonly webappUrl: string; readonly isDaemonProcess: boolean; readonly happyHomeDir: string; readonly logsDir: string; readonly attachmentsDir: string; readonly generatedImagesDir: string; readonly settingsFile: string; readonly privateKeyFile: string; readonly daemonStateFile: string; readonly daemonLockFile: string; readonly sessionsFile: string; readonly currentCliVersion: string; readonly isExperimentalEnabled: boolean; readonly disableCaffeinate: boolean; constructor(); } declare const configuration: Configuration; export { ApiClient, ApiSessionClient, RawJSONLinesSchema, configuration, logger }; export type { RawJSONLines };