From b433611857af4761d8036cf5bf1436f0e95e3d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Thu, 3 Sep 2026 21:26:20 +0800 Subject: [PATCH] feat(hololake): expose typed runtime context and modules --- .../contracts/public-runtime-v1.json | 7 ++- .../scripts/audit-clean-v1.py | 6 +++ .../src-tauri/src/lib.rs | 2 +- .../src-tauri/src/realtime_bridge.rs | 44 ++++++++++++++++++- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/product-source/hololake-clean-desktop/contracts/public-runtime-v1.json b/product-source/hololake-clean-desktop/contracts/public-runtime-v1.json index e0e807e30..9b9141076 100644 --- a/product-source/hololake-clean-desktop/contracts/public-runtime-v1.json +++ b/product-source/hololake-clean-desktop/contracts/public-runtime-v1.json @@ -4,7 +4,12 @@ "protocol": "GLP_LOCAL_REALTIME/1", "transport": {"bind":"127.0.0.1","ports":"39281-39290","framing":"NDJSON","authentication":"LOCAL_0600_TOKEN","authority_from_transport":false}, "client_messages": ["hello", "persona_response", "external_ai_message", "tcs_proposal", "ping"], - "server_messages": ["welcome", "user_message", "proposal_pending", "proposal_rejected", "agent_receipt", "accepted", "pong", "error"], + "server_messages": ["welcome", "system_context", "user_message", "proposal_pending", "proposal_rejected", "agent_receipt", "accepted", "pong", "error"], + "system_context": { + "schema": "hololake.persona-visible-system-context/v1", + "contains": ["CHANNEL_ID_AND_NAME", "PERSONA_STATE", "MODULE_STATES", "ENTERPRISE_ENTRANCE_STATE", "PENDING_AGENT_PROPOSALS", "SOURCE_KINDS", "AUTHORITY_BOUNDARIES"], + "forbidden": ["TOKENS", "PRIVATE_KEYS", "RAW_MACHINE_FINGERPRINT", "PRIVATE_FILE_PATHS", "PRIVATE_CONTENT"] + }, "source_kinds": ["USER_MESSAGE", "PERSONA_RESPONSE", "EXTERNAL_AI_MESSAGE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT"], "persona_states": ["READY_NO_PERSONA", "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED", "LOCAL_TRIAL_UNVERIFIED_HOST_CONNECTED"], "registered_gir_operations": ["KNOWLEDGE.CREATE", "KNOWLEDGE.UPDATE", "KNOWLEDGE.DELETE", "KNOWLEDGE.READ", "KNOWLEDGE.LIST"], diff --git a/product-source/hololake-clean-desktop/scripts/audit-clean-v1.py b/product-source/hololake-clean-desktop/scripts/audit-clean-v1.py index 7b88ba576..87bf77335 100644 --- a/product-source/hololake-clean-desktop/scripts/audit-clean-v1.py +++ b/product-source/hololake-clean-desktop/scripts/audit-clean-v1.py @@ -49,6 +49,12 @@ if runtime_contract["transport"]["bind"] != "127.0.0.1": errors.append("realtime bridge is not loopback-only") if runtime_contract["mutation_gate"] != "PENDING_HUMAN_APPROVAL_TO_EXPLICIT_UI_APPROVAL_TO_EXECUTION": errors.append("agent human approval gate missing") +if "system_context" not in runtime_contract.get("server_messages", []): + errors.append("persona-visible system context missing from GLP contract") +realtime_source = (ROOT / "src-tauri/src/realtime_bridge.rs").read_text() +for required_context in ("sourceKinds", "pendingAgentProposals", "enterpriseEntrance", "proposalIsNotExecution"): + if required_context not in realtime_source: + errors.append(f"persona-visible system context field missing: {required_context}") baseline = json.loads((ROOT / "contracts/clean-v1-execution-baseline.json").read_text()) if baseline.get("anchor_manifest_sha256") != "7e130de78f91a32726e22e4be7d92acf4da966c8968ec359062aa8186c4243c2": errors.append("current direct-language anchor manifest drift") diff --git a/product-source/hololake-clean-desktop/src-tauri/src/lib.rs b/product-source/hololake-clean-desktop/src-tauri/src/lib.rs index 73714752a..537e36b04 100644 --- a/product-source/hololake-clean-desktop/src-tauri/src/lib.rs +++ b/product-source/hololake-clean-desktop/src-tauri/src/lib.rs @@ -14,7 +14,7 @@ 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 bundled_modules() -> Result, String> { +pub(crate) fn bundled_modules() -> Result, String> { let registry: serde_json::Value = serde_json::from_str(include_str!("../../registries/module-registry.json")) .map_err(|error| format!("MODULE_REGISTRY_INVALID: {error}"))?; diff --git a/product-source/hololake-clean-desktop/src-tauri/src/realtime_bridge.rs b/product-source/hololake-clean-desktop/src-tauri/src/realtime_bridge.rs index ecb037ed7..f9b313fa1 100644 --- a/product-source/hololake-clean-desktop/src-tauri/src/realtime_bridge.rs +++ b/product-source/hololake-clean-desktop/src-tauri/src/realtime_bridge.rs @@ -1,4 +1,4 @@ -use crate::{agent_executor, model::SourceKind, persona_runtime, storage}; +use crate::{agent_executor, enterprise_entrance, model::SourceKind, persona_runtime, storage}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; #[cfg(unix)] @@ -223,6 +223,47 @@ fn send(writer: &mut TcpStream, value: Value) -> Result<(), String> { writer.flush().map_err(|e| e.to_string()) } +fn system_context(app: &AppHandle) -> Result { + let channel = storage::channel(app)?; + let persona = persona_runtime::snapshot(app)?; + let enterprise = enterprise_entrance::snapshot(app)?; + let proposals = agent_executor::list(app)?; + let modules = crate::bundled_modules()?; + Ok(json!({ + "type": "system_context", + "schema": "hololake.persona-visible-system-context/v1", + "recordedAt": storage::now(), + "channel": channel.map(|value| json!({"channelId":value.channel_id,"name":value.name})), + "persona": { + "state": persona.state, + "activePersonaId": persona.active_persona_id, + "verifiedExistingPersonaCount": persona.verified_existing_persona_count + }, + "modules": modules.into_iter().map(|module| json!({ + "moduleId":module.module_id, + "nameZh":module.name_zh, + "audience":module.audience, + "state":module.state + })).collect::>(), + "enterpriseEntrance": { + "state": enterprise.state, + "serverAuthorized": enterprise.server_authorized, + "enterpriseServerEmbedded": enterprise.enterprise_server_embedded + }, + "pendingAgentProposals": proposals.into_iter() + .filter(|proposal| proposal.state == "PENDING_HUMAN_APPROVAL") + .map(|proposal| json!({"proposalId":proposal.proposal_id,"programId":proposal.gir.program_id,"state":proposal.state})) + .collect::>(), + "sourceKinds": ["USER_MESSAGE","PERSONA_RESPONSE","EXTERNAL_AI_MESSAGE","SYSTEM_CONTEXT","PROTOCOL_EVENT","AGENT_ACTION","TOOL_RESULT","SYSTEM_RECEIPT"], + "boundaries": { + "transportGrantsPersona": false, + "transportGrantsExecution": false, + "systemReceiptIsNotPersonaSpeech": true, + "proposalIsNotExecution": true + } + })) +} + fn handle_connection( app: AppHandle, state: RealtimeBridgeState, @@ -280,6 +321,7 @@ fn handle_connection( &mut writer, json!({"type":"welcome","protocol":PROTOCOL,"bridgeId":hello.bridge_id,"personaId":hello.persona_id,"personaState":if hello.persona_id.is_some(){"LOCAL_TRIAL_UNVERIFIED_HOST_CONNECTED"}else{"EXTERNAL_AI_CONNECTED_NO_PERSONA"},"executionAuthority":false}), )?; + send(&mut writer, system_context(&app)?)?; let (tx, rx) = mpsc::channel::(); state .clients