feat(memory): config embedders (LOT C2) + suggestion contextuelle (LOT C3) + contexte projet partagé
- LOT C2 (§14.5.3) : use cases de configuration des embedders déclaratifs (List/Save/Delete + DescribeEmbedderEngines : modèles ONNX recommandés, environnement local détecté, stratégies compilées). UI EmbedderSettings. - LOT C3 (§14.5.5) : suggestion contextuelle best-effort à l'activation quand la mémoire dépasse le budget de recall sans embedder configuré (event EmbedderSuggested, anti-spam 1×/session, « ne plus demander »). - Contexte projet partagé .ideai/CONTEXT.md (model-agnostic) injecté à tous les agents/profils au lancement, avant la persona. UI ProjectContextPanel. Tests : backend workspace vert (0 échec) ; frontend 306/306. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -7,45 +7,47 @@
|
||||
use tauri::ipc::Channel;
|
||||
use tauri::State;
|
||||
|
||||
use crate::dto::DismissEmbedderSuggestionRequestDto;
|
||||
use application::{
|
||||
AppError, CloseProjectInput, CreateAgentInput, CreateLayoutInput, DeleteAgentInput,
|
||||
DeleteLayoutInput, DeleteTemplateInput, DetectAgentDriftInput, GitBranchesInput,
|
||||
GitCheckoutInput, GitCommitInput, GitGraphInput, GitInitInput, GitLogInput, GitStagePathInput,
|
||||
GitStatusInput, InspectConversationInput, LaunchAgentInput, ListAgentsInput, ListLayoutsInput, LoadLayoutInput,
|
||||
MutateLayoutInput, OpenProjectInput, ReadAgentContextInput, RenameLayoutInput,
|
||||
SetActiveLayoutInput, SnapshotRunningAgentsInput, SyncAgentWithTemplateInput,
|
||||
UpdateAgentContextInput,
|
||||
AssignSkillToAgentInput, CreateSkillInput, DeleteSkillInput, ListSkillsInput,
|
||||
UnassignSkillFromAgentInput, UpdateSkillInput,
|
||||
CreateMemoryInput, DeleteMemoryInput, GetMemoryInput, ListMemoriesInput, ReadMemoryIndexInput,
|
||||
RecallMemoryInput, ResolveMemoryLinksInput, UpdateMemoryInput,
|
||||
AppError, AssignSkillToAgentInput, CloseProjectInput, CreateAgentInput, CreateLayoutInput,
|
||||
CreateMemoryInput, CreateSkillInput, DeleteAgentInput, DeleteEmbedderProfileInput,
|
||||
DeleteLayoutInput, DeleteMemoryInput, DeleteSkillInput, DeleteTemplateInput,
|
||||
DetectAgentDriftInput, GetMemoryInput, GitBranchesInput, GitCheckoutInput, GitCommitInput,
|
||||
GitGraphInput, GitInitInput, GitLogInput, GitStagePathInput, GitStatusInput,
|
||||
InspectConversationInput, LaunchAgentInput, ListAgentsInput, ListLayoutsInput,
|
||||
ListMemoriesInput, ListSkillsInput, LoadLayoutInput, MutateLayoutInput, OpenProjectInput,
|
||||
ReadAgentContextInput, ReadMemoryIndexInput, ReadProjectContextInput, RecallMemoryInput,
|
||||
RenameLayoutInput, ResolveMemoryLinksInput, SetActiveLayoutInput, SnapshotRunningAgentsInput,
|
||||
SyncAgentWithTemplateInput, UnassignSkillFromAgentInput, UpdateAgentContextInput,
|
||||
UpdateMemoryInput, UpdateProjectContextInput, UpdateSkillInput,
|
||||
};
|
||||
use domain::ports::PtyHandle;
|
||||
|
||||
use crate::dto::{
|
||||
parse_agent_id, parse_close_terminal, parse_delete_profile, parse_layout_id, parse_node_id,
|
||||
parse_profile_id, LiveAgentListDto,
|
||||
parse_project_id, parse_session_id, parse_template_id, AgentDriftListDto, AgentDto,
|
||||
AgentListDto, ConfigureProfilesRequestDto, CreateAgentFromTemplateRequestDto,
|
||||
CreateAgentRequestDto, CreateLayoutRequestDto, CreateLayoutResultDto, CreateProjectRequestDto,
|
||||
parse_agent_id, parse_close_terminal, parse_delete_profile, parse_layout_id, parse_memory_slug,
|
||||
parse_node_id, parse_profile_id, parse_project_id, parse_session_id, parse_skill_id,
|
||||
parse_template_id, AgentDriftListDto, AgentDto, AgentListDto, AssignSkillRequestDto,
|
||||
AttachLiveAgentRequestDto, ConfigureProfilesRequestDto, ConversationDetailsDto,
|
||||
CreateAgentFromTemplateRequestDto, CreateAgentRequestDto, CreateLayoutRequestDto,
|
||||
CreateLayoutResultDto, CreateMemoryRequestDto, CreateProjectRequestDto, CreateSkillRequestDto,
|
||||
CreateTemplateRequestDto, DeleteLayoutRequestDto, DeleteLayoutResultDto,
|
||||
DetectProfilesRequestDto, DetectProfilesResponseDto, ErrorDto, FirstRunStateDto,
|
||||
GitBranchesDto, GitCheckoutRequestDto, GitCommitDto, GitCommitListDto, GitCommitRequestDto,
|
||||
GitStageRequestDto, GitStatusListDto, GraphCommitListDto, HealthRequestDto, HealthResponseDto,
|
||||
ConversationDetailsDto, InspectConversationRequestDto,
|
||||
LaunchAgentRequestDto, LayoutDto, LayoutOperationDto, ListLayoutsDto, OpenTerminalRequestDto,
|
||||
ProfileDto, ProfileListDto, ProjectDto, ProjectListDto, ReadAgentContextResponseDto,
|
||||
ReattachResultDto, RenameLayoutRequestDto, ResizeTerminalRequestDto, SaveProfileRequestDto,
|
||||
SetActiveLayoutRequestDto, SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto,
|
||||
TemplateListDto, TerminalClosedDto, TerminalSessionDto, UpdateAgentContextRequestDto,
|
||||
UpdateTemplateRequestDto, WriteTerminalRequestDto, parse_skill_id, AssignSkillRequestDto,
|
||||
CreateSkillRequestDto, SkillDto, SkillListDto, UnassignSkillRequestDto, UpdateSkillRequestDto,
|
||||
parse_memory_slug, CreateMemoryRequestDto, MemoryDto, MemoryIndexDto, MemoryLinksDto,
|
||||
MemoryListDto, RecallMemoryRequestDto, UpdateMemoryRequestDto,
|
||||
DetectProfilesRequestDto, DetectProfilesResponseDto, EmbedderEnginesDto, EmbedderProfileDto,
|
||||
EmbedderProfileListDto, ErrorDto, FirstRunStateDto, GitBranchesDto, GitCheckoutRequestDto,
|
||||
GitCommitDto, GitCommitListDto, GitCommitRequestDto, GitStageRequestDto, GitStatusListDto,
|
||||
GraphCommitListDto, HealthRequestDto, HealthResponseDto, InspectConversationRequestDto,
|
||||
LaunchAgentRequestDto, LayoutDto, LayoutOperationDto, ListLayoutsDto, LiveAgentListDto,
|
||||
MemoryDto, MemoryIndexDto, MemoryLinksDto, MemoryListDto, OpenTerminalRequestDto, ProfileDto,
|
||||
ProfileListDto, ProjectDto, ProjectListDto, ReadAgentContextResponseDto, ReattachResultDto,
|
||||
RecallMemoryRequestDto, RenameLayoutRequestDto, ResizeTerminalRequestDto,
|
||||
SaveEmbedderProfileRequestDto, SaveProfileRequestDto, SetActiveLayoutRequestDto, SkillDto,
|
||||
SkillListDto, SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto,
|
||||
TerminalClosedDto, TerminalSessionDto, UnassignSkillRequestDto, UpdateAgentContextRequestDto,
|
||||
UpdateMemoryRequestDto, UpdateProjectContextRequestDto, UpdateSkillRequestDto,
|
||||
UpdateTemplateRequestDto, WriteTerminalRequestDto,
|
||||
};
|
||||
use domain::{SkillRef, SkillScope};
|
||||
use crate::pty::{PtyBridge, PtyChunk};
|
||||
use crate::state::AppState;
|
||||
use domain::{SkillRef, SkillScope};
|
||||
|
||||
/// `health` — trivial command validating the full IPC pipeline
|
||||
/// (frontend gateway → invoke → command → use case → ports → event relay).
|
||||
@ -112,10 +114,7 @@ pub async fn open_project(
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a malformed id, `STORE` on failure).
|
||||
#[tauri::command]
|
||||
pub async fn close_project(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
pub async fn close_project(project_id: String, state: State<'_, AppState>) -> Result<(), ErrorDto> {
|
||||
let id = parse_project_id(&project_id)?;
|
||||
// T5: freeze `agent_was_running` on every agent leaf BEFORE any PTY release,
|
||||
// reading the live registry as it stands now. Best-effort: a snapshot failure
|
||||
@ -152,6 +151,49 @@ pub async fn list_projects(state: State<'_, AppState>) -> Result<ProjectListDto,
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `read_project_context` — read `.ideai/CONTEXT.md` for a project.
|
||||
///
|
||||
/// Missing context is returned as an empty string so a project whose `.ideai/`
|
||||
/// was deleted can still open cleanly.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a malformed id, `NOT_FOUND` if the
|
||||
/// project is unknown, `FILESYSTEM`/`STORE` on read or UTF-8 failure).
|
||||
#[tauri::command]
|
||||
pub async fn read_project_context(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<String, ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
state
|
||||
.read_project_context
|
||||
.execute(ReadProjectContextInput { project })
|
||||
.await
|
||||
.map(|out| out.content)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `update_project_context` — overwrite `.ideai/CONTEXT.md` for a project.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a malformed id, `NOT_FOUND` if the
|
||||
/// project is unknown, `FILESYSTEM` on write failure).
|
||||
#[tauri::command]
|
||||
pub async fn update_project_context(
|
||||
request: UpdateProjectContextRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
state
|
||||
.update_project_context
|
||||
.execute(UpdateProjectContextInput {
|
||||
project,
|
||||
content: request.content,
|
||||
})
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Terminals (L3)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -224,10 +266,7 @@ pub fn write_terminal(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let input = request.into_input()?;
|
||||
state
|
||||
.write_terminal
|
||||
.execute(input)
|
||||
.map_err(ErrorDto::from)
|
||||
state.write_terminal.execute(input).map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `resize_terminal` — resize a live PTY.
|
||||
@ -241,10 +280,7 @@ pub fn resize_terminal(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let input = request.into_input()?;
|
||||
state
|
||||
.resize_terminal
|
||||
.execute(input)
|
||||
.map_err(ErrorDto::from)
|
||||
state.resize_terminal.execute(input).map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `close_terminal` — kill a live PTY and tear down its channel.
|
||||
@ -629,6 +665,99 @@ pub async fn configure_profiles(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Embedder profiles & engines (LOT C2 — §14.5.3)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `list_embedder_profiles` — list the configured embedder profiles (empty when
|
||||
/// none configured ⇒ the default `none` posture).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`STORE` on `embedder.json` I/O failure).
|
||||
#[tauri::command]
|
||||
pub async fn list_embedder_profiles(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<EmbedderProfileListDto, ErrorDto> {
|
||||
state
|
||||
.list_embedder_profiles
|
||||
.execute()
|
||||
.await
|
||||
.map(EmbedderProfileListDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `save_embedder_profile` — create or replace (by id) a single embedder profile.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for empty id/name or zero dimension, `STORE`
|
||||
/// on I/O failure).
|
||||
#[tauri::command]
|
||||
pub async fn save_embedder_profile(
|
||||
request: SaveEmbedderProfileRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<EmbedderProfileDto, ErrorDto> {
|
||||
state
|
||||
.save_embedder_profile
|
||||
.execute(request.into())
|
||||
.await
|
||||
.map(EmbedderProfileDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `delete_embedder_profile` — delete an embedder profile by id.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`NOT_FOUND` if absent, `STORE` on I/O failure).
|
||||
#[tauri::command]
|
||||
pub async fn delete_embedder_profile(
|
||||
embedder_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
state
|
||||
.delete_embedder_profile
|
||||
.execute(DeleteEmbedderProfileInput { id: embedder_id })
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `describe_embedder_engines` — describe the engines available to the
|
||||
/// "configure an embedder?" UI: the recommended ONNX catalogue, a best-effort
|
||||
/// snapshot of the local environment, and which strategies are compiled in.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] — in practice never (the environment probe is best-effort).
|
||||
#[tauri::command]
|
||||
pub async fn describe_embedder_engines(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<EmbedderEnginesDto, ErrorDto> {
|
||||
state
|
||||
.describe_embedder_engines
|
||||
.execute()
|
||||
.await
|
||||
.map(EmbedderEnginesDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `dismiss_embedder_suggestion` — persist the user's response to the one-time
|
||||
/// embedder suggestion (LOT C3 — §14.5.5): `later` (re-proposable next session) or
|
||||
/// `never` (silenced for good). Resolves the project root from `project_id`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a malformed id, `NOT_FOUND` if the project
|
||||
/// is unknown, `STORE` on `.embedder-prompt.json` I/O failure).
|
||||
#[tauri::command]
|
||||
pub async fn dismiss_embedder_suggestion(
|
||||
request: DismissEmbedderSuggestionRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
state
|
||||
.dismiss_embedder_suggestion
|
||||
.execute(request.into_input(project.root))
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agents (L6)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -714,6 +843,36 @@ pub fn list_live_agents(
|
||||
))
|
||||
}
|
||||
|
||||
/// `attach_live_agent` — rebind an already-running agent session to a visible
|
||||
/// layout cell without respawning the CLI process.
|
||||
///
|
||||
/// This is the backend side of "a cell is a view": a closed cell can leave an
|
||||
/// agent running in the background, and opening the agent in a new cell updates
|
||||
/// the session's host node while preserving the PTY/session/scrollback.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for malformed ids, `NOT_FOUND` if the
|
||||
/// project/agent/live session is unknown).
|
||||
#[tauri::command]
|
||||
pub async fn attach_live_agent(
|
||||
request: AttachLiveAgentRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<LiveAgentListDto, ErrorDto> {
|
||||
let _project = resolve_project(&request.project_id, &state).await?;
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
let node_id = parse_node_id(&request.node_id)?;
|
||||
let session = state
|
||||
.terminal_sessions
|
||||
.rebind_agent_node(&agent_id, node_id)
|
||||
.ok_or_else(|| AppError::NotFound(format!("running session for agent {agent_id}")))?;
|
||||
|
||||
Ok(LiveAgentListDto::from_pairs(vec![(
|
||||
agent_id,
|
||||
session.node_id,
|
||||
session.id,
|
||||
)]))
|
||||
}
|
||||
|
||||
/// `read_agent_context` — read an agent's Markdown context.
|
||||
///
|
||||
/// # Errors
|
||||
@ -1190,10 +1349,7 @@ pub async fn git_log(
|
||||
/// Returns an [`ErrorDto`] (`INVALID` for a bad project id or root, `GIT` on
|
||||
/// failure).
|
||||
#[tauri::command]
|
||||
pub async fn git_init(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
pub async fn git_init(project_id: String, state: State<'_, AppState>) -> Result<(), ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
let root = project.root.as_str().to_owned();
|
||||
state
|
||||
@ -1231,8 +1387,8 @@ pub async fn git_graph(
|
||||
// Windows (L10)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use application::MoveTabToNewWindowInput;
|
||||
use crate::dto::{parse_tab_id, MoveTabResultDto};
|
||||
use application::MoveTabToNewWindowInput;
|
||||
|
||||
/// `move_tab_to_new_window` — detach a tab into a brand-new OS window.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user