import { type FileHandle } from 'node:fs/promises'; import type { OwnerAttachmentConfig } from '../config.js'; import type { OursIncomingFile, OursRetrievedFiles } from './ours-client.js'; export interface AttachmentReplyRef { wire_id: string; sentence?: number; } export interface IncomingAttachment { fileId: number; wireId: string; senderId: string; senderName: string; filename: string; mime: string; size: number; status: string; date: string; kind: 'file' | 'voice_message'; replyTo: AttachmentReplyRef | null; } export interface VoiceTranscription { configured: boolean; attempted: boolean; status: 'succeeded' | 'failed' | 'unavailable'; provider: string | null; text: string | null; errorCategory: string | null; audioPath: string; fileWireId: string; } export interface RetrievedAttachment extends IncomingAttachment { path: string; sha256: string; transcription?: VoiceTranscription; } export interface AdmittedAttachment { wireId: string; filename: string; path: string; declaredMime: string; detectedMime: string; size: number; sha256: string; kind: 'file' | 'voice_message'; transcription?: Omit; } /** * Admit the daemon's file listing. The rows are typed now, but every field is * still re-validated here: sender CID, wire id, sizes and ids all cross the * trust boundary and decide routing, so a daemon-side shape change must drop a * row rather than produce a half-built attachment. */ export declare function parseIncomingAttachments(raw: OursIncomingFile[] | undefined): IncomingAttachment[]; export declare function parseRetrievedAttachments(raw: OursRetrievedFiles | undefined, expected: IncomingAttachment[], recovered?: boolean): RetrievedAttachment[]; export declare function validateAttachmentSelection(files: IncomingAttachment[], config: OwnerAttachmentConfig): string | undefined; /** * Managed-agent -> owner egress limits. This intentionally does not consult * `enabled`: that is owner -> agent admission policy. */ export declare function validateAttachmentRelaySelection(files: IncomingAttachment[], config: OwnerAttachmentConfig): string | undefined; export declare function prepareAttachmentDirectory(root: string, requestId: string): Promise; export declare function admitAttachments(files: RetrievedAttachment[], dir: string, config: OwnerAttachmentConfig): Promise; /** Injectable short-write seam, so partial writes are provably handled. */ export interface AttachmentWriteDeps { write?(handle: FileHandle, bytes: Uint8Array, offset: number): Promise; } /** * Land crash-recovered file bytes inside an already-prepared request directory. * * The MCP path handed the daemon a `dest_path` and let its connector write the * file. Nothing writes on our behalf any more, so this owns both halves of that * contract: the destination is DERIVED from a validated wire id inside `dir` * rather than accepted from a caller, and the file is published by link-after- * fsync, so a crash or a short write can never leave a partial file where the * admission step would read it as complete. */ export declare function writeRecoveredAttachment(dir: string, wireId: string, bytes: Uint8Array, deps?: AttachmentWriteDeps): Promise; export declare function recoveredAttachment(file: IncomingAttachment, path: string): Promise; export declare function removeRequestDirectory(path: string): Promise; export declare function cleanupAttachmentRoot(root: string, now: number, retentionMs: number, limit?: number): Promise; export interface PendingAttachmentRequest { id: string; contact: string; originWireId: string; fileWireIds: string[]; createdAt: number; /** Present only for exact-managed-agent -> owner relay recovery. */ relayContact?: string; relayReplyTo?: string; } export declare class AttachmentRecoveryState { private readonly path; private pending; private corrupt; constructor(path: string); integrity(): boolean; list(): PendingAttachmentRequest[]; add(item: PendingAttachmentRequest): void; remove(id: string): void; cleanup(now: number, retentionMs: number): number; private assertHealthy; private persist; } export declare function safeField(value: unknown, max: number): string; export declare function sanitizeFilename(value: string): string;