1use crate::Session;
19use sioul_core::consent::Consent;
20use sioul_core::links::{self, Kind, LocalLinks, Loaded, World};
21use sioul_core::mailindex::MailIndex;
22use sioul_core::tasks::Task;
23
24pub const KEPT: &str = "(kept from agents)";
26
27pub fn of(s: &Session, loaded: &Loaded) -> Consent {
32 let gate = sioul_core::porch::Gate::load(&s.config, jiff::Timestamp::now().as_second());
33 let mut consent = Consent::of(loaded, s.config.mcp.outside_projects, Some(std::sync::Arc::new(gate)));
34 consent.keep_accounts(kept_accounts(&s.config), &loaded.mail);
35 consent
36}
37
38pub fn kept_accounts(config: &sioul_core::config::Config) -> Vec<(String, std::path::PathBuf)> {
42 if config.mcp.work_accounts {
43 return Vec::new();
44 }
45 config.every_account().filter(|a| a.is_microsoft_work()).map(|a| (a.id.clone(), a.maildir_path())).collect()
46}
47
48pub fn for_mail(s: &Session, index: MailIndex) -> (Consent, MailIndex) {
51 let projects = s.config.notes_root_path().and_then(|root| sioul_core::projects::ProjectStore::load(&root).ok()).map(|store| store.projects).unwrap_or_default();
52 let loaded = Loaded { projects, mail: index, local: LocalLinks::load(&LocalLinks::default_path()), ..Loaded::default() };
53 let consent = of(s, &loaded);
54 (consent, loaded.mail)
55}
56
57pub fn left_out(count: usize) -> Option<String> {
59 (count > 0).then(|| {
60 let things = if count == 1 { "1 more thing belongs".to_string() } else { format!("{count} more things belong") };
61 format!("({things} to projects the person keeps from agents, to no project while those are kept from agents, or to a work or school account kept from agents: left out on purpose. Do not guess what they are; ask the person if it matters.)")
62 })
63}
64
65pub fn kept(what: &str) -> String {
67 format!(
68 "{what} is kept from agents: it belongs to a project the person has not opened to agents, to no project while things outside projects are closed to them, or to a work or school account kept from agents. Sioul neither reads nor writes it for an agent. The person opens a project on its page in Sioul, or in Settings ▸ AI agents, where work and school accounts are opened too."
69 )
70}
71
72pub fn closed_project(id: &str) -> String {
74 format!("The project “{}” is closed to agents: Sioul neither reads it nor writes into it for an agent. The person opens it on its page in Sioul, or in Settings ▸ AI agents.", crate::one_line(id))
75}
76
77pub const OUTSIDE_CLOSED: &str = "Things in no project are closed to agents (Settings ▸ AI agents, “Things in no project”): Sioul writes nothing outside a project for an agent. Give a project open to agents (list_projects lists them), or ask the person.";
79
80pub fn allows_mail(consent: &Consent, world: &World, uri: &str) -> bool {
82 let id = links::id_of(&world.canonical(uri));
83 let card = world.mail.get(uri).and_then(|m| sioul_core::maildir::read_one(&m.path));
84 consent.allows_mail(Some(&id), card.as_ref())
85}
86
87pub fn allows(consent: &Consent, world: &World, uri: &str) -> bool {
89 let uri = world.canonical(uri);
90 match links::kind_of(&uri) {
91 Kind::Mail => allows_mail(consent, world, &uri),
92 _ => consent.allows(&uri),
93 }
94}
95
96pub fn redact(loaded: &mut Loaded, consent: &Consent) {
100 for task in loaded.tasks.iter_mut().filter(|t| !consent.allows(&links::task_uri(&t.uid))) {
101 *task = Task { title: KEPT.into(), notes: String::new(), location: String::new(), categories: Vec::new(), links: Vec::new(), contacts: Vec::new(), ..task.clone() };
102 }
103 for project in loaded.projects.iter_mut().filter(|c| !consent.is_open(&c.id)) {
104 project.title = KEPT.into();
105 project.client = None;
106 project.files.clear();
107 project.routes.clear();
108 }
109}