Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Binary/Main/
Entry.rs

1//! # Entry (Binary/Main)
2//!
3//! ## RESPONSIBILITIES
4//!
5//! Main application entry point that orchestrates the complete application
6//! lifecycle. This function coordinates:
7//! - Tokio runtime creation and management
8//! - CLI argument parsing
9//! - Application state initialization
10//! - Tauri application builder setup
11//! - Service initialization (Vine, Cocoon, Configuration)
12//! - Graceful shutdown handling
13//!
14//! ## ARCHITECTURAL ROLE
15//!
16//! The Entry module is the **primary entry point** in Mountain's architecture:
17//!
18//! ```text
19//! main.rs ──► Binary::Main::Entry::Fn()
20//!                                    │
21//!                                    ▼
22//! AppLifecycle ──► Service Initialization ──► Tauri App Run
23//!                                           │
24//!                                           ▼
25//!                                   Graceful Shutdown
26//! ```
27//!
28//! ## KEY COMPONENTS
29//!
30//! - **Fn()**: Main entry point exported as `Binary::Main::Fn()`
31//! - Tokio runtime management
32//! - Application state initialization via StateBuild
33//! - Tauri builder configuration via TauriBuild
34//! - Service orchestration (Vine, Cocoon, Configuration)
35//! - Event-driven lifecycle management
36//!
37//! ## ERROR HANDLING
38//!
39//! - Panics on fatal errors (Tokio runtime failure, Tauri build failure)
40//! - Logs errors for service initialization failures
41//! - Graceful degradation for non-critical service failures
42//!
43//! ## LOGGING
44//!
45//! Uses the TraceStep! macro for checkpoint logging at TRACE level.
46//! Additional logging at DEBUG, INFO, WARN, and ERROR levels throughout.
47//!
48//! ## PERFORMANCE CONSIDERATIONS
49//!
50//! - Tokio multi-threaded runtime for optimal performance
51//! - Asynchronous service initialization
52//! - Lazy initialization where possible
53//!
54//! ## TODO
55//! - [ ] Add crash recovery mechanism
56//! - [ ] Implement proper error dialog for startup failures
57//! - [ ] Add startup performance metrics
58
59use std::sync::{
60	Arc,
61	atomic::{AtomicBool, Ordering},
62};
63
64use tauri::{App, Manager, RunEvent, Wry};
65use Echo::Scheduler::{Scheduler::Scheduler, SchedulerBuilder::SchedulerBuilder};
66
67use crate::dev_log;
68use crate::{
69	// Crate root imports
70	ApplicationState::State::ApplicationState::ApplicationState,
71	Binary::Build::DnsCommands::{
72		StartupTime::init_dns_startup_time,
73		dns_get_forward_allowlist::dns_get_forward_allowlist,
74		dns_get_health_status::dns_get_health_status,
75		dns_get_server_info::dns_get_server_info,
76		dns_get_zone_info::dns_get_zone_info,
77		dns_health_check::dns_health_check,
78		dns_resolve::dns_resolve,
79		dns_test_resolution::dns_test_resolution,
80	},
81	// Binary submodule imports
82	Binary::Build::LocalhostPlugin::LocalhostPlugin as LocalhostPluginFn,
83	Binary::Build::LoggingPlugin::LoggingPlugin as LoggingPluginFn,
84	Binary::Build::Scheme::{self, DnsPort, init_service_registry, land_scheme_handler, register_land_service},
85	Binary::Build::ServiceRegistry::ServiceRegistry as ServiceRegistryFn,
86	Binary::Build::TauriBuild::TauriBuild as TauriBuildFn,
87	Binary::Build::WindowBuild::WindowBuild as WindowBuildFn,
88	Binary::Extension::ExtensionPopulate::Fn as ExtensionPopulateFn,
89	Binary::Extension::ScanPathConfigure::ScanPathConfigure as ScanPathConfigureFn,
90	Binary::Initialize::CliParse::Parse as CliParseFn,
91	Binary::Initialize::LogLevel::Resolve as ResolveLogLevel,
92	Binary::Initialize::PortSelector::BuildUrl as BuildPortUrl,
93	Binary::Initialize::PortSelector::Select as SelectPort,
94	Binary::Initialize::StateBuild::Build as BuildStateFn,
95	Binary::Register::AdvancedFeaturesRegister::AdvancedFeaturesRegister as AdvancedFeaturesRegisterFn,
96	Binary::Register::CommandRegister::CommandRegister as CommandRegisterFn,
97	Binary::Register::IPCServerRegister::IPCServerRegister as IPCServerRegisterFn,
98	Binary::Register::StatusReporterRegister::StatusReporterRegister as StatusReporterRegisterFn,
99	Binary::Register::WindSyncRegister::WindSyncRegister as WindSyncRegisterFn,
100	Binary::Service::CocoonStart::Fn as CocoonStartFn,
101	Binary::Service::ConfigurationInitialize::Fn as ConfigurationInitializeFn,
102	Binary::Service::VineStart::Fn as VineStartFn,
103	Binary::Shutdown::RuntimeShutdown::RuntimeShutdown as RuntimeShutdownFn,
104	Binary::Shutdown::SchedulerShutdown::SchedulerShutdown as SchedulerShutdownFn,
105	Command,
106	Environment::MountainEnvironment::MountainEnvironment,
107	ProcessManagement::InitializationData,
108	RunTime::ApplicationRunTime::ApplicationRunTime,
109	Track,
110};
111use super::AppLifecycle::AppLifecycleSetup;
112
113// Note: Tauri commands are used with fully qualified paths in generate_handler
114// because the __cmd_* macros generated by #[tauri::command] are module-local.
115
116/// Logs a checkpoint message at TRACE level.
117macro_rules! TraceStep {
118
119	($($arg:tt)*) => {{
120
121		dev_log!("lifecycle", $($arg)*);
122	}};
123}
124
125/// The main function that orchestrates the application lifecycle.
126///
127/// This function:
128/// 1. Creates a Tokio runtime
129/// 2. Parses CLI arguments
130/// 3. Builds application state
131/// 4. Creates a scheduler
132/// 5. Selects a port for the local server
133/// 6. Resolves the log level
134/// 7. Sets up the Tauri builder
135/// 8. Configures the application lifecycle
136/// 9. Runs the Tauri application
137/// 10. Handles graceful shutdown
138pub fn Fn() {
139	// Initialize the native keyring store (Keychain on macOS) before any
140	// code path that calls SecretProvider. keyring-core 1.0 requires an
141	// explicit store set via set_default_store() before Entry::new() can
142	// create or look up credentials. omitting this causes "No default
143	// store has been set, so cannot search or create entries" on every
144	// secrets.get call, which in turn prevents extensions (e.g. Roo Code)
145	// from completing their webview initialisation.
146	//
147	// The `not_keyutils` parameter only matters on Linux - macOS ignores
148	// it and always routes to the native Keychain.
149	match keyring::use_native_store(false) {
150		Ok(()) => dev_log!("lifecycle", "[Boot] [Keyring] Native store initialized for secret management"),
151
152		Err(E) => {
153			dev_log!(
154				"lifecycle",
155				"warn: [Boot] [Keyring] Failed to initialize native store ({}); secret operations will fall back to \
156				 no-op",
157				E
158			)
159		},
160	}
161
162	// Open `Mountain.dev.log` up front. Forces `InitFileSink` to create
163	// the session log header on disk before any other code can panic, so
164	// an early crash still leaves a file with a timestamp + pid + tag
165	// context for post-mortem. Env vars are read from the shell here (the
166	// `.env.Land` load below may add MORE keys but never overrides
167	// Trace / Record because `set_var` only runs when a
168	// key is currently unset). Harmless to call: the inner `OnceLock`
169	// gates repeat invocations.
170	crate::IPC::DevLog::InitEager::Fn();
171
172	// -------------------------------------------------------------------------
173	// [Boot] [Env] Enhance the process environment with the user's
174	// interactive-shell PATH / NVM_DIR / HOMEBREW_PREFIX / JAVA_HOME / …
175	// before any child process is spawned. macOS GUI launches (Finder,
176	// Dock, Spotlight, `open <bundle>.app`) start the app with a minimal
177	// env where Homebrew, NVM, and similar are not on PATH; without this
178	// step the Cocoon node binary, language servers, and `git` calls
179	// from extensions all fall back to system defaults (or fail).
180	//
181	// Skip entirely when launched from a TTY (terminal already has the
182	// right env). On macOS, `std::io::stdin().is_terminal()` is the
183	// canonical check - waits for is-terminal 0.5; in the interim,
184	// probe `TERM_PROGRAM` env var which macOS Terminal.app and iTerm2
185	// both set. `TERM=xterm-256color` alone is unreliable (pipelines
186	// set it too). No-op when skip or the shell probe fails/times out.
187	// -------------------------------------------------------------------------
188	let IsTtyLaunch =
189		std::env::var("TERM_PROGRAM").is_ok() || std::env::var("TERM").map_or(false, |V| V != "dumb" && V != "unknown");
190
191	if !IsTtyLaunch {
192		crate::Environment::Utility::EnhanceShellEnvironment::Fn();
193	}
194
195	// -------------------------------------------------------------------------
196	// [Boot] [Env] Load .env.Land into process env so standalone binary
197	// invocations pick up Product*, Tier*, Network* vars without requiring
198	// the shell to pre-source the env file. Skip when launched from a TTY
199	// (terminal already has the right env).
200	// -------------------------------------------------------------------------
201	if !IsTtyLaunch {
202		{
203			fn LoadEnvFile(Path:&std::path::Path) -> bool {
204				let Ok(Content) = std::fs::read_to_string(Path) else {
205					return false;
206				};
207
208				for Line in Content.lines() {
209					let Trimmed = Line.trim();
210
211					if Trimmed.is_empty() || Trimmed.starts_with('#') {
212						continue;
213					}
214
215					if let Some((Key, Value)) = Trimmed.split_once('=') {
216						let CleanKey = Key.trim();
217
218						let CleanValue = Value.trim().trim_matches('"').trim_matches('\'');
219
220						if std::env::var_os(CleanKey).is_none() {
221							// SAFETY: set_var is called once per key during bootstrap
222							// before any threads read env (Tokio runtime starts later
223							// in this function).
224							unsafe { std::env::set_var(CleanKey, CleanValue) };
225						}
226					}
227				}
228
229				true
230			}
231
232			let mut Candidates:Vec<std::path::PathBuf> = Vec::new();
233
234			if let Ok(Cwd) = std::env::current_dir() {
235				Candidates.push(Cwd.join(".env.Land"));
236
237				if let Some(Parent) = Cwd.parent() {
238					Candidates.push(Parent.join(".env.Land"));
239				}
240
241				Candidates.push(Cwd.join(".env.Land.Sample"));
242
243				if let Some(Parent) = Cwd.parent() {
244					Candidates.push(Parent.join(".env.Land.Sample"));
245				}
246			}
247
248			// Repo-layout probe: Target/debug/<bin> → four hops up lands at Land/.
249			if let Ok(Exe) = std::env::current_exe() {
250				let Ancestors:Vec<&std::path::Path> = Exe.ancestors().collect();
251
252				for Candidate in Ancestors.iter().take(6) {
253					Candidates.push(Candidate.join(".env.Land"));
254
255					Candidates.push(Candidate.join(".env.Land.Sample"));
256				}
257			}
258
259			let mut Loaded = false;
260
261			for Candidate in Candidates {
262				if Candidate.exists() && LoadEnvFile(&Candidate) {
263					crate::dev_log!("lifecycle", "[Boot] [Env] Loaded env from {}", Candidate.display());
264
265					Loaded = true;
266
267					break;
268				}
269			}
270
271			if !Loaded {
272				crate::dev_log!(
273					"lifecycle",
274					"[Boot] [Env] No .env.Land / .env.Land.Sample found - using defaults"
275				);
276			}
277		}
278	}
279
280	// -------------------------------------------------------------------------
281	// [Boot] [Tier] Resolved tier banner (Plan A Wave 1.7 runtime banner)
282	// -------------------------------------------------------------------------
283	crate::LandFixTier::LogResolvedTiers();
284
285	// -------------------------------------------------------------------------
286	// [Boot] [Profile] Self-report (BATCH-13 step 6)
287	//
288	// Build.sh exports `Browser`/`Mountain`/`Electron`/`Bundle`/`Compiler`/
289	// `Profile` into the shell that invokes cargo. `build.rs` captures
290	// those into `cargo:rustc-env=LAND_*` so they're baked into the binary -
291	// runtime env lookups don't survive launching the binary from Finder /
292	// another shell. `option_env!` falls back to "unknown" when the build
293	// ran outside Build.sh (e.g. plain `cargo build`).
294	// -------------------------------------------------------------------------
295	{
296		let NamedProfile = option_env!("Profile").unwrap_or("unknown");
297
298		let Workbench = option_env!("Pack").unwrap_or("Unknown");
299
300		let Bundle = option_env!("Bundle").unwrap_or("");
301
302		let Compiler = option_env!("Compiler").unwrap_or("default");
303
304		dev_log!(
305			"lifecycle",
306			"[LandFix:Profile] Active profile={} workbench={} bundle={} compiler={}",
307			NamedProfile,
308			Workbench,
309			Bundle,
310			Compiler
311		);
312	}
313
314	// -------------------------------------------------------------------------
315	// [Boot] [Runtime] Tokio runtime creation
316	// -------------------------------------------------------------------------
317	TraceStep!("[Boot] [Runtime] Building Tokio runtime...");
318
319	let Runtime = tokio::runtime::Builder::new_multi_thread()
320		.enable_all()
321		.build()
322		.expect("FATAL: Cannot build Tokio runtime.");
323
324	TraceStep!("[Boot] [Runtime] Tokio runtime built.");
325
326	Runtime.block_on(async {
327		// ---------------------------------------------------------------------
328		// [Boot] [Telemetry] Hydrate runtime env from compile-baked
329		// Constants so spawned children (Cocoon Node, Sky webview)
330		// see the same telemetry config Mountain itself was built
331		// with - even when the user invokes the bare binary without
332		// sourcing `.env.Land.PostHog`. Idempotent + debug-only.
333		// Must run BEFORE PostHogPlugin::Initialize so the client
334		// reads the same effective env as the children.
335		// ---------------------------------------------------------------------
336		crate::Binary::Build::PostHogPlugin::HydrateRuntimeEnvironment::Fn();
337
338		// ---------------------------------------------------------------------
339		// [Boot] [PostHog] Initialize telemetry client first so any
340		// error captured during the rest of boot lands in the project.
341		// No-op in release builds or when Report=false.
342		// ---------------------------------------------------------------------
343		crate::Binary::Build::PostHogPlugin::Initialize::Fn().await;
344
345		// ---------------------------------------------------------------------
346		// [Boot] [Common::Telemetry] Initialize the shared dual-pipe
347		// stack so library crates linked into Mountain (Echo, Mist,
348		// Common) emit through the same client. The HydrateRuntime
349		// Environment step above populated the env so this picks up
350		// the same Authorize/Beam/Capture values Mountain's plugin
351		// already loaded. Idempotent.
352		// ---------------------------------------------------------------------
353		CommonLibrary::Telemetry::Initialize::Fn(CommonLibrary::Telemetry::Tier::Tier::Mountain).await;
354
355		// ---------------------------------------------------------------------
356		// [Boot] [Args] CLI parsing (using CliParse module)
357		// ---------------------------------------------------------------------
358		let _WorkspaceConfigurationPath = CliParseFn();
359		let _InitialFolders:Vec<String> = vec![];
360
361		// ---------------------------------------------------------------------
362		// [Boot] [State] ApplicationState (using StateBuild module)
363		// ---------------------------------------------------------------------
364		dev_log!("lifecycle", "[Boot] [State] Building ApplicationState...");
365
366		// Create application state directly (StateBuild::Build with default config)
367		let AppState = ApplicationState::default();
368
369		// -------------------------------------------------------------------
370		// [Boot] [Workspace] Seed initial workspace folders so every extension
371		// that calls `vscode.workspace.findFiles(...)` at activation has
372		// something to walk. Precedence: --folder flags → positional dirs →
373		// Open env → CWD fallback. See
374		// CliParse::ParseWorkspaceFolders.
375		// -------------------------------------------------------------------
376		{
377			let InitialFolderPaths = crate::Binary::Initialize::CliParse::ParseWorkspaceFolders();
378			if InitialFolderPaths.is_empty() {
379				dev_log!(
380					"lifecycle",
381					"[Boot] [Workspace] No initial folders resolved - editor will open in \"no folder\" mode."
382				);
383			} else {
384				use crate::ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO;
385				let mut Folders:Vec<WorkspaceFolderStateDTO> = Vec::new();
386				for (Index, Path) in InitialFolderPaths.iter().enumerate() {
387					let Uri = match url::Url::from_directory_path(Path) {
388						Ok(U) => U,
389						Err(()) => {
390							dev_log!(
391								"lifecycle",
392								"warn: [Boot] [Workspace] Failed to build URL for {}; skipping",
393								Path.display()
394							);
395							continue;
396						},
397					};
398					let Name = Path
399						.file_name()
400						.and_then(|N| N.to_str())
401						.map(str::to_string)
402						.unwrap_or_else(|| Path.display().to_string());
403					match WorkspaceFolderStateDTO::New(Uri, Name, Index) {
404						Ok(Dto) => Folders.push(Dto),
405						Err(Error) => {
406							dev_log!(
407								"lifecycle",
408								"warn: [Boot] [Workspace] Failed to build folder DTO for {}: {}",
409								Path.display(),
410								Error
411							);
412						},
413					}
414				}
415				if !Folders.is_empty() {
416					// Seed state directly; Cocoon is not yet spawned at this
417					// point, so there is no sidecar to notify. The initial
418					// workspace makes it to Cocoon via `InitializeExtensionHost`'s
419					// `workspace` payload during its handshake instead.
420					AppState.Workspace.SetWorkspaceFolders(Folders);
421					dev_log!(
422						"lifecycle",
423						"[Boot] [Workspace] Seeded {} workspace folder(s).",
424						InitialFolderPaths.len()
425					);
426				}
427			}
428		}
429
430		dev_log!(
431			"lifecycle",
432			"[Boot] [State] ApplicationState created with {} workspace folders.",
433			AppState.Workspace.WorkspaceFolders.lock().map(|f| f.len()).unwrap_or(0)
434		);
435
436		// Create Arc for application state to be managed by Tauri
437		let AppStateArcForClosure = Arc::new(AppState.clone());
438
439		// ---------------------------------------------------------------------
440		// [Boot] [Runtime] Scheduler handles (using RuntimeBuild module)
441		// ---------------------------------------------------------------------
442		let Scheduler = Arc::new(SchedulerBuilder::Create().Build());
443		let SchedulerForClosure = Scheduler.clone();
444		TraceStep!("[Boot] [Echo] Scheduler handles prepared.");
445
446		// ---------------------------------------------------------------------
447		// [Boot] [Localhost] Port selection (using PortSelector module)
448		// ---------------------------------------------------------------------
449		let ServerPort = SelectPort();
450		let LocalhostUrl = BuildPortUrl(ServerPort);
451
452		// ---------------------------------------------------------------------
453		// [Boot] [Logging] Log level resolution (using LogLevel module)
454		// ---------------------------------------------------------------------
455		let log_level = ResolveLogLevel();
456
457		// ---------------------------------------------------------------------
458		// [Boot] [Tauri] Builder setup (using TauriBuild module)
459		// ---------------------------------------------------------------------
460		let Builder = TauriBuildFn();
461
462		Builder
463			.plugin(LoggingPluginFn(log_level))
464			.plugin(LocalhostPluginFn(ServerPort))
465			.manage(AppStateArcForClosure.clone())
466			.setup({
467				let LocalhostUrl = LocalhostUrl.clone();
468				let ServerPortForClosure = ServerPort;
469				move |app:&mut App| {
470					dev_log!("lifecycle", "[Lifecycle] [Setup] Setup hook started.");
471					dev_log!("lifecycle", "[Lifecycle] [Setup] LocalhostUrl={}", LocalhostUrl);
472
473					// ---------------------------------------------------------
474					// [Service Registry] Initialize service registry for land:// routing
475					// ---------------------------------------------------------
476					dev_log!(
477						"lifecycle",
478						"[Lifecycle] [Setup] Initializing ServiceRegistry for land:// scheme..."
479					);
480					let service_registry = ServiceRegistryFn::new();
481					init_service_registry(service_registry.clone());
482
483					// ---------------------------------------------------------
484					// [Service Registry] Register local HTTP services
485					// ---------------------------------------------------------
486					// Register the main code editor service
487					dev_log!(
488						"lifecycle",
489						"[Lifecycle] [Setup] Registering code.land.playform.cloud service on port {}",
490						ServerPortForClosure
491					);
492					register_land_service("code.land.playform.cloud", ServerPortForClosure);
493
494					// Register API editor service (same port for now, can be separate later)
495					register_land_service("api.land.playform.cloud", ServerPortForClosure);
496
497					// Register assets editor service (same port for now, can be separate later)
498					register_land_service("assets.land.playform.cloud", ServerPortForClosure);
499
500					// Make the registry available as managed state for Tauri commands
501					app.manage(service_registry);
502					dev_log!(
503						"lifecycle",
504						"[Lifecycle] [Setup] ServiceRegistry initialized and services registered."
505					);
506
507					// ---------------------------------------------------------
508					// [DNS Server] Start the Hickory DNS server
509					// ---------------------------------------------------------
510					// The DNS server must start BEFORE any webview loads to ensure
511					// that land:// protocol_resolution is available
512					dev_log!("lifecycle", "[Lifecycle] [Setup] Starting DNS server on preferred port 5380...");
513					let dns_port = Mist::start(5380).unwrap_or_else(|e| {
514						dev_log!(
515							"lifecycle",
516							"warn: [Lifecycle] [Setup] Failed to start DNS server on port 5380: {}",
517							e
518						);
519						// Fallback to random port if preferred port fails
520						Mist::start(0).unwrap_or_else(|e| {
521							dev_log!(
522								"lifecycle",
523								"error: [Lifecycle] [Setup] Completely failed to start DNS server: {}",
524								e
525							);
526							0 // Return 0 as error indicator
527						})
528					});
529
530					if dns_port == 0 {
531						dev_log!(
532							"lifecycle",
533							"warn: [Lifecycle] [Setup] DNS server failed to start, land:// protocol will not be \
534							 available"
535						);
536					} else {
537						dev_log!(
538							"lifecycle",
539							"[Lifecycle] [Setup] DNS server started successfully on port {}",
540							dns_port
541						);
542						// Initialize DNS startup time for tracking
543						crate::Binary::Build::DnsCommands::StartupTime::init_dns_startup_time();
544					}
545
546					// ---------------------------------------------------------
547					// [Mist WebSocket] Optional Sky↔Mountain direct transport
548					// ---------------------------------------------------------
549					// `TierWebSocket=Mist` activates a localhost JSON-RPC
550					// WebSocket on port 5051 that Sky's TauriMainProcessService
551					// can use for high-frequency IPC (`storage:updateItems`,
552					// decoration updates, model changes) instead of the
553					// Tauri-invoke + Mountain-gRPC double hop. `Disabled` (the
554					// default) skips the bind entirely so the surface stays
555					// pure Tauri. Wiring of the actual handler registry is
556					// staged - this boot-time gate establishes the port and
557					// secret so subsequent atom batches can register handlers
558					// against the existing HandlerRegistry without revisiting
559					// the Mountain boot path.
560					let TierWebSocketSetting = std::env::var("TierWebSocket")
561						.unwrap_or_else(|_| env!("TierWebSocket", "Disabled").to_string());
562
563					if TierWebSocketSetting == "Mist" {
564						dev_log!(
565							"lifecycle",
566							"[Lifecycle] [Setup] TierWebSocket=Mist - starting WebSocket transport on 127.0.0.1:5051"
567						);
568
569						let MistRegistry = Mist::WebSocket::HandlerRegistry::new();
570						let MistSecret = Mist::WebSocket::SharedSecret::random();
571
572						// Expose the secret to Cocoon/Sky via env. The startup
573						// helper (`MountainGetWorkbenchConfiguration`) reads
574						// MountainWebSocketSecret + Port to surface in the
575						// workbench configuration payload that Sky consumes.
576						unsafe {
577							std::env::set_var("MountainWebSocketSecret", MistSecret.as_hex());
578							std::env::set_var("MountainWebSocketPort", "5051");
579						}
580
581						tokio::spawn(async move {
582							if let Err(Error) = Mist::WebSocket::ServeLocal(5051, MistSecret, MistRegistry).await {
583								dev_log!("lifecycle", "warn: [Lifecycle] [Mist] WebSocket server exited: {:?}", Error);
584							}
585						});
586					} else {
587						dev_log!(
588							"lifecycle",
589							"[Lifecycle] [Setup] TierWebSocket={} - WebSocket transport disabled",
590							TierWebSocketSetting
591						);
592					}
593
594					// Register DnsPort as managed state for Tauri commands
595					app.manage(DnsPort(dns_port));
596
597					let AppHandle = app.handle().clone();
598					TraceStep!("[Lifecycle] [Setup] AppHandle acquired.");
599
600					// ---------------------------------------------------------
601					// Setup application lifecycle through AppLifecycle module
602					// ---------------------------------------------------------
603					let AppStateArcFromClosure = AppStateArcForClosure.clone();
604
605					if let Err(e) = AppLifecycleSetup(
606						app,
607						AppHandle.clone(),
608						LocalhostUrl.clone(),
609						SchedulerForClosure.clone(),
610						AppStateArcFromClosure,
611					) {
612						dev_log!("lifecycle", "error: [Lifecycle] [Setup] Failed to setup lifecycle: {}", e);
613					}
614
615					Ok(())
616				}
617			})
618			.register_asynchronous_uri_scheme_protocol("fiddee", |_ctx, request, responder| {
619				// Implemented: delegate to synchronous scheme handler
620				let response = crate::Binary::Build::Scheme::land_scheme_handler(&request);
621				responder.respond(response);
622			})
623			.register_asynchronous_uri_scheme_protocol("vscode-file", |ctx, request, responder| {
624				// VS Code Electron workbench uses vscode-file:// to load assets.
625				// Maps to embedded frontend assets from Sky/Target.
626				let AppHandle = ctx.app_handle().clone();
627				std::thread::spawn(move || {
628					let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &request);
629					responder.respond(response);
630				});
631			})
632			.register_asynchronous_uri_scheme_protocol("vscode-webview", |ctx, request, responder| {
633				// VS Code's `WebviewElement` wraps every extension webview in
634				// an iframe whose `src` is `vscode-webview://<authority>/index.html?...`.
635				// Without this handler the iframe stays blank and every
636				// extension that uses `webviewView` / `WebviewPanel` /
637				// `CustomEditor` (Roo Code, Claude, GitLens, custom editors)
638				// is dead. The handler serves the three-file `pre/`
639				// directory (`index.html`, `service-worker.js`, `fake.html`);
640				// extension HTML itself comes through later via the workbench's
641				// `swMessage` postMessage channel, not this scheme.
642				let AppHandle = ctx.app_handle().clone();
643				std::thread::spawn(move || {
644					let response = crate::Binary::Build::Scheme::VscodeWebviewSchemeHandler(&AppHandle, &request);
645					responder.respond(response);
646				});
647			})
648			.register_asynchronous_uri_scheme_protocol("vscode-webview-resource", |ctx, request, responder| {
649				// `vscode-webview-resource://<auth>/<path>` is the URI shape
650				// stock VS Code emits from `webview.asWebviewUri(...)`. The
651				// service worker inside `pre/index.html` would normally
652				// intercept these and proxy through the host. Land disables
653				// that SW (see Output `PatchWebviewIframeServiceWorker`)
654				// because WKWebView refuses SW registration on the
655				// `vscode-webview://` custom protocol; instead we register
656				// this scheme directly so any extension that hard-codes
657				// the URI form (or didn't go through Cocoon's `asWebviewUri`
658				// rewrite) still resolves. Strip the `<auth>` and forward
659				// the path to `VscodeFileSchemeHandler` by rewriting the
660				// URI to `vscode-file://vscode-app/<path>`.
661				let AppHandle = ctx.app_handle().clone();
662				std::thread::spawn(move || {
663					let Original = request.uri().to_string();
664					let RewrittenUri = match Original.strip_prefix("vscode-webview-resource://") {
665						Some(After) => {
666							let Rest = After.find('/').map(|I| &After[I..]).unwrap_or("/");
667							format!("vscode-file://vscode-app{}", Rest)
668						},
669						None => "vscode-file://vscode-app/".to_string(),
670					};
671					crate::dev_log!(
672						"scheme-assets",
673						"[LandFix:VscodeWebviewResource] {} -> {}",
674						Original,
675						RewrittenUri
676					);
677					let mut Builder = tauri::http::request::Request::builder().uri(&RewrittenUri);
678					for (Name, Value) in request.headers().iter() {
679						Builder = Builder.header(Name, Value);
680					}
681					let Forwarded = Builder
682						.method(request.method().clone())
683						.body(request.body().clone())
684						.unwrap_or_else(|_| request.clone());
685					let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &Forwarded);
686					responder.respond(response);
687				});
688			})
689			.register_asynchronous_uri_scheme_protocol("vscode-resource", |ctx, request, responder| {
690				// Legacy stock-VS Code resource scheme. Same handling as
691				// `vscode-webview-resource` - rewrite to `vscode-file://`
692				// and dispatch through the existing file handler.
693				let AppHandle = ctx.app_handle().clone();
694				std::thread::spawn(move || {
695					let Original = request.uri().to_string();
696					let RewrittenUri = match Original.strip_prefix("vscode-resource://") {
697						Some(After) => {
698							let Rest = After.find('/').map(|I| &After[I..]).unwrap_or("/");
699							format!("vscode-file://vscode-app{}", Rest)
700						},
701						None => "vscode-file://vscode-app/".to_string(),
702					};
703					crate::dev_log!("scheme-assets", "[LandFix:VscodeResource] {} -> {}", Original, RewrittenUri);
704					let mut Builder = tauri::http::request::Request::builder().uri(&RewrittenUri);
705					for (Name, Value) in request.headers().iter() {
706						Builder = Builder.header(Name, Value);
707					}
708					let Forwarded = Builder
709						.method(request.method().clone())
710						.body(request.body().clone())
711						.unwrap_or_else(|_| request.clone());
712					let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &Forwarded);
713					responder.respond(response);
714				});
715			})
716			.plugin(tauri_plugin_dialog::init())
717			.plugin(tauri_plugin_fs::init())
718			.invoke_handler(tauri::generate_handler![
719				crate::Binary::Tray::SwitchTrayIcon::SwitchTrayIcon,
720
721				crate::Binary::IPC::WorkbenchConfigurationCommand::MountainGetWorkbenchConfiguration,
722
723				Command::TreeView::GetTreeViewChildren::GetTreeViewChildren,
724
725				Command::LanguageFeature::MountainProvideHover::MountainProvideHover,
726
727				Command::LanguageFeature::MountainProvideCompletions::MountainProvideCompletions,
728
729				Command::LanguageFeature::MountainProvideDefinition::MountainProvideDefinition,
730
731				Command::LanguageFeature::MountainProvideReferences::MountainProvideReferences,
732
733				Command::SourceControlManagement::GetAllSourceControlManagementState::GetAllSourceControlManagementState,
734
735				Command::Keybinding::GetResolvedKeybinding::GetResolvedKeybinding,
736
737				Track::FrontendCommand::DispatchFrontendCommand::DispatchFrontendCommand,
738
739				Track::UIRequest::ResolveUIRequest::ResolveUIRequest,
740
741				Track::Webview::MountainWebviewPostMessageFromGuest::MountainWebviewPostMessageFromGuest,
742
743				crate::Binary::IPC::MessageReceiveCommand::MountainIPCReceiveMessage,
744
745				crate::Binary::IPC::StatusGetCommand::MountainIPCGetStatus,
746
747				crate::Binary::IPC::InvokeCommand::MountainIPCInvoke,
748
749				crate::Binary::IPC::WindConfigurationCommand::MountainGetWindDesktopConfiguration,
750
751				crate::Binary::IPC::ConfigurationUpdateCommand::MountainUpdateConfigurationFromWind,
752
753				crate::Binary::IPC::ConfigurationSyncCommand::MountainSynchronizeConfiguration,
754
755				crate::Binary::IPC::ConfigurationStatusCommand::MountainGetConfigurationStatus,
756
757				crate::Binary::IPC::IPCStatusCommand::MountainGetIPCStatus,
758
759				crate::Binary::IPC::IPCStatusHistoryCommand::MountainGetIPCStatusHistory,
760
761				crate::Binary::IPC::IPCStatusReportingStartCommand::MountainStartIPCStatusReporting,
762
763				crate::Binary::IPC::PerformanceStatsCommand::MountainGetPerformanceStats,
764
765				crate::Binary::IPC::CacheStatsCommand::MountainGetCacheStats,
766
767				crate::Binary::IPC::CollaborationSessionCommand::MountainCreateCollaborationSession,
768
769				crate::Binary::IPC::CollaborationSessionCommand::MountainGetCollaborationSessions,
770
771				crate::Binary::IPC::DocumentSyncCommand::MountainAddDocumentForSync,
772
773				crate::Binary::IPC::DocumentSyncCommand::MountainGetSyncStatus,
774
775				crate::Binary::IPC::UpdateSubscriptionCommand::MountainSubscribeToUpdates,
776
777				crate::Binary::IPC::ConfigurationDataCommand::GetConfigurationData,
778
779				crate::Binary::IPC::ConfigurationDataCommand::SaveConfigurationData,
780
781				crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceOpenFolder,
782
783				crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceListFolders,
784
785				crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceCloseAllFolders,
786
787				crate::Binary::Build::DnsCommands::dns_get_server_info::dns_get_server_info,
788
789				crate::Binary::Build::DnsCommands::dns_get_zone_info::dns_get_zone_info,
790
791				crate::Binary::Build::DnsCommands::dns_get_forward_allowlist::dns_get_forward_allowlist,
792
793				crate::Binary::Build::DnsCommands::dns_get_health_status::dns_get_health_status,
794
795				crate::Binary::Build::DnsCommands::dns_resolve::dns_resolve,
796
797				crate::Binary::Build::DnsCommands::dns_test_resolution::dns_test_resolution,
798
799				crate::Binary::Build::DnsCommands::dns_health_check::dns_health_check,
800
801				// Process commands (direct Tauri invoke from ProcessPolyfill)
802				crate::Binary::IPC::ProcessCommand::process_get_exec_path::process_get_exec_path,
803
804				crate::Binary::IPC::ProcessCommand::process_get_platform::process_get_platform,
805
806				crate::Binary::IPC::ProcessCommand::process_get_arch::process_get_arch,
807
808				crate::Binary::IPC::ProcessCommand::process_get_pid::process_get_pid,
809
810				crate::Binary::IPC::ProcessCommand::process_get_shell_env::process_get_shell_env,
811
812				crate::Binary::IPC::ProcessCommand::process_get_memory_info::process_get_memory_info,
813
814				// Health check commands (direct Tauri invoke from SharedProcessProxy)
815				crate::Binary::IPC::HealthCommand::cocoon_extension_host_health::cocoon_extension_host_health,
816
817				crate::Binary::IPC::HealthCommand::cocoon_search_service_health::cocoon_search_service_health,
818
819				crate::Binary::IPC::HealthCommand::cocoon_debug_service_health::cocoon_debug_service_health,
820
821				crate::Binary::IPC::HealthCommand::shared_process_service_health::shared_process_service_health,
822
823				crate::Binary::IPC::RenderDevLogCommand::RenderDevLog,
824
825				// LAND-PATCH B7-S6 P14.5: Vine notification broadcast
826				// subscription. `vine_subscribe_notifications` opens a
827				// Tauri Channel that drains the process-wide
828				// `Vine::Client` broadcast into the webview; Effect-TS
829				// `VineNotificationsLive` Layer wraps it as a
830				// `Stream<NotificationFrame>`. `vine_subscriber_count`
831				// is a diagnostic for verifying registrations didn't
832				// leak across reloads.
833				crate::Binary::IPC::VineSubscribeCommand::vine_subscribe_notifications,
834
835				crate::Binary::IPC::VineSubscribeCommand::vine_subscriber_count,
836			])
837			.build(tauri::generate_context!())
838			.expect("FATAL: Error while building Mountain Tauri application")
839			.run(move |app_handle:&tauri::AppHandle, event:tauri::RunEvent| {
840				// Debug-only: log selected lifecycle events
841				if cfg!(debug_assertions) {
842					match &event {
843						RunEvent::MainEventsCleared => {},
844						RunEvent::WindowEvent { .. } => {},
845						_ => dev_log!("lifecycle", "[Lifecycle] [RunEvent] {:?}", event),
846					}
847				}
848
849				if let RunEvent::ExitRequested { api, .. } = event {
850					// Shutdown runs once. The graceful path ends with
851					// `app_handle.exit(0)`, which Tauri re-delivers as a
852					// second `ExitRequested { code: Some(0) }`. On re-entry
853					// we must NOT `prevent_exit` or spawn the shutdown task
854					// again - Cocoon has already been SIGKILLed and the
855					// second pass would log spurious "tcp connect error"
856					// warnings trying to notify a dead sidecar.
857					static SHUTTING_DOWN:AtomicBool = AtomicBool::new(false);
858					if SHUTTING_DOWN.swap(true, Ordering::SeqCst) {
859						return;
860					}
861
862					dev_log!(
863						"lifecycle",
864						"warn: [Lifecycle] [Shutdown] Exit requested. Starting graceful shutdown..."
865					);
866					api.prevent_exit();
867
868					let SchedulerHandle = Scheduler.clone();
869					let app_handle_clone = app_handle.clone();
870
871					tokio::spawn(async move {
872						dev_log!("lifecycle", "[Lifecycle] [Shutdown] Shutting down ApplicationRunTime...");
873						let _ = RuntimeShutdownFn(&app_handle_clone).await;
874
875						dev_log!("lifecycle", "[Lifecycle] [Shutdown] Stopping Echo scheduler...");
876						let _ = SchedulerShutdownFn(SchedulerHandle).await;
877
878						dev_log!("lifecycle", "[Lifecycle] [Shutdown] Done. Exiting process.");
879						app_handle_clone.exit(0);
880					});
881				}
882			});
883
884		dev_log!("lifecycle", "[Lifecycle] [Exit] Mountain application has shut down.");
885	});
886}