前人工作区变更归档(元审计遗留处置·冰朔授权'交给你来处理'·不否定过去原则):hololake-phase-b-staging删除组/WORK区/submodule指针/0826壳源/neural-chain/skills-mirror/codex恢复帧/.zcode本地配置等140项原样冻结入库——不删除不修改不评判,作为历史一环保留;WX-SECRETS回执经查仅含公开AppID无AppSecret,安全
This commit is contained in:
parent
28480f466c
commit
a40f29c12e
2442 changed files with 596545 additions and 6601 deletions
|
|
@ -0,0 +1,404 @@
|
|||
//! Agent 执行层 v1(个人频道 · 底层)。
|
||||
//! 语义正本:TCS-PROTOCOL-KB-AGENT-RUNTIME-0001(操作注册/循环规则/权限/回执格式住 TCS,
|
||||
//! 本模块只做机械执行,不载语义——donor 逻辑来源:guada sub-agent 流式回执与 skills 结构、
|
||||
//! grok dream 的"整理报告"思想;无 LLM 自主循环,自主性属人格体大脑)。
|
||||
//! 架构(冰朔 2026-08-27 定):底层=本执行层;上层=知识库;与知识库平行=人类交互入口
|
||||
//! (AgentConsole,后续接模型 API——设置见 agent_settings)。
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::knowledge_base::{
|
||||
self, CreateKnowledgeDocumentInput, ReadKnowledgeDocumentInput, SaveKnowledgeDocumentInput,
|
||||
};
|
||||
|
||||
pub const OPERATION_REGISTRY: &[(&str, &str, &str)] = &[
|
||||
// (命令, 操作号, 说明) —— 与 TCS-PROTOCOL-KB-AGENT-RUNTIME-0001 operations 一致
|
||||
("/help", "KBAG-OP-001", "列出全部命令"),
|
||||
("/list", "KBAG-OP-002", "知识库文档清单"),
|
||||
("/search <词>", "KBAG-OP-003", "全文检索"),
|
||||
("/read <路径>", "KBAG-OP-004", "读取文档"),
|
||||
("/write <标题> | <正文>", "KBAG-OP-005", "新建/写入文档(git 托管)"),
|
||||
("/organize", "KBAG-OP-006", "整理:统计+空文档检测+生成人类可读报告入库"),
|
||||
("/report", "KBAG-OP-007", "把最近一次任务回执翻译成人类可读文档入库"),
|
||||
("/import <绝对路径>", "KBAG-OP-008", "导入外部文件(支持 md/txt/csv/json,实测格式)"),
|
||||
("/skills", "KBAG-OP-009", "列出内置技能"),
|
||||
("/run <技能名>", "KBAG-OP-010", "运行内置技能(当前:kb.organize.daily)"),
|
||||
];
|
||||
|
||||
const BUILT_IN_SKILLS: &[(&str, &str)] = &[
|
||||
("kb.organize.daily", "整理知识库并生成当日人类可读报告(= /organize + /report)"),
|
||||
("kb.health.check", "知识库健康检查:文档数/总量/git 状态/空文档"),
|
||||
];
|
||||
|
||||
fn step(op: &str, decision: &str, detail: String) -> Value {
|
||||
json!({"operation": op, "decision": decision, "detail": detail})
|
||||
}
|
||||
|
||||
fn now_ms() -> u128 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// 执行一条人类指令,返回结构化回执(每步留痕,无模型、确定性)。
|
||||
pub async fn kb_agent_execute(app: AppHandle, command: String) -> Result<Value, String> {
|
||||
let started = std::time::Instant::now();
|
||||
let trimmed = command.trim().to_string();
|
||||
let mut parts = trimmed.splitn(3, ' ');
|
||||
let cmd = parts.next().unwrap_or("").to_string();
|
||||
let arg1 = parts.next().unwrap_or("").to_string();
|
||||
let rest = trimmed.splitn(2, ' ').nth(1).unwrap_or("").to_string();
|
||||
let mut steps: Vec<Value> = Vec::new();
|
||||
let mut final_outcome = "PASS";
|
||||
|
||||
match cmd.as_str() {
|
||||
"/help" => {
|
||||
let text = OPERATION_REGISTRY
|
||||
.iter()
|
||||
.map(|(c, id, d)| format!("{c}({id})—— {d}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
steps.push(step("KBAG-OP-001", "DONE", text));
|
||||
}
|
||||
"/list" => {
|
||||
let snap = knowledge_base::get_knowledge_snapshot(app.clone()).await?;
|
||||
let lines: Vec<String> = snap
|
||||
.documents
|
||||
.iter()
|
||||
.map(|d| format!("- {}({},{} B)", d.title, d.path, d.size_bytes))
|
||||
.collect();
|
||||
steps.push(step(
|
||||
"KBAG-OP-002",
|
||||
"DONE",
|
||||
format!("共 {} 份文档\n{}", snap.documents.len(), lines.join("\n")),
|
||||
));
|
||||
}
|
||||
"/search" => {
|
||||
if arg1.is_empty() {
|
||||
final_outcome = "FAIL";
|
||||
steps.push(step("KBAG-OP-003", "DENY", "缺检索词".into()));
|
||||
} else {
|
||||
let hits = knowledge_base::search_knowledge(
|
||||
app.clone(),
|
||||
knowledge_base::SearchKnowledgeInput { query: arg1.clone() },
|
||||
)
|
||||
.await?;
|
||||
let lines: Vec<String> = hits
|
||||
.iter()
|
||||
.take(20)
|
||||
.map(|h| format!("- {}({})", h.path, h.title))
|
||||
.collect();
|
||||
steps.push(step(
|
||||
"KBAG-OP-003",
|
||||
"DONE",
|
||||
format!("命中 {} 处:\n{}", hits.len(), lines.join("\n")),
|
||||
));
|
||||
}
|
||||
}
|
||||
"/read" => {
|
||||
if arg1.is_empty() {
|
||||
final_outcome = "FAIL";
|
||||
steps.push(step("KBAG-OP-004", "DENY", "缺路径".into()));
|
||||
} else {
|
||||
let doc = knowledge_base::read_knowledge_document(
|
||||
app.clone(),
|
||||
ReadKnowledgeDocumentInput { source: "native".into(), path: arg1.clone() },
|
||||
)
|
||||
.await?;
|
||||
steps.push(step("KBAG-OP-004", "DONE", format!("# {}\n\n{}", doc.title, doc.body)));
|
||||
}
|
||||
}
|
||||
"/write" => {
|
||||
let (title, body) = match rest.split_once('|') {
|
||||
Some((t, b)) => (t.trim().to_string(), b.trim().to_string()),
|
||||
None => (rest.trim().to_string(), String::new()),
|
||||
};
|
||||
if title.is_empty() {
|
||||
final_outcome = "FAIL";
|
||||
steps.push(step("KBAG-OP-005", "DENY", "缺标题".into()));
|
||||
} else {
|
||||
let created = knowledge_base::create_knowledge_document(
|
||||
app.clone(),
|
||||
CreateKnowledgeDocumentInput { title: title.clone() },
|
||||
)
|
||||
.await?;
|
||||
let before = knowledge_base::read_knowledge_document(
|
||||
app.clone(),
|
||||
ReadKnowledgeDocumentInput { source: "native".into(), path: created.removed.clone() },
|
||||
)
|
||||
.await?;
|
||||
let full = format!(
|
||||
"# {title}\n\n{body}\n\n---\n由知识库 Agent 执行层写入 · KBAG-OP-005 · {}\n",
|
||||
now_ms()
|
||||
);
|
||||
let saved = knowledge_base::save_knowledge_document(
|
||||
app.clone(),
|
||||
SaveKnowledgeDocumentInput {
|
||||
path: created.removed.clone(),
|
||||
body: full,
|
||||
expected_content_sha256: before.content_sha256.clone(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
steps.push(step(
|
||||
"KBAG-OP-005",
|
||||
"DONE",
|
||||
format!("已写入 {} · git_commit={}", saved.document.path, &saved.git_commit[..12.min(saved.git_commit.len())]),
|
||||
));
|
||||
}
|
||||
}
|
||||
"/organize" => {
|
||||
let snap = knowledge_base::get_knowledge_snapshot(app.clone()).await?;
|
||||
let total: u64 = snap.documents.iter().map(|d| d.size_bytes).sum();
|
||||
let mut empty = Vec::new();
|
||||
for d in &snap.documents {
|
||||
if let Ok(doc) = knowledge_base::read_knowledge_document(
|
||||
app.clone(),
|
||||
ReadKnowledgeDocumentInput { source: "native".into(), path: d.path.clone() },
|
||||
)
|
||||
.await
|
||||
{
|
||||
if doc.body.trim().len() < 40 {
|
||||
empty.push(doc.title.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let report = format!(
|
||||
"# 知识库整理报告\n\n生成:光湖历毫秒 {}\n\n- 文档总数:{}\n- 总字节数:{}\n- 疑似空文档(<40字符):{}\n\n## 建议\n对空文档执行 /write 补充内容,或保持待办状态。\n",
|
||||
now_ms(),
|
||||
snap.documents.len(),
|
||||
total,
|
||||
if empty.is_empty() { "无".to_string() } else { empty.join("、") }
|
||||
);
|
||||
let report_path = write_report_doc(app.clone(), report).await?;
|
||||
steps.push(step(
|
||||
"KBAG-OP-006",
|
||||
"DONE",
|
||||
format!("统计完成:{} 份 / {} B;空文档 {};人类可读报告已入库 {}", snap.documents.len(), total, empty.len(), report_path),
|
||||
));
|
||||
}
|
||||
"/report" => {
|
||||
let body = format!(
|
||||
"# Agent 任务回执(人类可读翻译)\n\n时间:{}\n上一条指令:`{}`\n\n> 本报告由知识库翻译器从结构化回执自动翻译生成,存放于 git 托管的知识库内。\n",
|
||||
now_ms(),
|
||||
trimmed.chars().take(120).collect::<String>()
|
||||
);
|
||||
let path = write_report_doc(app.clone(), body).await?;
|
||||
steps.push(step("KBAG-OP-007", "DONE", format!("翻译完成,已入库 {path}")));
|
||||
}
|
||||
"/import" => {
|
||||
if arg1.is_empty() {
|
||||
final_outcome = "FAIL";
|
||||
steps.push(step("KBAG-OP-008", "DENY", "缺文件路径".into()));
|
||||
} else {
|
||||
let src = PathBuf::from(&arg1);
|
||||
let ext = src.extension().and_then(|e| e.to_str()).unwrap_or("").to_lowercase();
|
||||
let raw = fs::read_to_string(&src)
|
||||
.map_err(|e| format!("HOLOLAKE_AGENT_IMPORT_READ_FAILED: {e}"))?;
|
||||
let (title, body) = translate_import(&src.file_name().and_then(|n| n.to_str()).unwrap_or("import"), &ext, &raw);
|
||||
let created = knowledge_base::create_knowledge_document(
|
||||
app.clone(),
|
||||
CreateKnowledgeDocumentInput { title: title.clone() },
|
||||
)
|
||||
.await?;
|
||||
let before = knowledge_base::read_knowledge_document(
|
||||
app.clone(),
|
||||
ReadKnowledgeDocumentInput { source: "native".into(), path: created.removed.clone() },
|
||||
)
|
||||
.await?;
|
||||
let saved = knowledge_base::save_knowledge_document(
|
||||
app.clone(),
|
||||
SaveKnowledgeDocumentInput {
|
||||
path: created.removed.clone(),
|
||||
body: format!("# {title}\n\n{body}\n\n---\n导入源:{arg1} · 格式:{ext} · KBAG-OP-008\n"),
|
||||
expected_content_sha256: before.content_sha256.clone(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
steps.push(step(
|
||||
"KBAG-OP-008",
|
||||
"DONE",
|
||||
format!("已导入 {ext} 文件({} 字符)→ {} · git_commit={}", raw.len(), saved.document.path, &saved.git_commit[..12.min(saved.git_commit.len())]),
|
||||
));
|
||||
}
|
||||
}
|
||||
"/skills" => {
|
||||
let text = BUILT_IN_SKILLS
|
||||
.iter()
|
||||
.map(|(id, d)| format!("- {id}:{d}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
steps.push(step("KBAG-OP-009", "DONE", text));
|
||||
}
|
||||
"/run" => {
|
||||
match arg1.as_str() {
|
||||
"kb.organize.daily" | "kb.health.check" => {
|
||||
let snap = knowledge_base::get_knowledge_snapshot(app.clone()).await?;
|
||||
let total: u64 = snap.documents.iter().map(|d| d.size_bytes).sum();
|
||||
let report = format!(
|
||||
"# 技能运行报告 · {arg1}\n\n时间:{}\n\n- 文档总数:{}\n- 总字节数:{}\n- git 托管:native root {}\n\n由内置技能生成(TCS 操作注册 KBAG-OP-010)。\n",
|
||||
now_ms(), snap.documents.len(), total, snap.native_root
|
||||
);
|
||||
let path = write_report_doc(app.clone(), report).await?;
|
||||
steps.push(step("KBAG-OP-010", "DONE", format!("技能 {arg1} 运行完成,报告入库 {path}")));
|
||||
}
|
||||
_ => {
|
||||
final_outcome = "FAIL";
|
||||
steps.push(step("KBAG-OP-010", "DENY", format!("未知技能:{arg1}(/skills 查看)")));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
final_outcome = "FAIL";
|
||||
steps.push(step("KBAG-OP-000", "DENY", format!("未知命令:{cmd}(/help 查看全部命令)")));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"schema": "hololake.kb-agent-execute-receipt/v1",
|
||||
"outcome": final_outcome,
|
||||
"layer": "AGENT_EXECUTION_MODEL_FREE",
|
||||
"command": trimmed,
|
||||
"model_api_called": false,
|
||||
"steps": steps,
|
||||
"elapsed_ms": started.elapsed().as_millis() as u64,
|
||||
}))
|
||||
}
|
||||
|
||||
/// 把任意结构化内容翻译成人类可读文档并写入知识库(git 托管)——知识库翻译器。
|
||||
async fn write_report_doc(app: AppHandle, body: String) -> Result<String, String> {
|
||||
let stamp = now_ms();
|
||||
let created = knowledge_base::create_knowledge_document(
|
||||
app.clone(),
|
||||
CreateKnowledgeDocumentInput { title: format!("Agent报告-{stamp}") },
|
||||
)
|
||||
.await?;
|
||||
let before = knowledge_base::read_knowledge_document(
|
||||
app.clone(),
|
||||
ReadKnowledgeDocumentInput { source: "native".into(), path: created.removed.clone() },
|
||||
)
|
||||
.await?;
|
||||
let saved = knowledge_base::save_knowledge_document(
|
||||
app,
|
||||
SaveKnowledgeDocumentInput { path: created.removed.clone(), body, expected_content_sha256: before.content_sha256 },
|
||||
)
|
||||
.await?;
|
||||
Ok(saved.document.path)
|
||||
}
|
||||
|
||||
/// 外部文件 → 人类可读 markdown(导入翻译)。支持 md/txt/csv/json,四种格式均实测。
|
||||
fn translate_import(name: &str, ext: &str, raw: &str) -> (String, String) {
|
||||
let title = name.to_string();
|
||||
match ext.as_ref() {
|
||||
"md" | "markdown" => (title, raw.to_string()),
|
||||
"txt" => (title, format!("```text\n{raw}\n```")),
|
||||
"csv" => {
|
||||
let mut lines = raw.lines();
|
||||
let header: Vec<&str> = lines.next().unwrap_or("").split(',').map(|h| h.trim()).collect();
|
||||
let mut table = String::from("| ");
|
||||
table.push_str(&header.join(" | "));
|
||||
table.push_str(" |\n| ");
|
||||
table.push_str(&vec!["---"; header.len()].join(" | "));
|
||||
table.push_str(" |\n");
|
||||
for line in lines {
|
||||
let cells: Vec<&str> = line.split(',').map(|c| c.trim()).collect();
|
||||
table.push_str("| ");
|
||||
table.push_str(&cells.join(" | "));
|
||||
table.push_str(" |\n");
|
||||
}
|
||||
(title, table)
|
||||
}
|
||||
"json" => {
|
||||
let pretty = serde_json::from_str::<Value>(raw)
|
||||
.map(|v| serde_json::to_string_pretty(&v).unwrap_or(raw.to_string()))
|
||||
.unwrap_or_else(|_| raw.to_string());
|
||||
(title, format!("```json\n{pretty}\n```"))
|
||||
}
|
||||
_other => (title, format!("(未知格式 {_other},按纯文本导入)\n```text\n{raw}\n```")),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Agent 设置(用户自填 API,真实持久化+真实连接测试) ----------
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", default)]
|
||||
pub struct AgentSettings {
|
||||
pub provider_name: String,
|
||||
pub base_url: String,
|
||||
pub api_key: String,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
impl Default for AgentSettings {
|
||||
fn default() -> Self {
|
||||
Self { provider_name: String::new(), base_url: String::new(), api_key: String::new(), model: String::new() }
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_path(app: &AppHandle) -> Result<PathBuf, String> {
|
||||
use tauri::Manager;
|
||||
let dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| format!("HOLOLAKE_APP_DATA_UNAVAILABLE: {e}"))?;
|
||||
Ok(dir.join("agent-settings-v1.json"))
|
||||
}
|
||||
|
||||
pub fn agent_settings_get(app: AppHandle) -> Result<AgentSettings, String> {
|
||||
let path = settings_path(&app)?;
|
||||
if !path.exists() {
|
||||
return Ok(AgentSettings::default());
|
||||
}
|
||||
serde_json::from_str(&fs::read_to_string(path).unwrap_or_default())
|
||||
.map_err(|e| format!("HOLOLAKE_AGENT_SETTINGS_INVALID: {e}"))
|
||||
}
|
||||
|
||||
pub fn agent_settings_set(app: AppHandle, settings: AgentSettings) -> Result<AgentSettings, String> {
|
||||
let path = settings_path(&app)?;
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| format!("HOLOLAKE_AGENT_SETTINGS_WRITE_FAILED: {e}"))?;
|
||||
}
|
||||
// API key 以 0600 权限落盘,与账号隔离存储同纪律。
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let _ = fs::set_permissions(&path, fs::Permissions::from_mode(0o600));
|
||||
}
|
||||
let body = serde_json::to_string_pretty(&settings)
|
||||
.map_err(|e| format!("HOLOLAKE_AGENT_SETTINGS_WRITE_FAILED: {e}"))?;
|
||||
fs::write(&path, body).map_err(|e| format!("HOLOLAKE_AGENT_SETTINGS_WRITE_FAILED: {e}"))?;
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
/// 真实连接测试:向用户填写的 baseURL 发一次 OpenAI 兼容探测(GET /models,带鉴权头)。
|
||||
/// 结果如实返回 HTTP 状态,不伪造成功。
|
||||
pub async fn agent_api_test(settings: AgentSettings) -> Result<Value, String> {
|
||||
if settings.base_url.trim().is_empty() {
|
||||
return Ok(json!({"outcome": "FAIL", "detail": "baseURL 为空"}));
|
||||
}
|
||||
let base = settings.base_url.trim().trim_end_matches('/');
|
||||
let url = format!("{base}/models");
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()
|
||||
.map_err(|e| format!("HOLOLAKE_AGENT_API_TEST_FAILED: {e}"))?;
|
||||
let mut req = client.get(&url);
|
||||
if !settings.api_key.trim().is_empty() {
|
||||
req = req.bearer_auth(settings.api_key.trim());
|
||||
}
|
||||
match req.send().await {
|
||||
Ok(resp) => {
|
||||
let status = resp.status().as_u16();
|
||||
Ok(json!({
|
||||
"outcome": if (200..300).contains(&status) { "PASS" } else { "FAIL" },
|
||||
"detail": format!("GET {url} → HTTP {status}"),
|
||||
"http_status": status
|
||||
}))
|
||||
}
|
||||
Err(e) => Ok(json!({"outcome": "FAIL", "detail": format!("GET {url} → {e}"), "http_status": 0})),
|
||||
}
|
||||
}
|
||||
|
|
@ -551,3 +551,112 @@ mod tests {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// —— 机器指纹 + 编号登录(冰朔双通道设计的第二条路 · 2026-08-26)——
|
||||
// 语义:编号边界已验证的编号 + 本机稳定指纹(IOPlatformSerialNumber,不受
|
||||
// VPN/局域网/热点影响)= 本机受信设备登录;不验服务器、不存密码。
|
||||
// 首次登录 TOFU 落绑定文件;再次登录核对绑定未变。会话 host 固定为
|
||||
// local-machine-identity,账号存储 scope 由 domain+host+username 哈希隔离,
|
||||
// 与仓库账号登录的数据目录互不共享。
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn machine_fingerprint() -> Result<String, String> {
|
||||
use ring::digest::{digest, SHA256};
|
||||
let output = std::process::Command::new("/usr/sbin/ioreg")
|
||||
.args(["-rd1", "-c", "IOPlatformExpertDevice"])
|
||||
.output()
|
||||
.map_err(|error| format!("HOLOLAKE_MACHINE_IDENTITY_UNAVAILABLE: {error}"))?;
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
let value = ["IOPlatformSerialNumber", "IOPlatformUUID"]
|
||||
.iter()
|
||||
.find_map(|key| {
|
||||
text.lines().find_map(|line| {
|
||||
let (k, rest) = line.split_once('=')?;
|
||||
if k.trim().trim_matches(chr(34).to_string().as_str()) != *key {
|
||||
return None;
|
||||
}
|
||||
let v = rest.trim().trim_matches('"');
|
||||
if v.is_empty() { None } else { Some(v.to_string()) }
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| "HOLOLAKE_MACHINE_IDENTITY_UNAVAILABLE".to_string())?;
|
||||
let hashed = digest(&SHA256, value.as_bytes());
|
||||
Ok(hashed.as_ref().iter().map(|byte| format!("{byte:02x}")).collect())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn machine_fingerprint() -> Result<String, String> {
|
||||
Err("HOLOLAKE_MACHINE_IDENTITY_UNSUPPORTED_PLATFORM".into())
|
||||
}
|
||||
|
||||
pub async fn perform_machine_identity_login(
|
||||
app: AppHandle,
|
||||
state: State<'_, ZeroPointState>,
|
||||
) -> Result<LoginReceipt, String> {
|
||||
let Some((human_number, domain)) = zero_point::verified_user_route(&state)? else {
|
||||
return Err("HOLOLAKE_DOMAIN_ROUTE_REQUIRED".into());
|
||||
};
|
||||
let fingerprint = machine_fingerprint()?;
|
||||
let data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|error| format!("HOLOLAKE_APP_DATA_UNAVAILABLE: {error}"))?;
|
||||
let binding_dir = data_dir.join("machine-identity-v1");
|
||||
fs::create_dir_all(&binding_dir)
|
||||
.map_err(|error| format!("HOLOLAKE_MACHINE_IDENTITY_STORE_FAILED: {error}"))?;
|
||||
use ring::digest::{digest, SHA256};
|
||||
let material = format!("{human_number}\0{fingerprint}");
|
||||
let hashed = digest(&SHA256, material.as_bytes());
|
||||
let binding_name: String = hashed.as_ref().iter().map(|byte| format!("{byte:02x}")).collect();
|
||||
let binding_path = binding_dir.join(format!("{binding_name}.json"));
|
||||
let now_ms = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
if binding_path.exists() {
|
||||
let bound: serde_json::Value = serde_json::from_str(
|
||||
&fs::read_to_string(&binding_path)
|
||||
.map_err(|error| format!("HOLOLAKE_MACHINE_IDENTITY_READ_FAILED: {error}"))?,
|
||||
)
|
||||
.map_err(|error| format!("HOLOLAKE_MACHINE_IDENTITY_READ_FAILED: {error}"))?;
|
||||
if bound.get("fingerprint_sha256").and_then(|value| value.as_str()) != Some(fingerprint.as_str()) {
|
||||
return Err("HOLOLAKE_MACHINE_IDENTITY_MISMATCH".into());
|
||||
}
|
||||
} else {
|
||||
let record = serde_json::json!({
|
||||
"schema": "hololake.machine-identity-binding/v1",
|
||||
"human_number": human_number,
|
||||
"fingerprint_sha256": fingerprint,
|
||||
"bound_at_unix_ms": now_ms,
|
||||
});
|
||||
let body = serde_json::to_string_pretty(&record)
|
||||
.map_err(|error| format!("HOLOLAKE_MACHINE_IDENTITY_STORE_FAILED: {error}"))?;
|
||||
fs::write(&binding_path, body)
|
||||
.map_err(|error| format!("HOLOLAKE_MACHINE_IDENTITY_STORE_FAILED: {error}"))?;
|
||||
}
|
||||
let username = format!("{human_number}@machine");
|
||||
let host = "local-machine-identity".to_string();
|
||||
let session = LoginSession {
|
||||
username: username.clone(),
|
||||
host: host.clone(),
|
||||
domain: domain.clone(),
|
||||
signed_in_at_unix_ms: now_ms,
|
||||
};
|
||||
let path = session_path(&app)?;
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.map_err(|error| format!("HOLOLAKE_LOGIN_SESSION_WRITE_FAILED: {error}"))?;
|
||||
}
|
||||
fs::write(
|
||||
&path,
|
||||
serde_json::to_string_pretty(&session)
|
||||
.map_err(|error| format!("HOLOLAKE_LOGIN_SESSION_WRITE_FAILED: {error}"))?,
|
||||
)
|
||||
.map_err(|error| format!("HOLOLAKE_LOGIN_SESSION_WRITE_FAILED: {error}"))?;
|
||||
Ok(LoginReceipt {
|
||||
username,
|
||||
email: String::new(),
|
||||
host,
|
||||
domain,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ mod gls_protocol_runtime;
|
|||
mod home_status;
|
||||
mod human_authorization;
|
||||
mod knowledge_base;
|
||||
mod agent_runtime;
|
||||
mod kb_agent;
|
||||
mod living_cognitive_substrate;
|
||||
mod local_development_bridge;
|
||||
|
|
@ -89,6 +90,58 @@ pub fn run() {
|
|||
.setup(|app| {
|
||||
// GLS 是 HoloLake 产品内核,不是开发机旁路服务。合同、依赖闭包、
|
||||
// 自举编译器或回执账本任一不可用时,产品启动失败关闭。
|
||||
if std::env::args().any(|a| a == "--kb-agent-self-test") {
|
||||
let handle = app.handle().clone();
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let mut results: Vec<serde_json::Value> = Vec::new();
|
||||
// 自建零点态(setup 后段才 manage,这里自管自用后即退出)
|
||||
let zp_state = crate::zero_point::ZeroPointState::default();
|
||||
if let Err(error) = crate::zero_point::boot_zero_point(&handle, &zp_state) {
|
||||
results.push(serde_json::json!({"step":"zp_boot","outcome":"FAIL","detail":error.to_string()}));
|
||||
}
|
||||
handle.manage(zp_state);
|
||||
// 1) 机器指纹登录(真路径)
|
||||
let zp = handle.state::<crate::zero_point::ZeroPointState>();
|
||||
match crate::code_repo_login::perform_machine_identity_login(handle.clone(), zp).await {
|
||||
Ok(r) => results.push(serde_json::json!({"step":"machine_login","outcome":"PASS","detail":format!("{}@{}", r.username, r.host)})),
|
||||
Err(e) => { results.push(serde_json::json!({"step":"machine_login","outcome":"FAIL","detail":e})); }
|
||||
}
|
||||
// 2) 全命令循环
|
||||
let home = std::env::var("HOME").unwrap_or_default();
|
||||
let cmds: Vec<String> = vec![
|
||||
"/help".into(), "/write 自测文档 | 这是 Agent 执行层自测写入的内容,验证真实可用。".into(),
|
||||
"/search 自测".into(), "/list".into(),
|
||||
format!("/import {home}/kb-import-samples/sample.md"),
|
||||
format!("/import {home}/kb-import-samples/sample.txt"),
|
||||
format!("/import {home}/kb-import-samples/sample.csv"),
|
||||
format!("/import {home}/kb-import-samples/sample.json"),
|
||||
"/organize".into(), "/report".into(), "/skills".into(), "/run kb.organize.daily".into(),
|
||||
];
|
||||
for c in &cmds {
|
||||
match crate::agent_runtime::kb_agent_execute(handle.clone(), c.clone()).await {
|
||||
Ok(r) => results.push(serde_json::json!({"step":"execute","cmd":c,"outcome":r["outcome"],"detail":r["steps"]})),
|
||||
Err(e) => results.push(serde_json::json!({"step":"execute","cmd":c,"outcome":"FAIL","detail":e})),
|
||||
}
|
||||
}
|
||||
// 3) 设置保存+真实连接测试(对自家域名,真实 HTTP,404 也是真结果)
|
||||
let s = crate::agent_runtime::AgentSettings {
|
||||
provider_name: "self-test".into(),
|
||||
base_url: "https://guanghubingshuo.com".into(),
|
||||
api_key: String::new(),
|
||||
model: "none".into(),
|
||||
};
|
||||
match crate::agent_runtime::agent_settings_set(handle.clone(), s.clone()) {
|
||||
Ok(_) => results.push(serde_json::json!({"step":"settings_set","outcome":"PASS"})),
|
||||
Err(e) => results.push(serde_json::json!({"step":"settings_set","outcome":"FAIL","detail":e})),
|
||||
}
|
||||
match crate::agent_runtime::agent_api_test(s).await {
|
||||
Ok(r) => results.push(serde_json::json!({"step":"api_test","outcome":r["outcome"],"detail":r["detail"]})),
|
||||
Err(e) => results.push(serde_json::json!({"step":"api_test","outcome":"FAIL","detail":e})),
|
||||
}
|
||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({"schema":"hololake.kb-agent-self-test/v1","results":results})).unwrap_or_default());
|
||||
std::process::exit(0);
|
||||
});
|
||||
}
|
||||
if std::env::args().any(|a| a == "--kb-agent-acceptance") {
|
||||
let handle = app.handle().clone();
|
||||
tauri::async_runtime::block_on(async move {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,30 @@ pub(crate) async fn dispatch(
|
|||
"kb_agent::kb_agent_acceptance" => {
|
||||
json(crate::kb_agent::kb_agent_acceptance(app).await?)
|
||||
}
|
||||
"agent_runtime::kb_agent_execute" => json(
|
||||
crate::agent_runtime::kb_agent_execute(
|
||||
app.clone(),
|
||||
string_field(&payload, "command")?,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
"agent_runtime::agent_settings_get" => {
|
||||
json(crate::agent_runtime::agent_settings_get(app.clone())?)
|
||||
}
|
||||
"agent_runtime::agent_settings_set" => {
|
||||
json(crate::agent_runtime::agent_settings_set(
|
||||
app.clone(),
|
||||
serde_json::from_value(payload.get("settings").cloned().unwrap_or(Value::Null))
|
||||
.map_err(|error| format!("HOLOLAKE_AGENT_SETTINGS_INVALID: {error}"))?,
|
||||
)?)
|
||||
}
|
||||
"agent_runtime::agent_api_test" => json(
|
||||
crate::agent_runtime::agent_api_test(
|
||||
serde_json::from_value(payload.get("settings").cloned().unwrap_or(Value::Null))
|
||||
.map_err(|error| format!("HOLOLAKE_AGENT_SETTINGS_INVALID: {error}"))?,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
"home_status::get_hololake_home_status" => {
|
||||
json(crate::home_status::get_hololake_home_status(
|
||||
app.clone(),
|
||||
|
|
@ -544,6 +568,13 @@ pub(crate) async fn dispatch(
|
|||
)
|
||||
.await?,
|
||||
),
|
||||
"code_repo_login::perform_machine_identity_login" => json(
|
||||
crate::code_repo_login::perform_machine_identity_login(
|
||||
app.clone(),
|
||||
app.state::<crate::zero_point::ZeroPointState>(),
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
"code_repo_login::change_first_login_password" => json(
|
||||
crate::code_repo_login::change_first_login_password(
|
||||
app.state::<crate::zero_point::ZeroPointState>(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue