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.
|
||||
///
|
||||
|
||||
@ -827,6 +827,183 @@ impl From<FirstRunStateOutput> for FirstRunStateDto {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Embedder profiles & engines (LOT C2 — §14.5.3)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use application::{
|
||||
DismissChoice, DismissEmbedderSuggestionInput, EmbedderEnginesView, ListEmbedderProfilesOutput,
|
||||
OnnxModelView, SaveEmbedderProfileInput, SaveEmbedderProfileOutput,
|
||||
};
|
||||
use domain::profile::EmbedderProfile;
|
||||
|
||||
/// An embedder profile crossing the wire. [`EmbedderProfile`] already serialises
|
||||
/// camelCase (`id, name, strategy, model?, endpoint?, apiKeyEnv?, dimension`), so we
|
||||
/// embed it directly — the TS mirror matches this shape.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct EmbedderProfileDto(pub EmbedderProfile);
|
||||
|
||||
/// A list of embedder profiles (camelCase array on the wire).
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct EmbedderProfileListDto(pub Vec<EmbedderProfileDto>);
|
||||
|
||||
impl From<Vec<EmbedderProfile>> for EmbedderProfileListDto {
|
||||
fn from(v: Vec<EmbedderProfile>) -> Self {
|
||||
Self(v.into_iter().map(EmbedderProfileDto).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ListEmbedderProfilesOutput> for EmbedderProfileListDto {
|
||||
fn from(out: ListEmbedderProfilesOutput) -> Self {
|
||||
out.profiles.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SaveEmbedderProfileOutput> for EmbedderProfileDto {
|
||||
fn from(out: SaveEmbedderProfileOutput) -> Self {
|
||||
Self(out.profile)
|
||||
}
|
||||
}
|
||||
|
||||
/// Request DTO for `save_embedder_profile`: the embedder profile to upsert.
|
||||
///
|
||||
/// Carries the [`EmbedderProfile`] fields directly (the entity validates them in the
|
||||
/// use case). Deserialised camelCase to match the persisted/domain shape.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SaveEmbedderProfileRequestDto {
|
||||
/// The embedder profile to upsert.
|
||||
pub profile: EmbedderProfile,
|
||||
}
|
||||
|
||||
impl From<SaveEmbedderProfileRequestDto> for SaveEmbedderProfileInput {
|
||||
fn from(dto: SaveEmbedderProfileRequestDto) -> Self {
|
||||
let EmbedderProfile {
|
||||
id,
|
||||
name,
|
||||
strategy,
|
||||
model,
|
||||
endpoint,
|
||||
api_key_env,
|
||||
dimension,
|
||||
} = dto.profile;
|
||||
Self {
|
||||
id,
|
||||
name,
|
||||
strategy,
|
||||
model,
|
||||
endpoint,
|
||||
api_key_env,
|
||||
dimension,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One recommendable local ONNX model on the wire (camelCase).
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OnnxModelInfoDto {
|
||||
/// Stable model id accepted by a `localOnnx` profile's `model` field.
|
||||
pub id: String,
|
||||
/// Human-readable name for the UI.
|
||||
pub display_name: String,
|
||||
/// Length of the vectors this model produces.
|
||||
pub dimension: usize,
|
||||
/// Approximate download/disk size in megabytes.
|
||||
pub approx_size_mb: u32,
|
||||
/// Whether this is the recommended default model.
|
||||
pub recommended: bool,
|
||||
}
|
||||
|
||||
impl From<OnnxModelView> for OnnxModelInfoDto {
|
||||
fn from(m: OnnxModelView) -> Self {
|
||||
Self {
|
||||
id: m.id,
|
||||
display_name: m.display_name,
|
||||
dimension: m.dimension,
|
||||
approx_size_mb: m.approx_size_mb,
|
||||
recommended: m.recommended,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Response DTO for `describe_embedder_engines` (drives the "configure an embedder?"
|
||||
/// UI). All-camelCase wire shape.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EmbedderEnginesDto {
|
||||
/// The curated catalogue of recommendable local ONNX models.
|
||||
pub recommended_onnx: Vec<OnnxModelInfoDto>,
|
||||
/// Whether an Ollama-style local embedding server was detected (best-effort).
|
||||
pub ollama_detected: bool,
|
||||
/// Ids of the recommended ONNX models already present in the local cache.
|
||||
pub onnx_cached_models: Vec<String>,
|
||||
/// Whether the HTTP capability (`localServer`/`api`) is compiled into this binary.
|
||||
pub vector_http_enabled: bool,
|
||||
/// Whether the in-process ONNX capability (`localOnnx`) is compiled into this binary.
|
||||
pub vector_onnx_enabled: bool,
|
||||
}
|
||||
|
||||
impl From<EmbedderEnginesView> for EmbedderEnginesDto {
|
||||
fn from(v: EmbedderEnginesView) -> Self {
|
||||
Self {
|
||||
recommended_onnx: v.recommended_onnx.into_iter().map(Into::into).collect(),
|
||||
ollama_detected: v.ollama_detected,
|
||||
onnx_cached_models: v.onnx_cached_models,
|
||||
vector_http_enabled: v.vector_http_enabled,
|
||||
vector_onnx_enabled: v.vector_onnx_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Embedder suggestion (LOT C3 — §14.5.5)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The user's response to the embedder suggestion, on the wire (camelCase:
|
||||
/// `"later"` | `"never"`).
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum DismissChoiceDto {
|
||||
/// "Plus tard" — re-proposable next session.
|
||||
Later,
|
||||
/// "Ne plus demander" — never again.
|
||||
Never,
|
||||
}
|
||||
|
||||
impl From<DismissChoiceDto> for DismissChoice {
|
||||
fn from(c: DismissChoiceDto) -> Self {
|
||||
match c {
|
||||
DismissChoiceDto::Later => Self::Later,
|
||||
DismissChoiceDto::Never => Self::Never,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request DTO for `dismiss_embedder_suggestion`. The `project_id` is resolved to a
|
||||
/// project root by the command; `choice` is the user's dismissal.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DismissEmbedderSuggestionRequestDto {
|
||||
/// The project the suggestion concerned (UUID string).
|
||||
pub project_id: String,
|
||||
/// The user's choice.
|
||||
pub choice: DismissChoiceDto,
|
||||
}
|
||||
|
||||
impl DismissEmbedderSuggestionRequestDto {
|
||||
/// Builds the use-case input from a resolved project root + the DTO choice.
|
||||
#[must_use]
|
||||
pub fn into_input(self, project_root: domain::ProjectPath) -> DismissEmbedderSuggestionInput {
|
||||
DismissEmbedderSuggestionInput {
|
||||
project_root,
|
||||
choice: self.choice.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a [`DeleteProfileInput`] from a raw profile-id string.
|
||||
///
|
||||
/// # Errors
|
||||
@ -927,6 +1104,16 @@ pub struct UpdateAgentContextRequestDto {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/// Request DTO for `update_project_context`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateProjectContextRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// New project-level Markdown context, stored under `.ideai/CONTEXT.md`.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/// Request DTO for `launch_agent`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -1015,6 +1202,9 @@ pub struct LiveAgentDto {
|
||||
pub agent_id: String,
|
||||
/// The hosting layout leaf's node id (UUID string).
|
||||
pub node_id: String,
|
||||
/// The live PTY session id, used to reattach a newly-opened cell without
|
||||
/// respawning the agent.
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
/// Response DTO for `list_live_agents` (transparent array on the wire).
|
||||
@ -1023,21 +1213,35 @@ pub struct LiveAgentDto {
|
||||
pub struct LiveAgentListDto(pub Vec<LiveAgentDto>);
|
||||
|
||||
impl LiveAgentListDto {
|
||||
/// Builds the wire list from the registry's `(AgentId, NodeId)` pairs.
|
||||
/// Builds the wire list from the registry's `(AgentId, NodeId, SessionId)` tuples.
|
||||
#[must_use]
|
||||
pub fn from_pairs(pairs: Vec<(AgentId, NodeId)>) -> Self {
|
||||
pub fn from_pairs(pairs: Vec<(AgentId, NodeId, domain::SessionId)>) -> Self {
|
||||
Self(
|
||||
pairs
|
||||
.into_iter()
|
||||
.map(|(agent_id, node_id)| LiveAgentDto {
|
||||
.map(|(agent_id, node_id, session_id)| LiveAgentDto {
|
||||
agent_id: agent_id.to_string(),
|
||||
node_id: node_id.to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Request DTO for `attach_live_agent`: bind an already-running agent session to
|
||||
/// a visible layout cell without spawning a new process.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AttachLiveAgentRequestDto {
|
||||
/// Id of the owning project (validated for symmetry and future scoping).
|
||||
pub project_id: String,
|
||||
/// Id of the already-running agent.
|
||||
pub agent_id: String,
|
||||
/// Layout leaf that should display the live session.
|
||||
pub node_id: String,
|
||||
}
|
||||
|
||||
/// Parses an agent-id string (UUID) coming from the frontend.
|
||||
///
|
||||
/// # Errors
|
||||
@ -1056,9 +1260,9 @@ pub fn parse_agent_id(raw: &str) -> Result<AgentId, ErrorDto> {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use application::{
|
||||
AgentDrift, CreateAgentFromTemplateInput, CreateTemplateInput,
|
||||
CreateTemplateOutput, DetectAgentDriftOutput, ListTemplatesOutput, SyncAgentWithTemplateOutput,
|
||||
UpdateTemplateInput, UpdateTemplateOutput,
|
||||
AgentDrift, CreateAgentFromTemplateInput, CreateTemplateInput, CreateTemplateOutput,
|
||||
DetectAgentDriftOutput, ListTemplatesOutput, SyncAgentWithTemplateOutput, UpdateTemplateInput,
|
||||
UpdateTemplateOutput,
|
||||
};
|
||||
use domain::{AgentTemplate, TemplateId};
|
||||
|
||||
@ -1253,9 +1457,7 @@ pub struct SyncAgentWithTemplateRequestDto {
|
||||
// Git (L8)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use application::{
|
||||
GitBranchesOutput, GitCommitOutput, GitLogOutput, GitStatusOutput,
|
||||
};
|
||||
use application::{GitBranchesOutput, GitCommitOutput, GitLogOutput, GitStatusOutput};
|
||||
use domain::ports::{GitCommitInfo, GitFileStatus, GraphCommit};
|
||||
|
||||
/// One changed path returned by `git_status`.
|
||||
@ -1284,7 +1486,12 @@ pub struct GitStatusListDto(pub Vec<GitFileStatusDto>);
|
||||
|
||||
impl From<GitStatusOutput> for GitStatusListDto {
|
||||
fn from(out: GitStatusOutput) -> Self {
|
||||
Self(out.entries.into_iter().map(GitFileStatusDto::from).collect())
|
||||
Self(
|
||||
out.entries
|
||||
.into_iter()
|
||||
.map(GitFileStatusDto::from)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1655,13 +1862,23 @@ pub struct MemoryIndexDto(pub Vec<MemoryIndexEntryDto>);
|
||||
|
||||
impl From<ReadMemoryIndexOutput> for MemoryIndexDto {
|
||||
fn from(out: ReadMemoryIndexOutput) -> Self {
|
||||
Self(out.entries.into_iter().map(MemoryIndexEntryDto::from).collect())
|
||||
Self(
|
||||
out.entries
|
||||
.into_iter()
|
||||
.map(MemoryIndexEntryDto::from)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RecallMemoryOutput> for MemoryIndexDto {
|
||||
fn from(out: RecallMemoryOutput) -> Self {
|
||||
Self(out.entries.into_iter().map(MemoryIndexEntryDto::from).collect())
|
||||
Self(
|
||||
out.entries
|
||||
.into_iter()
|
||||
.map(MemoryIndexEntryDto::from)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -129,6 +129,21 @@ pub enum DomainEventDto {
|
||||
/// Project id.
|
||||
project_id: String,
|
||||
},
|
||||
/// A project's memory crossed the recall budget while no embedder is configured
|
||||
/// (LOT C3 — §14.5.5): a one-time, dismissible "configure an embedder?" hint.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
EmbedderSuggested {
|
||||
/// Project id.
|
||||
project_id: String,
|
||||
/// Whether a local Ollama-style embedding server was detected.
|
||||
ollama_detected: bool,
|
||||
/// Ids of recommended ONNX models already present in the local cache.
|
||||
onnx_cached: Vec<String>,
|
||||
/// Whether the HTTP capability is compiled in.
|
||||
vector_http_enabled: bool,
|
||||
/// Whether the in-process ONNX capability is compiled in.
|
||||
vector_onnx_enabled: bool,
|
||||
},
|
||||
/// Raw PTY output (normally routed to a per-session channel, not here).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
PtyOutput {
|
||||
@ -163,11 +178,7 @@ impl From<&DomainEvent> for DomainEventDto {
|
||||
template_id: template_id.to_string(),
|
||||
version: version.get(),
|
||||
},
|
||||
DomainEvent::AgentDriftDetected {
|
||||
agent_id,
|
||||
from,
|
||||
to,
|
||||
} => Self::AgentDriftDetected {
|
||||
DomainEvent::AgentDriftDetected { agent_id, from, to } => Self::AgentDriftDetected {
|
||||
agent_id: agent_id.to_string(),
|
||||
from: from.get(),
|
||||
to: to.get(),
|
||||
@ -212,6 +223,19 @@ impl From<&DomainEvent> for DomainEventDto {
|
||||
DomainEvent::MemoryIndexRebuilt { project_id } => Self::MemoryIndexRebuilt {
|
||||
project_id: project_id.to_string(),
|
||||
},
|
||||
DomainEvent::EmbedderSuggested {
|
||||
project_id,
|
||||
ollama_detected,
|
||||
onnx_cached,
|
||||
vector_http_enabled,
|
||||
vector_onnx_enabled,
|
||||
} => Self::EmbedderSuggested {
|
||||
project_id: project_id.to_string(),
|
||||
ollama_detected: *ollama_detected,
|
||||
onnx_cached: onnx_cached.clone(),
|
||||
vector_http_enabled: *vector_http_enabled,
|
||||
vector_onnx_enabled: *vector_onnx_enabled,
|
||||
},
|
||||
DomainEvent::PtyOutput { session_id, bytes } => Self::PtyOutput {
|
||||
session_id: session_id.to_string(),
|
||||
bytes: bytes.clone(),
|
||||
|
||||
@ -92,6 +92,8 @@ pub fn run() {
|
||||
commands::open_project,
|
||||
commands::close_project,
|
||||
commands::list_projects,
|
||||
commands::read_project_context,
|
||||
commands::update_project_context,
|
||||
commands::open_terminal,
|
||||
commands::write_terminal,
|
||||
commands::resize_terminal,
|
||||
@ -111,9 +113,15 @@ pub fn run() {
|
||||
commands::save_profile,
|
||||
commands::delete_profile,
|
||||
commands::configure_profiles,
|
||||
commands::list_embedder_profiles,
|
||||
commands::save_embedder_profile,
|
||||
commands::delete_embedder_profile,
|
||||
commands::describe_embedder_engines,
|
||||
commands::dismiss_embedder_suggestion,
|
||||
commands::create_agent,
|
||||
commands::list_agents,
|
||||
commands::list_live_agents,
|
||||
commands::attach_live_agent,
|
||||
commands::read_agent_context,
|
||||
commands::update_agent_context,
|
||||
commands::delete_agent,
|
||||
|
||||
@ -10,32 +10,37 @@ use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use application::{
|
||||
CloseProject, CloseTab, CloseTerminal, ConfigureProfiles, CreateAgentFromScratch,
|
||||
CreateAgentFromTemplate, CreateLayout, CreateProject, CreateTemplate, DeleteAgent,
|
||||
DeleteLayout, DeleteProfile, DeleteTemplate, DetectAgentDrift, DetectProfiles, FirstRunState,
|
||||
GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage,
|
||||
HealthUseCase, InspectConversation, LaunchAgent, ListAgents, ListLayouts, ListProfiles, ListProjects, ListSkills,
|
||||
ListTemplates, LiveAgentRegistry, LoadLayout, MoveTabToNewWindow, MutateLayout, OpenProject,
|
||||
OpenTerminal, OrchestratorService, ReadAgentContext, ReferenceProfiles, RenameLayout,
|
||||
ResizeTerminal, SaveProfile, SetActiveLayout, SnapshotRunningAgents,
|
||||
AssignSkillToAgent, CreateSkill, DeleteSkill, UnassignSkillFromAgent, UpdateSkill,
|
||||
CreateMemory, DeleteMemory, GetMemory, ListMemories, ReadMemoryIndex, RecallMemory,
|
||||
ResolveMemoryLinks, UpdateMemory,
|
||||
SyncAgentWithTemplate, TerminalSessions, UpdateAgentContext, UpdateTemplate, WriteToTerminal,
|
||||
AssignSkillToAgent, CheckEmbedderSuggestion, CloseProject, CloseTab, CloseTerminal,
|
||||
ConfigureProfiles, CreateAgentFromScratch, CreateAgentFromTemplate, CreateLayout, CreateMemory,
|
||||
CreateProject, CreateSkill, CreateTemplate, DeleteAgent, DeleteEmbedderProfile, DeleteLayout,
|
||||
DeleteMemory, DeleteProfile, DeleteSkill, DeleteTemplate, DescribeEmbedderEngines,
|
||||
DetectAgentDrift, DetectProfiles, DismissEmbedderSuggestion, FirstRunState, GetMemory,
|
||||
GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus,
|
||||
GitUnstage, HealthUseCase, InspectConversation, LaunchAgent, ListAgents, ListEmbedderProfiles,
|
||||
ListLayouts, ListMemories, ListProfiles, ListProjects, ListSkills, ListTemplates,
|
||||
LiveAgentRegistry, LoadLayout, MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject,
|
||||
OpenTerminal, OrchestratorService, ReadAgentContext, ReadMemoryIndex, ReadProjectContext,
|
||||
RecallMemory, ReferenceProfiles, RenameLayout, ResizeTerminal, ResolveMemoryLinks,
|
||||
SaveEmbedderProfile, SaveProfile, SetActiveLayout, SnapshotRunningAgents, SuggestedThisSession,
|
||||
SyncAgentWithTemplate, TerminalSessions, UnassignSkillFromAgent, UpdateAgentContext,
|
||||
UpdateMemory, UpdateProjectContext, UpdateSkill, UpdateTemplate, WriteToTerminal,
|
||||
AGENT_MEMORY_RECALL_BUDGET,
|
||||
};
|
||||
use domain::ports::{
|
||||
AgentContextStore, AgentRuntime, Clock, Embedder, EventBus, FileSystem, GitPort, IdGenerator,
|
||||
MemoryRecall, MemoryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore,
|
||||
TemplateStore,
|
||||
AgentContextStore, AgentRuntime, Clock, Embedder, EmbedderEnvInspector, EmbedderProfileStore,
|
||||
EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator, MemoryRecall, MemoryStore,
|
||||
ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore, TemplateStore,
|
||||
};
|
||||
use domain::{DomainEvent, EmbedderProfile, Project, ProjectId};
|
||||
|
||||
use infrastructure::{
|
||||
embedder_from_profile, AdaptiveMemoryRecall, ClaudeTranscriptInspector, CliAgentRuntime,
|
||||
FsEmbedderProfileStore, FsMemoryStore, FsOrchestratorWatcher, FsProfileStore, FsProjectStore,
|
||||
FsSkillStore, FsTemplateStore, Git2Repository, IdeaiContextStore, LocalFileSystem,
|
||||
LocalProcessSpawner, NaiveMemoryRecall, OrchestratorWatchHandle, PortablePtyAdapter,
|
||||
SystemClock, TokioBroadcastEventBus, UuidGenerator, VectorMemoryRecall, ONNX_CACHE_SUBDIR,
|
||||
EmbedderEnvProbe, FsEmbedderProfileStore, FsEmbedderPromptStore, FsMemoryStore,
|
||||
FsOrchestratorWatcher, FsProfileStore, FsProjectStore, FsSkillStore, FsTemplateStore,
|
||||
Git2Repository, IdeaiContextStore, LocalFileSystem, LocalProcessSpawner, NaiveMemoryRecall,
|
||||
OrchestratorWatchHandle, PortablePtyAdapter, SystemClock, TokioBroadcastEventBus,
|
||||
UuidGenerator, VectorMemoryRecall, DEFAULT_OLLAMA_BASE_URL, ONNX_CACHE_SUBDIR,
|
||||
RECOMMENDED_ONNX_MODELS, VECTOR_HTTP_ENABLED, VECTOR_ONNX_ENABLED,
|
||||
};
|
||||
|
||||
use crate::pty::PtyBridge;
|
||||
@ -59,6 +64,10 @@ pub struct AppState {
|
||||
pub close_tab: Arc<CloseTab>,
|
||||
/// List known projects.
|
||||
pub list_projects: Arc<ListProjects>,
|
||||
/// Read `.ideai/CONTEXT.md`.
|
||||
pub read_project_context: Arc<ReadProjectContext>,
|
||||
/// Overwrite `.ideai/CONTEXT.md`.
|
||||
pub update_project_context: Arc<UpdateProjectContext>,
|
||||
/// Open a terminal (spawn PTY, register session).
|
||||
pub open_terminal: Arc<OpenTerminal>,
|
||||
/// Write keystrokes to a terminal.
|
||||
@ -195,6 +204,19 @@ pub struct AppState {
|
||||
/// Recall the most relevant memory entries for a query within a budget
|
||||
/// (LOT B — §14.5.2).
|
||||
pub recall_memory: Arc<RecallMemory>,
|
||||
// --- Embedder config (LOT C2 — §14.5.3) ---
|
||||
/// List the configured embedder profiles (`embedder.json`).
|
||||
pub list_embedder_profiles: Arc<ListEmbedderProfiles>,
|
||||
/// Save (upsert, validating) an embedder profile.
|
||||
pub save_embedder_profile: Arc<SaveEmbedderProfile>,
|
||||
/// Delete an embedder profile by id.
|
||||
pub delete_embedder_profile: Arc<DeleteEmbedderProfile>,
|
||||
/// Describe the embedding engines available to the configuration UI (catalogue
|
||||
/// + detected local environment + compiled-in capabilities).
|
||||
pub describe_embedder_engines: Arc<DescribeEmbedderEngines>,
|
||||
// --- Embedder suggestion (LOT C3 — §14.5.5) ---
|
||||
/// Persist the user's response to the embedder suggestion (`later`/`never`).
|
||||
pub dismiss_embedder_suggestion: Arc<DismissEmbedderSuggestion>,
|
||||
// --- Orchestrator (§14.3) ---
|
||||
/// Dispatches validated orchestrator requests to the agent/skill use cases.
|
||||
/// Shared by every per-project filesystem watcher.
|
||||
@ -254,6 +276,8 @@ impl AppState {
|
||||
let close_project = Arc::new(CloseProject::new(Arc::clone(&store_port)));
|
||||
let close_tab = Arc::new(CloseTab::new(Arc::clone(&store_port)));
|
||||
let list_projects = Arc::new(ListProjects::new(Arc::clone(&store_port)));
|
||||
let read_project_context = Arc::new(ReadProjectContext::new(Arc::clone(&fs_port)));
|
||||
let update_project_context = Arc::new(UpdateProjectContext::new(Arc::clone(&fs_port)));
|
||||
|
||||
// --- PTY adapter + terminal use cases (L3) ---
|
||||
let pty = Arc::new(PortablePtyAdapter::new());
|
||||
@ -372,10 +396,11 @@ impl AppState {
|
||||
// Load the configured embedder profile (mono-profile for now: take the last
|
||||
// listed, fall back to `none`). A multi-profile selector is a follow-up; the
|
||||
// `none` default keeps recall strictly naïve and dependency-free.
|
||||
let embedder_store = FsEmbedderProfileStore::new(
|
||||
let embedder_store = Arc::new(FsEmbedderProfileStore::new(
|
||||
Arc::clone(&fs_port),
|
||||
app_data_dir.to_string_lossy().into_owned(),
|
||||
);
|
||||
));
|
||||
let embedder_store_port = Arc::clone(&embedder_store) as Arc<dyn EmbedderProfileStore>;
|
||||
// `build` may run inside an ambient async runtime (Tauri's `setup`, or
|
||||
// `#[tokio::test]`), so blocking the current thread on a future panics.
|
||||
// Drive the one-shot load on a dedicated thread with its own runtime.
|
||||
@ -401,6 +426,66 @@ impl AppState {
|
||||
&onnx_cache_dir,
|
||||
);
|
||||
|
||||
// --- Embedder configuration use cases (LOT C2 — §14.5.3) ---
|
||||
// CRUD over `embedder.json` (port-typed store, built above) + a read-only
|
||||
// description of the available engines. The environment probe shares the SAME
|
||||
// `onnx_cache_dir` the recall uses, so "is this model cached?" stays coherent.
|
||||
// The static ONNX catalogue and the compiled-capability flags are owned by
|
||||
// infrastructure and injected here (the application stays infra-free, DIP).
|
||||
let env_inspector = Arc::new(EmbedderEnvProbe::new(
|
||||
onnx_cache_dir.clone(),
|
||||
DEFAULT_OLLAMA_BASE_URL,
|
||||
)) as Arc<dyn EmbedderEnvInspector>;
|
||||
// Cloned for the suggestion check below (the original is moved into
|
||||
// `describe_embedder_engines`). Both share the same probe behaviour.
|
||||
let env_inspector_for_suggestion = Arc::clone(&env_inspector);
|
||||
let recommended_onnx: Vec<OnnxModelView> = RECOMMENDED_ONNX_MODELS
|
||||
.iter()
|
||||
.map(|m| OnnxModelView {
|
||||
id: m.id.to_owned(),
|
||||
display_name: m.display_name.to_owned(),
|
||||
dimension: m.dimension,
|
||||
approx_size_mb: m.approx_size_mb,
|
||||
recommended: m.recommended,
|
||||
})
|
||||
.collect();
|
||||
let list_embedder_profiles =
|
||||
Arc::new(ListEmbedderProfiles::new(Arc::clone(&embedder_store_port)));
|
||||
let save_embedder_profile =
|
||||
Arc::new(SaveEmbedderProfile::new(Arc::clone(&embedder_store_port)));
|
||||
let delete_embedder_profile =
|
||||
Arc::new(DeleteEmbedderProfile::new(Arc::clone(&embedder_store_port)));
|
||||
let describe_embedder_engines = Arc::new(DescribeEmbedderEngines::new(
|
||||
env_inspector,
|
||||
recommended_onnx,
|
||||
VECTOR_HTTP_ENABLED,
|
||||
VECTOR_ONNX_ENABLED,
|
||||
));
|
||||
|
||||
// --- Embedder suggestion (LOT C3 — §14.5.5) ---
|
||||
// Per-project dismissal state (`.ideai/memory/.embedder-prompt.json`) +
|
||||
// the in-memory "already suggested this session" guard, shared with the
|
||||
// launcher's best-effort check. The check publishes EmbedderSuggested at
|
||||
// most once per session per project, only while strategy is `none` and the
|
||||
// memory has outgrown the recall budget.
|
||||
let prompt_store = Arc::new(FsEmbedderPromptStore::new(Arc::clone(&fs_port)));
|
||||
let prompt_store_port = Arc::clone(&prompt_store) as Arc<dyn EmbedderPromptStore>;
|
||||
let suggested_this_session: SuggestedThisSession = SuggestedThisSession::default();
|
||||
let check_embedder_suggestion = Arc::new(CheckEmbedderSuggestion::new(
|
||||
Arc::clone(&embedder_store_port),
|
||||
Arc::clone(&memory_store_port),
|
||||
Arc::clone(&prompt_store_port),
|
||||
env_inspector_for_suggestion,
|
||||
Arc::clone(&events_port),
|
||||
Arc::clone(&suggested_this_session),
|
||||
AGENT_MEMORY_RECALL_BUDGET,
|
||||
VECTOR_HTTP_ENABLED,
|
||||
VECTOR_ONNX_ENABLED,
|
||||
));
|
||||
let dismiss_embedder_suggestion = Arc::new(DismissEmbedderSuggestion::new(Arc::clone(
|
||||
&prompt_store_port,
|
||||
)));
|
||||
|
||||
let create_agent = Arc::new(CreateAgentFromScratch::new(
|
||||
Arc::clone(&contexts_port),
|
||||
Arc::clone(&ids) as Arc<dyn IdGenerator>,
|
||||
@ -426,6 +511,7 @@ impl AppState {
|
||||
Arc::clone(&events_port),
|
||||
Arc::clone(&ids) as Arc<dyn IdGenerator>,
|
||||
Arc::clone(&memory_recall_port),
|
||||
Some(Arc::clone(&check_embedder_suggestion)),
|
||||
));
|
||||
|
||||
// --- Conversation inspection (T7) ---
|
||||
@ -437,11 +523,9 @@ impl AppState {
|
||||
let home_dir = std::env::var("HOME")
|
||||
.or_else(|_| std::env::var("USERPROFILE"))
|
||||
.unwrap_or_default();
|
||||
let inspectors: Vec<Arc<dyn domain::ports::SessionInspector>> =
|
||||
vec![Arc::new(ClaudeTranscriptInspector::new(
|
||||
Arc::clone(&fs_port),
|
||||
home_dir,
|
||||
))];
|
||||
let inspectors: Vec<Arc<dyn domain::ports::SessionInspector>> = vec![Arc::new(
|
||||
ClaudeTranscriptInspector::new(Arc::clone(&fs_port), home_dir),
|
||||
)];
|
||||
let inspect_conversation = Arc::new(InspectConversation::new(
|
||||
Arc::clone(&contexts_port),
|
||||
Arc::clone(&profile_store_port),
|
||||
@ -491,14 +575,20 @@ impl AppState {
|
||||
let git_status = Arc::new(GitStatus::new(Arc::clone(&git_port)));
|
||||
let git_stage = Arc::new(GitStage::new(Arc::clone(&git_port)));
|
||||
let git_unstage = Arc::new(GitUnstage::new(Arc::clone(&git_port)));
|
||||
let git_commit = Arc::new(GitCommit::new(Arc::clone(&git_port), Arc::clone(&events_port)));
|
||||
let git_commit = Arc::new(GitCommit::new(
|
||||
Arc::clone(&git_port),
|
||||
Arc::clone(&events_port),
|
||||
));
|
||||
let git_branches = Arc::new(GitBranches::new(Arc::clone(&git_port)));
|
||||
let git_checkout = Arc::new(GitCheckout::new(
|
||||
Arc::clone(&git_port),
|
||||
Arc::clone(&events_port),
|
||||
));
|
||||
let git_log = Arc::new(GitLog::new(Arc::clone(&git_port)));
|
||||
let git_init = Arc::new(GitInit::new(Arc::clone(&git_port), Arc::clone(&events_port)));
|
||||
let git_init = Arc::new(GitInit::new(
|
||||
Arc::clone(&git_port),
|
||||
Arc::clone(&events_port),
|
||||
));
|
||||
let git_graph = Arc::new(GitGraph::new(Arc::clone(&git_port)));
|
||||
|
||||
// --- Skill use cases (L12) ---
|
||||
@ -578,6 +668,8 @@ impl AppState {
|
||||
close_project,
|
||||
close_tab,
|
||||
list_projects,
|
||||
read_project_context,
|
||||
update_project_context,
|
||||
open_terminal,
|
||||
write_terminal,
|
||||
resize_terminal,
|
||||
@ -639,6 +731,11 @@ impl AppState {
|
||||
read_memory_index,
|
||||
resolve_memory_links,
|
||||
recall_memory,
|
||||
list_embedder_profiles,
|
||||
save_embedder_profile,
|
||||
delete_embedder_profile,
|
||||
describe_embedder_engines,
|
||||
dismiss_embedder_suggestion,
|
||||
orchestrator_service,
|
||||
orchestrator_watchers: Mutex::new(HashMap::new()),
|
||||
move_tab,
|
||||
|
||||
Reference in New Issue
Block a user