Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/IPC/WindServiceHandlers/
mod.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! Wind Service Handlers - dispatcher and sub-module aggregator.
4//! Domain files handle the individual handler implementations.
5
6pub mod Cocoon;
7
8pub mod Commands;
9
10pub mod Configuration;
11
12pub mod Encryption;
13
14pub mod Extension;
15
16pub mod ExtensionHost;
17
18pub mod Extensions;
19
20pub mod FileSystem;
21
22pub mod Git;
23
24pub mod Model;
25
26pub mod NativeDialog;
27
28pub mod NativeHost;
29
30pub mod Navigation;
31
32pub mod Output;
33
34pub mod Search;
35
36pub mod Sky;
37
38pub mod Storage;
39
40pub mod Terminal;
41
42pub mod UI;
43
44pub mod TreeView;
45
46pub mod Update;
47
48pub mod Utilities;
49
50// Local `use X::*;` (NOT `pub use`): brings the domain handler names into
51// this file's scope so the dispatch match arms below can call
52// `handle_foo(...)` unqualified. Local `use` is scoped to this file only;
53// external callers must spell the full path
54// (`WindServiceHandlers::Utilities::foo`).
55use std::{collections::HashMap, path::PathBuf, sync::Arc};
56
57use Cocoon::{ExtensionHostMessage::CocoonExtensionHostMessage, Notify::CocoonNotify, Request::CocoonRequest};
58use ExtensionHost::{
59	DebugService::{ExtensionHostDebugClose, ExtensionHostDebugReload},
60	Starter::{
61		ExtensionHostStarterCreate,
62		ExtensionHostStarterGetExitInfo,
63		ExtensionHostStarterKill,
64		ExtensionHostStarterStart,
65	},
66};
67use Sky::ReplayEvents::SkyReplayEvents;
68use TreeView::GetChildren::TreeGetChildren;
69use Update::UpdateService::{
70	UpdateApplyUpdate,
71	UpdateCheckForUpdates,
72	UpdateDownloadUpdate,
73	UpdateGetInitialState,
74	UpdateIsLatestVersion,
75	UpdateQuitAndInstall,
76};
77use Commands::*;
78use Configuration::*;
79use Encryption::{Decrypt::Decrypt, Encrypt::Encrypt};
80use Extensions::{
81	ExtensionsGet::ExtensionsGet,
82	ExtensionsGetAll::ExtensionsGetAll,
83	ExtensionsGetInstalled::ExtensionsGetInstalled,
84	ExtensionsIsActive::ExtensionsIsActive,
85};
86use FileSystem::{
87	Managed::{
88		FileCopy::*,
89		FileDelete::*,
90		FileExists::*,
91		FileMkdir::*,
92		FileMove::*,
93		FileRead::*,
94		FileReadBinary::*,
95		FileReaddir::*,
96		FileStat::*,
97		FileWrite::*,
98		FileWriteBinary::*,
99	},
100	Native::{
101		FileCloneNative::*,
102		FileCloseFd::FileCloseFd,
103		FileDeleteNative::*,
104		FileExistsNative::*,
105		FileMkdirNative::*,
106		FileOpenFd::FileOpenFd,
107		FileReadNative::*,
108		FileReaddirNative::*,
109		FileRealpath::*,
110		FileRenameNative::*,
111		FileStatNative::*,
112		FileUnwatch::FileUnwatch,
113		FileWatch::FileWatch,
114		FileWriteNative::*,
115	},
116};
117use Model::{
118	ModelClose::ModelClose,
119	ModelGet::ModelGet,
120	ModelGetAll::ModelGetAll,
121	ModelOpen::ModelOpen,
122	ModelUpdateContent::ModelUpdateContent,
123	TextfileRead::TextfileRead,
124	TextfileSave::TextfileSave,
125	TextfileWrite::TextfileWrite,
126};
127use NativeHost::{
128	Clipboard::{
129		NativeHasClipboard,
130		NativeReadClipboardBuffer,
131		NativeReadClipboardFindText,
132		NativeReadClipboardText,
133		NativeReadImage,
134		NativeTriggerPaste,
135		NativeWriteClipboardBuffer,
136		NativeWriteClipboardFindText,
137		NativeWriteClipboardText,
138	},
139	Exit::Exit,
140	FindFreePort::*,
141	GetColorScheme::*,
142	GetEnvironmentPaths::NativeGetEnvironmentPaths,
143	InstallShellCommand::InstallShellCommand,
144	IsFullscreen::*,
145	IsMaximized::*,
146	IsRunningUnderARM64Translation::NativeIsRunningUnderARM64Translation,
147	KillProcess::KillProcess,
148	MoveItemToTrash::NativeMoveItemToTrash,
149	OSProperties::*,
150	OSStatistics::*,
151	OpenDevTools::OpenDevTools,
152	OpenExternal::*,
153	PickFolder::*,
154	Quit::Quit,
155	Relaunch::Relaunch,
156	Reload::Reload,
157	ShowItemInFolder::*,
158	ShowMessageBox::NativeShowMessageBox,
159	ShowOpenDialog::*,
160	ShowSaveDialog::{NativeShowSaveDialog, UserInterfaceShowSaveDialog},
161	ToggleDevTools::ToggleDevTools,
162	UninstallShellCommand::UninstallShellCommand,
163};
164use Navigation::{
165	HistoryCanGoBack::HistoryCanGoBack,
166	HistoryCanGoForward::HistoryCanGoForward,
167	HistoryClear::HistoryClear,
168	HistoryGetStack::HistoryGetStack,
169	HistoryGoBack::HistoryGoBack,
170	HistoryGoForward::HistoryGoForward,
171	HistoryPush::HistoryPush,
172	LabelGetBase::LabelGetBase,
173	LabelGetURI::LabelGetURI,
174	LabelGetWorkspace::LabelGetWorkspace,
175};
176use Output::{
177	OutputAppend::OutputAppend,
178	OutputAppendLine::OutputAppendLine,
179	OutputClear::OutputClear,
180	OutputCreate::OutputCreate,
181	OutputShow::OutputShow,
182};
183use Search::*;
184use Storage::{
185	StorageDelete::StorageDelete,
186	StorageGet::StorageGet,
187	StorageGetItems::StorageGetItems,
188	StorageKeys::StorageKeys,
189	StorageSet::StorageSet,
190	StorageUpdateItems::StorageUpdateItems,
191};
192use Terminal::{
193	AttachToProcess::AttachToProcess,
194	DetachFromProcess::DetachFromProcess,
195	LocalPTYCreateProcess::LocalPTYCreateProcess,
196	LocalPTYFreePortKillProcess::LocalPTYFreePortKillProcess,
197	LocalPTYGetDefaultShell::LocalPTYGetDefaultShell,
198	LocalPTYGetEnvironment::LocalPTYGetEnvironment,
199	LocalPTYGetProfiles::LocalPTYGetProfiles,
200	LocalPTYResize::LocalPTYResize,
201	ReviveTerminalProcesses::ReviveTerminalProcesses,
202	SerializeTerminalState::SerializeTerminalState,
203	TerminalCreate::TerminalCreate,
204	TerminalDispose::TerminalDispose,
205	TerminalHide::TerminalHide,
206	TerminalSendText::TerminalSendText,
207	TerminalShow::TerminalShow,
208};
209use UI::{
210	Decoration::*,
211	Keybinding::*,
212	Lifecycle::*,
213	Notification::*,
214	Progress::*,
215	QuickInput::*,
216	Theme::*,
217	WorkingCopy::*,
218	Workspace::*,
219};
220use Utilities::{
221	ApplicationRoot::*,
222	ChannelPriority::*,
223	JsonValueHelpers::*,
224	MetadataEncoding::*,
225	PathExtraction::*,
226	RecentlyOpened::*,
227	UserdataDir::*,
228};
229use Echo::Task::Priority::Priority as EchoPriority;
230use serde_json::{Value, json};
231use tauri::{AppHandle, Manager};
232// Type aliases for Configuration DTOs to simplify usage
233use CommonLibrary::Configuration::DTO::{
234	ConfigurationOverridesDTO as ConfigurationOverridesDTOModule,
235	ConfigurationTarget as ConfigurationTargetModule,
236};
237
238use crate::dev_log;
239
240type ConfigurationOverridesDTO = ConfigurationOverridesDTOModule::ConfigurationOverridesDTO;
241
242type ConfigurationTarget = ConfigurationTargetModule::ConfigurationTarget;
243
244use CommonLibrary::{
245	Command::CommandExecutor::CommandExecutor,
246	Configuration::ConfigurationProvider::ConfigurationProvider,
247	Environment::Requires::Requires,
248	Error::CommonError::CommonError,
249	ExtensionManagement::ExtensionManagementService::ExtensionManagementService,
250	FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
251	IPC::SkyEvent::SkyEvent,
252	Storage::StorageProvider::StorageProvider,
253};
254
255use crate::{
256	ApplicationState::{
257		DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO,
258		State::{
259			ApplicationState::ApplicationState,
260			WorkspaceState::WorkspaceDelta::UpdateWorkspaceFoldersAndBroadcast,
261		},
262	},
263	RunTime::ApplicationRunTime::ApplicationRunTime,
264};
265
266/// Internal dispatcher for the single front-end Tauri command
267/// `MountainIPCInvoke` (registered in `Binary/Main/Entry.rs::invoke_handler!`,
268/// implemented in `Binary/IPC/InvokeCommand.rs`). The outer Tauri command
269/// receives `(method: String, params: Value)`, unwraps `params` into a
270/// `Vec<Value>`, then delegates here.
271///
272/// This function is **not** a Tauri command itself - removing the previously
273/// present `#[tauri::command]` attribute avoids the false impression that
274/// `mountain_ipc_invoke` is reachable from the webview under its snake-case
275/// name. All front-end callers (Wind, Sky, Output) must `invoke(
276/// "MountainIPCInvoke", { method, params })` through `InvokeCommand::
277/// MountainIPCInvoke`; this inner function is pure Rust-side plumbing.
278///
279/// The local parameter names (`command` / `Arguments`) are preserved for diff
280/// minimality; the frontend-facing contract (`method` / `params`) lives
281/// entirely in `InvokeCommand.rs`.
282pub async fn mountain_ipc_invoke(
283	ApplicationHandle:AppHandle,
284
285	command:String,
286
287	Arguments:Vec<Value>,
288) -> Result<Value, String> {
289	// Determine high-frequency status first - used to skip OTLP timing,
290	// dev-logs, span emission, and PostHog capture for noisy calls.
291	let IsHighFrequencyCommand = matches!(
292		command.as_str(),
293		"logger:log"
294			| "logger:info"
295			| "logger:debug"
296			| "logger:trace"
297			| "logger:warn"
298			| "logger:error"
299			| "logger:critical"
300			| "logger:flush"
301			| "logger:setLevel"
302			| "logger:getLevel"
303			| "logger:registerLogger"
304			| "logger:createLogger"
305			| "logger:deregisterLogger"
306			| "logger:getRegisteredLoggers"
307			| "logger:setVisibility"
308			| "log:registerLogger"
309			| "log:createLogger"
310			// File system - high-frequency VS Code workbench calls
311			| "file:stat"
312			| "file:readFile"
313			| "file:readdir"
314			| "file:writeFile"
315			| "file:delete"
316			| "file:rename"
317			| "file:realpath"
318			| "file:read"
319			| "file:write"
320			// Storage - polled constantly by VS Code services
321			| "storage:getItems"
322			| "storage:updateItems"
323			// Configuration - scoped-lookup hot path
324			| "configuration:lookup"
325			| "configuration:inspect"
326			// Themes - queried on every decoration/token change
327			| "themes:getColorTheme"
328			// Output/Progress - emitted in tight loops
329			| "output:append"
330			| "progress:report"
331			// Menubar - updated on every editor/selection change
332			| "menubar:updateMenubar"
333			// Ack-only event stubs - zero-cost dispatch
334			| "storage:onDidChangeItems"
335			| "storage:logStorage"
336			| "configuration:onDidChange"
337			| "workspaces:onDidChangeWorkspaceFolders"
338			| "workspaces:onDidChangeWorkspaceName"
339			// Command registry stubs
340			| "commands:registerCommand"
341			| "commands:unregisterCommand"
342			| "commands:onDidRegisterCommand"
343			| "commands:onDidExecuteCommand"
344	);
345
346	let OTLPStart = if IsHighFrequencyCommand { 0 } else { crate::IPC::DevLog::NowNano::Fn() };
347
348	// Silence the per-call invoke log for high-frequency methods that are
349	// not useful in forensic review. The workbench emits thousands of
350	// `logger:log` invocations per boot (every `console.*` call inside VS
351	// Code code becomes an IPC round-trip); keeping those lines only
352	// expands log volume without adding signal. The actual dispatch below
353	// still runs - this just skips the `[DEV:IPC] invoke:` line.
354
355	if !IsHighFrequencyCommand {
356		dev_log!("ipc", "invoke: {} args_count={}", command, Arguments.len());
357	}
358
359	// Ensure userdata directories exist on first IPC call
360	ensure_userdata_dirs();
361
362	// Get the application RunTime - deref the Tauri State into an owned Arc
363	// so we can hand it to an Echo scheduler task below (State<T> isn't
364	// Send across task boundaries).
365	let RunTime:Arc<ApplicationRunTime> = ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
366
367	// Short-circuit known no-op commands BEFORE Echo scheduler submission
368	// to avoid oneshot channel allocation, String clone, and scheduler
369	// overhead for calls that return Ok(Value::Null) unconditionally.
370	// These account for the bulk of high-frequency IPC traffic (logger,
371	// file watch, storage events, command registration).
372	if IsHighFrequencyCommand {
373		match command.as_str() {
374			// Logger: forward error/warn/critical to dev_log; drop the rest.
375			// `logger:log` (info/debug/trace) fires thousands of times per boot
376			// from VS Code console.* calls - we gate those to `vscode-log`
377			// which is opt-in. Errors and warnings are always surfaced.
378			"logger:error" | "logger:critical" => {
379				let Msg = Arguments.get(1).and_then(|V| V.as_str()).unwrap_or(
380					Arguments.first().and_then(|V| V.as_str()).unwrap_or(""),
381				);
382				if !Msg.is_empty() {
383					dev_log!("vscode-log", "[ERROR] {}", Msg);
384				}
385				return Ok(Value::Null);
386			},
387			"logger:warn" => {
388				let Msg = Arguments.get(1).and_then(|V| V.as_str()).unwrap_or(
389					Arguments.first().and_then(|V| V.as_str()).unwrap_or(""),
390				);
391				if !Msg.is_empty() {
392					dev_log!("vscode-log", "[WARN] {}", Msg);
393				}
394				return Ok(Value::Null);
395			},
396			"logger:log" | "logger:info" | "logger:debug" | "logger:trace"
397			| "logger:flush" | "logger:setLevel" | "logger:getLevel"
398			| "logger:createLogger" | "logger:registerLogger"
399			| "logger:deregisterLogger" | "logger:getRegisteredLoggers"
400			| "logger:setVisibility"
401			// Legacy log-service stubs: VS Code 1.87+ calls `log:registerLogger`
402			// / `log:createLogger` (short prefix) in addition to the `logger:*`
403			// family. Both are registered in Channel.rs so the "registered but no
404			// dispatch arm" error fired on every boot. Stub-ack here alongside the
405			// logger:* group.
406			| "log:registerLogger" | "log:createLogger"
407			// Storage event stubs: change delivery via Tauri events
408			| "storage:onDidChangeItems" | "storage:logStorage"
409			// Command registry stubs: side effects handled via gRPC
410			| "commands:registerCommand" | "commands:unregisterCommand"
411			| "commands:onDidRegisterCommand" | "commands:onDidExecuteCommand"
412			// Configuration event stub
413			| "configuration:onDidChange"
414			// Storage lifecycle stubs
415			| "storage:optimize" | "storage:isUsed" | "storage:close"
416			// Workspace event stubs: change delivery via Tauri events
417			| "workspaces:onDidChangeWorkspaceFolders"
418			| "workspaces:onDidChangeWorkspaceName" => {
419				return Ok(Value::Null);
420			},
421			// Menubar: acknowledged with atomic counter in the Echo path,
422			// but fast-path here to save scheduler overhead per call.
423			"menubar:updateMenubar" => {
424				use std::sync::atomic::{AtomicU64, Ordering as AO};
425
426				static MENUBAR_CALLS_FAST:AtomicU64 = AtomicU64::new(0);
427
428				let N = MENUBAR_CALLS_FAST.fetch_add(1, AO::Relaxed) + 1;
429
430				if N == 1 || N % 100 == 0 {
431					dev_log!("menubar", "menubar:updateMenubar (fast-path call #{})", N);
432				}
433
434				return Ok(Value::Null);
435			},
436			_ => {}, // fall through to Echo dispatch for real work
437		}
438	}
439
440	// Tag the pending IPC with its priority lane and submit the entire
441	// Tags match the route prefix: vfs, config, storage, extensions,
442	// terminal, output, textfile, notification, progress, quickinput,
443	// workspaces, themes, search, decorations, workingcopy, keybinding,
444	// lifecycle, label, model, history, commands, nativehost, window,
445	// exthost, encryption, menubar, update, url, grpc.
446	// Activate: Trace=all   or   Trace=vfs,ipc,config
447	//
448	// Atom O1 + O3: every invoke flows through `SubmitToEcho` below so the
449	// Echo work-stealing scheduler picks a lane based on `Channel::Priority()`.
450	// The dispatch match still runs inline - Echo's real value is queuing
451	// decisions under load, not moving a single future across threads. This
452	// keeps the 4900-line match legible while guaranteeing every inbound
453	// IPC hits the scheduler's priority machinery on its way out.
454	// =========================================================================
455
456	// Tag the pending IPC with its priority lane and submit the entire
457	// dispatch future to Echo. Results flow back through a oneshot channel
458	// so the Tauri caller still awaits a plain `Result<Value, String>`.
459	let CommandPriority = ResolveChannelPriority(&command);
460
461	let Scheduler = RunTime.Scheduler.clone();
462
463	let (ResultSender, ResultReceiver) = tokio::sync::oneshot::channel::<Result<Value, String>>();
464
465	let DispatchAppHandle = ApplicationHandle.clone();
466
467	let DispatchRuntime = RunTime.clone();
468
469	let DispatchCommand = command.clone();
470
471	let DispatchArgs = Arguments;
472
473	Scheduler.Submit(
474		async move {
475			let ApplicationHandle = DispatchAppHandle;
476			let RunTime = DispatchRuntime;
477			let command = DispatchCommand;
478			let Arguments = DispatchArgs;
479
480			let MatchResult = match command.as_str() {
481				// Configuration commands. VS Code's stock
482				// `ConfigurationService` channel calls `getValue` /
483				// `updateValue`; Mountain's native Effect-TS layer calls
484				// `get` / `update`. Alias both to the same handler so
485				// traffic from either rail lands in the same place.
486				"configuration:get" | "configuration:getValue" => {
487					dev_log!("config", "{}", command);
488					ConfigurationGet(RunTime.clone(), Arguments).await
489				},
490				"configuration:update" | "configuration:updateValue" => {
491					dev_log!("config", "{}", command);
492					ConfigurationUpdate(RunTime.clone(), Arguments).await
493				},
494				// `ConfigurationService` listens for `onDidChange` from
495				// the channel on the binary IPC rail. Mountain broadcasts
496				// config changes via a Tauri event directly; ack the
497				// channel-listen with Null so the ChannelClient doesn't
498				// leak a pending promise.
499				"configuration:onDidChange" => Ok(Value::Null),
500
501				// `configuration:lookup` is VS Code's
502				// `IConfigurationService.getValue(key)` called from the
503				// workbench's `ConfigurationService` singleton. Wire shape is
504				// identical to `configuration:get`; alias so both rails resolve
505				// the same underlying value.
506				"configuration:lookup" => {
507					dev_log!("config", "configuration:lookup (→ get)");
508					ConfigurationGet(RunTime.clone(), Arguments).await
509				},
510
511				// `configuration:inspect` is `IConfigurationService.inspect(key)`.
512				// The workbench destructures `{ value, default, user, workspace,
513				// workspaceFolder }` from the result unconditionally; returning a
514				// plain value or null crashes the Settings UI. We surface the
515				// current effective value in both `value` and `default` (since
516				// Mountain has no per-scope override layer yet) and null for the
517				// remaining scopes. VS Code treats null scopes as "not set",
518				// which is correct for Land where no user/workspace JSON overrides
519				// exist.
520				"configuration:inspect" => {
521					dev_log!("config", "configuration:inspect");
522					let CurrentValue = ConfigurationGet(RunTime.clone(), Arguments).await.unwrap_or(Value::Null);
523					Ok(json!({
524						"value": CurrentValue,
525						"default": CurrentValue,
526						"user": Value::Null,
527						"workspace": Value::Null,
528						"workspaceFolder": Value::Null,
529						"memory": Value::Null,
530					}))
531				},
532
533				// Logger commands - all logger:* are high-frequency and handled in the
534				// fast-path short-circuit above. These Echo arms are only reached
535				// if IS_HIGH_FREQUENCY detection changes; they provide the same
536				// dev_log output as the fast-path for safety.
537				"logger:log" | "logger:warn" | "logger:error" | "logger:info" | "logger:debug" | "logger:trace" => {
538					let Level = command.trim_start_matches("logger:");
539					let Msg = if Arguments.len() >= 2 {
540						let Tail:Vec<String> = Arguments
541							.iter()
542							.skip(1)
543							.filter_map(|V| V.as_str().map(str::to_owned).or_else(|| serde_json::to_string(V).ok()))
544							.collect();
545						Tail.join(" ")
546					} else {
547						Arguments
548							.first()
549							.and_then(|V| V.as_str().map(str::to_owned))
550							.unwrap_or_default()
551					};
552					if !Msg.is_empty() {
553						match Level {
554							"error" | "critical" => dev_log!("vscode-log", "[ERROR] {}", Msg),
555							"warn" => dev_log!("vscode-log", "[WARN] {}", Msg),
556							_ => dev_log!("vscode-log", "{}", Msg),
557						}
558					}
559					Ok(Value::Null)
560				},
561				"logger:flush"
562				| "logger:setLevel"
563				| "logger:getLevel"
564				| "logger:createLogger"
565				| "logger:registerLogger"
566				| "logger:deregisterLogger"
567				| "logger:getRegisteredLoggers"
568				| "logger:setVisibility" => Ok(Value::Null),
569
570				// File system commands - use native handlers with URI support.
571				//
572				// The primary names (`file:read`, `file:write`, `file:move`)
573				// match Mountain's original dispatch table and are what
574				// Wind's Effect-TS layer calls. VS Code's
575				// `DiskFileSystemProviderClient` (reached through the
576				// binary IPC bridge in Output/IPCRendererShim) uses the
577				// stock channel-client method names `readFile`,
578				// `writeFile`, `rename`; aliasing them here keeps both
579				// rails pointing at the same handler without duplicating
580				// logic or introducing a per-caller translation table.
581				"file:read" | "file:readFile" => FileReadNative(Arguments).await,
582				"file:write" | "file:writeFile" => FileWriteNative(Arguments).await,
583				"file:stat" => FileStatNative(Arguments).await,
584				"file:exists" => FileExistsNative(Arguments).await,
585				"file:delete" => FileDeleteNative(Arguments).await,
586				"file:copy" => FileCloneNative(Arguments).await,
587				"file:move" | "file:rename" => FileRenameNative(Arguments).await,
588				"file:mkdir" => FileMkdirNative(Arguments).await,
589				"file:readdir" => FileReaddirNative(Arguments).await,
590				"file:readBinary" => FileReadBinary(RunTime.clone(), Arguments).await,
591				"file:writeBinary" => FileWriteBinary(RunTime.clone(), Arguments).await,
592				// File watcher channel methods - `DiskFileSystemProvider`
593				// opens `watch` / `unwatch` channel calls to receive
594				"file:watch" => FileWatch(RunTime.clone(), Arguments).await,
595				"file:unwatch" => FileUnwatch(RunTime.clone(), Arguments).await,
596
597				// Storage commands. VS Code's
598				// `ApplicationStorageDatabaseClient` channel methods are
599				// `getItems` / `updateItems` / `optimize` / `close` /
600				// `isUsed`; the shorter `storage:get` / `storage:set` are
601				// Mountain-native conveniences. All route through the
602				// same ApplicationState storage backing.
603				"storage:get" => StorageGet(RunTime.clone(), Arguments).await,
604				"storage:set" => StorageSet(RunTime.clone(), Arguments).await,
605				"storage:getItems" => {
606					// Workbench services poll this on every theme / scope
607					// change; suppress the bare banner and rely on the IPC
608					// `invoke:`/`done:` summary for volume + latency.
609					dev_log!("storage-verbose", "storage:getItems");
610					StorageGetItems(RunTime.clone(), Arguments).await
611				},
612				"storage:updateItems" => {
613					dev_log!("storage-verbose", "storage:updateItems");
614					StorageUpdateItems(RunTime.clone(), Arguments).await
615				},
616				"storage:optimize" => {
617					dev_log!("storage", "storage:optimize");
618					Ok(Value::Null)
619				},
620				"storage:isUsed" => {
621					dev_log!("storage", "storage:isUsed");
622					Ok(Value::Null)
623				},
624				"storage:close" => {
625					dev_log!("storage", "storage:close");
626					Ok(Value::Null)
627				},
628				// Stock VS Code exposes `onDidChangeItems` as a channel
629				// event. Ack the listen-request; real change delivery is
630				// via Tauri event elsewhere.
631				"storage:onDidChangeItems" | "storage:logStorage" => {
632					dev_log!("storage-verbose", "{} (stub-ack)", command);
633					Ok(Value::Null)
634				},
635
636				// Environment commands
637				"environment:get" => {
638					dev_log!("config", "environment:get");
639					EnvironmentGet(RunTime.clone(), Arguments).await
640				},
641
642				// Native host commands
643				"native:showItemInFolder" => ShowItemInFolder(RunTime.clone(), Arguments).await,
644				"native:openExternal" => OpenExternal(RunTime.clone(), Arguments).await,
645
646				// Workbench commands
647				"workbench:getConfiguration" => WorkbenchConfiguration(RunTime.clone(), Arguments).await,
648
649				// Diagnostic: webview → Mountain dev-log bridge.
650				// First arg is a tag ("boot", "extService", …), second is the
651				// message, rest are optional structured fields we stringify.
652				// Atom H1c: added so workbench.js can surface diagnostic state
653				// into the same Mountain.dev.log that carries Rust-side events.
654				"diagnostic:log" => {
655					let Tag = Arguments.first().and_then(|V| V.as_str()).unwrap_or("webview").to_string();
656					let Message = Arguments.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
657					let Extras = if Arguments.len() > 2 {
658						let Tail:Vec<String> = Arguments
659							.iter()
660							.skip(2)
661							.map(|V| {
662								let S = serde_json::to_string(V).unwrap_or_default();
663								// Char-aware truncation - JSON-encoded values may
664								// embed multi-byte UTF-8 (extension names, repo
665								// paths with non-ASCII, debug payloads). Slicing
666								// at a fixed byte offset can land mid-codepoint
667								// and panic the tokio worker.
668								if S.len() > 240 {
669									let CutAt = S
670										.char_indices()
671										.map(|(Index, _)| Index)
672										.take_while(|Index| *Index <= 240)
673										.last()
674										.unwrap_or(0);
675									format!("{}…", &S[..CutAt])
676								} else {
677									S
678								}
679							})
680							.collect();
681						format!(" {}", Tail.join(" "))
682					} else {
683						String::new()
684					};
685					dev_log!("diagnostic", "[{}] {}{}", Tag, Message, Extras);
686					Ok(Value::Null)
687				},
688
689				// Command registry commands. Stock VS Code
690				// `MainThreadCommands` / `CommandService` channel methods
691				// are `executeCommand` and `getCommands`; Mountain's
692				// Effect-TS rail uses `execute` / `getAll`. Alias both.
693				"commands:execute" | "commands:executeCommand" => CommandsExecute(RunTime.clone(), Arguments).await,
694				"commands:getAll" | "commands:getCommands" => {
695					dev_log!("commands", "{}", command);
696					CommandsGetAll(RunTime.clone()).await
697				},
698				// Register/unregister from a side-car channel perspective
699				// is a no-op: Cocoon sends `$registerCommand` via gRPC
700				// (handled elsewhere). Ack Null so the workbench side
701				// doesn't hang on a promise.
702				"commands:registerCommand"
703				| "commands:unregisterCommand"
704				| "commands:onDidRegisterCommand"
705				| "commands:onDidExecuteCommand" => Ok(Value::Null),
706
707				// Extension host commands
708				"extensions:getAll" => {
709					dev_log!("extensions", "extensions:getAll");
710					ExtensionsGetAll(RunTime.clone()).await
711				},
712				"extensions:get" => {
713					dev_log!("extensions", "extensions:get");
714					ExtensionsGet(RunTime.clone(), Arguments).await
715				},
716				"extensions:isActive" => {
717					dev_log!("extensions", "extensions:isActive");
718					ExtensionsIsActive(RunTime.clone(), Arguments).await
719				},
720				// `extensions:activate(extensionId)` - send `$activateByEvent`
721				// to Cocoon so the extension host starts the extension. VS Code
722				// normally drives activation via the workbench's activation events
723				// (onStartupFinished, onLanguage:*, etc.); this path lets Wind's
724				// ExtensionsService trigger activation programmatically.
725				"extensions:activate" => {
726					let ExtensionId = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
727					dev_log!("extensions", "extensions:activate id={}", ExtensionId);
728					if ExtensionId.is_empty() {
729						Ok(Value::Null)
730					} else {
731						let Notification = json!({
732							"event": format!("onCustom:{}", ExtensionId),
733							"extensionId": ExtensionId,
734						});
735						let _ = crate::Vine::Client::SendNotification::Fn(
736							"cocoon-main".to_string(),
737							"$activateByEvent".to_string(),
738							Notification,
739						)
740						.await;
741						Ok(Value::Null)
742					}
743				},
744
745				// VS Code's Extensions sidebar →
746				// `ExtensionManagementChannelClient.getInstalled` goes through
747				// `sharedProcessService.getChannel('extensions')`. Sky's
748				// astro.config.ts Step 7b swaps the native SharedProcessService
749				// for a TauriMainProcessService-backed shim, so the call lands
750				// here as `extensions:getInstalled`. The expected return is
751				// `ILocalExtension[]` - a wrapper around each scanned manifest
752				// with `identifier.id`, `manifest`, `location`, `isBuiltin`, etc.
753				// `ExtensionsGetInstalled` builds that envelope;
754				// `ExtensionsGetAll` returns the raw manifest for
755				// callers (Cocoon, Wind Effect services) that want the flat
756				// shape. Do NOT alias these two - the payload shapes differ.
757				"extensions:getInstalled" | "extensions:scanSystemExtensions" => {
758					// Atom H1a: Arguments[0]=type, Arguments[1]=profileLocation URI,
759					// Arguments[2]=productVersion, Arguments[3]=??? (VS Code canonical is
760					// 3; shim appears to add a 4th). Dump to find out what it
761					// contains on post-nav page reloads where the sidebar
762					// renders 0 entries despite Mountain returning 94.
763					let ArgsSummary = Arguments
764						.iter()
765						.enumerate()
766						.map(|(Idx, V)| {
767							let Preview = serde_json::to_string(V).unwrap_or_default();
768							// Char-aware truncation - same UTF-8 hazard as
769							// the diagnostic-tag formatter above.
770							let Trimmed = if Preview.len() > 180 {
771								let CutAt = Preview
772									.char_indices()
773									.map(|(Index, _)| Index)
774									.take_while(|Index| *Index <= 180)
775									.last()
776									.unwrap_or(0);
777								format!("{}…", &Preview[..CutAt])
778							} else {
779								Preview
780							};
781							format!("[{}]={}", Idx, Trimmed)
782						})
783						.collect::<Vec<_>>()
784						.join(" ");
785					dev_log!("extensions", "{} Arguments={}", command, ArgsSummary);
786					// `scanSystemExtensions` is conceptually
787					// `getInstalled(type=ExtensionType.System)`, so override
788					// `Arguments[0]` to `0` before forwarding. Without the override
789					// a plain alias would inherit whatever the caller passed
790					// in Arguments[0] (which for the VS Code channel client is
791					// usually `null`) and leak User extensions into the
792					// System list - the same bug we just fixed at the
793					// handler layer, one level up.
794					let EffectiveArgs = if command == "extensions:scanSystemExtensions" {
795						let mut Overridden = Arguments.clone();
796						if Overridden.is_empty() {
797							Overridden.push(Value::Null);
798						}
799						Overridden[0] = json!(0);
800						Overridden
801					} else {
802						Arguments.clone()
803					};
804					ExtensionsGetInstalled(RunTime.clone(), EffectiveArgs).await
805				},
806				"extensions:scanUserExtensions" => {
807					// User-scope scan. Forward to the unified handler with
808					// `type=ExtensionType.User (1)` so VSIX-installed
809					// extensions under `~/.fiddee/extensions/*` come back
810					// even when the caller didn't pass an explicit type
811					// filter (VS Code's channel client does that on
812					// scan-user-extensions, which is why the sidebar
813					// previously saw an empty list after every
814					// Install-from-VSIX).
815					dev_log!("extensions", "{} (forwarded to getInstalled with type=User)", command);
816					let mut UserArgs = Arguments.clone();
817					if UserArgs.is_empty() {
818						UserArgs.push(Value::Null);
819					}
820					UserArgs[0] = json!(1);
821					ExtensionsGetInstalled(RunTime.clone(), UserArgs).await
822				},
823				"extensions:getUninstalled" => {
824					// Uninstalled state (extensions soft-deleted but kept in
825					// the profile) isn't tracked yet; an empty array is the
826					// correct "nothing pending uninstall" response.
827					dev_log!("extensions", "{} (returning [])", command);
828					Ok(Value::Array(Vec::new()))
829				},
830				// Gallery is offline: Mountain has no marketplace backend. Return
831				// empty arrays for every read and swallow every write, which
832				// mirrors what a network-air-gapped VS Code session shows.
833				"extensions:query" | "extensions:getExtensions" | "extensions:getRecommendations" => {
834					dev_log!("extensions", "{} (offline gallery - returning [])", command);
835					Ok(Value::Array(Vec::new()))
836				},
837				// `IExtensionsControlManifest` - consulted by the Extensions
838				// sidebar on every render (ExtensionEnablementService.ts:793)
839				// to mark malicious / deprecated / auto-updateable entries.
840				// With the gallery offline an empty envelope is correct; the
841				// shape (not null) matters - VS Code destructures each field.
842				"extensions:getExtensionsControlManifest" => {
843					dev_log!("extensions", "{} (offline gallery - empty manifest)", command);
844					Ok(json!({
845						"malicious": [],
846						"deprecated": {},
847						"search": [],
848						"autoUpdate": {},
849					}))
850				},
851				// Atom P1: `ExtensionsWorkbenchService.resetPinnedStateForAllUserExtensions`
852				// is invoked when the user toggles pinning semantics in the
853				// sidebar. Pin state is Wind-owned (Cocoon never sees it); the
854				// only Mountain-side cost is an acknowledgement so the
855				// extension-enablement service doesn't retry forever. Payload
856				// is optional - VS Code sometimes passes `{ refreshPinned: true }`.
857				"extensions:resetPinnedStateForAllUserExtensions" => {
858					dev_log!("extensions", "{} (no-op, pin state is UI-local)", command);
859					Ok(Value::Null)
860				},
861				// Atom K2: local VSIX install. Wind passes the file path from a
862				// "Install from VSIX…" prompt or drag-and-drop through to us; the
863				// previous stub silently returned `null` and the UI believed it
864				// had succeeded (that's the "VSIX isn't triggering or loading"
865				// regression). We now unpack the archive, stamp a DTO, register
866				// it in ScannedExtensions, and return the ILocalExtension wrapper
867				// so the sidebar refreshes without a window reload.
868				"extensions:install" => {
869					Extension::ExtensionInstall::ExtensionInstall(ApplicationHandle.clone(), RunTime.clone(), Arguments)
870						.await
871				},
872				"extensions:uninstall" => {
873					Extension::ExtensionUninstall::ExtensionUninstall(
874						ApplicationHandle.clone(),
875						RunTime.clone(),
876						Arguments,
877					)
878					.await
879				},
880
881				// `ExtensionManagementChannelClient.getManifest(vsix: URI)` - reads
882				// the `extension/package.json` from a `.vsix` archive without
883				// extracting it. Called by the "Install from VSIX…" preview and
884				// by drag-and-drop onto the Extensions sidebar. The renderer then
885				// accesses `manifest.publisher` / `.name` / `.displayName` on the
886				// returned object unconditionally; a missing handler or an Err
887				// response crashes the webview with
888				// `TypeError: undefined is not an object (evaluating 'manifest.publisher')`.
889				"extensions:getManifest" => {
890					let VsixPath = match Arguments.first() {
891						Some(serde_json::Value::String(Path)) => Path.clone(),
892						Some(Obj) => {
893							Obj.get("fsPath")
894								.and_then(|V| V.as_str())
895								.map(str::to_owned)
896								.or_else(|| Obj.get("path").and_then(|V| V.as_str()).map(str::to_owned))
897								.unwrap_or_default()
898						},
899						None => String::new(),
900					};
901					dev_log!("extensions", "extensions:getManifest vsix={}", VsixPath);
902					if VsixPath.is_empty() {
903						Err("extensions:getManifest: missing VSIX path argument".to_string())
904					} else {
905						let Path = std::path::PathBuf::from(&VsixPath);
906						match crate::ExtensionManagement::VsixInstaller::ReadFullManifest(&Path) {
907							Ok(Manifest) => Ok(Manifest),
908							Err(Error) => {
909								dev_log!(
910									"extensions",
911									"warn: [WindServiceHandlers] extensions:getManifest failed for '{}': {}",
912									VsixPath,
913									Error
914								);
915								Err(format!("extensions:getManifest failed: {}", Error))
916							},
917						}
918					}
919				},
920				// Reinstall and metadata-update still no-op for now; reinstall needs
921				// a gallery cache (we only have the on-disk unpack), and metadata
922				// update only matters for ratings/icons/readme which Land does not
923				// track. Left as explicit logs so the UI doesn't silently fail.
924				"extensions:reinstall" | "extensions:updateMetadata" => {
925					dev_log!("extensions", "{} (no-op: no gallery backend)", command);
926					Ok(Value::Null)
927				},
928
929				// Terminal commands
930				"terminal:create" => {
931					dev_log!("terminal", "terminal:create");
932					TerminalCreate(RunTime.clone(), Arguments).await
933				},
934				"terminal:sendText" => {
935					dev_log!("terminal", "terminal:sendText");
936					TerminalSendText(RunTime.clone(), Arguments).await
937				},
938				"terminal:dispose" => {
939					dev_log!("terminal", "terminal:dispose");
940					TerminalDispose(RunTime.clone(), Arguments).await
941				},
942				"terminal:show" => {
943					dev_log!("terminal", "terminal:show");
944					TerminalShow(RunTime.clone(), Arguments).await
945				},
946				"terminal:hide" => {
947					dev_log!("terminal", "terminal:hide");
948					TerminalHide(RunTime.clone(), Arguments).await
949				},
950
951				// Output channel commands
952				"output:create" => OutputCreate(ApplicationHandle.clone(), Arguments).await,
953				"output:append" => {
954					dev_log!("output", "output:append");
955					OutputAppend(ApplicationHandle.clone(), Arguments).await
956				},
957				"output:appendLine" => {
958					dev_log!("output", "output:appendLine");
959					OutputAppendLine(ApplicationHandle.clone(), Arguments).await
960				},
961				"output:clear" => {
962					dev_log!("output", "output:clear");
963					OutputClear(ApplicationHandle.clone(), Arguments).await
964				},
965				"output:show" => {
966					dev_log!("output", "output:show");
967					OutputShow(ApplicationHandle.clone(), Arguments).await
968				},
969
970				// TextFile commands
971				"textFile:read" => {
972					dev_log!("textfile", "textFile:read");
973					TextfileRead(RunTime.clone(), Arguments).await
974				},
975				"textFile:write" => {
976					dev_log!("textfile", "textFile:write");
977					TextfileWrite(RunTime.clone(), Arguments).await
978				},
979				"textFile:save" => TextfileSave(RunTime.clone(), Arguments).await,
980
981				// Storage commands (additional)
982				"storage:delete" => {
983					dev_log!("storage", "storage:delete");
984					StorageDelete(RunTime.clone(), Arguments).await
985				},
986				"storage:keys" => {
987					dev_log!("storage", "storage:keys");
988					StorageKeys(RunTime.clone()).await
989				},
990
991				// Notification commands (emit sky:// events for Sky to render)
992				"notification:show" => {
993					dev_log!("notification", "notification:show");
994					NotificationShow(ApplicationHandle.clone(), Arguments).await
995				},
996				"notification:showProgress" => {
997					dev_log!("notification", "notification:showProgress");
998					NotificationShowProgress(ApplicationHandle.clone(), Arguments).await
999				},
1000				"notification:updateProgress" => {
1001					dev_log!("notification", "notification:updateProgress");
1002					NotificationUpdateProgress(ApplicationHandle.clone(), Arguments).await
1003				},
1004				"notification:endProgress" => {
1005					dev_log!("notification", "notification:endProgress");
1006					NotificationEndProgress(ApplicationHandle.clone(), Arguments).await
1007				},
1008
1009				// Progress commands
1010				"progress:begin" => {
1011					dev_log!("progress", "progress:begin");
1012					ProgressBegin(ApplicationHandle.clone(), Arguments).await
1013				},
1014				"progress:report" => {
1015					dev_log!("progress", "progress:report");
1016					ProgressReport(ApplicationHandle.clone(), Arguments).await
1017				},
1018				"progress:end" => {
1019					dev_log!("progress", "progress:end");
1020					ProgressEnd(ApplicationHandle.clone(), Arguments).await
1021				},
1022
1023				// QuickInput commands
1024				"quickInput:showQuickPick" => {
1025					dev_log!("quickinput", "quickInput:showQuickPick");
1026					QuickInputShowQuickPick(RunTime.clone(), Arguments).await
1027				},
1028				"quickInput:showInputBox" => {
1029					dev_log!("quickinput", "quickInput:showInputBox");
1030					QuickInputShowInputBox(RunTime.clone(), Arguments).await
1031				},
1032
1033				// Workspaces commands. VS Code's `IWorkspacesService`
1034				// channel uses `getWorkspaceFolders` /
1035				// `addWorkspaceFolders`; Mountain's rail uses the
1036				// shorter `getFolders` / `addFolder`. Alias both.
1037				"workspaces:getFolders" | "workspaces:getWorkspaceFolders" | "workspaces:getWorkspace" => {
1038					dev_log!("workspaces", "{}", command);
1039					WorkspacesGetFolders(RunTime.clone()).await
1040				},
1041				"workspaces:addFolder" | "workspaces:addWorkspaceFolders" => {
1042					dev_log!("workspaces", "{}", command);
1043					WorkspacesAddFolder(RunTime.clone(), Arguments).await
1044				},
1045				"workspaces:removeFolder" | "workspaces:removeWorkspaceFolders" => {
1046					dev_log!("workspaces", "{}", command);
1047					WorkspacesRemoveFolder(RunTime.clone(), Arguments).await
1048				},
1049				"workspaces:getName" => {
1050					dev_log!("workspaces", "{}", command);
1051					WorkspacesGetName(RunTime.clone()).await
1052				},
1053				// Themes commands
1054				"themes:getActive" => {
1055					dev_log!("themes", "themes:getActive");
1056					ThemesGetActive(RunTime.clone()).await
1057				},
1058				"themes:list" => {
1059					dev_log!("themes", "themes:list");
1060					ThemesList(RunTime.clone()).await
1061				},
1062				"themes:set" => {
1063					dev_log!("themes", "themes:set");
1064					ThemesSet(RunTime.clone(), Arguments).await
1065				},
1066				// `IThemeService.getColorTheme()` - workbench channel method used
1067				// by tokenization, decoration, and the colour-picker to read the
1068				// active theme object. Wire shape differs from `themes:getActive`
1069				// only in name; alias to the same handler.
1070				"themes:getColorTheme" => {
1071					dev_log!("themes", "themes:getColorTheme (→ getActive)");
1072					ThemesGetActive(RunTime.clone()).await
1073				},
1074
1075				// Search commands. Stock VS Code `SearchService` channel
1076				// uses `textSearch` / `fileSearch`; Mountain's Effect-TS
1077				// rail uses `findInFiles` / `findFiles`. Alias both.
1078				"search:findInFiles" | "search:textSearch" | "search:searchText" => {
1079					dev_log!("search", "{}", command);
1080					SearchFindInFiles(RunTime.clone(), Arguments).await
1081				},
1082				"search:findFiles" | "search:fileSearch" | "search:searchFile" => {
1083					dev_log!("search", "{}", command);
1084					SearchFindFiles(RunTime.clone(), Arguments).await
1085				},
1086				// Cancellation / onProgress channel methods: workbench's
1087				// SearchService listens for these. We have no streaming
1088				// search yet, so ack with Null and let the workbench
1089				// treat the call as a no-op.
1090				"search:cancel" | "search:clearCache" | "search:onDidChangeResult" => {
1091					dev_log!("search", "{} (stub-ack)", command);
1092					Ok(Value::Null)
1093				},
1094
1095				// Decorations commands
1096				"decorations:get" => {
1097					dev_log!("decorations", "decorations:get");
1098					DecorationsGet(RunTime.clone(), Arguments).await
1099				},
1100				"decorations:getMany" => {
1101					dev_log!("decorations", "decorations:getMany");
1102					DecorationsGetMany(RunTime.clone(), Arguments).await
1103				},
1104				"decorations:set" => {
1105					dev_log!("decorations", "decorations:set");
1106					DecorationsSet(RunTime.clone(), Arguments).await
1107				},
1108				"decorations:clear" => {
1109					dev_log!("decorations", "decorations:clear");
1110					DecorationsClear(RunTime.clone(), Arguments).await
1111				},
1112
1113				// WorkingCopy commands
1114				"workingCopy:isDirty" => {
1115					dev_log!("workingcopy", "workingCopy:isDirty");
1116					WorkingCopyIsDirty(RunTime.clone(), Arguments).await
1117				},
1118				"workingCopy:setDirty" => {
1119					dev_log!("workingcopy", "workingCopy:setDirty");
1120					WorkingCopySetDirty(RunTime.clone(), Arguments).await
1121				},
1122				"workingCopy:getAllDirty" => {
1123					dev_log!("workingcopy", "workingCopy:getAllDirty");
1124					WorkingCopyGetAllDirty(RunTime.clone()).await
1125				},
1126				"workingCopy:getDirtyCount" => {
1127					dev_log!("workingcopy", "workingCopy:getDirtyCount");
1128					WorkingCopyGetDirtyCount(RunTime.clone()).await
1129				},
1130
1131				// Keybinding commands
1132				"keybinding:add" => {
1133					dev_log!("keybinding", "keybinding:add");
1134					KeybindingAdd(RunTime.clone(), Arguments).await
1135				},
1136				"keybinding:remove" => {
1137					dev_log!("keybinding", "keybinding:remove");
1138					KeybindingRemove(RunTime.clone(), Arguments).await
1139				},
1140				"keybinding:lookup" => {
1141					dev_log!("keybinding", "keybinding:lookup");
1142					KeybindingLookup(RunTime.clone(), Arguments).await
1143				},
1144				"keybinding:getAll" => {
1145					dev_log!("keybinding", "keybinding:getAll");
1146					KeybindingGetAll(RunTime.clone()).await
1147				},
1148
1149				// Lifecycle commands
1150				"lifecycle:getPhase" => {
1151					dev_log!("lifecycle", "lifecycle:getPhase");
1152					LifecycleGetPhase(RunTime.clone()).await
1153				},
1154				"lifecycle:whenPhase" => {
1155					dev_log!("lifecycle", "lifecycle:whenPhase");
1156					LifecycleWhenPhase(RunTime.clone(), Arguments).await
1157				},
1158				"lifecycle:requestShutdown" => {
1159					dev_log!("lifecycle", "lifecycle:requestShutdown");
1160					LifecycleRequestShutdown(ApplicationHandle.clone()).await
1161				},
1162				"lifecycle:advancePhase" | "lifecycle:setPhase" => {
1163					dev_log!("lifecycle", "{}", command);
1164					// Wind calls this at the end of every workbench init pass so
1165					// the phase advances Starting → Ready → Restored → Eventually.
1166					// Mountain emits `sky://lifecycle/phaseChanged` so any extension
1167					// host or service waiting on a later phase wakes up.
1168					let NewPhase = Arguments.first().and_then(|V| V.as_u64()).unwrap_or(1) as u8;
1169					RunTime
1170						.Environment
1171						.ApplicationState
1172						.Feature
1173						.Lifecycle
1174						.AdvanceAndBroadcast(NewPhase, &ApplicationHandle);
1175
1176					// Hidden-until-ready: the main window is built with
1177					// `.visible(false)` to suppress the four-repaint flash
1178					// (native chrome → inline bg → theme CSS → workbench
1179					// DOM). Phase 3 = Restored means `.monaco-workbench`
1180					// is attached and the first frame is painted; show
1181					// the window now so the user's first glimpse is the
1182					// finished editor rather than the paint cascade.
1183					//
1184					// `set_focus()` follows `show()` so keyboard input
1185					// routes to the editor immediately on reveal.
1186					// Failures are logged but swallowed - if the window
1187					// is already visible (phase 3 re-fired from another
1188					// consumer) Tauri returns a benign error.
1189					if NewPhase >= 3 {
1190						if let Some(MainWindow) = ApplicationHandle.get_webview_window("main") {
1191							if let Ok(false) = MainWindow.is_visible() {
1192								if let Err(Error) = MainWindow.show() {
1193									dev_log!(
1194										"lifecycle",
1195										"warn: [Lifecycle] main window show() failed on phase {}: {}",
1196										NewPhase,
1197										Error
1198									);
1199								} else {
1200									dev_log!(
1201										"lifecycle",
1202										"[Lifecycle] main window revealed on phase {} (hidden-until-ready)",
1203										NewPhase
1204									);
1205									let _ = MainWindow.set_focus();
1206								}
1207							}
1208						}
1209					}
1210
1211					Ok(json!(RunTime.Environment.ApplicationState.Feature.Lifecycle.GetPhase()))
1212				},
1213
1214				// Label commands
1215				"label:getUri" => {
1216					dev_log!("label", "label:getUri");
1217					LabelGetURI(RunTime.clone(), Arguments).await
1218				},
1219				"label:getWorkspace" => {
1220					dev_log!("label", "label:getWorkspace");
1221					LabelGetWorkspace(RunTime.clone()).await
1222				},
1223				"label:getBase" => {
1224					dev_log!("label", "label:getBase");
1225					LabelGetBase(Arguments).await
1226				},
1227
1228				// Model (text model registry) commands
1229				"model:open" => {
1230					dev_log!("model", "model:open");
1231					ModelOpen(RunTime.clone(), Arguments).await
1232				},
1233				"model:close" => {
1234					dev_log!("model", "model:close");
1235					ModelClose(RunTime.clone(), Arguments).await
1236				},
1237				"model:get" => {
1238					dev_log!("model", "model:get");
1239					ModelGet(RunTime.clone(), Arguments).await
1240				},
1241				"model:getAll" => {
1242					dev_log!("model", "model:getAll");
1243					ModelGetAll(RunTime.clone()).await
1244				},
1245				"model:updateContent" => {
1246					dev_log!("model", "model:updateContent");
1247					ModelUpdateContent(RunTime.clone(), Arguments).await
1248				},
1249
1250				// Navigation history commands
1251				"history:goBack" => {
1252					dev_log!("history", "history:goBack");
1253					HistoryGoBack(RunTime.clone()).await
1254				},
1255				"history:goForward" => {
1256					dev_log!("history", "history:goForward");
1257					HistoryGoForward(RunTime.clone()).await
1258				},
1259				"history:canGoBack" => {
1260					dev_log!("history", "history:canGoBack");
1261					HistoryCanGoBack(RunTime.clone()).await
1262				},
1263				"history:canGoForward" => {
1264					dev_log!("history", "history:canGoForward");
1265					HistoryCanGoForward(RunTime.clone()).await
1266				},
1267				"history:push" => {
1268					dev_log!("history", "history:push");
1269					HistoryPush(RunTime.clone(), Arguments).await
1270				},
1271				"history:clear" => {
1272					dev_log!("history", "history:clear");
1273					HistoryClear(RunTime.clone()).await
1274				},
1275				"history:getStack" => {
1276					dev_log!("history", "history:getStack");
1277					HistoryGetStack(RunTime.clone()).await
1278				},
1279
1280				// IPC status commands
1281				"mountain_get_status" => {
1282					let status = json!({
1283						"connected": true,
1284						"version": "1.0.0"
1285					});
1286					Ok(status)
1287				},
1288				"mountain_get_configuration" => {
1289					let config = json!({
1290						"editor": { "theme": "dark" },
1291						"extensions": { "installed": [] }
1292					});
1293					Ok(config)
1294				},
1295				"mountain_get_services_status" => {
1296					let services = json!({
1297						"editor": { "status": "running" },
1298						"extensionHost": { "status": "running" }
1299					});
1300					Ok(services)
1301				},
1302				"mountain_get_state" => {
1303					let state = json!({
1304						"ui": {},
1305						"editor": {},
1306						"workspace": {}
1307					});
1308					Ok(state)
1309				},
1310
1311				// =====================================================================
1312				// File system command ALIASES
1313				// VS Code's DiskFileSystemProviderClient calls readFile/writeFile/rename
1314				// but Mountain's original handlers use read/write/move.
1315				// =====================================================================
1316				"file:realpath" => FileRealpath(Arguments).await,
1317				"file:open" => FileOpenFd(Arguments).await,
1318				"file:close" => FileCloseFd(Arguments).await,
1319				"file:cloneFile" => FileCloneNative(Arguments).await,
1320
1321				// =====================================================================
1322				// Native Host commands (INativeHostService)
1323				// =====================================================================
1324
1325				// Dialogs
1326				"nativeHost:pickFolderAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1327				"nativeHost:pickFileAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1328				"nativeHost:pickFileFolderAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1329				"nativeHost:pickWorkspaceAndOpen" => NativePickFolder(ApplicationHandle.clone(), Arguments).await,
1330				"nativeHost:showOpenDialog" => NativeShowOpenDialog(ApplicationHandle.clone(), Arguments).await,
1331
1332				// Wind's `Files/Live.ts` calls `UserInterface.ShowOpenDialog` via
1333				// IPC and expects a bare `string[]` (file paths). The
1334				// `NativeShowOpenDialog` handler returns `{ canceled, filePaths }`.
1335				// Unwrap here so Wind's `Array.isArray(Result) ? Result : []`
1336				// finds the array rather than silently falling back to `[]`.
1337				"UserInterface.ShowOpenDialog" => {
1338					match NativeShowOpenDialog(ApplicationHandle.clone(), Arguments).await {
1339						Ok(Response) => {
1340							let Paths = Response
1341								.get("filePaths")
1342								.and_then(|V| V.as_array())
1343								.cloned()
1344								.unwrap_or_default();
1345							Ok(Value::Array(Paths))
1346						},
1347						Err(Error) => Err(Error),
1348					}
1349				},
1350				"nativeHost:showSaveDialog" => NativeShowSaveDialog(ApplicationHandle.clone(), Arguments).await,
1351				// Wind's `Files/Live.ts` calls `UserInterface.ShowSaveDialog` via
1352				// IPC and expects a bare path string (or undefined).
1353				"UserInterface.ShowSaveDialog" => {
1354					UserInterfaceShowSaveDialog(ApplicationHandle.clone(), Arguments).await
1355				},
1356				"nativeHost:showMessageBox" => NativeShowMessageBox(ApplicationHandle.clone(), Arguments).await,
1357
1358				// Environment paths - delegated to atomic handler.
1359				"nativeHost:getEnvironmentPaths" => NativeGetEnvironmentPaths(ApplicationHandle.clone()).await,
1360
1361				// OS info
1362				"nativeHost:getOSColorScheme" => {
1363					dev_log!("nativehost", "nativeHost:getOSColorScheme");
1364					NativeGetColorScheme().await
1365				},
1366				"nativeHost:getOSProperties" => {
1367					dev_log!("nativehost", "nativeHost:getOSProperties");
1368					NativeOSProperties().await
1369				},
1370				"nativeHost:getOSStatistics" => {
1371					dev_log!("nativehost", "nativeHost:getOSStatistics");
1372					NativeOSStatistics().await
1373				},
1374				"nativeHost:getOSVirtualMachineHint" => {
1375					dev_log!("nativehost", "nativeHost:getOSVirtualMachineHint");
1376					Ok(json!(0))
1377				},
1378
1379				// Window state
1380				"nativeHost:isWindowAlwaysOnTop" => {
1381					dev_log!("window", "nativeHost:isWindowAlwaysOnTop");
1382					Ok(json!(false))
1383				},
1384				"nativeHost:isFullScreen" => {
1385					dev_log!("window", "nativeHost:isFullScreen");
1386					NativeIsFullscreen(ApplicationHandle.clone()).await
1387				},
1388				"nativeHost:isMaximized" => {
1389					dev_log!("window", "nativeHost:isMaximized");
1390					NativeIsMaximized(ApplicationHandle.clone()).await
1391				},
1392				"nativeHost:getActiveWindowId" => {
1393					dev_log!("window", "nativeHost:getActiveWindowId");
1394					Ok(json!(1))
1395				},
1396				// LAND-FIX: workbench polls the cursor screen point for
1397				// hover hint / context-menu placement. Stock VS Code
1398				// returns the OS cursor location via Electron's
1399				// `screen.getCursorScreenPoint()`. Tauri/Wry on macOS
1400				// does not expose a stable equivalent (CGEvent location
1401				// works but adds an Objective-C trampoline per call).
1402				// Returning `{x:0, y:0}` is what stock VS Code itself
1403				// returns when no display is active; this is also what
1404				// Cocoon falls back to. Workbench uses the value only
1405				// to bias overlay placement; (0,0) places overlays at
1406				// the top-left of the active window which the layout
1407				// engine then clips to a sane position. The cost of
1408				// the unknown-IPC log spam outweighs the precision
1409				// loss.
1410				"nativeHost:getCursorScreenPoint" => {
1411					dev_log!("window", "nativeHost:getCursorScreenPoint");
1412					// Cursor position is used by the workbench to bias overlay
1413					// placement. (0,0) causes overlays to appear at the top-left
1414					// and get clipped to sane positions - zero overhead vs
1415					// spawning an osascript process per call.
1416					Ok(json!({ "x": 0, "y": 0 }))
1417				},
1418				"nativeHost:getWindows" => {
1419					let Title = std::env::var("ProductNameShort").unwrap_or_else(|_| "Land".into());
1420					let ActiveDoc = RunTime
1421						.Environment
1422						.ApplicationState
1423						.Workspace
1424						.GetActiveDocumentURI()
1425						.unwrap_or_default();
1426					Ok(json!([{ "id": 1, "title": Title, "filename": ActiveDoc }]))
1427				},
1428				"nativeHost:getWindowCount" => Ok(json!(1)),
1429
1430				// Auxiliary window spawners. VS Code's `nativeHostMainService.ts`
1431				// exposes `openAgentsWindow`, `openDevToolsWindow`, and
1432				// `openAuxiliaryWindow`, and Sky/Wind route these through the
1433				// `nativeHost:<method>` IPC channel. Without stubs, every call fires
1434				// `land:ipc:error:nativeHost.openAgentsWindow` in PostHog (1499
1435				// occurrences per the 2026-04-21 error report). Land doesn't have
1436				// AgentsView yet, so these are no-op acknowledgements - the calling
1437				// extension treats `undefined` as "window wasn't opened" rather than
1438				// an error.
1439				"nativeHost:openAgentsWindow" | "nativeHost:openDevToolsWindow" | "nativeHost:openAuxiliaryWindow" => {
1440					dev_log!("window", "{} (acknowledged, no-op - aux window unsupported)", command);
1441					Ok(Value::Null)
1442				},
1443
1444				// Window control - wired through the Tauri webview-window API so
1445				// focus/minimize/maximize/toggleFullScreen/close actually move the
1446				// native window the same way VS Code's Electron path does.
1447				"nativeHost:focusWindow" => {
1448					dev_log!("window", "{}", command);
1449					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1450						let _ = Window.set_focus();
1451					}
1452					Ok(Value::Null)
1453				},
1454				"nativeHost:maximizeWindow" => {
1455					dev_log!("window", "{}", command);
1456					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1457						let _ = Window.maximize();
1458					}
1459					Ok(Value::Null)
1460				},
1461				"nativeHost:unmaximizeWindow" => {
1462					dev_log!("window", "{}", command);
1463					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1464						let _ = Window.unmaximize();
1465					}
1466					Ok(Value::Null)
1467				},
1468				"nativeHost:minimizeWindow" => {
1469					dev_log!("window", "{}", command);
1470					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1471						let _ = Window.minimize();
1472					}
1473					Ok(Value::Null)
1474				},
1475				"nativeHost:toggleFullScreen" => {
1476					dev_log!("window", "{}", command);
1477					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1478						let IsFullscreen = Window.is_fullscreen().unwrap_or(false);
1479						let _ = Window.set_fullscreen(!IsFullscreen);
1480					}
1481					Ok(Value::Null)
1482				},
1483				"nativeHost:closeWindow" => {
1484					dev_log!("window", "{}", command);
1485					// `destroy()` tears the window down without firing
1486					// `CloseRequested` again, which lets us safely exit the
1487					// `prevent_close` intercept registered in AppLifecycle.
1488					// `close()` re-enters the intercept and the window
1489					// becomes unkillable.
1490					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1491						let _ = Window.destroy();
1492					}
1493					Ok(Value::Null)
1494				},
1495				"nativeHost:setWindowAlwaysOnTop" => {
1496					dev_log!("window", "{}", command);
1497					let OnTop = Arguments.first().and_then(|V| V.as_bool()).unwrap_or(false);
1498					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1499						let _ = Window.set_always_on_top(OnTop);
1500					}
1501					Ok(Value::Null)
1502				},
1503				"nativeHost:toggleWindowAlwaysOnTop" => {
1504					dev_log!("window", "{}", command);
1505					// Tauri doesn't expose a "get always on top" accessor on all
1506					// platforms, so toggle by tracking state via the webview title
1507					// prefix as a proxy. In practice the UI will call
1508					// `setWindowAlwaysOnTop` with an explicit bool immediately after,
1509					// so a best-effort flip is enough.
1510					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1511						let _ = Window.set_always_on_top(true);
1512					}
1513					Ok(Value::Null)
1514				},
1515				// `NSWindow.representedFilename` - sets the proxy icon in the
1516				// macOS title bar. Tauri doesn't expose this directly; use
1517				// Window.set_title as a best-effort (shows path in title).
1518				"nativeHost:setRepresentedFilename" => {
1519					dev_log!("window", "{}", command);
1520					let Path = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
1521					if !Path.is_empty() {
1522						if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1523							// Show just the filename component as the title; the
1524							// full path would overflow the title bar on deep trees.
1525							let Filename = std::path::Path::new(&Path)
1526								.file_name()
1527								.and_then(|N| N.to_str())
1528								.unwrap_or(&Path);
1529							let _ = Window.set_title(Filename);
1530						}
1531					}
1532					Ok(Value::Null)
1533				},
1534
1535				// `NSWindow.isDocumentEdited` - the ● dirty dot in the macOS
1536				// title bar. NSWindow::setDocumentEdited is not exposed by
1537				// Tauri 2.x's WebviewWindow API; acknowledged as no-op.
1538				"nativeHost:setDocumentEdited" => {
1539					let _ = Arguments;
1540					Ok(Value::Null)
1541				},
1542
1543				// `nativeHost:setMinimumSize` - enforce a minimum window size so
1544				// the workbench never collapses to a 1×1 pixel frame.
1545				"nativeHost:setMinimumSize" => {
1546					let Width = Arguments.first().and_then(|V| V.as_u64()).unwrap_or(400) as u32;
1547					let Height = Arguments.get(1).and_then(|V| V.as_u64()).unwrap_or(300) as u32;
1548					if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1549						let _ = Window.set_min_size(Some(tauri::Size::Physical(tauri::PhysicalSize {
1550							width:Width,
1551							height:Height,
1552						})));
1553					}
1554					Ok(Value::Null)
1555				},
1556
1557				// `nativeHost:positionWindow` - move the window to an explicit
1558				// screen position (used by multi-window restore).
1559				"nativeHost:positionWindow" => {
1560					if let Some(Rect) = Arguments.first() {
1561						let X = Rect.get("x").and_then(|V| V.as_i64()).unwrap_or(0) as i32;
1562						let Y = Rect.get("y").and_then(|V| V.as_i64()).unwrap_or(0) as i32;
1563						let W = Rect.get("width").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1564						let H = Rect.get("height").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1565						if let Some(Window) = ApplicationHandle.get_webview_window("main") {
1566							let _ =
1567								Window.set_position(tauri::Position::Physical(tauri::PhysicalPosition { x:X, y:Y }));
1568							if W > 0 && H > 0 {
1569								let _ =
1570									Window.set_size(tauri::Size::Physical(tauri::PhysicalSize { width:W, height:H }));
1571							}
1572						}
1573					}
1574					Ok(Value::Null)
1575				},
1576
1577				// Pure lifecycle/cosmetic signals - no Mountain-side action needed.
1578				"nativeHost:updateWindowControls"
1579				| "nativeHost:notifyReady"
1580				| "nativeHost:saveWindowSplash"
1581				| "nativeHost:updateTouchBar"
1582				| "nativeHost:moveWindowTop"
1583				| "nativeHost:setBackgroundThrottling"
1584				| "nativeHost:updateWindowAccentColor" => {
1585					dev_log!("window", "{}", command);
1586					Ok(Value::Null)
1587				},
1588
1589				// OS operations
1590				"nativeHost:isAdmin" => Ok(json!(false)),
1591				"nativeHost:isRunningUnderARM64Translation" => NativeIsRunningUnderARM64Translation().await,
1592				"nativeHost:hasWSLFeatureInstalled" => {
1593					#[cfg(target_os = "windows")]
1594					{
1595						Ok(json!(std::path::Path::new("C:\\Windows\\System32\\wsl.exe").exists()))
1596					}
1597					#[cfg(not(target_os = "windows"))]
1598					{
1599						Ok(json!(false))
1600					}
1601				},
1602				"nativeHost:showItemInFolder" => ShowItemInFolder(RunTime.clone(), Arguments).await,
1603				"nativeHost:openExternal" => OpenExternal(RunTime.clone(), Arguments).await,
1604				// Trash bin - atomic handler handles all platform variants.
1605				"nativeHost:moveItemToTrash" => {
1606					dev_log!("nativehost", "nativeHost:moveItemToTrash");
1607					NativeMoveItemToTrash(Arguments).await
1608				},
1609
1610				// Clipboard - atomic handlers backed by `arboard`.
1611				"nativeHost:readClipboardText" => {
1612					dev_log!("clipboard", "readClipboardText");
1613					NativeReadClipboardText(Arguments).await
1614				},
1615				"nativeHost:writeClipboardText" => {
1616					dev_log!("clipboard", "writeClipboardText");
1617					NativeWriteClipboardText(Arguments).await
1618				},
1619				"nativeHost:readClipboardFindText" => {
1620					dev_log!("clipboard", "readClipboardFindText");
1621					NativeReadClipboardFindText(Arguments).await
1622				},
1623				"nativeHost:writeClipboardFindText" => {
1624					dev_log!("clipboard", "writeClipboardFindText");
1625					NativeWriteClipboardFindText(Arguments).await
1626				},
1627				"nativeHost:readClipboardBuffer" => {
1628					dev_log!("clipboard", "readClipboardBuffer");
1629					NativeReadClipboardBuffer(Arguments).await
1630				},
1631				"nativeHost:writeClipboardBuffer" => {
1632					dev_log!("clipboard", "writeClipboardBuffer");
1633					NativeWriteClipboardBuffer(Arguments).await
1634				},
1635				"nativeHost:hasClipboard" => {
1636					dev_log!("clipboard", "hasClipboard");
1637					NativeHasClipboard(Arguments).await
1638				},
1639				"nativeHost:readImage" => {
1640					dev_log!("clipboard", "readImage");
1641					NativeReadImage(Arguments).await
1642				},
1643				"nativeHost:triggerPaste" => {
1644					dev_log!("clipboard", "triggerPaste");
1645					NativeTriggerPaste(Arguments).await
1646				},
1647
1648				// Process
1649				"nativeHost:getProcessId" => Ok(json!(std::process::id())),
1650				"nativeHost:killProcess" => KillProcess(Arguments).await,
1651
1652				// Network
1653				"nativeHost:findFreePort" => NativeFindFreePort(Arguments).await,
1654				"nativeHost:isPortFree" => {
1655					let Port = Arguments.first().and_then(|V| V.as_u64()).unwrap_or(0) as u16;
1656					if Port == 0 {
1657						Ok(json!(false))
1658					} else {
1659						let Free = tokio::net::TcpListener::bind(std::net::SocketAddr::from(([127, 0, 0, 1], Port)))
1660							.await
1661							.is_ok();
1662						Ok(json!(Free))
1663					}
1664				},
1665				// `IProxyService.resolveProxy` - return `DIRECT` when no proxy
1666				// env var is set, or the var's value when one is configured.
1667				// VS Code uses this before every authenticated HTTP request so
1668				// extensions that call `fetch` route through the right gateway.
1669				"nativeHost:resolveProxy" => {
1670					let Url = Arguments.first().and_then(|V| V.as_str()).unwrap_or("");
1671					let Scheme = if Url.starts_with("https") { "HTTPS" } else { "HTTP" };
1672					let ProxyEnv = std::env::var(format!("{}_PROXY", Scheme))
1673						.or_else(|_| std::env::var(format!("{}_proxy", Scheme.to_lowercase())))
1674						.or_else(|_| std::env::var("ALL_PROXY"))
1675						.or_else(|_| std::env::var("all_proxy"));
1676					match ProxyEnv {
1677						Ok(P) if !P.is_empty() => {
1678							Ok(json!(format!(
1679								"PROXY {}",
1680								P.trim_start_matches("http://").trim_start_matches("https://")
1681							)))
1682						},
1683						_ => Ok(json!("DIRECT")),
1684					}
1685				},
1686				"nativeHost:lookupAuthorization" => Ok(Value::Null),
1687				"nativeHost:lookupKerberosAuthorization" => Ok(Value::Null),
1688				"nativeHost:loadCertificates" => Ok(json!([])),
1689
1690				// Lifecycle
1691				"nativeHost:relaunch" => Relaunch(ApplicationHandle.clone(), Arguments).await,
1692				"nativeHost:reload" => Reload(ApplicationHandle.clone(), Arguments).await,
1693				"nativeHost:quit" => Quit(ApplicationHandle.clone(), Arguments).await,
1694				"nativeHost:exit" => Exit(ApplicationHandle.clone(), Arguments).await,
1695
1696				// Dev tools
1697				"nativeHost:openDevTools" => OpenDevTools(ApplicationHandle.clone(), Arguments).await,
1698				"nativeHost:toggleDevTools" => ToggleDevTools(ApplicationHandle.clone(), Arguments).await,
1699
1700				// Power
1701				"nativeHost:getSystemIdleState" => Ok(json!("active")),
1702				"nativeHost:getSystemIdleTime" => Ok(json!(0)),
1703				"nativeHost:getCurrentThermalState" => Ok(json!("nominal")),
1704				"nativeHost:isOnBatteryPower" => Ok(json!(false)),
1705				"nativeHost:startPowerSaveBlocker" => Ok(json!(0)),
1706				"nativeHost:stopPowerSaveBlocker" => Ok(json!(false)),
1707				"nativeHost:isPowerSaveBlockerStarted" => Ok(json!(false)),
1708
1709				// macOS specific
1710				"nativeHost:newWindowTab" => Ok(Value::Null),
1711				"nativeHost:showPreviousWindowTab" => Ok(Value::Null),
1712				"nativeHost:showNextWindowTab" => Ok(Value::Null),
1713				"nativeHost:moveWindowTabToNewWindow" => Ok(Value::Null),
1714				"nativeHost:mergeAllWindowTabs" => Ok(Value::Null),
1715				"nativeHost:toggleWindowTabsBar" => Ok(Value::Null),
1716				"nativeHost:installShellCommand" => InstallShellCommand(Arguments).await,
1717				"nativeHost:uninstallShellCommand" => UninstallShellCommand(Arguments).await,
1718
1719				// =====================================================================
1720				// Local PTY (terminal) commands
1721				// =====================================================================
1722				"localPty:getProfiles" => {
1723					dev_log!("terminal", "localPty:getProfiles");
1724					LocalPTYGetProfiles().await
1725				},
1726				"localPty:getDefaultSystemShell" => {
1727					dev_log!("terminal", "localPty:getDefaultSystemShell");
1728					LocalPTYGetDefaultShell().await
1729				},
1730				// `ILocalPtyService.getTerminalLayoutInfo` - return the last
1731				// layout snapshot so the workbench restores the terminal panel
1732				// (active tab, dimensions) across window reloads.
1733				// Key: "terminal:layoutInfo" in Mountain's `StorageProvider`.
1734				// `ILocalPtyService.getTerminalLayoutInfo` - return the persisted
1735				// layout snapshot so the workbench restores the terminal panel
1736				// (active tab, split dimensions) across window reloads.
1737				"localPty:getTerminalLayoutInfo" => {
1738					dev_log!("terminal", "localPty:getTerminalLayoutInfo");
1739					use CommonLibrary::{Environment::Requires::Requires, Storage::StorageProvider::StorageProvider};
1740					let StorageProvider:Arc<dyn StorageProvider> = RunTime.Environment.Require();
1741					match StorageProvider.GetStorageValue(true, "terminal:layoutInfo").await {
1742						Ok(Some(Stored)) => Ok(Stored),
1743						Ok(None) => Ok(Value::Null),
1744						Err(Error) => {
1745							dev_log!("terminal", "warn: [getTerminalLayoutInfo] storage read failed: {}", Error);
1746							Ok(Value::Null)
1747						},
1748					}
1749				},
1750				// `ILocalPtyService.setTerminalLayoutInfo` - persist the layout
1751				// snapshot so `getTerminalLayoutInfo` can replay it on next boot.
1752				"localPty:setTerminalLayoutInfo" => {
1753					dev_log!("terminal", "localPty:setTerminalLayoutInfo");
1754					use CommonLibrary::{Environment::Requires::Requires, Storage::StorageProvider::StorageProvider};
1755					let StorageProvider:Arc<dyn StorageProvider> = RunTime.Environment.Require();
1756					let Payload = Arguments.first().cloned().unwrap_or(Value::Null);
1757					let _ = StorageProvider
1758						.UpdateStorageValue(true, "terminal:layoutInfo".to_string(), Some(Payload))
1759						.await;
1760					Ok(Value::Null)
1761				},
1762				"localPty:getPerformanceMarks" => {
1763					dev_log!("terminal", "localPty:getPerformanceMarks");
1764					Ok(json!([]))
1765				},
1766				"localPty:reduceConnectionGraceTime" => {
1767					dev_log!("terminal", "localPty:reduceConnectionGraceTime");
1768					Ok(Value::Null)
1769				},
1770				"localPty:listProcesses" => {
1771					dev_log!("terminal", "localPty:listProcesses");
1772					Ok(json!([]))
1773				},
1774				"localPty:getEnvironment" => {
1775					dev_log!("terminal", "localPty:getEnvironment");
1776					LocalPTYGetEnvironment().await
1777				},
1778				// `IPtyService.getLatency` (per
1779				// `vs/platform/terminal/common/terminal.ts:341`) returns
1780				// `IPtyHostLatencyMeasurement[]`. The workbench polls this
1781				// to drive its "renderer ↔ pty host" health UI. We have
1782				// no separate pty host (Mountain spawns PTYs in-process),
1783				// so latency is effectively zero - return an empty array
1784				// matching the "no measurements available" branch the
1785				// workbench already handles. Without this route the call
1786				// surfaced as `Unknown IPC command: localPty:getLatency`
1787				// every poll cycle, and the renderer logged a
1788				// `TauriInvoke ok=false` line per attempt.
1789				"localPty:getLatency" => {
1790					dev_log!("terminal", "localPty:getLatency");
1791					Ok(json!([]))
1792				},
1793
1794				// `cocoon:request` - generic renderer→Cocoon RPC bridge.
1795				// Used by Sky-side bridges that need to dispatch a request
1796				// into the extension host (e.g. `webview.resolveView` to
1797				// trigger an extension's `resolveWebviewView` callback).
1798				// Wire shape: `params = [Method, Payload]`. Mountain
1799				// forwards to Cocoon via `Vine::Client::SendRequest` and
1800				// returns the response verbatim. Failure surfaces as a
1801				// stringified error so the renderer can fall through to
1802				// its alternative path (CustomEvent fan-out for legacy
1803				// observers).
1804				"cocoon:request" => {
1805					dev_log!("ipc", "cocoon:request method={:?}", Arguments.first());
1806					CocoonRequest(Arguments).await
1807				},
1808				"cocoon:notify" => {
1809					dev_log!("ipc", "cocoon:notify method={:?}", Arguments.first());
1810					CocoonNotify(Arguments).await
1811				},
1812
1813				// BATCH-19 Part B: VS Code's `LocalPtyService` talks to Mountain via
1814				// the `localPty:*` channel. The internal implementations reuse the
1815				// Tauri-side `terminal:*` handlers so PTY lifecycle stays identical
1816				// regardless of whether the request came from Sky (Wind) or from an
1817				// extension (Cocoon → Wind channel bridge).
1818				//
1819				// CONTRACT NOTE: `IPtyService.createProcess` is typed
1820				// `Promise<number>` (see `vs/platform/terminal/common/terminal.ts:
1821				// 316`). The workbench then does `new LocalPty(id, ...)` and
1822				// `this._ptys.set(id, pty)`. If we return the full
1823				// `{id,name,pid}` object the renderer keys `_ptys` by that
1824				// object, every `_ptys.get(<integer>)` lookup from
1825				// `onProcessData`/`onProcessReady` returns `undefined`, and
1826				// xterm receives zero bytes - the terminal panel renders
1827				// blank even though Mountain's PTY reader emits data
1828				// continuously. Strip down to the integer id here.
1829				"localPty:spawn" => {
1830					// `localPty:spawn` is Cocoon's Sky bridge path; preserve
1831					// the full `{id, name, pid}` shape because the older Wind
1832					// callers expect it. New `localPty:createProcess` and
1833					// `localPty:start` follow VS Code's typed contract below.
1834					dev_log!("terminal", "{}", command);
1835					TerminalCreate(RunTime.clone(), Arguments).await
1836				},
1837				"localPty:createProcess" => {
1838					dev_log!("terminal", "{}", command);
1839					LocalPTYCreateProcess(RunTime.clone(), Arguments).await
1840				},
1841				"localPty:start" => {
1842					// Eager-spawn pattern: `TerminalProvider::CreateTerminal`
1843					// already started the shell and reader task during
1844					// `localPty:createProcess`. `start` is a no-op that just
1845					// completes the workbench's launch promise. Returning
1846					// `Value::Null` matches `IPtyService.start`'s
1847					// `Promise<ITerminalLaunchError | ITerminalLaunchResult |
1848					// undefined>` (`undefined` branch). Routing this back
1849					// through `TerminalCreate` would spawn a SECOND
1850					// PTY for the same workbench terminal - the user-visible
1851					// pane is bound to id=1 from `createProcess`, but a
1852					// shadow PTY (id=2) starts and streams data nobody
1853					// renders.
1854					dev_log!("terminal", "{} no-op (eager-spawn)", command);
1855					Ok(Value::Null)
1856				},
1857				"localPty:input" | "localPty:write" => {
1858					dev_log!("terminal", "{}", command);
1859					TerminalSendText(RunTime.clone(), Arguments).await
1860				},
1861				"localPty:shutdown" | "localPty:dispose" => {
1862					dev_log!("terminal", "{}", command);
1863					TerminalDispose(RunTime.clone(), Arguments).await
1864				},
1865				"localPty:resize" => {
1866					dev_log!("terminal", "localPty:resize");
1867					LocalPTYResize(RunTime.clone(), Arguments).await
1868				},
1869				"localPty:acknowledgeDataEvent" => {
1870					// xterm flow-control heartbeat; no-op on Mountain side.
1871					Ok(Value::Null)
1872				},
1873				// `ILocalPtyService.getBackendOS` - VS Code uses this to decide
1874				// which profile list to show (Windows/Linux/macOS). Returns the
1875				// `OperatingSystem` enum value from
1876				// `vs/base/common/platform.ts`: 1 = Macintosh, 2 = Linux, 3 = Windows.
1877				"localPty:getBackendOS" => {
1878					#[cfg(target_os = "macos")]
1879					{
1880						Ok(json!(1))
1881					}
1882					#[cfg(target_os = "linux")]
1883					{
1884						Ok(json!(2))
1885					}
1886					#[cfg(target_os = "windows")]
1887					{
1888						Ok(json!(3))
1889					}
1890					#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
1891					{
1892						Ok(json!(2))
1893					}
1894				},
1895
1896				// `ILocalPtyService.refreshProperty` - returns the current value
1897				// of a PTY property. VS Code calls this for `ProcessId` (to show
1898				// PID in the terminal tab tooltip) and `Cwd` (for smart basename).
1899				// Property enum: 0=Cwd, 1=ProcessId, 2=Title, 3=OverrideName,
1900				// 4=ResolvedShellLaunchConfig, 5=ShellType
1901				// `ILocalPtyService.refreshProperty` - returns the current value
1902				// of a PTY property. VS Code calls this for `ProcessId` (tooltip)
1903				// and `Cwd` (smart basename).
1904				// Property enum: 0=Cwd, 1=ProcessId, 2=Title…
1905				"localPty:refreshProperty" => {
1906					use CommonLibrary::{
1907						Environment::Requires::Requires,
1908						Terminal::TerminalProvider::TerminalProvider,
1909					};
1910					let TerminalId = Arguments.first().and_then(|V| V.as_u64()).unwrap_or(0);
1911					let PropId = Arguments.get(1).and_then(|V| V.as_u64()).unwrap_or(0);
1912					if TerminalId == 0 {
1913						Ok(Value::Null)
1914					} else if PropId == 1 {
1915						let Provider:Arc<dyn TerminalProvider> = RunTime.Environment.Require();
1916						match Provider.GetTerminalProcessId(TerminalId).await {
1917							Ok(Some(Pid)) => Ok(json!(Pid)),
1918							_ => Ok(Value::Null),
1919						}
1920					} else {
1921						Ok(Value::Null)
1922					}
1923				},
1924
1925				// `ILocalPtyService.updateProperty` - workbench sets icon/title
1926				// on a running PTY; acknowledged, no Mountain-side state change.
1927				"localPty:updateProperty" => Ok(Value::Null),
1928
1929				// `ILocalPtyService.freePortKillProcess` - kill whatever process
1930				// is listening on a port so a new terminal can bind it.
1931				"localPty:freePortKillProcess" => {
1932					dev_log!("terminal", "localPty:freePortKillProcess");
1933					LocalPTYFreePortKillProcess(Arguments).await
1934				},
1935
1936				// `ILocalPtyService.serializeTerminalProcesses` - snapshot all
1937				// active terminals so the workbench can persist them to storage
1938				// and restore them across a window reload. Returns
1939				// `ISerializedTerminalState[]`.
1940				"localPty:serializeTerminalState" => {
1941					dev_log!("terminal", "localPty:serializeTerminalState");
1942					SerializeTerminalState(RunTime.clone()).await
1943				},
1944
1945				// `ILocalPtyService.reviveTerminalProcesses` - respawn shells from
1946				// a snapshot produced by `serializeTerminalState`. Accepts
1947				// `(ISerializedTerminalState[], dateTimeFormatLocale)`.
1948				"localPty:reviveTerminalProcesses" => {
1949					dev_log!(
1950						"terminal",
1951						"localPty:reviveTerminalProcesses count={}",
1952						Arguments.first().and_then(|V| V.as_array()).map(|A| A.len()).unwrap_or(0)
1953					);
1954					ReviveTerminalProcesses(RunTime.clone(), Arguments).await
1955				},
1956
1957				// `ILocalPtyService.getRevivedPtyNewId` - allocate a fresh
1958				// terminal ID for a revived PTY. The workbench calls this before
1959				// `reviveTerminalProcesses` to pre-assign an integer it can use
1960				// to key into `_ptys`. Returning the next atomic counter value
1961				// keeps IDs unique and collision-free across reloads.
1962				"localPty:getRevivedPtyNewId" => {
1963					let NewId = RunTime.Environment.ApplicationState.GetNextTerminalIdentifier();
1964					dev_log!("terminal", "localPty:getRevivedPtyNewId id={}", NewId);
1965					Ok(json!(NewId))
1966				},
1967
1968				// Session reconnect: reattach the workbench to a live Mountain
1969				// PTY after a window reload. The provider looks up the terminal
1970				// by id and returns its PID. DetachFromProcess is the inverse -
1971				// Mountain keeps the PTY running; output buffer accumulates for
1972				// the next attach or sky:replay-events drain.
1973				"localPty:attachToProcess" => {
1974					dev_log!("terminal", "localPty:attachToProcess");
1975					AttachToProcess(RunTime.clone(), Arguments).await
1976				},
1977				"localPty:detachFromProcess" => {
1978					dev_log!("terminal", "localPty:detachFromProcess");
1979					DetachFromProcess(RunTime.clone(), Arguments).await
1980				},
1981
1982				// Remaining `localPty:*` - no Mountain-side state needed.
1983				// `installAutoReply` / `uninstallAllAutoReplies`: shell-integration
1984				// auto-reply triggers (e.g. sudo password prompts) - not implemented.
1985				"localPty:processBinary"
1986				| "localPty:orphanQuestionReply"
1987				| "localPty:updateTitle"
1988				| "localPty:updateIcon"
1989				| "localPty:installAutoReply"
1990				| "localPty:uninstallAllAutoReplies" => Ok(Value::Null),
1991
1992				// =====================================================================
1993				// Update service - all stubs, no update server
1994				// =====================================================================
1995				"update:_getInitialState" => UpdateGetInitialState().await,
1996				"update:isLatestVersion" => UpdateIsLatestVersion().await,
1997				"update:checkForUpdates" => UpdateCheckForUpdates().await,
1998				"update:downloadUpdate" => UpdateDownloadUpdate().await,
1999				"update:applyUpdate" => UpdateApplyUpdate().await,
2000				"update:quitAndInstall" => UpdateQuitAndInstall().await,
2001
2002				// =====================================================================
2003				// Menubar
2004				// =====================================================================
2005				// VS Code fires `updateMenubar` on every active-editor / dirty /
2006				// selection change - now handled in the high-frequency fast-path
2007				// (see the `if IsHighFrequencyCommand` block above). This fallback
2008				// only fires if the command somehow bypasses the fast-path.
2009
2010				// =====================================================================
2011				// URL handler
2012				// =====================================================================
2013				"url:registerExternalUriOpener" => {
2014					dev_log!("url", "url:registerExternalUriOpener");
2015					Ok(Value::Null)
2016				},
2017
2018				// =====================================================================
2019				// Encryption
2020				// =====================================================================
2021				"encryption:encrypt" => Encrypt(Arguments).await,
2022				"encryption:decrypt" => Decrypt(Arguments).await,
2023
2024				// =====================================================================
2025				// Extension host starter - atomic handlers
2026				// =====================================================================
2027				"extensionHostStarter:createExtensionHost" => {
2028					dev_log!("exthost", "extensionHostStarter:createExtensionHost");
2029					ExtensionHostStarterCreate(Arguments).await
2030				},
2031				"extensionHostStarter:start" => {
2032					dev_log!("exthost", "extensionHostStarter:start");
2033					ExtensionHostStarterStart(Arguments).await
2034				},
2035				"extensionHostStarter:kill" => {
2036					dev_log!("exthost", "extensionHostStarter:kill");
2037					ExtensionHostStarterKill(Arguments).await
2038				},
2039				"extensionHostStarter:getExitInfo" => {
2040					dev_log!("exthost", "extensionHostStarter:getExitInfo");
2041					ExtensionHostStarterGetExitInfo(Arguments).await
2042				},
2043
2044				// =====================================================================
2045				// Extension host message relay (Wind → Mountain → Cocoon) - atomic
2046				// =====================================================================
2047				"cocoon:extensionHostMessage" => {
2048					dev_log!("exthost", "cocoon:extensionHostMessage");
2049					CocoonExtensionHostMessage(ApplicationHandle.clone(), Arguments).await
2050				},
2051
2052				// =====================================================================
2053				// Extension host debug service - atomic handlers
2054				// =====================================================================
2055				"extensionhostdebugservice:reload" => {
2056					dev_log!("exthost", "extensionhostdebugservice:reload");
2057					ExtensionHostDebugReload(ApplicationHandle.clone()).await
2058				},
2059				"extensionhostdebugservice:close" => {
2060					dev_log!("exthost", "extensionhostdebugservice:close");
2061					ExtensionHostDebugClose(ApplicationHandle.clone()).await
2062				},
2063				"extensionhostdebugservice:attachSession" | "extensionhostdebugservice:terminateSession" => {
2064					dev_log!("exthost", "{}", command);
2065					Ok(Value::Null)
2066				},
2067
2068				// =====================================================================
2069				// Workspaces - additional commands
2070				// =====================================================================
2071				"workspaces:getRecentlyOpened" => {
2072					dev_log!("workspaces", "workspaces:getRecentlyOpened");
2073					ReadRecentlyOpened()
2074				},
2075				"workspaces:removeRecentlyOpened" => {
2076					dev_log!("workspaces", "workspaces:removeRecentlyOpened");
2077					let Uri = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
2078					if !Uri.is_empty() {
2079						MutateRecentlyOpened(|List| {
2080							if let Some(Workspaces) = List.get_mut("workspaces").and_then(|V| V.as_array_mut()) {
2081								Workspaces
2082									.retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
2083							}
2084							if let Some(Files) = List.get_mut("files").and_then(|V| V.as_array_mut()) {
2085								Files.retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
2086							}
2087						});
2088					}
2089					Ok(Value::Null)
2090				},
2091				"workspaces:addRecentlyOpened" => {
2092					dev_log!("workspaces", "workspaces:addRecentlyOpened");
2093					// VS Code passes `[{ workspace?, folderUri?, fileUri?, label? }, …]`.
2094					let Entries:Vec<Value> = Arguments.first().and_then(|V| V.as_array()).cloned().unwrap_or_default();
2095					if !Entries.is_empty() {
2096						MutateRecentlyOpened(|List| {
2097							let Workspaces = List
2098								.get_mut("workspaces")
2099								.and_then(|V| V.as_array_mut())
2100								.map(|V| std::mem::take(V))
2101								.unwrap_or_default();
2102							let Files = List
2103								.get_mut("files")
2104								.and_then(|V| V.as_array_mut())
2105								.map(|V| std::mem::take(V))
2106								.unwrap_or_default();
2107							let mut MergedWorkspaces = Workspaces;
2108							let mut MergedFiles = Files;
2109							for Entry in Entries {
2110								let Folder = Entry
2111									.get("folderUri")
2112									.cloned()
2113									.or_else(|| Entry.get("workspace").and_then(|W| W.get("configPath").cloned()));
2114								let File = Entry.get("fileUri").cloned();
2115								if let Some(FolderUri) = Folder.and_then(|V| v_str(&V)) {
2116									MergedWorkspaces
2117										.retain(|E| E.get("uri").and_then(|V| V.as_str()).unwrap_or("") != FolderUri);
2118									let mut Item = serde_json::Map::new();
2119									Item.insert("uri".into(), json!(FolderUri));
2120									if let Some(Label) = Entry.get("label").and_then(|V| V.as_str()) {
2121										Item.insert("label".into(), json!(Label));
2122									}
2123									MergedWorkspaces.insert(0, Value::Object(Item));
2124								}
2125								if let Some(FileUri) = File.and_then(|V| v_str(&V)) {
2126									MergedFiles
2127										.retain(|E| E.get("uri").and_then(|V| V.as_str()).unwrap_or("") != FileUri);
2128									let mut Item = serde_json::Map::new();
2129									Item.insert("uri".into(), json!(FileUri));
2130									MergedFiles.insert(0, Value::Object(Item));
2131								}
2132							}
2133							// Cap at 50 each - matches VS Code's default in
2134							// `src/vs/platform/workspaces/common/workspaces.ts`.
2135							MergedWorkspaces.truncate(50);
2136							MergedFiles.truncate(50);
2137							List.insert("workspaces".into(), Value::Array(MergedWorkspaces));
2138							List.insert("files".into(), Value::Array(MergedFiles));
2139						});
2140					}
2141					Ok(Value::Null)
2142				},
2143				"workspaces:clearRecentlyOpened" => {
2144					dev_log!("workspaces", "workspaces:clearRecentlyOpened");
2145					MutateRecentlyOpened(|List| {
2146						List.insert("workspaces".into(), json!([]));
2147						List.insert("files".into(), json!([]));
2148					});
2149					Ok(Value::Null)
2150				},
2151				"workspaces:enterWorkspace" => {
2152					dev_log!("workspaces", "workspaces:enterWorkspace");
2153					Ok(Value::Null)
2154				},
2155				"workspaces:createUntitledWorkspace" => {
2156					dev_log!("workspaces", "workspaces:createUntitledWorkspace");
2157					Ok(Value::Null)
2158				},
2159				"workspaces:deleteUntitledWorkspace" => {
2160					dev_log!("workspaces", "workspaces:deleteUntitledWorkspace");
2161					Ok(Value::Null)
2162				},
2163				"workspaces:getWorkspaceIdentifier" => {
2164					// Return a stable identifier derived from the first workspace
2165					// folder's URI so VS Code's caching (recently-opened, per-workspace
2166					// storage, window-title derivation) keys off the real workspace
2167					// rather than the "untitled" fallback. `{ id, configPath }` is
2168					// VS Code's expected shape for a multi-root workspace identifier;
2169					// we only use single-root so configPath stays null.
2170					let Workspace = &RunTime.Environment.ApplicationState.Workspace;
2171					let Folders = Workspace.GetWorkspaceFolders();
2172					if let Some(First) = Folders.first() {
2173						use std::{
2174							collections::hash_map::DefaultHasher,
2175							hash::{Hash, Hasher},
2176						};
2177						let mut Hasher = DefaultHasher::new();
2178						First.URI.as_str().hash(&mut Hasher);
2179						let Id = format!("{:016x}", Hasher.finish());
2180						Ok(json!({
2181							"id": Id,
2182							"configPath": Value::Null,
2183							"uri": First.URI.to_string(),
2184						}))
2185					} else {
2186						Ok(Value::Null)
2187					}
2188				},
2189				"workspaces:getDirtyWorkspaces" => Ok(json!([])),
2190
2191				// Git (localGit channel) - implements stock VS Code's
2192				// ILocalGitService surface plus `exec` / `isAvailable` for
2193				// the built-in Git extension. Handlers spawn native `git`
2194				// via tokio::process. See Batch 4 in HANDOFF §-10.
2195				"git:exec" => {
2196					dev_log!("git", "git:exec");
2197					Git::HandleExec::HandleExec(Arguments).await
2198				},
2199				"git:clone" => {
2200					dev_log!("git", "git:clone");
2201					Git::HandleClone::HandleClone(Arguments).await
2202				},
2203				"git:pull" => {
2204					dev_log!("git", "git:pull");
2205					Git::HandlePull::HandlePull(Arguments).await
2206				},
2207				"git:checkout" => {
2208					dev_log!("git", "git:checkout");
2209					Git::HandleCheckout::HandleCheckout(Arguments).await
2210				},
2211				"git:revParse" => {
2212					dev_log!("git", "git:revParse");
2213					Git::HandleRevParse::HandleRevParse(Arguments).await
2214				},
2215				"git:fetch" => {
2216					dev_log!("git", "git:fetch");
2217					Git::HandleFetch::HandleFetch(Arguments).await
2218				},
2219				"git:revListCount" => {
2220					dev_log!("git", "git:revListCount");
2221					Git::HandleRevListCount::HandleRevListCount(Arguments).await
2222				},
2223				"git:cancel" => {
2224					dev_log!("git", "git:cancel");
2225					Git::HandleCancel::HandleCancel(Arguments).await
2226				},
2227				"git:isAvailable" => {
2228					dev_log!("git", "git:isAvailable");
2229					Git::HandleIsAvailable::HandleIsAvailable(Arguments).await
2230				},
2231
2232				// Tree-view child lookup from the renderer side. Mirrors the
2233				// Cocoon→Mountain `GetTreeChildren` gRPC path (see
2234				// `RPC/CocoonService/TreeView.rs::GetTreeChildren`) but is
2235				// invoked by the Wind/Sky tree-view bridge so the UI can
2236				// request children directly without waiting for Cocoon to
2237				// ask first. Delegated to atomic handler.
2238				"tree:getChildren" => TreeGetChildren(ApplicationHandle.clone(), RunTime.clone(), Arguments).await,
2239
2240				// SkyBridge event replay - delegated to atomic handler.
2241				"sky:replay-events" => SkyReplayEvents(ApplicationHandle.clone(), RunTime.clone()).await,
2242
2243				// `editor.revealRange` - sky-side shortcut to scroll Monaco to a range.
2244				// Extensions can also call this via `Context.SendToMountain` (gRPC Track
2245				// Effect path). This IPC arm lets Wind call it directly without gRPC.
2246				"editor:revealRange" | "window:revealRange" => {
2247					use tauri::Emitter;
2248					let Payload = Arguments.first().cloned().unwrap_or(Value::Null);
2249					let _ = ApplicationHandle.emit("sky://editor/revealRange", &Payload);
2250					Ok(Value::Null)
2251				},
2252
2253				// =====================================================================
2254				// Sky → Mountain editor state pushes
2255				// =====================================================================
2256
2257				// Sky pushes current selection whenever the user changes cursor position.
2258				// Mountain stores it and forwards to Cocoon so `activeTextEditor.selection`
2259				// and `onDidChangeTextEditorSelection` stay live.
2260				"sky:editor:selectionChanged" => {
2261					let Uri = Arguments
2262						.first()
2263						.and_then(|V| V.get("uri"))
2264						.and_then(|V| V.as_str())
2265						.unwrap_or("")
2266						.to_string();
2267					let Selections = Arguments
2268						.first()
2269						.and_then(|V| V.get("selections"))
2270						.cloned()
2271						.unwrap_or(Value::Array(Vec::new()));
2272					dev_log!("model", "[SelectionChanged] uri={}", Uri);
2273					// Store on workspace state
2274					if !Uri.is_empty() {
2275						RunTime
2276							.Environment
2277							.ApplicationState
2278							.Workspace
2279							.SetActiveDocumentURI(Some(Uri.clone()));
2280					}
2281					// Forward to Cocoon
2282					let Payload = json!({ "uri": Uri, "selections": Selections });
2283					let _ = crate::Vine::Client::SendNotification::Fn(
2284						"cocoon-main".to_string(),
2285						"window.didChangeTextEditorSelection".to_string(),
2286						Payload,
2287					)
2288					.await;
2289					Ok(Value::Null)
2290				},
2291
2292				// Sky pushes active editor info when user switches tabs.
2293				"sky:editor:activeChanged" => {
2294					let Payload = Arguments.first().cloned().unwrap_or(Value::Null);
2295					let Uri = Payload.get("uri").and_then(Value::as_str).unwrap_or("").to_string();
2296					dev_log!("model", "[ActiveEditorChanged] uri={}", Uri);
2297					if !Uri.is_empty() {
2298						RunTime
2299							.Environment
2300							.ApplicationState
2301							.Workspace
2302							.SetActiveDocumentURI(Some(Uri.clone()));
2303					}
2304					let _ = crate::Vine::Client::SendNotification::Fn(
2305						"cocoon-main".to_string(),
2306						"window.didChangeActiveTextEditor".to_string(),
2307						Payload,
2308					)
2309					.await;
2310					Ok(Value::Null)
2311				},
2312
2313				// =====================================================================
2314				// Language features (forward to Cocoon Node.js runtime)
2315				// =====================================================================
2316				// These are VS Code language-intelligence channels. Mountain has no
2317				// native implementation - Cocoon's extension host processes them via
2318				// the LanguageProviderRegistry. All go through cocoon:request bridge.
2319				"languages:getAll" | "languages:getEncodedLanguageId" => {
2320					dev_log!("extensions", "languages: {} (→ Cocoon)", command);
2321					let Payload = Arguments.into_iter().next().unwrap_or(Value::Null);
2322					let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 3000).await;
2323					Ok(
2324						crate::Vine::Client::SendRequest::Fn("cocoon-main", command.clone(), Payload, 5_000)
2325							.await
2326							.unwrap_or(Value::Array(Vec::new())),
2327					)
2328				},
2329
2330				// =====================================================================
2331				// SCM - forward to Cocoon's vscode.scm namespace
2332				// =====================================================================
2333				"scm:createSourceControl" | "scm:getSourceControls" | "scm:setActiveProvider" => {
2334					dev_log!("ipc", "scm: {} (→ Cocoon)", command);
2335					let Payload = if Arguments.is_empty() {
2336						Value::Null
2337					} else if Arguments.len() == 1 {
2338						Arguments.into_iter().next().unwrap()
2339					} else {
2340						Value::Array(Arguments)
2341					};
2342					let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 3000).await;
2343					Ok(
2344						crate::Vine::Client::SendRequest::Fn("cocoon-main", command.clone(), Payload, 10_000)
2345							.await
2346							.unwrap_or(Value::Null),
2347					)
2348				},
2349
2350				// =====================================================================
2351				// Debug - forward to Cocoon's vscode.debug namespace
2352				// =====================================================================
2353				"debug:startDebugging"
2354				| "debug:stopDebugging"
2355				| "debug:getSessions"
2356				| "debug:getBreakpoints"
2357				| "debug:addBreakpoints"
2358				| "debug:removeBreakpoints" => {
2359					dev_log!("ipc", "debug: {} (→ Cocoon)", command);
2360					let Payload = if Arguments.is_empty() {
2361						Value::Null
2362					} else if Arguments.len() == 1 {
2363						Arguments.into_iter().next().unwrap()
2364					} else {
2365						Value::Array(Arguments)
2366					};
2367					let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 3000).await;
2368					Ok(
2369						crate::Vine::Client::SendRequest::Fn("cocoon-main", command.clone(), Payload, 10_000)
2370							.await
2371							.unwrap_or(Value::Null),
2372					)
2373				},
2374
2375				// =====================================================================
2376				// Tasks - forward to Cocoon's vscode.tasks namespace
2377				// =====================================================================
2378				"tasks:executeTask" | "tasks:getTasks" | "tasks:getTaskExecution" => {
2379					dev_log!("ipc", "tasks: {} (→ Cocoon)", command);
2380					let Payload = if Arguments.is_empty() {
2381						Value::Null
2382					} else if Arguments.len() == 1 {
2383						Arguments.into_iter().next().unwrap()
2384					} else {
2385						Value::Array(Arguments)
2386					};
2387					let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 3000).await;
2388					Ok(
2389						crate::Vine::Client::SendRequest::Fn("cocoon-main", command.clone(), Payload, 10_000)
2390							.await
2391							.unwrap_or(Value::Null),
2392					)
2393				},
2394
2395				// =====================================================================
2396				// Authentication - forward to Cocoon's vscode.authentication namespace
2397				// =====================================================================
2398				"auth:getSessions" | "auth:createSession" | "auth:removeSession" => {
2399					dev_log!("ipc", "auth: {} (→ Cocoon)", command);
2400					let Payload = if Arguments.is_empty() {
2401						Value::Null
2402					} else if Arguments.len() == 1 {
2403						Arguments.into_iter().next().unwrap()
2404					} else {
2405						Value::Array(Arguments)
2406					};
2407					let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 3000).await;
2408					Ok(
2409						crate::Vine::Client::SendRequest::Fn("cocoon-main", command.clone(), Payload, 10_000)
2410							.await
2411							.unwrap_or(Value::Null),
2412					)
2413				},
2414
2415				// Atom L2 + NodeDeferred: unknown-command fallback.
2416				// First consults the Channel registry (three states):
2417				//   1. typo / never-registered → log + defer to Cocoon
2418				//   2. registered but no dispatch arm → log + defer to Cocoon
2419				//   3. Cocoon returns error → surface as IPC error
2420				//
2421				// When `TierIPC=NodeDeferred` or `TierIPC=Node` (set in
2422				// .env.Land) unknown commands are forwarded to Cocoon's
2423				// Node.js runtime via gRPC instead of returning an error.
2424				// This lets VS Code API surfaces that live in the extension
2425				// host (language features, SCM, debug, tasks, etc.) resolve
2426				// without requiring a Mountain dispatch arm.
2427				_ => {
2428					use std::str::FromStr;
2429
2430					// Check if command should defer to Cocoon's Node.js runtime.
2431					// The env var is baked in at build time via rustc-env from
2432					// build.rs; at runtime we also accept it via process env for
2433					// debug overrides.
2434					let TierIPC = std::env::var("TierIPC").unwrap_or_else(|_| "Mountain".into());
2435					let ShouldDefer = TierIPC == "NodeDeferred" || TierIPC == "Node";
2436
2437					if ShouldDefer {
2438						// Forward to Cocoon via cocoon:request bridge.
2439						// Cocoon's RequestRoutingHandler + extension namespaces
2440						// cover language:*, scm:*, debug:*, tasks:*, auth:*, etc.
2441						let Payload = if Arguments.is_empty() {
2442							Value::Null
2443						} else if Arguments.len() == 1 {
2444							Arguments.into_iter().next().unwrap()
2445						} else {
2446							Value::Array(Arguments)
2447						};
2448						dev_log!("ipc", "deferred → Cocoon: {}", command);
2449						let _ = crate::Vine::Client::WaitForClientConnection::Fn("cocoon-main", 3000).await;
2450						match crate::Vine::Client::SendRequest::Fn("cocoon-main", command.clone(), Payload, 15_000)
2451							.await
2452						{
2453							Ok(Response) => Ok(Response),
2454							Err(CocoonError) => {
2455								dev_log!(
2456									"ipc",
2457									"warn: [NodeDeferred] {} deferred but Cocoon rejected: {:?}",
2458									command,
2459									CocoonError
2460								);
2461								Ok(Value::Null)
2462							},
2463						}
2464					} else {
2465						match CommonLibrary::IPC::Channel::Channel::from_str(&command) {
2466							Ok(KnownChannel) => {
2467								dev_log!(
2468									"ipc",
2469									"error: [WindServiceHandlers] Channel {:?} is registered but has no dispatch arm",
2470									KnownChannel
2471								);
2472								Err(format!("IPC channel registered but unimplemented: {}", command))
2473							},
2474							Err(_) => {
2475								dev_log!("ipc", "error: [WindServiceHandlers] Unknown IPC command: {}", command);
2476								Err(format!("Unknown IPC command: {}", command))
2477							},
2478						}
2479					}
2480				},
2481			};
2482
2483			if ResultSender.send(MatchResult).is_err() {
2484				dev_log!(
2485					"ipc",
2486					"warn: [WindServiceHandlers] IPC result receiver dropped before dispatch completed"
2487				);
2488			}
2489		},
2490		CommandPriority,
2491	);
2492
2493	let Result = match ResultReceiver.await {
2494		Ok(Dispatched) => Dispatched,
2495		Err(_) => {
2496			dev_log!(
2497				"ipc",
2498				"error: [WindServiceHandlers] IPC task cancelled before producing a result"
2499			);
2500			Err("IPC task cancelled before result was produced".to_string())
2501		},
2502	};
2503
2504	// Emit OTLP span for every IPC call - visible in Jaeger at localhost:16686
2505	// Skip for high-frequency silenced calls to avoid thousands of spans
2506	// per session (logger, file I/O, storage polling).
2507	if !IsHighFrequencyCommand {
2508		let IsErr = Result.is_err();
2509		let SpanName = if IsErr {
2510			format!("land:mountain:ipc:{}:error", command)
2511		} else {
2512			format!("land:mountain:ipc:{}", command)
2513		};
2514		crate::otel_span!(&SpanName, OTLPStart, &[("ipc.command", command.as_str())]);
2515
2516		// Emit `land:mountain:handler:complete` to PostHog for every dispatched IPC.
2517		// Pairs with `land:cocoon:handler:complete` to populate the Feature
2518		// Parity dashboard's Node-vs-Rust handler-latency comparison.
2519		let HandlerElapsedNanos = crate::IPC::DevLog::NowNano::Fn().saturating_sub(OTLPStart);
2520		let HandlerDurationMs = HandlerElapsedNanos / 1_000_000;
2521		crate::Binary::Build::PostHogPlugin::CaptureHandler::Fn(&command, HandlerDurationMs, !IsErr);
2522	}
2523
2524	// Atom I13: paired entry/exit line per invoke. `invoke: <cmd>` on the way
2525	// in (emitted at the top of this fn); `done: <cmd> ok=… t_ns=…` on the
2526	// way out. A `grep "logger:log"` before showed only the entry half;
2527	// having both halves makes latency diagnosis a single pipe:
2528	//     grep "logger:log" Mountain.dev.log | awk '…'
2529	// without hopping across Jaeger. High-frequency commands still skip the
2530	// entry line but DO emit an exit - frequencies still aggregate, but each
2531	// is individually accounted for.
2532	if !IsHighFrequencyCommand {
2533		let ElapsedNanos = crate::IPC::DevLog::NowNano::Fn().saturating_sub(OTLPStart);
2534		dev_log!("ipc", "done: {} ok={} t_ns={}", command, !Result.is_err(), ElapsedNanos);
2535	}
2536
2537	Result
2538}
2539
2540pub fn register_wind_ipc_handlers(ApplicationHandle:&tauri::AppHandle) -> Result<(), String> {
2541	dev_log!("lifecycle", "registering IPC handlers");
2542
2543	// Note: These handlers are automatically registered when included in the
2544	// Tauri invoke_handler macro in the main binary
2545
2546	Ok(())
2547}