Skip to main content

Mountain/IPC/WindServiceHandlers/
mod.rs

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