Skip to main content

Mountain/Vine/Server/
MountainVinegRPCService.rs

1//! # MountainVinegRPCService
2//!
3//! Defines the gRPC service implementation for Mountain. This struct handles
4//! incoming RPC calls from the `Cocoon` sidecar, dispatches them to the
5//! application's core logic via the `Track` module, and returns the results.
6//!
7//! ## Service Methods
8//!
9//! - **process_cocoon_request**: Handles request-response calls from Cocoon
10//! - **send_cocoon_notification**: Handles fire-and-forget notifications from
11//!   Cocoon
12//! - **cancel_operation**: Cancels long-running operations requested by Cocoon
13//!
14//! ## Request Processing
15//!
16//! 1. Deserialize JSON parameters from request
17//! 2. Validate method name and parameters
18//! 3. Dispatch request to Track::DispatchLogic
19//! 4. Serialize response or error
20//! 5. Return gRPC response with proper status codes
21//!
22//! ## Error Handling
23//!
24//! All errors are converted to JSON-RPC compliant Error objects:
25//! - Parse errors: code -32700
26//! - Server errors: code -32000
27//! - Method not found: code -32601
28//! - Invalid params: code -32602
29//!
30//! ## Security
31//!
32//! - Parameter validation before processing
33//! - Message size limits enforced
34//! - Method name sanitization
35//! - Safe error messages (no sensitive data)
36
37use std::{collections::HashMap, sync::Arc};
38
39use serde_json::{Value, json};
40use tauri::{AppHandle, Emitter};
41use tokio::sync::RwLock;
42use tonic::{Request, Response, Status};
43use ::Vine::Generated::{
44	CancelOperationRequest,
45	Empty,
46	GenericNotification,
47	GenericRequest,
48	GenericResponse,
49	RpcError as RPCError,
50	mountain_service_server::MountainService,
51};
52
53use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Track, dev_log};
54
55/// Configuration for MountainService
56mod ServiceConfig {
57
58	/// Maximum number of concurrent operations
59	pub const MAX_CONCURRENT_OPERATIONS:usize = 50;
60
61	/// Default timeout for operation cancellation
62	pub const CANCELLATION_TIMEOUT_MS:u64 = 5000;
63
64	/// Maximum method name length
65	pub const MAX_METHOD_NAME_LENGTH:usize = 128;
66}
67
68/// The concrete implementation of the `MountainService` gRPC service.
69///
70/// This service handles all incoming RPC calls from the Cocoon sidecar,
71/// validating requests, dispatching to appropriate handlers, and returning
72/// responses in the expected gRPC format.
73pub struct MountainVinegRPCService {
74	/// Tauri application handle for VS Code integration
75	ApplicationHandle:AppHandle,
76
77	/// Application runtime containing core dependencies
78	RunTime:Arc<ApplicationRunTime>,
79
80	/// Registry of active operations with their cancellation tokens
81	/// Maps request ID to cancellation token for operation cancellation
82	ActiveOperations:Arc<RwLock<HashMap<u64, tokio_util::sync::CancellationToken>>>,
83}
84
85impl MountainVinegRPCService {
86	/// Accessor for Tauri `AppHandle` - used by the per-wire-method atoms
87	/// in `Vine::Server::Notification::*` that need to emit
88	/// `sky://` / `cocoon:*` events downstream. Kept as a thin read so the
89	/// struct's fields can stay private; atoms should never mutate the
90	/// handle, only `emit` through it.
91	pub fn ApplicationHandle(&self) -> &AppHandle { &self.ApplicationHandle }
92
93	/// Accessor for the shared `ApplicationRunTime`. Notification atoms
94	/// reach `Environment.ApplicationState.*` (provider registry, extension
95	/// registry, scheduler) through this. Clone from `Arc` when the atom
96	/// needs to keep it across an `.await` boundary.
97	pub fn RunTime(&self) -> &Arc<ApplicationRunTime> { &self.RunTime }
98}
99
100impl MountainVinegRPCService {
101	/// Creates a new instance of the Mountain gRPC service.
102	///
103	/// # Parameters
104	/// - `ApplicationHandle`: Tauri app handle for framework integration
105	/// - `RunTime`: Application runtime with core dependencies
106	///
107	/// # Returns
108	/// New MountainVinegRPCService instance
109	pub fn Create(ApplicationHandle:AppHandle, RunTime:Arc<ApplicationRunTime>) -> Self {
110		dev_log!("grpc", "[MountainVinegRPCService] New instance created");
111
112		Self {
113			ApplicationHandle,
114
115			RunTime,
116
117			ActiveOperations:Arc::new(RwLock::new(HashMap::new())),
118		}
119	}
120
121	/// Registers an operation for potential cancellation
122	///
123	/// # Parameters
124	/// - `request_id`: The request identifier for the operation
125	///
126	/// # Returns
127	/// A cancellation token that can be used to cancel the operation
128	pub async fn RegisterOperation(&self, request_id:u64) -> tokio_util::sync::CancellationToken {
129		let token = tokio_util::sync::CancellationToken::new();
130
131		self.ActiveOperations.write().await.insert(request_id, token.clone());
132
133		dev_log!(
134			"grpc",
135			"[MountainVinegRPCService] Registered operation {} for cancellation",
136			request_id
137		);
138
139		token
140	}
141
142	/// Unregisters an operation after completion
143	///
144	/// # Parameters
145	/// - `request_id`: The request identifier to unregister
146	pub async fn UnregisterOperation(&self, request_id:u64) {
147		self.ActiveOperations.write().await.remove(&request_id);
148
149		dev_log!("grpc", "[MountainVinegRPCService] Unregistered operation {}", request_id);
150	}
151
152	/// Validates a generic request before processing.
153	///
154	/// # Parameters
155	/// - `request`: The request to validate
156	///
157	/// # Returns
158	/// - `Ok(())`: Request is valid
159	/// - `Err(Status)`: Validation failed with appropriate gRPC status
160	fn ValidateRequest(&self, request:&GenericRequest) -> Result<(), Status> {
161		// Validate method name
162		if request.method.is_empty() {
163			return Err(Status::invalid_argument("Method name cannot be empty"));
164		}
165
166		if request.method.len() > ServiceConfig::MAX_METHOD_NAME_LENGTH {
167			return Err(Status::invalid_argument(format!(
168				"Method name exceeds maximum length of {} characters",
169				ServiceConfig::MAX_METHOD_NAME_LENGTH
170			)));
171		}
172
173		// Validate parameter size (rough estimate using JSON bytes)
174		if request.parameter.len() > 4 * 1024 * 1024 {
175			return Err(Status::resource_exhausted("Request parameter size exceeds limit"));
176		}
177
178		// Check for potentially malicious method names
179		if request.method.contains("../") || request.method.contains("::") {
180			return Err(Status::permission_denied("Invalid method name format"));
181		}
182
183		Ok(())
184	}
185
186	/// Creates a JSON-RPC compliant error response.
187	///
188	/// # Parameters
189	/// - `RequestIdentifier`: The request ID to echo back
190	/// - `code`: JSON-RPC error code
191	/// - `message`: Error message
192	/// - `data`: Optional error data (serialized)
193	///
194	/// # Returns
195	/// GenericResponse with error populated
196	fn CreateErrorResponse(RequestIdentifier:u64, code:i32, message:String, data:Option<Vec<u8>>) -> GenericResponse {
197		GenericResponse {
198			request_identifier:RequestIdentifier,
199
200			result:vec![],
201
202			error:Some(RPCError { code, message, data:data.unwrap_or_default() }),
203		}
204	}
205
206	/// Creates a successful JSON-RPC response.
207	///
208	/// # Parameters
209	/// - `RequestIdentifier`: The request ID to echo back
210	/// - `result`: Result value to serialize
211	///
212	/// # Returns
213	/// GenericResponse with result populated, or error if serialization fails
214	fn CreateSuccessResponse(RequestIdentifier:u64, result:&Value) -> GenericResponse {
215		let result_bytes = match serde_json::to_vec(result) {
216			Ok(bytes) => bytes,
217
218			Err(e) => {
219				dev_log!("grpc", "error: [MountainVinegRPCService] Failed to serialize result: {}", e);
220
221				// Return error response instead
222				return Self::CreateErrorResponse(
223					RequestIdentifier,
224					-32603, // Internal error
225					"Failed to serialize response".to_string(),
226					None,
227				);
228			},
229		};
230
231		GenericResponse { request_identifier:RequestIdentifier, result:result_bytes, error:None }
232	}
233}
234
235#[tonic::async_trait]
236impl MountainService for MountainVinegRPCService {
237	// LAND-PATCH B7-S6 P2: bidirectional streaming channel.
238	// Stub for now - the multiplexer that drains incoming Envelopes
239	// and dispatches to the unary handler tree is implemented in a
240	// follow-up patch (Patch 14). Until then this returns
241	// `Unimplemented` so callers fall back to the unary path.
242	type OpenChannelFromCocoonStream = std::pin::Pin<
243		Box<
244			dyn tonic::codegen::tokio_stream::Stream<Item = Result<::Vine::Generated::Envelope, tonic::Status>>
245				+ Send
246				+ 'static,
247		>,
248	>;
249
250	async fn open_channel_from_cocoon(
251		&self,
252
253		_request:tonic::Request<tonic::Streaming<::Vine::Generated::Envelope>>,
254	) -> Result<tonic::Response<Self::OpenChannelFromCocoonStream>, tonic::Status> {
255		Err(tonic::Status::unimplemented(
256			"OpenChannelFromCocoon: streaming multiplexer not yet wired (Patch 14); use unary endpoints",
257		))
258	}
259
260	/// Handles generic request-response RPCs from Cocoon.
261	///
262	/// This is the main entry point for Cocoon to request operations from
263	/// Mountain. It validates the request, deserializes parameters, dispatches
264	/// to the Track module, and returns the result or error in JSON-RPC
265	/// format.
266	///
267	/// # Parameters
268	/// - `request`: GenericRequest containing method name and serialized
269	///   parameters
270	///
271	/// # Returns
272	/// - `Ok(Response<GenericResponse>)`: Response with result or error
273	/// - `Err(Status)`: gRPC status error (only for critical failures)
274	async fn process_cocoon_request(
275		&self,
276
277		request:Request<GenericRequest>,
278	) -> Result<Response<GenericResponse>, Status> {
279		let RequestData = request.into_inner();
280
281		let MethodName = RequestData.method.clone();
282
283		let RequestIdentifier = RequestData.request_identifier;
284
285		let ReceiveInstant = std::time::Instant::now();
286
287		// Single consolidated receive line - replaces the previous
288		// three-line burst (`Received…` + `Params for…` + `Dispatching…`)
289		// that fired per RPC × thousands of RPCs per session. One log
290		// statement is enough to reconstruct the request lifecycle from
291		// the file: method, id, payload size in bytes. Gated under
292		// `grpc-verbose` so the default `short` trace stays quiet;
293		// failures still flow through the `grpc` (non-verbose) tag in
294		// the validate / dispatch error paths below.
295		dev_log!(
296			"grpc-verbose",
297			"[MountainVinegRPCService] recv id={} method={} size={}B",
298			RequestIdentifier,
299			MethodName,
300			RequestData.parameter.len()
301		);
302
303		// Hot-path instrumentation (BATCH-16). Every RPC that shows up with
304		// uniform 700 ms latency (tree.register, Configuration.Inspect,
305		// Command.Execute) emits a `[LandFix:RPC]` marker here so p50/p95 can
306		// be derived from the log without patching every handler. The
307		// monotonic `t_ns` is a `SystemTime::UNIX_EPOCH` offset so Cocoon's
308		// `process.hrtime.bigint()` wire-send stamp can be diffed into three
309		// hops: wire → grpc-recv (transit), grpc-recv → dispatch-enter
310		// (Track resolve), dispatch-enter → registered (handler body).
311		let IsHotRpc = matches!(
312			MethodName.as_str(),
313			"$tree:register" | "tree.register" | "Configuration.Inspect" | "Command.Execute"
314		);
315
316		if IsHotRpc {
317			let InstrumentRecvNs = std::time::SystemTime::now()
318				.duration_since(std::time::UNIX_EPOCH)
319				.map(|D| D.as_nanos())
320				.unwrap_or(0);
321
322			// Per-call receive timestamp for latency diagnosis - only
323			// useful when actively profiling. Gate under `rpc-latency`
324			// so `short` / `grpc` don't print it.
325			dev_log!(
326				"rpc-latency",
327				"[LandFix:RPC] grpc-recv method={} id={} size={} t_ns={}",
328				MethodName,
329				RequestIdentifier,
330				RequestData.parameter.len(),
331				InstrumentRecvNs
332			);
333		}
334
335		// Validate request before processing
336		if let Err(status) = self.ValidateRequest(&RequestData) {
337			dev_log!("grpc", "warn: [MountainVinegRPCService] Request validation failed: {}", status);
338
339			return Ok(Response::new(Self::CreateErrorResponse(
340				RequestIdentifier,
341				-32602, // Invalid params
342				status.message().to_string(),
343				None,
344			)));
345		}
346
347		// Deserialize JSON parameters. The byte-count + method are
348		// already captured in the consolidated `recv id=…` line above;
349		// no additional `Params for [ID: …]` emit is needed.
350		let ParametersValue:Value = match serde_json::from_slice(&RequestData.parameter) {
351			Ok(v) => v,
352
353			Err(e) => {
354				let msg = format!("Failed to deserialize parameters for method '{}': {}", MethodName, e);
355
356				dev_log!("grpc", "error: {}", msg);
357
358				return Ok(Response::new(Self::CreateErrorResponse(
359					RequestIdentifier,
360					-32700, // Parse error
361					msg,
362					None,
363				)));
364			},
365		};
366
367		// Dispatch line removed - the `recv id=… method=…` line above
368		// is the single source of truth for "this RPC started"; the
369		// completion path emits its own line on success / error.
370
371		// Dispatch request to Track module for processing
372		let DispatchResult = Track::SideCarRequest::DispatchSideCarRequest::DispatchSideCarRequest(
373			self.ApplicationHandle.clone(),
374			self.RunTime.clone(),
375			// In the future, this could come from connection metadata
376			"cocoon-main".to_string(),
377			MethodName.clone(),
378			ParametersValue,
379		)
380		.await;
381
382		match DispatchResult {
383			Ok(SuccessfulResult) => {
384				if IsHotRpc {
385					// Hot-RPC dispatched latency line - already narrow
386					// (~50 tagged RPCs per session). Route to `rpc-latency`
387					// so the profiling context stays opt-in.
388					dev_log!(
389						"rpc-latency",
390						"[LandFix:RPC] dispatched method={} id={} elapsed={}ms",
391						MethodName,
392						RequestIdentifier,
393						ReceiveInstant.elapsed().as_millis()
394					);
395				}
396
397				// Success completion fires per request (14k+ in long sessions).
398				// Failures still log under the unconditional `error:` path
399				// below, so routing this to `grpc-verbose` doesn't hide real
400				// problems.
401				dev_log!(
402					"grpc-verbose",
403					"[MountainVinegRPCService] Request [ID: {}] completed successfully",
404					RequestIdentifier
405				);
406
407				Ok(Response::new(Self::CreateSuccessResponse(RequestIdentifier, &SuccessfulResult)))
408			},
409
410			Err(ErrorString) => {
411				// Routine 404s - extensions probe for optional workspace
412				// files on activate:
413				//   - `FileSystem.ReadFile` → missing cache files (terminal-suggest, JSON
414				//     schema associations, composer.json, Gemfile.lock, Drupal.php).
415				//   - `FileSystem.Stat` → optional config probes.
416				// Both surface as "resource not found" / "not found" /
417				// "ENOENT". Downgrade to `grpc-verbose` so the default
418				// log reflects genuine failures only. The response still
419				// returns -32000 so Cocoon's shim can convert it to a
420				// proper `vscode.FileSystemError.FileNotFound`.
421				let LowerError = ErrorString.to_lowercase();
422
423				// "Path is outside of the registered workspace folders" /
424				// "Permission denied" responses come from the path-security
425				// guard in `Environment/Utility/PathSecurity.rs` when an
426				// extension probes a directory outside the open workspace
427				// (Svelte's `enableContextMenu` walks every `package.json`
428				// in the entire workspace tree, including out-of-root
429				// submodule dependencies). From the extension's perspective
430				// these are equivalent to "file not present" and must NOT
431				// count against Cocoon's circuit breaker - a workspace with
432				// many sibling submodules trips the breaker open within the
433				// first few hundred ms of activation otherwise.
434				let LooksLike404 = (MethodName == "FileSystem.ReadFile"
435					|| MethodName == "FileSystem.Stat"
436					|| MethodName == "FileSystem.ReadDirectory")
437					&& (LowerError.contains("resource not found")
438						|| LowerError.contains("not found")
439						|| LowerError.contains("enoent")
440						|| LowerError.contains("no such file or directory")
441						|| LowerError.contains("entity not found")
442						|| LowerError.contains("os error 2")
443						|| LowerError.contains("path is outside of the registered workspace")
444						|| LowerError.contains("permission denied for operation")
445						|| LowerError.contains("workspace is not trusted"));
446
447				if LooksLike404 {
448					dev_log!(
449						"grpc-verbose",
450						"[LandFix:MountainVinegRPC] Request [ID: {}] {} 404 (benign): {}",
451						RequestIdentifier,
452						MethodName,
453						ErrorString
454					);
455				} else {
456					dev_log!(
457						"grpc",
458						"error: [MountainVinegRPCService] Request [ID: {}] failed: {}",
459						RequestIdentifier,
460						ErrorString
461					);
462				}
463
464				// Distinct code -32004 for benign 404s lets the Cocoon shim
465				// classify them without a string-regex round-trip. -32000
466				// stays the catch-all for genuine failures.
467				let ErrorCode = if LooksLike404 { -32004 } else { -32000 };
468
469				Ok(Response::new(Self::CreateErrorResponse(
470					RequestIdentifier,
471					ErrorCode,
472					ErrorString,
473					None,
474				)))
475			},
476		}
477	}
478
479	/// Handles generic fire-and-forget notifications from Cocoon.
480	///
481	/// Notifications do not expect a response beyond acknowledgment.
482	/// They are used for status updates, events, and other asynchronous
483	/// notifications.
484	///
485	/// # Parameters
486	/// - `request`: GenericNotification with method name and parameters
487	///
488	/// # Returns
489	/// - `Ok(Response<Empty>)`: Notification was received and logged
490	/// - `Err(Status)`: Critical error during processing
491	///
492	/// # TODO
493	/// Future implementation should route notifications to dedicated handlers:
494	/// ```rust,ignore
495	/// let Parameter: Value = serde_json::from_slice(&notification.parameter)?;
496	/// NotificationHandler::Handle(MethodName, Parameter).await?;
497	/// ```
498	async fn send_cocoon_notification(&self, request:Request<GenericNotification>) -> Result<Response<Empty>, Status> {
499		let NotificationData = request.into_inner();
500
501		let MethodName = NotificationData.method;
502
503		// Notifications are even higher-volume than requests
504		// (progress.report alone fires 2500+ times per long activation).
505		// Move under `grpc-verbose` alongside the request-side banner.
506		dev_log!(
507			"grpc-verbose",
508			"[MountainVinegRPCService] Received gRPC Notification: Method='{}'",
509			MethodName
510		);
511
512		// Validate notification method name
513		if MethodName.is_empty() {
514			dev_log!(
515				"grpc",
516				"warn: [MountainVinegRPCService] Received notification with empty method name"
517			);
518
519			return Err(Status::invalid_argument("Method name cannot be empty"));
520		}
521
522		// Route notifications to appropriate handlers based on MethodName. Currently
523		// only logs known notification types and acknowledges all others. A complete
524		// implementation would maintain a registry of notification handlers per method,
525		// route notifications to registered handlers asynchronously, allow handlers
526		// to perform side effects (state updates, UI updates), support cancellation
527		// and timeouts for long-running handlers, and log unhandled notifications
528		// at debug level for diagnostics. Known notifications include:
529		// ExtensionActivated, ExtensionDeactivated, WebviewReady.
530
531		// Parse parameters for handlers that need them
532		let Parameter:Value = if NotificationData.parameter.is_empty() {
533			Value::Null
534		} else {
535			serde_json::from_slice(&NotificationData.parameter).unwrap_or(Value::Null)
536		};
537
538		match MethodName.as_str() {
539			// Batch 15: extension-host + progress + languages arms now live
540			// as atoms under `Vine::Server::Notification::*`. Each match arm
541			"extensionHostMessage" => {
542				::Vine::Server::Notification::Support::RelayToSky::Fn(
543					self,
544					"cocoon:extensionHostReply",
545					&Parameter,
546					"",
547					"",
548				);
549			},
550
551			"ExtensionActivated" => {
552				::Vine::Server::Notification::Support::RelayToSky::Fn(
553					self,
554					"cocoon:extensionActivated",
555					&Parameter,
556					"",
557					"",
558				);
559			},
560
561			"ExtensionDeactivated" => {
562				dev_log!(
563					"grpc",
564					"[Extension] deactivated id={}",
565					Parameter.get("extensionId").and_then(Value::as_str).unwrap_or("?")
566				);
567			},
568
569			"WebviewReady" => {
570				dev_log!(
571					"grpc",
572					"[Webview] ready handle={}",
573					Parameter.get("handle").and_then(Value::as_str).unwrap_or("?")
574				);
575			},
576
577			"progress.start" => {
578				::Vine::Server::Notification::ProgressStart::ProgressStart(self, &Parameter).await;
579			},
580
581			"progress.report" => {
582				::Vine::Server::Notification::ProgressReport::ProgressReport(self, &Parameter).await;
583			},
584
585			"progress.end" => {
586				::Vine::Server::Notification::ProgressEnd::ProgressEnd(self, &Parameter).await;
587			},
588
589			"languages.setDocumentLanguage" => {
590				::Vine::Server::Notification::Support::RelayToSky::Fn(
591					self,
592					"sky://languages/setDocumentLanguage",
593					&Parameter,
594					"grpc",
595					"",
596				);
597			},
598
599			"workspace.applyEdit" => {
600				::Vine::Server::Notification::Support::RelayToSky::Fn(
601					self,
602					"sky://workspace/applyEdit",
603					&Parameter,
604					"",
605					"",
606				);
607			},
608
609			"window.showTextDocument" => {
610				::Vine::Server::Notification::Support::RelayToSky::Fn(
611					self,
612					"sky://window/showTextDocument",
613					&Parameter,
614					"",
615					"",
616				);
617			},
618
619			// Batch 16: the remaining Cocoon-notification arms, now pure
620			// atom delegations. Each wire method lives in its own file
621			// under `Vine::Server::Notification::*`. "Group atoms"
622			// (TerminalLifecycle, DebugLifecycle, WebviewLifecycle, etc.)
623			// handle 3-4 wire methods that share the same relay pattern.
624			"webview.setTitle"
625			| "webview.setIconPath"
626			| "webview.setHtml"
627			| "webview.setOptions"
628			| "webview.updateView"
629			| "webview.reveal" => {
630				::Vine::Server::Notification::WebviewLifecycle::WebviewLifecycle(self, &MethodName, &Parameter).await;
631			},
632
633			"window.createTerminal" => {
634				::Vine::Server::Notification::WindowCreateTerminal::WindowCreateTerminal(self, &Parameter).await;
635			},
636
637			"terminal.sendText" | "terminal.show" | "terminal.hide" | "terminal.dispose" => {
638				::Vine::Server::Notification::TerminalLifecycle::TerminalLifecycle(self, &MethodName, &Parameter).await;
639			},
640
641			// Tree view refresh - extension fired its `onDidChangeTreeData`
642			// event. Relay to Sky which calls `ITreeView.refresh()` to
643			// trigger a fresh getChildren() round-trip.
644			"tree.refresh" => {
645				::Vine::Server::Notification::Support::RelayToSky::Fn(
646					self,
647					"sky://tree-view/refresh",
648					&Parameter,
649					"grpc",
650					"[Tree] refresh",
651				);
652			},
653
654			// EnvironmentVariableCollection mutations - applied to every
655			// PTY spawn that follows. The variant dispatch lives in the
656			// notification module since each op writes to the same global
657			// registry.
658			"terminal.envCollection.replace"
659			| "terminal.envCollection.append"
660			| "terminal.envCollection.prepend"
661			| "terminal.envCollection.delete"
662			| "terminal.envCollection.clear"
663			| "terminal.envCollection.setPersistent"
664			| "terminal.envCollection.setDescription" => {
665				super::Notification::TerminalEnvCollection::TerminalEnvCollectionDispatch(
666					self,
667					&MethodName,
668					&Parameter,
669				)
670				.await;
671			},
672
673			"window.createTextEditorDecorationType" | "window.disposeTextEditorDecorationType" => {
674				::Vine::Server::Notification::DecorationTypeLifecycle::DecorationTypeLifecycle(
675					self,
676					&MethodName,
677					&Parameter,
678				)
679				.await;
680			},
681
682			// Extension called `editor.setDecorations(type, ranges)`.
683			// Batched and emitted as `sky://decoration/set-ranges` so Sky can
684			// apply the ranges to the Monaco editor for the matching URI.
685			"window.setTextEditorDecorations" => {
686				::Vine::Server::Notification::SetTextEditorDecorations::SetTextEditorDecorations(self, &Parameter)
687					.await;
688			},
689
690			// Extension called `editor.edit(cb)` - an in-place text mutation.
691			// Payload: `{ uri, edits: [{range, text}] }`.
692			// Sky applies via `ICodeEditorService` → `editor.executeEdits`.
693			"window.applyTextEdits" => {
694				::Vine::Server::Notification::ApplyTextEdits::ApplyTextEdits(self, &Parameter).await;
695			},
696
697			"debug.addBreakpoints" | "debug.removeBreakpoints" | "debug.consoleAppend" => {
698				::Vine::Server::Notification::DebugLifecycle::DebugLifecycle(self, &MethodName, &Parameter).await;
699			},
700
701			"statusBar.update" | "statusBar.dispose" => {
702				::Vine::Server::Notification::StatusBarLifecycle::StatusBarLifecycle(self, &MethodName, &Parameter)
703					.await;
704			},
705
706			"statusBar.message" => {
707				::Vine::Server::Notification::StatusBarMessage::StatusBarMessage(self, &Parameter).await;
708			},
709
710			"window.showMessage" => {
711				::Vine::Server::Notification::WindowShowMessage::WindowShowMessage(self, &Parameter).await;
712			},
713
714			"registerCommand" => {
715				::Vine::Server::Notification::RegisterCommand::RegisterCommand(self, &Parameter).await;
716			},
717
718			"unregisterCommand" => {
719				::Vine::Server::Notification::UnregisterCommand::UnregisterCommand(self, &Parameter).await;
720			},
721
722			// NOTE: `outputChannel.*` arms were previously here fanning to
723			// the wrong `sky://output-channel/*` channel. Batch 9 atoms
724			// below correctly route to `sky://output/*`; the legacy arm
725			// was removed to stop it from shadowing the atoms.
726
727			// Batch 8: provider unregister atoms. Each wire method lives in
728			// its own `Notification/<Name>.rs` atom - the arm is a pure
729			// delegation so adding a variant stays a one-line change here
730			// plus one new file.
731			// Pure provider-unregistration atoms: read handle, call VineHost,
732			// log. No intermediate file needed - call Vine's support helper
733			// directly. Atoms with extra logic (scheme log, handle computation,
734			// sky relay) go through a named Vine atom.
735			"unregister_authentication_provider" => {
736				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
737					self,
738					&Parameter,
739					"authentication",
740				);
741			},
742
743			"unregister_debug_adapter" => {
744				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
745					self,
746					&Parameter,
747					"debug_adapter",
748				);
749			},
750
751			"unregister_debug_configuration_provider" => {
752				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
753					self,
754					&Parameter,
755					"debug_configuration",
756				);
757			},
758
759			"unregister_external_uri_opener" => {
760				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
761					self,
762					&Parameter,
763					"external_uri_opener",
764				);
765			},
766
767			"unregister_remote_authority_resolver" => {
768				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
769					self,
770					&Parameter,
771					"remote_authority_resolver",
772				);
773			},
774
775			"unregister_task_provider" => {
776				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(self, &Parameter, "task");
777			},
778
779			"unregister_file_system_provider" => {
780				dev_log!(
781					"provider-register",
782					"[ProviderUnregister] file_system scheme={}",
783					Parameter.get("scheme").and_then(Value::as_str).unwrap_or("")
784				);
785
786				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
787					self,
788					&Parameter,
789					"file_system",
790				);
791			},
792
793			// scmId handle computation + UnregisterProvider + sky relay - keeps its Vine atom.
794			"unregister_scm_provider" => {
795				::Vine::Server::Notification::UnregisterScmProvider::UnregisterScmProvider(self, &Parameter).await;
796			},
797
798			"unregister_uri_handler" => {
799				dev_log!(
800					"provider-register",
801					"[ProviderUnregister] uri_handler scheme={}",
802					Parameter.get("scheme").and_then(Value::as_str).unwrap_or("")
803				);
804
805				::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
806					self,
807					&Parameter,
808					"uri_handler",
809				);
810			},
811
812			"update_scm_group" => {
813				::Vine::Server::Notification::UpdateScmGroup::UpdateScmGroup(self, &Parameter).await;
814			},
815
816			// SCM register pair: explicit arms BEFORE the language-providers
817			// OR-block below. Without these, both `register_scm_provider` and
818			// `register_scm_resource_group` fell into the catch-all language-
819			// providers branch which only writes to
820			// `Extension::ProviderRegistration` - never to
821			// `ApplicationState::Feature::Markers::SourceControlManagement*`,
822			// so the SCM viewlet stayed empty even after vscode.git's
823			// `createSourceControl(...)` round-tripped successfully. The new
824			// atoms write the markers + emit the `sky://scm/*` events the
825			// renderer subscribes to.
826			"register_scm_provider" => {
827				::Vine::Server::Notification::RegisterScmProvider::RegisterScmProvider(self, &Parameter).await;
828			},
829
830			"register_scm_resource_group" => {
831				::Vine::Server::Notification::RegisterScmResourceGroup::RegisterScmResourceGroup(self, &Parameter)
832					.await;
833			},
834
835			"progress.update" => {
836				::Vine::Server::Notification::Support::RelayToSky::Fn(
837					self,
838					"sky://notification/progress-update",
839					&Parameter,
840					"grpc",
841					"[Progress] update",
842				);
843			},
844
845			"progress.complete" => {
846				::Vine::Server::Notification::Support::RelayToSky::Fn(
847					self,
848					"sky://progress/complete",
849					&Parameter,
850					"grpc",
851					"[Progress] complete",
852				);
853			},
854
855			"setStatusBarText" => {
856				::Vine::Server::Notification::SetStatusBarText::SetStatusBarText(self, &Parameter).await;
857			},
858
859			"disposeStatusBarItem" => {
860				::Vine::Server::Notification::DisposeStatusBarItem::DisposeStatusBarItem(self, &Parameter).await;
861			},
862
863			// output.* and outputChannel.* both forward to sky://output/* channels.
864			// Pure relay arms are inlined; atoms with coalescer or payload reshape keep their Vine impl.
865			"output.create" => {
866				::Vine::Server::Notification::Support::RelayToSky::Fn(
867					self,
868					"sky://output/create",
869					&Parameter,
870					"grpc",
871					"[Output] create",
872				);
873			},
874
875			"output.append" => {
876				::Vine::Server::Notification::Support::RelayToSky::Fn(
877					self,
878					"sky://output/append",
879					&Parameter,
880					"grpc",
881					"[Output] append",
882				);
883			},
884
885			"output.appendLine" => {
886				::Vine::Server::Notification::OutputAppendLine::OutputAppendLine(self, &Parameter).await;
887			},
888
889			"output.clear" => {
890				::Vine::Server::Notification::Support::RelayToSky::Fn(
891					self,
892					"sky://output/clear",
893					&Parameter,
894					"grpc",
895					"[Output] clear",
896				);
897			},
898
899			"output.show" => {
900				::Vine::Server::Notification::Support::RelayToSky::Fn(
901					self,
902					"sky://output/show",
903					&Parameter,
904					"grpc",
905					"[Output] show",
906				);
907			},
908
909			"output.dispose" => {
910				::Vine::Server::Notification::Support::RelayToSky::Fn(
911					self,
912					"sky://output/dispose",
913					&Parameter,
914					"grpc",
915					"[Output] dispose",
916				);
917			},
918
919			"output.replace" => {
920				::Vine::Server::Notification::OutputReplace::OutputReplace(self, &Parameter).await;
921			},
922
923			"outputChannel.create" => {
924				::Vine::Server::Notification::Support::RelayToSky::Fn(
925					self,
926					"sky://output/create",
927					&Parameter,
928					"output-verbose",
929					"[OutputChannel] create",
930				);
931			},
932
933			"outputChannel.append" => {
934				::Vine::Server::Notification::OutputChannelAppend::OutputChannelAppend(self, &Parameter).await;
935			},
936
937			"outputChannel.clear" => {
938				::Vine::Server::Notification::Support::RelayToSky::Fn(
939					self,
940					"sky://output/clear",
941					&Parameter,
942					"grpc",
943					"[OutputChannel] clear",
944				);
945			},
946
947			"outputChannel.replace" => {
948				::Vine::Server::Notification::Support::RelayToSky::Fn(
949					self,
950					"sky://output/replace",
951					&Parameter,
952					"grpc",
953					"[OutputChannel] replace",
954				);
955			},
956
957			"outputChannel.show" => {
958				::Vine::Server::Notification::Support::RelayToSky::Fn(
959					self,
960					"sky://output/show",
961					&Parameter,
962					"grpc",
963					"[OutputChannel] show",
964				);
965			},
966
967			"outputChannel.hide" => {
968				::Vine::Server::Notification::OutputChannelHide::OutputChannelHide(self, &Parameter).await;
969			},
970
971			"outputChannel.dispose" => {
972				::Vine::Server::Notification::Support::RelayToSky::Fn(
973					self,
974					"sky://output/dispose",
975					&Parameter,
976					"grpc",
977					"[OutputChannel] dispose",
978				);
979			},
980
981			"webview.postMessage" => {
982				::Vine::Server::Notification::WebviewPostMessage::WebviewPostMessage(self, &Parameter).await;
983			},
984
985			"webview.dispose" => {
986				::Vine::Server::Notification::WebviewDispose::WebviewDispose(self, &Parameter).await;
987			},
988
989			"set_language_configuration" => {
990				::Vine::Server::Notification::SetLanguageConfiguration::SetLanguageConfiguration(self, &Parameter)
991					.await;
992			},
993
994			"openExternal" => {
995				::Vine::Server::Notification::OpenExternal::OpenExternal(self, &Parameter).await;
996			},
997
998			"security.incident" => {
999				::Vine::Server::Notification::SecurityIncident::SecurityIncident(self, &Parameter).await;
1000			},
1001
1002			// Cocoon → Mountain: language-feature provider registration.
1003			// All 46+ `register_*` / `register_*_provider` variants delegate
1004			// to `Vine::Server::Notification::RegisterLanguageProvider` which
1005			// strips the prefix/suffix, logs, then calls
1006			// `VineHost::RegisterLanguageProvider` (Mountain's impl does the
1007			// type-name → ProviderType enum mapping and DTO construction).
1008			"register_authentication_provider"
1009			| "register_call_hierarchy_provider"
1010			| "register_code_actions_provider"
1011			| "register_code_lens_provider"
1012			| "register_color_provider"
1013			| "register_completion_item_provider"
1014			| "register_debug_adapter"
1015			| "register_debug_configuration_provider"
1016			| "register_declaration_provider"
1017			| "register_definition_provider"
1018			| "register_document_drop_edit_provider"
1019			| "register_document_formatting_provider"
1020			| "register_document_highlight_provider"
1021			| "register_document_link_provider"
1022			| "register_document_paste_edit_provider"
1023			| "register_document_range_formatting_provider"
1024			| "register_document_symbol_provider"
1025			| "register_evaluatable_expression_provider"
1026			| "register_external_uri_opener"
1027			| "register_file_decoration_provider"
1028			| "register_file_system_provider"
1029			| "register_folding_range_provider"
1030			| "register_hover_provider"
1031			| "register_implementation_provider"
1032			| "register_inlay_hints_provider"
1033			| "register_inline_completion_item_provider"
1034			| "register_inline_edit_provider"
1035			| "register_inline_values_provider"
1036			| "register_linked_editing_range_provider"
1037			| "register_mapped_edits_provider"
1038			| "register_multi_document_highlight_provider"
1039			| "register_notebook_content_provider"
1040			| "register_notebook_serializer"
1041			| "register_on_type_formatting_provider"
1042			| "register_reference_provider"
1043			| "register_remote_authority_resolver"
1044			| "register_rename_provider"
1045			| "register_resource_label_formatter"
1046			| "register_selection_range_provider"
1047			| "register_semantic_tokens_provider"
1048			| "register_signature_help_provider"
1049			| "register_task_provider"
1050			| "register_terminal_link_provider"
1051			| "register_terminal_profile_provider"
1052			| "register_text_document_content_provider"
1053			| "register_type_definition_provider"
1054			| "register_type_hierarchy_provider"
1055			| "register_uri_handler"
1056			| "register_workspace_symbol_provider" => {
1057				let _ = ::Vine::Server::Notification::RegisterLanguageProvider::RegisterLanguageProvider(
1058					self,
1059					&MethodName,
1060					&Parameter,
1061				)
1062				.await;
1063			},
1064
1065			_ => {
1066				dev_log!("grpc", "[MountainVinegRPCService] Cocoon notification: {}", MethodName);
1067
1068				// No typed match arm exists for this notification - it hits
1069				// the default path and becomes a `cocoon:<method>` Tauri
1070				// event that Wind may or may not listen for. The
1071				// `notif-drop` tag surfaces every fall-through so we can
1072				// tell at a glance which notifications Cocoon emits that
1073				// Mountain has no first-class handler for. The large OR
1074				// match above covers every `register_*` / `register_*_provider`
1075				// variant the Cocoon vscode-API shim is known to emit;
1076				// anything reaching here is either a new upstream addition or
1077				// an `unregister_*` / generic notification without a typed
1078				// handler. Payload preview included so diagnosis doesn't need
1079				// a second run.
1080				let PayloadPreview = if NotificationData.parameter.len() <= 160 {
1081					String::from_utf8_lossy(&NotificationData.parameter).into_owned()
1082				} else {
1083					let Slice = &NotificationData.parameter[..160];
1084
1085					format!("{}…", String::from_utf8_lossy(Slice))
1086				};
1087
1088				dev_log!(
1089					"notif-drop",
1090					"[NotifDrop] method={} payload_bytes={} preview={:?} (falls through to cocoon:{} event)",
1091					MethodName,
1092					NotificationData.parameter.len(),
1093					PayloadPreview,
1094					MethodName
1095				);
1096
1097				// Forward all unknown notifications as Tauri events so Wind
1098				// can subscribe to any Cocoon-originated event.
1099				// Sanitize: Tauri only allows [a-zA-Z0-9\-/:_] in event names.
1100				// Dots → slashes (e.g. "webview.setOptions" → "webview/setOptions");
1101				// any other invalid char → "-".
1102				let SanitizedMethod:String = MethodName
1103					.chars()
1104					.map(|C| {
1105						match C {
1106							'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '/' | ':' | '_' => C,
1107							'.' => '/',
1108							_ => '-',
1109						}
1110					})
1111					.collect();
1112
1113				let EventName = format!("cocoon:{}", SanitizedMethod);
1114
1115				if let Err(Error) = self.ApplicationHandle.emit(&EventName, &Parameter) {
1116					dev_log!(
1117						"grpc",
1118						"warn: [MountainVinegRPCService] Failed to emit {}: {}",
1119						EventName,
1120						Error
1121					);
1122				}
1123			},
1124		}
1125
1126		Ok(Response::new(Empty {}))
1127	}
1128
1129	/// Handles a request from Cocoon to cancel a long-running operation.
1130	///
1131	/// This method is called when Cocoon wants to cancel an operation that
1132	/// was previously initiated via process_cocoon_request.
1133	///
1134	/// # Parameters
1135	/// - `request`: CancelOperationRequest with the request ID to cancel
1136	///
1137	/// # Returns
1138	/// - `Ok(Response<Empty>)`: Cancellation was initiated
1139	/// - `Err(Status)`: Critical error during cancellation
1140	async fn cancel_operation(&self, request:Request<CancelOperationRequest>) -> Result<Response<Empty>, Status> {
1141		let cancel_request = request.into_inner();
1142
1143		let RequestIdentifierToCancel = cancel_request.request_identifier_to_cancel;
1144
1145		dev_log!(
1146			"grpc",
1147			"[MountainVinegRPCService] Received CancelOperation request for RequestID: {}",
1148			RequestIdentifierToCancel
1149		);
1150
1151		// Look up the operation in the active operations registry
1152		let cancel_token = {
1153			let operations = self.ActiveOperations.read().await;
1154
1155			operations.get(&RequestIdentifierToCancel).cloned()
1156		};
1157
1158		match cancel_token {
1159			Some(token) => {
1160				// Trigger cancellation token to signal the operation to abort
1161				token.cancel();
1162
1163				dev_log!(
1164					"grpc",
1165					"[MountainVinegRPCService] Successfully initiated cancellation for operation {}",
1166					RequestIdentifierToCancel
1167				);
1168
1169				// Note: We don't remove the token here - the operation itself should
1170				// call UnregisterOperation when it completes. This allows the
1171				// operation to detect the cancellation and clean up properly.
1172
1173				Ok(Response::new(Empty {}))
1174			},
1175
1176			None => {
1177				// Operation not found - it may have already completed
1178				dev_log!(
1179					"grpc",
1180					"warn: [MountainVinegRPCService] Cannot cancel operation {}: operation not found (may have \
1181					 already completed)",
1182					RequestIdentifierToCancel
1183				);
1184
1185				// Return success anyway - the operation is not running
1186				Ok(Response::new(Empty {}))
1187			},
1188		}
1189	}
1190}