use crate::desktop::{
DesktopContextResponse, DesktopDaemonRequest, DesktopErrorEnvelope, DesktopHistoryResponse,
DesktopImportSessionRequest, DesktopImportSessionResponse, DesktopLifecycleActionDto,
DesktopMemoryActionRequest, DesktopMemoryDraftRequest, DesktopMetadataDto,
DesktopPromptOptimizeRequest, DesktopPromptOptimizeResponse, DesktopRecordLookupRequest,
DesktopRecordResponse, DesktopRouteRequest, DesktopService, DesktopSessionActionRequest,
DesktopSessionActionResponse, DesktopSessionBrowserRequest, DesktopSessionBrowserResponse,
DesktopSessionDetailRequest, DesktopSessionDetailResponse, DesktopStatusRequest,
DesktopStatusResponse, DesktopWakeupRequest, DesktopWakeupResponse, DesktopWikiIndexRequest,
DesktopWikiIndexResponse, DesktopWikiLintRequest, DesktopWikiLintResponse,
DesktopWorkbenchRequest, DesktopWorkbenchResponse, DesktopWriteResponse,
};
use crate::domain::{MemoryScope, OutputFormat, TargetTool, WakeupProfile};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub type TauriCommandResult<T> = Result<T, DesktopErrorEnvelope>;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriContextCommand {
pub config_path: PathBuf,
pub vault_root_override: Option<PathBuf>,
pub cwd: PathBuf,
pub task: String,
#[serde(default)]
pub files: Vec<String>,
pub target: TargetTool,
pub format: OutputFormat,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriWakeupCommand {
pub config_path: PathBuf,
pub vault_root_override: Option<PathBuf>,
pub cwd: PathBuf,
pub task: String,
#[serde(default)]
pub files: Vec<String>,
pub target: TargetTool,
pub profile: WakeupProfile,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriPromptOptimizeCommand {
pub config_path: PathBuf,
pub vault_root_override: Option<PathBuf>,
pub cwd: PathBuf,
pub task: String,
#[serde(default)]
pub files: Vec<String>,
pub target: TargetTool,
pub profile: WakeupProfile,
pub provider: Option<String>,
pub session_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriDaemonCommand {
pub enabled: bool,
pub daemon_bin: Option<PathBuf>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriWorkbenchCommand {
pub config_path: PathBuf,
pub daemon: Option<TauriDaemonCommand>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriRecordCommand {
pub config_path: PathBuf,
pub record_id: String,
pub daemon: Option<TauriDaemonCommand>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriSessionBrowserCommand {
pub config_path: PathBuf,
pub page: Option<usize>,
pub per_page: Option<usize>,
pub provider: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriSessionDetailCommand {
pub config_path: PathBuf,
pub session_id: String,
pub message_offset: Option<usize>,
pub message_limit: Option<usize>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriSessionActionCommand {
pub config_path: PathBuf,
pub session_id: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriStatusCommand {
pub config_path: PathBuf,
pub vault_root_override: Option<PathBuf>,
pub cwd: PathBuf,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriImportSessionCommand {
pub config_path: PathBuf,
pub provider: String,
pub session_id: String,
#[serde(default)]
pub apply: bool,
#[serde(default)]
pub actor: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriMemoryDraftCommand {
pub config_path: PathBuf,
pub title: String,
pub summary: String,
pub memory_type: String,
pub scope: MemoryScope,
pub source_ref: String,
pub project_id: Option<String>,
pub user_id: Option<String>,
pub sensitivity: Option<String>,
#[serde(default)]
pub metadata: DesktopMetadataDto,
#[serde(default)]
pub entities: Vec<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub triggers: Vec<String>,
#[serde(default)]
pub related_files: Vec<String>,
#[serde(default)]
pub related_records: Vec<String>,
#[serde(default)]
pub supersedes: Option<String>,
#[serde(default)]
pub applies_to: Vec<String>,
#[serde(default)]
pub valid_until: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct TauriMemoryActionCommand {
pub config_path: PathBuf,
pub record_id: String,
pub action: DesktopLifecycleActionDto,
#[serde(default)]
pub metadata: DesktopMetadataDto,
}
pub fn desktop_run_context(
command: TauriContextCommand,
) -> TauriCommandResult<DesktopContextResponse> {
DesktopService::new().run_context(command.into_request())
}
pub fn desktop_build_wakeup(
command: TauriWakeupCommand,
) -> TauriCommandResult<DesktopWakeupResponse> {
DesktopService::new().build_wakeup(command.into_request())
}
pub fn desktop_optimize_prompt(
command: TauriPromptOptimizeCommand,
) -> TauriCommandResult<DesktopPromptOptimizeResponse> {
DesktopService::new().optimize_prompt(command.into_request())
}
pub fn desktop_load_workbench(
command: TauriWorkbenchCommand,
) -> TauriCommandResult<DesktopWorkbenchResponse> {
DesktopService::new().load_workbench(command.into_request())
}
pub fn desktop_get_record(
command: TauriRecordCommand,
) -> TauriCommandResult<Option<DesktopRecordResponse>> {
DesktopService::new().get_record(command.into_request())
}
pub fn desktop_get_history(
command: TauriRecordCommand,
) -> TauriCommandResult<DesktopHistoryResponse> {
DesktopService::new().get_history(command.into_request())
}
pub fn desktop_browse_sessions(
command: TauriSessionBrowserCommand,
) -> TauriCommandResult<DesktopSessionBrowserResponse> {
DesktopService::new().browse_sessions(command.into_request())
}
pub fn desktop_get_session(
command: TauriSessionDetailCommand,
) -> TauriCommandResult<Option<DesktopSessionDetailResponse>> {
DesktopService::new().get_session(command.into_request())
}
pub fn desktop_collect_status(
command: TauriStatusCommand,
) -> TauriCommandResult<DesktopStatusResponse> {
DesktopService::new().collect_status(command.into_request())
}
pub fn desktop_continue_session(
command: TauriSessionActionCommand,
) -> TauriCommandResult<DesktopSessionActionResponse> {
DesktopService::new().continue_session(command.into_request())
}
pub fn desktop_delete_session(
command: TauriSessionActionCommand,
) -> TauriCommandResult<DesktopSessionActionResponse> {
DesktopService::new().delete_session(command.into_request())
}
pub fn desktop_record_manual(
command: TauriMemoryDraftCommand,
) -> TauriCommandResult<DesktopWriteResponse> {
DesktopService::new().record_manual(command.into_request())
}
pub fn desktop_propose_memory(
command: TauriMemoryDraftCommand,
) -> TauriCommandResult<DesktopWriteResponse> {
DesktopService::new().propose_memory(command.into_request())
}
pub fn desktop_apply_memory_action(
command: TauriMemoryActionCommand,
) -> TauriCommandResult<DesktopWriteResponse> {
DesktopService::new().apply_memory_action(command.into_request())
}
pub fn desktop_import_session(
command: TauriImportSessionCommand,
) -> TauriCommandResult<DesktopImportSessionResponse> {
DesktopService::new().import_session(command.into_request())
}
pub fn desktop_wiki_lint(
request: DesktopWikiLintRequest,
) -> TauriCommandResult<DesktopWikiLintResponse> {
DesktopService::new().wiki_lint(request)
}
pub fn desktop_read_wiki_index(
request: DesktopWikiIndexRequest,
) -> TauriCommandResult<DesktopWikiIndexResponse> {
DesktopService::new().read_wiki_index(request)
}
impl TauriContextCommand {
fn into_request(self) -> DesktopRouteRequest {
DesktopRouteRequest {
config_path: self.config_path,
vault_root_override: self.vault_root_override,
cwd: self.cwd,
task: self.task,
files: self.files,
target: self.target,
format: self.format,
}
}
}
impl TauriWakeupCommand {
fn into_request(self) -> DesktopWakeupRequest {
DesktopWakeupRequest {
config_path: self.config_path,
vault_root_override: self.vault_root_override,
cwd: self.cwd,
task: self.task,
files: self.files,
target: self.target,
profile: self.profile,
}
}
}
impl TauriPromptOptimizeCommand {
fn into_request(self) -> DesktopPromptOptimizeRequest {
DesktopPromptOptimizeRequest {
config_path: self.config_path,
vault_root_override: self.vault_root_override,
cwd: self.cwd,
task: self.task,
files: self.files,
target: self.target,
profile: self.profile,
provider: self.provider,
session_id: self.session_id,
}
}
}
impl TauriWorkbenchCommand {
fn into_request(self) -> DesktopWorkbenchRequest {
DesktopWorkbenchRequest {
config_path: self.config_path,
daemon: self.daemon.map(TauriDaemonCommand::into_request),
}
}
}
impl TauriRecordCommand {
fn into_request(self) -> DesktopRecordLookupRequest {
DesktopRecordLookupRequest {
config_path: self.config_path,
record_id: self.record_id,
daemon: self.daemon.map(TauriDaemonCommand::into_request),
}
}
}
impl TauriDaemonCommand {
fn into_request(self) -> DesktopDaemonRequest {
DesktopDaemonRequest {
enabled: self.enabled,
daemon_bin: self.daemon_bin,
}
}
}
impl TauriSessionBrowserCommand {
fn into_request(self) -> DesktopSessionBrowserRequest {
DesktopSessionBrowserRequest {
config_path: self.config_path,
page: self.page.unwrap_or(1),
per_page: self.per_page.unwrap_or(10),
provider: self.provider,
}
}
}
impl TauriSessionDetailCommand {
fn into_request(self) -> DesktopSessionDetailRequest {
DesktopSessionDetailRequest {
config_path: self.config_path,
session_id: self.session_id,
message_offset: self.message_offset,
message_limit: self.message_limit,
}
}
}
impl TauriStatusCommand {
fn into_request(self) -> DesktopStatusRequest {
DesktopStatusRequest {
config_path: self.config_path,
vault_root_override: self.vault_root_override,
cwd: self.cwd,
}
}
}
impl TauriSessionActionCommand {
fn into_request(self) -> DesktopSessionActionRequest {
DesktopSessionActionRequest {
config_path: self.config_path,
session_id: self.session_id,
}
}
}
impl TauriMemoryDraftCommand {
fn into_request(self) -> DesktopMemoryDraftRequest {
DesktopMemoryDraftRequest {
config_path: self.config_path,
title: self.title,
summary: self.summary,
memory_type: self.memory_type,
scope: self.scope,
source_ref: self.source_ref,
project_id: self.project_id,
user_id: self.user_id,
sensitivity: self.sensitivity,
metadata: self.metadata,
entities: self.entities,
tags: self.tags,
triggers: self.triggers,
related_files: self.related_files,
related_records: self.related_records,
supersedes: self.supersedes,
applies_to: self.applies_to,
valid_until: self.valid_until,
}
}
}
impl TauriMemoryActionCommand {
fn into_request(self) -> DesktopMemoryActionRequest {
DesktopMemoryActionRequest {
config_path: self.config_path,
record_id: self.record_id,
action: self.action,
metadata: self.metadata,
}
}
}
impl TauriImportSessionCommand {
fn into_request(self) -> DesktopImportSessionRequest {
DesktopImportSessionRequest {
config_path: self.config_path,
provider: self.provider,
session_id: self.session_id,
apply: self.apply,
actor: self.actor,
}
}
}
#[cfg(test)]
mod tests {
use super::{
TauriContextCommand, TauriDaemonCommand, TauriMemoryActionCommand, TauriMemoryDraftCommand,
TauriRecordCommand, TauriWakeupCommand, TauriWorkbenchCommand, desktop_apply_memory_action,
desktop_build_wakeup, desktop_get_history, desktop_get_record, desktop_load_workbench,
desktop_propose_memory, desktop_run_context,
};
use crate::desktop::{DesktopLifecycleActionDto, DesktopMetadataDto};
use crate::domain::{
MemoryLifecycleState, MemoryScope, OutputFormat, TargetTool, WakeupProfile,
};
use std::fs;
use tempfile::tempdir;
fn setup_workspace() -> (tempfile::TempDir, std::path::PathBuf, std::path::PathBuf) {
let temp = tempdir().unwrap();
let vault_root = temp.path().join("vault");
let repo_root = temp.path().join("repo");
fs::create_dir_all(vault_root.join("10-Projects")).unwrap();
fs::create_dir_all(&repo_root).unwrap();
fs::write(
vault_root.join("10-Projects/spool.md"),
"---\ntitle: spool\nmemory_type: project\n---\n\nDesktop tauri command test note.\n",
)
.unwrap();
let config_path = temp.path().join("spool.toml");
fs::write(
&config_path,
format!(
"[vault]\nroot = \"{}\"\n\n[output]\ndefault_format = \"markdown\"\nmax_chars = 4000\nmax_notes = 8\n\n[[projects]]\nid = \"spool\"\nname = \"spool\"\nrepo_paths = [\"{}\"]\nnote_roots = [\"10-Projects\"]\n",
vault_root.display(),
repo_root.display(),
),
)
.unwrap();
(temp, config_path, repo_root)
}
#[test]
fn tauri_commands_should_parse_inputs_and_run_context_and_wakeup() {
let (_temp, config_path, repo_root) = setup_workspace();
let context = desktop_run_context(TauriContextCommand {
config_path: config_path.clone(),
vault_root_override: None,
cwd: repo_root.clone(),
task: "summarize current project context".to_string(),
files: vec!["src/lib.rs".to_string(), "src/main.rs".to_string()],
target: TargetTool::Claude,
format: OutputFormat::Markdown,
})
.unwrap();
assert_eq!(context.bundle.route.debug.note_count, 1);
let wakeup = desktop_build_wakeup(TauriWakeupCommand {
config_path,
vault_root_override: None,
cwd: repo_root,
task: "prepare restart packet".to_string(),
files: Vec::new(),
target: TargetTool::Claude,
profile: WakeupProfile::Project,
})
.unwrap();
assert_eq!(wakeup.packet.profile, WakeupProfile::Project);
}
#[test]
fn tauri_commands_should_cover_lifecycle_read_and_write_flows() {
let (_temp, config_path, _repo_root) = setup_workspace();
let created = desktop_propose_memory(TauriMemoryDraftCommand {
config_path: config_path.clone(),
title: "测试偏好".to_string(),
summary: "先 smoke 再收口".to_string(),
memory_type: "workflow".to_string(),
scope: MemoryScope::User,
source_ref: "session:1".to_string(),
project_id: None,
user_id: Some("long".to_string()),
sensitivity: None,
metadata: DesktopMetadataDto {
actor: Some("desktop".to_string()),
reason: Some("captured from tauri command".to_string()),
evidence_refs: vec!["session:1".to_string(), "obsidian://workflow".to_string()],
},
entities: Vec::new(),
tags: Vec::new(),
triggers: Vec::new(),
related_files: Vec::new(),
related_records: Vec::new(),
supersedes: None,
applies_to: Vec::new(),
valid_until: None,
})
.unwrap();
assert_eq!(created.entry.record.state, MemoryLifecycleState::Candidate);
let workbench = desktop_load_workbench(TauriWorkbenchCommand {
config_path: config_path.clone(),
daemon: Some(TauriDaemonCommand {
enabled: false,
daemon_bin: None,
}),
})
.unwrap();
assert_eq!(workbench.snapshot.pending_review.len(), 1);
let record = desktop_get_record(TauriRecordCommand {
config_path: config_path.clone(),
record_id: created.entry.record_id.clone(),
daemon: None,
})
.unwrap()
.unwrap();
assert_eq!(record.record.record.title, "测试偏好");
let action = desktop_apply_memory_action(TauriMemoryActionCommand {
config_path: config_path.clone(),
record_id: created.entry.record_id.clone(),
action: DesktopLifecycleActionDto::Accept,
metadata: DesktopMetadataDto::default(),
})
.unwrap();
assert_eq!(action.entry.record.state, MemoryLifecycleState::Accepted);
let history = desktop_get_history(TauriRecordCommand {
config_path,
record_id: created.entry.record_id,
daemon: None,
})
.unwrap();
assert_eq!(history.history.len(), 2);
}
}