Skip to main content

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