feat: embed cross-platform HoloLake terminal link
This commit is contained in:
parent
0be5ab3407
commit
ae7f883891
16 changed files with 808 additions and 122 deletions
|
|
@ -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!({
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
@ -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{}",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue