/** * Filesystem state sync (docs/fs-watch.md): wire constants, message * builders, record codecs, and the client-side mirror reducer. * * The server maintains a canonical replica of a watched tree and streams * ordered state diffs (`FS_UPDATE`). The complete client obligation is * {@link FsMirror}: apply records to a map, acknowledge. Loss, overflow, * and recovery are not wire concepts — the server restages (`RESET … SYNC`) * whenever an incremental diff is not possible. * * All integers little-endian, tightly packed, as everywhere in the protocol. */ import type { SessionId } from "./types.js"; import type { ReactiveStore } from "./reactive.js"; /** Start a sync: [0x40][nonce:2][flags:2][latency_ms:2][inline_max:4][path_len:2][path:N] * then, with `FS_SYNC_EXCLUDE`, [exclude_len:2][exclude:M]; then, with * `FS_SYNC_FROM_PTY`, [src_pty_id:2]. */ export declare const C2S_FS_SYNC = 64; /** Stop a sync: [0x41][sync_id:2] */ export declare const C2S_FS_STOP = 65; /** Cumulative acknowledgement: [0x42][sync_id:2][update_id:4] */ export declare const C2S_FS_ACK = 66; /** Fetch full content of one file: [0x43][nonce:2][sync_id:2][path_len:2][path:N] */ export declare const C2S_FS_FETCH = 67; /** Sync accepted or rejected: [0x40][nonce:2][sync_id:2][status:1][detail_len:2][detail:N] */ export declare const S2C_FS_SYNCED = 64; /** State diff: [0x41][sync_id:2][update_id:4][flags:1][records:LZ4] */ export declare const S2C_FS_UPDATE = 65; /** Fetch response: [0x42][nonce:2][status:1][data:LZ4] */ export declare const S2C_FS_FILE = 66; /** Sync terminated: [0x43][sync_id:2][reason:1] */ export declare const S2C_FS_CLOSED = 67; /** `S2C_HELLO` feature bit: server supports the `FS_*` message family, * reads and writes alike. A read-only deployment (`BLIT_FS_WRITE=0` on * the server) still advertises this bit and answers writes with * `FS_DONE_PERMISSION`. */ export declare const FEATURE_FS: number; /** `sync_id` reported by a failed `FS_SYNCED`. */ export declare const FS_SYNC_ID_INVALID = 65535; export declare const FS_SYNC_RECURSIVE: number; export declare const FS_SYNC_CONTENT: number; export declare const FS_SYNC_CROSS_FILESYSTEM: number; /** The root is a single FILE (docs/design/fs-watch.md "Single-file sync"): * the mirror holds exactly one entry keyed `""`. Mutually exclusive with * `RECURSIVE` — the combination is rejected server-side. */ export declare const FS_SYNC_SINGLE: number; /** Resolve the sync's base directory from a pty's live cwd: a trailing * `[src_pty_id:2]` names a pty and the server joins `path` onto its cwd * (docs/ide.md Decision 3). It comes last, after any `EXCLUDE` field. */ export declare const FS_SYNC_FROM_PTY: number; /** Omit every entry whose final component is exactly `.git` — directory or * gitfile — from enumeration, hashing, hints, and records. A pure name * filter: no git data is read (docs/design/fs-watch.md "Ignoring"). */ export declare const FS_SYNC_EXCLUDE_GIT: number; /** Honor `.gitignore` in and above the root, plus the governing * repository's `$GIT_DIR/info/exclude`, the user's `core.excludesFile`, * and its `core.ignorecase`. */ export declare const FS_SYNC_GITIGNORE: number; /** A trailing `[exclude_len:2][exclude:M]` carries client patterns — * gitignore syntax, one per line, anchored at the sync root and applied * above every other rule, so `!keep` re-includes. The flag is what makes * the field parseable, and what makes a server too old to filter refuse * the sync instead of silently mirroring the whole tree. */ export declare const FS_SYNC_EXCLUDE: number; /** Honor `.ignore` in and above the root — ripgrep's convention, which a * project uses to hide things from tooling without telling git to stop * tracking them. Separate from `GITIGNORE` because the two answer * different questions, and `.ignore` brings none of git's * repository-wide sources with it. */ export declare const FS_SYNC_DOTIGNORE: number; /** Root the sync at this connection's drag staging dir instead of a server * path: the path is ignored (sent empty), the dir is auto-created, and it * lives until the connection closes. Browser drag-and-drop stages files * here so `C2S_SURFACE_DRAG_DROP` can name them without inlining their * bytes. Carries no trailer; invalid with `FS_SYNC_FROM_PTY`. */ export declare const FS_SYNC_STAGING: number; /** Begin a staged snapshot: apply this and subsequent records to an empty * staging map instead of the live map. */ export declare const FS_UPDATE_RESET: number; /** Atomically replace the live map with the staging map (no-op without one). */ export declare const FS_UPDATE_SYNC: number; export declare const FS_STATUS_OK = 0; export declare const FS_STATUS_NOT_FOUND = 1; export declare const FS_STATUS_PERMISSION_DENIED = 2; export declare const FS_STATUS_RESOURCE_LIMIT = 3; export declare const FS_STATUS_OTHER = 4; export declare const FS_FILE_OK = 0; export declare const FS_FILE_NOT_FOUND = 1; export declare const FS_FILE_UNREADABLE = 2; export declare const FS_FILE_OTHER = 3; export declare const FS_CLOSED_CLIENT_REQUEST = 0; export declare const FS_CLOSED_ROOT_GONE = 1; export declare const FS_CLOSED_PERMISSION_LOST = 2; export declare const FS_CLOSED_BACKEND_FAILED = 3; export declare const FS_CLOSED_RESOURCE_LIMIT = 4; /** Client-side pseudo-reason: the connection dropped or was re-established. * Sync state does not survive reconnects — re-`syncFs`. */ export declare const FS_CLOSED_CONNECTION_LOST = -1; /** Human-readable `S2C_FS_SYNCED` failure status. */ export declare function fsStatusText(status: number, detail: string): string; /** Rejection from a refused `FS_SYNC` open, carrying the wire status and * detail so callers can pick a fallback without parsing the message — * e.g. a `single` open refused by a pre-`FS_SYNC_SINGLE` server (any * status other than not-found/permission) falls back to a directory * sync. The message stays `Sync failed: ${fsStatusText(...)}`. */ export declare class FsOpenError extends Error { readonly status: number; readonly detail: string; constructor(status: number, detail: string); } /** Human-readable `S2C_FS_FILE` failure status. */ export declare function fsFileStatusText(status: number): string; export declare const FS_RECORD_UPSERT = 1; export declare const FS_RECORD_DELETE = 2; export declare const FS_RECORD_MOVE = 3; export declare const FS_ENTRY_TYPE_MASK = 3; export declare const FS_ENTRY_FILE = 0; export declare const FS_ENTRY_DIR = 1; export declare const FS_ENTRY_SYMLINK = 2; export declare const FS_ENTRY_OTHER = 3; /** Entry exists but its content could not be read. */ export declare const FS_ENTRY_UNREADABLE: number; /** Content omitted: over `inline_max` or the sync did not request content. */ export declare const FS_ENTRY_NO_CONTENT: number; /** File changed repeatedly while being read; content omitted, another * upsert follows once it settles. */ export declare const FS_ENTRY_UNSTABLE: number; /** Set on an `FS_ENTRY_SYMLINK` whose target is a directory, which the sync * enumerates like any other. The type alone cannot distinguish a link to a * directory from one to a file, so this is what tells a tree the entry is * expandable. */ export declare const FS_ENTRY_LINK_DIR: number; /** Set on a directory whose enumeration skipped at least one child the * sync's exclusion rules cover. Excluded paths are absent rather than * marked, so without this a client cannot tell an empty directory from a * filtered one — a file tree needs it to say "some items hidden". * * Prompt when it goes up, lazy when it comes down: the first excluded * child costs one re-listing of its directory, while the last one * disappearing clears the flag only at that directory's next * enumeration. So a tree may briefly show "hidden items" on a directory * that no longer has any. */ export declare const FS_ENTRY_FILTERED: number; export declare const FS_CONTENT_NONE = 0; export declare const FS_CONTENT_FULL = 1; export declare const FS_CONTENT_DELTA = 2; /** Fixed part of `C2S_FS_SYNC`, up to and including `path_len`. */ export declare const FS_SYNC_HEADER = 13; export declare function buildFsSyncMessage(nonce: number, flags: number, latencyMs: number, inlineMax: number, path: string, srcPtyId?: number, /** Gitignore-syntax patterns, one per line. Empty omits the field. */ exclude?: string): Uint8Array; export declare function buildFsStopMessage(syncId: number): Uint8Array; export declare function buildFsAckMessage(syncId: number, updateId: number): Uint8Array; export declare function buildFsFetchMessage(nonce: number, syncId: number, path: string): Uint8Array; export declare const C2S_FS_SEARCH = 70; export declare const S2C_FS_SEARCH = 69; /** [0x46][nonce:2][limit:2][root_len:2][root:N][query_len:2][query:N] */ export declare function buildFsSearchMessage(nonce: number, limit: number, root: string, query: string): Uint8Array; /** [0x45][nonce:2][status:1][count:2] repeated{ [path_len:2][path:N] } */ export declare function parseFsSearchResult(data: Uint8Array): { nonce: number; status: number; paths: string[]; } | null; export declare const C2S_FS_INDEX = 71; export declare const S2C_FS_INDEX = 70; /** The walk hit a budget; the list is a prefix of the tree, so callers * should keep server-side search for this root. */ export declare const FS_INDEX_TRUNCATED: number; /** Protocol cap on `count` — a larger claim is malformed. Without it, a * hostile count of tiny records forces millions of decode calls from a * small frame (the decompression guard bounds bytes, not record counts). */ export declare const FS_INDEX_MAX_COUNT = 1000000; /** A fetched candidate list: root-relative paths, sorted, * gitignore-filtered server-side. */ export type FsFileIndex = { paths: string[]; truncated: boolean; }; /** [0x47][nonce:2][flags:1][root_len:2][root:N] — flags reserved (0). */ export declare function buildFsIndexMessage(nonce: number, root: string): Uint8Array; /** [0x46][nonce:2][status:1][flags:1][count:4][paths:LZ4] where the * decompressed payload is repeated{ [path_len:2][path:N] }. Applies the * standard decompression guard; null = malformed, over-sized, or a * payload that disagrees with `count`. */ export declare function parseFsIndexResult(data: Uint8Array): { nonce: number; status: number; flags: number; paths: string[]; } | null; export declare const C2S_FS_READ = 77; export declare const S2C_FS_READ = 72; /** Answer each group with the first path in it that can be read. */ export declare const FS_READ_FIRST: number; /** Answer which path, not what is in it. */ export declare const FS_READ_NO_CONTENT: number; export declare const FS_READ_MAX_PATHS = 512; /** One answered path. `content` is empty unless `status` is `FS_FILE_OK`, and * always empty when the request asked for no content. */ export type FsReadRecord = { status: number; path: string; content: Uint8Array; }; /** * [0x4D][nonce:2][flags:1][max_bytes:4][group_count:2] then group_count × * ( [path_count:2] then path_count × [path_len:2][path:N] ). * * A group is one question: with `FS_READ_FIRST` each is answered by its own * first readable path, which is how one message resolves a search path per name. */ export declare function buildFsReadMessage(nonce: number, flags: number, maxBytes: number, groups: readonly (readonly string[])[]): Uint8Array; /** [0x48][nonce:2][status:1][count:2][records:LZ4] where the decompressed * payload is repeated{ [status:1][path_len:2][path:N][size:4][data:size] }. * Applies the standard decompression guard; null = malformed. */ export declare function parseFsReadResult(data: Uint8Array): { nonce: number; status: number; records: FsReadRecord[]; } | null; export declare const C2S_FS_GREP = 72; export declare const S2C_FS_GREP = 71; /** Match case exactly. Unset (the default) is case-insensitive. */ export declare const FS_GREP_CASE_SENSITIVE: number; /** `query` is a regex. Unset (the default) treats it as a literal string. */ export declare const FS_GREP_REGEX: number; /** Search gitignored files too, ranked after every tracked one. Unset (the * default) skips them — on a real repo that is the difference between * milliseconds and seconds. */ export declare const FS_GREP_NO_IGNORE: number; /** Match only whole words — the pattern is wrapped in `\b(?:…)\b` after * literal escaping, so it composes with either mode. */ export declare const FS_GREP_WORD: number; /** A budget clipped the search: matches exist that are not in this * response. Exact — set only when something was actually dropped. */ export declare const FS_GREP_TRUNCATED: number; export declare const FS_GREP_RECORD_FILE = 1; export declare const FS_GREP_RECORD_MATCH = 2; /** The file is gitignored. It is still searched — ignore rules rank rather * than filter here — but sorts after every non-ignored file. */ export declare const FS_GREP_FILE_IGNORED: number; export type FsGrepRecord = /** FILE 0x01: [kind:1][flags:1][n:2][path_len:2][path:N] — the next `n` * match records belong to this file. */ { kind: "file"; flags: number; n: number; path: string; } /** MATCH 0x02: [kind:1][line:4][col:4][end_line:4][end_col:4][text_len:4][text:N]. * 0-based lines, UTF-8 byte columns — an LSP-shaped range. `endLine` * differs from `line` when the pattern matched across a newline, and * `text` then holds every line the match spans, joined by `\n`. */ | { kind: "match"; line: number; col: number; endLine: number; endCol: number; text: string; }; /** One file's hits, as the UI consumes them. */ export interface FsGrepFile { /** Root-relative path. */ path: string; /** Gitignored — ranked last, and a client may dim it. */ ignored: boolean; matches: { line: number; col: number; endLine: number; endCol: number; text: string; }[]; } export interface FsGrepResult { files: FsGrepFile[]; /** A budget clipped the search. */ truncated: boolean; } export interface FsGrepOptions { /** Match case exactly; default is case-insensitive. */ caseSensitive?: boolean; /** Treat the query as a regex; default is a literal string. */ regex?: boolean; /** Include gitignored files, ranked last. Default respects ignore rules, * which is what keeps a search of a repo with build output fast. */ noIgnore?: boolean; /** Match whole words only. */ word?: boolean; /** Cap on total matches; 0/omitted means the server default. */ maxMatches?: number; /** Cap on matches from any one file; 0/omitted means the server default. */ maxPerFile?: number; } /** Build a `C2S_FS_GREP`: * [0x48][nonce:2][flags:1][max_matches:2][max_per_file:2][root_len:2][root:N][query_len:2][query:N] */ export declare function buildFsGrepMessage(nonce: number, root: string, query: string, opts?: FsGrepOptions): Uint8Array; /** * Decode an uncompressed `FS_GREP` records payload. Unknown kinds are * skipped via `record_len`; a record whose body overruns ends iteration, * matching the Rust codec. */ export declare function fsGrepRecords(data: Uint8Array): Generator; /** Parse an `S2C_FS_GREP`: * [0x47][nonce:2][status:1][flags:1][detail_len:2][detail:N][records:LZ4]. * Applies the standard decompression guard; null = malformed. */ export declare function parseFsGrepResult(data: Uint8Array): { nonce: number; status: number; flags: number; detail: string; files: FsGrepFile[]; } | null; export declare const C2S_FS_WRITE = 68; export declare const C2S_FS_OP = 69; export declare const S2C_FS_DONE = 68; export declare const FS_DONE_OK = 0; export declare const FS_DONE_NOT_FOUND = 2; export declare const FS_DONE_WRONG_TYPE = 3; export declare const FS_DONE_PERMISSION = 4; export declare const FS_DONE_TOO_LARGE = 5; export declare const FS_DONE_BUDGET = 6; export declare const FS_DONE_INVALID = 7; export declare const FS_DONE_OTHER = 9; /** A precondition failed; `FsDone.hash` carries the current on-disk hash. */ export declare const FS_DONE_CONFLICT = 11; /** Chunked upload: the chunk's offset is not the server's resume point; * the reply's `received` field names where to resend from. */ export declare const FS_DONE_OFFSET_MISMATCH = 128; /** Chunked upload: the assembled size does not match the declared size. */ export declare const FS_DONE_SIZE_MISMATCH = 129; /** Chunked upload: the `upload_id` is unknown (never began, finished, or * cancelled already). */ export declare const FS_DONE_UNKNOWN_UPLOAD = 130; /** Human-readable `FS_DONE` status. */ export declare function fsDoneStatusText(status: number): string; export declare const FS_WRITE_NO_CAS: number; export declare const FS_WRITE_MKPARENTS: number; export declare const FS_WRITE_DURABLE: number; export declare const FS_WRITE_FOLLOW_SYMLINK: number; export declare const FS_WRITE_CONTENT_FULL = 1; export declare const FS_WRITE_CONTENT_DELTA = 2; export declare const FS_OP_MKDIR = 1; export declare const FS_OP_REMOVE = 2; export declare const FS_OP_RENAME = 3; /** Create or retarget a symlink at `b` targeting the verbatim string `a`; * a symlink's content hash is BLAKE3-128 of its target bytes. */ export declare const FS_OP_SYMLINK = 4; /** Create a hard link at `b` to the regular file at `a`. */ export declare const FS_OP_HARDLINK = 5; export declare const FS_OP_NO_CAS: number; export declare const FS_OP_MKPARENTS: number; export interface FsWriteArgs { nonce: number; syncId: number; flags: number; /** CAS precondition hash (0n = create-exclusive; ignored under NO_CAS). */ base: bigint; mode: number; contentKind: number; path: string; content: Uint8Array; } export declare function buildFsWriteMessage(a: FsWriteArgs): Uint8Array; export interface FsOpArgs { nonce: number; syncId: number; op: number; flags: number; base: bigint; mode: number; a: string; b: string; } export declare function buildFsOpMessage(o: FsOpArgs): Uint8Array; export interface FsDone { nonce: number; status: number; /** Post-op content hash on success; current on-disk hash on CONFLICT. */ hash: bigint; mtimeNs: bigint; } /** Parse an `S2C_FS_DONE`; null = malformed or wrong opcode. */ export declare function parseFsDoneMessage(msg: Uint8Array): FsDone | null; /** Build an `FS_DONE` (tests and mock servers). */ export declare function buildFsDoneMessage(nonce: number, status: number, hash: bigint, mtimeNs: bigint): Uint8Array; /** Begin a chunked upload: [0x49][nonce:2][sync_id:2][flags:1][mode:4][size:8][path_len:2][path:N] */ export declare const C2S_FS_UPLOAD_BEGIN = 73; /** One chunk: [0x4a][upload_id:2][offset:8][data:LZ4] */ export declare const C2S_FS_UPLOAD_CHUNK = 74; /** Commit an upload: [0x4b][nonce:2][upload_id:2] */ export declare const C2S_FS_UPLOAD_FINISH = 75; /** Abandon an upload (no reply): [0x4c][upload_id:2] */ export declare const C2S_FS_UPLOAD_CANCEL = 76; /** Begin accepted or rejected: [0x49][nonce:2][status:1][upload_id:2] */ export declare const S2C_FS_UPLOAD_BEGIN = 73; /** Chunk ack: [0x4a][upload_id:2][status:1][received:8] */ export declare const S2C_FS_UPLOAD_CHUNK = 74; /** Commit result: [0x4b][nonce:2][status:1][hash:16][mtime_ns:8] — the * `FS_DONE` payload shape on success. */ export declare const S2C_FS_UPLOAD_FINISH = 75; export declare const FS_UPLOAD_NO_CAS: number; export declare const FS_UPLOAD_MKPARENTS: number; export declare const FS_UPLOAD_DURABLE: number; export declare const FS_UPLOAD_FOLLOW_SYMLINK: number; export interface FsUploadBeginArgs { nonce: number; syncId: number; flags: number; /** Precondition, exactly as FS_WRITE's base: ignored under NO_CAS; * 0 without NO_CAS = create-exclusive; otherwise CAS against the * current content hash. Checked at BEGIN (fail fast) and re-verified * at FINISH before the rename. */ base: bigint; mode: number; /** Total plaintext bytes to be uploaded. */ size: number; path: string; } export declare function buildFsUploadBeginMessage(a: FsUploadBeginArgs): Uint8Array; /** Build a `C2S_FS_UPLOAD_CHUNK`; `data` is the plaintext chunk, compressed * with the same lz4-prepend-size framing as `FS_WRITE` content. */ export declare function buildFsUploadChunkMessage(uploadId: number, offset: number, data: Uint8Array): Uint8Array; export declare function buildFsUploadFinishMessage(nonce: number, uploadId: number): Uint8Array; export declare function buildFsUploadCancelMessage(uploadId: number): Uint8Array; export interface FsUploadBeginReply { nonce: number; status: number; uploadId: number; /** Current on-disk content hash when `status` is CONFLICT, 0 otherwise * (same convention as `FsDone.hash`). */ hash: bigint; mtimeNs: bigint; } /** Parse an `S2C_FS_UPLOAD_BEGIN`; null = malformed or wrong opcode. */ export declare function parseFsUploadBeginReply(msg: Uint8Array): FsUploadBeginReply | null; export interface FsUploadChunkAck { uploadId: number; status: number; /** Cumulative plaintext bytes accepted; on `FS_DONE_OFFSET_MISMATCH`, * the resume point to resend from. */ received: number; } /** Parse an `S2C_FS_UPLOAD_CHUNK` ack; null = malformed or wrong opcode. */ export declare function parseFsUploadChunkAck(msg: Uint8Array): FsUploadChunkAck | null; export interface FsUploadFinishReply { nonce: number; status: number; /** Post-write content hash on success (same slot as `FsDone.hash`). */ hash: bigint; /** The hash's raw 16 wire bytes (little-endian u128), for callers that * want bytes rather than a bigint. */ hashBytes: Uint8Array; mtimeNs: bigint; } /** Parse an `S2C_FS_UPLOAD_FINISH`; null = malformed or wrong opcode. */ export declare function parseFsUploadFinishReply(msg: Uint8Array): FsUploadFinishReply | null; /** Build an `S2C_FS_UPLOAD_BEGIN` (tests and mock servers). */ export declare function buildFsUploadBeginReply(nonce: number, status: number, uploadId: number, hash?: bigint, mtimeNs?: bigint): Uint8Array; /** Build an `S2C_FS_UPLOAD_CHUNK` ack (tests and mock servers). */ export declare function buildFsUploadChunkAck(uploadId: number, status: number, received: number): Uint8Array; /** Build an `S2C_FS_UPLOAD_FINISH` (tests and mock servers). */ export declare function buildFsUploadFinishReply(nonce: number, status: number, hash: bigint, mtimeNs: bigint): Uint8Array; /** * Cap on any single LZ4-decompressed fs payload, mirroring the Rust guard: * the declared size is checked *before* allocating, so a hostile or corrupt * length cannot force a giant allocation. Large trees arrive as many * bounded updates, never one huge one. */ export declare const FS_MAX_DECOMPRESSED: number; /** * Decompress an lz4_flex `compress_prepend_size` payload * (`[uncompressed_len:4][lz4 block]`), refusing declared sizes over * {@link FS_MAX_DECOMPRESSED}. Returns null on any malformation. */ export declare function fsDecompress(data: Uint8Array): Uint8Array | null; /** * Compress with a literal-only LZ4 block (always valid, never smaller than * the input) in `compress_prepend_size` framing. Enough to build * `FS_UPDATE`/`FS_FILE` messages in tests and mock servers; real servers * use a full encoder. */ export declare function fsCompressLiteral(data: Uint8Array): Uint8Array; /** * Compress into a standard LZ4 block (greedy hash-table matcher) in * `compress_prepend_size` framing — the C2S counterpart of * {@link fsDecompress}, also decodable by the server's lz4_flex. Honors * the block end rules (the last 5 bytes stay literals, no match starts * within the last 12), so tiny inputs and inputs that never match fall * back to the literal-only encoding. */ export declare function fsCompress(data: Uint8Array): Uint8Array; /** One decoded record from an `FS_UPDATE` payload. */ export type FsRecord = { kind: "upsert"; path: string; entryFlags: number; size: number; /** Nanoseconds since the epoch; exceeds 2^53, hence bigint. */ mtimeNs: bigint; mode: number; /** BLAKE3 truncated to 128 bits; 0n for non-files or unknown. */ hash: bigint; content: FsContent; } /** Remove `path` and every path under it. */ | { kind: "delete"; path: string; } /** Rename the `from` subtree to `to`. */ | { kind: "move"; from: string; to: string; }; export type FsContent = { kind: "none"; } | { kind: "full"; data: Uint8Array; } /** LEB128 instruction stream against the last content this client acked * for this path: 0x01 COPY [offset][len], 0x02 INSERT [len][bytes]. */ | { kind: "delta"; ops: Uint8Array; }; /** Append one record to an uncompressed `FS_UPDATE` records buffer. */ export declare function appendFsRecord(buf: number[], record: FsRecord): void; /** * Decode records from an uncompressed `FS_UPDATE` payload. Unknown kinds * are skipped via `record_len`; a malformed record ends iteration (the * update is applied up to that point and the rest dropped — * forward-compatible with future record extensions). */ export declare function fsRecords(data: Uint8Array): Generator; /** Build an `FS_UPDATE` from an uncompressed records buffer (tests/mocks). */ export declare function buildFsUpdateMessage(syncId: number, updateId: number, flags: number, records: Uint8Array): Uint8Array; /** * Parse an `S2C_FS_FILE` message (starting at the opcode byte). Applies the * standard decompression guard; null = malformed or over-sized. */ export declare function parseFsFileMessage(msg: Uint8Array): { nonce: number; status: number; data: Uint8Array; } | null; export interface FsSyncOptions { /** Watch the whole subtree (default) or only the root's immediate children. */ recursive?: boolean; /** The root is a single FILE (docs/design/fs-watch.md "Single-file * sync"): the mirror holds exactly one entry keyed `""` (the file * itself). Delete/rename-away arrives as `DELETE ""` with the sync * staying open, recreate as `UPSERT ""`; fetches and writes address * path `""`. Mutually exclusive with `recursive` (`syncFs` throws on * the combination); a server predating the flag refuses the open — * the rejection is an {@link FsOpenError} so callers can fall back. */ single?: boolean; /** Attach file bytes to upserts (hashes always sync). */ content?: boolean; /** Descend into mount points. */ crossFilesystem?: boolean; /** Shorthand for `gitignore`, `dotIgnore` and `excludeGit` together — * what "ignore what the repo ignores" usually means. Off by default, * so a sync only narrows when asked; on a checkout it is the * difference between mirroring the work tree and mirroring * `node_modules` and `.git` too. */ ignore?: boolean; /** Honor `.gitignore` in and above the root, plus the governing * repository's `$GIT_DIR/info/exclude`, the user's `core.excludesFile`, * and its `core.ignorecase`. */ gitignore?: boolean; /** Honor `.ignore` files (ripgrep's convention), which bring none of * git's repository-wide sources with them. */ dotIgnore?: boolean; /** Omit `.git` directories and gitfiles. A pure name filter — no git * data is read — and usually what you want alongside `ignore`, since * `.git` is not in anyone's `.gitignore`. */ excludeGit?: boolean; /** Extra gitignore-syntax patterns, anchored at the sync root and * applied above every other rule, so `"!keep"` re-includes something * the ignore files hide. Excluded paths are never enumerated, hashed, * or counted against the server's entry budget. */ exclude?: string[]; /** Batching/settle window in ms; 0 = server default (20). */ latencyMs?: number; /** Per-file inline content cap in bytes; 0 = server default (16 MiB). */ inlineMax?: number; /** Called for each applied record (the mirror already reflects it). */ onRecord?: (record: FsRecord) => void; /** A staged snapshot began (`RESET`): the server is restaging instead of * diffing. Only consumers replaying records into their own map care. */ onReset?: () => void; /** The live map is coherent: initial snapshot done, or a restage swapped in. */ onSync?: () => void; /** The live map changed. Updates that only accumulate in the staging * map during a `RESET`…`SYNC` restage don't fire this (or the reactive * notifier) — the `SYNC` swap does, once. */ onUpdate?: () => void; /** The sync ended: an `FS_CLOSED` reason, or * {@link FS_CLOSED_CONNECTION_LOST} when the connection dropped. */ onClosed?: (reason: number) => void; /** Resolve the sync's base directory from this session's live cwd: `path` * is joined onto the source pty's server-side cwd, so the tree follows * `cd` (docs/ide.md Decision 3). The session must be on the same * connection as the sync. */ fromSessionId?: SessionId; /** Root the sync at this connection's drag staging dir * ({@link FS_SYNC_STAGING}): `path` is ignored and sent empty, the dir * is auto-created server-side, and it lives until the connection * closes. Browser drag-and-drop stages dropped files here so a DROP * message names them instead of inlining their bytes. Invalid with * `fromSessionId` — `syncFs` throws on the combination. */ staging?: boolean; } /** A live sync established by `BlitConnection.syncFs`. */ /** Options for {@link FsSyncHandle.writeFile}. */ export interface FsWriteOptions { /** CAS: write only if the current content hash equals this (from * `live.get(path)?.hash`). Mutually exclusive with `create`/`force`. */ ifHash?: bigint; /** The exact bytes this client believes are on disk — the content the * nonzero `ifHash` hashes. When set, the write is encoded as a * single-span delta against them when clearly smaller * (docs/design/fs-write.md content_kind 2); otherwise it goes out * full, unchanged. The ops apply against the bytes the CAS * precondition names, so `force`, `create`, or a missing/zero * `ifHash` rejects client-side. A pre-delta server answers INVALID * and the write retries once automatically as a full write with the * same precondition; only the retry's outcome surfaces. */ deltaBase?: Uint8Array; /** Create-exclusive: fail with a conflict if the path already exists. */ create?: boolean; /** Overwrite unconditionally, ignoring any precondition. */ force?: boolean; /** File mode (e.g. 0o644); omitted/0 preserves the existing mode. */ mode?: number; /** Create missing parent directories. */ createParents?: boolean; /** fsync the file and its parent before resolving. */ durable?: boolean; } /** Options for {@link FsSyncHandle.upload}. */ export interface FsUploadOptions { /** Unix mode for a created file; omitted/0 preserves the default. */ mode?: number; /** Create missing parent directories. */ createParents?: boolean; /** fsync the file and its parent before resolving. */ durable?: boolean; /** CAS: upload only if the current content hash equals this. Checked * at BEGIN (fail fast, before bytes flow) and re-verified at FINISH; * a mismatch rejects with an {@link FsConflictError} carrying the * current on-disk hash. Mutually exclusive with `create`/`force`. */ ifHash?: bigint; /** Create-exclusive: fail with CONFLICT when the target exists. */ create?: boolean; /** Overwrite unconditionally (the default when neither `ifHash` nor * `create` is given). */ force?: boolean; /** Plaintext bytes per chunk; default 256 KiB. Each chunk rides its own * transport frame, LZ4-compressed. Chunks are small and at most 512 KiB * is left unacked on the wire, so interactive input sharing the * connection is not stuck behind a large upload backlog. */ chunkSize?: number; /** Progress in cumulative plaintext bytes accepted by the server. */ onProgress?: (uploaded: number, total: number) => void; /** Aborting sends `FS_UPLOAD_CANCEL` and rejects the promise. */ signal?: AbortSignal; } /** Result of a successful chunked upload. */ export interface FsUploadResult { /** Post-write content hash: the raw 16 wire bytes (little-endian u128). */ hash: Uint8Array; /** The same hash as a bigint (matches `FsWriteResult.hash`). */ hashU128: bigint; /** Modification time in nanoseconds since the epoch (number; loses * sub-microsecond precision — use `mtimeNs` when exactness matters). */ mtime: number; /** Modification time in nanoseconds since the epoch, full precision. */ mtimeNs: bigint; } /** Options for {@link FsSyncHandle.symlink} / {@link FsSyncHandle.hardlink}. */ export interface FsLinkOptions { /** Replace only if the current entry's content hash equals this (a * symlink's hash covers its target bytes: `live.get(path)?.hash`). */ ifHash?: bigint; /** Replace unconditionally. Without `ifHash`/`force`, creation is * exclusive: an existing entry rejects with {@link FsConflictError}. */ force?: boolean; /** Create missing parent directories. */ createParents?: boolean; } /** Result of a successful write/mkdir. */ export interface FsWriteResult { /** Post-op content hash (0n for a directory). */ hash: bigint; mtimeNs: bigint; } export interface FsSyncHandle extends ReactiveStore { readonly syncId: number; /** Canonical root path on the server. */ readonly root: string; /** The mirrored tree: wire path → node, "" = the root itself. * Replaced wholesale when a staged snapshot swaps in — re-read after * `onSync`, don't retain across callbacks. */ readonly live: ReadonlyMap; /** Pull one file's full content (for `FS_ENTRY_NO_CONTENT` entries). */ fetch(path: string): Promise; /** Write a file (docs/design/fs-write.md). `path` is the wire/mirror-key * form (as in `live`). Rejects with an {@link FsConflictError} carrying * the current on-disk hash when a precondition fails. On success the * returned hash is also recorded as {@link lastWrittenHash} so the * matching echo can be recognized. Every full-content write goes out as * a chunked {@link upload} — paced so it can't stall interactive input * sharing the connection; only delta writes (`deltaBase`) still use a * single FS_WRITE frame, small by construction. */ writeFile(path: string, data: Uint8Array, options?: FsWriteOptions): Promise; /** Upload a file as an ordered run of chunks (the `FS_UPLOAD_*` family) * for content too large — or too inconvenient — for a single * `writeFile` frame. Accepts a `Blob` (e.g. a dropped `File`) and reads * it slice by slice, so the whole file is never held in memory at once. * Chunks are pipelined a few frames ahead; `onProgress` reports the * server's cumulative ack. Resolves from the FINISH reply; rejects on an * error status or abort (which also sends `FS_UPLOAD_CANCEL`). The * returned hash is recorded as {@link lastWrittenHash}, like a write. */ upload(path: string, data: Uint8Array | Blob, opts?: FsUploadOptions): Promise; /** Create a directory. */ mkdir(path: string, options?: { mode?: number; createParents?: boolean; }): Promise; /** Remove a file or subtree; `ifHash` makes it conditional on a file. */ remove(path: string, options?: { ifHash?: bigint; }): Promise; /** Rename/move a file or subtree. */ rename(from: string, to: string, options?: { createParents?: boolean; }): Promise; /** Create — or, with `ifHash`/`force`, atomically retarget — a symlink * at `path` pointing at the verbatim string `target` (relative, * absolute, or dangling; never resolved by the sync). The returned * hash covers the target bytes and is recorded as * {@link lastWrittenHash} for self-echo suppression. */ symlink(target: string, path: string, options?: FsLinkOptions): Promise; /** Create a hard link at `path` to the regular file at `source` (both * wire paths under the root). */ hardlink(source: string, path: string, options?: FsLinkOptions): Promise; /** The hash of this handle's most recent successful `writeFile` at * `path`, for self-echo suppression: when an incoming UPSERT's `hash` * equals this, the change is this handle's own write and the editor * model already holds it (never `setValue` your own echo). Scoped to * the handle, not the shared sync — another handle's write on the same * file is an external change to this one. The entry is dropped once * that echo has been delivered to every callback, so check it inside * `onRecord` (or a subscriber), not later. */ lastWrittenHash(path: string): bigint | undefined; /** Release this handle. Wire-identical opens share one server sync, so * the wire stop goes out with the last handle; `onClosed` fires with * client-request either way. */ stop(): void; } /** Rejection from a write/op whose precondition failed. `hash` is the * current on-disk content hash — rebase against it and retry. */ export declare class FsConflictError extends Error { readonly hash: bigint; constructor(hash: bigint); } /** One node in a mirrored tree. */ export interface FsNode { entryFlags: number; size: number; mtimeNs: bigint; mode: number; hash: bigint; /** Present when the sync requested content and the file fits the inline * limit. `null` does not mean empty — check `entryFlags`. */ content: Uint8Array | null; } /** Outcome of one applied `FS_UPDATE`. */ export interface FsApplyResult { /** The update_id to acknowledge. */ updateId: number; /** Whether `live` changed: false while records only accumulate in the * staging map during a `RESET`…`SYNC` restage, true on the `SYNC` swap * or a direct (non-staged) mutation. */ liveChanged: boolean; } /** * The complete client obligation: apply updates, read `live`. * * Paths are relative to the sync root, `/`-separated, "" = the root itself. */ export declare class FsMirror { live: Map; private staging; /** The staging map while a `RESET`…`SYNC` restage is in flight, else * null. Record consumers joining a shared sync mid-restage replay it * to synthesize a coherent join point; everyone else reads `live`. */ get staged(): ReadonlyMap | null; /** * Apply one `FS_UPDATE` message (starting at the opcode byte). * Returns the update_id to acknowledge, or null if malformed. */ applyUpdate(msg: Uint8Array): number | null; /** * Like {@link applyUpdate}, but also reports whether `live` changed and * optionally collects each decoded record into `records` — one * decompress + decode shared by the mirror and per-record callbacks. */ apply(msg: Uint8Array, records?: FsRecord[]): FsApplyResult | null; } /** * Single-span delta, the client mirror of the server encoder * (crates/fssync/src/lib.rs `encode_delta`): the longest common prefix * and suffix become `COPY`s, the middle an `INSERT` — an instruction * stream {@link applyFsDelta} decodes back to `next`. Covers appends, * prepends, truncations, and one contiguous in-place edit; scattered * edits degrade to a large `INSERT`, so callers only send the delta when * it is clearly smaller than the full content. */ export declare function encodeFsDelta(base: Uint8Array, next: Uint8Array): Uint8Array; /** Apply a content delta (LEB128 COPY/INSERT instruction stream) to a base. */ export declare function applyFsDelta(base: Uint8Array, ops: Uint8Array): Uint8Array | null; //# sourceMappingURL=fs.d.ts.map