Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/IPC/WindServiceHandlers/
mod.rs

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