feat(hololake): expose typed runtime context and modules

This commit is contained in:
冰朔 2026-09-03 21:26:20 +08:00
commit b433611857
4 changed files with 56 additions and 3 deletions

View file

@ -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"],

View file

@ -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")

View file

@ -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<Vec<ModuleRecord>, String> {
pub(crate) fn bundled_modules() -> Result<Vec<ModuleRecord>, String> {
let registry: serde_json::Value =
serde_json::from_str(include_str!("../../registries/module-registry.json"))
.map_err(|error| format!("MODULE_REGISTRY_INVALID: {error}"))?;

View file

@ -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<Value, String> {
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::<Vec<_>>(),
"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::<Vec<_>>(),
"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::<String>();
state
.clients