Skip to main content

spool/desktop/
dto.rs

1//! 桌面 facade 的请求/响应 DTO,以及 DTO 的轻量映射 impl。
2
3use super::helpers::parse_csv_items;
4use crate::domain::{
5    ContextBundle, MemoryScope, OutputFormat, RouteInput, TargetTool, WakeupPacket, WakeupProfile,
6};
7use crate::lifecycle_service::{LifecycleAction, LifecycleWorkbenchSnapshot};
8use crate::lifecycle_store::{
9    LedgerEntry, ProposeMemoryRequest, RecordMemoryRequest, TransitionMetadata,
10};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::path::PathBuf;
14use ts_rs::TS;
15
16// ---- Requests ----
17
18#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
19#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
20pub struct DesktopRouteRequest {
21    #[ts(type = "string")]
22    pub config_path: PathBuf,
23    #[ts(type = "string | null")]
24    pub vault_root_override: Option<PathBuf>,
25    #[ts(type = "string")]
26    pub cwd: PathBuf,
27    pub task: String,
28    pub files: Vec<String>,
29    pub target: TargetTool,
30    pub format: OutputFormat,
31}
32
33#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
34#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
35pub struct DesktopWakeupRequest {
36    #[ts(type = "string")]
37    pub config_path: PathBuf,
38    #[ts(type = "string | null")]
39    pub vault_root_override: Option<PathBuf>,
40    #[ts(type = "string")]
41    pub cwd: PathBuf,
42    pub task: String,
43    pub files: Vec<String>,
44    pub target: TargetTool,
45    pub profile: WakeupProfile,
46}
47
48#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
49#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
50pub struct DesktopPromptOptimizeRequest {
51    #[ts(type = "string")]
52    pub config_path: PathBuf,
53    #[ts(type = "string | null")]
54    pub vault_root_override: Option<PathBuf>,
55    #[ts(type = "string")]
56    pub cwd: PathBuf,
57    pub task: String,
58    pub files: Vec<String>,
59    pub target: TargetTool,
60    pub profile: WakeupProfile,
61    pub provider: Option<String>,
62    pub session_id: Option<String>,
63}
64
65#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
66#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
67pub struct DesktopWorkbenchRequest {
68    #[ts(type = "string")]
69    pub config_path: PathBuf,
70    pub daemon: Option<DesktopDaemonRequest>,
71}
72
73#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
74#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
75pub struct DesktopRecordLookupRequest {
76    #[ts(type = "string")]
77    pub config_path: PathBuf,
78    pub record_id: String,
79    pub daemon: Option<DesktopDaemonRequest>,
80}
81
82#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
83#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
84pub struct DesktopSessionBrowserRequest {
85    #[ts(type = "string")]
86    pub config_path: PathBuf,
87    pub page: usize,
88    pub per_page: usize,
89    pub provider: Option<String>,
90}
91
92#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
93#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
94pub struct DesktopSessionDetailRequest {
95    #[ts(type = "string")]
96    pub config_path: PathBuf,
97    pub session_id: String,
98    pub message_offset: Option<usize>,
99    pub message_limit: Option<usize>,
100}
101
102#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
103#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
104pub struct DesktopSessionActionRequest {
105    #[ts(type = "string")]
106    pub config_path: PathBuf,
107    pub session_id: String,
108}
109
110#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
111#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
112pub struct DesktopStatusRequest {
113    #[ts(type = "string")]
114    pub config_path: PathBuf,
115    #[ts(type = "string | null")]
116    pub vault_root_override: Option<PathBuf>,
117    #[ts(type = "string")]
118    pub cwd: PathBuf,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
122#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
123pub struct DesktopWikiLintRequest {
124    #[ts(type = "string")]
125    pub config_path: PathBuf,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
129#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
130pub struct DesktopWikiIndexRequest {
131    #[ts(type = "string")]
132    pub config_path: PathBuf,
133    #[serde(default)]
134    pub project_id: Option<String>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
138#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
139pub struct DesktopImportSessionRequest {
140    #[ts(type = "string")]
141    pub config_path: PathBuf,
142    pub provider: String,
143    pub session_id: String,
144    #[serde(default)]
145    pub apply: bool,
146    #[serde(default)]
147    pub actor: Option<String>,
148}
149
150pub type DesktopImportSessionResponse = crate::memory_importer::ImportSessionResponse;
151
152#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
153#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
154pub struct DesktopMemoryDraftRequest {
155    #[ts(type = "string")]
156    pub config_path: PathBuf,
157    pub title: String,
158    pub summary: String,
159    pub memory_type: String,
160    pub scope: MemoryScope,
161    pub source_ref: String,
162    pub project_id: Option<String>,
163    pub user_id: Option<String>,
164    pub sensitivity: Option<String>,
165    pub metadata: DesktopMetadataDto,
166    // Structured retrieval signals
167    #[serde(default)]
168    pub entities: Vec<String>,
169    #[serde(default)]
170    pub tags: Vec<String>,
171    #[serde(default)]
172    pub triggers: Vec<String>,
173    #[serde(default)]
174    pub related_files: Vec<String>,
175    #[serde(default)]
176    pub related_records: Vec<String>,
177    #[serde(default)]
178    pub supersedes: Option<String>,
179    #[serde(default)]
180    pub applies_to: Vec<String>,
181    #[serde(default)]
182    pub valid_until: Option<String>,
183}
184
185#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
186#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
187pub struct DesktopMemoryActionRequest {
188    #[ts(type = "string")]
189    pub config_path: PathBuf,
190    pub record_id: String,
191    pub action: DesktopLifecycleActionDto,
192    pub metadata: DesktopMetadataDto,
193}
194
195#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
196#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
197pub struct DesktopDaemonRequest {
198    pub enabled: bool,
199    #[ts(type = "string | null")]
200    pub daemon_bin: Option<PathBuf>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, TS)]
204#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
205pub struct DesktopMetadataDto {
206    pub actor: Option<String>,
207    pub reason: Option<String>,
208    pub evidence_refs: Vec<String>,
209}
210
211#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS)]
212#[serde(rename_all = "snake_case")]
213#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
214pub enum DesktopLifecycleActionDto {
215    Accept,
216    Promote,
217    Archive,
218}
219
220// ---- Responses ----
221
222#[derive(Debug, Clone, Serialize, TS)]
223#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
224pub struct DesktopContextResponse {
225    pub rendered: String,
226    pub explain: String,
227    pub used_format: OutputFormat,
228    #[ts(type = "string")]
229    pub used_vault_root: PathBuf,
230    pub bundle: ContextBundle,
231}
232
233#[derive(Debug, Clone, Serialize, TS)]
234#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
235pub struct DesktopWakeupResponse {
236    #[ts(type = "string")]
237    pub used_vault_root: PathBuf,
238    pub bundle: ContextBundle,
239    pub packet: WakeupPacket,
240}
241
242pub type DesktopPromptOptimizeResponse = crate::memory_gateway::PromptOptimizeResponse;
243
244#[derive(Debug, Clone, Serialize, TS)]
245#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
246pub struct DesktopWorkbenchResponse {
247    pub snapshot: LifecycleWorkbenchSnapshot,
248    #[ts(type = "unknown")]
249    pub payload: Value,
250}
251
252#[derive(Debug, Clone, Serialize, TS)]
253#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
254pub struct DesktopRecordResponse {
255    pub record: LedgerEntry,
256    #[ts(type = "unknown")]
257    pub payload: Value,
258    pub rendered: String,
259    pub available_actions: Vec<DesktopLifecycleActionDto>,
260}
261
262#[derive(Debug, Clone, Serialize, TS)]
263#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
264pub struct DesktopHistoryResponse {
265    pub record_id: String,
266    pub history: Vec<LedgerEntry>,
267    #[ts(type = "unknown")]
268    pub payload: Value,
269    pub rendered: String,
270}
271
272#[derive(Debug, Clone, Serialize, TS)]
273#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
274pub struct DesktopWriteResponse {
275    pub entry: LedgerEntry,
276    #[ts(type = "unknown")]
277    pub payload: Value,
278}
279
280#[derive(Debug, Clone, Serialize, TS)]
281#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
282pub struct DesktopSessionItem {
283    pub provider: String,
284    pub session_id: String,
285    pub title: String,
286    pub summary: Option<String>,
287    pub prompt_preview: Option<String>,
288    pub cwd: Option<String>,
289    pub source_path: Option<String>,
290    pub project_path: Option<String>,
291    pub updated_at: String,
292    pub record_count: usize,
293    pub pending_review_count: usize,
294    pub wakeup_ready_count: usize,
295    pub titles: Vec<String>,
296    pub memory_types: Vec<String>,
297}
298
299#[derive(Debug, Clone, Serialize, TS)]
300#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
301pub struct DesktopSessionBrowserResponse {
302    pub sessions: Vec<DesktopSessionItem>,
303    pub page: usize,
304    pub per_page: usize,
305    pub total: usize,
306    pub has_more: bool,
307}
308
309#[derive(Debug, Clone, Serialize, TS)]
310#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
311pub struct DesktopSessionMessage {
312    pub role: String,
313    pub timestamp: String,
314    pub content: String,
315    pub truncated: bool,
316}
317
318#[derive(Debug, Clone, Serialize, TS)]
319#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
320pub struct DesktopSessionDetailResponse {
321    pub session: DesktopSessionItem,
322    pub records: Vec<LedgerEntry>,
323    pub messages: Vec<DesktopSessionMessage>,
324    pub total_messages: usize,
325    pub showing_recent_messages: usize,
326    pub has_more_messages: bool,
327    pub latest_user_message: Option<String>,
328}
329
330#[derive(Debug, Clone, Serialize, TS)]
331#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
332pub struct DesktopSessionActionResponse {
333    pub session_id: String,
334    pub provider: String,
335    pub command: Option<String>,
336    pub message: String,
337}
338
339#[derive(Debug, Clone, Serialize, TS)]
340#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
341pub struct DesktopWikiLintResponse {
342    #[ts(type = "string")]
343    pub used_vault_root: PathBuf,
344    pub report: crate::wiki_lint::LintReport,
345    pub markdown: String,
346}
347
348#[derive(Debug, Clone, Serialize, TS)]
349#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
350pub struct DesktopWikiIndexResponse {
351    #[ts(type = "string")]
352    pub used_vault_root: PathBuf,
353    pub markdown: Option<String>,
354}
355
356// ---- DTO 映射 impl ----
357
358impl DesktopRouteRequest {
359    pub(super) fn to_route_input(&self) -> RouteInput {
360        RouteInput {
361            task: self.task.clone(),
362            cwd: self.cwd.clone(),
363            files: self.files.clone(),
364            target: self.target,
365            format: self.format,
366        }
367    }
368}
369
370impl DesktopWakeupRequest {
371    pub(super) fn to_route_input(&self) -> RouteInput {
372        RouteInput {
373            task: self.task.clone(),
374            cwd: self.cwd.clone(),
375            files: self.files.clone(),
376            target: self.target,
377            format: OutputFormat::Json,
378        }
379    }
380}
381
382impl DesktopPromptOptimizeRequest {
383    pub(super) fn to_route_input(&self) -> RouteInput {
384        RouteInput {
385            task: self.task.clone(),
386            cwd: self.cwd.clone(),
387            files: self.files.clone(),
388            target: self.target,
389            format: OutputFormat::Prompt,
390        }
391    }
392}
393
394impl DesktopMemoryDraftRequest {
395    pub(super) fn to_record_request(&self) -> RecordMemoryRequest {
396        RecordMemoryRequest {
397            title: self.title.clone(),
398            summary: self.summary.clone(),
399            memory_type: self.memory_type.clone(),
400            scope: self.scope,
401            source_ref: self.source_ref.clone(),
402            project_id: self.project_id.clone(),
403            user_id: self.user_id.clone(),
404            sensitivity: self.sensitivity.clone(),
405            metadata: self.metadata.clone().into_transition_metadata(),
406            entities: self.entities.clone(),
407            tags: self.tags.clone(),
408            triggers: self.triggers.clone(),
409            related_files: self.related_files.clone(),
410            related_records: self.related_records.clone(),
411            supersedes: self.supersedes.clone(),
412            applies_to: self.applies_to.clone(),
413            valid_until: self.valid_until.clone(),
414        }
415    }
416
417    pub(super) fn to_propose_request(&self) -> ProposeMemoryRequest {
418        ProposeMemoryRequest {
419            title: self.title.clone(),
420            summary: self.summary.clone(),
421            memory_type: self.memory_type.clone(),
422            scope: self.scope,
423            source_ref: self.source_ref.clone(),
424            project_id: self.project_id.clone(),
425            user_id: self.user_id.clone(),
426            sensitivity: self.sensitivity.clone(),
427            metadata: self.metadata.clone().into_transition_metadata(),
428            entities: self.entities.clone(),
429            tags: self.tags.clone(),
430            triggers: self.triggers.clone(),
431            related_files: self.related_files.clone(),
432            related_records: self.related_records.clone(),
433            supersedes: self.supersedes.clone(),
434            applies_to: self.applies_to.clone(),
435            valid_until: self.valid_until.clone(),
436        }
437    }
438}
439
440impl DesktopMetadataDto {
441    pub fn from_evidence_csv(value: &str) -> Self {
442        Self {
443            actor: None,
444            reason: None,
445            evidence_refs: parse_csv_items(value),
446        }
447    }
448
449    pub(super) fn into_transition_metadata(self) -> TransitionMetadata {
450        TransitionMetadata {
451            actor: self.actor,
452            reason: self.reason,
453            evidence_refs: self.evidence_refs,
454        }
455    }
456}
457
458impl DesktopLifecycleActionDto {
459    pub(super) fn from_lifecycle_action(action: LifecycleAction) -> Self {
460        match action {
461            LifecycleAction::Accept => Self::Accept,
462            LifecycleAction::PromoteToCanonical => Self::Promote,
463            LifecycleAction::Archive => Self::Archive,
464        }
465    }
466
467    pub(super) fn into_lifecycle_action(self) -> LifecycleAction {
468        match self {
469            Self::Accept => LifecycleAction::Accept,
470            Self::Promote => LifecycleAction::PromoteToCanonical,
471            Self::Archive => LifecycleAction::Archive,
472        }
473    }
474}