Skip to main content

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