Skip to main content

qualia_client_core/api/
jobs.rs

1//! Local job scheduler
2
3#![allow(non_snake_case)]
4
5use super::*;
6
7use std::path::Path;
8
9/// Schedule one agent turn as a background job (queued, off the chat thread). Routed local-first; a
10/// remote-MCP agent's turn is sent out over MCP. Returns the created job as JSON.
11pub fn schedule_agent_job(
12    session_id: String,
13    agent_slug: Option<String>,
14    prompt: String,
15) -> Result<serde_json::Value, String> {
16    let agent_updated_at_unix = if let Some(slug) = agent_slug.as_deref() {
17        let state = crate::state::APP_STATE
18            .get()
19            .ok_or("Application not initialized")?;
20        let storage = state
21            .config
22            .lock()
23            .map_err(|error| error.to_string())?
24            .storage_path
25            .clone();
26        Some(
27            crate::agent_registry::get_agent(Path::new(&storage), slug)
28                .ok_or_else(|| format!("unknown agent @{slug}"))?
29                .updated_at_unix,
30        )
31    } else {
32        None
33    };
34    let job = crate::local_job_scheduler::LocalJobScheduler::global().enqueue(
35        crate::local_job_scheduler::LocalJobKind::AgentTurn {
36            session_id,
37            agent_slug,
38            agent_updated_at_unix,
39            prompt,
40        },
41    )?;
42    serde_json::to_value(job).map_err(|e| e.to_string())
43}
44
45/// Snapshot of the local job queue (jobs + status counts).
46pub fn list_local_jobs() -> Result<serde_json::Value, String> {
47    let snap = crate::local_job_scheduler::LocalJobScheduler::global().snapshot()?;
48    serde_json::to_value(snap).map_err(|e| e.to_string())
49}
50
51/// Cancel a job by id (queued → cancelled; running → cooperative cancel).
52pub fn cancel_local_job(id: String) -> Result<bool, String> {
53    crate::local_job_scheduler::LocalJobScheduler::global().cancel(&id)
54}
55
56/// Re-run a finished job with the same bounded inputs.
57pub fn retry_local_job(id: String) -> Result<serde_json::Value, String> {
58    let job = crate::local_job_scheduler::LocalJobScheduler::global().retry(&id)?;
59    serde_json::to_value(job).map_err(|e| e.to_string())
60}
61
62/// Clear completed/failed/cancelled history without affecting active work.
63pub fn clear_finished_local_jobs() -> Result<usize, String> {
64    crate::local_job_scheduler::LocalJobScheduler::global().clear_finished()
65}
66
67pub fn schedule_model_download(
68    url: String,
69    filename: String,
70    model_id: String,
71) -> Result<serde_json::Value, String> {
72    let job = crate::local_job_scheduler::LocalJobScheduler::global().enqueue(
73        crate::local_job_scheduler::LocalJobKind::ModelDownload {
74            url,
75            filename,
76            model_id,
77        },
78    )?;
79    serde_json::to_value(job).map_err(|e| e.to_string())
80}
81
82pub fn schedule_model_activation(model_name: String) -> Result<serde_json::Value, String> {
83    let job = crate::local_job_scheduler::LocalJobScheduler::global()
84        .enqueue(crate::local_job_scheduler::LocalJobKind::ModelActivation { model_name })?;
85    serde_json::to_value(job).map_err(|e| e.to_string())
86}
87
88pub fn schedule_anatomy_asset_acquire(model: String) -> Result<serde_json::Value, String> {
89    // Validate before queueing so typo failures are immediate and visible at the initiating control.
90    crate::wellfair::api::parse_anatomy_model(&model)?;
91    let job = crate::local_job_scheduler::LocalJobScheduler::global()
92        .enqueue(crate::local_job_scheduler::LocalJobKind::AnatomyAssetAcquire { model })?;
93    serde_json::to_value(job).map_err(|e| e.to_string())
94}
95
96/// Enqueue a job for a specific apparatus (`did:q42:device:…`). Empty target → this install.
97/// Remote devices fail closed until multi-device dispatch is live.
98pub fn schedule_job_on_device(
99    kind_json: String,
100    target_device_id: Option<String>,
101) -> Result<serde_json::Value, String> {
102    let kind: crate::local_job_scheduler::LocalJobKind =
103        serde_json::from_str(&kind_json).map_err(|e| format!("invalid job kind: {e}"))?;
104    let job = crate::local_job_scheduler::LocalJobScheduler::global()
105        .enqueue_for_device(kind, target_device_id)?;
106    serde_json::to_value(job).map_err(|e| e.to_string())
107}
108
109pub fn ensure_chat_session() -> Result<String, String> {
110    if let Some(id) = get_last_chat_session_id() {
111        let state = crate::state::APP_STATE.get().unwrap();
112        let storage = state.config.lock().unwrap().storage_path.clone();
113        if crate::chat_session::load_session(Path::new(&storage), &id).is_ok() {
114            return Ok(id);
115        }
116    }
117    create_chat_session(None)
118}
119
120pub fn create_group_chat_session(
121    title: Option<String>,
122    participant_dids: Vec<String>,
123) -> Result<String, String> {
124    let state = crate::state::APP_STATE.get().unwrap();
125    let storage = state.config.lock().unwrap().storage_path.clone();
126    crate::chat_session::create_group_session(Path::new(&storage), title, &participant_dids)
127        .map_err(|e| e.to_string())
128}
129
130pub fn add_chat_participant(
131    session_id: String,
132    participant_did: String,
133) -> Result<serde_json::Value, String> {
134    let state = crate::state::APP_STATE.get().unwrap();
135    let storage = state.config.lock().unwrap().storage_path.clone();
136    let participants =
137        crate::chat_session::add_participant(Path::new(&storage), &session_id, &participant_did)
138            .map_err(|e| e.to_string())?;
139    serde_json::to_value(participants).map_err(|e| e.to_string())
140}
141
142pub fn remove_chat_participant(
143    session_id: String,
144    participant_did: String,
145) -> Result<serde_json::Value, String> {
146    let state = crate::state::APP_STATE.get().unwrap();
147    let storage = state.config.lock().unwrap().storage_path.clone();
148    let participants =
149        crate::chat_session::remove_participant(Path::new(&storage), &session_id, &participant_did)
150            .map_err(|e| e.to_string())?;
151    serde_json::to_value(participants).map_err(|e| e.to_string())
152}
153
154pub fn get_chat_participants(session_id: String) -> Result<serde_json::Value, String> {
155    let state = crate::state::APP_STATE.get().unwrap();
156    let storage = state.config.lock().unwrap().storage_path.clone();
157    let participants = crate::chat_session::get_participants(Path::new(&storage), &session_id)
158        .map_err(|e| e.to_string())?;
159    serde_json::to_value(participants).map_err(|e| e.to_string())
160}
161
162pub fn get_local_agent_config(session_id: String) -> Result<serde_json::Value, String> {
163    let state = crate::state::APP_STATE.get().unwrap();
164    let storage = state.config.lock().unwrap().storage_path.clone();
165    let cfg = crate::chat_agents::load_local_agent_config(Path::new(&storage), &session_id)?;
166    serde_json::to_value(cfg).map_err(|e| e.to_string())
167}
168
169pub fn update_agent_outcome_sharing(
170    session_id: String,
171    policy_json: String,
172) -> Result<serde_json::Value, String> {
173    let policy: crate::chat_agents::OutcomeSharingPolicy =
174        serde_json::from_str(&policy_json).map_err(|e| e.to_string())?;
175    let state = crate::state::APP_STATE.get().unwrap();
176    let storage = state.config.lock().unwrap().storage_path.clone();
177    let cfg = crate::chat_agents::update_outcome_sharing(Path::new(&storage), &session_id, policy)?;
178    serde_json::to_value(cfg).map_err(|e| e.to_string())
179}
180
181pub fn get_default_outcome_sharing(session_id: String) -> Result<serde_json::Value, String> {
182    let state = crate::state::APP_STATE.get().unwrap();
183    let storage = state.config.lock().unwrap().storage_path.clone();
184    let session = crate::chat_session::load_session(Path::new(&storage), &session_id)
185        .map_err(|e| e.to_string())?;
186    let policy = crate::chat_agents::default_outcome_sharing(session.meta.session_kind);
187    serde_json::to_value(policy).map_err(|e| e.to_string())
188}