/** * Pure UI-domain types — TS mirrors of the backend DTOs (camelCase wire format, * ARCHITECTURE §1.3). No React, no Tauri here: these are plain data shapes the * ports speak in, so view logic and gateways stay testable in isolation. */ /** Health report returned by the `health` gateway/command. */ export interface HealthReport { version: string; alive: boolean; timeMillis: number; correlationId: string; note: string | null; } /** * Lifecycle status of a local model server during an agent launch (F35, mirror * of the backend `ModelServerStatusDto`, tagged on `state`, camelCase wire). * * Contract note — the backend event carries the `serverId` on the *envelope* * ({@link DomainEvent} `modelServerStatusChanged`), not inside the status, and it * does NOT emit `baseURL`/`model` in the status payload: the UI reads what the * backend actually sends. `reused` (on `ready`) tells apart a freshly-started * server from a reused running one. */ export type ModelServerStatus = | { state: "notConfigured" } | { state: "probing" } | { state: "starting" } | { /** * The server is downloading/preparing a remote model. Progress fields are * optional (all `null` in the F1 MVP; the bar/% display is the F2 stretch): * the UI only needs `downloading` to raise the cell overlay. */ state: "downloading"; downloadedBytes: number | null; totalBytes: number | null; percent: number | null; source: string | null; } | { state: "ready"; reused: boolean } | { state: "failed"; code: string; message: string }; /** * Stop policy of a managed local model server (mirror of the backend * `StopPolicyDto`, camelCase wire): * - `keepAlive`: leave the process running, * - `stopOnAppExit`: stop when IdeA exits (the effective default), * - `stopWhenUnused`: stop when no profile references it. */ export type StopPolicy = "keepAlive" | "stopOnAppExit" | "stopWhenUnused"; /** * Where the served `.gguf` model comes from (F35 V2, mirror of the backend * `ModelSourceDto`, camelCase wire, tagged on `type`): * - `localPath`: an absolute `.gguf` path already present on disk (`--model`), * - `huggingFace`: a `namespace/repo[:quant]` reference llama.cpp downloads and * caches automatically (`-hf`). */ export type ModelSource = | { type: "localPath"; path: string } | { type: "huggingFace"; repo: string }; /** * A declared local model server (F35, mirror of the backend flat * `LocalModelServerConfigDto`, camelCase wire). Global to IdeA (not project * scoped). Referenced by an OpenCode profile through * `OpenCodeConfig.localModelServerId` (= this `id`). * * Contract notes: * - `id` is a UUID **minted client-side** on create (the backend requires a valid * UUID; it does not generate the server id, only an internal model id it hides). * - `servedModelName` is what the user configures and what is sent to OpenCode as * the `model` — the backend's internal `model.id`/`model.label` are not exposed. * - `modelSource` (V2) replaces the former `modelPath`; it is required by the * backend when `autoStart` is `true`. */ export interface LocalModelServerConfig { id: string; /** Only `llamaCpp` is supported today (camelCase, not "llamacpp"). */ kind: "llamaCpp"; name: string; /** OpenAI-compatible base URL served by `llama-server` (normalised to `/v1`). */ baseURL: string; port: number; /** Explicit model source (`.gguf` path or Hugging Face ref). Required for auto-start. */ modelSource?: ModelSource; /** Model name exposed by the server and sent to OpenCode as `model`. */ servedModelName: string; /** Explicit `llama-server` executable path/command (optional). */ binaryPath?: string; /** llama.cpp `--host` (default `127.0.0.1`). */ host: string; /** llama.cpp `-ngl` GPU layers (optional; `0` = CPU only). */ gpuLayers?: number; /** llama.cpp `-c` context size (optional). */ contextSize?: number; /** Whether to pass `--jinja` (Jinja chat template). */ jinja: boolean; args: string[]; autoStart: boolean; stopPolicy: StopPolicy; } /** * A previewed `llama-server` command line (F35 V2, mirror of the backend * `PreviewModelServerCommandDto`). Built by the backend from a draft config — * never reconstructed client-side. */ export interface ModelServerCommandPreview { /** Resolved executable (e.g. `llama-server` or an absolute path). */ command: string; /** Argument vector, unescaped. */ args: string[]; /** Human-readable, shell-escaped command line. */ display: string; } /** * How the embedded web server is exposed (ticket #68, mirror of the backend * `ServerExposureMode`). * * - `localOnly` — binds loopback, no remote access. * - `remoteProxyLocal` — binds loopback; an HTTPS reverse proxy runs on *this* * machine and reaches IdeA over loopback. * - `remoteProxyOtherMachine` — binds a concrete LAN address so a proxy on * *another* host can reach it; that proxy must be declared in * `trustedProxies`. */ export type ServerExposureMode = | "localOnly" | "remoteProxyLocal" | "remoteProxyOtherMachine"; /** * Persisted embedded-server exposure settings (mirror of the backend * `ServerExposureSettingsDto`). * * The backend validates every field on save/start; the UI never derives network * facts (LAN addresses, upstream URL) from these — it asks `previewExposure`. */ export interface ServerExposureSettings { mode: ServerExposureMode; /** TCP port to bind. `0` asks the OS for an ephemeral port. */ port: number; /** Public HTTPS origin, required by both remote modes. */ publicOrigin?: string; /** * Peers allowed to contact IdeA, as IPs or CIDRs. **Not** a listen address. * Required (non-empty) by `remoteProxyOtherMachine`. */ trustedProxies: string[]; /** * Concrete LAN address to bind, required by `remoteProxyOtherMachine`. Must * come from {@link ServerExposurePreview.candidateLanAddresses} — the UI is * never allowed to invent one. */ lanBindAddress?: string; } /** A non-fatal diagnostic from the exposure preview (never blocks a start). */ export interface DiagnosticWarning { /** Stable warning code (e.g. `missingTrustedProxy`). */ code: string; /** Human-readable, actionable message. */ message: string; } /** * Backend-derived preview of a draft exposure config (mirror of * `ServerExposurePreviewDto`). The backend is the sole authority on LAN * addresses and the proxy upstream URL. */ export interface ServerExposurePreview { /** LAN addresses the backend discovered on this host. */ candidateLanAddresses: string[]; /** URL the reverse proxy should target; absent in `localOnly`. */ upstreamUrl?: string; /** Non-fatal diagnostics to surface alongside the form. */ warnings: DiagnosticWarning[]; } /** Embedded-server lifecycle state (mirror of `EmbeddedServerStatusStateDto`). */ export type EmbeddedServerState = | "stopped" | "starting" | "running" | "stopping" | "failed"; /** * Embedded-server status (mirror of `EmbeddedServerStatusDto`). `pairingCode` * is a runtime-only secret: it exists while the server runs, is never * persisted, and must never be written into a settings field. */ export interface EmbeddedServerStatus { state: EmbeddedServerState; /** Local URL, when running. */ localUrl?: string; /** Public URL, when a remote mode is configured. */ publicUrl?: string; /** Upstream URL to hand to the reverse proxy. */ upstreamUrl?: string; /** Runtime pairing code — present only while running. */ pairingCode?: string; /** Last failure, when `state` is `failed`. */ error?: GatewayError; } /** A domain event relayed from the backend (tagged union on `type`). */ export type DomainEvent = | { type: "projectCreated"; projectId: string } | { type: "agentLaunched"; agentId: string; sessionId: string } | { type: "agentExited"; agentId: string; code: number } | { /** * A local model server changed lifecycle state while (re)launching an * agent whose OpenCode profile binds `localModelServerId` (F35). Keyed by * `serverId`; the frontend correlates it back to the agent through the * profile's `opencode.localModelServerId`. */ type: "modelServerStatusChanged"; serverId: string; status: ModelServerStatus; } | { /** * An agent launch failed before a runtime session was created (F35). Unlike * a generic "agent failed", this carries an actionable `message` and a * stable `code`; `cause` namespaces the failure (e.g. `"model_server"`). */ type: "agentLaunchFailed"; agentId: string; cause: string; code: string; message: string; } | { type: "agentProfileChanged"; agentId: string; profileId: string } | { type: "agentBusyChanged"; agentId: string; busy: boolean } | { type: "backgroundTaskChanged"; projectId: string; taskId: string; agentId: string; state: string; } | { type: "agentInboxChanged"; agentId: string; depth: number; action: string; } | { type: "agentWakeChanged"; projectId: string; agentId: string; action: string; reason?: string; } | { /** * A delegation is ready to be injected into the agent's native terminal * (ARCHITECTURE §20). The backend is the queue/busy authority but no longer * PTY-writes the turn: the frontend write-portal runs the handshake (b→e) * and writes `text` + `submitSequence`. `submitSequence`/`submitDelayMs` * come from the target's profile; absent ⇒ the portal applies its defaults * (`"\r"`, ~350 ms). */ type: "delegationReady"; agentId: string; ticket: string; text: string; submitSequence?: string; submitDelayMs?: number; } | { /** * An agent entered a session/rate limit (ARCHITECTURE §21). Low-frequency, * model-agnostic. `resetsAtMs` is the reset instant in epoch-milliseconds; * absent ⇒ unknown (no reliable time → no auto-resume). The frontend badges * "limité jusqu'à HH:MM" when known, "limité" otherwise. */ type: "agentRateLimited"; agentId: string; resetsAtMs?: number; } | { /** * An auto-resume was armed for a rate-limited agent (ARCHITECTURE §21). * `fireAtMs` is the wake-up deadline in epoch-milliseconds. The frontend * shows the countdown + the "Annuler la reprise" button (cancellable window). */ type: "agentResumeScheduled"; agentId: string; fireAtMs: number; } | { /** * An agent's auto-resume was cancelled (ARCHITECTURE §21): the user clicked * "Annuler la reprise". The frontend removes the countdown but keeps the * "limité" state (no auto-resume will fire). */ type: "agentResumeCancelled"; agentId: string; } | { /** * An agent was effectively resumed after a limit (ARCHITECTURE §21): the * wake-up fired (or an immediate resume). The frontend clears all limit state. */ type: "agentResumed"; agentId: string; } | { /** * Human net (level 3): a session limit is suspected without any reliable * reset time (ARCHITECTURE §21.1). IdeA never resumes blind: this asks the * frontend to surface an "heure inconnue" state and prompt the user. * `resetsAtMs` carries an estimate when one exists, else absent. */ type: "agentRateLimitSuspected"; agentId: string; resetsAtMs?: number; } | { type: "templateUpdated"; templateId: string; version: number } | { type: "agentDriftDetected"; agentId: string; from: number; to: number } | { type: "agentSynced"; agentId: string; to: number } | { type: "layoutChanged"; projectId: string } | { type: "remoteConnected"; projectId: string } | { type: "gitStateChanged"; projectId: string } | { type: "memorySaved"; slug: string } | { type: "orchestratorRequestProcessed"; requesterId: string; action: string; ok: boolean; /** * Which entry door the delegation arrived through: `"mcp"` (MCP server) or * `"file"` (`.ideai/requests` watcher). Optional for backward-compatibility: * an event relayed without it simply yields no source badge. */ source?: "mcp" | "file"; } | { type: "issueCreated"; issueId: string; issueRef: string } | { type: "issueUpdated"; issueRef: string; version: number } | { /** * A ticket was deleted (ticket #6). Mirror of the backend * `DomainEventDto::IssueDeleted`. `issueRef` is the deleted `#N`; * `freedSprint` is the sprint id it was released from, when any. Starts with * `issue`, so {@link isTicketEvent} matches it and ticket lists refresh * automatically; an open detail on the same `issueRef` closes. */ type: "issueDeleted"; projectId: string; issueRef: string; freedSprint: string | null; } | { type: "issueStatusChanged"; issueRef: string; status: TicketStatus; version: number; } | { type: "issuePriorityChanged"; issueRef: string; priority: TicketPriority; version: number; } | { type: "issueCarnetUpdated"; issueRef: string; version: number } | { type: "issueLinked"; issueRef: string; target: string; kind: TicketLinkKind; version: number; } | { type: "issueUnlinked"; issueRef: string; target: string; kind: TicketLinkKind; version: number; } | { type: "issueAgentAssigned"; issueRef: string; agentId: string; version: number; } | { type: "issueAgentUnassigned"; issueRef: string; agentId: string; version: number; } | { /** * A ticket changed sprint membership (ticket #10). Mirror of the backend * `DomainEventDto::IssueSprintChanged`. `from`/`to` are sprint ids (`null` * ⇒ no sprint). Starts with `issue`, so {@link isTicketEvent} matches it * and ticket lists refresh automatically. */ type: "issueSprintChanged"; issueRef: string; from: string | null; to: string | null; version: number; } | { type: "sprintCreated"; sprintId: string; order: number } | { type: "sprintRenamed"; sprintId: string; name: string; version: number } | { type: "sprintReordered"; sprintId: string; order: number; version: number; } | { type: "sprintDeleted"; sprintId: string } | { /** * A ticket AI assistant chat was opened (ticket #8). Mirror of the backend * `DomainEventDto::TicketAssistantOpened`. `issueRef` is the bound ticket, * `profileId` the runtime AI profile driving the assistant. */ type: "ticketAssistantOpened"; issueRef: string; profileId: string; } | { /** A ticket AI assistant chat was closed/disposed (ticket #8). */ type: "ticketAssistantClosed"; issueRef: string; } | { /** * An intermediate assistant announcement emitted **during** an inter-agent * structured turn (ticket #4, "annonces inter-agent"). Mirror of the backend * `DomainEventDto::AgentAnnouncement`. Ephemeral (never persisted): the front * folds these into a live, bounded, per-`(target, ticketId)` index that feeds * the target-cell overlay (F3) and the requester-cell preview (F2). * * `requester` is `"user"` or the requesting agent's id; `target` is the agent * being contacted; `text` is the human-readable réflexion/annonce. */ type: "agentAnnouncement"; projectId: string; requester: string; target: string; ticketId: string; text: string; atMs: number; } | { type: "ptyOutput"; sessionId: string; bytes: number[] }; /** * Whether a domain event is one of the ticket (`Issue*`) events. A small helper * so ticket view-models refresh on any ticket mutation without re-enumerating * every variant at each call site. */ export function isTicketEvent( event: DomainEvent, ): event is Extract { return event.type.startsWith("issue"); } /** * Whether a domain event is a sprint lifecycle event (`sprint*`). Lets the * ticket view-model re-fetch the sprint list when sprints are created, renamed, * reordered or deleted (ticket #10). */ export function isSprintEvent( event: DomainEvent, ): event is Extract { return event.type.startsWith("sprint"); } /** Where a project physically lives (mirror of the backend `RemoteRef`). */ export type RemoteRef = | { kind: "local" } | { kind: "ssh"; host: string; port: number; user: string; auth: unknown; remoteRoot: string; } | { kind: "wsl"; distro: string }; /** A project as returned by the `project` gateway (mirror of `ProjectDto`). */ export interface Project { id: string; name: string; root: string; remote: RemoteRef; createdAt: number; } /** Stable error shape mirrored from the backend `ErrorDto`. */ export interface GatewayError { code: string; message: string; } // --------------------------------------------------------------------------- // Work state (UX conversations/delegations live read-model) // --------------------------------------------------------------------------- /** Live session currently associated with an agent in the work-state read-model. */ export interface LiveWorkSession { nodeId: string; sessionId: string; kind: "pty" | "structured"; } /** Busy/idle status for an agent in the work-state read-model. */ export type WorkBusyState = | { state: "idle" } | { state: "busy"; ticket: string; sinceMs: number }; /** FIFO status for an input/ticket currently tracked by the backend queue. */ export type TicketWorkStatus = "inProgress" | "queued"; /** Who produced an input/ticket in the work-state read-model. */ export type TicketWorkSource = | { kind: "human" } | { kind: "agent"; agentId: string }; /** One queued or in-progress input/ticket for an agent. */ export interface AgentTicketState { ticketId: string; conversationId: string; position: number; status: TicketWorkStatus; source: TicketWorkSource; requesterLabel: string; taskPreview: string; taskLen: number; } /** One first-class background task visible in the work-state read-model. */ export interface BackgroundCompletion { taskId: string; ownerAgentId: string; projectId: string; kind: string; status: "running" | "completed" | "failed" | "cancelled" | "pending" | "delivered"; exitCode: number | null; /** Human-readable summary / error / reason of a terminal result (ticket #5). */ summary: string | null; stdoutTail: string | null; stderrTail: string | null; /** Last-update timestamp (epoch ms); chronological ordering key. */ updatedAtMs: number; } /** One item currently pending in an agent's inbox. */ export interface InboxItem { id: string; agentId: string; source: string; kind: string; body: string; createdAtMs: number; correlationId?: string; priority: number; } /** Availability of a compact conversation summary in the work-state read-model. */ export type ConversationPreviewStatus = | "ready" | "missing" | "partial" | "unavailable"; /** Recent turn preview attached to a conversation summary. */ export interface ConversationTurnWorkPreview { role: "prompt" | "response" | "toolActivity"; source: TicketWorkSource; atMs: number; textPreview: string; textLen: number; } /** Compact thread context joined to tickets by conversation id. */ export interface ConversationWorkSummary { conversationId: string; status: ConversationPreviewStatus; objectivePreview: string | null; summaryPreview: string | null; summaryLen: number; upTo: string | null; recentTurns: ConversationTurnWorkPreview[]; } /** One agent row in the project work-state read-model. */ export interface AgentWorkState { agentId: string; name: string; profileId: string; live?: LiveWorkSession; busy: WorkBusyState; tickets: AgentTicketState[]; inboxDepth?: number; inbox?: InboxItem[]; backgroundTasks?: BackgroundCompletion[]; } /** Minimal read-only live-state surface for a project. */ export interface ProjectWorkState { agents: AgentWorkState[]; conversations: ConversationWorkSummary[]; } // --------------------------------------------------------------------------- // Conversation transcript (LS7 — human thread-per-pair viewer, mirror of LS6 DTO) // --------------------------------------------------------------------------- /** Nature of a transcript turn (mirror of the backend `TurnRole`). */ export type TurnRole = "prompt" | "response" | "toolActivity"; /** Origin of a transcript turn (mirror of the backend `TurnSource`). */ export type TurnSource = { kind: "human" } | { kind: "agent"; agentId: string }; /** One turn of the human transcript — full text, never truncated. */ export interface TurnView { id: string; atMs: number; role: TurnRole; source: TurnSource; text: string; textLen: number; } /** Pagination travel direction (`"backward"` = towards older turns). */ export type PageDirection = "forward" | "backward"; /** A page of the human transcript, oldest-to-newest. */ export interface TurnPage { turns: TurnView[]; hasMore: boolean; nextAnchor?: string; } // --------------------------------------------------------------------------- // Permissions (LP1) // --------------------------------------------------------------------------- /** Agent capability governed by IdeA permissions. */ export type Capability = "read" | "write" | "delete" | "executeBash"; /** Permission rule verdict. */ export type PermissionEffect = "allow" | "deny"; /** Fallback stance when no permission rule matches. */ export type PermissionPosture = "ask" | "allow" | "deny"; /** Glob path scope, relative to the project root. */ export type PathScope = string[]; /** Shell command matcher. */ export type CommandMatcher = | { kind: "exact"; value: string } | { kind: "prefix"; value: string } | { kind: "glob"; value: string }; /** Per-command bash verdict. */ export interface CommandRule { matcher: CommandMatcher; effect: PermissionEffect; } /** One permission rule. */ export interface PermissionRule { capability: Capability; effect: PermissionEffect; paths?: PathScope; commands?: CommandRule[]; } /** Permission policy bundle. */ export interface PermissionSet { rules: PermissionRule[]; fallback: PermissionPosture; } /** One sparse agent override in `.ideai/permissions.json`. */ export interface AgentPermissionOverride { agentId: string; permissions: PermissionSet; } /** Full project permission document. */ export interface ProjectPermissions { version: number; projectDefaults?: PermissionSet; agents?: AgentPermissionOverride[]; } /** Effective project+agent policy. */ export interface EffectivePermissions { rules: PermissionRule[]; fallback: PermissionPosture; } // --------------------------------------------------------------------------- // Layout (L4) — mirror of the domain `LayoutTree` (ARCHITECTURE §3, §7). // --------------------------------------------------------------------------- /** Split direction: `row` = columns (left→right), `column` = rows (top→bottom). */ export type Direction = "row" | "column"; /** A terminal-hosting leaf cell. `session` is the hosted SessionId, if any. * `agent` is the agent id if an agent is pinned to this cell (absent = plain terminal). * `conversationId` is the persistent CLI conversation id (survives PTY close/reopen, * lets the agent resume); omitted when absent (mirrors the backend `skip_serializing_if`). * `agentWasRunning` records whether the cell's agent process was running at close time; * omitted when `false`. */ export interface LeafCell { id: string; session?: string | null; agent?: string; conversationId?: string; agentWasRunning?: boolean; } /** A weighted child of a split. `weight` is a relative (`> 0`) share. */ export interface WeightedChild { node: LayoutNode; weight: number; } /** A weighted n-ary split (rows or columns). */ export interface SplitContainer { id: string; direction: Direction; children: WeightedChild[]; } /** A spreadsheet grid cell placement with spans. */ export interface GridCell { node: LayoutNode; row: number; col: number; rowSpan: number; colSpan: number; } /** A spreadsheet-style grid with per-row/col weights and span-based merging. */ export interface GridContainer { id: string; colWeights: number[]; rowWeights: number[]; cells: GridCell[]; } /** * A node in the layout tree. Tagged on `type` with the payload under `node`, * matching the backend `#[serde(tag = "type", content = "node")]`. */ export type LayoutNode = | { type: "leaf"; node: LeafCell } | { type: "split"; node: SplitContainer } | { type: "grid"; node: GridContainer }; /** The root of a tab's terminal layout. */ export interface LayoutTree { root: LayoutNode; } /** * A layout mutation (mirror of the backend `LayoutOperationDto`, tagged on * `type`). Node/session ids are UUID strings. */ export type LayoutOperation = | { type: "split"; target: string; direction: Direction; newLeaf: string; container: string; } | { type: "merge"; container: string; keepIndex: number } | { type: "resize"; container: string; weights: number[] } | { type: "move"; from: string; to: string } | { type: "setSession"; target: string; session?: string | null } | { type: "setCellAgent"; target: string; agent: string | null } | { type: "setCellConversation"; target: string; conversationId: string | null } | { type: "setAgentRunning"; target: string; running: boolean }; /** The kind of a named layout. */ export type LayoutKind = "terminal" | "gitGraph"; /** Named layout entry returned by `listLayouts`. */ export interface LayoutInfo { id: string; name: string; kind: LayoutKind; } /** * A commit node in the full git graph (DAG). `refs` carries short branch/tag * names (e.g. `"main"`, `"tag: v1.0"`). `timestamp` is seconds since Unix epoch. */ export interface GraphCommit { hash: string; summary: string; parents: string[]; refs: string[]; author: string; timestamp: number; } /** Response of `listLayouts`. */ export interface LayoutList { layouts: LayoutInfo[]; activeId: string; } /** Unsubscribe handle returned by event subscriptions. */ export type Unsubscribe = () => void; // --------------------------------------------------------------------------- // AI profiles & first-run (L5) — mirror of the domain `AgentProfile` / // `ContextInjection` (CONTEXT §9, ARCHITECTURE §3). // --------------------------------------------------------------------------- /** * Context-injection strategy (tagged on `strategy`, mirror of the backend * `ContextInjection`): * - `conventionFile`: write the `.md` to a conventional file (e.g. `CLAUDE.md`), * - `flag`: pass the context path through a CLI flag, * - `stdin`: pipe the content on stdin, * - `env`: pass the context via an environment variable. */ export type ContextInjection = | { strategy: "conventionFile"; target: string } | { strategy: "flag"; flag: string } | { strategy: "stdin" } | { strategy: "env"; var: string }; /** The four injection strategy discriminants. */ export type InjectionStrategy = ContextInjection["strategy"]; /** * Structured-execution adapter of a profile (mirror of the backend * `StructuredAdapter`, camelCase wire format). Absent (`structuredAdapter` * omitted) ⇒ the profile is a plain TUI/PTY agent (historical behaviour); * present ⇒ the profile is selectable as a structured AI agent: * - `claude` / `codex` / `openCode`: driven by a spawned CLI binary, * - `openAiCompatible`: driven by IdeA's native HTTP adapter against an * OpenAI-compatible chat server (Ollama, llama.cpp, a LAN runtime) — see * {@link HttpChatConfig}. */ export type StructuredAdapter = "claude" | "codex" | "openCode" | "openAiCompatible"; /** * HTTP configuration of an OpenAI-compatible chat server (mirror of the backend * `HttpChatConfig`, camelCase wire format). Carried by a profile whose * {@link StructuredAdapter} is `openAiCompatible`. * * Transparent by design: secrets are never stored here — `apiKeyEnv` is the * *name* of an environment variable, never the key itself (the backend resolves * it at call time). */ export interface HttpChatConfig { /** Root or `/chat/completions` endpoint of the HTTP server (http/https). */ endpoint: string; /** Model name sent in the `/chat/completions` payload. */ model: string; /** Name of the env var holding the API key (optional). Never the key. */ apiKeyEnv?: string; /** Per-turn request timeout, in milliseconds. */ requestTimeoutMs?: number; /** Connection timeout, in milliseconds. */ connectTimeoutMs?: number; /** Tool-calling re-loop guard for one turn (defaults to ~16 backend-side). */ maxToolIterations?: number; } /** * Configuration for an OpenCode process-backed profile pointed at its own * llama.cpp (OpenAI-compatible) endpoint. Mirror of the backend `OpenCodeConfig` * (camelCase wire format: `baseURL`, `apiKey?`, `model`, `reasoning?`, * `attachment?`). OpenCode is a locally-piloted CLI, distinct from the in-process * {@link HttpChatConfig} adapter. */ export interface OpenCodeConfig { /** * OpenAI-compatible base URL served by `llama-server` (http/https), for * example `http://localhost:8080/v1`. */ baseURL: string; /** * Optional API key forwarded to the provider — the key *itself*, not an env * var name (a local llama.cpp typically needs none / a placeholder). Omitted * when blank (mirrors the backend `skip_serializing_if`). */ apiKey?: string; /** Model name served by `llama-server`, for example `qwen3-coder-30b`. */ model: string; /** Enables model reasoning. Effective default: `true`. */ reasoning?: boolean; /** Enables attachments. Effective default: `false`. */ attachment?: boolean; /** * Optional id of a managed local model server (F35) this profile binds to. * When set, IdeA can start/stop the referenced `llama-server` for the profile; * omitted when the profile points at an externally-managed endpoint (mirrors * the backend `skip_serializing_if`). Opaque string id on the wire. */ localModelServerId?: string; } /** * A declarative AI-CLI profile (mirror of the backend `AgentProfile`). `id` is a * UUID string; `detect` is the optional detection command line. */ export interface AgentProfile { id: string; name: string; command: string; args: string[]; contextInjection: ContextInjection; detect: string | null; cwdTemplate: string; /** Optional CLI flags for agent-session continuity: `assignFlag` to bind a * conversation id at launch, `resumeFlag` to resume an existing conversation. */ session?: { assignFlag?: string; resumeFlag: string }; /** * Structured-execution adapter (mirror of the backend `structured_adapter`, * `skip_serializing_if = Option::is_none`). Absent ⇒ plain TUI/PTY profile; * present ⇒ selectable structured AI agent. Additive: existing Claude/Codex * profiles are unaffected. */ structuredAdapter?: StructuredAdapter; /** * HTTP config for a `openAiCompatible` structured adapter (mirror of the * backend `chat_http`, `skip_serializing_if = Option::is_none`). Absent for * every historical profile and every non-HTTP adapter. */ chatHttp?: HttpChatConfig; /** OpenCode process-backed config. Present for `structuredAdapter: "openCode"`. */ opencode?: OpenCodeConfig; } /** Availability of a candidate profile after detection (mirror of the DTO). */ export interface ProfileAvailability { profile: AgentProfile; available: boolean; } /** First-run state (mirror of `FirstRunStateDto`). */ export interface FirstRunState { isFirstRun: boolean; referenceProfiles: AgentProfile[]; } // --------------------------------------------------------------------------- // Agents (L6) — mirror of the domain `Agent` / `AgentOrigin` (ARCHITECTURE §6). // --------------------------------------------------------------------------- /** * Origin of an agent (tagged on `type`, mirror of the backend `AgentOrigin`): * - `scratch`: created from scratch, no template link, * - `fromTemplate`: derived from a template, tracking the last synced version. */ export type AgentOrigin = | { type: "scratch" } | { type: "fromTemplate"; templateId: string; syncedTemplateVersion: number }; /** * A project-scoped agent (mirror of the backend `Agent` DTO, camelCase wire * format). `contextPath` is the relative path of the agent's `.md` within * `.ideai/` (e.g. `agents/foo.md`). */ export interface Agent { id: string; name: string; contextPath: string; profileId: string; origin: AgentOrigin; synchronized: boolean; /** Skills assigned to this agent (injected into its convention file). */ skills: SkillRef[]; } /** * A terminal/PTY session as returned by the backend (mirror of * `TerminalSessionDto`, camelCase wire format). Surfaced by `changeAgentProfile` * as the freshly relaunched session when a live agent was hot-swapped. */ export interface TerminalSession { /** Stable session id (UUID) — used for write/resize/close + the output channel. */ sessionId: string; /** Working directory the shell runs in. */ cwd: string; /** Current rows. */ rows: number; /** Current cols. */ cols: number; /** * Conversation id assigned by this (re)launch, when the profile supports * session assignment and the hosting cell had none yet; absent otherwise. */ assignedConversationId?: string; } /** * An agent that can be resumed when its project is (re)opened (mirror of the * backend `ResumableAgentDto`, camelCase wire format; ARCHITECTURE §15.2). It is * a read-only inventory entry computed at open time — never a spawn: * - `nodeId` is the layout leaf hosting the agent (where to relaunch), * - `conversationId` is the persisted CLI conversation id; absent ⇒ relaunch fresh, * - `wasRunning` is `agentWasRunning` frozen at close time (drives the status label), * - `resumeSupported` reflects whether the agent's profile exposes a usable * session strategy; when `false`, "Reprendre" relaunches fresh ("relance à neuf"). */ export interface ResumableAgent { agentId: string; name: string; nodeId: string; conversationId?: string; wasRunning: boolean; resumeSupported: boolean; } // --------------------------------------------------------------------------- // Skills (L12) — mirror of the domain `Skill` / `SkillRef`. // --------------------------------------------------------------------------- /** * Where a skill lives (selects its backing store): `global` skills are reusable * across projects; `project` skills are specific to one project's `.ideai/`. */ export type SkillScope = "global" | "project"; /** * A reusable, model-agnostic workflow assignable to agents (mirror of the * backend `Skill` DTO, camelCase wire format). */ export interface Skill { id: string; name: string; contentMd: string; scope: SkillScope; } /** A reference from an agent to one assigned skill (mirror of `SkillRef`). */ export interface SkillRef { skillId: string; scope: SkillScope; } // --------------------------------------------------------------------------- // Memory (L14) — mirror of the backend `MemoryDto` / `MemoryIndexEntryDto`. // --------------------------------------------------------------------------- /** * The category of a memory note (selects how it is recalled/injected). Mirrors * the backend `type` field; identity of a note is its **slug** (kebab-case). */ export type MemoryType = "user" | "feedback" | "project" | "reference"; /** * A memory note (mirror of the backend `Memory` DTO, camelCase wire format). * `name` doubles as the human title and the source of the slug identity. */ export interface Memory { name: string; description: string; type: MemoryType; content: string; } /** * One entry of the memory index (mirror of the backend `MemoryIndexEntry`): * a lightweight, recall-oriented projection of a note. */ export interface MemoryIndexEntry { slug: string; title: string; hook: string; type: MemoryType; } /** A resolved `[[wikilink]]` target — the slug of another note. */ export type MemoryLink = string; // --------------------------------------------------------------------------- // Embedder (L14 / lot C2) — mirror of the backend `EmbedderProfileDto` and // `EmbedderEnginesDto`. Drives the memory/embedder settings panel. Identity of // a profile is its `id`; changing the active embedder takes effect at the next // app start (the UI says so explicitly). // --------------------------------------------------------------------------- /** * Embedding strategy of an embedder profile (mirror of the backend `strategy`). * `none` ⇒ no vector tier (naïve recall); the other strategies select where the * embeddings come from. */ export type EmbedderStrategy = "localOnnx" | "localServer" | "api" | "none"; /** * A declarative embedder profile (mirror of the backend `EmbedderProfile` DTO, * camelCase wire format). Transparent by design: secrets are never stored here — * `apiKeyEnv` is the *name* of an environment variable, never the key itself. */ export interface EmbedderProfile { id: string; name: string; strategy: EmbedderStrategy; /** Model id/name (ONNX model, server/api model). Omitted for `none`. */ model?: string; /** Server/API endpoint URL. Omitted for `localOnnx` / `none`. */ endpoint?: string; /** Name of the env var holding the API key (api strategy). Never the key. */ apiKeyEnv?: string; /** Embedding vector dimension. */ dimension: number; } /** * A recommended local ONNX engine (mirror of the backend `recommendedOnnx` * entry). `e5-small` is the curated default (`recommended: true`). */ export interface RecommendedOnnxEngine { id: string; displayName: string; dimension: number; approxSizeMb: number; recommended: boolean; } /** * Capabilities/availability snapshot of the embedder engines on this build * (mirror of the backend `describe_embedder_engines`). The `vector*Enabled` * flags reflect Cargo feature flags compiled into the build: a strategy whose * flag is `false` is shown disabled ("not available in this build"). */ export interface EmbedderEngines { recommendedOnnx: RecommendedOnnxEngine[]; ollamaDetected: boolean; onnxCachedModels: string[]; vectorHttpEnabled: boolean; vectorOnnxEnabled: boolean; } // --------------------------------------------------------------------------- // Templates (L7) — mirror of the domain `Template` / `AgentDrift`. // --------------------------------------------------------------------------- /** * A reusable agent template (mirror of the backend `Template` DTO). * `version` is incremented on each `updateTemplate` call. */ export interface Template { id: string; name: string; contentMd: string; version: number; defaultProfileId: string; } /** * Drift between a synchronized agent's last-synced template version and the * template's current version (mirror of `AgentDrift` backend DTO). */ export interface AgentDrift { agentId: string; from: number; to: number; } // --------------------------------------------------------------------------- // Git (L8) — UI-domain types for the git feature. // --------------------------------------------------------------------------- /** A file in the working tree with its staging state. */ export interface GitFileStatus { path: string; staged: boolean; } /** A git commit summary (short representation). */ export interface GitCommit { hash: string; summary: string; } /** The list of branches and the currently checked-out branch. */ export interface GitBranches { branches: string[]; current: string | null; } // --------------------------------------------------------------------------- // Tickets (public wire name for the backend `Issue` model — "ticket" is the // user-visible term; all commands/DTOs/events speak `ticket`/`issue`). Mirrors // the `Ticket*Dto` shapes in `crates/app-tauri/src/tickets.rs` (camelCase). // --------------------------------------------------------------------------- /** A ticket reference on the wire, always `#` (e.g. `"#42"`). */ export type TicketRef = string; /** Lifecycle status of a ticket. */ export type TicketStatus = "open" | "inProgress" | "QA" | "closed"; /** Priority of a ticket. */ export type TicketPriority = "low" | "medium" | "high" | "critical"; /** The kind of relationship between two tickets. */ export type TicketLinkKind = | "relatesTo" | "blocks" | "blockedBy" | "duplicates" | "dependsOn"; /** Who performed a ticket mutation (mirror of `TicketActorDto`, tagged `kind`). */ export type TicketActor = | { kind: "user" } | { kind: "agent"; agentId: string } | { kind: "system" }; /** One directed link from a ticket to another (mirror of `TicketLinkDto`). */ export interface TicketLink { targetRef: TicketRef; kind: TicketLinkKind; } /** Lifecycle status of a sprint (mirror of the backend `SprintStatus`). */ export type SprintStatus = "planned" | "active" | "done"; /** A sprint bucket tickets can belong to (mirror of `SprintDto`). */ export interface Sprint { id: string; /** Reorderable position; sections are displayed by ascending `order`. */ order: number; name: string; status: SprintStatus; /** How many tickets currently belong to this sprint. */ ticketCount: number; /** Optimistic-concurrency version. */ version: number; } /** The full ticket, as returned by `ticket_read`/`ticket_create`/… . */ export interface Ticket { id: string; ref: TicketRef; number: number; title: string; description: string; status: TicketStatus; priority: TicketPriority; /** Sprint membership (`null` ⇒ no sprint). Additive (ticket #10). */ sprintId?: string | null; /** Present only when the ticket was read with `includeCarnet`. */ carnet?: string; links: TicketLink[]; assignedAgentIds: string[]; createdBy: TicketActor; updatedBy: TicketActor; createdAt: number; updatedAt: number; /** Optimistic-concurrency version; echoed back as `expectedVersion` on writes. */ version: number; } /** A ticket summary row from `ticket_list` (mirror of `TicketSummaryDto`). */ export interface TicketSummary { ref: TicketRef; path: string; title: string; status: TicketStatus; priority: TicketPriority; /** Sprint membership (`null` ⇒ no sprint). Additive (ticket #10). */ sprintId?: string | null; assignedAgentIds: string[]; updatedAt: number; } /** One page of ticket summaries (mirror of `TicketListDto`). */ export interface TicketList { items: TicketSummary[]; /** Opaque cursor for the next page, absent when the list is exhausted. */ nextCursor?: string; } /** The scoped-to-a-ticket Markdown carnet (mirror of `TicketCarnetDto`). */ export interface TicketCarnet { ref: TicketRef; carnet: string; version: number; } /** * A ticket AI assistant chat session (mirror of the backend `TicketChatDto`, * ticket #8). `sessionId` is the live structured session driving the assistant; * `requester` is the assistant agent id; `issueRef` the bound ticket. */ export interface TicketChat { sessionId: string; requester: string; issueRef: TicketRef; } /** * One streamed chunk of an assistant turn (mirror of the backend `ReplyChunk`, * tagged on `kind`). `textDelta` is an incremental text fragment; `toolActivity` * a best-effort activity badge; `final` the deterministic end-of-turn chunk * carrying the aggregated content — after it the turn is frozen. `error` is a * visible terminal fallback (ticket #60): the model produced no usable answer, * so the turn ends with a human-readable explanation instead of a silent hang. */ export type ReplyChunk = | { kind: "textDelta"; text: string } | { kind: "toolActivity"; label: string } | { kind: "final"; content: string } | { kind: "error"; message: string };