feat: embed cross-platform HoloLake terminal link

This commit is contained in:
冰朔 2026-08-17 18:59:37 +08:00
commit ae7f883891
16 changed files with 808 additions and 122 deletions

View file

@ -789,6 +789,12 @@ dependencies = [
"syn 2.0.115",
]
[[package]]
name = "doctest-file"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359"
[[package]]
name = "document-features"
version = "0.2.12"
@ -1492,6 +1498,7 @@ dependencies = [
"dirs",
"fs2",
"futures-util",
"interprocess",
"reqwest",
"ring",
"rusqlite",
@ -1507,6 +1514,7 @@ dependencies = [
"tokio",
"url",
"uuid",
"widestring",
]
[[package]]
@ -1804,6 +1812,19 @@ dependencies = [
"cfb",
]
[[package]]
name = "interprocess"
version = "2.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "798de1433ba514cc6c04c4144c2469af81396e4906195218737c776d47769572"
dependencies = [
"doctest-file",
"libc",
"recvmsg",
"widestring",
"windows-sys 0.61.2",
]
[[package]]
name = "ipnet"
version = "2.12.1"
@ -3022,7 +3043,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -3159,6 +3180,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "recvmsg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
[[package]]
name = "redox_syscall"
version = "0.5.18"
@ -5154,6 +5181,12 @@ dependencies = [
"windows-core 0.61.2",
]
[[package]]
name = "widestring"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -17,6 +17,7 @@ tauri-build = { version = "2.5.4", features = [] }
[dependencies]
dirs = "6"
fs2 = "0.4"
interprocess = "2.4.2"
ring = "0.17"
rusqlite = { version = "0.31", features = ["bundled"] }
base64 = "0.22"
@ -33,5 +34,8 @@ reqwest = { version = "0.13.2", default-features = false, features = ["cookies",
tokio = { version = "1", features = ["time"] }
futures-util = "0.3"
[target.'cfg(windows)'.dependencies]
widestring = "1"
[dev-dependencies]
tempfile = "3"

View file

@ -1,9 +1,9 @@
use crate::circular_lake_membrane::{receive_at as receive_language_at, ReceiveLanguageInput};
use crate::direct_local_session::{
append_event_at, authenticate_context_at, authenticate_privileged_at, direct_session_root,
issue_ticket_at, open_at, resume_at, AppendSessionEventInput, AuthenticateSessionInput,
AuthenticatedSessionContext, DirectSessionReceipt, IssueDiscoveryTicketInput, OpenSessionInput,
ResumeSessionInput,
heartbeat_at, issue_ticket_at, open_at, resume_at, AppendSessionEventInput,
AuthenticateSessionInput, AuthenticatedSessionContext, DirectSessionReceipt,
IssueDiscoveryTicketInput, OpenSessionInput, ResumeSessionInput,
};
use crate::dynamic_capability_routing::{
install_trusted_registry_at, record_health_at, resolve_at as resolve_capability_route_at,
@ -29,13 +29,23 @@ use crate::pncc_remote_git::{
use crate::pncc_repository_binding::{
inspect_mounted_at as inspect_mounted_pncc_at, InspectMountedPnccRepositoryInput,
};
#[cfg(unix)]
use interprocess::local_socket::GenericFilePath;
#[cfg(windows)]
use interprocess::local_socket::GenericNamespaced;
use interprocess::local_socket::{prelude::*, ListenerNonblockingMode, ListenerOptions};
#[cfg(windows)]
use interprocess::os::windows::{
local_socket::ListenerOptionsExt, security_descriptor::SecurityDescriptor,
};
use interprocess::TryClone;
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs::{self, OpenOptions};
use std::io::{self, BufRead, BufReader, Read, Write};
#[cfg(unix)]
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::{Path, PathBuf};
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
@ -45,10 +55,26 @@ use std::thread;
use std::time::Duration;
use tauri::{AppHandle, Manager};
use uuid::Uuid;
#[cfg(windows)]
use widestring::U16CString;
#[cfg(windows)]
trait OpenOptionsModeExt {
fn mode(&mut self, mode: u32) -> &mut Self;
}
#[cfg(windows)]
impl OpenOptionsModeExt for OpenOptions {
fn mode(&mut self, _mode: u32) -> &mut Self {
self
}
}
const BROKER_SCHEMA: &str = "hololake.direct-local-broker/v1";
const MAX_REQUEST_BYTES: u64 = 128 * 1024;
const DISCOVERY_SCHEMA: &str = "hololake.nearby-ai-discovery/v1";
const TERMINAL_LINK_PROTOCOL: &str = "HOLOLAKE_TERMINAL_LINK/2";
const ENVIRONMENT_FRAME_TTL_MS: u128 = 45_000;
pub struct DirectLocalBrokerHandle {
shutdown: Arc<AtomicBool>,
@ -131,7 +157,7 @@ struct BrokerStorageRoots {
impl Drop for DirectLocalBrokerHandle {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::Release);
let _ = UnixStream::connect(&self.socket_path);
let _ = connect_endpoint(&self.socket_path);
if let Some(worker) = self.worker.take() {
let _ = worker.join();
}
@ -152,6 +178,8 @@ enum BrokerRequest {
Ping,
OpenSession(OpenSessionInput),
ResumeSession(ResumeSessionInput),
HeartbeatSession(AuthenticateSessionInput),
GetWorkEnvironment(AuthenticatedWorkEnvironmentInput),
AppendEvent(AppendSessionEventInput),
ResolveCapabilityRoute(AuthenticatedRouteInput),
InstallDynamicNodeRegistry(AuthenticatedRegistryInput),
@ -263,6 +291,49 @@ struct AuthenticatedDevelopmentReleaseInput {
lane: ReleaseWriteLaneInput,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AuthenticatedWorkEnvironmentInput {
session: AuthenticateSessionInput,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct WorkEnvironmentFrame {
schema: &'static str,
state: &'static str,
environment_id: String,
runtime_owner: &'static str,
continuity_owner: &'static str,
transport: &'static str,
mcp_role: &'static str,
protocol_runtime: &'static str,
account_key: String,
session_id: String,
lane_id: String,
client_instance_id: String,
session_event_cursor: u64,
session_observed_at_unix_ms: u128,
development_write_lane_state: String,
development_writer_matches_session: bool,
workspace_authority: &'static str,
supervised_shell_execution: bool,
protocol_restoration_by_model_required: bool,
refresh_before_mutation_required: bool,
issued_at_unix_ms: u128,
valid_until_unix_ms: u128,
frame_sha256: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct HeartbeatEnvironmentReceipt {
schema: &'static str,
state: &'static str,
session: DirectSessionReceipt,
environment: WorkEnvironmentFrame,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct BrokerResponse {
@ -314,7 +385,11 @@ fn nearby_discovery_snapshot() -> NearbyAiDiscoverySnapshot {
schema: DISCOVERY_SCHEMA,
state: "DISCOVERABLE_ON_SAME_DEVICE",
service_name: "HoloLake",
transport: "STANDARD_APP_DATA_DESCRIPTOR_TO_USER_ONLY_UNIX_SOCKET",
transport: if cfg!(windows) {
"STANDARD_APP_DATA_DESCRIPTOR_TO_USER_PRIVATE_NAMED_PIPE"
} else {
"STANDARD_APP_DATA_DESCRIPTOR_TO_USER_PRIVATE_UNIX_SOCKET"
},
language_protocol: "GLP/1.0",
automatic_same_device_discovery: true,
large_invitation_copy_required: false,
@ -327,50 +402,138 @@ fn nearby_discovery_snapshot() -> NearbyAiDiscoverySnapshot {
pub fn run_connector() -> Result<(), String> {
let descriptor_path = connector_descriptor_path()?;
let stdin = io::stdin();
let mut stdout = io::stdout().lock();
let mut stream = None;
for line in stdin.lock().lines() {
let line = line.map_err(|error| format!("HOLOLAKE_CONNECTOR_INPUT_FAILED: {error}"))?;
if line.trim().is_empty() {
continue;
}
if line.len() as u64 > MAX_REQUEST_BYTES {
write_connector_transport_error(&mut stdout, "HOLOLAKE_BROKER_REQUEST_TOO_LARGE")?;
continue;
}
let needs_connection = match stream.as_mut() {
Some(connected) => probe_connection(connected).is_err(),
None => true,
};
if needs_connection {
let descriptor = read_connector_descriptor(&descriptor_path)?;
stream = Some(connect_with_startup_grace(&descriptor)?);
}
let result = send_connector_request(
stream
.as_mut()
.ok_or("HOLOLAKE_DIRECT_LOCAL_BROKER_NOT_RUNNING")?,
line.as_bytes(),
);
match result {
Ok(response) => {
stdout
.write_all(&response)
.and_then(|_| stdout.flush())
.map_err(|error| format!("HOLOLAKE_CONNECTOR_OUTPUT_FAILED: {error}"))?;
}
Err(error) => {
stream = None;
write_connector_transport_error(
&mut stdout,
&format!("HOLOLAKE_TERMINAL_LINK_RESPONSE_UNCERTAIN: {error}"),
)?;
}
}
}
Ok(())
}
fn read_connector_descriptor(path: &Path) -> Result<ConnectorDescriptor, String> {
let descriptor: ConnectorDescriptor = serde_json::from_slice(
&fs::read(&descriptor_path)
&fs::read(path)
.map_err(|error| format!("HOLOLAKE_BROKER_DESCRIPTOR_UNAVAILABLE: {error}"))?,
)
.map_err(|error| format!("HOLOLAKE_BROKER_DESCRIPTOR_INVALID: {error}"))?;
if descriptor.schema != BROKER_SCHEMA
|| descriptor.state != "LISTENING"
|| descriptor.transport != "UNIX_STREAM_JSON_LINES"
|| !matches!(
descriptor.transport.as_str(),
"UNIX_STREAM_JSON_LINES" | "WINDOWS_NAMED_PIPE_JSON_LINES"
)
{
return Err("HOLOLAKE_BROKER_DESCRIPTOR_UNSUPPORTED".into());
}
let mut stream = connect_with_startup_grace(&descriptor)?;
Ok(descriptor)
}
fn probe_connection(stream: &mut LocalSocketStream) -> Result<(), String> {
let response = send_connector_request(stream, br#"{"operation":"PING"}"#)?;
let value: Value = serde_json::from_slice(&response)
.map_err(|_| "HOLOLAKE_CONNECTOR_PROBE_INVALID".to_string())?;
if value.get("ok").and_then(Value::as_bool) == Some(true)
&& value
.pointer("/result/terminalLinkProtocol")
.and_then(Value::as_str)
== Some(TERMINAL_LINK_PROTOCOL)
{
Ok(())
} else {
Err("HOLOLAKE_CONNECTOR_PROBE_REJECTED".into())
}
}
fn send_connector_request(
stream: &mut LocalSocketStream,
request: &[u8],
) -> Result<Vec<u8>, String> {
stream
.write_all(request)
.and_then(|_| stream.write_all(b"\n"))
.and_then(|_| stream.flush())
.map_err(|error| format!("HOLOLAKE_CONNECTOR_WRITE_FAILED: {error}"))?;
let read_stream = stream
.try_clone()
.map_err(|error| format!("HOLOLAKE_CONNECTOR_CLONE_FAILED: {error}"))?;
let output = thread::Builder::new()
.name("hololake-connector-output".into())
.spawn(move || -> Result<(), String> {
let mut reader = BufReader::new(read_stream);
let mut stdout = io::stdout().lock();
io::copy(&mut reader, &mut stdout)
.and_then(|_| stdout.flush())
.map_err(|error| format!("HOLOLAKE_CONNECTOR_OUTPUT_FAILED: {error}"))?;
Ok(())
})
.map_err(|error| format!("HOLOLAKE_CONNECTOR_OUTPUT_THREAD_FAILED: {error}"))?;
let mut stdin = io::stdin().lock();
io::copy(&mut stdin, &mut stream)
.and_then(|_| stream.shutdown(std::net::Shutdown::Write))
.map_err(|error| format!("HOLOLAKE_CONNECTOR_INPUT_FAILED: {error}"))?;
output
.join()
.map_err(|_| "HOLOLAKE_CONNECTOR_OUTPUT_THREAD_PANICKED".to_string())??;
Ok(())
let mut response = Vec::new();
BufReader::new(read_stream)
.take(MAX_REQUEST_BYTES + 1)
.read_until(b'\n', &mut response)
.map_err(|error| format!("HOLOLAKE_CONNECTOR_READ_FAILED: {error}"))?;
if response.is_empty()
|| !response.ends_with(b"\n")
|| response.len() as u64 > MAX_REQUEST_BYTES
{
return Err("HOLOLAKE_CONNECTOR_RESPONSE_INVALID".into());
}
Ok(response)
}
fn connect_with_startup_grace(descriptor: &ConnectorDescriptor) -> Result<UnixStream, String> {
fn write_connector_transport_error(output: &mut impl Write, error: &str) -> Result<(), String> {
serde_json::to_writer(
&mut *output,
&serde_json::json!({
"schema": BROKER_SCHEMA,
"ok": false,
"error": error,
"continuityOwner": "HOLOLAKE",
"safeToRetryAfterStatusRead": true
}),
)
.map_err(|failure| format!("HOLOLAKE_CONNECTOR_OUTPUT_FAILED: {failure}"))?;
output
.write_all(b"\n")
.and_then(|_| output.flush())
.map_err(|failure| format!("HOLOLAKE_CONNECTOR_OUTPUT_FAILED: {failure}"))
}
fn connect_with_startup_grace(
descriptor: &ConnectorDescriptor,
) -> Result<LocalSocketStream, String> {
if descriptor.process_id == 0 {
return Err("HOLOLAKE_BROKER_DESCRIPTOR_UNSUPPORTED".into());
}
let mut last_error = None;
for _ in 0..50 {
match UnixStream::connect(&descriptor.socket_path) {
match connect_endpoint(Path::new(&descriptor.socket_path)) {
Ok(stream) => return Ok(stream),
Err(error)
if matches!(
@ -392,6 +555,59 @@ fn connect_with_startup_grace(descriptor: &ConnectorDescriptor) -> Result<UnixSt
))
}
fn connect_endpoint(path: &Path) -> std::io::Result<LocalSocketStream> {
#[cfg(unix)]
{
let name = path.to_fs_name::<GenericFilePath>()?;
return LocalSocketStream::connect(name);
}
#[cfg(windows)]
{
let endpoint = path.to_string_lossy();
let name = endpoint.as_ref().to_ns_name::<GenericNamespaced>()?;
LocalSocketStream::connect(name)
}
}
fn listen_endpoint(path: &Path) -> Result<LocalSocketListener, String> {
#[cfg(unix)]
let name = path
.to_fs_name::<GenericFilePath>()
.map_err(|error| format!("HOLOLAKE_BROKER_NAME_INVALID: {error}"))?;
#[cfg(windows)]
let endpoint = path.to_string_lossy();
#[cfg(windows)]
let name = endpoint
.as_ref()
.to_ns_name::<GenericNamespaced>()
.map_err(|error| format!("HOLOLAKE_BROKER_NAME_INVALID: {error}"))?;
let options = ListenerOptions::new().name(name);
#[cfg(windows)]
let options = options.security_descriptor(user_private_windows_security_descriptor()?);
options
.create_sync()
.map_err(|error| format!("HOLOLAKE_BROKER_BIND_FAILED: {error}"))
}
/// The protected DACL grants full access only to Local System and the object
/// owner. The named-pipe owner is the HoloLake desktop account that creates it.
/// HoloLake session authentication remains mandatory above this OS boundary.
#[cfg(windows)]
fn user_private_windows_security_descriptor() -> Result<SecurityDescriptor, String> {
let sddl = U16CString::from_str("D:P(A;;GA;;;SY)(A;;GA;;;OW)")
.map_err(|error| format!("HOLOLAKE_BROKER_WINDOWS_DACL_INVALID: {error}"))?;
SecurityDescriptor::deserialize(&sddl)
.map_err(|error| format!("HOLOLAKE_BROKER_WINDOWS_DACL_FAILED: {error}"))
}
fn platform_transport() -> &'static str {
if cfg!(windows) {
"WINDOWS_NAMED_PIPE_JSON_LINES"
} else {
"UNIX_STREAM_JSON_LINES"
}
}
fn connector_descriptor_path() -> Result<PathBuf, String> {
if let Some(path) = std::env::var_os("HOLOLAKE_BROKER_DESCRIPTOR") {
return Ok(PathBuf::from(path));
@ -442,26 +658,28 @@ fn start_at(
let development_root = development_root_from_session_root(&session_root)?;
fs::create_dir_all(&development_root)
.map_err(|error| format!("HOLOLAKE_BRIDGE_STORAGE_UNAVAILABLE: {error}"))?;
#[cfg(unix)]
if let Some(parent) = socket_path.parent() {
fs::create_dir_all(parent)
.map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_DIR_FAILED: {error}"))?;
fs::set_permissions(parent, fs::Permissions::from_mode(0o700))
.map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_PERMISSIONS_FAILED: {error}"))?;
}
#[cfg(unix)]
if socket_path.exists() {
if UnixStream::connect(&socket_path).is_ok() {
if connect_endpoint(&socket_path).is_ok() {
return Err("HOLOLAKE_DIRECT_LOCAL_BROKER_ALREADY_RUNNING".into());
}
fs::remove_file(&socket_path)
.map_err(|error| format!("HOLOLAKE_BROKER_STALE_SOCKET_REMOVE_FAILED: {error}"))?;
}
let listener = UnixListener::bind(&socket_path)
.map_err(|error| format!("HOLOLAKE_BROKER_BIND_FAILED: {error}"))?;
let listener = listen_endpoint(&socket_path)?;
#[cfg(unix)]
fs::set_permissions(&socket_path, fs::Permissions::from_mode(0o600))
.map_err(|error| format!("HOLOLAKE_BROKER_SOCKET_PERMISSIONS_FAILED: {error}"))?;
listener
.set_nonblocking(true)
.set_nonblocking(ListenerNonblockingMode::Accept)
.map_err(|error| format!("HOLOLAKE_BROKER_NONBLOCKING_FAILED: {error}"))?;
write_descriptor(
@ -469,7 +687,7 @@ fn start_at(
&BrokerDescriptor {
schema: BROKER_SCHEMA,
state: "LISTENING",
transport: "UNIX_STREAM_JSON_LINES",
transport: platform_transport(),
socket_path: socket_path.to_string_lossy().into_owned(),
max_request_bytes: MAX_REQUEST_BYTES,
process_id: std::process::id(),
@ -502,6 +720,7 @@ fn start_at(
&worker_shutdown,
&worker_authenticated_connections,
);
#[cfg(unix)]
let _ = fs::remove_file(worker_socket);
})
.map_err(|error| format!("HOLOLAKE_BROKER_THREAD_FAILED: {error}"))?;
@ -518,14 +737,14 @@ fn start_at(
}
fn serve(
listener: UnixListener,
listener: LocalSocketListener,
roots: BrokerStorageRoots,
shutdown: &AtomicBool,
authenticated_connections: &Arc<AtomicUsize>,
) {
while !shutdown.load(Ordering::Acquire) {
match listener.accept() {
Ok((stream, _)) => {
Ok(stream) => {
if shutdown.load(Ordering::Acquire) {
break;
}
@ -549,7 +768,7 @@ fn serve(
}
fn serve_connection(
mut stream: UnixStream,
mut stream: LocalSocketStream,
roots: &BrokerStorageRoots,
authenticated_connections: Arc<AtomicUsize>,
) {
@ -642,6 +861,8 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
BrokerRequest::Ping => serde_json::to_value(serde_json::json!({
"state": "READY",
"continuityOwner": "HOLOLAKE",
"terminalLinkProtocol": TERMINAL_LINK_PROTOCOL,
"transport": platform_transport(),
"mcpRole": "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY"
}))
.map_err(|error| error.to_string()),
@ -649,6 +870,25 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
BrokerRequest::ResumeSession(input) => resume_at(session_root, input)
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
BrokerRequest::HeartbeatSession(input) => heartbeat_at(session_root, &input)
.and_then(|session| {
work_environment_frame_at(
session_root,
development_root,
&AuthenticatedWorkEnvironmentInput { session: input },
)
.map(|environment| HeartbeatEnvironmentReceipt {
schema: "hololake.terminal-link-heartbeat/v2",
state: "HEARTBEAT_ACK_ENVIRONMENT_REFRESHED",
session,
environment,
})
})
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
BrokerRequest::GetWorkEnvironment(input) => {
work_environment_frame_at(session_root, development_root, &input)
.and_then(|frame| serde_json::to_value(frame).map_err(|error| error.to_string()))
}
BrokerRequest::AppendEvent(input) => append_event_at(session_root, input)
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
BrokerRequest::ResolveCapabilityRoute(input) => {
@ -719,7 +959,9 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
.and_then(|ticket| serde_json::to_value(ticket).map_err(|error| error.to_string()))
}
BrokerRequest::AcquireDevelopmentWriteLane(input) => {
authenticate_development_request(session_root, &input.session, &input.lane.account_id)
let session = input.session;
let account_id = input.lane.account_id.clone();
authenticate_development_request(session_root, &session, &account_id)
.and_then(|context| {
if context.lane_id != input.lane.lane_id
|| context.client_instance_id != input.lane.owner_instance_id
@ -729,7 +971,21 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
acquire_development_lane_at(development_root, input.lane)
})
.and_then(|receipt| {
serde_json::to_value(receipt).map_err(|error| error.to_string())
let mut value =
serde_json::to_value(receipt).map_err(|error| error.to_string())?;
let environment = work_environment_frame_at(
session_root,
development_root,
&AuthenticatedWorkEnvironmentInput { session },
)?;
value
.as_object_mut()
.ok_or("HOLOLAKE_DEVELOPMENT_LANE_RECEIPT_INVALID")?
.insert(
"environment".into(),
serde_json::to_value(environment).map_err(|error| error.to_string())?,
);
Ok(value)
})
}
BrokerRequest::InspectDevelopmentWriteLane(input) => {
@ -760,6 +1016,85 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
}
}
fn work_environment_frame_at(
session_root: &Path,
development_root: &Path,
input: &AuthenticatedWorkEnvironmentInput,
) -> Result<WorkEnvironmentFrame, String> {
let context =
authenticate_development_request(session_root, &input.session, &input.session.account_id)?;
let writer = inspect_development_lane_at(development_root, &input.session.account_id)?;
let writer_matches = writer.state == "ACTIVE"
&& writer.lane_id.as_deref() == Some(context.lane_id.as_str())
&& writer.owner_instance_id.as_deref() == Some(context.client_instance_id.as_str());
let state = if writer_matches {
"VERIFIED_HOLOLAKE_WORK_ENVIRONMENT"
} else {
"HOLOLAKE_SESSION_CONNECTED_WRITE_LANE_REQUIRED"
};
let issued_at_unix_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|duration| duration.as_millis())
.map_err(|error| format!("HOLOLAKE_SYSTEM_CLOCK_INVALID: {error}"))?;
let valid_until_unix_ms = issued_at_unix_ms + ENVIRONMENT_FRAME_TTL_MS;
let environment_id = format!(
"env-{}",
&sha256_hex(
format!(
"{}\n{}\n{}\n{}",
context.account_key,
context.session_id,
context.lane_id,
context.client_instance_id
)
.as_bytes()
)[..24]
);
let frame_sha256 = sha256_hex(
format!(
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
environment_id,
state,
context.session_id,
context.lane_id,
context.client_instance_id,
context.last_event_sequence,
writer.state,
valid_until_unix_ms
)
.as_bytes(),
);
Ok(WorkEnvironmentFrame {
schema: "hololake.programming-ai-work-environment/v1",
state,
environment_id,
runtime_owner: "HOLOLAKE_NATIVE_DESKTOP",
continuity_owner: "HOLOLAKE",
transport: TERMINAL_LINK_PROTOCOL,
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
protocol_runtime: "GLS_RUNTIME_MANIFEST_V2_AND_NATIVE_KERNEL",
account_key: context.account_key,
session_id: context.session_id,
lane_id: context.lane_id,
client_instance_id: context.client_instance_id,
session_event_cursor: context.last_event_sequence,
session_observed_at_unix_ms: context.observed_at_unix_ms,
development_write_lane_state: writer.state.to_string(),
development_writer_matches_session: writer_matches,
workspace_authority: if writer_matches {
"HOLOLAKE_CONTROL_PLANE_ACTIVE_REGISTERED_CODE_CHANNEL_REQUIRED"
} else {
"NO_MUTATION_UNTIL_HOLOLAKE_WRITE_LANE"
},
supervised_shell_execution: false,
protocol_restoration_by_model_required: false,
refresh_before_mutation_required: true,
issued_at_unix_ms,
valid_until_unix_ms,
frame_sha256,
})
}
fn authenticate_development_request(
session_root: &Path,
session: &AuthenticateSessionInput,
@ -860,9 +1195,13 @@ fn short_socket_path(app_data: &Path) -> PathBuf {
.take(8)
.map(|byte| format!("{byte:02x}"))
.collect::<String>();
std::env::temp_dir()
.join(format!("hololake-{key}"))
.join("broker.sock")
if cfg!(windows) {
PathBuf::from(format!("LOCAL\\world.guanghu.hololake.{key}"))
} else {
std::env::temp_dir()
.join(format!("hololake-{key}"))
.join("broker.sock")
}
}
fn write_descriptor(path: &Path, descriptor: &BrokerDescriptor) -> Result<(), String> {
@ -894,7 +1233,7 @@ mod tests {
use tempfile::TempDir;
fn request(socket: &Path, value: Value) -> Value {
let mut stream = UnixStream::connect(socket).unwrap();
let mut stream = connect_endpoint(socket).unwrap();
serde_json::to_writer(&mut stream, &value).unwrap();
stream.write_all(b"\n").unwrap();
let mut line = String::new();
@ -1305,6 +1644,14 @@ mod tests {
);
assert_eq!(acquired["ok"], true);
assert_eq!(acquired["result"]["state"], "ACQUIRED");
assert_eq!(
acquired["result"]["environment"]["state"],
"VERIFIED_HOLOLAKE_WORK_ENVIRONMENT"
);
assert_eq!(
acquired["result"]["environment"]["protocolRestorationByModelRequired"],
false
);
let inspected = request(
&socket,
@ -1355,6 +1702,99 @@ mod tests {
assert_eq!(available["result"]["state"], "AVAILABLE");
}
#[test]
fn heartbeat_refreshes_a_verified_hololake_work_environment_frame() {
let temp = TempDir::new().unwrap();
let socket = temp.path().join("runtime/broker.sock");
let descriptor = temp.path().join("broker.json");
let sessions = temp
.path()
.join("accounts-v1/test-account/direct-local-session-v1");
let routes = temp.path().join("routes");
fs::create_dir_all(&sessions).unwrap();
fs::create_dir_all(&routes).unwrap();
let _broker = start_at(sessions.clone(), routes, descriptor, socket.clone()).unwrap();
let ticket = issue_ticket_at(
&sessions,
IssueDiscoveryTicketInput {
account_id: "human-1".into(),
lane_id: "DEV-1".into(),
client_instance_id: "codex-1".into(),
},
)
.unwrap();
let opened = request(
&socket,
serde_json::json!({
"operation": "OPEN_SESSION",
"input": {
"accountId": "human-1",
"laneId": "DEV-1",
"clientInstanceId": "codex-1",
"discoveryTicket": ticket.discovery_ticket
}
}),
);
let session = serde_json::json!({
"accountId": "human-1",
"sessionId": opened["result"]["sessionId"],
"resumeSecret": opened["result"]["resumeSecret"]
});
let before_lane = request(
&socket,
serde_json::json!({
"operation": "GET_WORK_ENVIRONMENT",
"input": { "session": session }
}),
);
assert_eq!(
before_lane["result"]["state"],
"HOLOLAKE_SESSION_CONNECTED_WRITE_LANE_REQUIRED"
);
assert_eq!(
before_lane["result"]["protocolRestorationByModelRequired"],
false
);
request(
&socket,
serde_json::json!({
"operation": "ACQUIRE_DEVELOPMENT_WRITE_LANE",
"input": {
"session": session,
"lane": {
"accountId": "human-1",
"laneId": "DEV-1",
"ownerInstanceId": "codex-1",
"resumeToken": null
}
}
}),
);
let heartbeat = request(
&socket,
serde_json::json!({
"operation": "HEARTBEAT_SESSION",
"input": session
}),
);
assert_eq!(
heartbeat["result"]["state"],
"HEARTBEAT_ACK_ENVIRONMENT_REFRESHED"
);
assert_eq!(
heartbeat["result"]["environment"]["state"],
"VERIFIED_HOLOLAKE_WORK_ENVIRONMENT"
);
assert_eq!(
heartbeat["result"]["environment"]["runtimeOwner"],
"HOLOLAKE_NATIVE_DESKTOP"
);
assert_eq!(
heartbeat["result"]["environment"]["developmentWriterMatchesSession"],
true
);
}
#[test]
fn expression_only_visitor_cannot_acquire_a_development_lane() {
let temp = TempDir::new().unwrap();
@ -1411,7 +1851,7 @@ mod tests {
fs::create_dir_all(&routes).unwrap();
let broker = start_at(sessions.clone(), routes, descriptor, socket.clone()).unwrap();
let probe = UnixStream::connect(&socket).unwrap();
let probe = connect_endpoint(&socket).unwrap();
thread::sleep(Duration::from_millis(50));
assert_eq!(broker.active_connection_count(), 0);
drop(probe);
@ -1425,7 +1865,7 @@ mod tests {
},
)
.unwrap();
let mut connector = UnixStream::connect(&socket).unwrap();
let mut connector = connect_endpoint(&socket).unwrap();
serde_json::to_writer(
&mut connector,
&serde_json::json!({

View file

@ -1,57 +0,0 @@
use serde::Serialize;
use tauri::AppHandle;
const DISCOVERY_SCHEMA: &str = "hololake.nearby-ai-discovery/v1";
/// Windows currently has no Unix-domain socket transport. Keep the public app
/// launchable while reporting the connector boundary as closed, rather than
/// silently opening a TCP listener with weaker local-user isolation.
#[derive(Default)]
pub struct DirectLocalBrokerState;
impl DirectLocalBrokerState {
pub fn ensure_started(&self, _app: &AppHandle) -> Result<bool, String> {
Ok(false)
}
pub fn active_connection_count(&self) -> usize {
0
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NearbyAiDiscoverySnapshot {
pub schema: &'static str,
pub state: &'static str,
pub service_name: &'static str,
pub transport: &'static str,
pub language_protocol: &'static str,
pub automatic_same_device_discovery: bool,
pub large_invitation_copy_required: bool,
pub generic_ai_visitor: &'static str,
pub guanghu_persona: &'static str,
pub local_network_discovery: &'static str,
pub authority: &'static str,
}
#[tauri::command]
pub fn get_nearby_ai_discovery() -> NearbyAiDiscoverySnapshot {
NearbyAiDiscoverySnapshot {
schema: DISCOVERY_SCHEMA,
state: "WINDOWS_TRANSPORT_NOT_YET_IMPLEMENTED",
service_name: "HoloLake",
transport: "CLOSED_NO_TCP_FALLBACK",
language_protocol: "GLP/1.0",
automatic_same_device_discovery: false,
large_invitation_copy_required: false,
generic_ai_visitor: "DEFERRED",
guanghu_persona: "BINDING_EVIDENCE_REQUIRED",
local_network_discovery: "DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL",
authority: "UNAVAILABLE_IS_NOT_AUTHORIZATION",
}
}
pub fn run_connector() -> Result<(), String> {
Err("HOLOLAKE_WINDOWS_DIRECT_LOCAL_CONNECTOR_NOT_IMPLEMENTED".into())
}

View file

@ -38,6 +38,12 @@ pub struct DiscoveryTicketReceipt {
pub client_instance_id: String,
pub discovery_ticket: String,
pub issued_at_unix_ms: u128,
pub terminal_transport: &'static str,
pub connector_executable: String,
pub connector_arguments: Vec<&'static str>,
pub heartbeat_interval_ms: u64,
pub environment_refresh_interval_ms: u64,
pub required_bootstrap_operations: Vec<&'static str>,
pub receipt_id: String,
}
@ -76,6 +82,8 @@ pub(crate) struct AuthenticatedSessionContext {
pub session_id: String,
pub lane_id: String,
pub client_instance_id: String,
pub observed_at_unix_ms: u128,
pub last_event_sequence: u64,
}
#[derive(Clone, Debug, Serialize)]
@ -91,6 +99,10 @@ pub struct DirectSessionReceipt {
pub observed_at_unix_ms: u128,
pub last_event_sequence: u64,
pub resume_secret: Option<String>,
pub continuity_owner: &'static str,
pub transport: &'static str,
pub mcp_role: &'static str,
pub required_next_operation: &'static str,
pub receipt_id: String,
}
@ -199,6 +211,17 @@ pub async fn append_direct_local_session_event(
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_JOIN_FAILED: {error}"))?
}
#[tauri::command]
pub async fn heartbeat_direct_local_session(
app: AppHandle,
input: AuthenticateSessionInput,
) -> Result<DirectSessionReceipt, String> {
let root = direct_session_root(&app)?;
tauri::async_runtime::spawn_blocking(move || heartbeat_at(&root, &input))
.await
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_JOIN_FAILED: {error}"))?
}
pub(crate) fn direct_session_root(app: &AppHandle) -> Result<PathBuf, String> {
let root = crate::authenticated_storage::account_storage_root(app, "direct-local-session-v1")?;
fs::create_dir_all(&root)
@ -356,6 +379,18 @@ pub(crate) fn issue_ticket_at(
client_instance_id: input.client_instance_id,
discovery_ticket,
issued_at_unix_ms,
terminal_transport: "HOLOLAKE_TERMINAL_LINK/2",
connector_executable: std::env::current_exe()
.map(|path| path.to_string_lossy().into_owned())
.unwrap_or_else(|_| "HoloLake".into()),
connector_arguments: vec!["--connector"],
heartbeat_interval_ms: 15_000,
environment_refresh_interval_ms: 30_000,
required_bootstrap_operations: vec![
"OPEN_SESSION",
"ACQUIRE_DEVELOPMENT_WRITE_LANE",
"GET_WORK_ENVIRONMENT",
],
receipt_id: sha256_hex(
format!("ISSUED\n{discovery_ticket_sha256}\n{issued_at_unix_ms}").as_bytes(),
),
@ -446,6 +481,29 @@ pub(crate) fn append_event_at(
Ok(event_receipt("APPENDED", &event))
}
pub(crate) fn heartbeat_at(
root: &Path,
input: &AuthenticateSessionInput,
) -> Result<DirectSessionReceipt, String> {
validate_identifier(&input.account_id, "ACCOUNT")?;
validate_identifier(&input.session_id, "SESSION")?;
validate_secret(&input.resume_secret, "RESUME_SECRET")?;
let account_key = sha256_hex(input.account_id.as_bytes());
require_active_session(root, &account_key, &input.session_id)?;
let path = session_path(root, &account_key, &input.session_id);
let _lock = lock_session(&path)?;
let mut record = read_session(&path)?;
authorize(
&record,
&account_key,
&input.session_id,
&input.resume_secret,
)?;
record.observed_at_unix_ms = now_unix_ms()?;
write_record_atomic(&path, &record)?;
Ok(session_receipt("HEARTBEAT_ACK", &record, None))
}
pub(crate) fn authenticate_with_lane_at(
root: &Path,
input: &AuthenticateSessionInput,
@ -493,6 +551,8 @@ pub(crate) fn authenticate_context_at(
session_id: record.session_id,
lane_id: record.lane_id,
client_instance_id: record.client_instance_id,
observed_at_unix_ms: record.observed_at_unix_ms,
last_event_sequence: record.last_event_sequence,
})
}
@ -652,6 +712,10 @@ fn session_receipt(
observed_at_unix_ms: record.observed_at_unix_ms,
last_event_sequence: record.last_event_sequence,
resume_secret,
continuity_owner: "HOLOLAKE",
transport: "HOLOLAKE_TERMINAL_LINK/2",
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
required_next_operation: "GET_WORK_ENVIRONMENT",
receipt_id: sha256_hex(
format!(
"{state}\n{}\n{}\n{}\n{}",

View file

@ -21,6 +21,9 @@ pub struct HoloLakeHomeStatus {
pub release_recovery_state: String,
pub automatic_upstream_updates: bool,
pub mcp_role: &'static str,
pub terminal_link_protocol: &'static str,
pub terminal_link_transport: &'static str,
pub environment_frame_policy: &'static str,
}
#[tauri::command]
@ -41,6 +44,13 @@ pub fn get_hololake_home_status(
release_recovery_state: crate::release_update::release_recovery_state(&app)?,
automatic_upstream_updates: false,
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
terminal_link_protocol: "HOLOLAKE_TERMINAL_LINK/2",
terminal_link_transport: if cfg!(windows) {
"WINDOWS_USER_PRIVATE_NAMED_PIPE"
} else {
"MACOS_LINUX_USER_PRIVATE_UNIX_SOCKET"
},
environment_frame_policy: "REQUIRED_AFTER_CONNECT_RESUME_AND_BEFORE_MUTATION",
});
}
let session_root = direct_session_root(&app)?;
@ -57,5 +67,12 @@ pub fn get_hololake_home_status(
release_recovery_state: crate::release_update::release_recovery_state(&app)?,
automatic_upstream_updates: false,
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
terminal_link_protocol: "HOLOLAKE_TERMINAL_LINK/2",
terminal_link_transport: if cfg!(windows) {
"WINDOWS_USER_PRIVATE_NAMED_PIPE"
} else {
"MACOS_LINUX_USER_PRIVATE_UNIX_SOCKET"
},
environment_frame_policy: "REQUIRED_AFTER_CONNECT_RESUME_AND_BEFORE_MUTATION",
})
}

View file

@ -2,10 +2,6 @@ mod authenticated_storage;
mod circular_lake_membrane;
mod code_channel;
mod code_repo_login;
#[cfg(unix)]
mod direct_local_broker;
#[cfg(windows)]
#[path = "direct_local_broker_windows.rs"]
mod direct_local_broker;
mod direct_local_session;
mod dynamic_capability_routing;
@ -50,6 +46,7 @@ pub fn run() {
direct_local_session::open_direct_local_session,
direct_local_session::resume_direct_local_session,
direct_local_session::append_direct_local_session_event,
direct_local_session::heartbeat_direct_local_session,
direct_local_broker::get_nearby_ai_discovery,
gls_protocol_runtime::get_gls_protocol_runtime,
gls_protocol_kernel::get_gls_protocol_kernel,

View file

@ -854,6 +854,8 @@ mod tests {
session_id: "channel-session-1".into(),
lane_id: "codex-current-channel".into(),
client_instance_id: instance.into(),
observed_at_unix_ms: 1,
last_event_sequence: 0,
},
}
}