/**
* Fresh Editor TypeScript Plugin API
*
* This file provides type definitions for the Fresh editor's TypeScript plugin system.
* Plugins have access to the global `editor` object which provides methods to:
* - Query editor state (buffers, cursors, viewports)
* - Modify buffer content (insert, delete text)
* - Add visual decorations (overlays, highlighting)
* - Interact with the editor UI (status messages, prompts)
*
* AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
* Generated by fresh-plugin-api-macros + ts-rs from JsEditorApi impl
*/
/**
* Get the editor API instance.
* Plugins must call this at the top of their file to get a scoped editor object.
*/
declare function getEditor(): EditorAPI;
/**
* Register a function as a named handler on the global scope.
*
* Handler functions registered this way can be referenced by name in
* `editor.registerCommand()`, `editor.on()`, and mode keybindings.
*
* The `fn` parameter is typed as `Function` because the runtime passes
* different argument shapes depending on the caller: command handlers
* receive no arguments, event handlers receive an event-specific data
* object (e.g. `{ buffer_id: number }`), and prompt handlers receive
* `{ prompt_type: string, input: string }`. Type-annotate your handler
* parameters to match the event you are handling.
*
* @param name - Handler name (referenced by registerCommand, on, etc.)
* @param fn - The handler function
*/
declare function registerHandler(name: string, fn: Function): void;
/** Handle for a cancellable async operation */
interface ProcessHandle<T> extends PromiseLike<T> {
	/** Promise that resolves to the result when complete */
	readonly result: Promise<T>;
	/** Cancel/kill the operation. Returns true if cancelled, false if already completed */
	kill(): Promise<boolean>;
}
/** Buffer identifier */
type BufferId = number;
/** Split identifier */
type SplitId = number;
/**
* Payload delivered to handlers registered with `editor.on("mouse_click", ...)`.
*
* All coordinate fields are in cell (terminal character) units. `buffer_*`
* fields are `null` when the click did not land in any buffer panel.
*/
interface MouseClickHookArgs {
	/** Screen column (0-indexed). */
	column: number;
	/** Screen row (0-indexed). */
	row: number;
	/** Mouse button: "left", "right", "middle". */
	button: string;
	/** Modifier keys (e.g. "shift"). */
	modifiers: string;
	/** X offset of the content area the click landed in. */
	content_x: number;
	/** Y offset of the content area the click landed in. */
	content_y: number;
	/** Buffer under the click, or `null` when outside any buffer panel. */
	buffer_id: number | null;
	/** 0-indexed buffer row (line number) of the click, accounting for scroll. */
	buffer_row: number | null;
	/** 0-indexed byte column inside the buffer row. */
	buffer_col: number | null;
}
/**
* Registry of typed plugin APIs surfaced through
* `editor.exportPluginApi` / `editor.getPluginApi`.
*
* Plugins that want their surface to be typed for downstream
* consumers augment this interface in their own source:
*
* ```ts
* // in my_plugin.ts
* export type MyPluginApi = { doThing(): void };
* declare global {
*   interface FreshPluginRegistry {
*     "my-plugin": MyPluginApi;
*   }
* }
* ```
*
* `editor.getPluginApi("my-plugin")` then returns
* `MyPluginApi | null` without any `as`-cast on the consumer side.
* Plugins that skip the augmentation still work — the untyped
* `getPluginApi<T = unknown>(name: string): T | null` overload
* takes over.
*
* Each plugin's augmentation is emitted to
* `<config_dir>/types/plugins.d.ts` at load time (via oxc's
* isolated-declarations), so init.ts sees every loaded plugin's
* registry entry automatically.
*/
interface FreshPluginRegistry {}
type TextPropertyEntry = {
	/**
	* Text content for this entry
	*/
	text: string;
	/**
	* Optional properties attached to this text (e.g., file path, line number)
	*/
	properties?: Record<string, unknown>;
	/**
	* Optional whole-entry styling
	*/
	style?: Partial<OverlayOptions>;
	/**
	* Optional sub-range styling within this entry
	*/
	inlineOverlays?: Array<InlineOverlay>;
	/**
	* Pad this entry's text with spaces to this many columns when drawing.
	*
	* **Render-only**: the padding is applied at draw time, so
	* `getBufferText()` returns the unpadded text you supplied. Column
	* alignment cannot be checked by reading the buffer back — if you need
	* that, embed real spaces with `padEnd` instead.
	*
	* See `TextPropertyEntry::pad_to_chars`.
	*/
	padToChars?: number;
	/**
	* Truncate this entry's text to at most this many columns when drawing,
	* with an ellipsis when the budget allows one.
	*
	* **Render-only**, like `padToChars`: `getBufferText()` returns the full
	* untruncated text.
	*
	* See `TextPropertyEntry::truncate_to_chars`.
	*/
	truncateToChars?: number;
	/**
	* See `TextPropertyEntry::segments`.
	*/
	segments?: Array<StyledSegment>;
};
type TsCompositeLayoutConfig = {
	/**
	* Layout type: "side-by-side", "stacked", or "unified"
	*/
	type: string;
	/**
	* Width ratios for side-by-side (e.g., [0.5, 0.5])
	*/
	ratios?: Array<number>;
	/**
	* Show separator between panes
	*/
	showSeparator: boolean;
	/**
	* Spacing for stacked layout
	*/
	spacing?: number;
};
type TsCompositeSourceConfig = {
	/**
	* Buffer ID of the source buffer (required)
	*/
	bufferId: number;
	/**
	* Label for this pane (e.g., "OLD", "NEW")
	*/
	label: string;
	/**
	* Whether this pane is editable
	*/
	editable: boolean;
	/**
	* Style configuration
	*/
	style: TsCompositePaneStyle | null;
};
type TsCompositePaneStyle = {
	/**
	* Background color for added lines (RGB)
	* Using [u8; 3] instead of (u8, u8, u8) for better rquickjs_serde compatibility
	*/
	addBg?: [number, number, number];
	/**
	* Background color for removed lines (RGB)
	*/
	removeBg?: [number, number, number];
	/**
	* Background color for modified lines (RGB)
	*/
	modifyBg?: [number, number, number];
	/**
	* Gutter style: "line-numbers", "diff-markers", "both", or "none"
	*/
	gutterStyle?: string;
};
type TsCompositeHunk = {
	/**
	* Starting line in old buffer (0-indexed)
	*/
	oldStart: number;
	/**
	* Number of lines in old buffer
	*/
	oldCount: number;
	/**
	* Starting line in new buffer (0-indexed)
	*/
	newStart: number;
	/**
	* Number of lines in new buffer
	*/
	newCount: number;
	/**
	* Per-line operations for the hunk, in git order: one char per line —
	* `' '` context, `'-'` deletion (old only), `'+'` addition (new only).
	* When present, the side-by-side alignment follows git's classification
	* exactly (unchanged lines stay paired); when absent, the host falls back
	* to a positional pairing. Optional for backward compatibility.
	*/
	ops?: string;
};
type TsCreateCompositeBufferOptions = {
	/**
	* Buffer name (displayed in tabs/title)
	*/
	name: string;
	/**
	* Mode for keybindings
	*/
	mode: string;
	/**
	* Layout configuration
	*/
	layout: TsCompositeLayoutConfig;
	/**
	* Source pane configurations
	*/
	sources: Array<TsCompositeSourceConfig>;
	/**
	* Diff hunks for alignment (optional)
	*/
	hunks: Array<TsCompositeHunk> | null;
	/**
	* When set, the first render will scroll to center the Nth hunk (0-indexed).
	* This avoids timing issues with imperative scroll commands that depend on
	* render-created state (viewport dimensions, view state).
	*/
	initialFocusHunk?: number;
};
type ViewportInfo = {
	/**
	* Byte position of the first visible line
	*/
	topByte: number;
	/**
	* Line number of the first visible line (None when line index unavailable, e.g. large file before scan)
	*/
	topLine: number | null;
	/**
	* Left column offset (horizontal scroll)
	*/
	leftColumn: number;
	/**
	* Viewport width
	*/
	width: number;
	/**
	* Viewport height
	*/
	height: number;
};
type ScreenSize = {
	width: number;
	height: number;
};
type KeyEventPayload = {
	/**
	* Key name (e.g. `"a"`, `"escape"`, `"f1"`).
	*/
	key: string;
	/**
	* Ctrl held.
	*/
	ctrl: boolean;
	/**
	* Alt held.
	*/
	alt: boolean;
	/**
	* Shift held (only meaningful for non-character keys; for
	* printable characters the case is already encoded in `key`).
	*/
	shift: boolean;
	/**
	* Super / Cmd / Meta held.
	*/
	meta: boolean;
};
type SplitSnapshot = {
	/**
	* Stable split identifier; matches the values used by
	* `setSplitBuffer`, `focusSplit`, `getSplitByLabel`, etc.
	*/
	splitId: number;
	/**
	* Buffer currently shown in this split.
	*/
	bufferId: BufferId;
	/**
	* Label set by `setSplitLabel`, when this pane has one. Reported here so
	* a later script can re-find a pane it named earlier — the ids change
	* across restarts, the label is what the caller chose.
	*/
	label: string | null;
	/**
	* Column of this pane's left edge, in terminal cells, measured from
	* the left edge of the editor area. This is what answers "which pane
	* is on the left" — compare `x` between panes rather than guessing
	* from list order.
	*/
	x: number;
	/**
	* Row of this pane's top edge, in terminal cells, measured from the
	* top of the editor area. Compare `y` to tell top from bottom.
	*/
	y: number;
	/**
	* Pane width in cells, separator excluded.
	*/
	width: number;
	/**
	* Pane height in cells, separator excluded.
	*/
	height: number;
	/**
	* Viewport (top byte / dimensions) for this split's active buffer.
	* This is the *text* viewport: it excludes the tab bar and any
	* gutter, so it is smaller than the pane rect above.
	*/
	viewport: ViewportInfo;
};
type SplitCreated = {
	/**
	* The new pane's id — pass to `openFileInSplit`, `focusSplit`, ...
	*/
	splitId: number;
	/**
	* The pane the split was created from, still live and now smaller.
	*/
	sourceSplitId: number;
	/**
	* Buffer shown in the new pane.
	*/
	bufferId: BufferId;
	/**
	* Geometry of the new pane (see `SplitSnapshot`).
	*/
	x: number;
	y: number;
	width: number;
	height: number;
};
type SplitWindowOptions = {
	/**
	* Divider orientation. Default `"vertical"` — panes side by side.
	*/
	direction?: SplitAxis;
	/**
	* Which side the new pane lands on. Default `"after"`.
	*/
	place?: SplitPlacement;
	/**
	* First child's share of the space, 0.0–1.0. Default 0.5.
	* "First" is the left/top pane regardless of `place`.
	*/
	ratio?: number;
	/**
	* Open this file in the new pane. Relative paths resolve against the
	* window's root. When omitted the new pane shows the same buffer as
	* the pane it was split from, which is what the keyboard split does.
	*/
	file?: string;
	/**
	* Leave focus where it was instead of moving it into the new pane.
	* Default false (the new pane takes focus, matching the keyboard
	* split).
	*/
	keepFocus?: boolean;
};
type SplitAxis = "vertical" | "horizontal";
type SplitPlacement = "before" | "after";
type LineTarget = {
	/**
	* Row in the source buffer, 0-indexed, that carries this target.
	*/
	line: number;
	/**
	* File to open. Relative paths resolve against the window's root.
	*/
	path: string;
	/**
	* Line to land on in that file, 0-indexed. Defaults to the top.
	*/
	target?: number;
	/**
	* Label of the pane to open into (`setSplitLabel`). When the label
	* names no live pane — or is omitted — the editor opens beside the
	* buffer holding the targets rather than replacing it, so an index
	* never eats its own pane.
	*/
	into?: string;
};
type PaneDescription = {
	/**
	* Pass to `openFileInSplit`, `focusSplit`, `setSplitRatio`, ...
	*/
	splitId: number;
	/**
	* Buffer shown in this pane.
	*/
	bufferId: BufferId;
	/**
	* `"terminal"` (a PTY), `"file"` (backed by a path), or `"virtual"`
	* (a plugin-owned scratch buffer).
	*/
	kind: string;
	/**
	* Label set by `setSplitLabel`, when this pane has one — how a script
	* finds a pane it named in an earlier run.
	*/
	label: string | null;
	/**
	* Absolute path when this pane shows a file, else `None`.
	*/
	path: string | null;
	/**
	* Short label — the file name, or the buffer's name for the rest.
	*/
	name: string;
	/**
	* Whether this pane has focus.
	*/
	active: boolean;
	/**
	* Unsaved changes.
	*/
	modified: boolean;
	/**
	* On-screen geometry, in editor-area cells. Panes are listed left to
	* right, top to bottom, so `panes[0]` is the leftmost/topmost; `x`
	* and `y` say so precisely.
	*/
	x: number;
	y: number;
	width: number;
	height: number;
};
type WorkspaceDescription = {
	/**
	* Working directory of the active window.
	*/
	cwd: string;
	/**
	* The window this script is pointed at.
	*/
	windowId: bigint;
	/**
	* Durable id of that window — the one still valid after a restart.
	*/
	stableId: string;
	/**
	* Every open workspace, so a script can tell whether the thing it
	* wants is in another window.
	*/
	windows: Array<WindowInfo>;
	/**
	* The active window's panes, in visual order (left to right, top to
	* bottom).
	*/
	panes: Array<PaneDescription>;
	/**
	* Which pane has focus; also flagged on the pane itself.
	*/
	activeSplitId: number;
};
type LayoutHints = {
	/**
	* Optional compose width for centering/wrapping
	*/
	composeWidth?: number;
	/**
	* Optional column guides for aligned tables
	*/
	columnGuides?: Array<number>;
};
type ViewTokenWire = {
	/**
	* Source byte offset in the buffer. None for injected content (annotations).
	*/
	source_offset: number | null;
	/**
	* The token content
	*/
	kind: ViewTokenWireKind;
	/**
	* Optional styling for injected content (only used when source_offset is None)
	*/
	style?: ViewTokenStyle;
};
type ViewTokenWireKind = {
	"Text": string;
} | "Newline" | "Space" | "Break" | {
	"BinaryByte": number;
};
type TokenColor = [number, number, number] | string;
type ViewTokenStyle = {
	/**
	* Foreground color. Either `[r, g, b]` or a named/theme string —
	* see [`TokenColor`].
	*/
	fg: TokenColor | null;
	/**
	* Background color. Either `[r, g, b]` or a named/theme string —
	* see [`TokenColor`].
	*/
	bg: TokenColor | null;
	/**
	* Whether to render in bold
	*/
	bold: boolean;
	/**
	* Whether to render in italic
	*/
	italic: boolean;
	/**
	* Whether to render with underline
	*/
	underline: boolean;
};
type PromptSuggestion = {
	/**
	* The text to display
	*/
	text: string;
	/**
	* Optional description
	*/
	description?: string;
	/**
	* The value to use when selected (defaults to text if None)
	*/
	value?: string;
	/**
	* Whether this suggestion is disabled (greyed out, defaults to false)
	*/
	disabled?: boolean;
	/**
	* Optional styled rendering of `description`. When present, the
	* suggestion list renders these spans (in order) in place of the
	* plain `description` text — letting a plugin highlight a portion
	* of the row, e.g. the symbol word inside a code-line snippet.
	*/
	description_spans?: Array<StyledText>;
	/**
	* Optional keyboard shortcut
	*/
	keybinding?: string;
};
type DirEntry = {
	/**
	* File/directory name
	*/
	name: string;
	/**
	* True if this is a file
	*/
	is_file: boolean;
	/**
	* True if this is a directory
	*/
	is_dir: boolean;
};
type BufferInfo = {
	/**
	* Buffer ID
	*/
	id: number;
	/**
	* File path (if any)
	*/
	path: string;
	/**
	* The buffer's display name — what the tab shows.
	*
	* For a file buffer this is the filename (or project-relative path). For
	* a **virtual buffer it is the `name` you passed to
	* `createVirtualBuffer`**, which is how a plugin finds its own panel
	* again: `listBuffers().find(b => b.is_virtual && b.name === "…")`.
	* Before this field existed the only handle was
	* `is_virtual && path === ""`, which cannot tell two plugins' panels
	* apart — or two panels of your own.
	*/
	name: string;
	/**
	* Whether the buffer has been modified
	*/
	modified: boolean;
	/**
	* Length of buffer in bytes
	*/
	length: number;
	/**
	* Number of lines, when the buffer has been indexed. `None` for a very
	* large file whose line index hasn't been built yet — the one case
	* where the count genuinely isn't known.
	*
	* Worth reading after writing content: a buffer built from spans that
	* forgot their newlines reports a plausible `length` and one line,
	* which is otherwise only visible by looking at the screen.
	*/
	line_count: number | null;
	/**
	* Whether this is a virtual buffer (not backed by a file)
	*/
	is_virtual: boolean;
	/**
	* Whether this buffer is a live terminal (a PTY, not text). Terminal
	* buffers are also `is_virtual`; this distinguishes "a shell is
	* running here" from a plugin-owned scratch buffer, which is what
	* `describeWorkspace()` reports as the pane's `kind`.
	*/
	is_terminal: boolean;
	/**
	* Whether editing is disabled for this buffer.
	*/
	editing_disabled: boolean;
	/**
	* Current view mode of the active split: "source" or "compose"
	*/
	view_mode: string;
	/**
	* True if any split showing this buffer has compose mode enabled.
	* Plugins should use this (not `view_mode`) to decide whether to maintain
	* decorations, since decorations live on the buffer and are filtered
	* per-split at render time.
	*/
	is_composing_in_any_split: boolean;
	/**
	* Compose width (if set), from the active split's view state
	*/
	compose_width: number | null;
	/**
	* The detected language for this buffer (e.g., "rust", "markdown", "text")
	*/
	language: string;
	/**
	* Whether this tab was opened in "preview" (ephemeral) mode — true when
	* opened via single-click in the file explorer and not yet committed
	* (no edit, no double-click, no tab-click, no layout change). Plugins
	* that react to buffer lifecycle events should generally treat preview
	* buffers as transient; e.g. a diagnostics panel may want to skip
	* refreshing itself for a preview tab.
	*/
	is_preview: boolean;
	/**
	* Split ids that currently hold this buffer (empty when the buffer is
	* open but not visible in any split — e.g. background-opened tabs
	* that haven't been focused). Lets plugins implement "focus existing
	* buffer if visible, else open new" without having to track split
	* ids across editor restarts (which reassign them). The list is a
	* snapshot at the last `update_plugin_state_snapshot` tick.
	*/
	splits: number[];
};
type WindowInfo = {
	/**
	* Stable session id. The base session is always `1`.
	*/
	id: number;
	/**
	* Durable workspace identity (`ws-…`), minted once when the workspace is
	* created and carried in its on-disk snapshot. Unlike `id` — a per-process
	* handle re-derived at every boot — this survives restarts, relabels and
	* moves, so it is the id to hand out to anything that must still mean the
	* same workspace later (an agent recording where it put its work, say).
	* Empty only for a legacy workspace file written before stable ids.
	*/
	stable_id: string;
	/**
	* User-visible label (defaults to root basename).
	*/
	label: string;
	/**
	* Absolute project root.
	*/
	root: string;
	/**
	* Project this session belongs to — the canonical repo root
	* (or arbitrary directory) the user pointed the new-session
	* form at. For sessions without an explicit project (legacy
	* sessions, the launch session, sessions created outside the
	* orchestrator's new-session form) this equals `root` — the
	* host normalises at the API boundary so plugins never have
	* to deal with `null`/`undefined`/`""` ambiguity (`??` only
	* falls through on `null`, but the orchestrator's
	* `WindowInfo` round-trips a `Some(PathBuf::new())` as `""`,
	* which then becomes a poisoned lex sort key — observed as
	* the Windows-only dock reorder).
	*/
	project_path: string;
	/**
	* `true` when the session shares its working tree with
	* other sessions (worktree-creation was off at session
	* time, or the session lives in a non-git directory).
	* Persistence-only field; defaults to `false` and isn't
	* emitted when false.
	*/
	shared_worktree?: boolean;
	/**
	* Remote backend identity when this session's backend is not
	* host-local (SSH / Kubernetes). Carried for live remote windows
	* *and* for dormant (not-yet-connected / disconnected) sessions, so
	* the dock can badge a restored SSH session before any connection
	* exists. `None` for local sessions and plugin-managed backends
	* (devcontainer), whose facet the owning plugin supplies itself.
	*/
	remote?: RemoteBackendInfo | null;
};
type RemoteBackendInfo = {
	/**
	* Backend kind: `"ssh"` or `"kubernetes"`.
	*/
	kind: string;
	/**
	* Short human identity for the row (e.g. `deploy@build-01`,
	* `ns/pod`).
	*/
	detail: string;
	/**
	* `true` when the backend connection is currently live; `false` for a
	* dormant session (restored from disk, not yet connected, or whose
	* last connect failed).
	*/
	connected: boolean;
};
type JsDiagnostic = {
	/**
	* Document URI
	*/
	uri: string;
	/**
	* Diagnostic message
	*/
	message: string;
	/**
	* Severity: 1=Error, 2=Warning, 3=Info, 4=Hint, null=unknown
	*/
	severity: number | null;
	/**
	* Range in the document
	*/
	range: JsRange;
	/**
	* Source of the diagnostic (e.g., "typescript", "eslint")
	*/
	source?: string;
};
type JsRange = {
	/**
	* Start position
	*/
	start: JsPosition;
	/**
	* End position
	*/
	end: JsPosition;
};
type JsPosition = {
	/**
	* Zero-indexed line number
	*/
	line: number;
	/**
	* Zero-indexed character offset
	*/
	character: number;
};
type ActionSpec = {
	/**
	* Action name (e.g., "move_word_right", "delete_line")
	*/
	action: string;
	/**
	* Number of times to repeat the action (default 1)
	*/
	count: number;
	/**
	* Action payload arguments for actions that carry data, e.g.
	* `{ "char": "x" }` for `insert_char` or `{ "text": "hello" }` for
	* `prompt_confirm_with_text`. Empty/absent for the common no-arg
	* actions (motions, edits, commands). This is what lets a recorded
	* macro — which contains `InsertChar` and other payload actions —
	* round-trip losslessly through `executeActions`.
	*/
	args: Record<string, unknown>;
};
type TsActionPopupAction = {
	/**
	* Unique action identifier (returned in ActionPopupResult)
	*/
	id: string;
	/**
	* Display text for the button (can include command hints)
	*/
	label: string;
};
type ActionPopupOptions = {
	/**
	* Unique identifier for the popup (used in ActionPopupResult)
	*/
	id: string;
	/**
	* Title text for the popup
	*/
	title: string;
	/**
	* Body message (supports basic formatting)
	*/
	message: string;
	/**
	* Action buttons to display
	*/
	actions: Array<TsActionPopupAction>;
	/**
	* Optional buffer to scope the popup to. When set, the popup only
	* renders while that buffer is active (and is dismissed when the buffer
	* closes), rather than floating over every buffer. Omit for global
	* notifications like install help raised from a status-bar click.
	*/
	buffer_id?: number;
};
type AddMenuItemOptions = {
	/**
	* Target menu, matched against each menu's stable `id` ("View",
	* "File", …) first and its display label second. A menu that matches
	* neither is left alone and the call is a no-op.
	*/
	menu: string;
	/**
	* Row label, already localised by the plugin (`editor.t(…)`).
	*/
	label: string;
	/**
	* Action dispatched when the row is chosen. A name the editor doesn't
	* know is routed to the plugin action of the same name — i.e. the
	* handler registered with `registerHandler`.
	*/
	action: string;
	/**
	* Menu-context key whose boolean value renders the row's checkmark
	* (e.g. `"dock"`). Omit for a plain action row.
	*/
	checkbox?: string;
	/**
	* Menu-context key gating whether the row is enabled. Omit for a row
	* that is always available.
	*/
	when?: string;
	/**
	* Insert directly after the existing row whose action or label this
	* names. Ignored when nothing matches (the row is appended instead).
	*/
	after?: string;
	/**
	* Insert directly before the existing row whose action or label this
	* names. Ignored when `after` is set, or when nothing matches.
	*/
	before?: string;
};
type TsLspMenuItem = {
	/**
	* Stable identifier used as the `action_id` in the resulting
	* `action_popup_result` event (prefixed by `{plugin_id}|`).
	*/
	id: string;
	/**
	* Display label shown in the popup row.
	*/
	label: string;
};
type FileExplorerDecoration = {
	/**
	* File path to decorate
	*/
	path: string;
	/**
	* Symbol to display (e.g., "●", "M", "A")
	*/
	symbol: string;
	/**
	* Color as RGB array or theme key string (e.g., "ui.file_status_added_fg")
	*/
	color: OverlayColorSpec;
	/**
	* Priority for display when multiple decorations exist (higher wins)
	*/
	priority: number;
};
type FileExplorerSlotEntry = {
	/**
	* File or directory path to override.
	*/
	path: string;
	/**
	* Optional leading-slot override.
	*/
	leading: FileExplorerLeadingSlot | null;
	/**
	* Explicitly suppress the compatibility leading slot for this path.
	*/
	suppressLeading: boolean;
	/**
	* Optional trailing-slot override.
	*/
	trailing: FileExplorerTrailingSlot | null;
	/**
	* Explicitly suppress the compatibility trailing slot for this path.
	*/
	suppressTrailing: boolean;
	/**
	* Optional filename colour override.
	*/
	nameColor: OverlayColorSpec | null;
	/**
	* Explicitly suppress compatibility filename colouring for this path.
	*/
	suppressNameColor: boolean;
	/**
	* Priority for display when multiple overrides exist (higher wins).
	*/
	priority: number;
};
type FileExplorerLeadingSlot = {
	/**
	* Text shown in the leading slot (for example, an icon glyph).
	*/
	text: string;
	/**
	* Foreground colour for the leading slot.
	*/
	color: OverlayColorSpec;
	/**
	* Minimum display width reserved for the leading slot.
	*/
	minWidth: number;
};
type FileExplorerTrailingSlot = {
	/**
	* Text shown in the trailing slot (for example, a badge glyph).
	*/
	text: string;
	/**
	* Foreground colour for the trailing slot.
	*/
	color: OverlayColorSpec;
	/**
	* Optional tooltip shown when hovering the trailing slot.
	*/
	tooltip: FileExplorerTooltip | null;
};
type FileExplorerTooltip = {
	/**
	* Tooltip title shown in the popup border.
	*/
	title: string;
	/**
	* Body lines shown inside the popup.
	*/
	lines: Array<string>;
};
type FormatterPackConfig = {
	/**
	* Command to run (e.g., "prettier", "rustfmt")
	*/
	command: string;
	/**
	* Arguments to pass to the formatter
	*/
	args: Array<string>;
};
type ProcessLimitsPackConfig = {
	/**
	* Maximum memory usage as percentage of total system memory (null = no limit)
	*/
	maxMemoryPercent: number | null;
	/**
	* Maximum CPU usage as percentage of total CPU (null = no limit)
	*/
	maxCpuPercent: number | null;
	/**
	* Enable resource limiting
	*/
	enabled: boolean | null;
};
type TerminalResult = {
	/**
	* The created buffer ID (for use with setSplitBuffer, etc.)
	*/
	bufferId: number;
	/**
	* The terminal ID (for use with sendTerminalInput, closeTerminal)
	*/
	terminalId: number;
	/**
	* The split ID (if created in a new split)
	*/
	splitId: number | null;
};
type CreateWindowWithTerminalOptions = {
	/**
	* Absolute path to the new session's worktree / project
	* root. Relative paths are rejected (logged, no window
	* created).
	*/
	root: string;
	/**
	* Human-readable label for the new session. When empty,
	* defaults to the basename of `root`.
	*/
	label: string;
	/**
	* Working directory for the spawned terminal. Defaults to
	* `root` when omitted.
	*/
	cwd?: string;
	/**
	* Argv to spawn directly inside the PTY. `None` keeps the
	* shell-and-type behaviour; `Some([cmd, ...args])` runs the
	* command as the PTY child (used by Orchestrator so the
	* agent process is the PTY's direct child).
	*/
	command?: Array<string>;
	/**
	* Tab title override. Defaults to `command[0]`'s basename
	* when `command` is set, or "Terminal N" otherwise.
	*/
	title?: string;
	/**
	* Argv to run on *restore* instead of re-running `command`, when
	* the session is reopened after an editor restart. Used by
	* Orchestrator agent-resume: a session launched with
	* `claude --session-id <id>` sets `resume` to
	* `["claude", "--resume", "<id>"]` (or `["claude", "--continue"]`),
	* so a restored session rejoins its conversation rather than starting
	* a fresh agent. `None` keeps `command` as the restore command. The id
	* is a plain argv element — never interpolated into a shell string.
	*/
	resume?: Array<string>;
	/**
	* Extra environment variables to set in the spawned
	* terminal's child process, on top of the inherited/activated
	* env. Applied after the editor's control vars (`TERM`,
	* `FRESH_SESSION`), so a plugin's entry wins over those only
	* when it names the same key. `None` (the default) adds
	* nothing — old callers behave exactly as before.
	*/
	env?: { [key in string] : string };
	/**
	* When set, the host mints an unforgeable capability token bound
	* to the NEW window and injects it into the spawned terminal as
	* `FRESH_CMD_TOKEN`. A client presenting that token over the
	* control socket may drive this window by submitting scripts.
	* `false` (the default) mints no token and injects nothing.
	*
	* The grant is all-or-nothing on purpose: a script can call
	* anything the plugin API exposes, so a narrower list would
	* describe a boundary that isn't there.
	*/
	allowScript?: boolean;
	/**
	* Seed the terminal into this **existing** window — one created by
	* `createPreparingWindow` — instead of opening a new one. The window
	* keeps its id, its durable `stableId`, and everything keyed off them
	* (a manual rename, its folder, its dock position), so a workspace the
	* user has been looking at (and organising) since they asked for it
	* becomes the live session rather than being replaced by one.
	*
	* `root` still applies: a workspace opens as a placeholder before its
	* worktree exists, so adopting it re-roots the window at the directory
	* that was finally created. Ignored — and the call falls back to
	* creating a fresh window — when the id names no preparing window.
	*/
	adoptWindow?: number;
};
type SessionWithTerminalResult = {
	/**
	* The new window's id — a per-process handle, valid until this editor
	* exits. Use `stableId` for anything that has to outlive the process.
	*/
	windowId: number;
	/**
	* The new workspace's durable identity (`ws-…`), stable across restarts.
	*/
	stableId: string;
	/**
	* The seeded terminal's id (for `sendTerminalInput`, etc.).
	*/
	terminalId: number;
	/**
	* The seeded terminal buffer's id.
	*/
	bufferId: number;
};
type CreatePreparingWindowOptions = {
	/**
	* Absolute path the placeholder window roots at. It must exist — use
	* the project directory when the workspace's own directory is what is
	* still being created; the adopt step re-roots the window onto the
	* final directory.
	*/
	root: string;
	/**
	* Human-readable label. Empty defaults to the basename of `root`.
	*/
	label: string;
	/**
	* Progress line shown on the placeholder page.
	*/
	message: string;
	/**
	* Focus the new window immediately. `false` (the default) builds it in
	* the background and leaves the user where they are.
	*/
	activate?: boolean;
};
type PreparingWindowResult = {
	/**
	* The new window's id — a per-process handle, valid until this editor
	* exits.
	*/
	windowId: number;
	/**
	* The new workspace's durable identity (`ws-…`), stable across restarts.
	*/
	stableId: string;
};
type CreateTerminalOptions = {
	/**
	* Working directory for the terminal (defaults to editor cwd)
	*/
	cwd?: string;
	/**
	* Split direction: `"horizontal"` or `"vertical"` (default:
	* `"vertical"`).
	*
	* The name describes the **divider**, not the arrangement:
	* `"vertical"` puts the panes side by side, `"horizontal"` stacks them.
	*/
	direction?: string;
	/**
	* Split ratio 0.0-1.0 (default: 0.5)
	*/
	ratio?: number;
	/**
	* Whether to focus the new terminal split (default: true)
	*/
	focus?: boolean;
	/**
	* Whether this terminal is part of the user's persisted workspace.
	* Defaults to `false` for plugin-created terminals — they are typically
	* one-off tool UIs (rebuilds, exec shells, build output) and should
	* start with empty scrollback on each invocation. Set to `true` only
	* when the plugin owns a terminal that the user should see restored
	* across editor restarts.
	*/
	persistent?: boolean;
	/**
	* Optional session id to attach the new terminal buffer to.
	* Defaults to the active session at creation time. Setting this
	* lets Orchestrator and similar plugins spawn a terminal *into* an
	* inactive session (e.g. an agent in a worktree the user hasn't
	* dived into yet). The terminal's split is created in that
	* session's stashed split tree; the buffer is attached to the
	* target session's membership set rather than the active one's.
	*/
	windowId?: WindowId;
	/**
	* Argv to spawn directly inside the PTY instead of the host's
	* configured shell. `None` (default) keeps the historical
	* behaviour: spawn the user's shell and let the caller type into
	* it via `sendTerminalInput`. `Some([cmd, ...args])` runs that
	* exact command as the PTY child — no shell middleman, so the
	* process exits cleanly when the agent does and the
	* terminal-buffer's `terminal_exit` plugin hook reflects the
	* agent's real exit status. Used by Orchestrator so a session
	* with agent `python3` is just python3 in the PTY rather than
	* bash-running-python3-as-a-subshell-command.
	*/
	command?: Array<string>;
	/**
	* Tab title for the terminal buffer. Defaults to `command[0]`
	* (when `command` is set) or `"Terminal N"` (the historical
	* auto-numbered title). If another terminal in the same window
	* already uses the requested title, the host appends `" (k)"`
	* to disambiguate. Empty string is treated the same as `None`.
	*/
	title?: string;
	/**
	* Extra environment variables to set in the spawned terminal's
	* child process, on top of the inherited/activated env. Mirrors
	* `CreateWindowWithTerminalOptions::env`. `None` (the default)
	* adds nothing, so existing callers behave exactly as before.
	*/
	env?: { [key in string] : string };
	/**
	* Argv to run when this terminal is *restored* or *restarted*,
	* instead of re-running `command`. The exact counterpart of
	* `CreateWindowWithTerminalOptions::resume`, so an agent launched
	* into an existing window rejoins its conversation on restart the
	* same way one born in its own window does — a session started with
	* `claude --session-id <id>` sets `resume` to
	* `["claude", "--resume", "<id>"]`. `None` keeps `command` as the
	* restore argv. The id is a plain argv element — never interpolated
	* into a shell string.
	*
	* Setting `command` (with or without `resume`) also marks the
	* terminal as a restorable *session* terminal, so it survives a
	* workspace save even when `persistent` is false — the same
	* exception `createWindowWithTerminal` relies on.
	*/
	resume?: Array<string>;
	/**
	* When set, the host mints an unforgeable capability token bound
	* to the TARGET window (the active window, or `windowId` when
	* set) and injects it into the spawned terminal as
	* `FRESH_CMD_TOKEN` (alongside `FRESH_SESSION`). This lets an
	* agent spawned into an *existing* window drive it by submitting
	* scripts — the same capability a `createWindowWithTerminal`
	* agent gets. `false` (the default) mints no token and injects
	* nothing.
	*
	* The grant is all-or-nothing on purpose: a script can call
	* anything the plugin API exposes, so a narrower list would
	* describe a boundary that isn't there.
	*/
	allowScript?: boolean;
};
type CursorInfo = {
	/**
	* Byte position of the cursor
	*/
	position: number;
	/**
	* Selection range (if any)
	*/
	selection: {
		start: number;
		end: number;
	} | null;
	/**
	* 0-indexed line number of the cursor. `null` when the line index is
	* unavailable — e.g. a huge file whose line scan hasn't completed, where
	* the editor positions purely by byte offset. Plugins must treat `null`
	* as "unknown", never as line 0.
	*/
	line: number | null;
};
type OverlayOptions = {
	/**
	* Foreground color - RGB array or theme key string
	*/
	fg?: OverlayColorSpec | null;
	/**
	* Background color - RGB array or theme key string
	*/
	bg?: OverlayColorSpec | null;
	/**
	* Whether to render with underline
	*/
	underline: boolean;
	/**
	* Whether to render in bold
	*/
	bold: boolean;
	/**
	* Whether to render in italic
	*/
	italic: boolean;
	/**
	* Whether to render with strikethrough
	*/
	strikethrough: boolean;
	/**
	* Whether to extend background color to end of line
	*/
	extendToLineEnd: boolean;
	/**
	* Whether to render with reverse video (fg/bg swapped). Used for
	* block-style text carets in form controls, where a hardware
	* cursor isn't available (modal overlays).
	*/
	reversed: boolean;
	/**
	* When `true`, `fg` is applied only on cells whose existing fg
	* matches this overlay's resolved bg — i.e. a same-colour fg/bg
	* collision. Lets a row-wide overlay stay legible on tokens that
	* share the bg's colour without repainting unrelated tokens.
	*/
	fgOnCollisionOnly: boolean;
	/**
	* Optional URL for OSC 8 terminal hyperlinks.
	* When set, the overlay text becomes a clickable hyperlink in terminals
	* that support OSC 8 escape sequences.
	*/
	url?: string | null;
};
type OverlayColorSpec = [number, number, number] | string;
type InlineOverlay = {
	/**
	* Start offset within the entry's text. See `unit`.
	*/
	start: number;
	/**
	* End offset within the entry's text (exclusive). See `unit`.
	*/
	end: number;
	/**
	* Styling options for this range
	*/
	style: Partial<OverlayOptions>;
	/**
	* Optional properties for this sub-range (e.g., click target metadata)
	*/
	properties?: Record<string, any>;
	/**
	* Unit for `start` / `end`. Defaults to `byte`.
	*/
	unit?: OffsetUnit;
};
type OffsetUnit = "byte" | "char";
type StyledSegment = {
	/**
	* Verbatim text for this segment.
	*/
	text: string;
	/**
	* When set, the host emits an `InlineOverlay` covering this
	* segment's text in the final entry.
	*/
	style?: Partial<OverlayOptions>;
	/**
	* Additional overlays inside this segment. Offsets are in
	* the overlay's own `unit`, relative to the segment's start
	* (NOT the final entry text); the host shifts them by the
	* segment's position during concatenation.
	*/
	overlays?: Array<InlineOverlay>;
};
type GrammarInfoSnapshot = {
	/**
	* The grammar name as used in config files (case-insensitive matching)
	*/
	name: string;
	/**
	* Where this grammar was loaded from (e.g. "built-in", "plugin (myplugin)")
	*/
	source: string;
	/**
	* File extensions associated with this grammar
	*/
	file_extensions: Array<string>;
	/**
	* Optional short name alias (e.g., "bash" for "Bourne Again Shell (bash)")
	*/
	short_name: string | null;
};
type AnimationRect = {
	x: number;
	y: number;
	width: number;
	height: number;
};
type PluginAnimationEdge = "top" | "bottom" | "left" | "right";
type PluginAnimationKind = {
	"kind": "slideIn";
	from: PluginAnimationEdge;
	durationMs: number;
	delayMs: number;
};
type HintEntry = {
	/**
	* The key chord, e.g. `"Tab"`, `"Alt+P"`, `"Esc"`.
	*/
	keys: string;
	/**
	* The human-readable label for the action.
	*/
	label: string;
};
type ButtonKind = "normal" | "primary" | "danger";
type TreeNode = {
	/**
	* The pre-rendered row content (text + per-row overlays).
	* The host renders this verbatim after the indent + disclosure
	* prefix; plugin overlays are byte-shifted by the prefix
	* length.
	*/
	text: TextPropertyEntry;
	/**
	* 0-based depth — controls leading indent (`depth * 2` spaces).
	*/
	depth: number;
	/**
	* When true, render a disclosure glyph (`▶` collapsed / `▼`
	* expanded) and emit a hit area over it that fires the `expand`
	* event. Leaf nodes (`false`) get no glyph and no expand hit;
	* the row width occupies the full row.
	*/
	hasChildren: boolean;
	/**
	* Per-node checkbox state. Only rendered when the parent
	* `Tree` has `checkable: true`. `None` = no checkbox glyph;
	* `Some(true)` = `[v]`; `Some(false)` = `[ ]`. The plugin
	* owns the truth — the host fires `widget_event { event_type:
	* "toggle" }` and the plugin pushes the new state back via
	* `WidgetMutation::SetCheckedKeys`.
	*/
	checked?: boolean | null;
	/**
	* Continuation lines rendered below the node's primary `text`
	* line when the parent `Tree` has `item_height > 1`. Each entry
	* is one screen row, indented to align under the primary line's
	* body (past the indent + disclosure/checkbox prefix). The host
	* renders at most `item_height - 1` of them and blank-pads a
	* shorter node so every row in the tree is the same fixed height.
	* Ignored when `item_height == 1`.
	*/
	extraLines?: Array<TextPropertyEntry>;
};
type WidgetSpec = {
	"kind": "row";
	children: Array<WidgetSpec>;
	key?: string | null;
	/**
	* When true, children that don't fit on one line reflow onto
	* additional lines (growing the row's height) instead of being
	* truncated. Children are never split — wrap happens at child
	* boundaries — so wrap a logical group (e.g. a toggle + its
	* accelerator) in a nested non-wrapping `Row` to keep it intact.
	* Ignored when the row contains multi-line (block) children.
	*/
	wrap: boolean;
} | {
	"kind": "col";
	children: Array<WidgetSpec>;
	key?: string | null;
} | {
	"kind": "hintBar";
	entries: Array<HintEntry>;
	key?: string | null;
} | {
	"kind": "toggle";
	checked: boolean;
	label: string;
	focused: boolean;
	/**
	* Neither checked nor unchecked: renders a neutral `[-]` chip.
	* Used for values that are unset and inherit from a lower
	* config layer (issue #2345) — a definite `[ ]` would read as
	* "the user turned this off". Defaults to `false`.
	*/
	indeterminate: boolean;
	/**
	* Form-style layout: render `label: [v]` (label first, chip
	* after) instead of the default `[v] label`. In this layout
	* the toggle's hit area covers only the chip, so clicks on
	* the label don't flip the value. Defaults to `false`.
	*/
	labelFirst: boolean;
	/**
	* Pad the label to this display width in `label_first`
	* layout so a column of controls aligns their chips. `0` =
	* no padding. Defaults to `0`.
	*/
	labelWidth: number;
	key?: string | null;
} | {
	"kind": "number";
	/**
	* Initial value. Read at first render only; instance state
	* takes over thereafter.
	*/
	value: number;
	/**
	* Inclusive lower bound. Values clamp to it when set.
	*/
	min?: number | null;
	/**
	* Inclusive upper bound. Values clamp to it when set.
	*/
	max?: number | null;
	/**
	* Amount added / subtracted per step. Defaults to `1`.
	*/
	step: number;
	/**
	* Render the value as an integer (no decimal point). The
	* value is still carried as `f64`; only the display is
	* truncated. Defaults to `false`.
	*/
	integer: boolean;
	/**
	* Render the value as a percentage: display is `value * 100`
	* suffixed with `%` (so a stored `0.25` shows as `25%`).
	* Mirrors the Settings UI's float-as-percent controls.
	* Defaults to `false`.
	*/
	percent: boolean;
	/**
	* Optional label rendered before the value cell. Empty =
	* omitted.
	*/
	label?: string;
	/**
	* Whether this widget has visual focus. Initial-only once
	* the host owns focus (same as `Toggle`).
	*/
	focused: boolean;
	/**
	* Pad the label to this display width so a column of
	* controls aligns their value cells. `0` = no padding.
	*/
	labelWidth: number;
	/**
	* In-place edit buffer. `Some` = the value is being edited:
	* the cell renders this text (with caret / selection) instead
	* of the formatted value. `None` = display mode.
	*/
	editText?: string | null;
	/**
	* Byte offset of the edit caret within `edit_text`. `-1` =
	* no caret (ignored unless `edit_text` is `Some`).
	*/
	editCursor: number;
	/**
	* Selection byte range within `edit_text` (`start`, `end`).
	* `-1` for either end = no selection.
	*/
	editSelStart: number;
	editSelEnd: number;
	key?: string | null;
} | {
	"kind": "dropdown";
	/**
	* The selectable options, in display order.
	*/
	options: Array<string>;
	/**
	* Initial selected index into `options`. Read at first
	* render only; instance state takes over thereafter.
	* Clamped to `[0, options.len())`.
	*/
	selectedIndex: number;
	/**
	* Optional label rendered before the value button. Empty =
	* omitted.
	*/
	label?: string;
	/**
	* Whether this widget has visual focus. Initial-only once
	* the host owns focus.
	*/
	focused: boolean;
	/**
	* Pad the label to this display width so a column of
	* controls aligns their value buttons. `0` = no padding.
	*/
	labelWidth: number;
	/**
	* Whether the option list is expanded inline below the value
	* button (`▲` arrow + one row per visible option). Closed
	* (`▼`) by default.
	*/
	open: boolean;
	/**
	* First visible option row when `open` and the list is
	* taller than its window. Defaults to `0`.
	*/
	scrollOffset: number;
	key?: string | null;
} | {
	"kind": "dualList";
	/**
	* The full universe of selectable options.
	*/
	options: Array<DualListOption>;
	/**
	* Initial ordered set of included option values. Seed only;
	* instance state takes over after first render.
	*/
	included: Array<string>;
	/**
	* Option values owned by a sibling list — filtered out of
	* this list's Available column so the two never overlap.
	*/
	excluded: Array<string>;
	/**
	* Optional label rendered above the columns. Empty =
	* omitted.
	*/
	label?: string;
	/**
	* Whether this widget has visual focus. Initial-only once
	* the host owns focus.
	*/
	focused: boolean;
	/**
	* Which column the cursor sits in: `true` = Included,
	* `false` = Available. Seed only, like `included` — host
	* instance state takes over after first render. Hosts that
	* drive the control themselves (Settings) keep re-supplying
	* it so the rendered cursor tracks their own state.
	*/
	activeIncluded: boolean;
	/**
	* Cursor row within the Available column. Seed only (see
	* `active_included`).
	*/
	availableCursor: number;
	/**
	* Cursor row within the Included column. Seed only (see
	* `active_included`).
	*/
	includedCursor: number;
	/**
	* Optional one-line key hint rendered under the columns
	* (e.g. `↑↓:Move  Shift+←→:Add/Remove`). Empty = omitted.
	* The control's keys are not guessable from its shape, so
	* hosts are expected to supply their own localized copy.
	*/
	hint?: string;
	/**
	* Number of body rows the columns occupy. Plugin computes
	* from its viewport.
	*/
	visibleRows: number;
	key?: string | null;
} | {
	"kind": "button";
	label: string;
	focused: boolean;
	intent: ButtonKind;
	key?: string | null;
	/**
	* When true, the button renders in a muted style, is dropped
	* from the Tab cycle, and clicks on it are ignored. Use for
	* actions that aren't currently available against the
	* surrounding state (e.g. "Archive" on the base session). The
	* button still occupies its layout cell so the surrounding
	* row doesn't reshuffle when the disabled flag flips.
	*/
	disabled: boolean;
	/**
	* When false, the button is dropped from the Tab cycle (but
	* still renders and stays clickable). Used for radio-style
	* groups — a row of buttons where only the *active* option
	* should be a Tab stop and ←/→ moves the selection within
	* the group, so Tab advances one stop per group rather than
	* one stop per option. Defaults to true (ordinary buttons
	* are tabbable).
	*/
	focusable: boolean;
	/**
	* Render the label alone — no `[ ]` frame, no focus-marker
	* gutter — turning the button into a bare *icon affordance*
	* (a `×` close glyph, a `▾` chevron) rather than a framed
	* action. Use it where the glyph itself is the control and a
	* frame would read as clutter; keep the default `false` for
	* anything with a word on it.
	*
	* This controls layout only; `hover_style` controls how the
	* button looks under the pointer.
	*/
	bare: boolean;
	/**
	* Stretch the button across the full width it is laid out in
	* (the panel's content width, or its share of an enclosing
	* `Row`), padding the label with spaces — and truncating it
	* with an `…` when the width can't hold it.
	*
	* This exists because focus / hover paint the button's *own*
	* cells: a natural-width button leaves the rest of its row
	* unhighlighted even when the surrounding container pads the
	* row out (a `LabeledSection` pads every child to its inner
	* width). Dropdown and context-menu entries are rows of a
	* menu, not free-standing actions, so their highlight has to
	* span the row — set this on them and the host fills the row
	* at the width it actually rendered, with no plugin-side
	* width guess to drift on a resize or a dock drag.
	*
	* Leave it off for a free-standing action, and off for
	* anything inside an anchored popup that sizes itself to its
	* content — filling there stretches the popup to the whole
	* panel width.
	*/
	fullWidth: boolean;
	/**
	* Style applied while the pointer is over this button. `None`
	* (the default) leaves it looking the same hovered as not.
	*
	* Hover is host state — it changes with mouse motion and no
	* plugin round-trip — so the plugin declares the *appearance*
	* once in the spec and the host applies it as the pointer
	* moves. Nothing crosses the plugin bridge on a hover.
	*
	* It outranks focus styling while both apply: the pointer is
	* the more immediate signal, and the one the user is actively
	* driving.
	*
	* For a close glyph, `ui.tab_close_hover_fg` is the editor's
	* shared "close affordance under the pointer" key — the tab
	* `×` and the file explorer's `×` both read it, so a plugin
	* naming it gets the same highlight users already know.
	*
	* `Button` is the first kind to carry this; other widget kinds
	* adopt it with the same field plus a `ctx.is_hovered(key)`
	* check in their renderer.
	*/
	hoverStyle?: Partial<OverlayOptions>;
} | {
	"kind": "spacer";
	cols: number;
	flex: boolean;
	key?: string | null;
} | {
	"kind": "divider";
	/**
	* Glyph repeated across the full width. Defaults to `─`.
	*/
	ch: string;
	/**
	* Optional whole-rule styling (e.g. a dim `fg`). Same shape as a
	* styled segment's `style`.
	*/
	style?: Partial<OverlayOptions>;
	key?: string | null;
} | {
	"kind": "list";
	items: Array<TextPropertyEntry>;
	/**
	* Optional parallel array of per-item widget specs. When
	* non-empty it **overrides** `items`: each entry is rendered
	* via the normal widget renderer into a multi-row block
	* (e.g. a `LabeledSection` for a rounded "card"/"pill"), and
	* the list lays items out, selects, scrolls, and routes
	* clicks in *item* units — one card per logical item,
	* regardless of how many terminal rows it occupies. All
	* cards share a uniform height (the tallest item's row count;
	* shorter items pad). `item_keys` / `selected_index` are
	* still indexed per item. Interactive widgets nested inside a
	* card aren't routed yet — the whole card is one `select`
	* hit. Leave empty for the classic one-row-per-`items` list.
	*/
	itemSpecs?: Array<WidgetSpec>;
	itemKeys: Array<string>;
	selectedIndex: number;
	/**
	* Number of rows of the panel's available height the list
	* should occupy. `None` (omitted) = auto: the host sizes the
	* window from the panel height it already knows, so the
	* plugin never re-derives layout arithmetic. An explicit
	* value pins the window to that many rows, exactly as
	* before. (Legacy fallback when the host has no height for
	* the surface: 20 rows.)
	*/
	visibleRows?: number | null;
	/**
	* Whether `Tab` / `Shift+Tab` will land focus on this
	* list. Defaults to `true` (lists are normal tabbable
	* widgets). Picker-style usage typically sets this to
	* `false` so Tab moves between the filter input and
	* the action buttons, while Up/Down on the focused
	* filter still forwards to the list via host smart-key
	* dispatch.
	*/
	focusable: boolean;
	key?: string | null;
} | {
	"kind": "tree";
	nodes: Array<TreeNode>;
	itemKeys: Array<string>;
	selectedIndex: number;
	/**
	* Rows of the panel's available height the tree occupies.
	* `None` (omitted) = auto from the host-known panel height;
	* an explicit value pins the window as before. (Legacy
	* fallback when the host has no height: 20 rows.)
	*/
	visibleRows?: number | null;
	/**
	* Initial-only set of expanded item keys. Once the widget
	* has rendered, the host's instance-state `expanded_keys`
	* is authoritative; updating this field on subsequent specs
	* has no effect (use `WidgetMutation::SetExpandedKeys` to
	* override host state).
	*/
	expandedKeys: Array<string>;
	/**
	* When true, every node with `checked: Some(_)` renders a
	* `[v]` / `[ ]` glyph and emits a `toggle` hit area over
	* the glyph. Click on the glyph fires `widget_event {
	* event_type: "toggle", payload: { key, checked: <new> } }`;
	* the plugin updates its model and pushes the new state
	* back via `WidgetMutation::SetCheckedKeys`.
	*/
	checkable: boolean;
	/**
	* Fixed number of screen rows each node occupies. `1` (the
	* default) is the classic single-line tree. A larger value
	* renders every node as a card of that many rows — the
	* node's primary `text` plus its `extra_lines`, blank-padded
	* to this height. Windowing/scroll stay node-based (the node
	* budget becomes `visible_rows / item_height`), so all
	* existing single-line trees are unaffected.
	*/
	itemHeight: number;
	/**
	* When true (and `item_height > 1`), each *card* node — a
	* leaf carrying `extra_lines` and no checkbox — renders
	* inside a rounded border (`╭─…─╮` / `╰─…─╯` spanning the
	* panel width), taking `item_height + 2` rows: top border,
	* `item_height` content rows, bottom border. Non-card nodes
	* (e.g. folder headers) render as plain single rows instead
	* of being blank-padded to the card height. Restores the
	* bordered-pill look the Orchestrator dock's card density
	* had before it moved to a tree (issue #2703). Scroll and
	* selection stay node-based; rows per node just vary.
	*/
	cardBorders: boolean;
	/**
	* Columns of indent per depth level. `2` (the default) is the
	* classic tree step. A panel only a couple of dozen columns wide
	* that nests several levels deep can drop to `1` and spend those
	* columns on node text instead — each level is still marked by
	* the disclosure glyph (or the blank standing in for one).
	*/
	indentCols: number;
	key?: string | null;
} | {
	"kind": "text";
	/**
	* Initial text. Spec value is read at first render only;
	* instance state takes over thereafter.
	*/
	value: string;
	/**
	* Initial byte-offset cursor within `value`. Negative
	* (encoded as `i32` in JSON) means "no cursor" — clamped
	* to `[0, value.len()]` host-side.
	*/
	cursorByte: number;
	/**
	* Whether this widget has visual focus.
	*/
	focused: boolean;
	/**
	* Optional label rendered before / above the editing
	* region. Empty = omitted.
	*/
	label?: string;
	/**
	* Placeholder shown when unfocused and `value` is empty.
	*/
	placeholder?: string | null;
	/**
	* Number of visible rows of editing region. `0` falls back
	* to `1` (single-line). `1` = single-line behaviour;
	* `>= 2` = multi-line behaviour. See the type-level doc
	* for the per-mode semantics.
	*/
	rows: number;
	/**
	* Visible column width. `0` = auto-fit (single-line) or
	* panel width (multi-line). When set, single-line
	* head-truncates with `…` and multi-line tail-truncates
	* per-line.
	*/
	fieldWidth: number;
	/**
	* Single-line soft cap on visible chars after the
	* `field_width` pad. `0` = no cap. Ignored when `rows > 1`.
	*/
	maxVisibleChars: number;
	/**
	* Stretch the visible field to fill the available
	* width of the enclosing container. Overrides
	* `field_width` when set: the renderer computes
	* `panel_width - label_overhead - bracket_overhead` as
	* the effective visible width. Multi-line widgets
	* already fill the panel width by default; this flag is
	* most useful for single-line inputs inside a
	* `LabeledSection` or a flexible row.
	*/
	fullWidth: boolean;
	/**
	* Optional completion candidates. When non-empty AND
	* `label` is non-empty (the chrome trigger), the
	* renderer paints a popup directly under the input,
	* inside a unified box: the input's normal `╰─...─╯`
	* bottom border becomes a dimmed `┄` separator, the
	* labeled section's side borders extend down through
	* the candidate rows, and a single `╰─...─╯` bottom
	* closes the whole block. Candidates render left-
	* aligned with the input's text (the position right
	* after `[`), with the host-managed selected index
	* highlighted.
	*
	* Smart-key dispatch on a focused Text-with-completions:
	* Up/Down moves selection (host-internal, no event),
	* Tab fires `completion_accept` with the selected
	* candidate, Enter / Escape fire `completion_dismiss`
	* (the dispatcher's normal "Enter focus-advance / Esc
	* close panel" only runs once the popup is closed).
	*
	* Plugins push candidates in response to the text
	* widget's `change` event via
	* `WidgetMutation::SetCompletions`. An empty `items`
	* closes the popup.
	*/
	completions?: Array<string | CompletionItem>;
	/**
	* How many candidate rows the popup paints at once
	* when it opens. Excess candidates stay reachable
	* via Up/Down (host auto-scrolls to keep selection
	* in view) or the mouse wheel; a thumb glyph paints
	* in the right edge of the popup whenever there's
	* more to scroll. `0` (default) falls back to `5`.
	*/
	completionsVisibleRows: number;
	/**
	* Paint the caret as a REVERSED block cell inside the row
	* (in addition to publishing the hardware-cursor position).
	* Modal form surfaces (e.g. Settings) use this — a hardware
	* cursor isn't shown over a modal, so the block cell is the
	* only visible caret. Defaults to `false`.
	*/
	blockCaret: boolean;
	/**
	* Selection byte range within `value` (`start`, `end`), shown
	* with the selection background while the widget is focused.
	* `-1` for either end = no selection. Seed-only, like
	* `cursor_byte`: once host-owned instance state exists, the
	* editor's own selection wins. Mirrors `Number`'s
	* `edit_sel_start`/`edit_sel_end`.
	*/
	selStart: number;
	selEnd: number;
	/**
	* Form label-column width. When `> 0` (and `label` is set) the
	* single-line field pads the label to this column and separates
	* it from the value with `: `, so a column of `Text`, `Toggle`,
	* `Number`, and `Dropdown` controls aligns their value cells
	* (the Settings entry dialog sets it to the page's max label
	* width). `0` (default) keeps the compact `label [value]` form
	* plugins get by default. Clamped to keep the cell on-screen on
	* narrow surfaces.
	*/
	labelWidth: number;
	/**
	* Reject every mutating operation (typing, Backspace/Delete,
	* Cut, Paste) while keeping caret motion, selection, and Copy.
	* Implied by `markdown`.
	*/
	readOnly: boolean;
	/**
	* Render `value` as a markdown *document* (multi-line only,
	* `rows > 1`): the host renders it through the same markdown
	* engine as LSP hover docs — headings, emphasis, inline code,
	* links, syntax-highlighted fences — word-wrapped to the
	* widget's width. The caret, selection, and Copy operate on
	* the rendered plain text, so what you copy is what you see.
	* Markdown mode is **forcibly read-only**: the value only
	* changes via a spec update.
	*/
	markdown: boolean;
	key?: string | null;
} | {
	"kind": "labeledSection";
	/**
	* Legend text printed in the top border. Empty = no
	* legend (the top border becomes one unbroken line).
	*/
	label: string;
	/**
	* The single wrapped widget. Boxed because `WidgetSpec`
	* is recursive.
	*/
	child: WidgetSpec;
	/**
	* When this section is a Block child of a Row, request
	* `width_pct` percent of the row's `panel_width` instead
	* of the equal-split default. Multiple siblings with
	* `width_pct` set sum to ≤ 100; the remainder splits
	* equally among siblings without an explicit width.
	* Out-of-range values (0 or > 100) fall back to the
	* equal-split path.
	*/
	widthPct?: number | null;
	key?: string | null;
} | {
	"kind": "windowEmbed";
	/**
	* Numeric editor-window id, matching `WindowId(N).0`.
	* `0` (or any unknown id) renders empty placeholder
	* rows without dispatching the per-window render.
	* `u32` rather than `u64` to keep the TS binding a
	* plain `number`; window ids never exceed 4B in
	* practice.
	*/
	windowId: number;
	/**
	* Number of visible rows the embed should occupy.
	*/
	rows: number;
	key?: string | null;
} | {
	"kind": "raw";
	entries: Array<TextPropertyEntry>;
	key?: string | null;
} | {
	"kind": "overlay";
	child: WidgetSpec;
	key?: string | null;
} | {
	"kind": "component";
	child: WidgetSpec;
	key?: string | null;
} | {
	"kind": "popup";
	child: WidgetSpec;
	key?: string | null;
	/**
	* Anchor `[row, col]` in the panel's inner coordinates the
	* popup drops from (the host resolves the final screen rect
	* — opening below the anchor, flipping above near the frame
	* edge, clamped on screen). `None` anchors at the popup's
	* own position in the tree.
	*/
	anchor?: [number, number] | null;
	/**
	* When true, the popup escapes the panel's clipping and is
	* painted at screen level (what the dropdown pop-over does);
	* false keeps it panel-clipped like `Overlay`.
	*/
	screenSpace: boolean;
};
type WidgetAction = {
	"kind": "focusAdvance";
	delta: number;
} | {
	"kind": "activate";
} | {
	"kind": "selectMove";
	delta: number;
} | {
	"kind": "textInputKey";
	key: string;
} | {
	"kind": "textInputChar";
	text: string;
} | {
	"kind": "key";
	key: string;
};
type WidgetMutation = {
	"kind": "setValue";
	widgetKey: string;
	value: string;
	cursorByte?: number | null;
} | {
	"kind": "setCompletions";
	widgetKey: string;
	items: Array<string | CompletionItem>;
} | {
	"kind": "setChecked";
	widgetKey: string;
	checked: boolean;
} | {
	"kind": "setSelectedIndex";
	widgetKey: string;
	index: number;
} | {
	"kind": "setNumber";
	widgetKey: string;
	value: number;
} | {
	"kind": "setDropdown";
	widgetKey: string;
	index: number;
} | {
	"kind": "setDualIncluded";
	widgetKey: string;
	included: Array<string>;
} | {
	"kind": "setItems";
	widgetKey: string;
	items: Array<TextPropertyEntry>;
	itemKeys: Array<string>;
} | {
	"kind": "setExpandedKeys";
	widgetKey: string;
	keys: Array<string>;
} | {
	"kind": "setCheckedKeys";
	widgetKey: string;
	checked: boolean;
	keys: Array<string>;
} | {
	"kind": "appendTreeNodes";
	widgetKey: string;
	newNodes: Array<TreeNode>;
	newItemKeys: Array<string>;
} | {
	"kind": "setRawEntries";
	widgetKey: string;
	entries: Array<TextPropertyEntry>;
} | {
	"kind": "setFocusKey";
	widgetKey: string;
};
type SearchTakeResult = {
	/**
	* Matches discovered since the previous take()
	*/
	matches: Array<GrepMatch>;
	/**
	* Whether the producer has finished (no more matches will arrive)
	*/
	done: boolean;
	/**
	* Total number of matches the producer has emitted across all batches
	* (including ones already drained on prior take() calls)
	*/
	totalSeen: number;
	/**
	* Whether the producer stopped early because it hit `maxResults`
	*/
	truncated: boolean;
	/**
	* Producer error, if any (e.g., invalid regex). When set, `done` is also true.
	*/
	error?: string | null;
};
interface SearchHandle {
	searchId: number;
	take(): SearchTakeResult;
	cancel(): void;
}
type ReplaceResult = {
	/**
	* Number of replacements made
	*/
	replacements: number;
	/**
	* Buffer ID of the edited buffer
	*/
	bufferId: number;
};
type AuthorityPath = {
	kind: "authority";
	value: string;
};
type AuthorityFilesystem = {
	kind: "local";
};
type AuthoritySpawner = {
	kind: "local";
} | {
	kind: "docker-exec";
	container_id: string;
	user?: string | null;
	workspace?: string | null;
	env?: [string, string][];
};
type AuthorityTerminalWrapper = {
	kind: "host-shell";
} | {
	kind: "explicit";
	command: string;
	args: string[];
	manages_cwd?: boolean;
};
type AuthorityPayload = {
	filesystem: AuthorityFilesystem;
	spawner: AuthoritySpawner;
	terminal_wrapper: AuthorityTerminalWrapper;
	display_label?: string;
	/**
	* Optional host↔remote workspace path mapping. The dev-container
	* authority sets both roots (editor.getCwd() on host;
	* remoteWorkspaceFolder on container) so LSP URIs translate at the
	* host/container boundary. Local and SSH authorities omit it.
	*/
	path_translation?: PathTranslationSpec;
};
type PathTranslationSpec = {
	host_root: string;
	remote_root: string;
};
type BackgroundProcessResult = {
	/**
	* Unique process ID for later reference
	*/
	process_id: number;
	/**
	* Process exit code (0 usually means success, -1 if killed)
	* Only present when the process has exited
	*/
	exit_code: number;
};
type BufferSavedDiff = {
	equal: boolean;
	byte_ranges: Array<[number, number]>;
};
type CreateVirtualBufferInExistingSplitOptions = {
	/**
	* Buffer name (displayed in tabs/title)
	*/
	name: string;
	/**
	* Target split ID (required)
	*/
	splitId: number;
	/**
	* Mode for keybindings (e.g., "git-log", "search-results")
	*/
	mode?: string;
	/**
	* Whether buffer is read-only (default: false)
	*/
	readOnly?: boolean;
	/**
	* Show line numbers in gutter (default: true)
	*/
	showLineNumbers?: boolean;
	/**
	* Show cursor (default: true)
	*/
	showCursors?: boolean;
	/**
	* Disable text editing (default: false)
	*/
	editingDisabled?: boolean;
	/**
	* Enable line wrapping
	*/
	lineWrap?: boolean;
	/**
	* Initial content as **spans, concatenated verbatim** — a span is a run
	* of text with optional styling, not a line. Nothing inserts newlines
	* for you, so `[{text:"a"},{text:"b"}]` is the single line `ab`. Include
	* `\n` yourself — `[{text:"a\n"},{text:"b\n"}]` is two lines.
	*
	* If you are an agent putting text in front of a human, prefer writing a
	* file and opening it (`splitWindow({ file })` /
	* `openFileInSplit(splitId, path)`): you get syntax highlighting, search,
	* save, and ANSI escape codes rendered as colour, none of which a virtual
	* buffer gives you. Virtual buffers are for plugin-owned panels —
	* ephemeral, styled per span, driven by a mode's keybindings.
	*/
	entries?: Array<TextPropertyEntry>;
	/**
	* Initial cursor line (0-indexed). Applied to the new buffer *before*
	* it becomes the active buffer; see the matching field on
	* `CreateVirtualBufferOptions` for the rationale.
	*/
	initialCursorLine?: number;
};
type CreateVirtualBufferInSplitOptions = {
	/**
	* Buffer name (displayed in tabs/title)
	*/
	name: string;
	/**
	* Mode for keybindings (e.g., "git-log", "search-results")
	*/
	mode?: string;
	/**
	* Whether buffer is read-only (default: false)
	*/
	readOnly?: boolean;
	/**
	* Split ratio 0.0-1.0 (default: 0.5)
	*/
	ratio?: number;
	/**
	* Split direction: `"horizontal"` or `"vertical"`.
	*
	* The name describes the **divider**, not the arrangement:
	* `"vertical"` puts the panes side by side (a vertical divider between
	* them), `"horizontal"` stacks them. Same convention as `splitWindow`.
	*/
	direction?: string;
	/**
	* Panel ID to split from
	*/
	panelId?: string;
	/**
	* Show line numbers in gutter (default: true)
	*/
	showLineNumbers?: boolean;
	/**
	* Show cursor (default: true)
	*/
	showCursors?: boolean;
	/**
	* Disable text editing (default: false)
	*/
	editingDisabled?: boolean;
	/**
	* Enable line wrapping
	*/
	lineWrap?: boolean;
	/**
	* Place the new buffer before (left/top of) the existing content (default: false)
	*/
	before?: boolean;
	/**
	* Initial content as **spans, concatenated verbatim** — a span is a run
	* of text with optional styling, not a line. Nothing inserts newlines
	* for you, so `[{text:"a"},{text:"b"}]` is the single line `ab`. Include
	* `\n` yourself — `[{text:"a\n"},{text:"b\n"}]` is two lines.
	*
	* If you are an agent putting text in front of a human, prefer writing a
	* file and opening it (`splitWindow({ file })` /
	* `openFileInSplit(splitId, path)`): you get syntax highlighting, search,
	* save, and ANSI escape codes rendered as colour, none of which a virtual
	* buffer gives you. Virtual buffers are for plugin-owned panels —
	* ephemeral, styled per span, driven by a mode's keybindings.
	*/
	entries?: Array<TextPropertyEntry>;
	/**
	* Split role tag. When set to `"utility_dock"`, the dispatcher
	* routes this buffer to the existing dock leaf if one exists,
	* instead of creating a new split. See
	* `docs/internal/tui-editor-layout-design.md` Section 2.
	*/
	role?: string;
	/**
	* Whether the buffer is user-scrollable (default: true). Set to
	* `false` for self-managing widget panels (those whose List/Tree
	* owns its own scroll window): it suppresses the buffer scrollbar
	* and pins the viewport so a drag can't push the panel chrome
	* off-screen and reveal empty space below.
	*/
	scrollable?: boolean;
};
type CreateVirtualBufferOptions = {
	/**
	* Buffer name (displayed in tabs/title)
	*/
	name: string;
	/**
	* Mode for keybindings (e.g., "git-log", "search-results")
	*/
	mode?: string;
	/**
	* Whether buffer is read-only (default: false)
	*/
	readOnly?: boolean;
	/**
	* Show line numbers in gutter (default: false)
	*/
	showLineNumbers?: boolean;
	/**
	* Show cursor (default: true)
	*/
	showCursors?: boolean;
	/**
	* Disable text editing (default: false)
	*/
	editingDisabled?: boolean;
	/**
	* Hide from tab bar (default: false)
	*/
	hiddenFromTabs?: boolean;
	/**
	* Initial content as **spans, concatenated verbatim** — a span is a run
	* of text with optional styling, not a line. Nothing inserts newlines
	* for you, so `[{text:"a"},{text:"b"}]` is the single line `ab`. Include
	* `\n` yourself — `[{text:"a\n"},{text:"b\n"}]` is two lines.
	*
	* If you are an agent putting text in front of a human, prefer writing a
	* file and opening it (`splitWindow({ file })` /
	* `openFileInSplit(splitId, path)`): you get syntax highlighting, search,
	* save, and ANSI escape codes rendered as colour, none of which a virtual
	* buffer gives you. Virtual buffers are for plugin-owned panels —
	* ephemeral, styled per span, driven by a mode's keybindings.
	*/
	entries?: Array<TextPropertyEntry>;
	/**
	* Show the new buffer in this existing pane instead of taking over the
	* focused one.
	*
	* Without it the buffer becomes active in whichever pane has focus —
	* which is what you want for a panel the user just asked for, and
	* emphatically not what you want when arranging a layout, where it
	* silently replaces whatever the user was looking at.
	*/
	splitId?: number;
	/**
	* Initial cursor line (0-indexed). Applied to the new buffer *before*
	* it becomes the active buffer, so plugins that want to land the
	* cursor on a specific line don't have to chase a race against user
	* input between "buffer becomes active" and a follow-up
	* `setBufferCursor`. Using a line index (rather than a byte offset)
	* keeps the byte-math on the host side where the buffer content is
	* already in UTF-8 bytes, avoiding the UTF-16-vs-UTF-8 mismatch a
	* plugin would otherwise have to navigate.
	*/
	initialCursorLine?: number;
	/**
	* Override indentation-guide visibility for this buffer (default: follows
	* the global setting, but virtual buffers show none). Set `true` when the
	* buffer displays real source — e.g. a file opened at a past commit.
	*/
	indentationGuide?: boolean;
};
type DiffBaselineResult = {
	/**
	* The buffer content version the hunks were computed against (0 for
	* baseline-pair diffs, which involve no live buffer). A plugin that
	* renders decorations re-checks this against the buffer's current
	* version instead of copying buffer text around for coherence.
	*/
	revision: bigint;
	/**
	* "exact": content-accurate line hunks. "byteCoarse": the buffer's
	* line index isn't available yet (large file before its line-feed
	* scan), so no line hunks could be produced; callers fall back to
	* their own coarse rendering.
	*/
	fidelity: "exact" | "byteCoarse";
	/**
	* Line hunks, same contract as `computeLineDiff`. Empty means the
	* sides are equal (when `fidelity` is "exact").
	*/
	hunks: Array<LineDiffHunk>;
};
type GrepMatch = {
	/**
	* Absolute file path
	*/
	file: string;
	/**
	* Buffer ID if the file is open (0 if not)
	*/
	bufferId: number;
	/**
	* Byte offset of match start in the file/buffer content
	*/
	byteOffset: number;
	/**
	* Match length in bytes
	*/
	length: number;
	/**
	* 1-indexed line number
	*/
	line: number;
	/**
	* 1-indexed column number
	*/
	column: number;
	/**
	* The matched line content (for display)
	*/
	context: string;
};
type LanguagePackConfig = {
	/**
	* Comment prefix for line comments (e.g., "//" or "#")
	*/
	commentPrefix: string | null;
	/**
	* Block comment start marker (e.g., slash-star)
	*/
	blockCommentStart: string | null;
	/**
	* Block comment end marker (e.g., star-slash)
	*/
	blockCommentEnd: string | null;
	/**
	* Whether to use tabs instead of spaces for indentation
	*/
	useTabs: boolean | null;
	/**
	* Tab size (number of spaces per tab level)
	*/
	tabSize: number | null;
	/**
	* Whether auto-indent is enabled
	*/
	autoIndent: boolean | null;
	/**
	* Whether to show whitespace tab indicators (→) for this language
	* Defaults to true. Set to false for languages like Go/Hare that use tabs for indentation.
	*/
	showWhitespaceTabs: boolean | null;
	/**
	* Formatter configuration
	*/
	formatter: FormatterPackConfig | null;
};
type LineDiffHunk = {
	/**
	* First affected line in the old text (0-based).
	*/
	oldStart: number;
	/**
	* Number of old-side lines in the hunk (0 for pure insertion).
	*/
	oldCount: number;
	/**
	* First affected line in the new text (0-based).
	*/
	newStart: number;
	/**
	* Number of new-side lines in the hunk (0 for pure deletion).
	*/
	newCount: number;
};
type LocalPath = {
	kind: "local";
	value: string;
};
type LspServerPackConfig = {
	/**
	* Command to start the LSP server
	*/
	command: string;
	/**
	* Arguments to pass to the command
	*/
	args: Array<string>;
	/**
	* Whether to auto-start the server when a matching file is opened
	*/
	autoStart: boolean | null;
	/**
	* LSP initialization options
	*/
	initializationOptions: Record<string, unknown> | null;
	/**
	* Process resource limits (memory and CPU)
	*/
	processLimits: ProcessLimitsPackConfig | null;
};
type RemoteAgentTransport = {
	kind: "kubectl-exec";
	/** kubeconfig context to select (`--context`); omit for the current one. */
	context?: string | null;
	namespace: string;
	pod: string;
	/** Target container in a multi-container pod (`-c`). */
	container?: string | null;
	/** Pod-side workspace root the terminal opens in. */
	workspace?: string | null;
} | {
	kind: "ssh";
	/** Login user. Optional — omit for `host` / `ssh://host`, letting ssh pick
	* the user from its own config or the current local user. */
	user?: string | null;
	host: string;
	port?: number | null;
	identity_file?: string | null;
	/** Remote directory to root the session at. */
	remote_path?: string | null;
	/** Extra `ssh` arguments (e.g. `-J jump`, `-o ProxyCommand=…`) applied to
	* every ssh invocation for this session. */
	extra_args?: string[];
};
type RemoteAgentSpec = {
	transport: RemoteAgentTransport;
	/**
	* Captured in-pod env (PATH/HOME/LANG/…) applied to LSP spawns and
	* binary-presence probes. Omit when no probe was run.
	*/
	base_env?: [string, string][];
	/**
	* When true, attach as a NEW window (born-attached, coexisting with the
	* existing windows) instead of the default global restart that replaces the
	* whole editor's authority. The Orchestrator sets this so a cloud session is
	* a real session row beside local ones.
	*/
	window?: boolean;
	/** Window label (window mode only). Omit to use the transport's display. */
	label?: string;
	/** Optional agent argv for the new window's seed terminal (window mode). */
	command?: string[];
};
type RemoteIndicatorStatePayload = {
	kind: "local";
} | {
	kind: "connecting";
	label?: string | null;
} | {
	kind: "connected";
	label?: string | null;
} | {
	kind: "failed_attach";
	error?: string | null;
} | {
	kind: "disconnected";
	label?: string | null;
};
type ScrollbarMarker = {
	/**
	* Byte offset of the marked location. Preferred over `line`.
	*/
	position?: number;
	/**
	* 0-based logical line number, converted to a byte anchor at set time.
	* Ignored when `position` is present.
	*/
	line?: number;
	/**
	* Optional exclusive end byte offset, making this a range marker.
	*/
	end?: number;
	/**
	* Optional 0-based end line, **inclusive**, making this a range marker.
	* Ignored when `end` is present.
	*
	* The line counterpart to `end`, for producers that work in line
	* coordinates — a `git diff` parser knows a hunk's first and last line
	* but not their byte offsets. Without it such a plugin has to emit one
	* marker per line to paint a hunk's streak, which costs a byte lookup
	* and two anchors per line for a resolution the track cannot show.
	*/
	endLine?: number;
	/**
	* Marker color — RGB array or theme key. Theme keys resolve at render
	* time, so markers follow theme changes.
	*/
	color: OverlayColorSpec;
	/**
	* Priority when several markers land on the same track cell (higher
	* wins). Defaults to 0.
	*/
	priority?: number;
};
type SpawnResult = {
	/**
	* Complete stdout as string
	*/
	stdout: string;
	/**
	* Complete stderr as string
	*/
	stderr: string;
	/**
	* Process exit code (0 usually means success, -1 if killed)
	*/
	exit_code: number;
};
type StyledText = {
	text: string;
	style?: Partial<OverlayOptions>;
};
type TextPropertiesAtCursor = Array<Record<string, unknown>>;
type TsHighlightSpan = {
	start: number;
	end: number;
	color: [number, number, number];
	bold: boolean;
	italic: boolean;
};
type VirtualBufferResult = {
	/**
	* The created buffer ID
	*/
	bufferId: number;
	/**
	* The split ID (if created in a new split)
	*/
	splitId: number | null;
};
type WindowPath = {
	kind: "authority";
	window: number;
	value: string;
};
/**
* Main editor API interface
*/
interface EditorAPI {
	/**
	* Get the plugin API version. Plugins can check this to verify
	* the editor supports the features they need.
	*/
	apiVersion(): number;
	/**
	* The name of the plugin this `editor` handle belongs to. Used by the
	* M3 plugin-API plane (`exportPluginApi` tags the exporter). Plugin
	* authors generally don't call this directly.
	*/
	pluginName(): string;
	/**
	* Publish a typed API surface under `name`. Another plugin (typically
	* `init.ts`) can reach it later via `getPluginApi(name)`. Calling
	* again with the same `name` replaces the previous registration
	* (idempotent — reload works). Exports are auto-dropped when the
	* calling plugin is unloaded.
	* 
	* Returns `true` on success. Rejects with a TypeError if `name` is
	* empty or `api` is not an object (functions and primitives are not
	* valid API surfaces — only objects).
	*/
	exportPluginApi(name: string, api: unknown): boolean;
	/**
	* Look up a plugin API previously published via `exportPluginApi`.
	* Returns the api object (restored into the caller's context) or
	* `null` if no plugin exports under that name.
	*/
	getPluginApi(name: string): unknown | null;
	/**
	* Get the active buffer ID (0 if none)
	*/
	getActiveBufferId(): number;
	/**
	* Get the active split ID
	*/
	getActiveSplitId(): number;
	/**
	* Returns true when search highlights are currently active in the buffer.
	* Becomes true after a search is confirmed; false once cleared.
	*/
	hasActiveSearch(): boolean;
	/**
	* List all open buffers - returns array of BufferInfo objects
	*/
	listBuffers(): BufferInfo[];
	/**
	* List all available grammars with source info - returns array of GrammarInfo objects
	*/
	listGrammars(): GrammarInfoSnapshot[];
	/**
	* Register keys of all recorded macros in the active session, sorted.
	* Reads the per-tick snapshot, so it never crosses the IPC boundary.
	*/
	listMacros(): string[];
	/**
	* The recorded steps of the macro under `register` as `ActionSpec[]`, or
	* `null` if no macro is stored there. The returned array is the exact
	* shape `editor.executeActions` accepts, so a macro round-trips into a
	* replay script with no translation — this equivalence is the core of the
	* macro<->code bridge.
	*/
	getMacro(register: string): ActionSpec[] | null;
	/**
	* Define (or replace) the macro under `register` from a step list. Lets
	* `init.ts` seed registers at startup so a saved macro plays back exactly
	* like a hand-recorded one. Returns true if the command was queued.
	*/
	defineMacro(register: string, steps: ActionSpec[]): boolean;
	/**
	* Play the macro stored under `register` (same effect as the built-in
	* "play macro" action). Returns true if the command was queued.
	*/
	playMacro(register: string): boolean;
	debug(msg: string): void;
	info(msg: string): void;
	warn(msg: string): void;
	error(msg: string): void;
	setStatus(msg: string): void;
	copyToClipboard(text: string): void;
	setClipboard(text: string): void;
	/**
	* Get the display label for a keybinding by action name and optional mode.
	* Returns null if no binding is found.
	*/
	getKeybindingLabel(action: string, mode: string | null): string | null;
	/**
	* Register a command in the command palette (Ctrl+P).
	* 
	* Usually you should omit `context` so the command is always visible.
	* If provided, the command is **hidden** unless your plugin has activated
	* that context with `editor.setContext(name, true)` or the focused buffer's
	* virtual mode (from `defineMode()`) matches. This is for plugin-defined
	* contexts only (e.g. `"tour-active"`, `"review-mode"`), not built-in
	* editor modes.
	*/
	registerCommand(name: string, description: string, handlerName: string, context?: string | null, options?: {
		terminalBypass?: boolean;
	} | null): boolean;
	/**
	* Unregister a command by name
	*/
	unregisterCommand(name: string): boolean;
	/**
	* Set a context (for keybinding conditions)
	*/
	setContext(name: string, active: boolean): boolean;
	/**
	* Execute a built-in action
	*/
	executeAction(actionName: string): boolean;
	/**
	* Answer a command that was dispatched with a request id (a `RunCommand`
	* from the agent command channel).
	* 
	* Plugins do not normally call this: the host wraps every such dispatch so
	* that whatever the handler *returns* — or the promise it returns, once it
	* resolves — becomes the answer, and a throw becomes the failure. Call it
	* directly only to answer early, or to answer from somewhere other than
	* the handler's own return path. `output` is the JSON-encoded result the
	* caller prints; answering an unknown or already-answered id is a no-op.
	*/
	completeCommand(requestId: number, ok: boolean, output: string | null, error: string | null): boolean;
	/**
	* Cancel the active prompt / overlay — the same teardown the
	* Escape key triggers. Lets a plugin dismiss a prompt it opened
	* (e.g. exporting Live Grep results to a dock panel) without
	* routing a synthetic keypress.
	*/
	cancelPrompt(): boolean;
	/**
	* Register a custom statusbar token.
	* Token will be named "plugin_name:token_name" where plugin_name is the current plugin.
	* Returns true if registration succeeded, false if invalid or already registered.
	*/
	registerStatusBarElement(tokenName: string, title: string): boolean;
	/**
	* Set the value of a status-bar token for a specific buffer.
	* The full token key sent to the editor is "plugin_name:token_name".
	*/
	setStatusBarValue(bufferId: number, tokenName: string, value: string): boolean;
	/**
	* Translate a string - reads plugin name from __pluginName__ global
	* Args is optional - can be omitted, undefined, null, or an object
	*/
	t(key: string, ...args: unknown[]): string;
	/**
	* Get cursor position in active buffer
	*/
	getCursorPosition(): number;
	/**
	* Get file path for a buffer
	*/
	getBufferPath(bufferId: number): string;
	/**
	* Get buffer length in bytes
	*/
	getBufferLength(bufferId: number): number;
	/**
	* Check if buffer has unsaved changes
	*/
	isBufferModified(bufferId: number): boolean;
	/**
	* Save a buffer to a specific file path
	* Used by :w filename to save unnamed buffers or save-as
	*/
	saveBufferToPath(bufferId: number, path: string): boolean;
	/**
	* Get buffer info by ID
	*/
	getBufferInfo(bufferId: number): BufferInfo | null;
	/**
	* Get primary cursor info for active buffer
	*/
	getPrimaryCursor(): CursorInfo | null;
	/**
	* Get all cursors for active buffer
	*/
	getAllCursors(): CursorInfo[];
	/**
	* Get all cursor positions as byte offsets
	*/
	getAllCursorPositions(): number[];
	/**
	* Get viewport info for active buffer
	*/
	getViewport(): ViewportInfo | null;
	/**
	* Total terminal dimensions in cells. Unlike `getViewport()`
	* (which reports the active split, shrunk by any vertical
	* split layout), this reflects the full terminal — what a
	* floating overlay sized by `heightPct` actually gets.
	*/
	getScreenSize(): ScreenSize;
	/**
	* List every split with its active buffer and viewport.
	* 
	* Plugins that need to operate on every visible buffer
	* simultaneously (multi-split flash labels, syncing decorations
	* across panes, …) iterate this list rather than only seeing
	* `getViewport()`'s active-split data.  Order is unspecified.
	*/
	listSplits(): SplitSnapshot[];
	/**
	* Get the line number (0-indexed) of the primary cursor.
	* 
	* @deprecated Use `getPrimaryCursor()?.line` instead. This accessor cannot
	* represent "line index unavailable" (huge files before their line scan) —
	* it returns `0` in that case, indistinguishable from a real first line.
	* `getPrimaryCursor().line` is `number | null` and also covers every cursor
	* via `getAllCursors()`.
	*/
	getCursorLine(): number;
	/**
	* Get the byte offset of the start of a line (0-indexed line number)
	* Returns null if the line number is out of range
	*/
	getLineStartPosition(line: number): Promise<number | null>;
	/**
	* Get the byte offset of the end of a line (0-indexed line number)
	* Returns the position after the last character of the line (before newline)
	* Returns null if the line number is out of range
	*/
	getLineEndPosition(line: number): Promise<number | null>;
	/**
	* Get the total number of lines in the active buffer
	* Returns null if buffer not found
	*/
	getBufferLineCount(): Promise<number | null>;
	/**
	* Cursor info for the active composite (side-by-side diff) buffer.
	* 
	* Resolves with `null` when the active buffer is not a composite
	* buffer, otherwise an object describing the focused pane and the
	* 0-indexed source line shown in each pane on the cursor's aligned
	* row (`null` where a pane has no content on that row). Lets a plugin
	* map a side-by-side cursor back to a concrete file version + line.
	*/
	getCompositeCursorInfo(): Promise<{
		focusedPane: number;
		paneCount: number;
		lines: Array<number | null>;
	} | null>;
	/**
	* Scroll a split to center a specific line in the viewport
	* Line is 0-indexed (0 = first line)
	*/
	scrollToLineCenter(splitId: number, bufferId: number, line: number): boolean;
	/**
	* Scroll any split/panel showing `buffer_id` so `line` is visible.
	* Unlike `scrollToLineCenter`, this does not require a split id — it
	* updates every split's viewport whose active buffer is the given
	* buffer, including inner leaves of a buffer group. Use this from
	* a panel plugin to keep the user's "selected" row in view after
	* arrow-key navigation (the plugin's own selection state isn't
	* automatically reflected in the buffer cursor, so the core-driven
	* viewport would otherwise stay put).
	*/
	scrollBufferToLine(bufferId: number, line: number): boolean;
	/**
	* Find buffer by file path, returns buffer ID or 0 if not found
	*/
	findBufferByPath(path: string): number;
	/**
	* Get diff between buffer content and last saved version
	*/
	getBufferSavedDiff(bufferId: number): BufferSavedDiff | null;
	/**
	* Insert text at a position in a buffer
	*/
	insertText(bufferId: number, position: number, text: string): boolean;
	/**
	* Delete a range from a buffer
	*/
	deleteRange(bufferId: number, start: number, end: number): boolean;
	/**
	* Insert text at cursor position in active buffer
	*/
	insertAtCursor(text: string): boolean;
	/**
	* Open a file, optionally at a specific line/column.
	* 
	* `editor.openFile(path)` is the whole request most of the time.
	*/
	openFile(path: string, line?: number | null, column?: number | null): boolean;
	/**
	* Open a file in the background — no focus change, no
	* active-split mutation. `windowId` defaults to the active
	* session. Setting it to an inactive session id loads the
	* file's buffer and adds it as a tab in that session's
	* stashed split tree, ready to be revealed on next dive.
	* Orchestrator uses this to populate worktree sessions with
	* preselected files.
	*/
	openFileInBackground(path: string, windowId?: number): boolean;
	/**
	* Open a file in a specific split
	*/
	openFileInSplit(splitId: number, path: string, line?: number, column?: number): boolean;
	/**
	* Open `path` as a regular buffer in forced large-file (file-backed)
	* mode. The file is created (empty) if missing — designed for
	* buffers that will be filled by a concurrent `spawnProcess` with
	* `stdoutTo`. Resolves with the new buffer's id, or `null` on
	* failure.
	* 
	* Pair with `refreshBufferFromDisk` to grow the buffer as the
	* streaming write advances.
	*/
	openFileStreaming(path: string): Promise<number | null>;
	/**
	* Re-stat the file backing `bufferId` and extend the buffer if the
	* file has grown. Resolves with the new total byte length, or
	* `null` if the buffer has no file path or doesn't exist.
	* 
	* Used to drive a streaming display: while a `spawnProcess` writes
	* to a temp file, the plugin polls this on a timer so the buffer
	* length tracks the file length.
	*/
	refreshBufferFromDisk(bufferId: number): Promise<number | null>;
	/**
	* Show a buffer in the current split
	*/
	showBuffer(bufferId: number): boolean;
	/**
	* Close a buffer. Pass `force: true` to discard unsaved changes.
	* 
	* **A modified buffer is not closed** unless `force` is set — the user's
	* unsaved edits are not a plugin's to throw away. A scratch buffer the
	* plugin created and filled itself counts as modified, so disposing of
	* one needs `closeBuffer(id, true)`.
	* 
	* The returned boolean is **"the request was delivered"**, not "the
	* buffer closed": this call is fire-and-forget, and the editor decides
	* afterwards. A refusal is logged editor-side but is invisible here, so
	* confirm with `listBuffers()` (after `await editor.flush()`) when it
	* matters. Without `force` the sequence that used to be required was
	* delete-the-contents, `saveBufferToPath`, then close — three
	* round-trips, the first two of which returned `true` while achieving
	* nothing.
	*/
	closeBuffer(bufferId: number, force?: boolean | null): boolean;
	/**
	* Close other buffers in split
	*/
	closeOtherBuffersInSplit(bufferId: number, splitId: number): boolean;
	/**
	* Close all buffers in split
	*/
	closeAllBuffersInSplit(splitId: number): boolean;
	/**
	* Close buffers to right in split
	*/
	closeBuffersToRightInSplit(bufferId: number, splitId: number): boolean;
	/**
	* Close buffers to left in split
	*/
	closeBuffersToLeftInSplit(bufferId: number, splitId: number): boolean;
	/**
	* Move the active tab to the left in the active split
	*/
	moveTabToLeft(): boolean;
	/**
	* Move the active tab to the right in the active split
	*/
	moveTabToRight(): boolean;
	/**
	* Start a frame-buffer animation over an arbitrary screen region.
	* Returns an animation id usable with `cancelAnimation`.
	*/
	animateArea(rect: AnimationRect, kind: PluginAnimationKind): number;
	/**
	* Start an animation over the on-screen Rect currently occupied by a
	* virtual buffer. No-op if the buffer is not visible.
	*/
	animateVirtualBuffer(bufferId: number, kind: PluginAnimationKind): number;
	/**
	* Cancel an animation previously started via `animateArea` or
	* `animateVirtualBuffer`. No-op if the ID is unknown or already done.
	*/
	cancelAnimation(id: number): boolean;
	/**
	* Subscribe to an editor event
	*/
	on(eventName: string, handlerName: string): void;
	/**
	* Unsubscribe from an event
	*/
	off(eventName: string, handlerName: string): void;
	/**
	* Get an environment variable
	*/
	getEnv(name: string): string | null;
	/**
	* Get current working directory
	*/
	getCwd(): string;
	/**
	* Get the active authority's display label.
	* 
	* Empty means the local (default) authority. A non-empty value
	* means a plugin-installed or SSH authority is in effect (e.g.
	* `"Container:abc123def456"` for a devcontainer). Intended as a
	* simple "am I already attached?" check that survives editor
	* restarts — the label lives on the `Editor` state snapshot so it
	* is fresh after the authority-transition restart flow.
	*/
	getAuthorityLabel(): string;
	/**
	* Current Workspace Trust level for the active project: `"restricted"`,
	* `"trusted"`, or `"blocked"` (empty when unavailable). Exposed to JS as
	* `editor.workspaceTrustLevel()`. Plugins that run repo-controlled work
	* should treat anything other than `"trusted"` as "do not execute".
	*/
	workspaceTrustLevel(): string;
	/**
	* Whether an environment is currently active (set via `editor.setEnv`).
	* Exposed to JS as `editor.envActive()`. Lets the env-manager plugin
	* reflect activation and re-establish its file watch after the restart
	* that `setEnv` triggers.
	*/
	envActive(): boolean;
	/**
	* The environment core detected in the workspace, as a JSON string
	* (`{name, kind, snippet}`) or empty when none. Exposed to JS as
	* `editor.detectedEnv()`. Detection lives only in core; the env-manager
	* plugin consumes this result instead of probing the filesystem itself.
	*/
	detectedEnv(): string;
	/**
	* Join path components (variadic - accepts multiple string arguments)
	* Always uses forward slashes for cross-platform consistency (like Node.js path.posix.join)
	* 
	* Preserves up to 2 leading slashes, which matters on Windows: Rust's
	* `Path::canonicalize` returns `\\?\`-prefixed paths, and `editor.getCwd()`
	* surfaces that to plugin code verbatim. After the backslash→slash
	* normalization the prefix becomes `//?/C:/...`; collapsing the leading
	* `//` to a single `/` yields `/?/C:/...`, which every filesystem API on
	* Windows rejects, breaking `findConfig()`-style plugin logic.
	*/
	pathJoin(...parts: string[]): string;
	/**
	* Get directory name from path
	*/
	pathDirname(path: string): string;
	/**
	* Get file name from path
	*/
	pathBasename(path: string): string;
	/**
	* Get file extension
	*/
	pathExtname(path: string): string;
	/**
	* Check if path is absolute
	*/
	pathIsAbsolute(path: string): boolean;
	/**
	* Convert a file:// URI to a local file path.
	* Handles percent-decoding and Windows drive letters.
	* Returns an empty string if the URI is not a valid file URI.
	*/
	fileUriToPath(uri: string): string;
	/**
	* Convert a local file path to a file:// URI.
	* Handles Windows drive letters and special characters.
	* Returns an empty string if the path cannot be converted.
	*/
	pathToFileUri(path: string): string;
	/**
	* Get the UTF-8 byte length of a JavaScript string.
	* 
	* JS strings are UTF-16 internally, so `str.length` returns the number of
	* UTF-16 code units, not the number of bytes in a UTF-8 encoding.  The
	* editor API uses byte offsets for all buffer positions (overlays, cursor,
	* getBufferText ranges, etc.).  This helper lets plugins convert JS string
	* lengths / regex match indices to the byte offsets the editor expects.
	*/
	utf8ByteLength(text: string): number;
	/**
	* Line-level diff of two texts (native patience diff; see
	* `fresh_core::diff`). Returns hunks of differing line ranges in
	* increasing order; equal regions are not reported. Lines are
	* 0-indexed `\n`-terminated segments (a final unterminated segment
	* counts as a line), matching the `text.split("\n")`-and-drop-
	* trailing-empty convention plugins already use for line arrays.
	* 
	* Never refuses an input: pathological chunks degrade to coarser
	* hunks instead of failing, so callers don't need a "diff too
	* large" path. Runs synchronously on the plugin thread — cost is
	* near-linear in input size, far below the JS it replaces.
	*/
	computeLineDiff(oldText: string, newText: string): LineDiffHunk[];
	/**
	* Check if a file exists on the path's filesystem (a window's authority,
	* or the local host for a `LocalPath`).
	*/
	fileExists(path: string | LocalPath | WindowPath | AuthorityPath): boolean;
	/**
	* Read file contents from the path's filesystem.
	*/
	readFile(path: string | LocalPath | WindowPath | AuthorityPath): string | null;
	/**
	* Write file contents to the path's filesystem. Parent directories are
	* created as needed.
	*/
	writeFile(path: string | LocalPath | WindowPath | AuthorityPath, content: string): boolean;
	/**
	* Read directory contents (returns array of {name, is_file, is_dir})
	*/
	readDir(path: string | LocalPath | WindowPath | AuthorityPath): DirEntry[];
	/**
	* Create a directory (and all parent directories) recursively on the
	* path's filesystem. Returns true if the directory was created or already
	* exists.
	*/
	createDir(path: string | LocalPath | WindowPath | AuthorityPath): boolean;
	/**
	* Permanently remove a file or directory on the path's filesystem
	* (recursively for directories). For safety, the path must be under the OS
	* temp directory or the Fresh config directory. Returns true on success.
	*/
	removePath(path: string | LocalPath | WindowPath | AuthorityPath): boolean;
	/**
	* Rename/move a file or directory. Both paths must target the same
	* filesystem (a cross-backend move is rejected). Returns true on success.
	*/
	renamePath(from: string | LocalPath | WindowPath | AuthorityPath, to: string | LocalPath | WindowPath | AuthorityPath): boolean;
	/**
	* Copy a file or directory recursively to a new location. Both paths must
	* target the same filesystem. Returns true on success.
	*/
	copyPath(from: string | LocalPath | WindowPath | AuthorityPath, to: string | LocalPath | WindowPath | AuthorityPath): boolean;
	/**
	* Construct a `LocalPath` — a path that always resolves on the local
	* editor host, regardless of the active window's authority. Use for
	* editor-owned state under the config/data dirs.
	*/
	localPath(path: string): LocalPath;
	/**
	* Construct a `WindowPath` — a path that resolves on a specific window's
	* authority filesystem, regardless of which window is focused.
	*/
	windowPath(windowId: number, path: string): WindowPath;
	/**
	* Construct an `AuthorityPath` — the active window's authority filesystem,
	* stated explicitly. Routes identically to passing a bare string, but is
	* self-documenting: bundled plugins use it so bare strings are reserved as
	* the backward-compatible default for external plugins.
	*/
	authorityPath(path: string): AuthorityPath;
	/**
	* Get the OS temporary directory path.
	*/
	getTempDir(): string;
	/**
	* Parse a JSONC (JSON with comments) string into a JS value.
	* 
	* Accepts the JSONC superset: line and block comments, trailing
	* commas, single-quoted strings, and unquoted object keys — matching
	* devcontainer.json / tsconfig.json / VS Code settings.json.
	* 
	* Throws a JS error (catchable with try/catch) when the input is not
	* valid JSONC, like `JSON.parse` does for invalid JSON.
	*/
	parseJsonc(text: string): unknown;
	/**
	* Get current config as JS object.
	* 
	* The snapshot holds an `Arc<serde_json::Value>` that was serialized
	* on the editor side the last time the underlying `Arc<Config>`
	* changed. Cloning the Arc inside the read lock is a refcount bump;
	* the actual walk into the JS runtime happens outside the lock.
	*/
	getConfig(): unknown;
	/**
	* Get user config as JS object. Same Arc-clone pattern as `get_config`.
	*/
	getUserConfig(): unknown;
	/**
	* Declare a boolean config field for the calling plugin.
	* 
	* Validates `options` synchronously: the JS call throws if any
	* unknown key is present or if `default` isn't a boolean. The
	* Settings UI grows a "Plugin Settings → <plugin>" sub-category
	* containing a toggle for this field. Returns the current value
	* (user-set if present, otherwise the declared `default`).
	*/
	defineConfigBoolean(name: string, options: {
		default: boolean;
		description?: string;
	}): boolean;
	/**
	* Declare an integer config field for the calling plugin. Throws on
	* invalid options or if the default falls outside `minimum/maximum`.
	*/
	defineConfigInteger(name: string, options: {
		default: number;
		description?: string;
		minimum?: number;
		maximum?: number;
	}): number;
	/**
	* Declare a floating-point number config field. Throws on bad
	* options or default outside `minimum/maximum`.
	*/
	defineConfigNumber(name: string, options: {
		default: number;
		description?: string;
		minimum?: number;
		maximum?: number;
	}): number;
	/**
	* Declare a free-form string config field.
	*/
	defineConfigString(name: string, options: {
		default: string;
		description?: string;
	}): string;
	/**
	* Declare an array-of-strings config field (e.g. a list of
	* patterns). The Settings UI renders this as a list editor.
	*/
	defineConfigStringArray(name: string, options: {
		default: string[];
		description?: string;
	}): string[];
	/**
	* Get the calling plugin's settings as a JS object.
	* 
	* Returns the merged value at `config.plugins.<plugin_name>.settings`.
	* The shape comes from whatever the plugin declared via
	* `editor.definePluginConfig(...)` (defaults pre-populated by the
	* host, user overrides on top from the Settings UI). Returns `null`
	* if the plugin hasn't declared a schema and has no user-set value.
	*/
	getPluginConfig(): unknown;
	/**
	* Reload configuration from file
	*/
	reloadConfig(): void;
	/**
	* Set a single config setting in the runtime layer for this session.
	* 
	* `path` is dot-separated (e.g. `"editor.tab_size"`). `value` is any JSON
	* value in the shape the setting expects. The write lives in an
	* in-memory layer scoped to the calling plugin — it does not modify
	* `config.json`, and unloading the plugin (or reloading init.ts) drops
	* it. Intended use is `init.ts` running a conditional:
	* `if (editor.getEnv("SSH_TTY")) editor.setSetting("terminal.mouse", false);`
	* 
	* Returns `true` if the write was queued. The actual update is
	* asynchronous; a subsequent `getConfig()` will reflect it after the
	* editor processes the command.
	*/
	setSetting(path: string, value: unknown): boolean;
	/**
	* Reload theme registry from disk
	* Call this after installing theme packages or saving new themes
	*/
	reloadThemes(): void;
	/**
	* Reload theme registry and apply a theme atomically
	*/
	reloadAndApplyTheme(themeName: string): void;
	/**
	* Register a TextMate grammar file for a language
	* The grammar will be pending until reload_grammars() is called
	*/
	registerGrammar(language: string, grammarPath: string, extensions: string[]): boolean;
	/**
	* Register language configuration (comment prefix, indentation, formatter)
	*/
	registerLanguageConfig(language: string, config: LanguagePackConfig): boolean;
	/**
	* Register an LSP server for a language
	*/
	registerLspServer(language: string, config: LspServerPackConfig): boolean;
	/**
	* Reload the grammar registry to apply registered grammars (async)
	* Call this after registering one or more grammars.
	* Returns a Promise that resolves when the grammar rebuild completes.
	*/
	reloadGrammars(): Promise<void>;
	/**
	* Get the directory where this plugin's files are stored.
	* For package plugins this is `<plugins_dir>/packages/<plugin_name>/`.
	*/
	getPluginDir(): string;
	/**
	* Get config directory path
	*/
	getConfigDir(): string;
	/**
	* Get the persistent data directory path (DirectoryContext::data_dir).
	* Intended for plugin state that should outlive a single session — e.g.
	* review-diff comments keyed off git state.
	*/
	getDataDir(): string;
	/**
	* Directory holding terminal scrollback backing files for the current
	* working directory. Each project root / worktree has its own subdir, so
	* Universal Search's terminal scope can stay scoped to the active
	* project rather than spanning every project's terminals.
	*/
	getTerminalDir(): string;
	/**
	* Per-working-directory data root for plugin state scoped to the current
	* project root / worktree (`<data_dir>/workdirs/<encoded-cwd>/`). Use
	* instead of `getDataDir()` for state that should not be shared across
	* worktrees. The directory is not created here — callers create what
	* they need under it.
	*/
	getWorkingDataDir(): string;
	/**
	* Get themes directory path
	*/
	getThemesDir(): string;
	/**
	* Apply a theme by name
	*/
	applyTheme(themeName: string): boolean;
	/**
	* Override theme colors in-memory for the running session. `overrides`
	* is a JS object mapping `"section.field"` keys (same namespace as
	* `getThemeSchema`) to `[r, g, b]` triplets (0–255 each).
	* 
	* Unknown keys are dropped silently; out-of-range values are clamped
	* to `0..=255`. Overrides survive until the next `applyTheme` call
	* (which replaces the whole `Theme`). Intended for fast animation
	* loops from `init.ts` — no disk I/O, no theme-registry rescan.
	*/
	overrideThemeColors(overrides: unknown): boolean;
	/**
	* Get theme schema as JS object
	*/
	getThemeSchema(): unknown;
	/**
	* Get list of builtin themes as JS object
	*/
	getBuiltinThemes(): unknown;
	/**
	* Full theme registry (builtins + user themes + packages + bundles).
	* Keyed by canonical registry key; each value carries `_key` / `_pack`.
	*/
	getAllThemes(): unknown;
	/**
	* Delete a custom theme (alias for deleteThemeSync)
	*/
	deleteTheme(name: string): boolean;
	/**
	* Get theme data (JSON) by name from the in-memory cache
	*/
	getThemeData(name: string): unknown;
	/**
	* Save a theme file to the user themes directory, returns the saved path
	*/
	saveThemeFile(name: string, content: string): string;
	/**
	* Check if a user theme file exists
	*/
	themeFileExists(name: string): boolean;
	/**
	* Get file stat information
	*/
	fileStat(path: string | LocalPath | WindowPath | AuthorityPath): unknown;
	/**
	* Check if a background process is still running
	*/
	isProcessRunning(processId: number): boolean;
	/**
	* Kill a process by ID (alias for killBackgroundProcess)
	*/
	killProcess(processId: number): boolean;
	/**
	* Translate a key for a specific plugin
	*/
	pluginTranslate(pluginName: string, key: string, args?: Record<string, unknown>): string;
	/**
	* Move a buffer into `splitId`: show it there, and remove its tab from
	* the pane that held it before.
	* 
	* Use this to *rearrange* what is where. `setSplitBuffer` only changes
	* which of a pane's existing tabs is visible, so building a move out of
	* it leaves the original tab stranded in its old pane.
	* 
	* Queued, like every layout mutation: the returned bool only reports that
	* the command was sent, not that it took effect, and a read issued right
	* after it still sees the old state. `await editor.flush()` before
	* reading back.
	*/
	moveBufferToSplit(bufferId: number, splitId: number): boolean;
	/**
	* Make lines of a buffer clickable: a click or Enter on a listed line
	* opens what it points at.
	* 
	* ```js
	* editor.setLineTargets(bufferId, [
	* { line: 0, path: "src/main.rs", target: 41, into: "code" },
	* { line: 1, path: "src/lib.rs",  target: 12, into: "code" },
	* ]);
	* ```
	* 
	* `line` is the row *in this buffer*; `target` is the line to land on in
	* the file, both 0-indexed. `into` names a pane by its `setSplitLabel`
	* label; when it names no live pane the target opens beside this one, so
	* an index never replaces itself with what you clicked.
	* 
	* The editor owns the behaviour, which is the point: a script that builds
	* an index — a search result list, an error list, a review map — exits
	* immediately, and a `mouse_click` handler would die with it. These
	* targets keep working.
	* 
	* Replaces any previous targets for the buffer; pass `[]` to clear.
	*/
	setLineTargets(bufferId: number, targets: LineTarget[]): boolean;
	/**
	* Describe the editor as it is right now: the panes of the active
	* window in visual order with their geometry and contents, which one
	* has focus, the working directory, and every open workspace.
	* 
	* This is the call to start from. It answers "which pane is on the
	* left", "is that a terminal or a file", and "what am I pointed at"
	* in one read, instead of stitching `listSplits` + `getBufferInfo` +
	* `getActiveSplitId` together and still not knowing pane order.
	* 
	* Reads the snapshot, so it is cheap and synchronous — but it
	* observes the state as of the last applied batch. After a mutation,
	* `await editor.flush()` first (or await the mutation itself, if it
	* returns a promise) or this reports what was true before it.
	* 
	* ```js
	* const ws = editor.describeWorkspace();
	* const left = ws.panes[0];               // leftmost pane
	* const term = ws.panes.find(p => p.kind === "terminal");
	* ```
	*/
	describeWorkspace(): WorkspaceDescription;
	/**
	* Split the active pane and resolve with the new pane.
	* 
	* This is the primitive for arranging panes. `direction` names the
	* *divider*: `"vertical"` puts panes side by side (left | right),
	* `"horizontal"` stacks them (top / bottom). `place` says which side
	* the new pane lands on — `"before"` is left/top, `"after"` (the
	* default, and what the keyboard split does) is right/bottom.
	* 
	* Resolves *after* the layout has been applied and the readable
	* snapshot refreshed, with the new pane's id and geometry — so
	* `listSplits()` / `describeWorkspace()` called next observe the split
	* that was just made, and "did it land on the left" is answered by the
	* `x` that comes back rather than by guessing.
	* 
	* ```js
	* // Terminal on the left, README on the right:
	* const left = await editor.splitWindow({ direction: "vertical", place: "before" });
	* await editor.createTerminal({ splitId: left.splitId });
	* ```
	* 
	* Rejects when the pane could not be created.
	*/
	splitWindow(opts: SplitWindowOptions): Promise<SplitCreated>;
	/**
	* Wait for every mutation queued so far to be applied, then resolve.
	* 
	* Commands are queued and drained on the editor thread, so a read
	* issued right after a mutation reports the state from *before* it:
	* `setSplitRatio(...)` followed by `listSplits()` returns the old
	* widths. Awaiting this closes that window, which is what lets a
	* single script change the layout and then verify what it changed.
	* 
	* ```js
	* editor.setSplitRatio(splitId, 0.3);
	* await editor.flush();
	* return editor.describeWorkspace();   // reflects the new ratio
	* ```
	*/
	flush(): Promise<void>;
	/**
	* Create a composite buffer (async)
	* 
	* Uses typed CreateCompositeBufferOptions - serde validates field names at runtime
	* via `deny_unknown_fields` attribute
	*/
	createCompositeBuffer(opts: TsCreateCompositeBufferOptions): Promise<number>;
	/**
	* Update alignment hunks for a composite buffer
	* 
	* Uses typed Vec<CompositeHunk> - serde validates field names at runtime
	*/
	updateCompositeAlignment(bufferId: number, hunks: TsCompositeHunk[]): boolean;
	/**
	* Close a composite buffer
	*/
	closeCompositeBuffer(bufferId: number): boolean;
	/**
	* Force-materialize render-dependent state (like `layoutIfNeeded` in UIKit).
	* After calling this, commands that depend on view state created during
	* rendering (e.g., `compositeNextHunk`) will work correctly.
	*/
	flushLayout(): boolean;
	/**
	* Put a composite buffer's cursor on the row showing `line`
	* (0-indexed) of pane `pane` — 0 is the left/OLD pane — and scroll
	* it into view.
	* 
	* `initialFocusHunk` on `createCompositeBuffer` lands the view on a
	* hunk; this lands it on a *line*, which is what a plugin holding a
	* concrete file position wants (following a review comment, or
	* keeping the reader's place when a diff view flips between its
	* unified and side-by-side layouts). No-op if that pane has no such
	* line.
	* 
	* Queued, like every layout mutation: the returned bool only reports
	* that the command was sent.
	*/
	setCompositeCursorLine(bufferId: number, pane: number, line: number): boolean;
	/**
	* Navigate to the next hunk in a composite buffer
	*/
	compositeNextHunk(bufferId: number): boolean;
	/**
	* Navigate to the previous hunk in a composite buffer
	*/
	compositePrevHunk(bufferId: number): boolean;
	/**
	* Request syntax highlights for a buffer range (async)
	*/
	getHighlights(bufferId: number, start: number, end: number): Promise<TsHighlightSpan[]>;
	/**
	* Add an overlay with styling options
	* 
	* Colors can be specified as RGB arrays `[r, g, b]` or theme key strings.
	* Theme keys are resolved at render time, so overlays update with theme changes.
	* 
	* Theme key examples: "ui.status_bar_fg", "editor.selection_bg", "syntax.keyword"
	* 
	* Options: fg, bg (RGB array or theme key string), bold, italic, underline,
	* strikethrough, extend_to_line_end (all booleans, default false).
	* 
	* Example usage in TypeScript:
	* ```typescript
	* editor.addOverlay(bufferId, "my-namespace", 0, 10, {
	* fg: "syntax.keyword",           // theme key
	* bg: [40, 40, 50],               // RGB array
	* bold: true,
	* strikethrough: true,
	* });
	* ```
	*/
	addOverlay(bufferId: number, namespace: string, start: number, end: number, options: Record<string, unknown>): boolean;
	/**
	* Declare a one-line overlay that follows this buffer's cursor.
	* 
	* Takes the same options as `addOverlay` and paints the same way — the
	* difference is who places it. The host re-derives the range from the
	* cursor while drawing each frame, so the bar marks the row the caret
	* is on in that very frame. Painting it by hand from `cursor_moved`
	* cannot: the hook fires after the move that already drew, so the bar
	* lands a frame late and visibly trails a held arrow key.
	* 
	* Pass `null` to withdraw it.
	* 
	* ```typescript
	* editor.setCursorLineOverlay(bufferId, {
	* bg: "editor.selection_bg",
	* extendToLineEnd: true,
	* });
	* ```
	*/
	setCursorLineOverlay(bufferId: number, options: unknown): boolean;
	/**
	* Clear all overlays in a namespace
	*/
	clearNamespace(bufferId: number, namespace: string): boolean;
	/**
	* Clear all overlays from a buffer
	*/
	clearAllOverlays(bufferId: number): boolean;
	/**
	* Clear all overlays that overlap with a byte range
	*/
	clearOverlaysInRange(bufferId: number, start: number, end: number): boolean;
	/**
	* Clear overlays in a namespace that overlap with a byte range
	*/
	clearOverlaysInRangeForNamespace(bufferId: number, namespace: string, start: number, end: number): boolean;
	/**
	* Remove an overlay by its handle
	*/
	removeOverlay(bufferId: number, handle: string): boolean;
	/**
	* Add a conceal range that hides or replaces a byte range during rendering.
	* 
	* `activation` optionally makes the conceal cursor-dependent:
	* `"unless-cursor-in"` (active only while no cursor is inside
	* `[scopeStart, scopeEnd)`) or `"if-cursor-in"` (active only while a
	* cursor IS inside it). Emitting both renderings of a
	* cursor-revealable decoration once — instead of rebuilding markers
	* on every cursor move — is what keeps cursor movement free of
	* marker churn (and of the cache invalidation it causes).
	*/
	addConceal(bufferId: number, namespace: string, start: number, end: number, replacement: string | null, activation?: string, scopeStart?: number, scopeEnd?: number): boolean;
	/**
	* Clear all conceal ranges in a namespace
	*/
	clearConcealNamespace(bufferId: number, namespace: string): boolean;
	/**
	* Clear all conceal ranges that overlap with a byte range
	*/
	clearConcealsInRange(bufferId: number, start: number, end: number): boolean;
	/**
	* Display width of a single Unicode code point, in terminal columns
	* (0 for control/zero-width, 2 for CJK/fullwidth and most emoji, else 1).
	* 
	* Backed by the editor's own width logic (`fresh_core::display_width`), so
	* plugins measure width exactly as the editor lays out cells — no
	* per-plugin width tables. An invalid code point returns 0.
	*/
	charWidth(codePoint: number): number;
	/**
	* Display width of a string, in terminal columns (the sum of its
	* characters' widths). Prefer this over per-character `charWidth` calls
	* when measuring whole cells — one boundary crossing instead of many.
	*/
	stringWidth(text: string): number;
	/**
	* Clear conceal ranges overlapping a byte range, restricted to one
	* namespace — other plugins' conceals in the range are untouched.
	*/
	clearConcealsInRangeForNamespace(bufferId: number, namespace: string, start: number, end: number): boolean;
	/**
	* Add a collapsed fold range. Hides bytes [start, end) from
	* rendering — the line containing `start - 1` (the fold "header")
	* stays visible, while subsequent lines covered by the range are
	* skipped.
	*/
	addFold(bufferId: number, start: number, end: number, placeholder?: string): boolean;
	/**
	* Clear every collapsed fold range on the buffer.
	*/
	clearFolds(bufferId: number): boolean;
	/**
	* Publish a set of toggleable fold ranges on the buffer. Same
	* shape an LSP `foldingRange` response would take. Unlike
	* `addFold`, this does *not* pre-collapse anything — the
	* standard fold-toggle keybinding finds the range under the
	* cursor and collapses or expands it on demand. Replacing call
	* replaces the prior set.
	* 
	* `ranges` is a JS array of objects shaped
	* `{ startLine, endLine, kind? }` (lines are 0-indexed).
	* `kind` is one of `"comment"`, `"imports"`, `"region"` per
	* the LSP spec; omitted/unknown values are accepted as plain
	* folds.
	*/
	setFoldingRanges(bufferId: number, rangesArr: Record<string, unknown>[]): boolean;
	/**
	* Add a soft break point for marker-based line wrapping.
	* 
	* `activation` optionally makes the break cursor-dependent — same
	* semantics as `addConceal`'s activation parameters.
	* 
	* `prefix` optionally draws a glyph run at the head of the continuation
	* row, shaped `{ text, fg?, bg?, bold?, italic? }` with the same colour
	* spec `addOverlay` takes (a theme key string or an `[r, g, b]` array).
	* It is drawn *inside* the `indent` columns rather than in addition to
	* them, so a wrapped block quote can keep its `▌` down every row without
	* shifting the text. `indent` grows to fit a prefix wider than it.
	*/
	addSoftBreak(bufferId: number, namespace: string, position: number, indent: number, activation?: string | null, scopeStart?: number | null, scopeEnd?: number | null, prefix?: Record<string, unknown> | null): boolean;
	/**
	* Clear all soft breaks in a namespace
	*/
	clearSoftBreakNamespace(bufferId: number, namespace: string): boolean;
	/**
	* Clear all soft breaks that fall within a byte range
	*/
	clearSoftBreaksInRange(bufferId: number, start: number, end: number): boolean;
	/**
	* Set layout hints (compose width, column guides) for a buffer/split
	* directly.
	*/
	setLayoutHints(bufferId: number, splitId: number | null, hints: LayoutHints): boolean;
	/**
	* Set file explorer decorations for a namespace
	*/
	setFileExplorerDecorations(namespace: string, decorations: Record<string, unknown>[]): boolean;
	/**
	* Clear file explorer decorations for a namespace
	*/
	clearFileExplorerDecorations(namespace: string): boolean;
	/**
	* Set file explorer slot overrides for a namespace
	*/
	setFileExplorerSlots(namespace: string, slots: Record<string, unknown>[]): boolean;
	/**
	* Clear file explorer slot overrides for a namespace
	*/
	clearFileExplorerSlots(namespace: string): boolean;
	/**
	* Add virtual text (inline text that doesn't exist in the buffer)
	*/
	addVirtualText(bufferId: number, virtualTextId: string, position: number, text: string, r: number, g: number, b: number, before: boolean, useBg: boolean): boolean;
	/**
	* Remove a virtual text by ID
	*/
	removeVirtualText(bufferId: number, virtualTextId: string): boolean;
	/**
	* Add styled virtual text — richer form of `addVirtualText` whose
	* `options` accepts an `addOverlay`-style record: `fg`/`bg` may
	* be RGB arrays or theme-key strings, plus `bold`/`italic`. Theme
	* keys are resolved at render time so the label follows theme
	* changes live.
	*/
	addVirtualTextStyled(bufferId: number, virtualTextId: string, position: number, text: string, options: Record<string, unknown>, before: boolean): boolean;
	/**
	* Remove virtual texts whose ID starts with the given prefix
	*/
	removeVirtualTextsByPrefix(bufferId: number, prefix: string): boolean;
	/**
	* Clear all virtual texts from a buffer
	*/
	clearVirtualTexts(bufferId: number): boolean;
	/**
	* Clear all virtual texts in a namespace
	*/
	clearVirtualTextNamespace(bufferId: number, namespace: string): boolean;
	/**
	* Clear virtual lines in a namespace whose anchor byte falls in
	* `[start, end)`. The per-line analogue of `clearConcealsInRange`, so a
	* plugin can rebuild one line's virtual lines without nuking the namespace.
	*/
	clearVirtualLinesInRange(bufferId: number, namespace: string, start: number, end: number): boolean;
	/**
	* Clear *inline* virtual texts whose id starts with `idPrefix` and whose
	* anchor byte falls in `[start, end)`. The inline analogue of
	* `clearVirtualLinesInRange`, so a per-line pass can rebuild one line's
	* inline decorations without dropping the rest of the set.
	*/
	clearVirtualTextsInRange(bufferId: number, idPrefix: string, start: number, end: number): boolean;
	/**
	* Add a virtual line (full line above/below a position)
	* 
	* The `options` object accepts:
	* * `fg`, `bg` — either an `[r, g, b]` array (each `0..=255`) or a
	* theme-key string (e.g. `"editor.line_number_fg"`).  Theme keys
	* are resolved at render time so the line follows theme changes.
	* Both default to `null` (no foreground / transparent background).
	* * `gutterGlyph` — optional single character (any short string)
	* rendered in the line-number column on this virtual line's
	* first visual row. Use to mark e.g. a deletion line with "-"
	* so the indicator sits next to the deleted content instead
	* of on the following source line.
	* * `gutterColor` — color for `gutterGlyph`, same shape as
	* `fg`/`bg`. Falls back to the theme's line-number fg.
	*/
	addVirtualLine(bufferId: number, position: number, text: string, options: Record<string, unknown>, above: boolean, namespace: string, priority: number): boolean;
	/**
	* Show a prompt and wait for user input (async)
	* Returns the user input or null if cancelled
	*/
	prompt(label: string, initialValue: string): Promise<string | null>;
	/**
	* Open the editor's native Open File browser and wait for a pick
	* (async) — the terminal analogue of a browser's file-input dialog.
	* Resolves with the chosen file's absolute path, or null if the
	* user cancels. The browser anchors where Open File does (the
	* active file's directory, else the window's working directory),
	* with the same navigation: Backspace walks up the tree, Tab
	* descends into directories, and typed input filters or resolves
	* as a path. No buffer is opened — the path is only returned.
	* 
	* `directory` anchors the browser somewhere else (a relative path
	* resolves against the window's working directory) and typed
	* relative input then resolves there too. `showHidden` overrides
	* the config's dotfile visibility for this pick — pass `true` when
	* the file being picked is itself a dotfile (a tour manifest, an
	* editorconfig), which the default would hide.
	*/
	pickFile(label: string, directory?: string | null, showHidden?: boolean | null): Promise<string | null>;
	/**
	* Start an interactive prompt.
	* 
	* When `floatingOverlay` is true, the editor renders the prompt
	* and its suggestions inside a centred floating frame instead of
	* the bottom minibuffer row (issue #1796 — Live Grep). The flag
	* is rendering-only; confirm/cancel/hooks behave identically to a
	* non-overlay prompt of the same `promptType`.
	*/
	startPrompt(label: string, promptType: string, floatingOverlay?: boolean): boolean;
	/**
	* Begin a key-capture window for the calling plugin.
	* 
	* Pair with `endKeyCapture()` around any `getNextKey()` loop.
	* While capture is active, keys arriving between two
	* `getNextKey()` calls are buffered in-order rather than
	* falling through to the buffer / mode bindings, so fast typing,
	* pastes, or held-key auto-repeat are delivered losslessly.
	* Without this, a plugin's input loop has a race where keys
	* typed while the plugin is mid-redraw can leak into the editor.
	*/
	beginKeyCapture(): boolean;
	/**
	* End the key-capture window and discard any unconsumed buffered
	* keys.  Call from a `finally` block so capture is released even
	* if the plugin's loop throws.
	*/
	endKeyCapture(): boolean;
	/**
	* Wait for the next keypress and resolve with a `KeyEventPayload`.
	* 
	* While the returned promise is pending the editor consumes the
	* next key and resolves it; the key does not propagate to mode
	* bindings or other dispatch. Multiple in-flight requests across
	* plugins are FIFO. Designed for short input loops (flash labels,
	* vi find-char, replace-char) that would otherwise need to bind
	* every printable key in `defineMode`.
	* 
	* For lossless capture against fast typing or paste, wrap the
	* loop with `beginKeyCapture()` / `endKeyCapture()`.
	*/
	getNextKey(): Promise<KeyEventPayload>;
	/**
	* Start a prompt with initial value. See `startPrompt` for the
	* meaning of `floatingOverlay`.
	*/
	startPromptWithInitial(label: string, promptType: string, initialValue: string, floatingOverlay?: boolean): boolean;
	/**
	* Set suggestions for the current prompt
	* 
	* Uses typed Vec<Suggestion> - serde validates field names at runtime
	*/
	setPromptSuggestions(suggestions: PromptSuggestion[], selectedIndex?: number | null): boolean;
	setPromptInputSync(sync: boolean): boolean;
	/**
	* Set the title shown in the floating-overlay prompt's frame
	* header (issue #1796) as styled segments. Each segment
	* carries optional `Partial<OverlayOptions>`, the same
	* styling primitive used by virtual text — plugins mark
	* keybinding hints with `{ fg: "ui.help_key_fg" }`,
	* separators with `{ fg: "ui.popup_border_fg" }`, etc. Pass
	* an empty array to clear the title and fall back to the
	* prompt-type default. Has no visible effect on non-overlay
	* prompts.
	*/
	setPromptTitle(title: StyledText[]): boolean;
	/**
	* Set the footer chrome row of the floating-overlay prompt's
	* results pane. Plugins use this for hotkey-hint banners
	* (Orchestrator's `[n] new   [d] dive   [Esc] close` row).
	* Empty array clears the footer. Has no visible effect on
	* non-overlay prompts.
	*/
	setPromptFooter(footer: StyledText[]): boolean;
	/**
	* Set the floating-overlay prompt's input-row status text (right-aligned,
	* left of the match count). Empty string clears it.
	*/
	setPromptStatus(status: string): boolean;
	/**
	* Set the floating-overlay prompt's toolbar as a `WidgetSpec` (real,
	* clickable `Toggle`/`Button` widgets rendered in the header band, in
	* place of the styled-text title). Pass `null`/`undefined` to clear it.
	*/
	setPromptToolbar(specObj: unknown): boolean;
	/**
	* Toggle a floating-overlay toolbar control by its widget `key`. The host
	* owns the toggle's checked state, flips it, and emits a `widget_event`
	* the plugin can listen for. Lets a plugin route its own Alt+… shortcut
	* through the same host path as a click / Space on the toggle.
	*/
	toggleOverlayToolbarWidget(key: string): boolean;
	/**
	* Override the currently-highlighted suggestion row in the
	* open prompt. The editor clamps `index` to the suggestion
	* list's bounds and the renderer scrolls it into view on
	* the next frame. No-op when no prompt is open or the
	* suggestion list is empty. Typical use: re-opening a
	* picker and pre-selecting the entry the user last acted on
	* (Orchestrator highlights the active session).
	*/
	setPromptSelectedIndex(index: number): boolean;
	/**
	* Define a buffer mode (takes bindings as array of [key, command] pairs)
	*/
	defineMode(name: string, bindingsArr: string[][], readOnly?: boolean, allowTextInput?: boolean, inheritNormalBindings?: boolean): boolean;
	/**
	* Set the global editor mode
	*/
	setEditorMode(mode: string | null): boolean;
	/**
	* Get the current editor mode
	*/
	getEditorMode(): string | null;
	/**
	* Close a split.
	* 
	* Queued, like every layout mutation: the returned bool only reports that
	* the command was sent, not that it took effect, and a read issued right
	* after it still sees the old state. `await editor.flush()` before
	* reading back.
	*/
	closeSplit(splitId: number): boolean;
	/**
	* Show one of a split's existing tabs. To *move* a buffer into a pane —
	* and take it out of the pane it was in — use `moveBufferToSplit`.
	* 
	* Queued, like every layout mutation: the returned bool only reports that
	* the command was sent, not that it took effect, and a read issued right
	* after it still sees the old state. `await editor.flush()` before
	* reading back.
	*/
	setSplitBuffer(splitId: number, bufferId: number): boolean;
	/**
	* Move focus to a split.
	* 
	* To open something without taking focus in the first place, prefer
	* `splitWindow({ keepFocus: true })` — one call, and the user's cursor
	* never moves.
	* 
	* Queued, like every layout mutation: the returned bool only reports that
	* the command was sent, not that it took effect, and a read issued right
	* after it still sees the old state. `await editor.flush()` before
	* reading back.
	*/
	focusSplit(splitId: number): boolean;
	/**
	* Create a new editor session rooted at `root`. `root` must be
	* an absolute path; relative paths are rejected by the editor
	* (logged, no session created). The new session's id is
	* reported via the `window_created` hook payload — plugins
	* that need the id should listen for that event rather than
	* polling `listWindows`.
	* 
	* Returns `false` only when the IPC channel to the editor is
	* closed (editor is shutting down).
	*/
	createWindow(root: string, label: string): boolean;
	/**
	* Make the session with id `id` the active one. No-op if
	* already active. Errors (id not found) are logged on the
	* editor side; the JS caller can verify by reading
	* `activeWindow()` after.
	* 
	* **Not every id you can read is a window id.** The orchestrator's
	* `listWorkspaces()` reports a *negative* `windowId` for a workspace it
	* discovered on disk but has never activated — there is no window yet,
	* so the negative value is a placeholder, not a handle. Passing one here
	* returns `false`; to open such a workspace use
	* `getPluginApi("orchestrator").focusWorkspace(workspaceId)`, which
	* attaches a session at the worktree first.
	* 
	* Returns `false` for any non-positive id rather than throwing. It used
	* to be declared as an unsigned integer, so a negative id failed inside
	* the JS→Rust conversion with `Error converting from js 'f64' into type
	* 'u64': Underflow` — an exception, from a line that looked fine, naming
	* nothing the caller had written.
	*/
	setActiveWindow(id: number): boolean;
	/**
	* Switch the active window with a directional wipe on the
	* incoming content. `from_edge`: "top" | "bottom" | "left" |
	* "right". See `PluginCommand::SetActiveWindowAnimated`.
	* 
	* Same id rules as `setActiveWindow`: a non-positive id returns `false`.
	*/
	setActiveWindowAnimated(id: number, fromEdge: string): boolean;
	/**
	* Restrict (and order) the windows that Next/Prev Window cycle
	* through to `ids`, in this order. An empty array clears the
	* override (back to every window, by id). Non-open ids are skipped
	* at cycle time. See `PluginCommand::SetWindowCycleOrder`.
	*/
	setWindowCycleOrder(ids: number[]): boolean;
	/**
	* Close session `id`. Refuses to close the active session or
	* the base session (id 1). Logs and no-ops on failure.
	*/
	closeWindow(id: number): boolean;
	/**
	* Forget a directory's persisted workspace so a permanently deleted
	* or archived in-place session does not reappear on the next launch.
	* `closeWindow` only drops the live window — this removes the on-disk
	* registry file the session discovery would otherwise rediscover.
	* Call it *after* `closeWindow` for a session whose directory stays
	* on disk (a worktree-owning session is forgotten by removing its
	* worktree instead). No-op if nothing is persisted for `root`.
	*/
	deleteWorkspace(root: string): boolean;
	/**
	* Eagerly initialise an inactive session's per-session state
	* (file tree walk, ignore matcher, etc.) without diving.
	* No-op for the active session or unknown id.
	*/
	prewarmWindow(id: number): boolean;
	/**
	* Register a `notify`-backed watch on `path`. Returns a
	* promise that resolves to a numeric `handle` (also passed
	* in subsequent `path_changed` event payloads). The promise
	* rejects on `notify` errors (path missing, kernel limit).
	* 
	* `recursive` defaults to `false`. Non-recursive watches
	* cover the path itself plus its direct children for
	* directories — see `services/file_watcher.rs` for the
	* rationale.
	*/
	watchPath(path: string, recursive?: boolean): Promise<number>;
	/**
	* Drop a watcher by its handle. Unknown handles are
	* silently ignored.
	*/
	unwatchPath(handle: number): boolean;
	/**
	* Tell the editor that the floating-overlay prompt's
	* preview pane should render the entire split tree of
	* session `id` natively. `0` (or any unknown id) clears the
	* override and the preview falls back to the existing
	* path-based phantom-leaf renderer.
	* 
	* Orchestrator calls this on each prompt-selection-change so
	* the right pane shows the highlighted session's full
	* editor UI live — splits, terminals, syntax highlighting,
	* decorations — at native rendering cost.
	*/
	previewWindowInRect(id: number): boolean;
	/**
	* Clear the session-preview override. Equivalent to
	* `previewWindowInRect(0)` but reads better at call sites.
	*/
	clearWindowPreview(): boolean;
	/**
	* All editor sessions, sorted by id (creation order). Always
	* non-empty (the base session is always present).
	*/
	listWindows(): WindowInfo[];
	/**
	* The currently active session id. Always present in
	* `listWindows()`.
	*/
	activeWindow(): number;
	/**
	* Set the scroll position of a split.
	* 
	* Queued, like every layout mutation: the returned bool only reports that
	* the command was sent, not that it took effect, and a read issued right
	* after it still sees the old state. `await editor.flush()` before
	* reading back.
	*/
	setSplitScroll(splitId: number, topByte: number): boolean;
	/**
	* Resize the split that `split_id` lives in.
	* 
	* `split_id` is a leaf id (as returned by `getActiveSplitId`,
	* `listSplits`, `BufferInfo.splits`, `createTerminal`); the editor
	* resolves it to its parent split container and sets that container's
	* ratio, moving the divider between this pane and its sibling. `ratio`
	* is the fraction of space given to the container's FIRST child
	* (0.0–1.0, 0.5 = equal), clamped to [0.1, 0.9]. A leaf with no parent
	* container (the only pane) is a no-op.
	* 
	* Queued, like every layout mutation: the returned bool only reports that
	* the command was sent, not that the resize succeeded, and a read issued
	* right after it still sees the old widths. `await editor.flush()` before
	* reading back.
	*/
	setSplitRatio(splitId: number, ratio: number): boolean;
	/**
	* Set a label on a split (e.g., "sidebar")
	*/
	setSplitLabel(splitId: number, label: string): boolean;
	/**
	* Remove a label from a split
	*/
	clearSplitLabel(splitId: number): boolean;
	/**
	* Find a split by label (async)
	*/
	getSplitByLabel(label: string): Promise<number | null>;
	/**
	* Distribute all splits evenly
	*/
	distributeSplitsEvenly(): boolean;
	/**
	* Set cursor position in a buffer
	*/
	setBufferCursor(bufferId: number, position: number): boolean;
	/**
	* Toggle whether the editor draws a native caret in this buffer.
	* 
	* Buffer-group panel buffers default to `show_cursors = false`, which
	* also blocks all native movement actions in `action_to_events`. Plugins
	* that want native cursor motion in a panel (e.g. magit-style row
	* navigation) call this with `true` after `createBufferGroup` returns.
	*/
	setBufferShowCursors(bufferId: number, show: boolean): boolean;
	/**
	* Set a line indicator in the gutter
	*/
	setLineIndicator(bufferId: number, line: number, namespace: string, symbol: string, r: number, g: number, b: number, priority: number): boolean;
	/**
	* Batch set line indicators in the gutter
	*/
	setLineIndicators(bufferId: number, lines: number[], namespace: string, symbol: string, r: number, g: number, b: number, priority: number): boolean;
	/**
	* Clear line indicators in a namespace
	*/
	clearLineIndicators(bufferId: number, namespace: string): boolean;
	/**
	* Replace this namespace's scrollbar markers for a buffer.
	* 
	* Markers are painted on the vertical scrollbar track at positions
	* proportional to their location in the buffer, so marked content is
	* visible at a glance even when it is scrolled off screen. Each marker is
	* positioned by byte offset (`position`, preferred — it is exact on files
	* of any size) or by 0-based `line`, optionally spans to `end`, and
	* carries an RGB triple or a theme key as its `color`.
	* 
	* The set is replaced atomically, so a refresh never renders a partially
	* rebuilt set.
	*/
	setScrollbarMarkers(bufferId: number, namespace: string, markers: ScrollbarMarker[]): boolean;
	/**
	* Replace only the scrollbar markers currently anchored in
	* `[start, end)`, leaving this namespace's markers elsewhere in the
	* buffer untouched.
	* 
	* This is the primitive for plugins that decorate the viewport as it
	* scrolls (a `lines_changed` producer): publish the region you just
	* scanned without resending — or losing — the rest of the file.
	*/
	setScrollbarMarkersInRange(bufferId: number, namespace: string, start: number, end: number, markers: ScrollbarMarker[]): boolean;
	/**
	* Remove all scrollbar markers in a namespace
	*/
	clearScrollbarMarkers(bufferId: number, namespace: string): boolean;
	/**
	* Show or hide line numbers for a buffer **on the user's behalf**.
	* 
	* This records the same explicit per-buffer pin as "Toggle Line Numbers
	* (Current Buffer)": it beats any mode default, and is persisted with the
	* rest of the per-file workspace state. Use it for a setting the user
	* asked for — vi's `:set number` / `:set nonumber` are exactly that, a
	* typed command that happens to arrive through a plugin.
	* 
	* A mode stating its own preference for the buffers it has taken over
	* wants `setLineNumbersDefault` instead: re-asserting the pin from a
	* `buffer_activated` handler overwrites whatever the user chose
	* (issue #2931).
	*/
	setLineNumbers(bufferId: number, enabled: boolean): boolean;
	/**
	* Set this plugin's line-number *default* for a buffer, the way
	* `setFoldIndicators` does for the gutter's fold arrows.
	* 
	* Pass `null` to withdraw the plugin's opinion and fall back to the
	* user's own setting. The plugin's value is stored separately from that
	* setting and is never persisted, so it can neither overwrite a
	* deliberate choice — "Toggle Line Numbers (Current Buffer)" and
	* `setLineNumbers` still win while this is set — nor leak into the saved
	* session. A mode that hides the gutter should still clear its value on
	* the way out.
	*/
	setLineNumbersDefault(bufferId: number, enabled: boolean | null): boolean;
	/**
	* Show or hide the gutter's fold indicators (`▾` / `▸`) for a buffer in
	* the active split, the way `setLineNumbers` does for line numbers.
	* 
	* Pass `null` to withdraw the plugin's opinion and fall back to the
	* user's own setting. The plugin's value is stored separately from that
	* setting and is never persisted, so it can neither overwrite a
	* deliberate choice — "Toggle Folding Indicators (Current Buffer)" still
	* wins while this is set — nor leak into the saved session. A mode that
	* hides them should still clear its value on the way out.
	*/
	setFoldIndicators(bufferId: number, enabled: boolean | null): boolean;
	/**
	* Enable or disable indentation guides for a buffer, overriding the global
	* `editor.indentation_guide` setting. Tool views that render non-editable
	* content (e.g. the Git Log commit-detail diff) disable them.
	*/
	setIndentationGuide(bufferId: number, enabled: boolean): boolean;
	/**
	* Set the view mode for a buffer ("source" or "compose")
	*/
	setViewMode(bufferId: number, mode: string): boolean;
	/**
	* Enable or disable line wrapping for a buffer/split
	*/
	setLineWrap(bufferId: number, splitId: number | null, enabled: boolean): boolean;
	/**
	* Set plugin-managed per-buffer view state (write-through to snapshot + command for persistence)
	*/
	setViewState(bufferId: number, key: string, value: unknown): boolean;
	/**
	* Get plugin-managed per-buffer view state (reads from snapshot)
	*/
	getViewState(bufferId: number, key: string): unknown;
	/**
	* Create or replace an interval marker `[start, end)` with `payload`,
	* keyed by `key`, on the given buffer. The editor keeps `start`/`end`
	* shifted across edits; an edit inside the range is the plugin's signal
	* (via after_insert/after_delete) to re-parse and update or delete it.
	*/
	createMarker(bufferId: number, key: string, start: number, end: number, payload: unknown): boolean;
	/**
	* Update an existing marker's payload (keeping its current byte range).
	* Returns false if no marker with `key` exists on the buffer.
	*/
	updateMarker(bufferId: number, key: string, payload: unknown): boolean;
	/**
	* Delete a marker by key. Returns false if it did not exist.
	*/
	deleteMarker(bufferId: number, key: string): boolean;
	/**
	* Return all markers on the buffer whose range overlaps `[start, end)`,
	* as an array of `{ id, start, end, payload }`. O(n) over the buffer's
	* markers (a handful for typical documents).
	*/
	queryMarkers(bufferId: number, start: number, end: number): unknown;
	/**
	* Return a single marker by key as `{ id, start, end, payload }`, or null.
	*/
	getMarker(bufferId: number, key: string): unknown;
	/**
	* Set plugin-managed global state (write-through to snapshot + command for persistence).
	* State is automatically isolated per plugin using the plugin's name.
	* TODO: Need to think about plugin isolation / namespacing strategy for these APIs.
	*/
	setGlobalState(key: string, value: unknown): boolean;
	/**
	* Get plugin-managed global state (reads from snapshot).
	* State is automatically isolated per plugin using the plugin's name.
	* TODO: Need to think about plugin isolation / namespacing strategy for these APIs.
	*/
	getGlobalState(key: string): unknown;
	/**
	* Set per-session state on the **active** session. Same
	* shape as `setGlobalState` (write-through to snapshot +
	* dispatched to editor; null/undefined deletes), but the
	* underlying storage lives on `Session.plugin_state` and
	* swaps with the rest of session state on `setActiveWindow`.
	* Plugins that genuinely want per-project state use this;
	* Orchestrator itself uses `setGlobalState` because its session
	* list lives above session boundaries.
	*/
	setWindowState(key: string, value: unknown): boolean;
	/**
	* Get per-session state from the **active** session
	* (snapshot read). `undefined` if missing.
	*/
	getWindowState(key: string): unknown;
	/**
	* Create a scroll sync group for anchor-based synchronized scrolling
	*/
	createScrollSyncGroup(groupId: number, leftSplit: number, rightSplit: number): boolean;
	/**
	* Set sync anchors for a scroll sync group
	*/
	setScrollSyncAnchors(groupId: number, anchors: number[][]): boolean;
	/**
	* Remove a scroll sync group
	*/
	removeScrollSyncGroup(groupId: number): boolean;
	/**
	* Execute multiple actions in sequence
	* 
	* Takes typed ActionSpec array - serde validates field names at runtime
	*/
	executeActions(actions: ActionSpec[]): boolean;
	/**
	* Show an action popup
	* 
	* Takes a typed ActionPopupOptions struct - serde validates field names at runtime
	*/
	showActionPopup(opts: ActionPopupOptions): boolean;
	/**
	* Contribute a row to one of the menu bar's menus (e.g. a "Show Dock"
	* toggle under "View"). The target menu and the neighbour named by
	* `after` / `before` are matched by stable id (a menu `id`, an item's
	* `action`) as well as by display label, so the placement survives a
	* locale change. Naming a menu that doesn't exist is a no-op.
	* 
	* Takes a typed AddMenuItemOptions struct - serde validates field
	* names at runtime.
	*/
	addMenuItem(opts: AddMenuItemOptions): boolean;
	/**
	* Contribute (or replace, or clear) menu rows for the LSP-Servers
	* popup. Pass an empty `items` to clear this plugin's slice for
	* the given language. See `PluginCommand::SetLspMenuContributions`.
	*/
	setLspMenuContributions(pluginId: string, language: string, items: TsLspMenuItem[]): boolean;
	/**
	* Disable LSP for a specific language
	*/
	disableLspForLanguage(language: string): boolean;
	/**
	* Restart LSP server for a specific language
	*/
	restartLspForLanguage(language: string): boolean;
	/**
	* Claim an LSP URI scheme (e.g. "slang-synth"). LSP navigations that
	* resolve to a non-file URI with this scheme are routed to the
	* `lsp_open_external_uri` hook instead of the core's fallback message.
	*/
	registerLspUriScheme(scheme: string): boolean;
	/**
	* Mark the buffer backing `path` read-only. Race-free right after
	* `openFile` because both are FIFO commands.
	*/
	markFileReadOnly(path: string): boolean;
	/**
	* Set the workspace root URI for a specific language's LSP server
	* This allows plugins to specify project roots (e.g., directory containing .csproj)
	*/
	setLspRootUri(language: string, uri: string): boolean;
	/**
	* Get all diagnostics from LSP
	*/
	getAllDiagnostics(): JsDiagnostic[];
	/**
	* Get registered event handlers for an event
	*/
	getHandlers(eventName: string): string[];
	/**
	* Create a virtual buffer in current split (async, returns buffer and split IDs)
	*/
	createVirtualBuffer(opts: CreateVirtualBufferOptions): Promise<VirtualBufferResult>;
	/**
	* Create a virtual buffer in a new split (async, returns buffer and split IDs)
	*/
	createVirtualBufferInSplit(opts: CreateVirtualBufferInSplitOptions): Promise<VirtualBufferResult>;
	/**
	* Create a virtual buffer in an existing split (async, returns buffer and split IDs)
	*/
	createVirtualBufferInExistingSplit(opts: CreateVirtualBufferInExistingSplitOptions): Promise<VirtualBufferResult>;
	/**
	* Set the content of a panel within a buffer group
	*/
	setPanelContent(groupId: number, panelName: string, entriesArr: Record<string, unknown>[]): boolean;
	/**
	* Close a buffer group
	*/
	closeBufferGroup(groupId: number): boolean;
	/**
	* Switch a virtual buffer's mode — the keybinding set that applies
	* while it is focused.
	* 
	* A panel that grows a text field (an in-panel filter) needs the
	* single-key commands of its normal mode to stop firing while the
	* user types; giving the panel a text-input mode for the duration
	* does that without the plugin re-registering bindings. Modes are
	* declared with `defineMode`; a mode name with no definition falls
	* back to the global bindings.
	*/
	setBufferMode(bufferId: number, mode: string): boolean;
	/**
	* Show or hide one panel of a buffer group, without tearing the
	* group down.
	* 
	* The panel's buffer, its content and its scroll position all
	* survive being hidden — only the group's split tree changes, so
	* the remaining panels take over the freed space and a re-shown
	* panel comes back where it was. Use it for optional sidebars a
	* mode wants to toggle (a file list, a comments rail) instead of
	* closing and recreating the group.
	* 
	* Hiding a panel that holds focus moves focus to a panel that is
	* still rendered; focusing a hidden panel is a no-op. Returns
	* `false` if the group or panel is unknown, or if the call would
	* hide the group's last visible panel.
	* 
	* Queued, like every layout mutation: the returned bool only reports
	* that the command was sent.
	*/
	setBufferGroupPanelVisible(groupId: number, panelName: string, visible: boolean): boolean;
	/**
	* Focus a specific panel within a buffer group
	*/
	focusBufferGroupPanel(groupId: number, panelName: string): boolean;
	/**
	* Re-point a buffer-group's panel at a different buffer id.
	* 
	* Streaming plugins (e.g. git-log) allocate one file-backed
	* buffer per item and call this on navigation to swap which
	* buffer the panel displays — instead of mutating a single
	* shared buffer's contents. Resolves with `true` on success.
	*/
	setBufferGroupPanelBuffer(groupId: number, panelName: string, bufferId: number): Promise<boolean>;
	/**
	* Replace a virtual buffer's content with a list of styled **spans**.
	* 
	* Spans are concatenated *verbatim*: they are runs of text, not lines,
	* and nothing inserts separators for you. `[{text:"a"},{text:"b"}]` is
	* the single line `ab`; for two lines, write `[{text:"a\n"},{text:"b\n"}]`.
	* A buffer built without those newlines reports a plausible `length` and
	* a `lineCount` of 1 — read it back from `getBufferInfo` if it matters,
	* since otherwise the mistake is only visible on screen.
	* 
	* If you are an agent putting text in front of a human, prefer writing a
	* file and opening it (`splitWindow({ file })` /
	* `openFileInSplit(splitId, path)`). A file buffer gives you syntax
	* highlighting, search, save, and renders ANSI escape codes as colour —
	* so command output can go straight in. Virtual buffers exist for
	* plugin-owned panels: ephemeral, styled per span, driven by a mode's
	* keybindings.
	*/
	setVirtualBufferContent(bufferId: number, entriesArr: Record<string, unknown>[]): boolean;
	/**
	* Get text properties at cursor position (returns JS array)
	*/
	getTextPropertiesAtCursor(bufferId: number): TextPropertiesAtCursor;
	/**
	* Mount a declarative widget panel inside a virtual buffer.
	* 
	* `spec` is a `WidgetSpec` JSON tree (see fresh.d.ts for the
	* shape). The host renders the spec into the buffer; subsequent
	* `updateWidgetPanel` calls re-render the panel against the
	* previously-mounted spec.
	* 
	* Returns true on successful queue, false if the IPC channel is
	* closed.
	*/
	mountWidgetPanel(panelId: number, bufferId: number, specObj: unknown): boolean;
	/**
	* Replace the spec of a previously-mounted widget panel.
	* No-op if the panel id was never mounted.
	*/
	updateWidgetPanel(panelId: number, specObj: unknown): boolean;
	/**
	* Unmount a previously-mounted widget panel. The plugin retains
	* ownership of the underlying virtual buffer.
	*/
	unmountWidgetPanel(panelId: number): boolean;
	/**
	* Route a keystroke / nav action to the panel's focused widget.
	* 
	* `action` is a `WidgetAction` JSON object — see fresh.d.ts for
	* the shapes (`{kind: "focusAdvance", delta: 1}` etc.). Plugin's
	* `defineMode` bindings dispatch into here for keys handled by
	* the widget layer; the host runtime acts on the panel's
	* currently focused widget and fires `widget_event` as
	* appropriate.
	*/
	widgetCommand(panelId: number, actionObj: unknown): boolean;
	/**
	* Apply a targeted mutation to a mounted widget panel — the
	* IPC fast path. Use instead of `updateWidgetPanel` when the
	* model change touches a single widget; the host applies the
	* mutation in place without re-transmitting the full spec.
	* See `WidgetMutation` in fresh.d.ts for the shapes.
	*/
	widgetMutate(panelId: number, mutationObj: unknown): boolean;
	/**
	* Mount a declarative widget panel as a centered floating
	* overlay (not bound to any virtual buffer).
	*/
	mountFloatingWidget(panelId: number, specObj: unknown, widthPct: number, heightPct: number, asDock?: boolean, focusMarker?: boolean, title?: string, closable?: boolean, startBlurred?: boolean): boolean;
	/**
	* Replace the spec of the currently-mounted floating widget panel.
	*/
	updateFloatingWidget(panelId: number, specObj: unknown): boolean;
	/**
	* Tear down the floating widget panel.
	*/
	unmountFloatingWidget(panelId: number): boolean;
	/**
	* Control a mounted floating panel's placement / focus without
	* re-sending its spec. `op`: "dock" (`arg` = width in columns),
	* "center", "focus", "blur", "fullscreen" (`arg != 0` makes a
	* centered panel cover the whole frame over the dock). See
	* `PluginCommand::FloatingPanelControl`.
	*/
	floatingPanelControl(panelId: number, op: string, arg: number): boolean;
	/**
	* Spawn a process (async, returns request_id)
	* 
	* **No shell is involved.** `command` is executed directly, so quoting,
	* globbing, `|`, `&&`, `>` and `$VAR` are not interpreted — pass the
	* program and its arguments already split:
	* 
	* ```js
	* await editor.spawnProcess("gh", ["api", "graphql", "-f", query], repoDir);
	* ```
	* 
	* Wrapping the call in `/bin/sh -lc "…"` to get shell behaviour is
	* usually a mistake: a login shell sources the user's profile, which is
	* slow and can block outright.
	* 
	* The child inherits the **editor's** environment, including `PATH`, so
	* `git`, `gh` and anything else the user can run from their shell
	* resolves by bare name — no absolute paths needed.
	* 
	* `cwd` is the third argument; without it the child inherits the
	* editor's working directory, which is not necessarily the workspace you
	* meant.
	* 
	* Optional 4th argument `stdoutTo: string` pipes the child's stdout
	* directly into the named file instead of buffering it. The
	* resolved `SpawnResult.stdout` is empty in that case; the bytes
	* land on disk for `openFile` to pick up as a file-backed buffer.
	*/
	spawnProcess(command: string, args: string[], cwd?: string, stdoutTo?: string): ProcessHandle<SpawnResult>;
	/**
	* Spawn a process on the host regardless of the active authority.
	* 
	* Intended for plugin internals that must run host-side work
	* (e.g. `devcontainer up`) before installing an authority that
	* would otherwise route the spawn elsewhere. Same calling shape
	* as `spawnProcess`.
	*/
	spawnHostProcess(command: string, args: string[], cwd?: string): ProcessHandle<SpawnResult>;
	/**
	* Install a new authority via an opaque payload.
	* 
	* The payload is a JS object describing filesystem + spawner +
	* terminal wrapper + display label. The canonical schema lives in
	* the `AuthorityPayload` type in `fresh-editor`; plugins should
	* hand-build objects that match it. Fire-and-forget: the editor
	* restarts as part of the transition, so the plugin is reloaded
	* before any follow-up work can run on this call's return value.
	*/
	setAuthority(payload: AuthorityPayload): boolean;
	/**
	* Restore the default local authority. Same restart semantics as
	* `setAuthority`.
	*/
	clearAuthority(): void;
	/**
	* Attach to a remote agent that needs a live connection (an SSH host or a
	* `kubectl exec` agent in a Kubernetes pod). The connect is asynchronous —
	* the editor spawns the carrier, bootstraps the agent and builds the
	* session in the background — and this returns a promise that settles on
	* the real outcome:
	* 
	* * resolves once the session (authority + window) is fully
	* constructed, so a caller can keep its dialog open until there is a
	* real session to show;
	* * rejects with the failure reason (e.g. ssh "Could not resolve
	* hostname") if the connect or window creation fails — in which case
	* no window is created and the editor stays on its current authority.
	* 
	* The payload schema (`RemoteAgentSpec`) lives in `fresh-editor`;
	* plugins hand-build an object matching it.
	*/
	attachRemoteAgent(payload: RemoteAgentSpec): Promise<void>;
	/**
	* Cancel any in-flight `attachRemoteAgent` connect — the New-Session
	* dialog's Cancel. The pending promise rejects with "cancelled" and the
	* background connect's late result is discarded, so no window is built.
	* A no-op when nothing is connecting.
	*/
	cancelRemoteAgent(): void;
	/**
	* Activate an environment: set the live env recipe (`snippet` run in
	* `dir`). Applied to every spawn, re-evaluated on demand — no restart.
	* Honored only when the workspace is Trusted.
	*/
	setEnv(snippet: string, dir: string | null): void;
	/**
	* Deactivate the environment — spawns return to the inherited env.
	*/
	clearEnv(): void;
	/**
	* Override the Remote Indicator's displayed state. Plugins call
	* this to surface lifecycle transitions that the authority layer
	* doesn't know about yet — "Connecting" while `devcontainer up`
	* runs, "FailedAttach" after a non-zero exit, etc.
	* 
	* Accepts a tagged JS object:
	* ```ts
	* editor.setRemoteIndicatorState({ kind: "connecting", label: "Building" });
	* editor.setRemoteIndicatorState({ kind: "failed_attach", error: "exit 1" });
	* editor.setRemoteIndicatorState({ kind: "connected", label: "Container:abc" });
	* editor.setRemoteIndicatorState({ kind: "local" });
	* ```
	* 
	* The override sticks until replaced or cleared via
	* `clearRemoteIndicatorState`. Editor restart (e.g. on
	* `setAuthority`) resets it — plugins must reassert after a
	* post-restart init if they want the override to persist.
	*/
	setRemoteIndicatorState(state: RemoteIndicatorStatePayload): boolean;
	/**
	* Drop any active Remote Indicator override. Safe to call even
	* without a prior `setRemoteIndicatorState`.
	*/
	clearRemoteIndicatorState(): void;
	/**
	* Fetch a URL over HTTP(S) and stream the response body into `target_path`.
	* 
	* Resolves with a `SpawnResult`-shaped value: `exit_code` is `0` on a
	* 2xx response (file written), the HTTP status code on non-2xx
	* (target file untouched), and `-1` on transport errors. `stderr`
	* carries an error message in the non-success cases; `stdout` is
	* always empty.
	* 
	* This uses the editor's built-in HTTP client (`ureq`), so plugins
	* don't need `curl`/`wget` on PATH.
	*/
	httpFetch(url: string, targetPath: string): ProcessHandle<SpawnResult>;
	/**
	* Wait for a process to complete and get its result (async)
	*/
	spawnProcessWait(processId: number): Promise<SpawnResult>;
	/**
	* Read buffer text.
	* 
	* `getBufferText(id)` returns the **whole buffer** — the common case,
	* and the one that used to require reading `length` from
	* `getBufferInfo` and passing it back. `getBufferText(id, start, end)`
	* still reads a byte range, so existing callers are unaffected.
	* 
	* Byte offsets, not character or line offsets.
	*/
	getBufferText(bufferId: number, start?: number, end?: number): Promise<string>;
	/**
	* Register a diff baseline for a buffer (async). `kind` is one of
	* "saved" | "disk" | "gitRef" | "gitIndex"; `gitRef` carries the ref
	* for kind "gitRef". Resolves with the baseline id once the
	* reference content is loaded host-side — no file content ever
	* crosses the plugin bridge. Baselines are dropped automatically
	* when their buffer closes, or explicitly via
	* `releaseDiffBaseline`.
	*/
	registerDiffBaseline(bufferId: number, kind: string, gitRef: string | null): Promise<number>;
	/**
	* Diff a buffer's live content against a registered baseline
	* (async). Resolves with a `DiffBaselineResult`; check its
	* `revision` against the buffer's current version before anchoring
	* decorations on the hunks.
	*/
	diffAgainstBaseline(bufferId: number, baselineId: number): Promise<DiffBaselineResult>;
	/**
	* Diff two registered baselines against each other (async) — e.g.
	* disk vs HEAD, the git-gutter comparison. Resolves with a
	* `DiffBaselineResult` whose `revision` is 0.
	*/
	diffBaselinePair(oldBaselineId: number, newBaselineId: number): Promise<DiffBaselineResult>;
	/**
	* Fetch baseline lines for `(startLine, count)` ranges in one
	* batched call (async). Lines come back without trailing newlines,
	* grouped per requested range — fetch only the old-side lines a
	* diff view actually renders.
	*/
	getBaselineLines(baselineId: number, ranges: number[][]): Promise<string[][]>;
	/**
	* Reload a baseline's reference content (async; call after a HEAD
	* move or an external write). Resolves once the fresh content is
	* serving.
	*/
	refreshDiffBaseline(baselineId: number): Promise<void>;
	/**
	* Drop a registered diff baseline.
	*/
	releaseDiffBaseline(baselineId: number): void;
	/**
	* Run `handlerName` every `intervalMs` milliseconds until cancelled.
	* Returns a timer id for `clearInterval`.
	* 
	* The alternative — a detached `while (alive) { await editor.delay(ms);
	* … }` loop — does work, and the bundled dashboard plugin uses one. But
	* it puts three obligations on you that a timer discharges for free:
	* 
	* 1. **A throw anywhere in the loop body ends it, silently.** The loop
	* is a detached async function, so the rejection has nowhere to
	* surface; the panel simply stops updating, with nothing in the log
	* pointing at why. Every `await` inside must be individually
	* guarded. A timer handler's throw is caught and logged by the host,
	* and the *next* tick still fires.
	* 2. **You must cancel it yourself.** A loop keeps running after its
	* plugin is unloaded or reloaded until its own guard notices, so it
	* needs a liveness check that survives a reload — an identity check
	* (`myBufferId === currentBufferId`), not a boolean, or a reopened
	* panel ends up with two loops. Timers are cancelled on unload.
	* 3. **The first iteration is one period late** unless you also do the
	* work once before entering the loop.
	* 
	* A loop is still the better shape when each iteration's decision
	* depends on the last one's result, or when you want a single ticker
	* driving many items on their own schedules (again: see dashboard.ts,
	* which ticks at 1s and re-runs a section only once its own TTL has
	* expired, so cost scales with the sum of the sections' rates rather
	* than tick-rate × section-count).
	* 
	* The handler is named, not passed as a function, for the same reason
	* `registerCommand` takes a name: the host invokes it by looking it up
	* on `globalThis`. Declare it with `registerHandler("myTick", fn)`.
	* 
	* The handler may be `async`; a fire is not awaited, and a slow handler
	* does not delay the editor. Ticks are *not* queued — if a fire is
	* still outstanding when the next is due, the next simply happens, so
	* guard re-entrancy yourself (`if (inFlight) return;`) when a tick can
	* outlast its period.
	* 
	* Timers are cancelled automatically when the owning plugin is unloaded
	* or reloaded, so a hot-reload during development does not leave the
	* previous copy ticking alongside the new one.
	* 
	* `intervalMs` is clamped to a floor (see `MIN_PLUGIN_TIMER_MS` in the
	* host) so a `0` cannot spin the editor.
	*/
	setInterval(intervalMs: number, handlerName: string): number;
	/**
	* Run `handlerName` once, `delayMs` from now. Returns a timer id, so a
	* pending one-shot can still be cancelled with `clearInterval`.
	* 
	* Same contract as `setInterval` — named handler, host-driven, cancelled
	* on plugin unload. Use it when the continuation should happen whether
	* or not the code that scheduled it is still around; use
	* `await editor.delay(ms)` when you are pausing work you are already
	* inside of and want to keep the local variables.
	*/
	setTimeout(delayMs: number, handlerName: string): number;
	/**
	* Cancel a timer from `setInterval` / `setTimeout`.
	* 
	* Returns `false` when this plugin holds no live timer under that id —
	* which covers a typo, a double-cancel, and a one-shot that has already
	* fired. None of those is an error, so none throws.
	* 
	* Only your own timers are cancellable: ids come from a counter shared
	* across plugins, so accepting an arbitrary id would let one plugin stop
	* another's refresh.
	*/
	clearInterval(timerId: number): boolean;
	/**
	* Delay/sleep (async, returns request_id)
	* 
	* Resolves after `durationMs`. Two things it is very good at:
	* 
	* - a pause inside work you are already inside of — a debounce, a retry
	* backoff, a settle before reading state back;
	* - a **timeout**, by racing it against the real work:
	* ```js
	* const timedOut = Symbol("timeout");
	* const outcome = await Promise.race([
	* doTheWork().then(() => "ok"),
	* editor.delay(8000).then(() => timedOut),
	* ]);
	* ```
	* which is how the bundled dashboard stops one slow section from
	* stalling the panel.
	* 
	* For a *periodic background* task, weigh it against
	* `editor.setInterval(ms, "handlerName")`. A detached
	* `while (…) { await editor.delay(ms); … }` loop works, but it dies
	* silently on the first unguarded throw, is not cancelled when the
	* plugin unloads, and does its first iteration one period late — see
	* `setInterval` for when each shape is the right one.
	* 
	* Note the loop keeps running after the plugin that created it is
	* unloaded or reloaded, until its own guard notices. Gate it on an
	* identity (`myBufferId === currentBufferId`) rather than a boolean, or
	* reloading leaves two loops racing.
	*/
	delay(durationMs: number): Promise<void>;
	/**
	* Project-wide grep search (async)
	* Searches all files in the project, respecting .gitignore.
	* Open buffers with dirty edits are searched in-memory.
	*/
	grepProject(pattern: string, fixedString: boolean | null, caseSensitive: boolean | null, maxResults: number | null, wholeWords: boolean | null): Promise<GrepMatch[]>;
	/**
	* Begin a streaming project-wide search and return a `SearchHandle`.
	* The producer (host) writes matches at full speed into shared state;
	* the consumer drains via `handle.take()` at its own cadence. Call
	* `handle.cancel()` to abort.
	*/
	beginSearch(pattern: string, opts?: {
		fixedString?: boolean;
		caseSensitive?: boolean;
		maxResults?: number;
		wholeWords?: boolean;
		sourceBufferId?: number;
		fileGlob?: string;
	}): SearchHandle;
	/**
	* Replace matches in a file's buffer (async)
	* Opens the file if not already in a buffer, applies edits via the buffer model,
	* and saves. All edits are grouped as a single undo action.
	*/
	replaceInFile(filePath: string, matches: number[][], replacement: string, bufferId?: number): Promise<ReplaceResult>;
	/**
	* Send LSP request (async, returns request_id)
	*/
	sendLspRequest(language: string, method: string, params: Record<string, unknown> | null): Promise<unknown>;
	/**
	* Spawn a background process (async, returns request_id which is also process_id)
	*/
	spawnBackgroundProcess(command: string, args: string[], cwd?: string): ProcessHandle<BackgroundProcessResult>;
	/**
	* Kill a background process
	*/
	killBackgroundProcess(processId: number): boolean;
	/**
	* Create a new terminal in a split (async, returns TerminalResult)
	*/
	createTerminal(opts?: CreateTerminalOptions): Promise<TerminalResult>;
	/**
	* Create a new editor window seeded with an agent terminal as
	* its only buffer. Atomic — replaces the legacy
	* `createWindow` + `setActiveWindow` + `createTerminal`
	* chain that left a transient `[No Name]` tab alongside the
	* agent terminal.
	*/
	createWindowWithTerminal(opts: CreateWindowWithTerminalOptions): Promise<SessionWithTerminalResult>;
	/**
	* Open a workspace *before* its contents exist: a real window (own id,
	* durable stable id, label, authority) showing a "still being built"
	* placeholder page. Focus can move into it right away, and the dock
	* row is a full workspace — renameable, filable, closable — while the
	* slow part (a `git worktree add`, say) runs behind it.
	* 
	* Narrate progress with `setWindowPreparing`, then hand the id to
	* `createWindowWithTerminal` as `adoptWindow` to turn the placeholder
	* into the live session in place, ids and all.
	*/
	createPreparingWindow(opts: CreatePreparingWindowOptions): Promise<PreparingWindowResult>;
	/**
	* Update the progress line (and displayed name) on a preparing window
	* — `failed` switches it to the error copy — or clear the preparing
	* state with `done` so the window renders as an ordinary session
	* again. An empty `label` leaves the displayed name alone.
	* 
	* Returns `false` only when the channel to the editor is closed.
	*/
	setWindowPreparing(id: number, message: string, label: string | null, failed: boolean, done: boolean): boolean;
	/**
	* Send input data to a terminal
	*/
	sendTerminalInput(terminalId: number, data: string): boolean;
	/**
	* Close a terminal
	*/
	closeTerminal(terminalId: number): boolean;
	/**
	* Send `signal` ("SIGTERM" / "SIGKILL" / "SIGINT" / "SIGHUP")
	* to every process group the window `id` is tracking. The
	* window's authority decides delivery; this is the
	* canonical entry point for "stop everything this window
	* owns" rather than reaching at the terminal level. Returns
	* `false` only when the command channel is closed.
	*/
	signalWindow(id: number, signal: string): boolean;
	/**
	* Force refresh of line display
	*/
	refreshLines(bufferId: number): boolean;
	/**
	* Get the current locale
	*/
	getCurrentLocale(): string;
	/**
	* Load a plugin from a file path (async)
	*/
	loadPlugin(path: string): Promise<boolean>;
	/**
	* Unload a plugin by name (async)
	*/
	unloadPlugin(name: string): Promise<boolean>;
	/**
	* Reload a plugin by name (async)
	*/
	reloadPlugin(name: string): Promise<boolean>;
	/**
	* List all loaded plugins (async)
	* Returns array of { name: string, path: string, enabled: boolean }
	*/
	listPlugins(): Promise<Array<{
		name: string;
		path: string;
		enabled: boolean;
	}>>;
	/**
	* Re-read `~/.config/fresh/init.ts` and run it — the scriptable form of
	* the "init: Reload" palette command, and the same thing
	* `fresh --cmd init reload` sends.
	* 
	* Use this rather than `reloadPlugin("init.ts")`: init.ts is not loaded
	* from a path (its plugin path is the sentinel `<buffer:init.ts>`), so
	* the by-name plugin reload cannot find it.
	* 
	* Reloading drops the previous init.ts's commands, handlers, event
	* subscriptions and settings before the new source runs, so the
	* author → reload → test loop needs no editor restart. Resolves `true`
	* once the new source has run; rejects with the parse error if the file
	* does not compile (the old init.ts stays live in that case).
	* 
	* Calling this *from* init.ts re-enters the reload; guard it if you do.
	*/
	reloadInit(): Promise<boolean>;
	/**
	* Run a registered command by the exact name it shows in the command
	* palette — the same dispatch the palette performs on that row, so a
	* command handler is exercised through its real path rather than by
	* calling the plugin function directly.
	* 
	* Resolves `true` when the command was found and dispatched; rejects
	* when no command carries that name (so a typo is an error, not a
	* silent no-op). The command's *own* async work is not awaited — this
	* resolves once dispatch happened, exactly like a keypress would.
	* 
	* `fresh --cmd command run "<name>"` is this call from a shell.
	*/
	runCommand(name: string): Promise<boolean>;
	/**
	* Every registered command — built-ins and plugin commands together —
	* as `{ name, description, source, plugin }`, where `source` is
	* `"builtin"` or `"plugin"` and `plugin` names the owner (empty for
	* built-ins).
	* 
	* The point of this from a plugin's own script: confirming that your
	* `registerCommand` actually landed, under the name you expect, before
	* hunting for why the palette "doesn't show it".
	*/
	listCommands(): Promise<Array<{
		name: string;
		description: string;
		source: string;
		plugin: string;
	}>>;
}
/**
* Typed overload of `editor.getPluginApi`. When the caller passes a
* key that some loaded plugin declared in `FreshPluginRegistry`, the
* return type is narrowed to that plugin's API. Unknown names fall
* through to the untyped `unknown | null` signature.
*/
interface EditorAPI {
	getPluginApi<K extends keyof FreshPluginRegistry>(name: K): FreshPluginRegistry[K] | null;
}
/**
* Typed overload of `editor.defineConfigEnum`. The macro-generated
* signature can't express `<E extends string>` propagating from the
* `values` array into the return type, so it's declared here. Use
* `as const` on the `values` array to get a literal-union return:
*
* ```ts
* const mode = editor.defineConfigEnum("mode", {
*   values: ["normal", "insert"] as const,
*   default: "normal",
* });
* mode; // typed as "normal" | "insert"
* ```
*
* Typed overload of `editor.getPluginConfig`. Plugins that declared
* their fields via `defineConfigX` can pass the shape type explicitly:
* `editor.getPluginConfig<{ autoEnable: boolean; ... }>()`. Without
* the generic, falls back to `unknown`.
*/
interface EditorAPI {
	defineConfigEnum<E extends string>(name: string, options: {
		values: readonly E[];
		default: NoInfer<E>;
		description?: string;
	}): E;
	getPluginConfig<T = unknown>(): T;
}
/**
* Maps every hook event name to its payload type.
*
* Payloads match the flat JSON produced by `hook_args_to_json` on the Rust
* side (`HookArgs` is `#[serde(untagged)]`, so each variant serializes as its
* fields only). The TypeScript types here are derived directly from the Rust
* field definitions and must be kept in sync with `fresh-core/src/hooks.rs`.
*
* `action` in `pre_command`/`post_command` is the serde JSON of the `Action`
* enum: unit variants serialize as a plain string (e.g. `"MoveLeft"`),
* tuple variants as a single-key object (e.g. `{"InsertChar": "a"}`).
*/
interface HookEventMap {
	// ── lifecycle ────────────────────────────────────────────────────────────
	editor_initialized: Record<string, never>;
	plugins_loaded: Record<string, never>;
	ready: Record<string, never>;
	focus_gained: Record<string, never>;
	authority_changed: {
		label: string;
	};
	trust_changed: {
		level: "trusted" | "restricted" | "blocked";
	};
	/**
	* The effective config changed — the user saved from the Settings UI,
	* or the config was reloaded from disk. Payload-free by design:
	* re-read what you care about with `editor.getPluginConfig()` /
	* `editor.getConfig()`, both of which already reflect the new values
	* when the handler runs.
	*
	* Any plugin that caches a `defineConfigX` value (rather than reading
	* it at point of use) should subscribe, or its setting will appear to
	* do nothing until the editor restarts. Does not fire for the
	* plugin's own `editor.setSetting(...)` writes.
	*/
	config_changed: Record<string, never>;
	// ── buffer lifecycle ─────────────────────────────────────────────────────
	buffer_activated: {
		buffer_id: number;
	};
	buffer_deactivated: {
		buffer_id: number;
	};
	buffer_closed: {
		buffer_id: number;
	};
	// ── file I/O ─────────────────────────────────────────────────────────────
	before_file_open: {
		path: string;
	};
	after_file_open: {
		path: string;
		buffer_id: number;
	};
	before_file_save: {
		path: string;
		buffer_id: number;
	};
	after_file_save: {
		path: string;
		buffer_id: number;
	};
	/**
	* Fired after a buffer is reloaded from disk: auto-revert picked up an
	* external change (e.g. `git checkout <ref> -- <file>` in another
	* terminal), or the user ran an explicit revert. Reloads don't fire
	* `after_file_save`, so plugins that surface disk-derived state
	* (git gutter, etc.) should subscribe to this too or their decorations
	* go stale on every external reset.
	*/
	after_file_revert: {
		path: string;
		buffer_id: number;
	};
	/**
	* Fired by the file explorer after a paste/duplicate/etc. mutates
	* the filesystem without going through a buffer save. Plugins that
	* surface FS-derived state (git status badges, etc.) should
	* subscribe in addition to `after_file_save` to refresh on
	* explorer-driven changes too.
	*/
	after_file_explorer_change: {
		path: string;
	};
	// ── text edits ───────────────────────────────────────────────────────────
	before_insert: {
		buffer_id: number;
		position: number;
		text: string;
	};
	after_insert: {
		buffer_id: number;
		position: number;
		text: string;
		affected_start: number;
		affected_end: number;
		start_line: number;
		end_line: number;
		lines_added: number;
	};
	before_delete: {
		buffer_id: number;
		start: number;
		end: number;
	};
	after_delete: {
		buffer_id: number;
		start: number;
		end: number;
		deleted_text: string;
		affected_start: number;
		deleted_len: number;
		start_line: number;
		end_line: number;
		lines_removed: number;
	};
	// ── cursor & viewport ────────────────────────────────────────────────────
	cursor_moved: {
		buffer_id: number;
		cursor_id: number;
		old_position: number;
		new_position: number;
		line: number;
		text_properties: Record<string, unknown>[];
	};
	viewport_changed: {
		split_id: number;
		buffer_id: number;
		top_byte: number;
		top_line: number | null;
		width: number;
		height: number;
	};
	// ── rendering ────────────────────────────────────────────────────────────
	render_start: {
		buffer_id: number;
	};
	render_line: {
		buffer_id: number;
		line_number: number;
		byte_start: number;
		byte_end: number;
		content: string;
	};
	lines_changed: {
		buffer_id: number;
		lines: {
			line_number: number;
			byte_start: number;
			byte_end: number;
			content: string;
			/** This line's role in an embedded-language region — a Markdown fenced
			* code block, a Vue `<script>`/`<style>` block — as the highlighting
			* engine classifies it while parsing. `"open"` and `"close"` are the
			* delimiter lines; `"body"` is content strictly inside.
			*
			* Absent for ordinary lines AND when the region state could not be
			* resolved (a >1MiB buffer whose viewport has no parse checkpoint before
			* it yet). Treat absence as *unknown*, never as "outside a region": the
			* point of this field is that a bare ``` opens or closes depending on
			* every fence above it, so there is nothing to fall back on. */
			region?: "open" | "body" | "close";
			/** Where this line sits in a table the buffer's grammar recognizes.
			* `role` is the line's kind (`"header"` is the column-name row,
			* `"delimiter"` the `|---|---|` row, `"row"` a data row); `first_row`
			* marks the data row directly below the delimiter; `last` marks the
			* table's final line.
			*
			* Companion to `region`, and recoverable where that is not: "is this a
			* table row" *is* derivable from a line's own text, so a consumer may
			* fall back to its own rule when this is absent. What it cannot derive
			* is where the table starts and ends — that needs the neighbouring
			* lines, and an edit-sized batch does not contain them.
			*
			* `last` is false rather than unknown when the engine could not see the
			* line below the table, so a consumer drawing a closing edge from it
			* draws none instead of one in the wrong place. */
			table?: {
				role: "header" | "delimiter" | "row";
				first_row: boolean;
				last: boolean;
			};
		}[];
		/** Buffer version these byte ranges were captured at. Pass back to
		* coordinate-mapping APIs to repair stale offsets from this batch. */
		epoch: number;
		/** Whether any split shows this buffer in compose/preview mode, read from
		* the live view states as this batch was built.
		*
		* Gate decoration work on this, not on
		* `getBufferInfo(buffer_id).is_composing_in_any_split`. The editor marks
		* these lines as seen the moment it sends the batch, so the batch is the
		* only offer they get, while `getBufferInfo` reads a state snapshot
		* refreshed on the editor thread's own schedule — early in a mode change
		* it still reports the mode the buffer just left. Gating on the snapshot
		* therefore drops the first decoration pass at random, leaving the
		* document undecorated until an edit or a scroll produces another
		* batch. */
		is_composing_in_any_split: boolean;
	};
	// ── commands ─────────────────────────────────────────────────────────────
	pre_command: {
		action: string | Record<string, unknown>;
	};
	post_command: {
		action: string | Record<string, unknown>;
	};
	/**
	* NOT EMITTED. Declared here historically, but nothing in the editor ever
	* fires it: `editor.on("idle", ...)` registers successfully and the handler
	* is never called. Listed so that its absence is documented rather than
	* discovered — do not build on it. For "run something later", drive it from
	* an event that does fire (`cursor_moved`, `buffer_changed`) or from your
	* own `spawnProcess` timer.
	*/
	idle: {
		milliseconds: number;
	};
	resize: {
		width: number;
		height: number;
	};
	// ── prompts ──────────────────────────────────────────────────────────────
	prompt_changed: {
		prompt_type: string;
		input: string;
	};
	prompt_confirmed: {
		prompt_type: string;
		input: string;
		selected_index: number | null;
	};
	prompt_cancelled: {
		prompt_type: string;
		input: string;
	};
	prompt_selection_changed: {
		prompt_type: string;
		selected_index: number;
	};
	// ── mouse ────────────────────────────────────────────────────────────────
	mouse_click: MouseClickHookArgs;
	mouse_move: {
		column: number;
		row: number;
		content_x: number;
		content_y: number;
	};
	mouse_scroll: {
		buffer_id: number;
		delta: number;
		col: number;
		row: number;
	};
	// ── LSP ──────────────────────────────────────────────────────────────────
	diagnostics_updated: {
		uri: string;
		count: number;
	};
	lsp_references: {
		symbol: string;
		locations: {
			file: string;
			line: number;
			column: number;
		}[];
	};
	lsp_implementation: {
		symbol: string;
		locations: {
			file: string;
			line: number;
			column: number;
		}[];
	};
	lsp_server_request: {
		language: string;
		method: string;
		server_command: string;
		params: string | null;
	};
	lsp_server_error: {
		language: string;
		server_command: string;
		error_type: string;
		message: string;
	};
	lsp_status_clicked: {
		language: string;
		has_error: boolean;
		missing_servers: string[];
		user_dismissed: boolean;
	};
	// ── UI events ────────────────────────────────────────────────────────────
	action_popup_result: {
		popup_id: string;
		action_id: string;
	};
	/**
	* User clicked a plugin-registered status-bar token. Subscribers
	* filter by `plugin_name` + `token_name`. Use this to re-open a
	* deferred prompt or surface the relevant settings UI for whatever
	* the token represents (e.g. trust chip → trust-elevation popup).
	*/
	status_bar_token_clicked: {
		plugin_name: string;
		token_name: string;
	};
	process_output: {
		process_id: number;
		data: string;
	};
	language_changed: {
		buffer_id: number;
		language: string;
	};
	theme_inspect_key: {
		theme_name: string;
		key: string;
	};
	keyboard_shortcuts: {
		bindings: {
			key: string;
			action: string;
		}[];
	};
	// ── PTY terminals (see crates/fresh-core/src/hooks.rs) ───────────────────
	// `window_id` is the editor window owning the terminal (== session id),
	// so a plugin can attribute output to a session: output from ANY terminal
	// in the window counts, and it fires on every PTY read (in-place redraws
	// and carriage-return progress bars register, not just newlines).
	terminal_output: {
		terminal_id: number;
		window_id: number;
		last_line: string;
	};
	terminal_exit: {
		terminal_id: number;
		window_id: number;
		exit_code: number | null;
	};
	// ── filesystem watching (watchPath plugin API) ────────────────────────────
	path_changed: {
		handle: number;
		path: string;
		/** "modify" | "create" | "delete" | "rename" | "other" */
		kind: string;
	};
	// ── editor sessions (Orchestrator; see orchestrator-sessions-design.md) ────────
	window_created: {
		id: number;
		label: string;
		root: string;
	};
	window_closed: {
		id: number;
	};
	active_window_changed: {
		previous_id: number | null;
		active_id: number;
	};
	// ── widget runtime ───────────────────────────────────────────────────────
	/**
	* A widget mounted via `editor.mountWidgetPanel` emitted a
	* semantic event. Fired when the host's hit-test routes a mouse
	* click to a `Toggle` / `Button` widget node within a mounted
	* widget panel. See `docs/internal/plugin-widget-library-design.md`.
	*
	* Panel ids are plugin-local: the host keys panels by
	* (plugin, id) and delivers each event only to the plugin that
	* owns the panel, so ids never need to be globally unique.
	* Routing is by `panel_id` (matches the id the plugin allocated
	* at mount time) plus `widget_key` (the stable `key` set on the
	* widget spec node, or empty when the spec did not assign one).
	*
	* `event_type` and `payload` shapes:
	*   * Toggle: `event_type = "toggle"`, `payload = { checked: <new> }`.
	*   * Button: `event_type = "activate"`, `payload = {}`.
	*/
	widget_event: {
		panel_id: number;
		widget_key: string;
		event_type: string;
		payload: Record<string, unknown>;
	};
}
/**
* Typed overloads of `editor.on` / `editor.off`.
*
* When the event name is a key of `HookEventMap` the handler receives a
* fully-typed payload — TypeScript will flag misspelled field accesses at
* compile time. Unknown event names fall through to the untyped base
* signatures in the EditorAPI interface.
*
* Both function-value and handler-name forms are supported:
*
* ```ts
* editor.on("buffer_activated", (args) => { /* args.buffer_id is number *\/ });
* editor.on("buffer_activated", "myHandler");   // registerHandler("myHandler", fn)
* ```
*/
interface EditorAPI {
	on<K extends keyof HookEventMap>(eventName: K, handler: (args: HookEventMap[K]) => boolean | void | Promise<boolean | void>): void;
	on<K extends keyof HookEventMap>(eventName: K, handlerName: string): void;
	off<K extends keyof HookEventMap>(eventName: K, handler: (args: HookEventMap[K]) => boolean | void | Promise<boolean | void>): void;
	off<K extends keyof HookEventMap>(eventName: K, handlerName: string): void;
	/**
	* Create a buffer group: multiple panels appearing as one tab.
	* This is an async runtime binding (not a direct #[qjs] method).
	*/
	createBufferGroup(name: string, mode: string, layout: unknown): Promise<BufferGroupResult>;
}
