/** * UI ports (gateways) — interfaces describing *what the UI needs*, independent * of transport (ARCHITECTURE §1.3). React components depend on these, never on * `@tauri-apps/api` directly. Implemented by the Tauri adapters and by mocks. * * Signatures are intentionally minimal/skeletal for L1; later lots flesh out * each gateway as their use cases land. They are aligned with the planned use * cases (ARCHITECTURE §6) so the shape is stable. */ import type { Agent, AgentDrift, AgentProfile, DomainEvent, EmbedderEngines, EmbedderProfile, FirstRunState, GitBranches, GitCommit, GitFileStatus, GraphCommit, HealthReport, LayoutKind, LayoutList, LayoutOperation, LayoutTree, Memory, MemoryIndexEntry, MemoryLink, MemoryType, EffectivePermissions, PermissionSet, PageDirection, Project, ProjectPermissions, ProjectWorkState, ProfileAvailability, ResumableAgent, ReplyChunk, Skill, SkillScope, Sprint, Template, TerminalSession, Ticket, TicketChat, TicketCarnet, TicketLinkKind, TicketList, TicketPriority, TicketStatus, TurnPage, Unsubscribe, } from "@/domain"; /** System-level gateway: health/ping + global domain-event subscription. */ export interface SystemGateway { /** Calls the backend `health` command (smoke test of the whole pipeline). */ health(note?: string): Promise; /** Subscribes to relayed domain events. */ onDomainEvent(handler: (event: DomainEvent) => void): Promise; /** * Opens a native folder picker and returns the chosen path, or `null` if the * user cancelled. This is the only sanctioned way to pick a folder — all call * sites go through this port; the Tauri plugin is only imported in the adapter. */ pickFolder(): Promise; } /** Input for {@link AgentGateway.createAgent}. */ export interface CreateAgentInput { name: string; profileId: string; initialContent?: string; } /** * Best-effort enriched details about a CLI conversation (T7), used to enrich the * resume popup. Both fields are optional: a missing inspector or a missing * transcript yields an empty object — the popup degrades to the status alone. */ export interface ConversationDetails { /** A short, best-effort label for the conversation (last topic). */ lastTopic?: string; /** A best-effort cumulative token count. */ tokenCount?: number; } /** Agents: create, list, read/update context, delete, launch (L6). */ export interface AgentGateway { /** Lists all agents belonging to the given project. */ listAgents(projectId: string): Promise; /** * Lists the agents that can be **resumed** when the project is (re)opened * (ARCHITECTURE §15.2): a read-only inventory of agent cells that were running * and/or carry a persisted conversation id at close time. Drives the * `ResumeProjectPanel` shown once on open; an empty list ⇒ no panel. Pure * inventory: no PTY is spawned by this call. */ listResumableAgents(projectId: string): Promise; /** * Lists the agents that currently own a live session and the cell hosting * each. Used to disable an agent already running in another cell (it cannot be * launched a second time — one live session per agent). */ listLiveAgents(projectId: string): Promise; /** * Rebinds an already-live agent session to another visible layout cell without * respawning the CLI process. Used when a background agent is opened in a cell, * or when a live session is moved from a now-detached/closed cell. * * Backends that do not yet support agent-session rebinding may omit this * method; the UI will show the session as running elsewhere but cannot attach * it from a different cell. */ attachLiveAgent?( projectId: string, agentId: string, nodeId: string, ): Promise; /** * Stops an already-live agent session without going through terminal teardown * commands from the Work panel. The backend owns the live-agent lifecycle. */ stopLiveAgent?(projectId: string, agentId: string): Promise; /** Creates a new agent from scratch; returns the created agent. */ createAgent(projectId: string, input: CreateAgentInput): Promise; /** * Hot-swaps an agent's runtime AI profile (Chantier A). The conversation * history is abandoned (the product decision is "start fresh"); the agent's * context `.md` and project memory are preserved. When the agent has a live * session it is relaunched on the new profile and returned as * {@link TerminalSession} so the hosting cell can rebind; otherwise * `relaunchedSession` is absent. */ changeAgentProfile( projectId: string, agentId: string, profileId: string, rows: number, cols: number, ): Promise<{ agent: Agent; relaunchedSession?: TerminalSession }>; /** Reads an agent's `.md` context by agent id. */ readContext(projectId: string, agentId: string): Promise; /** Overwrites an agent's `.md` context. */ updateContext(projectId: string, agentId: string, content: string): Promise; /** Removes an agent from the project. */ deleteAgent(projectId: string, agentId: string): Promise; /** * Launches the agent: opens a PTY, spawns the CLI, and wires the output stream. * Mirrors {@link TerminalGateway.openTerminal} but invokes `launch_agent`. * * The returned handle carries an optional {@link TerminalHandle.assignedConversationId}: * when the agent's profile assigns a fresh CLI conversation id on this first * launch (the cell had none yet), it is surfaced here so the caller can persist * it on the hosting leaf (`setCellConversation`) and resume on the next open. * Pass the leaf's current `conversationId` via {@link OpenTerminalOptions} to * resume an existing conversation instead. */ launchAgent( projectId: string, agentId: string, options: OpenTerminalOptions, onData: (bytes: Uint8Array) => void, ): Promise; /** * Re-attaches to an agent's already-running PTY (same backend mechanism as * {@link TerminalGateway.reattach}; agent sessions share the session-based * terminal commands). Used when an agent cell's view re-mounts after a * navigation/layout change, so the agent is never killed. */ reattach( sessionId: string, onData: (bytes: Uint8Array) => void, ): Promise; /** * Reads best-effort {@link ConversationDetails} for an agent's conversation * (T7), to enrich the resume popup with the last topic + a token indicator. * Best-effort by contract: a missing/unsupported inspector or a missing * transcript resolves to an empty object (never rejects for "no details"). */ inspectConversation( projectId: string, agentId: string, conversationId: string, ): Promise; } /** Options for opening a terminal. */ export interface OpenTerminalOptions { /** Working directory (typically the project root). */ cwd: string; /** Initial terminal height in rows. */ rows: number; /** Initial terminal width in columns. */ cols: number; /** * Persistent CLI conversation id recorded on the hosting cell, if any. When * present, an agent launch **resumes** that conversation; when absent the * launch may *assign* a fresh id (surfaced on the returned handle). Only * meaningful for {@link AgentGateway.launchAgent}; ignored for plain terminals. */ conversationId?: string; /** * The layout leaf (node) hosting this launch. Drives the "one live session per * agent" invariant backend-side: launching an agent already running in a * *different* node is refused (`AGENT_ALREADY_RUNNING`); the same node is * idempotent. Only meaningful for {@link AgentGateway.launchAgent}. */ nodeId?: string; } /** One currently-live agent and the cell hosting it (see {@link AgentGateway.listLiveAgents}). */ export interface LiveAgent { /** The live agent's id. */ agentId: string; /** The node (layout leaf) hosting the agent's live session. */ nodeId: string; /** * The live PTY session id, when the backend exposes it. Required for opening * a background/running agent in a new visible cell without respawning it. */ sessionId: string; /** Runtime kind backing the live agent. */ kind: "pty" | "structured"; } /** Result returned when the backend stops a live agent. */ export interface StoppedLiveAgent { agentId: string; sessionId: string; kind: "pty" | "structured"; } /** * A live terminal handle returned by {@link TerminalGateway.openTerminal}. * * The output stream is delivered to the `onData` callback passed at open time * (over a Tauri Channel in the real adapter); the handle exposes the input/ * control operations and a `close` that also tears the stream down. */ export interface TerminalHandle { /** Stable session id (UUID) used by the backend for this PTY. */ readonly sessionId: string; /** * Conversation id **assigned** by an agent launch when the profile minted a * fresh one (the cell had none yet). Present only on handles returned by * {@link AgentGateway.launchAgent}; `undefined` for plain terminals, resumes, * or profiles without a session block. The caller persists it on the hosting * leaf so the next open resumes instead of re-assigning. */ readonly assignedConversationId?: string; /** Sends bytes (xterm keystrokes) to the PTY. */ write(data: Uint8Array): Promise; /** Resizes the PTY. */ resize(rows: number, cols: number): Promise; /** * Detaches the **view** from the PTY without killing it: stops the local * output subscription so a torn-down view (navigation / layout change) stops * receiving bytes, while the backend PTY keeps running. The session can later * be re-attached via {@link TerminalGateway.reattach}. * * This is the lifecycle the view's cleanup must use — never {@link close}. */ detach(): void; /** * Kills the PTY and stops the output stream. Reserved for an **explicit** user * action (closing the terminal); navigation must never call this. */ close(): Promise; } /** * The write-portal contract an agent-cell terminal talks to (ARCHITECTURE §20). * The portal owns the *human line* counter, the suspension flag (raised while it * injects a delegation) and — once the view has a live PTY — the handle it * writes through. The terminal view is the only effective PTY writer * (single-writer invariant): it relays human keystrokes and the portal injects * via the same handle, never a second physical writer. */ export interface WritePortal { /** * Reports a raw human keystroke chunk (`term.onData`) so the portal can keep * its line counter. Called for **every** keystroke in agent mode, including * while suspended (the portal decides what to count). */ onHumanData(data: string): void; /** * Whether the keystroke relay to the PTY is currently suspended (the portal * is injecting a delegation). When `true`, the view drops keystrokes so they * do not race the injected text. */ isSuspended(): boolean; /** Binds the live PTY handle so the portal can inject through it. */ bindHandle(handle: TerminalHandle): void; /** Drops the handle reference (view torn down). */ unbindHandle(): void; } /** * Terminals (L3): open a PTY with a per-session output stream, then write/ * resize/close it through the returned {@link TerminalHandle}. */ export interface TerminalGateway { /** * Opens a terminal. `onData` receives every chunk of PTY output (bytes) as it * arrives. Resolves once the PTY is spawned and the stream is wired. */ openTerminal( options: OpenTerminalOptions, onData: (bytes: Uint8Array) => void, ): Promise; /** * Re-attaches to an **already-running** PTY identified by `sessionId` (after a * view was torn down by navigation/layout change). Returns the live handle and * the retained scrollback, which the caller repaints into xterm before the new * output stream (`onData`) starts delivering subsequent bytes. Does NOT * re-spawn the process. * * Rejects if the session is no longer alive (the caller then opens fresh). */ reattach( sessionId: string, onData: (bytes: Uint8Array) => void, ): Promise; /** * Kills a live PTY by its session id, independently of any view-held handle. * Used when a cell's agent changes: the old PTY must be torn down even though * the owning {@link TerminalHandle} is private to its (unmounting) view, whose * cleanup only ever {@link TerminalHandle.detach}es. Best-effort: resolves even * if the session is already gone. * * This is the only sanctioned way to kill a PTY outside * {@link TerminalHandle.close}. */ closeTerminal(sessionId: string): Promise; } /** The outcome of {@link TerminalGateway.reattach}. */ export interface ReattachResult { /** The live terminal handle for the re-attached session. */ handle: TerminalHandle; /** The retained scrollback bytes to repaint before the live stream resumes. */ scrollback: Uint8Array; } /** Projects: create/open/close/list (L2). */ export interface ProjectGateway { /** Lists the projects known to the registry. */ listProjects(): Promise; /** Creates a project from a root; returns the created project. */ createProject(name: string, root: string): Promise; /** Opens a project by id; returns the opened project. */ openProject(projectId: string): Promise; /** Closes a project by id. */ closeProject(projectId: string): Promise; /** Reads the shared project context stored at `.ideai/CONTEXT.md`. */ readProjectContext(projectId: string): Promise; /** Overwrites the shared project context stored at `.ideai/CONTEXT.md`. */ updateProjectContext(projectId: string, content: string): Promise; } /** Layout: load the terminal grid tree and apply mutating operations (L4). */ export interface LayoutGateway { /** Loads a project's layout tree (defaults to a single cell if none persisted). * When `layoutId` is omitted, the active layout for the project is used. */ loadLayout(projectId: string, layoutId?: string): Promise; /** * Applies a split/merge/resize/move/setSession/setCellAgent operation; the backend persists * `.ideai/layout.json` and returns the resulting tree. * When `layoutId` is omitted, the active layout for the project is used. */ mutateLayout( projectId: string, operation: LayoutOperation, layoutId?: string, ): Promise; /** Lists all named layouts for a project, with the current active id. */ listLayouts(projectId: string): Promise; /** Creates a new named layout for a project; returns the new layout id. */ createLayout(projectId: string, name: string, kind?: LayoutKind): Promise<{ layoutId: string }>; /** Renames a layout. */ renameLayout(projectId: string, layoutId: string, name: string): Promise; /** Deletes a layout; returns the new active layout id. */ deleteLayout(projectId: string, layoutId: string): Promise<{ activeId: string }>; /** * Sets the active layout for a project. Returns the layout id the backend * *actually* activated — authoritative (invariant I4): it equals the requested * id when valid, else the unchanged current active id (self-healing fallback * when the requested id was stale, e.g. after an external overwrite of * `layouts.json`). Callers must adopt this id rather than the one they asked for. */ setActiveLayout(projectId: string, layoutId: string): Promise<{ activeId: string }>; } /** Git: status/commit/checkout/… (L8). */ export interface GitGateway { status(projectId: string): Promise; stage(projectId: string, path: string): Promise; unstage(projectId: string, path: string): Promise; commit(projectId: string, message: string): Promise; branches(projectId: string): Promise; checkout(projectId: string, branch: string): Promise; log(projectId: string, limit: number): Promise; init(projectId: string): Promise; /** Returns the full commit DAG for the git-graph layout. */ graph(projectId: string, limit: number): Promise; } /** Remote (SSH/WSL) connection management (L9). */ export interface RemoteGateway { connect(projectId: string): Promise; } /** Input for {@link TemplateGateway.createTemplate}. */ export interface CreateTemplateInput { name: string; content: string; defaultProfileId: string; } /** * Templates (L7): CRUD for agent templates, creation of agents from templates, * drift detection and synchronisation. */ export interface TemplateGateway { /** Lists all templates. */ listTemplates(): Promise; /** Creates a new template; returns the created template. */ createTemplate(input: CreateTemplateInput): Promise