Ajoute le paramètre de tri dans TicketListQuery et l'expose via ticket_list (surface app-tauri + MCP). Ports et adaptateur mock frontend alignés sur le contrat de tri. La partie UI (contrôle de tri dans TicketFacetsBar) suivra dans un commit frontend dédié. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
907 lines
35 KiB
TypeScript
907 lines
35 KiB
TypeScript
/**
|
|
* 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<HealthReport>;
|
|
/** Subscribes to relayed domain events. */
|
|
onDomainEvent(handler: (event: DomainEvent) => void): Promise<Unsubscribe>;
|
|
/**
|
|
* 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<string | null>;
|
|
}
|
|
|
|
/** 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<Agent[]>;
|
|
/**
|
|
* 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<ResumableAgent[]>;
|
|
/**
|
|
* 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<LiveAgent[]>;
|
|
/**
|
|
* 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<LiveAgent>;
|
|
/**
|
|
* 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<StoppedLiveAgent>;
|
|
/** Creates a new agent from scratch; returns the created agent. */
|
|
createAgent(projectId: string, input: CreateAgentInput): Promise<Agent>;
|
|
/**
|
|
* 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<string>;
|
|
/** Overwrites an agent's `.md` context. */
|
|
updateContext(projectId: string, agentId: string, content: string): Promise<void>;
|
|
/** Removes an agent from the project. */
|
|
deleteAgent(projectId: string, agentId: string): Promise<void>;
|
|
/**
|
|
* 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<TerminalHandle>;
|
|
/**
|
|
* 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<ReattachResult>;
|
|
/**
|
|
* 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<ConversationDetails>;
|
|
}
|
|
|
|
/** 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<void>;
|
|
/** Resizes the PTY. */
|
|
resize(rows: number, cols: number): Promise<void>;
|
|
/**
|
|
* 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<void>;
|
|
}
|
|
|
|
/**
|
|
* 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<TerminalHandle>;
|
|
/**
|
|
* 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<ReattachResult>;
|
|
/**
|
|
* 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<void>;
|
|
}
|
|
|
|
/** 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<Project[]>;
|
|
/** Creates a project from a root; returns the created project. */
|
|
createProject(name: string, root: string): Promise<Project>;
|
|
/** Opens a project by id; returns the opened project. */
|
|
openProject(projectId: string): Promise<Project>;
|
|
/** Closes a project by id. */
|
|
closeProject(projectId: string): Promise<void>;
|
|
/** Reads the shared project context stored at `.ideai/CONTEXT.md`. */
|
|
readProjectContext(projectId: string): Promise<string>;
|
|
/** Overwrites the shared project context stored at `.ideai/CONTEXT.md`. */
|
|
updateProjectContext(projectId: string, content: string): Promise<void>;
|
|
}
|
|
|
|
/** 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<LayoutTree>;
|
|
/**
|
|
* 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<LayoutTree>;
|
|
/** Lists all named layouts for a project, with the current active id. */
|
|
listLayouts(projectId: string): Promise<LayoutList>;
|
|
/** 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<void>;
|
|
/** 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<GitFileStatus[]>;
|
|
stage(projectId: string, path: string): Promise<void>;
|
|
unstage(projectId: string, path: string): Promise<void>;
|
|
commit(projectId: string, message: string): Promise<GitCommit>;
|
|
branches(projectId: string): Promise<GitBranches>;
|
|
checkout(projectId: string, branch: string): Promise<void>;
|
|
log(projectId: string, limit: number): Promise<GitCommit[]>;
|
|
init(projectId: string): Promise<void>;
|
|
/** Returns the full commit DAG for the git-graph layout. */
|
|
graph(projectId: string, limit: number): Promise<GraphCommit[]>;
|
|
}
|
|
|
|
/** Remote (SSH/WSL) connection management (L9). */
|
|
export interface RemoteGateway {
|
|
connect(projectId: string): Promise<void>;
|
|
}
|
|
|
|
/** 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<Template[]>;
|
|
/** Creates a new template; returns the created template. */
|
|
createTemplate(input: CreateTemplateInput): Promise<Template>;
|
|
/** Updates a template's content; increments its version; returns the updated template. */
|
|
updateTemplate(templateId: string, content: string): Promise<Template>;
|
|
/** Deletes a template by id. */
|
|
deleteTemplate(templateId: string): Promise<void>;
|
|
/**
|
|
* Creates an agent in `projectId` based on the given template.
|
|
* The agent's origin will be `fromTemplate`; its context will be the template's `contentMd`.
|
|
*/
|
|
createAgentFromTemplate(
|
|
projectId: string,
|
|
templateId: string,
|
|
opts?: { name?: string; synchronized?: boolean },
|
|
): Promise<Agent>;
|
|
/** Returns the list of synchronized agents in `projectId` whose template has been updated. */
|
|
detectDrift(projectId: string): Promise<AgentDrift[]>;
|
|
/**
|
|
* Syncs a single agent to the current version of its template.
|
|
* Returns `{ synced: true, version }` on success, `{ synced: false, version: null }` for
|
|
* scratch / non-synchronized agents.
|
|
*/
|
|
syncAgent(
|
|
projectId: string,
|
|
agentId: string,
|
|
): Promise<{ synced: boolean; version: number | null }>;
|
|
}
|
|
|
|
/** Input for {@link SkillGateway.createSkill}. */
|
|
export interface CreateSkillInput {
|
|
/** Owning project (resolved to a root; ignored on disk for `global`). */
|
|
projectId: string;
|
|
name: string;
|
|
content: string;
|
|
scope: SkillScope;
|
|
}
|
|
|
|
/**
|
|
* Skills (L12): CRUD for reusable, model-agnostic workflows in either scope, and
|
|
* agent↔skill assignment. Assigned skills are injected into the agent's
|
|
* convention file at activation (handled backend-side, ARCHITECTURE §14.2).
|
|
*/
|
|
export interface SkillGateway {
|
|
/** Lists the skills in one scope for the given project. */
|
|
listSkills(projectId: string, scope: SkillScope): Promise<Skill[]>;
|
|
/** Creates a new skill; returns the created skill. */
|
|
createSkill(input: CreateSkillInput): Promise<Skill>;
|
|
/** Updates a skill's content; returns the updated skill. */
|
|
updateSkill(
|
|
projectId: string,
|
|
scope: SkillScope,
|
|
skillId: string,
|
|
content: string,
|
|
): Promise<Skill>;
|
|
/** Deletes a skill by id from its scope's store. */
|
|
deleteSkill(
|
|
projectId: string,
|
|
scope: SkillScope,
|
|
skillId: string,
|
|
): Promise<void>;
|
|
/** Assigns a skill to an agent (idempotent). */
|
|
assignSkill(
|
|
projectId: string,
|
|
agentId: string,
|
|
skillId: string,
|
|
scope: SkillScope,
|
|
): Promise<void>;
|
|
/** Unassigns a skill from an agent (idempotent). */
|
|
unassignSkill(
|
|
projectId: string,
|
|
agentId: string,
|
|
skillId: string,
|
|
): Promise<void>;
|
|
}
|
|
|
|
/** Input for {@link MemoryGateway.createMemory}. */
|
|
export interface CreateMemoryInput {
|
|
/** Owning project (resolved to its `.ideai/` memory store). */
|
|
projectId: string;
|
|
/** Human title; also the source of the note's slug identity. */
|
|
name: string;
|
|
description: string;
|
|
type: MemoryType;
|
|
content: string;
|
|
}
|
|
|
|
/**
|
|
* Memory (L14): CRUD over project memory notes + the recall-oriented index and
|
|
* `[[wikilink]]` resolution. Identity is the **slug** (kebab-case), not a UUID;
|
|
* a note's slug is immutable, so `updateMemory` never changes it. Mirrors the
|
|
* seven backend memory commands (+ optional `recall`).
|
|
*/
|
|
export interface MemoryGateway {
|
|
/** Lists every memory note of a project (full payloads). */
|
|
listMemories(projectId: string): Promise<Memory[]>;
|
|
/** Reads a single note by slug. */
|
|
getMemory(projectId: string, slug: string): Promise<Memory>;
|
|
/** Creates a new note; returns the created note. */
|
|
createMemory(input: CreateMemoryInput): Promise<Memory>;
|
|
/** Updates a note's mutable fields (slug is immutable); returns the updated note. */
|
|
updateMemory(
|
|
projectId: string,
|
|
slug: string,
|
|
description: string,
|
|
type: MemoryType,
|
|
content: string,
|
|
): Promise<Memory>;
|
|
/** Deletes a note by slug. */
|
|
deleteMemory(projectId: string, slug: string): Promise<void>;
|
|
/** Reads the recall-oriented memory index. */
|
|
readIndex(projectId: string): Promise<MemoryIndexEntry[]>;
|
|
/** Resolves a note's `[[wikilinks]]` to the slugs of existing target notes. */
|
|
resolveLinks(projectId: string, slug: string): Promise<MemoryLink[]>;
|
|
/** Best-effort recall: the index entries most relevant to `text`, within a token budget. */
|
|
recall(
|
|
projectId: string,
|
|
text: string,
|
|
tokenBudget: number,
|
|
): Promise<MemoryIndexEntry[]>;
|
|
}
|
|
|
|
/**
|
|
* Agent input control (ARCHITECTURE §20). The mediated-input strip is gone: the
|
|
* agent cell is a **native terminal** and human keystrokes (Enter included) go
|
|
* straight to the PTY. This port only carries the two out-of-band controls:
|
|
*
|
|
* - **Interrompre** → `interrupt` (preempt the current turn — a control byte,
|
|
* not an enqueue).
|
|
* - **Ack** → `delegationDelivered`: the cell's write-portal confirms it has
|
|
* effectively written a delegation's text into the native PTY (closes the
|
|
* "the turn was delivered" loop for observability; the `ask` wake-up still
|
|
* rides on `idea_reply`).
|
|
*
|
|
* The component talks to this gateway via DI, never to `invoke()` directly.
|
|
*/
|
|
export interface InputGateway {
|
|
/** Interrompre = preempt: signals the current turn to stop (not an enqueue). */
|
|
interrupt(projectId: string, agentId: string): Promise<void>;
|
|
/**
|
|
* Ack: the cell's write-portal has written the delegation `ticket` into the
|
|
* agent's native PTY (ARCHITECTURE §20.3). Best-effort; never changes the
|
|
* ticket correlation.
|
|
*/
|
|
delegationDelivered(
|
|
projectId: string,
|
|
agentId: string,
|
|
ticket: string,
|
|
): Promise<void>;
|
|
/**
|
|
* Reports whether a **frontend terminal cell** is mounted for `agentId` (`true`
|
|
* when the cell's write-portal binds its PTY handle, `false` on unmount). The
|
|
* backend mediator needs this to deliver a turn to a **headless** agent — one
|
|
* delegated in the background with no cell, where nobody would consume
|
|
* `delegationReady`: it then writes the task into the PTY itself. An agent with a
|
|
* mounted cell keeps the write-portal path. Best-effort; never throws into the UI.
|
|
*/
|
|
setFrontAttached(agentId: string, attached: boolean): Promise<void>;
|
|
/**
|
|
* Cancels the **auto-resume** armed for a rate-limited agent (ARCHITECTURE §21):
|
|
* the user clicked "Annuler la reprise" during the cancellable window. Disarms
|
|
* the scheduled wake-up. Resolves to `true` iff a resume was effectively
|
|
* cancelled (a wake-up was still pending); `false` if none was armed or it had
|
|
* already fired (the resume then runs its course). The backend also emits
|
|
* `agentResumeCancelled` on success, which clears the countdown in the UI.
|
|
*/
|
|
cancelResume(agentId: string): Promise<boolean>;
|
|
/**
|
|
* Human net (level 3, ARCHITECTURE §21.1): arms a resume at a user-chosen
|
|
* instant for an agent whose limit was *suspected* without a reliable reset
|
|
* time. `resetsAtMs` is the chosen wake-up in epoch-milliseconds; the backend
|
|
* clamps a past instant to "now" (⇒ immediate resume). This arms the same
|
|
* cancellable resume as the automatic path and re-emits `agentResumeScheduled`,
|
|
* so the UI flips on its own from "heure inconnue" to the nominal countdown.
|
|
*/
|
|
setResumeAt(agentId: string, resetsAtMs: number): Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* AI profiles & first-run (L5). Drives the first-run wizard and profile
|
|
* management: the pre-filled reference catalogue, detection of installed CLIs,
|
|
* and CRUD/batch persistence of the chosen/edited/custom profiles.
|
|
*/
|
|
export interface ProfileGateway {
|
|
/** First-run state: whether to show the wizard + the reference catalogue. */
|
|
firstRunState(): Promise<FirstRunState>;
|
|
/** The pre-filled, editable reference catalogue (Claude/Codex/Gemini/Aider). */
|
|
referenceProfiles(): Promise<AgentProfile[]>;
|
|
/** Probes each candidate's detection command; returns availability (✓/✗). */
|
|
detectProfiles(candidates: AgentProfile[]): Promise<ProfileAvailability[]>;
|
|
/** Lists the configured profiles. */
|
|
listProfiles(): Promise<AgentProfile[]>;
|
|
/** Creates or replaces (by id) a single profile; returns the saved profile. */
|
|
saveProfile(profile: AgentProfile): Promise<AgentProfile>;
|
|
/** Deletes a profile by id. */
|
|
deleteProfile(profileId: string): Promise<void>;
|
|
/** Persists the batch of chosen profiles, closing the first run. */
|
|
configureProfiles(profiles: AgentProfile[]): Promise<AgentProfile[]>;
|
|
}
|
|
|
|
/** Input for {@link EmbedderGateway.saveEmbedderProfile}. */
|
|
export interface SaveEmbedderProfileInput {
|
|
profile: EmbedderProfile;
|
|
}
|
|
|
|
/**
|
|
* Embedder configuration (L14 / lot C2). Drives the memory/embedder settings
|
|
* panel: lists the configured embedder profiles, the available engines (with
|
|
* build-time feature flags so unavailable strategies can be shown disabled),
|
|
* and persists / removes profiles. Changing the active embedder takes effect at
|
|
* the next app start. Mirrors the four backend embedder commands.
|
|
*/
|
|
export interface EmbedderGateway {
|
|
/** Lists the configured embedder profiles (transparent: no secrets). */
|
|
listEmbedderProfiles(): Promise<EmbedderProfile[]>;
|
|
/** Creates or replaces (by id) a single profile; returns the saved profile. */
|
|
saveEmbedderProfile(profile: EmbedderProfile): Promise<EmbedderProfile>;
|
|
/** Deletes an embedder profile by id. */
|
|
deleteEmbedderProfile(embedderId: string): Promise<void>;
|
|
/** Describes the available engines + build feature flags + detection. */
|
|
describeEmbedderEngines(): Promise<EmbedderEngines>;
|
|
}
|
|
|
|
/** Project and agent permission management (LP1). */
|
|
export interface PermissionGateway {
|
|
/** Reads the full project permission document. */
|
|
getProjectPermissions(projectId: string): Promise<ProjectPermissions>;
|
|
/** Replaces or removes project-level default permissions. */
|
|
updateProjectPermissions(
|
|
projectId: string,
|
|
permissions: PermissionSet | null,
|
|
): Promise<ProjectPermissions>;
|
|
/** Replaces or removes one agent-specific override. */
|
|
updateAgentPermissions(
|
|
projectId: string,
|
|
agentId: string,
|
|
permissions: PermissionSet | null,
|
|
): Promise<ProjectPermissions>;
|
|
/** Resolves effective permissions for one agent. */
|
|
resolveAgentPermissions(
|
|
projectId: string,
|
|
agentId: string,
|
|
): Promise<EffectivePermissions | null>;
|
|
}
|
|
|
|
/** Read-only live work-state read-model for conversations/delegations. */
|
|
export interface WorkStateGateway {
|
|
/** Reads the current per-agent live/offline and idle/busy state for a project. */
|
|
getProjectWorkState(projectId: string): Promise<ProjectWorkState>;
|
|
/**
|
|
* Cancels a running/pending background task by id. The read-model refreshes
|
|
* through the `backgroundTaskChanged` domain event.
|
|
*/
|
|
cancelBackgroundTask(taskId: string): Promise<void>;
|
|
/**
|
|
* Re-runs a failed/cancelled background task under a **new** task id (the
|
|
* original id is never reused).
|
|
*/
|
|
retryBackgroundTask(taskId: string): Promise<void>;
|
|
}
|
|
|
|
/** One paginated page request over a conversation transcript (LS7). */
|
|
export interface ConversationPageRequest {
|
|
/** Turn id to paginate around; omit to start from a thread end. */
|
|
anchor?: string;
|
|
/** Travel direction; defaults to `"backward"` (latest page first). */
|
|
direction?: PageDirection;
|
|
/** Requested page size (clamped by the backend to `[1, 200]`). */
|
|
limit?: number;
|
|
}
|
|
|
|
/** Read-only, paginated access to a conversation's full transcript (LS7). */
|
|
export interface ConversationGateway {
|
|
/** Reads one page of a conversation's transcript (full text, never truncated). */
|
|
readPage(
|
|
projectId: string,
|
|
conversationId: string,
|
|
request?: ConversationPageRequest,
|
|
): Promise<TurnPage>;
|
|
}
|
|
|
|
/**
|
|
* Filter/search criteria for {@link TicketGateway.list} (ticket #12).
|
|
*
|
|
* Status and priority are **multi-select**: `statuses`/`priorities` are sets of
|
|
* accepted values with OR semantics *within* a facet and AND *across* facets. An
|
|
* empty or absent array means "no constraint on this facet" (all values pass).
|
|
* `sort` is optional; when absent, the backend preserves its historical order.
|
|
*/
|
|
export interface TicketListQuery {
|
|
statuses?: TicketStatus[];
|
|
priorities?: TicketPriority[];
|
|
assignedAgentId?: string;
|
|
/** Free-text match over title/description. */
|
|
text?: string;
|
|
sort?: TicketListSort;
|
|
limit?: number;
|
|
cursor?: string;
|
|
}
|
|
|
|
export type TicketListSortField = "number" | "priority" | "status" | "title";
|
|
|
|
export type TicketListSortDirection = "asc" | "desc";
|
|
|
|
export interface TicketListSort {
|
|
field: TicketListSortField;
|
|
direction: TicketListSortDirection;
|
|
}
|
|
|
|
/** Input for {@link TicketGateway.create}. */
|
|
export interface CreateTicketInput {
|
|
title: string;
|
|
description?: string;
|
|
priority?: TicketPriority;
|
|
status?: TicketStatus;
|
|
assignedAgentIds?: string[];
|
|
}
|
|
|
|
/**
|
|
* Fields to change on {@link TicketGateway.update}. Only the provided keys are
|
|
* touched; `expectedVersion` is mandatory (optimistic concurrency — a stale
|
|
* value surfaces a `versionConflict` {@link GatewayError}).
|
|
*/
|
|
export interface UpdateTicketInput {
|
|
title?: string;
|
|
description?: string;
|
|
status?: TicketStatus;
|
|
priority?: TicketPriority;
|
|
assignedAgentIds?: string[];
|
|
expectedVersion: number;
|
|
}
|
|
|
|
/**
|
|
* Ticket gateway (public wire name for the backend `Issue` model). The only
|
|
* frontend place that knows the `ticket_*` command names; features consume this
|
|
* port through DI. Every mutation carries an `expectedVersion` and rejects with
|
|
* a `versionConflict` {@link GatewayError} when the ticket moved underneath.
|
|
*/
|
|
export interface TicketGateway {
|
|
/** Creates a ticket and returns it. */
|
|
create(projectId: string, input: CreateTicketInput): Promise<Ticket>;
|
|
/** Reads one ticket, optionally including its carnet body. */
|
|
read(
|
|
projectId: string,
|
|
ref: string,
|
|
includeCarnet?: boolean,
|
|
): Promise<Ticket>;
|
|
/** Lists tickets matching the (optional) filter/search query. */
|
|
list(projectId: string, query?: TicketListQuery): Promise<TicketList>;
|
|
/** Applies partial edits (title/description/status/priority/assignees). */
|
|
update(
|
|
projectId: string,
|
|
ref: string,
|
|
input: UpdateTicketInput,
|
|
): Promise<Ticket>;
|
|
/**
|
|
* Deletes a ticket (ticket #6). Rejects with a `notFound` {@link GatewayError}
|
|
* when the ref does not exist. On success the backend emits an `issueDeleted`
|
|
* domain event (payload `{ projectId, issueRef, freedSprint }`) — the single
|
|
* source of truth that drives the ticket's removal from lists and the closing
|
|
* of an open detail view. Callers must not mutate local state imperatively.
|
|
*/
|
|
delete(projectId: string, ref: string): Promise<void>;
|
|
/** Reads the ticket-scoped Markdown carnet. */
|
|
readCarnet(projectId: string, ref: string): Promise<TicketCarnet>;
|
|
/** Replaces (not appends) the ticket-scoped carnet body. */
|
|
updateCarnet(
|
|
projectId: string,
|
|
ref: string,
|
|
carnet: string,
|
|
expectedVersion: number,
|
|
): Promise<Ticket>;
|
|
/** Links this ticket to another `#id` with the given relationship kind. */
|
|
link(
|
|
projectId: string,
|
|
ref: string,
|
|
targetRef: string,
|
|
kind: TicketLinkKind,
|
|
expectedVersion: number,
|
|
): Promise<Ticket>;
|
|
/** Removes a link (optionally scoped to a single kind). */
|
|
unlink(
|
|
projectId: string,
|
|
ref: string,
|
|
targetRef: string,
|
|
expectedVersion: number,
|
|
kind?: TicketLinkKind,
|
|
): Promise<Ticket>;
|
|
/** Assigns/unassigns an agent to the ticket. */
|
|
assign(
|
|
projectId: string,
|
|
ref: string,
|
|
agentId: string,
|
|
assigned: boolean,
|
|
expectedVersion: number,
|
|
): Promise<Ticket>;
|
|
/** Lists the project's sprints, ordered by `order` (ticket #10). */
|
|
listSprints(projectId: string): Promise<Sprint[]>;
|
|
/**
|
|
* Sets (or clears with `sprintId === null`) a ticket's sprint membership
|
|
* (ticket #10). Optimistic concurrency: `expectedVersion` must match.
|
|
*/
|
|
setTicketSprint(
|
|
projectId: string,
|
|
ref: string,
|
|
sprintId: string | null,
|
|
expectedVersion: number,
|
|
): Promise<Ticket>;
|
|
/** Creates a sprint (name required by the backend) and returns it (#11). */
|
|
createSprint(projectId: string, name: string): Promise<Sprint>;
|
|
/**
|
|
* Renames a sprint. Optimistic concurrency: `expectedVersion` must match the
|
|
* sprint's current version (#11).
|
|
*/
|
|
renameSprint(
|
|
projectId: string,
|
|
sprintId: string,
|
|
name: string,
|
|
expectedVersion: number,
|
|
): Promise<Sprint>;
|
|
/**
|
|
* Reorders sprints to the given full list of ids (execution order). Returns
|
|
* the reordered sprint list (#11).
|
|
*/
|
|
reorderSprints(projectId: string, orderedIds: string[]): Promise<Sprint[]>;
|
|
/**
|
|
* Deletes a sprint. The backend unassigns its tickets (it does NOT delete
|
|
* them); they fall back to the "no sprint" bucket (#11).
|
|
*/
|
|
deleteSprint(projectId: string, sprintId: string): Promise<void>;
|
|
/**
|
|
* Opens an AI assistant chat bound to a ticket (ticket #8). Spawns a
|
|
* structured assistant session driven by `profileId`; returns the live
|
|
* session handle. One session per ticket — reopening supersedes.
|
|
*/
|
|
openTicketChat(
|
|
projectId: string,
|
|
issueRef: string,
|
|
profileId: string,
|
|
): Promise<TicketChat>;
|
|
/** Closes (disposes) a ticket's assistant chat session (ticket #8). */
|
|
closeTicketChat(projectId: string, issueRef: string): Promise<void>;
|
|
/**
|
|
* Sends a message on an open assistant session and streams the reply back as
|
|
* {@link ReplyChunk}s via `onChunk` (ticket #8). Resolves once the turn has
|
|
* started; the turn ends at the `final` chunk. Reuses the shared `agent_send`
|
|
* chat transport (the same as the structured chat cell).
|
|
*/
|
|
sendTicketChat(
|
|
sessionId: string,
|
|
message: string,
|
|
onChunk: (chunk: ReplyChunk) => void,
|
|
): Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* The full set of gateways the app depends on, injected via the DI provider.
|
|
* The composition (real vs mock) is chosen in `app/`.
|
|
*/
|
|
export interface Gateways {
|
|
system: SystemGateway;
|
|
agent: AgentGateway;
|
|
input: InputGateway;
|
|
terminal: TerminalGateway;
|
|
project: ProjectGateway;
|
|
layout: LayoutGateway;
|
|
git: GitGateway;
|
|
remote: RemoteGateway;
|
|
profile: ProfileGateway;
|
|
template: TemplateGateway;
|
|
skill: SkillGateway;
|
|
memory: MemoryGateway;
|
|
embedder: EmbedderGateway;
|
|
permission: PermissionGateway;
|
|
workState: WorkStateGateway;
|
|
conversation: ConversationGateway;
|
|
ticket: TicketGateway;
|
|
}
|