mod agent_executor; mod enterprise_entrance; mod model; mod persona_runtime; mod realtime_bridge; mod storage; use model::*; use serde::Serialize; use std::{fs, path::Path}; use tauri::{AppHandle, Manager, State}; use tauri_plugin_dialog::DialogExt; const UPDATE_ENDPOINT: &str = "https://guanghulab.com/hololake/releases/latest.json"; const PUBLIC_STATE: &str = "SIGNED_UPDATE_FEED_READY_NO_RELEASE"; fn receipt( app: &AppHandle, state: &str, message: &str, git: Option, event: TimelineEvent, ) -> Result { storage::append_event(app, &event)?; Ok(MutationReceipt { receipt_id: storage::id("HL-RCP"), state: state.into(), message: message.into(), git_commit: git, event, }) } #[tauri::command] fn system_snapshot(app: AppHandle) -> Result { Ok(SystemSnapshot { app_version: env!("CARGO_PKG_VERSION").into(), channel: storage::channel(&app)?, timeline: storage::events(&app)?, documents: storage::list_docs(&app)?, module_registry_version: "1".into(), public_update_endpoint: UPDATE_ENDPOINT.into(), public_distribution_state: PUBLIC_STATE.into(), external_ai_bridges: storage::bridges(&app)?, }) } #[tauri::command] fn create_channel(app: AppHandle, name: String) -> Result { let name = name.trim(); if name.is_empty() || name.chars().count() > 48 { return Err("CHANNEL_NAME_INVALID".into()); } if storage::channel(&app)?.is_some() { return Err("CHANNEL_ALREADY_EXISTS".into()); } let repo = storage::private_repo_root(&app)?; let c = Channel { channel_id: format!( "HL-CH-{}", uuid::Uuid::new_v4().simple().to_string()[..8].to_uppercase() ), name: name.into(), created_at: storage::now(), private_git_path: repo.to_string_lossy().into_owned(), public_distribution_state: PUBLIC_STATE.into(), }; storage::save_channel(&app, &c)?; let e = storage::event( SourceKind::SystemReceipt, "频道已建立", format!("{} 已登记;私人 Git 已初始化。", c.channel_id), "SUCCEEDED", ); storage::append_event(&app, &e)?; storage::git_commit( &repo, "chore(channel): initialize personal HoloLake channel", )?; Ok(c) } #[tauri::command] fn submit_user_message( app: AppHandle, realtime: State<'_, realtime_bridge::RealtimeBridgeState>, content: String, ) -> Result { let content = content.trim(); if content.is_empty() { return Err("EMPTY_USER_MESSAGE".into()); } if storage::channel(&app)?.is_none() { return Err("CHANNEL_NOT_INITIALIZED".into()); } let e = storage::event(SourceKind::UserMessage, "用户语言", content, "RECORDED"); storage::append_event(&app, &e)?; realtime.broadcast_value(serde_json::json!({"type":"user_message","event":e})); let waiting = storage::event( SourceKind::SystemReceipt, "系统回执", "用户语言已按 USER_MESSAGE 保存并实时发送到已连接的外部 AI;系统不伪造人格回应。", "WAITING", ); receipt( &app, "WAITING_PERSONA", "用户语言已记录并投递到实时桥。", None, waiting, ) } fn clean_title(title: &str) -> Result { let t = title.trim(); if t.is_empty() || t.chars().count() > 120 { return Err("KNOWLEDGE_TITLE_INVALID".into()); } Ok(t.chars() .map(|c| if "\\/:*?\"<>|".contains(c) { '-' } else { c }) .collect()) } #[tauri::command] fn read_knowledge_document(app: AppHandle, path: String) -> Result { storage::read_doc(&app, &path) } #[tauri::command] fn create_knowledge_document(app: AppHandle, title: String) -> Result { let title = clean_title(&title)?; let root = storage::knowledge_root(&app)?; let rel = format!("{}.md", title); let p = root.join("docs").join(&rel); if p.exists() { return Err("KNOWLEDGE_DOCUMENT_EXISTS".into()); } fs::write(&p, format!("# {}\n\n", title)).map_err(|e| e.to_string())?; let git = storage::git_commit(&root, "feat(knowledge): create document")?; let e = storage::event( SourceKind::AgentAction, "Agent 执行", format!("已创建知识页 {rel}"), "SUCCEEDED", ); receipt(&app, "SUCCEEDED", "知识页已创建。", git, e) } #[tauri::command] fn save_knowledge_document( app: AppHandle, path: String, body: String, ) -> Result { let rel = storage::safe_rel(&path)?; if rel.extension().and_then(|x| x.to_str()) != Some("md") { return Err("KNOWLEDGE_DOCUMENT_TYPE_REJECTED".into()); } let root = storage::knowledge_root(&app)?; let p = root.join("docs").join(rel); if !p.exists() { return Err("KNOWLEDGE_DOCUMENT_NOT_FOUND".into()); } fs::write(&p, body).map_err(|e| e.to_string())?; let git = storage::git_commit(&root, "docs(knowledge): save document")?; let e = storage::event( SourceKind::SystemReceipt, "系统回执", format!("{path} 已写入私人 Git。"), "SUCCEEDED", ); receipt(&app, "SUCCEEDED", "保存完成。", git, e) } #[tauri::command] fn delete_knowledge_document(app: AppHandle, path: String) -> Result { let rel = storage::safe_rel(&path)?; let root = storage::knowledge_root(&app)?; let from = root.join("docs").join(&rel); if !from.exists() { return Err("KNOWLEDGE_DOCUMENT_NOT_FOUND".into()); } let trash = root.join(".trash").join(format!( "{}-{}", storage::id("deleted"), rel.file_name() .and_then(|x| x.to_str()) .unwrap_or("document.md") )); fs::create_dir_all(trash.parent().unwrap()).map_err(|e| e.to_string())?; fs::rename(from, &trash).map_err(|e| e.to_string())?; let git = storage::git_commit(&root, "chore(knowledge): move document to trash")?; let e = storage::event( SourceKind::SystemReceipt, "系统回执", format!("{path} 已移入可恢复回收站。"), "SUCCEEDED", ); receipt(&app, "SUCCEEDED", "已移入回收站。", git, e) } fn copy_import(src: &Path, dst: &Path, count: &mut usize) -> Result<(), String> { if *count >= 1000 { return Err("IMPORT_FILE_LIMIT_EXCEEDED".into()); } for entry in fs::read_dir(src).map_err(|e| e.to_string())? { let entry = entry.map_err(|e| e.to_string())?; let p = entry.path(); if p.file_name() .and_then(|x| x.to_str()) .map(|x| x.starts_with('.')) .unwrap_or(false) { continue; } if p.is_dir() { let folder_name = clean_title( p.file_name() .and_then(|value| value.to_str()) .unwrap_or("未命名分类"), )?; let child = dst.join(folder_name); fs::create_dir_all(&child).map_err(|e| e.to_string())?; copy_import(&p, &child, count)?; continue; } let ext = p .extension() .and_then(|x| x.to_str()) .unwrap_or("") .to_lowercase(); if !["md", "txt", "markdown"].contains(&ext.as_str()) { continue; } let bytes = fs::read(&p).map_err(|e| e.to_string())?; if bytes.len() > 10 * 1024 * 1024 { continue; } let body = String::from_utf8_lossy(&bytes); let title = p.file_stem().and_then(|x| x.to_str()).unwrap_or("导入页面"); let name = format!("{}-{}.md", clean_title(title)?, &storage::sha(&bytes)[..8]); fs::write( dst.join(name), if ext == "md" || ext == "markdown" { body.into_owned() } else { format!("# {}\n\n{}", title, body) }, ) .map_err(|e| e.to_string())?; *count += 1 } Ok(()) } #[tauri::command] async fn import_knowledge_folder(app: AppHandle) -> Result, String> { let Some(folder) = app .dialog() .file() .set_title("导入到光湖知识库") .blocking_pick_folder() else { return Ok(None); }; let src = folder.into_path().map_err(|e| e.to_string())?; let root = storage::knowledge_root(&app)?; let source_name = clean_title( src.file_name() .and_then(|value| value.to_str()) .unwrap_or("外部知识库"), )?; let dst = root.join("docs").join("导入").join(source_name); fs::create_dir_all(&dst).map_err(|e| e.to_string())?; let mut count = 0; copy_import(&src, &dst, &mut count)?; let git = storage::git_commit(&root, "feat(knowledge): import external folder")?; let protocol = storage::event( SourceKind::ProtocolEvent, "协议运行", format!("导入边界校验完成;接受 Markdown/纯文本,共 {count} 页。原目录未改写。"), "SUCCEEDED", ); storage::append_event(&app, &protocol)?; let e = storage::event( SourceKind::SystemReceipt, "系统回执", format!("外部文件夹已转换为 {count} 个光湖知识页。"), "SUCCEEDED", ); Ok(Some(receipt( &app, "SUCCEEDED", "文件夹导入完成。", git, e, )?)) } #[tauri::command] async fn export_knowledge_document(app: AppHandle, path: String) -> Result, String> { let doc = storage::read_doc(&app, &path)?; let Some(target) = app .dialog() .file() .set_title("下载知识页") .set_file_name(&path) .blocking_save_file() else { return Ok(None); }; let p = target.into_path().map_err(|e| e.to_string())?; fs::write(&p, doc.body).map_err(|e| e.to_string())?; let e = storage::event( SourceKind::ToolResult, "工具结果", format!("知识页已下载到 {}", p.display()), "SUCCEEDED", ); storage::append_event(&app, &e)?; Ok(Some(p.to_string_lossy().into_owned())) } #[tauri::command] fn register_external_ai_bridge( app: AppHandle, display_name: String, ) -> Result { let name = display_name.trim(); if name.is_empty() || name.chars().count() > 64 { return Err("EXTERNAL_AI_NAME_INVALID".into()); } let bridge_id = format!( "HL-AI-{}", uuid::Uuid::new_v4().simple().to_string()[..8].to_uppercase() ); let root = storage::root(&app)?.join("external-ai").join(&bridge_id); let inbox = root.join("inbox"); let outbox = root.join("outbox"); fs::create_dir_all(&inbox).map_err(|e| e.to_string())?; fs::create_dir_all(&outbox).map_err(|e| e.to_string())?; let bridge = ExternalAiBridge { bridge_id, display_name: name.into(), inbox_path: inbox.to_string_lossy().into_owned(), outbox_path: outbox.to_string_lossy().into_owned(), state: "GLP_REALTIME_REGISTERED_NO_EXECUTION_AUTHORITY".into(), }; let mut all = storage::bridges(&app)?; all.push(bridge.clone()); storage::save_bridges(&app, &all)?; let e = storage::event( SourceKind::SystemReceipt, "外部 AI 入口已建立", format!( "{} 仅获得来源明确的语言收发目录,不获得人格与执行权限。", bridge.bridge_id ), "SUCCEEDED", ); storage::append_event(&app, &e)?; Ok(bridge) } fn ensure_default_external_ai_bridge(app: &AppHandle) -> Result<(), String> { let mut bridges = storage::bridges(app)?; if bridges .iter() .any(|bridge| bridge.bridge_id == "HLP-BRIDGE-LOCAL-DEFAULT") { return Ok(()); } let root = storage::root(app)? .join("external-ai") .join("HLP-BRIDGE-LOCAL-DEFAULT"); let inbox = root.join("inbox"); let outbox = root.join("outbox"); fs::create_dir_all(&inbox).map_err(|e| e.to_string())?; fs::create_dir_all(&outbox).map_err(|e| e.to_string())?; bridges.push(ExternalAiBridge { bridge_id: "HLP-BRIDGE-LOCAL-DEFAULT".into(), display_name: "本机编程 AI".into(), inbox_path: inbox.to_string_lossy().into_owned(), outbox_path: outbox.to_string_lossy().into_owned(), state: "GLP_REALTIME_REGISTERED_NO_EXECUTION_AUTHORITY".into(), }); storage::save_bridges(app, &bridges) } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct RuntimeOverview { realtime: realtime_bridge::RealtimeBridgeStatus, persona: persona_runtime::PersonaRuntimeSnapshot, proposals: Vec, } #[tauri::command] fn get_runtime_overview( app: AppHandle, realtime: State<'_, realtime_bridge::RealtimeBridgeState>, ) -> Result { Ok(RuntimeOverview { realtime: realtime.status(), persona: persona_runtime::snapshot(&app)?, proposals: agent_executor::list(&app)?, }) } #[tauri::command] fn get_realtime_invitation( app: AppHandle, realtime: State<'_, realtime_bridge::RealtimeBridgeState>, ) -> Result { realtime_bridge::invitation(&app, &realtime) } #[tauri::command] fn register_public_persona( app: AppHandle, display_name: String, ) -> Result { persona_runtime::register(&app, display_name) } #[tauri::command] fn delete_trial_persona( app: AppHandle, persona_id: String, exact_confirmation: String, ) -> Result { persona_runtime::delete_trial(&app, &persona_id, &exact_confirmation) } #[tauri::command] fn compile_tcs_agent_proposal( app: AppHandle, request: agent_executor::TcsCompileRequest, ) -> Result { agent_executor::queue(&app, request, "HOLOLAKE_LOCAL_UI".into(), None) } #[tauri::command] fn approve_agent_proposal( app: AppHandle, realtime: State<'_, realtime_bridge::RealtimeBridgeState>, proposal_id: String, ) -> Result { let receipt = agent_executor::approve(&app, &proposal_id)?; realtime.broadcast_value(serde_json::json!({"type":"agent_receipt","receipt":receipt})); Ok(receipt) } #[tauri::command] fn reject_agent_proposal( app: AppHandle, realtime: State<'_, realtime_bridge::RealtimeBridgeState>, proposal_id: String, ) -> Result { let proposal = agent_executor::reject(&app, &proposal_id)?; realtime .broadcast_value(serde_json::json!({"type":"proposal_rejected","proposalId":proposal_id})); Ok(proposal) } #[tauri::command] fn enterprise_entrance_snapshot( app: AppHandle, ) -> Result { enterprise_entrance::snapshot(&app) } #[tauri::command] fn prepare_enterprise_device( app: AppHandle, ) -> Result { enterprise_entrance::prepare_device(&app) } #[tauri::command] fn prepare_enterprise_binding( app: AppHandle, domain_id: String, responsible_human_id: String, persona_id: String, ) -> Result { enterprise_entrance::prepare_binding(&app, &domain_id, &responsible_human_id, &persona_id) } #[tauri::command] fn sign_enterprise_challenge( app: AppHandle, request_id: String, challenge_id: String, nonce: String, expires_unix_ms: u128, ) -> Result { enterprise_entrance::sign_challenge(&app, &request_id, &challenge_id, &nonce, expires_unix_ms) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .setup(|app| { let diagnostic = storage::root(app.handle()) .map_err(std::io::Error::other)? .join("startup-runtime-diagnostic.json"); storage::write_json( &diagnostic, &serde_json::json!({"state":"SETUP_ENTERED","recordedAt":storage::now()}), ) .map_err(std::io::Error::other)?; if std::env::var_os("HOLOLAKE_RUNTIME_TEST_ROOT").is_some() { if let Ok(channel_name) = std::env::var("HOLOLAKE_TEST_CHANNEL_NAME") { create_channel(app.handle().clone(), channel_name) .map_err(std::io::Error::other)?; } if let Ok(display_name) = std::env::var("HOLOLAKE_TEST_PERSONA_NAME") { persona_runtime::register(app.handle(), display_name) .map_err(std::io::Error::other)?; } } ensure_default_external_ai_bridge(app.handle()).map_err(std::io::Error::other)?; let state = match realtime_bridge::start(app.handle().clone()) { Ok(state) => state, Err(error) => { let _ = storage::write_json( &diagnostic, &serde_json::json!({"state":"LISTENER_FAILED","error":error.clone(),"recordedAt":storage::now()}), ); return Err(std::io::Error::other(error).into()); } }; storage::write_json( &diagnostic, &serde_json::json!({"state":"LISTENER_BOUND","status":state.status(),"recordedAt":storage::now()}), ) .map_err(std::io::Error::other)?; let invitation = realtime_bridge::invitation(app.handle(), &state).map_err(std::io::Error::other)?; storage::write_json( &storage::root(app.handle()) .map_err(std::io::Error::other)? .join("startup-runtime-receipt.json"), &serde_json::json!({ "schema":"hololake.startup-runtime-receipt/v1", "state":"GLP_LISTENING", "protocol":invitation.protocol, "endpoint":invitation.endpoint, "recordedAt":storage::now() }), ) .map_err(std::io::Error::other)?; app.manage(state); Ok(()) }) .invoke_handler(tauri::generate_handler![ system_snapshot, create_channel, submit_user_message, read_knowledge_document, create_knowledge_document, save_knowledge_document, delete_knowledge_document, import_knowledge_folder, export_knowledge_document, register_external_ai_bridge, get_runtime_overview, get_realtime_invitation, register_public_persona, delete_trial_persona, compile_tcs_agent_proposal, approve_agent_proposal, reject_agent_proposal ,enterprise_entrance_snapshot ,prepare_enterprise_device ,prepare_enterprise_binding ,sign_enterprise_challenge ]) .run(tauri::generate_context!()) .expect("HoloLake runtime failed") } #[cfg(test)] mod tests { use super::*; #[test] fn title_rejects_empty() { assert!(clean_title(" ").is_err()) } #[test] fn title_sanitizes_paths() { assert_eq!(clean_title("a/b").unwrap(), "a-b") } #[test] fn folder_import_preserves_classification_tree() { let source = tempfile::tempdir().unwrap(); let destination = tempfile::tempdir().unwrap(); let category = source.path().join("产品架构"); fs::create_dir_all(&category).unwrap(); fs::write(category.join("决定.txt"), "分类内容").unwrap(); let mut count = 0; copy_import(source.path(), destination.path(), &mut count).unwrap(); assert_eq!(count, 1); let imported = fs::read_dir(destination.path().join("产品架构")) .unwrap() .collect::, _>>() .unwrap(); assert_eq!(imported.len(), 1); assert_eq!( imported[0] .path() .extension() .and_then(|value| value.to_str()), Some("md") ); } }