Skip to main content

Mountain/RPC/CocoonService/
mod.rs

1// # CocoonServiceImpl Implementation
2//
3// This module implements the main gRPC service for Mountain-Cocoon
4// communication. It handles all requests from the Cocoon extension host
5// sidecar.
6//
7// ## Service Responsibilities
8//
9// - **Initialization**: Handshake and extension host initialization
10// - **Commands**: Register and execute extension commands
11// - **Language Features**: Hover, completion, definition, references, code
12// actions
13// - **File System**: Read, write, stat, and watch files
14// - **Terminal**: Manage terminal instances and I/O
15// - **Tree View**: Register providers and get tree children
16// - **SCM**: Source control management and git operations
17// - **Debug**: Debug adapter registration and session management
18// - **Save Participants**: Handle save events from extensions
19//
20// ## Architecture
21//
22// The service maintains references to:
23// - `MountainEnvironment`: Access to all Mountain services and providers
24// - `ActiveOperations`: Registry of cancellable operations
25//
26// ## Error Handling
27//
28// All methods return `tonic::Result<T>` and use proper error conversion
29// from internal errors to gRPC status codes.
30
31#[allow(unused_imports)]
32use std::{
33	collections::HashMap,
34	sync::Arc,
35	time::{SystemTime, UNIX_EPOCH},
36};
37
38use async_trait::async_trait;
39use CommonLibrary::{
40	Command::CommandExecutor::CommandExecutor,
41	LanguageFeature::{
42		DTO::{PositionDTO::PositionDTO, ProviderType::ProviderType},
43		LanguageFeatureProviderRegistry::LanguageFeatureProviderRegistry,
44	},
45	Secret::SecretProvider::SecretProvider,
46	Terminal::TerminalProvider::TerminalProvider,
47	UserInterface::{
48		DTO::{
49			InputBoxOptionsDTO::InputBoxOptionsDTO,
50			QuickPickItemDTO::QuickPickItemDTO,
51			QuickPickOptionsDTO::QuickPickOptionsDTO,
52		},
53		UserInterfaceProvider::UserInterfaceProvider,
54	},
55};
56use serde_json::json;
57use tokio::sync::RwLock;
58use tonic::{Request, Response, Status};
59use url::Url;
60
61use crate::{
62	ApplicationState::DTO::{
63		ProviderRegistrationDTO::ProviderRegistrationDTO,
64		WorkspaceFolderStateDTO::WorkspaceFolderStateDTO,
65	},
66	Environment::MountainEnvironment::MountainEnvironment,
67};
68// Import generated protobuf types
69use crate::dev_log;
70use crate::Vine::Generated::{
71	// Service trait
72	// Extended Language + Window + FS + Output + Task + Auth + Debug + Extension types
73	AppendOutputRequest,
74	ApplyEditRequest,
75	ApplyEditResponse,
76	Argument,
77	CancelOperationRequest,
78
79	ClearOutputRequest,
80	CloseTerminalRequest,
81	CodeAction,
82
83	CompletionItem,
84	CopyFileRequest,
85	CreateDirectoryRequest,
86	CreateOutputChannelRequest,
87	CreateOutputChannelResponse,
88	CreateStatusBarItemRequest,
89	CreateStatusBarItemResponse,
90	CreateWebviewPanelRequest,
91	CreateWebviewPanelResponse,
92	DebugConfiguration,
93	DeleteFileRequest,
94	DeleteSecretRequest,
95	DisposeOutputRequest,
96	DisposeWebviewPanelRequest,
97	// Common types
98	Empty,
99	ExecuteCommandRequest,
100	ExecuteCommandResponse,
101	ExecuteTaskRequest,
102	ExecuteTaskResponse,
103	ExtensionInfo,
104	// Workspace Operations
105	FindFilesRequest,
106	FindFilesResponse,
107	FindTextInFilesRequest,
108	FindTextInFilesResponse,
109	GenericNotification,
110	// Common generic types
111	GenericRequest,
112	GenericResponse,
113	GetAllExtensionsResponse,
114	GetAuthenticationSessionRequest,
115	GetAuthenticationSessionResponse,
116	GetConfigurationRequest,
117	GetConfigurationResponse,
118	GetExtensionRequest,
119	GetExtensionResponse,
120	// Secret Storage
121	GetSecretRequest,
122	GetSecretResponse,
123	GetTreeChildrenRequest,
124	GetTreeChildrenResponse,
125	GitExecRequest,
126	GitExecResponse,
127
128	// Initialization
129	InitExtensionHostRequest,
130
131	Location,
132	OnDidReceiveMessageRequest,
133
134	OpenDocumentRequest,
135	OpenDocumentResponse,
136	OpenExternalRequest,
137	// Terminal
138	OpenTerminalRequest,
139	// Save Participants
140	ParticipateInSaveRequest,
141	ParticipateInSaveResponse,
142	Position,
143	PostWebviewMessageRequest,
144	ProvideCallHierarchyRequest,
145	ProvideCallHierarchyResponse,
146	ProvideCodeActionsRequest,
147	ProvideCodeActionsResponse,
148	ProvideCodeLensesRequest,
149	ProvideCodeLensesResponse,
150	ProvideCompletionItemsRequest,
151	ProvideCompletionItemsResponse,
152	ProvideDefinitionRequest,
153	ProvideDefinitionResponse,
154	ProvideDocumentFormattingRequest,
155	ProvideDocumentFormattingResponse,
156	ProvideDocumentHighlightsRequest,
157	ProvideDocumentHighlightsResponse,
158	ProvideDocumentRangeFormattingRequest,
159	ProvideDocumentRangeFormattingResponse,
160	ProvideDocumentSymbolsRequest,
161	ProvideDocumentSymbolsResponse,
162	ProvideFoldingRangesRequest,
163	ProvideFoldingRangesResponse,
164	ProvideHoverRequest,
165	ProvideHoverResponse,
166	ProvideInlayHintsRequest,
167	ProvideInlayHintsResponse,
168	ProvideLinkedEditingRangesRequest,
169	ProvideLinkedEditingRangesResponse,
170	ProvideOnTypeFormattingRequest,
171	ProvideOnTypeFormattingResponse,
172	ProvideReferencesRequest,
173	ProvideReferencesResponse,
174	ProvideRenameEditsRequest,
175	ProvideRenameEditsResponse,
176	ProvideSelectionRangesRequest,
177	ProvideSelectionRangesResponse,
178	ProvideSemanticTokensRequest,
179	ProvideSemanticTokensResponse,
180	ProvideSignatureHelpRequest,
181	ProvideSignatureHelpResponse,
182	ProvideTypeHierarchyRequest,
183	ProvideTypeHierarchyResponse,
184	ProvideWorkspaceSymbolsRequest,
185	ProvideWorkspaceSymbolsResponse,
186	Range,
187	// File System
188	ReadFileRequest,
189	ReadFileResponse,
190	ReaddirRequest,
191	ReaddirResponse,
192	RegisterAuthenticationProviderRequest,
193	// Commands
194	RegisterCommandRequest,
195	// Debug
196	RegisterDebugAdapterRequest,
197	RegisterOnTypeFormattingProviderRequest,
198	// Language Features
199	RegisterProviderRequest,
200	// SCM
201	RegisterScmProviderRequest,
202	RegisterSemanticTokensProviderRequest,
203	RegisterSignatureHelpProviderRequest,
204	RegisterTaskProviderRequest,
205	// Tree View
206	RegisterTreeViewProviderRequest,
207	RenameFileRequest,
208	ReportProgressRequest,
209	ResizeTerminalRequest,
210	RpcError,
211	SaveAllRequest,
212	SaveAllResponse,
213	SetStatusBarTextRequest,
214	SetWebviewHtmlRequest,
215	ShowInputBoxRequest,
216	ShowInputBoxResponse,
217	ShowMessageRequest,
218	ShowMessageResponse,
219	ShowOutputRequest,
220	ShowProgressRequest,
221	ShowProgressResponse,
222	ShowQuickPickRequest,
223	ShowQuickPickResponse,
224	// Window Operations
225	ShowTextDocumentRequest,
226	ShowTextDocumentResponse,
227	SourceControlResourceState,
228	StartDebuggingRequest,
229	StartDebuggingResponse,
230
231	StatRequest,
232	StatResponse,
233	StopDebuggingRequest,
234	StoreSecretRequest,
235	TerminalClosedNotification,
236	TerminalDataNotification,
237
238	TerminalInputRequest,
239	TerminalOpenedNotification,
240	TerminalProcessIdNotification,
241	TerminateTaskRequest,
242	TextDocumentSaveReason,
243	TextEdit,
244	TextEditForSave,
245
246	TextMatch,
247	TreeItem,
248
249	UnregisterCommandRequest,
250
251	UpdateConfigurationRequest,
252	UpdateScmGroupRequest,
253	UpdateWorkspaceFoldersRequest,
254
255	Uri,
256	ViewColumn,
257
258	WatchFileRequest,
259
260	WorkspaceFolder,
261	WriteFileRequest,
262	cocoon_service_server::CocoonService,
263	on_did_receive_message_request,
264	post_webview_message_request,
265};
266
267/// Implementation of the CocoonService gRPC server
268///
269/// This struct handles all incoming requests from the Cocoon extension host
270/// sidecar and dispatches them to the appropriate Mountain services.
271#[derive(Clone)]
272pub struct CocoonServiceImpl {
273	/// Mountain environment providing access to all services
274	environment:Arc<MountainEnvironment>,
275
276	/// Registry of active operations with their cancellation tokens
277	/// Maps request ID to cancellation token for operation cancellation
278	ActiveOperations:Arc<RwLock<HashMap<u64, tokio_util::sync::CancellationToken>>>,
279}
280
281impl CocoonServiceImpl {
282	/// Creates a new instance of the CocoonService server
283	///
284	/// # Parameters
285	/// - `environment`: Mountain environment with access to all services
286	///
287	/// # Returns
288	/// A new CocoonService instance
289	pub fn new(environment:Arc<MountainEnvironment>) -> Self {
290		dev_log!("cocoon", "[CocoonService] New instance created");
291
292		Self { environment, ActiveOperations:Arc::new(RwLock::new(HashMap::new())) }
293	}
294
295	/// Registers an operation for potential cancellation
296	///
297	/// # Parameters
298	/// - `request_id`: The request identifier for the operation
299	///
300	/// # Returns
301	/// A cancellation token that can be used to cancel the operation
302	pub async fn RegisterOperation(&self, request_id:u64) -> tokio_util::sync::CancellationToken {
303		let token = tokio_util::sync::CancellationToken::new();
304		self.ActiveOperations.write().await.insert(request_id, token.clone());
305		dev_log!("cocoon", "[CocoonService] Registered operation {} for cancellation", request_id);
306		token
307	}
308
309	/// Unregisters an operation after completion
310	///
311	/// # Parameters
312	/// - `request_id`: The request identifier to unregister
313	pub async fn UnregisterOperation(&self, request_id:u64) {
314		self.ActiveOperations.write().await.remove(&request_id);
315		dev_log!("cocoon", "[CocoonService] Unregistered operation {}", request_id);
316	}
317
318	/// Registers a language feature provider in ApplicationState.
319	///
320	/// Converts the gRPC request fields into a `ProviderRegistrationDTO` and
321	/// stores it in `ApplicationState.Extension.ProviderRegistration`.
322	///
323	/// # Parameters
324	/// - `handle`: Unique provider handle
325	/// - `provider_type`: The type of language feature
326	/// - `language_selector`: Language scope (e.g. "typescript")
327	/// - `extension_id`: Extension that registered this provider
328	fn RegisterProvider(&self, handle:u32, provider_type:ProviderType, language_selector:&str, extension_id:&str) {
329		// SideCarIdentifier = "cocoon-main" so FeatureMethods::invoke_provider can
330		// route back via Vine::Client::SendRequestToSideCar("cocoon-main", ...).
331		// Selector stored as array so ProviderLookup::get_matching_provider's
332		// `.as_array()` call finds the language entry: [{ "language": "typescript" }].
333		let dto = ProviderRegistrationDTO {
334			Handle:handle,
335			ProviderType:provider_type,
336			Selector:json!([{ "language": language_selector }]),
337			SideCarIdentifier:"cocoon-main".to_string(),
338			ExtensionIdentifier:json!(extension_id),
339			Options:None,
340		};
341		self.environment
342			.ApplicationState
343			.Extension
344			.ProviderRegistration
345			.RegisterProvider(handle, dto);
346		dev_log!(
347			"cocoon",
348			"[CocoonService] Provider {:?} registered: handle={}, language={}",
349			provider_type,
350			handle,
351			language_selector
352		);
353	}
354
355	/// Extracts a filesystem path from a URI proto message.
356	///
357	/// Handles both `file://` URIs and bare paths. Returns `None` if the URI
358	/// is absent or the path cannot be extracted.
359	fn UriToPath(uri_opt:Option<&Uri>) -> Option<std::path::PathBuf> {
360		let value = uri_opt?.value.as_str();
361		if value.is_empty() {
362			return None;
363		}
364		// Strip file:// prefix if present
365		let path_str = if let Some(Stripped) = value.strip_prefix("file://") {
366			Stripped
367		} else if value.starts_with('/') || (value.len() > 1 && value.as_bytes()[1] == b':') {
368			// Bare absolute path (Unix or Windows)
369			value
370		} else {
371			// Unknown scheme - return as-is
372			value
373		};
374		Some(std::path::PathBuf::from(path_str))
375	}
376}
377
378#[async_trait]
379impl CocoonService for CocoonServiceImpl {
380	/// Process Mountain requests from Cocoon (generic request-response).
381	///
382	/// Routes legacy `fs.*` / `commands.*` / `secrets.*` method names used by
383	/// Cocoon's `FileSystemService` and other services that call Mountain via
384	/// the generic `ProcessCocoonRequest` RPC instead of the typed methods.
385	///
386	/// Parameters are JSON-encoded bytes in `request.parameter`. Results are
387	/// JSON-encoded bytes in `response.result`.
388	async fn process_mountain_request(
389		&self,
390		request:Request<GenericRequest>,
391	) -> Result<Response<GenericResponse>, Status> {
392		let Req = request.into_inner();
393		let RequestId = Req.request_identifier;
394
395		dev_log!(
396			"cocoon",
397			"[CocoonService] generic request: method={} id={}",
398			Req.method,
399			RequestId
400		);
401
402		/// Serialise a value into the `result` bytes of a GenericResponse.
403		fn OkResponse(RequestId:u64, Value:&impl serde::Serialize) -> Response<GenericResponse> {
404			let Bytes = serde_json::to_vec(Value).unwrap_or_default();
405			Response::new(GenericResponse { request_identifier:RequestId, result:Bytes, error:None })
406		}
407
408		/// Build an error GenericResponse.
409		fn ErrResponse(RequestId:u64, Code:i32, Message:String) -> Response<GenericResponse> {
410			Response::new(GenericResponse {
411				request_identifier:RequestId,
412				result:Vec::new(),
413				error:Some(RpcError { code:Code, message:Message, data:Vec::new() }),
414			})
415		}
416
417		// Deserialise the generic parameter bytes as a JSON value
418		let Params:serde_json::Value = if Req.parameter.is_empty() {
419			serde_json::Value::Null
420		} else {
421			serde_json::from_slice(&Req.parameter).unwrap_or(serde_json::Value::Null)
422		};
423
424		match Req.method.as_str() {
425			// ---- File System ---- (Cocoon FileSystemService uses these paths)
426			"fs.readFile" | "file:read" => {
427				let Path = Params
428					.as_str()
429					.or_else(|| Params.get("path").and_then(|V| V.as_str()))
430					.unwrap_or("");
431				match tokio::fs::read(Path).await {
432					Ok(Content) => Ok(OkResponse(RequestId, &Content)),
433					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.readFile: {}", Error))),
434				}
435			},
436			"fs.writeFile" | "file:write" => {
437				let Path = Params.get("path").and_then(|V| V.as_str()).unwrap_or("");
438				let Content:Vec<u8> = Params
439					.get("content")
440					.and_then(|V| V.as_array())
441					.map(|A| A.iter().filter_map(|B| B.as_u64().map(|N| N as u8)).collect())
442					.unwrap_or_default();
443				match tokio::fs::write(Path, &Content).await {
444					Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
445					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.writeFile: {}", Error))),
446				}
447			},
448			"fs.stat" | "file:stat" => {
449				let Path = Params
450					.as_str()
451					.or_else(|| Params.get("path").and_then(|V| V.as_str()))
452					.unwrap_or("");
453				match tokio::fs::metadata(Path).await {
454					Ok(Meta) => {
455						let Mtime = Meta
456							.modified()
457							.ok()
458							.and_then(|T| T.duration_since(UNIX_EPOCH).ok())
459							.map(|D| D.as_millis() as u64)
460							.unwrap_or(0);
461						Ok(OkResponse(
462							RequestId,
463							&json!({
464								"type": if Meta.is_dir() { 2 } else { 1 },
465								"is_file": Meta.is_file(),
466								"is_directory": Meta.is_dir(),
467								"size": Meta.len(),
468								"mtime": Mtime,
469							}),
470						))
471					},
472					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.stat: {}", Error))),
473				}
474			},
475			"fs.listDir" | "fs.readdir" | "file:readdir" => {
476				let Path = Params
477					.as_str()
478					.or_else(|| Params.get("path").and_then(|V| V.as_str()))
479					.unwrap_or("");
480				match tokio::fs::read_dir(Path).await {
481					Ok(mut Entries) => {
482						// Return [{name, type}] where type 1=File 2=Directory
483						let mut Items:Vec<serde_json::Value> = Vec::new();
484						while let Ok(Some(Entry)) = Entries.next_entry().await {
485							if let Some(Name) = Entry.file_name().to_str() {
486								let IsDir = Entry.file_type().await.map(|T| T.is_dir()).unwrap_or(false);
487								Items.push(json!({ "name": Name, "type": if IsDir { 2u32 } else { 1u32 } }));
488							}
489						}
490						Ok(OkResponse(RequestId, &Items))
491					},
492					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.listDir: {}", Error))),
493				}
494			},
495			"fs.createDir" | "file:mkdir" => {
496				let Path = Params
497					.as_str()
498					.or_else(|| Params.get("path").and_then(|V| V.as_str()))
499					.unwrap_or("");
500				match tokio::fs::create_dir_all(Path).await {
501					Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
502					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.createDir: {}", Error))),
503				}
504			},
505			"fs.delete" | "file:delete" => {
506				let Path = Params
507					.as_str()
508					.or_else(|| Params.get("path").and_then(|V| V.as_str()))
509					.unwrap_or("");
510				let Result = if std::path::Path::new(Path).is_dir() {
511					tokio::fs::remove_dir_all(Path).await
512				} else {
513					tokio::fs::remove_file(Path).await
514				};
515				match Result {
516					Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
517					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.delete: {}", Error))),
518				}
519			},
520			"fs.rename" | "file:move" => {
521				let From = Params.get("from").and_then(|V| V.as_str()).unwrap_or("");
522				let To = Params.get("to").and_then(|V| V.as_str()).unwrap_or("");
523				match tokio::fs::rename(From, To).await {
524					Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
525					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.rename: {}", Error))),
526				}
527			},
528			// ---- Commands ----
529			"commands.execute" => {
530				let CommandId = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
531				let Arg = Params.get("arg").cloned().unwrap_or(serde_json::Value::Null);
532				match self.environment.ExecuteCommand(CommandId, Arg).await {
533					Ok(Value) => Ok(OkResponse(RequestId, &Value)),
534					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
535				}
536			},
537			// ---- Commands (Cocoon MountainGRPCClient format) ----
538			"executeCommand" => {
539				let CommandId = Params.get("commandId").and_then(|V| V.as_str()).unwrap_or("").to_string();
540				let Arg = Params
541					.get("arguments")
542					.and_then(|A| A.as_array())
543					.and_then(|A| A.first())
544					.cloned()
545					.unwrap_or(serde_json::Value::Null);
546				match self.environment.ExecuteCommand(CommandId, Arg).await {
547					Ok(Value) => Ok(OkResponse(RequestId, &json!({ "result": Value }))),
548					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
549				}
550			},
551			"unregisterCommand" => {
552				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
553				let CommandId = Params.get("commandId").and_then(|V| V.as_str()).unwrap_or("").to_string();
554				match self.environment.UnregisterCommand(ExtensionId, CommandId).await {
555					Ok(()) => Ok(OkResponse(RequestId, &json!({ "success": true }))),
556					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
557				}
558			},
559			// ---- Window dialogs (Window.ts method names) ----
560			"UserInterface.ShowOpenDialog" => {
561				use CommonLibrary::UserInterface::{
562					DTO::OpenDialogOptionsDTO::OpenDialogOptionsDTO,
563					UserInterfaceProvider::UserInterfaceProvider,
564				};
565				let Title = Params
566					.get(0)
567					.and_then(|V| V.get("title"))
568					.and_then(|T| T.as_str())
569					.map(|S| S.to_string());
570				let Options = OpenDialogOptionsDTO {
571					Base:CommonLibrary::UserInterface::DTO::DialogOptionsDTO::DialogOptionsDTO {
572						Title,
573						..Default::default()
574					},
575					..OpenDialogOptionsDTO::default()
576				};
577				match self.environment.ShowOpenDialog(Some(Options)).await {
578					Ok(Some(Paths)) => {
579						let Uris:Vec<String> = Paths.iter().map(|P| format!("file://{}", P.display())).collect();
580						Ok(OkResponse(RequestId, &json!(Uris)))
581					},
582					Ok(None) => Ok(OkResponse(RequestId, &json!(serde_json::Value::Array(vec![])))),
583					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
584				}
585			},
586			"UserInterface.ShowSaveDialog" => {
587				use CommonLibrary::UserInterface::{
588					DTO::SaveDialogOptionsDTO::SaveDialogOptionsDTO,
589					UserInterfaceProvider::UserInterfaceProvider,
590				};
591				let Title = Params
592					.get(0)
593					.and_then(|V| V.get("title"))
594					.and_then(|T| T.as_str())
595					.map(|S| S.to_string());
596				let Options = SaveDialogOptionsDTO {
597					Base:CommonLibrary::UserInterface::DTO::DialogOptionsDTO::DialogOptionsDTO {
598						Title,
599						..Default::default()
600					},
601					..SaveDialogOptionsDTO::default()
602				};
603				match self.environment.ShowSaveDialog(Some(Options)).await {
604					Ok(Some(Path)) => Ok(OkResponse(RequestId, &json!(format!("file://{}", Path.display())))),
605					Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
606					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
607				}
608			},
609			"UserInterface.ShowInputBox" => {
610				use CommonLibrary::UserInterface::{
611					DTO::InputBoxOptionsDTO::InputBoxOptionsDTO,
612					UserInterfaceProvider::UserInterfaceProvider,
613				};
614				let Opts = Params.get(0);
615				let Options = InputBoxOptionsDTO {
616					Prompt:Opts
617						.and_then(|V| V.get("prompt"))
618						.and_then(|P| P.as_str())
619						.map(|S| S.to_string()),
620					PlaceHolder:Opts
621						.and_then(|V| V.get("placeHolder"))
622						.and_then(|P| P.as_str())
623						.map(|S| S.to_string()),
624					IsPassword:Some(Opts.and_then(|V| V.get("password")).and_then(|B| B.as_bool()).unwrap_or(false)),
625					Value:Opts
626						.and_then(|V| V.get("value"))
627						.and_then(|V| V.as_str())
628						.map(|S| S.to_string()),
629					Title:None,
630					IgnoreFocusOut:None,
631				};
632				match self.environment.ShowInputBox(Some(Options)).await {
633					Ok(Some(Text)) => Ok(OkResponse(RequestId, &json!(Text))),
634					Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
635					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
636				}
637			},
638			// ---- Native shell operations ----
639			"openExternal" => {
640				use tauri::Emitter;
641				let Url = Params
642					.as_str()
643					.or_else(|| Params.get("url").and_then(|V| V.as_str()))
644					.unwrap_or("")
645					.to_string();
646				// Emit to Sky - Sky uses Tauri shell plugin to open the URL
647				let _ = self
648					.environment
649					.ApplicationHandle
650					.emit("sky://native/openExternal", json!({ "url": Url }));
651				Ok(OkResponse(RequestId, &json!({ "success": true })))
652			},
653			// ---- Window (Cocoon MountainGRPCClient format) ----
654			"showTextDocument" => {
655				use tauri::Emitter;
656				let Uri = Params
657					.get("uri")
658					.and_then(|V| V.get("value").or(Some(V)))
659					.and_then(|V| V.as_str())
660					.unwrap_or("")
661					.to_string();
662				let ViewColumn = Params.get("viewColumn").and_then(|V| V.as_i64()).map(|N| N + 2);
663				let PreserveFocus = Params.get("preserveFocus").and_then(|V| V.as_bool()).unwrap_or(false);
664				let _ = self.environment.ApplicationHandle.emit(
665					"sky://editor/openDocument",
666					json!({ "uri": Uri, "viewColumn": ViewColumn, "preserveFocus": PreserveFocus }),
667				);
668				Ok(OkResponse(RequestId, &json!({ "success": true })))
669			},
670			"showInformation" => {
671				use CommonLibrary::UserInterface::{
672					DTO::MessageSeverity::MessageSeverity,
673					UserInterfaceProvider::UserInterfaceProvider,
674				};
675				let Message = Params.get("message").and_then(|V| V.as_str()).unwrap_or("").to_string();
676				let Items:Option<serde_json::Value> = Params
677					.get("items")
678					.cloned()
679					.filter(|V| V.is_array() && !V.as_array().unwrap().is_empty());
680				match self.environment.ShowMessage(MessageSeverity::Info, Message, Items).await {
681					Ok(Some(Selected)) => Ok(OkResponse(RequestId, &json!({ "selectedItem": Selected }))),
682					Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
683					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
684				}
685			},
686			"showWarning" => {
687				use CommonLibrary::UserInterface::{
688					DTO::MessageSeverity::MessageSeverity,
689					UserInterfaceProvider::UserInterfaceProvider,
690				};
691				let Message = Params.get("message").and_then(|V| V.as_str()).unwrap_or("").to_string();
692				let Items:Option<serde_json::Value> = Params
693					.get("items")
694					.cloned()
695					.filter(|V| V.is_array() && !V.as_array().unwrap().is_empty());
696				match self.environment.ShowMessage(MessageSeverity::Warning, Message, Items).await {
697					Ok(Some(Selected)) => Ok(OkResponse(RequestId, &json!({ "selectedItem": Selected }))),
698					Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
699					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
700				}
701			},
702			"showError" => {
703				use CommonLibrary::UserInterface::{
704					DTO::MessageSeverity::MessageSeverity,
705					UserInterfaceProvider::UserInterfaceProvider,
706				};
707				let Message = Params.get("message").and_then(|V| V.as_str()).unwrap_or("").to_string();
708				let Items:Option<serde_json::Value> = Params
709					.get("items")
710					.cloned()
711					.filter(|V| V.is_array() && !V.as_array().unwrap().is_empty());
712				match self.environment.ShowMessage(MessageSeverity::Error, Message, Items).await {
713					Ok(Some(Selected)) => Ok(OkResponse(RequestId, &json!({ "selectedItem": Selected }))),
714					Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
715					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
716				}
717			},
718			"createStatusBarItem" => {
719				use tauri::Emitter;
720				let Id = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
721				let Text = Params.get("text").and_then(|V| V.as_str()).unwrap_or("").to_string();
722				let Tooltip = Params.get("tooltip").and_then(|V| V.as_str()).unwrap_or("").to_string();
723				let _ = self
724					.environment
725					.ApplicationHandle
726					.emit("sky://statusbar/create", json!({ "id": Id, "text": Text, "tooltip": Tooltip }));
727				Ok(OkResponse(RequestId, &json!({ "itemId": Id })))
728			},
729			"setStatusBarText" => {
730				use tauri::Emitter;
731				let ItemId = Params.get("itemId").and_then(|V| V.as_str()).unwrap_or("").to_string();
732				let Text = Params.get("text").and_then(|V| V.as_str()).unwrap_or("").to_string();
733				let _ = self
734					.environment
735					.ApplicationHandle
736					.emit("sky://statusbar/update", json!({ "id": ItemId, "text": Text }));
737				Ok(OkResponse(RequestId, &json!({ "success": true })))
738			},
739			"createWebviewPanel" => {
740				use tauri::Emitter;
741				let ViewType = Params.get("viewType").and_then(|V| V.as_str()).unwrap_or("").to_string();
742				let Title = Params.get("title").and_then(|V| V.as_str()).unwrap_or("").to_string();
743				let Handle = std::time::SystemTime::now()
744					.duration_since(std::time::UNIX_EPOCH)
745					.map(|D| D.as_millis() as u64)
746					.unwrap_or(0);
747				let _ = self.environment.ApplicationHandle.emit("sky://webview/create", json!({ "handle": Handle, "viewType": ViewType, "title": Title, "viewColumn": Params.get("viewColumn"), "preserveFocus": Params.get("preserveFocus").and_then(|V| V.as_bool()).unwrap_or(false) }));
748				Ok(OkResponse(RequestId, &json!({ "handle": Handle })))
749			},
750			"setWebviewHtml" => {
751				use tauri::Emitter;
752				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0);
753				let Html = Params.get("html").and_then(|V| V.as_str()).unwrap_or("").to_string();
754				let _ = self
755					.environment
756					.ApplicationHandle
757					.emit("sky://webview/setHtml", json!({ "handle": Handle, "html": Html }));
758				Ok(OkResponse(RequestId, &json!({ "success": true })))
759			},
760			// ---- Workspace (Cocoon MountainGRPCClient format) ----
761			"findFiles" => {
762				use std::path::PathBuf;
763
764				use globset::GlobBuilder;
765				let Pattern = Params.get("pattern").and_then(|V| V.as_str()).unwrap_or("**").to_string();
766				let WorkspaceFolders = self.environment.ApplicationState.Workspace.GetWorkspaceFolders();
767				if WorkspaceFolders.is_empty() {
768					return Ok(OkResponse(RequestId, &json!({ "uris": Vec::<String>::new() })));
769				}
770				let RootPath = PathBuf::from(&WorkspaceFolders[0].URI.to_string().replace("file://", ""));
771				let Matcher = match GlobBuilder::new(&Pattern).literal_separator(false).build() {
772					Ok(G) => G.compile_matcher(),
773					Err(E) => return Ok(ErrResponse(RequestId, -32000, format!("Invalid glob: {}", E))),
774				};
775				let mut Files:Vec<String> = Vec::new();
776				let mut Stack = vec![RootPath.clone()];
777				'find_outer: while let Some(Dir) = Stack.pop() {
778					let mut Entries = match tokio::fs::read_dir(&Dir).await {
779						Ok(E) => E,
780						Err(_) => continue,
781					};
782					while let Ok(Some(Entry)) = Entries.next_entry().await {
783						let Path = Entry.path();
784						if Path.file_name().map(|N| N.to_string_lossy().starts_with('.')).unwrap_or(false) {
785							continue;
786						}
787						if Path.is_dir() {
788							Stack.push(Path);
789							continue;
790						}
791						let Rel = Path.strip_prefix(&RootPath).unwrap_or(&Path).to_string_lossy().to_string();
792						if Matcher.is_match(&Rel) {
793							Files.push(format!("file://{}", Path.to_string_lossy()));
794							if Files.len() >= 500 {
795								break 'find_outer;
796							}
797						}
798					}
799				}
800				Ok(OkResponse(RequestId, &json!({ "uris": Files })))
801			},
802			"findTextInFiles" => {
803				use std::path::PathBuf;
804
805				use globset::GlobBuilder;
806				let Pattern = Params.get("pattern").and_then(|V| V.as_str()).unwrap_or("").to_string();
807				let IncludeStr = Params
808					.get("include")
809					.and_then(|V| V.as_array())
810					.and_then(|A| A.first())
811					.and_then(|V| V.as_str())
812					.map(|S| S.to_string())
813					.unwrap_or_else(|| "**".to_string());
814				let WorkspaceFolders = self.environment.ApplicationState.Workspace.GetWorkspaceFolders();
815				if WorkspaceFolders.is_empty() {
816					return Ok(OkResponse(RequestId, &json!({ "matches": Vec::<serde_json::Value>::new() })));
817				}
818				let RootPath = PathBuf::from(&WorkspaceFolders[0].URI.to_string().replace("file://", ""));
819				let Matcher = GlobBuilder::new(&IncludeStr)
820					.literal_separator(false)
821					.build()
822					.map(|G| G.compile_matcher())
823					.ok();
824				let PatternLower = Pattern.to_lowercase();
825				let mut Matches:Vec<serde_json::Value> = Vec::new();
826				let mut Stack = vec![RootPath.clone()];
827				'text_outer: while let Some(Dir) = Stack.pop() {
828					let mut Entries = match tokio::fs::read_dir(&Dir).await {
829						Ok(E) => E,
830						Err(_) => continue,
831					};
832					while let Ok(Some(Entry)) = Entries.next_entry().await {
833						let Path = Entry.path();
834						if Path.file_name().map(|N| N.to_string_lossy().starts_with('.')).unwrap_or(false) {
835							continue;
836						}
837						if Path.is_dir() {
838							Stack.push(Path);
839							continue;
840						}
841						let Rel = Path.strip_prefix(&RootPath).unwrap_or(&Path).to_string_lossy().to_string();
842						if let Some(Ref) = &Matcher {
843							if !Ref.is_match(&Rel) {
844								continue;
845							}
846						}
847						let Content = match tokio::fs::read_to_string(&Path).await {
848							Ok(C) => C,
849							Err(_) => continue,
850						};
851						for (LineIdx, Line) in Content.lines().enumerate() {
852							if Line.to_lowercase().contains(&PatternLower) {
853								Matches.push(json!({ "uri": format!("file://{}", Path.to_string_lossy()), "lineNumber": LineIdx + 1, "preview": Line.trim() }));
854								if Matches.len() >= 1000 {
855									break 'text_outer;
856								}
857							}
858						}
859					}
860				}
861				Ok(OkResponse(RequestId, &json!({ "matches": Matches })))
862			},
863			"openDocument" => {
864				use tauri::Emitter;
865				let Uri = Params
866					.get("uri")
867					.and_then(|V| V.get("value").or(Some(V)))
868					.and_then(|V| V.as_str())
869					.unwrap_or("")
870					.to_string();
871				let ViewColumn = Params.get("viewColumn").and_then(|V| V.as_i64());
872				let _ = self
873					.environment
874					.ApplicationHandle
875					.emit("sky://editor/openDocument", json!({ "uri": Uri, "viewColumn": ViewColumn }));
876				Ok(OkResponse(RequestId, &json!({ "success": true })))
877			},
878			"saveAll" => {
879				use tauri::Emitter;
880				let IncludeUntitled = Params.get("includeUntitled").and_then(|V| V.as_bool()).unwrap_or(false);
881				let _ = self
882					.environment
883					.ApplicationHandle
884					.emit("sky://editor/saveAll", json!({ "includeUntitled": IncludeUntitled }));
885				Ok(OkResponse(RequestId, &json!({ "success": true })))
886			},
887			"applyEdit" => {
888				use tauri::Emitter;
889				let Uri = Params
890					.get("uri")
891					.and_then(|V| V.get("value").or(Some(V)))
892					.and_then(|V| V.as_str())
893					.unwrap_or("")
894					.to_string();
895				let Edits = Params.get("edits").cloned().unwrap_or(json!([]));
896				let _ = self
897					.environment
898					.ApplicationHandle
899					.emit("sky://editor/applyEdits", json!({ "uri": Uri, "edits": Edits }));
900				Ok(OkResponse(RequestId, &json!({ "success": true })))
901			},
902			// ---- Secret Storage (Cocoon MountainGRPCClient format) ----
903			"getSecret" => {
904				use CommonLibrary::Secret::SecretProvider::SecretProvider;
905				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
906				let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
907				match self.environment.GetSecret(ExtensionId, Key).await {
908					Ok(Some(Value)) => Ok(OkResponse(RequestId, &json!({ "value": Value }))),
909					Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
910					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
911				}
912			},
913			"storeSecret" => {
914				use CommonLibrary::Secret::SecretProvider::SecretProvider;
915				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
916				let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
917				let Value = Params.get("value").and_then(|V| V.as_str()).unwrap_or("").to_string();
918				match self.environment.StoreSecret(ExtensionId, Key, Value).await {
919					Ok(()) => Ok(OkResponse(RequestId, &json!({ "success": true }))),
920					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
921				}
922			},
923			"deleteSecret" => {
924				use CommonLibrary::Secret::SecretProvider::SecretProvider;
925				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
926				let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
927				match self.environment.DeleteSecret(ExtensionId, Key).await {
928					Ok(()) => Ok(OkResponse(RequestId, &json!({ "success": true }))),
929					Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
930				}
931			},
932			// ---- FS aliases (Cocoon MountainGRPCClient uses different key names) ----
933			"readFile" => {
934				let Uri = Params
935					.get("uri")
936					.and_then(|V| V.as_str())
937					.or_else(|| Params.as_str())
938					.unwrap_or("")
939					.replace("file://", "");
940				match tokio::fs::read(&Uri).await {
941					Ok(Content) => Ok(OkResponse(RequestId, &Content)),
942					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("readFile: {}", Error))),
943				}
944			},
945			"writeFile" => {
946				let Uri = Params.get("uri").and_then(|V| V.as_str()).unwrap_or("").replace("file://", "");
947				let Content:Vec<u8> = Params
948					.get("content")
949					.and_then(|V| V.as_array())
950					.map(|A| A.iter().filter_map(|B| B.as_u64().map(|N| N as u8)).collect())
951					.unwrap_or_default();
952				match tokio::fs::write(&Uri, &Content).await {
953					Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
954					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("writeFile: {}", Error))),
955				}
956			},
957			"stat" => {
958				let Uri = Params
959					.get("uri")
960					.and_then(|V| V.as_str())
961					.or_else(|| Params.as_str())
962					.unwrap_or("")
963					.replace("file://", "");
964				match tokio::fs::metadata(&Uri).await {
965					Ok(Meta) => {
966						let Mtime = Meta
967							.modified()
968							.ok()
969							.and_then(|T| T.duration_since(UNIX_EPOCH).ok())
970							.map(|D| D.as_millis() as u64)
971							.unwrap_or(0);
972						Ok(OkResponse(
973							RequestId,
974							&json!({ "type": if Meta.is_dir() { 2 } else { 1 }, "is_file": Meta.is_file(), "is_directory": Meta.is_dir(), "size": Meta.len(), "mtime": Mtime }),
975						))
976					},
977					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("stat: {}", Error))),
978				}
979			},
980			"readdir" => {
981				let Uri = Params
982					.get("uri")
983					.and_then(|V| V.as_str())
984					.or_else(|| Params.as_str())
985					.unwrap_or("")
986					.replace("file://", "");
987				match tokio::fs::read_dir(&Uri).await {
988					Ok(mut Entries) => {
989						let mut Names:Vec<String> = Vec::new();
990						while let Ok(Some(Entry)) = Entries.next_entry().await {
991							if let Some(Name) = Entry.file_name().to_str() {
992								Names.push(Name.to_string());
993							}
994						}
995						Ok(OkResponse(RequestId, &Names))
996					},
997					Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("readdir: {}", Error))),
998				}
999			},
1000			// ---- Unknown ----
1001			_ => {
1002				dev_log!("cocoon", "warn: [CocoonService] Unknown generic method: {}", Req.method);
1003				Ok(ErrResponse(RequestId, -32601, format!("Method '{}' not found", Req.method)))
1004			},
1005		}
1006	}
1007
1008	/// Send Mountain notifications to Cocoon (generic fire-and-forget)
1009	/// Routes by notification.method string to the appropriate Mountain
1010	/// handler. Called by Cocoon's
1011	/// `MountainGRPCClient.sendNotification(method, params)`.
1012	async fn send_mountain_notification(
1013		&self,
1014		request:Request<GenericNotification>,
1015	) -> Result<Response<Empty>, Status> {
1016		let notification = request.into_inner();
1017		dev_log!(
1018			"cocoon",
1019			"[CocoonService] Notification router: method='{}'",
1020			notification.method
1021		);
1022
1023		// Deserialise notification parameters as JSON
1024		let Params:serde_json::Value = if notification.parameter.is_empty() {
1025			serde_json::Value::Null
1026		} else {
1027			serde_json::from_slice(&notification.parameter).unwrap_or(serde_json::Value::Null)
1028		};
1029
1030		match notification.method.as_str() {
1031			// ---- Commands ----
1032			"registerCommand" => {
1033				let CommandId = Params.get("commandId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1034				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1035				if let Err(Error) = self.environment.RegisterCommand(ExtensionId, CommandId.clone()).await {
1036					dev_log!(
1037						"cocoon",
1038						"warn: [CocoonService] notification: registerCommand '{}' failed: {:?}",
1039						CommandId,
1040						Error
1041					);
1042				}
1043			},
1044			"unregisterCommand" => {
1045				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1046				let CommandId = Params.get("commandId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1047				let _ = self.environment.UnregisterCommand(ExtensionId, CommandId).await;
1048			},
1049			// ---- Language Providers (APIFactoryService.ts register_*_provider strings) ----
1050			"register_hover_provider" => {
1051				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1052				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1053				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1054				self.RegisterProvider(Handle, ProviderType::Hover, Selector, ExtId);
1055			},
1056			"register_completion_item_provider" => {
1057				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1058				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1059				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1060				self.RegisterProvider(Handle, ProviderType::Completion, Selector, ExtId);
1061			},
1062			"register_definition_provider" => {
1063				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1064				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1065				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1066				self.RegisterProvider(Handle, ProviderType::Definition, Selector, ExtId);
1067			},
1068			"register_reference_provider" => {
1069				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1070				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1071				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1072				self.RegisterProvider(Handle, ProviderType::References, Selector, ExtId);
1073			},
1074			"register_code_actions_provider" => {
1075				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1076				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1077				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1078				self.RegisterProvider(Handle, ProviderType::CodeAction, Selector, ExtId);
1079			},
1080			"register_document_highlight_provider" => {
1081				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1082				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1083				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1084				self.RegisterProvider(Handle, ProviderType::DocumentHighlight, Selector, ExtId);
1085			},
1086			"register_document_symbol_provider" => {
1087				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1088				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1089				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1090				self.RegisterProvider(Handle, ProviderType::DocumentSymbol, Selector, ExtId);
1091			},
1092			"register_workspace_symbol_provider" => {
1093				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1094				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1095				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1096				self.RegisterProvider(Handle, ProviderType::WorkspaceSymbol, Selector, ExtId);
1097			},
1098			"register_rename_provider" => {
1099				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1100				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1101				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1102				self.RegisterProvider(Handle, ProviderType::Rename, Selector, ExtId);
1103			},
1104			"register_document_formatting_provider" => {
1105				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1106				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1107				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1108				self.RegisterProvider(Handle, ProviderType::DocumentFormatting, Selector, ExtId);
1109			},
1110			"register_document_range_formatting_provider" => {
1111				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1112				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1113				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1114				self.RegisterProvider(Handle, ProviderType::DocumentRangeFormatting, Selector, ExtId);
1115			},
1116			"register_on_type_formatting_provider" => {
1117				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1118				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1119				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1120				self.RegisterProvider(Handle, ProviderType::OnTypeFormatting, Selector, ExtId);
1121			},
1122			"register_signature_help_provider" => {
1123				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1124				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1125				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1126				self.RegisterProvider(Handle, ProviderType::SignatureHelp, Selector, ExtId);
1127			},
1128			"register_code_lens_provider" => {
1129				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1130				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1131				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1132				self.RegisterProvider(Handle, ProviderType::CodeLens, Selector, ExtId);
1133			},
1134			"register_folding_range_provider" => {
1135				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1136				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1137				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1138				self.RegisterProvider(Handle, ProviderType::FoldingRange, Selector, ExtId);
1139			},
1140			"register_selection_range_provider" => {
1141				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1142				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1143				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1144				self.RegisterProvider(Handle, ProviderType::SelectionRange, Selector, ExtId);
1145			},
1146			"register_semantic_tokens_provider" => {
1147				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1148				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1149				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1150				self.RegisterProvider(Handle, ProviderType::SemanticTokens, Selector, ExtId);
1151			},
1152			"register_inlay_hints_provider" => {
1153				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1154				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1155				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1156				self.RegisterProvider(Handle, ProviderType::InlayHint, Selector, ExtId);
1157			},
1158			"register_type_hierarchy_provider" => {
1159				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1160				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1161				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1162				self.RegisterProvider(Handle, ProviderType::TypeHierarchy, Selector, ExtId);
1163			},
1164			"register_call_hierarchy_provider" => {
1165				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1166				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1167				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1168				self.RegisterProvider(Handle, ProviderType::CallHierarchy, Selector, ExtId);
1169			},
1170			"register_linked_editing_range_provider" => {
1171				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1172				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1173				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1174				self.RegisterProvider(Handle, ProviderType::LinkedEditingRange, Selector, ExtId);
1175			},
1176			"register_document_link_provider" => {
1177				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1178				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1179				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1180				self.RegisterProvider(Handle, ProviderType::DocumentLink, Selector, ExtId);
1181			},
1182			"register_color_provider" => {
1183				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1184				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1185				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1186				self.RegisterProvider(Handle, ProviderType::Color, Selector, ExtId);
1187			},
1188			"register_implementation_provider" => {
1189				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1190				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1191				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1192				self.RegisterProvider(Handle, ProviderType::Implementation, Selector, ExtId);
1193			},
1194			"register_type_definition_provider" => {
1195				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1196				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1197				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1198				self.RegisterProvider(Handle, ProviderType::TypeDefinition, Selector, ExtId);
1199			},
1200			"register_declaration_provider" => {
1201				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1202				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1203				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1204				self.RegisterProvider(Handle, ProviderType::Declaration, Selector, ExtId);
1205			},
1206			"register_evaluatable_expression_provider" => {
1207				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1208				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1209				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1210				self.RegisterProvider(Handle, ProviderType::EvaluatableExpression, Selector, ExtId);
1211			},
1212			"register_inline_values_provider" => {
1213				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0) as u32;
1214				let Selector = Params.get("language_selector").and_then(|V| V.as_str()).unwrap_or("*");
1215				let ExtId = Params.get("extension_id").and_then(|V| V.as_str()).unwrap_or("");
1216				self.RegisterProvider(Handle, ProviderType::InlineValues, Selector, ExtId);
1217			},
1218			// ---- Webview ----
1219			"onDidReceiveMessage" => {
1220				use tauri::Emitter;
1221				let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0);
1222				let Message = Params
1223					.get("stringMessage")
1224					.and_then(|V| V.as_str())
1225					.map(|S| S.to_string())
1226					.or_else(|| Params.get("bytesMessage").map(|_| "[binary]".to_string()))
1227					.unwrap_or_default();
1228				let _ = self
1229					.environment
1230					.ApplicationHandle
1231					.emit("sky://webview/message", json!({ "handle": Handle, "message": Message }));
1232			},
1233			// ---- Secrets (fire-and-forget variants) ----
1234			"storeSecret" => {
1235				use CommonLibrary::Secret::SecretProvider::SecretProvider;
1236				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1237				let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
1238				let Value = Params.get("value").and_then(|V| V.as_str()).unwrap_or("").to_string();
1239				let _ = self.environment.StoreSecret(ExtensionId, Key, Value).await;
1240			},
1241			"deleteSecret" => {
1242				use CommonLibrary::Secret::SecretProvider::SecretProvider;
1243				let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1244				let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
1245				let _ = self.environment.DeleteSecret(ExtensionId, Key).await;
1246			},
1247			// ---- File system (fire-and-forget write) ----
1248			"writeFile" => {
1249				let Uri = Params
1250					.get("uri")
1251					.and_then(|V| V.get("value").or(Some(V)))
1252					.and_then(|V| V.as_str())
1253					.unwrap_or("")
1254					.replace("file://", "");
1255				let Content:Vec<u8> = Params
1256					.get("content")
1257					.and_then(|V| V.as_array())
1258					.map(|A| A.iter().filter_map(|B| B.as_u64().map(|N| N as u8)).collect())
1259					.unwrap_or_default();
1260				let _ = tokio::fs::write(&Uri, &Content).await;
1261			},
1262			// ---- Webview panel ----
1263			"webview.postMessage" => {
1264				use tauri::Emitter;
1265				let PanelId = Params.get("panelId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1266				let Method = Params.get("method").and_then(|V| V.as_str()).unwrap_or("").to_string();
1267				let MsgParams = Params.get("params").cloned().unwrap_or(serde_json::Value::Null);
1268				let _ = self.environment.ApplicationHandle.emit(
1269					"sky://webview/message",
1270					json!({ "panelId": PanelId, "method": Method, "params": MsgParams }),
1271				);
1272			},
1273			"webview.dispose" => {
1274				use tauri::Emitter;
1275				let PanelId = Params.get("panelId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1276				let _ = self
1277					.environment
1278					.ApplicationHandle
1279					.emit("sky://webview/dispose", json!({ "panelId": PanelId }));
1280			},
1281			// ---- Progress indicator ----
1282			"progress.start" => {
1283				use tauri::Emitter;
1284				let Id = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
1285				let Title = Params.get("title").and_then(|V| V.as_str()).map(|S| S.to_string());
1286				let Location = Params.get("location").cloned();
1287				let Cancellable = Params.get("cancellable").and_then(|V| V.as_bool()).unwrap_or(false);
1288				let _ = self.environment.ApplicationHandle.emit(
1289					"sky://progress/start",
1290					json!({ "id": Id, "title": Title, "location": Location, "cancellable": Cancellable }),
1291				);
1292			},
1293			"progress.update" => {
1294				use tauri::Emitter;
1295				let Id = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
1296				let Message = Params.get("message").and_then(|V| V.as_str()).map(|S| S.to_string());
1297				let Increment = Params.get("increment").and_then(|V| V.as_f64());
1298				let _ = self.environment.ApplicationHandle.emit(
1299					"sky://progress/update",
1300					json!({ "id": Id, "message": Message, "increment": Increment }),
1301				);
1302			},
1303			"progress.complete" => {
1304				use tauri::Emitter;
1305				let Id = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
1306				let _ = self
1307					.environment
1308					.ApplicationHandle
1309					.emit("sky://progress/complete", json!({ "id": Id }));
1310			},
1311			// ---- Native shell ----
1312			"openExternal" => {
1313				use tauri::Emitter;
1314				let Url = Params.get("url").and_then(|V| V.as_str()).unwrap_or("").to_string();
1315				let _ = self
1316					.environment
1317					.ApplicationHandle
1318					.emit("sky://native/openExternal", json!({ "url": Url }));
1319			},
1320			// ---- StatusBar updates (fire-and-forget from Window.ts setters) ----
1321			"setStatusBarText" | "statusBar.setText" => {
1322				use tauri::Emitter;
1323				let ItemId = Params.get("itemId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1324				let Text = Params.get("text").and_then(|V| V.as_str()).unwrap_or("").to_string();
1325				let _ = self
1326					.environment
1327					.ApplicationHandle
1328					.emit("sky://statusbar/update", json!({ "id": ItemId, "text": Text }));
1329			},
1330			"disposeStatusBarItem" | "statusBar.dispose" => {
1331				use tauri::Emitter;
1332				let ItemId = Params.get("itemId").and_then(|V| V.as_str()).unwrap_or("").to_string();
1333				let _ = self
1334					.environment
1335					.ApplicationHandle
1336					.emit("sky://statusbar/dispose", json!({ "id": ItemId }));
1337			},
1338			// ---- Output channel (fire-and-forget from Window.ts OutputChannel proxy) ----
1339			"output.create" => {
1340				use tauri::Emitter;
1341				let Id = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
1342				let Name = Params.get("name").and_then(|V| V.as_str()).unwrap_or("").to_string();
1343				let _ = self
1344					.environment
1345					.ApplicationHandle
1346					.emit("sky://output/create", json!({ "id": Id, "name": Name }));
1347			},
1348			"output.append" => {
1349				use tauri::Emitter;
1350				let Channel = Params.get("channel").and_then(|V| V.as_str()).unwrap_or("").to_string();
1351				let Text = Params.get("value").and_then(|V| V.as_str()).unwrap_or("").to_string();
1352				let _ = self
1353					.environment
1354					.ApplicationHandle
1355					.emit("sky://output/append", json!({ "channel": Channel, "text": Text }));
1356			},
1357			"output.appendLine" => {
1358				use tauri::Emitter;
1359				let Channel = Params.get("channel").and_then(|V| V.as_str()).unwrap_or("").to_string();
1360				let Line = Params.get("value").and_then(|V| V.as_str()).unwrap_or("").to_string();
1361				let _ = self.environment.ApplicationHandle.emit(
1362					"sky://output/append",
1363					json!({ "channel": Channel, "text": format!("{}\n", Line) }),
1364				);
1365			},
1366			"output.clear" => {
1367				use tauri::Emitter;
1368				let Channel = Params.get("channel").and_then(|V| V.as_str()).unwrap_or("").to_string();
1369				let _ = self
1370					.environment
1371					.ApplicationHandle
1372					.emit("sky://output/clear", json!({ "channel": Channel }));
1373			},
1374			"output.show" => {
1375				use tauri::Emitter;
1376				let Channel = Params.get("channel").and_then(|V| V.as_str()).unwrap_or("").to_string();
1377				let _ = self
1378					.environment
1379					.ApplicationHandle
1380					.emit("sky://output/show", json!({ "channel": Channel }));
1381			},
1382			"output.dispose" => {
1383				use tauri::Emitter;
1384				let Channel = Params.get("channel").and_then(|V| V.as_str()).unwrap_or("").to_string();
1385				let _ = self
1386					.environment
1387					.ApplicationHandle
1388					.emit("sky://output/dispose", json!({ "channel": Channel }));
1389			},
1390			// ---- Language configuration ----
1391			"set_language_configuration" => {
1392				// Language configuration is consumed by Sky - emit for workbench to pick up
1393				use tauri::Emitter;
1394				let Language = Params.get("language").and_then(|V| V.as_str()).unwrap_or("").to_string();
1395				let _ = self
1396					.environment
1397					.ApplicationHandle
1398					.emit("sky://language/configure", json!({ "language": Language }));
1399			},
1400			_ => {
1401				dev_log!(
1402					"cocoon",
1403					"[CocoonService] Unknown notification method: '{}'",
1404					notification.method
1405				);
1406			},
1407		}
1408
1409		Ok(Response::new(Empty {}))
1410	}
1411
1412	/// Cancel operations requested by Mountain
1413	async fn cancel_operation(&self, request:Request<CancelOperationRequest>) -> Result<Response<Empty>, Status> {
1414		let cancel_request = request.into_inner();
1415		dev_log!(
1416			"cocoon",
1417			"[CocoonService] Cancel operation request: {}",
1418			cancel_request.request_identifier_to_cancel
1419		);
1420
1421		// ActiveOperations tracking and cancellation logic
1422		if let Some(token) = self
1423			.ActiveOperations
1424			.read()
1425			.await
1426			.get(&cancel_request.request_identifier_to_cancel)
1427		{
1428			dev_log!(
1429				"cocoon",
1430				"[CocoonService] Triggering cancellation token for operation {}",
1431				cancel_request.request_identifier_to_cancel
1432			);
1433			token.cancel();
1434		} else {
1435			dev_log!(
1436				"cocoon",
1437				"warn: [CocoonService] No active operation found for cancellation: {}",
1438				cancel_request.request_identifier_to_cancel
1439			);
1440		}
1441
1442		Ok(Response::new(Empty {}))
1443	}
1444
1445	// ==================== Initialization ====================
1446
1447	/// Handshake - Called by Cocoon to signal readiness
1448	async fn initial_handshake(&self, _request:Request<Empty>) -> Result<Response<Empty>, Status> {
1449		dev_log!("cocoon", "[CocoonService] Initial handshake received from Cocoon");
1450		Ok(Response::new(Empty {}))
1451	}
1452
1453	/// Initialize Extension Host - Mountain sends initialization data to Cocoon
1454	async fn init_extension_host(&self, request:Request<InitExtensionHostRequest>) -> Result<Response<Empty>, Status> {
1455		let req = request.into_inner();
1456		dev_log!(
1457			"cocoon",
1458			"[CocoonService] Initializing extension host with {} workspace folders",
1459			req.workspace_folders.len()
1460		);
1461
1462		// Initialize workspace folders in MountainEnvironment
1463		// This stub logs the workspace folders for debugging
1464		for folder in &req.workspace_folders {
1465			dev_log!(
1466				"cocoon",
1467				"[CocoonService] Workspace folder: {} ({})",
1468				folder.name,
1469				folder.uri.as_ref().map(|u| &u.value).unwrap_or(&String::new())
1470			);
1471		}
1472
1473		// Initialize configuration from request
1474		dev_log!("cocoon", "[CocoonService] Configuration: {} keys", req.configuration.len());
1475
1476		// Store workspace folders from the init payload into ApplicationState
1477		let Folders:Vec<WorkspaceFolderStateDTO> = req
1478			.workspace_folders
1479			.iter()
1480			.enumerate()
1481			.filter_map(|(Index, F)| {
1482				let UriValue = F.uri.as_ref().map(|U| U.value.as_str()).unwrap_or("");
1483				url::Url::parse(UriValue)
1484					.ok()
1485					.and_then(|ParsedUrl| WorkspaceFolderStateDTO::New(ParsedUrl, F.name.clone(), Index).ok())
1486			})
1487			.collect();
1488
1489		if !Folders.is_empty() {
1490			self.environment.ApplicationState.Workspace.SetWorkspaceFolders(Folders);
1491			dev_log!(
1492				"cocoon",
1493				"[CocoonService] Workspace folders stored: {}",
1494				req.workspace_folders.len()
1495			);
1496		}
1497
1498		Ok(Response::new(Empty {}))
1499	}
1500
1501	// ==================== Commands ====================
1502
1503	/// Register Command - Cocoon registers an extension command
1504	async fn register_command(&self, request:Request<RegisterCommandRequest>) -> Result<Response<Empty>, Status> {
1505		let req = request.into_inner();
1506		dev_log!(
1507			"cocoon",
1508			"[CocoonService] Registering command '{}' from extension '{}'",
1509			req.command_id,
1510			req.extension_id
1511		);
1512
1513		// Wire to CommandExecutor::RegisterCommand which stores a Proxied handler
1514		// pointing back to the Cocoon sidecar.
1515		if let Err(Error) = self
1516			.environment
1517			.RegisterCommand(req.extension_id.clone(), req.command_id.clone())
1518			.await
1519		{
1520			dev_log!(
1521				"cocoon",
1522				"warn: [CocoonService] Failed to register command '{}': {:?}",
1523				req.command_id,
1524				Error
1525			);
1526		} else {
1527			dev_log!(
1528				"cocoon",
1529				"[CocoonService] Command registered: id={}, title={:?}",
1530				req.command_id,
1531				req.title
1532			);
1533		}
1534
1535		Ok(Response::new(Empty {}))
1536	}
1537
1538	/// Execute Contributed Command - Mountain executes an extension command
1539	async fn execute_contributed_command(
1540		&self,
1541		request:Request<ExecuteCommandRequest>,
1542	) -> Result<Response<ExecuteCommandResponse>, Status> {
1543		let req = request.into_inner();
1544		dev_log!(
1545			"cocoon",
1546			"[CocoonService] Executing command '{}' with {} arguments",
1547			req.command_id,
1548			req.arguments.len()
1549		);
1550
1551		// Look up command handler and execute with parameters
1552		// This stub logs the command execution for debugging
1553		for (i, arg) in req.arguments.iter().enumerate() {
1554			dev_log!("cocoon", "[CocoonService] Argument {}: {:?}", i, arg);
1555		}
1556
1557		// Convert the first Argument oneof value to a serde_json::Value
1558		let Arg:serde_json::Value = req
1559			.arguments
1560			.first()
1561			.and_then(|A| A.value.as_ref())
1562			.map(|V| {
1563				match V {
1564					crate::Vine::Generated::argument::Value::StringValue(S) => json!(S),
1565					crate::Vine::Generated::argument::Value::IntValue(I) => json!(I),
1566					crate::Vine::Generated::argument::Value::BoolValue(B) => json!(B),
1567					crate::Vine::Generated::argument::Value::BytesValue(Bytes) => {
1568						serde_json::from_slice(Bytes).unwrap_or(serde_json::Value::Null)
1569					},
1570				}
1571			})
1572			.unwrap_or(serde_json::Value::Null);
1573
1574		match self.environment.ExecuteCommand(req.command_id, Arg).await {
1575			Ok(Value) => {
1576				let Bytes = serde_json::to_vec(&Value).unwrap_or_default();
1577				Ok(Response::new(ExecuteCommandResponse {
1578					result:Some(crate::Vine::Generated::execute_command_response::Result::Value(Bytes)),
1579				}))
1580			},
1581			Err(Error) => {
1582				let Bytes = serde_json::to_vec(&Error.to_string()).unwrap_or_default();
1583				Ok(Response::new(ExecuteCommandResponse {
1584					result:Some(crate::Vine::Generated::execute_command_response::Result::Error(
1585						crate::Vine::Generated::RpcError { code:-32000, message:Error.to_string(), data:Bytes },
1586					)),
1587				}))
1588			},
1589		}
1590	}
1591
1592	/// Unregister Command - Unregister a previously registered command
1593	async fn unregister_command(&self, request:Request<UnregisterCommandRequest>) -> Result<Response<Empty>, Status> {
1594		let req = request.into_inner();
1595		dev_log!("cocoon", "[CocoonService] Unregistering command '{}'", req.command_id);
1596
1597		// Wire to CommandExecutor::UnregisterCommand
1598		if let Err(Error) = self.environment.UnregisterCommand(String::new(), req.command_id.clone()).await {
1599			dev_log!(
1600				"cocoon",
1601				"warn: [CocoonService] Failed to unregister command '{}': {:?}",
1602				req.command_id,
1603				Error
1604			);
1605		} else {
1606			dev_log!("cocoon", "[CocoonService] Command removed: {}", req.command_id);
1607		}
1608
1609		Ok(Response::new(Empty {}))
1610	}
1611
1612	// ==================== Language Features ====================
1613
1614	/// Register Hover Provider - Register a hover provider
1615	async fn register_hover_provider(
1616		&self,
1617		request:Request<RegisterProviderRequest>,
1618	) -> Result<Response<Empty>, Status> {
1619		let req = request.into_inner();
1620		dev_log!(
1621			"cocoon",
1622			"[CocoonService] Registering hover provider for '{}' with handle {}",
1623			req.language_selector,
1624			req.handle
1625		);
1626		self.RegisterProvider(req.handle, ProviderType::Hover, &req.language_selector, &req.extension_id);
1627		Ok(Response::new(Empty {}))
1628	}
1629
1630	/// Provide Hover - Request hover information
1631	///
1632	/// Delegates to LanguageFeatureProviderRegistry::ProvideHover which:
1633	/// 1. Looks up the matching provider in ProviderRegistration by document
1634	///    URI
1635	/// 2. Forwards to Cocoon via generic gRPC ($provideHover)
1636	/// 3. Cocoon's InvokeLanguageProvider calls the JS extension provider
1637	/// 4. Result bubbles back through the chain
1638	async fn provide_hover(
1639		&self,
1640		request:Request<ProvideHoverRequest>,
1641	) -> Result<Response<ProvideHoverResponse>, Status> {
1642		let req = request.into_inner();
1643		dev_log!("cocoon", "[CocoonService] Providing hover for provider {}", req.provider_handle);
1644
1645		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
1646		let document_uri =
1647			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
1648
1649		let position = req.position.as_ref();
1650		let position_dto = PositionDTO {
1651			LineNumber:position.map(|p| p.line).unwrap_or(0),
1652			Column:position.map(|p| p.character).unwrap_or(0),
1653		};
1654
1655		match self.environment.ProvideHover(document_uri, position_dto).await {
1656			Ok(Some(hover)) => {
1657				let markdown = hover
1658					.Contents
1659					.iter()
1660					.map(|c| c.Value.as_str())
1661					.collect::<Vec<_>>()
1662					.join("\n---\n");
1663				let range = hover.Range.map(|r| {
1664					Range {
1665						start:Some(Position { line:r.StartLineNumber, character:r.StartColumn }),
1666						end:Some(Position { line:r.EndLineNumber, character:r.EndColumn }),
1667					}
1668				});
1669				Ok(Response::new(ProvideHoverResponse { markdown, range }))
1670			},
1671			Ok(None) => {
1672				dev_log!("cocoon", "[CocoonService] No hover provider found for request");
1673				Ok(Response::new(ProvideHoverResponse { markdown:String::new(), range:None }))
1674			},
1675			Err(e) => {
1676				dev_log!("cocoon", "warn: [CocoonService] Hover failed: {}", e);
1677				Err(Status::internal(format!("Hover failed: {}", e)))
1678			},
1679		}
1680	}
1681
1682	/// Register Completion Item Provider - Register a completion provider
1683	async fn register_completion_item_provider(
1684		&self,
1685		request:Request<RegisterProviderRequest>,
1686	) -> Result<Response<Empty>, Status> {
1687		let req = request.into_inner();
1688		dev_log!(
1689			"cocoon",
1690			"[CocoonService] Registering completion provider for '{}' with handle {}",
1691			req.language_selector,
1692			req.handle
1693		);
1694		self.RegisterProvider(req.handle, ProviderType::Completion, &req.language_selector, &req.extension_id);
1695		Ok(Response::new(Empty {}))
1696	}
1697
1698	/// Provide Completion Items - delegates to LanguageFeatureProviderRegistry
1699	async fn provide_completion_items(
1700		&self,
1701		request:Request<ProvideCompletionItemsRequest>,
1702	) -> Result<Response<ProvideCompletionItemsResponse>, Status> {
1703		let req = request.into_inner();
1704		dev_log!(
1705			"cocoon",
1706			"[CocoonService] Providing completions for provider {}",
1707			req.provider_handle
1708		);
1709
1710		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
1711		let document_uri =
1712			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
1713		let position = req.position.as_ref();
1714		let position_dto = PositionDTO {
1715			LineNumber:position.map(|p| p.line).unwrap_or(0),
1716			Column:position.map(|p| p.character).unwrap_or(0),
1717		};
1718		let context_dto = CommonLibrary::LanguageFeature::DTO::CompletionContextDTO::CompletionContextDTO {
1719			TriggerKind:CommonLibrary::LanguageFeature::DTO::CompletionContextDTO::CompletionTriggerKindDTO::Invoke,
1720			TriggerCharacter:if req.trigger_character.is_empty() {
1721				None
1722			} else {
1723				Some(req.trigger_character.clone())
1724			},
1725		};
1726
1727		match self
1728			.environment
1729			.ProvideCompletions(document_uri, position_dto, context_dto, None)
1730			.await
1731		{
1732			Ok(Some(list)) => {
1733				let items = list
1734					.Suggestions
1735					.iter()
1736					.map(|s| {
1737						CompletionItem {
1738							label:s.Label.as_str().map(|l| l.to_string()).unwrap_or_default(),
1739							kind:format!("{}", s.Kind),
1740							detail:s.Detail.clone().unwrap_or_default(),
1741							documentation:Vec::new(),
1742							insert_text:s.InsertText.as_ref().and_then(|v| v.as_str()).unwrap_or("").to_string(),
1743						}
1744					})
1745					.collect();
1746				Ok(Response::new(ProvideCompletionItemsResponse { items }))
1747			},
1748			Ok(None) => Ok(Response::new(ProvideCompletionItemsResponse { items:Vec::new() })),
1749			Err(e) => Err(Status::internal(format!("Completions failed: {}", e))),
1750		}
1751	}
1752
1753	/// Register Definition Provider - Register a definition provider
1754	async fn register_definition_provider(
1755		&self,
1756		request:Request<RegisterProviderRequest>,
1757	) -> Result<Response<Empty>, Status> {
1758		let req = request.into_inner();
1759		dev_log!(
1760			"cocoon",
1761			"[CocoonService] Registering definition provider for '{}' with handle {}",
1762			req.language_selector,
1763			req.handle
1764		);
1765		self.RegisterProvider(req.handle, ProviderType::Definition, &req.language_selector, &req.extension_id);
1766		Ok(Response::new(Empty {}))
1767	}
1768
1769	/// Provide Definition - delegates to LanguageFeatureProviderRegistry
1770	async fn provide_definition(
1771		&self,
1772		request:Request<ProvideDefinitionRequest>,
1773	) -> Result<Response<ProvideDefinitionResponse>, Status> {
1774		let req = request.into_inner();
1775		dev_log!(
1776			"cocoon",
1777			"[CocoonService] Providing definition for provider {}",
1778			req.provider_handle
1779		);
1780
1781		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
1782		let document_uri =
1783			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
1784		let position = req.position.as_ref();
1785		let position_dto = PositionDTO {
1786			LineNumber:position.map(|p| p.line).unwrap_or(0),
1787			Column:position.map(|p| p.character).unwrap_or(0),
1788		};
1789
1790		match self.environment.ProvideDefinition(document_uri, position_dto).await {
1791			Ok(Some(locations)) => {
1792				let proto_locations = locations
1793					.iter()
1794					.map(|loc| {
1795						Location {
1796							uri:Some(Uri { value:loc.Uri.to_string() }),
1797							range:Some(Range {
1798								start:Some(Position {
1799									line:loc.Range.StartLineNumber,
1800									character:loc.Range.StartColumn,
1801								}),
1802								end:Some(Position { line:loc.Range.EndLineNumber, character:loc.Range.EndColumn }),
1803							}),
1804						}
1805					})
1806					.collect();
1807				Ok(Response::new(ProvideDefinitionResponse { locations:proto_locations }))
1808			},
1809			Ok(None) => Ok(Response::new(ProvideDefinitionResponse { locations:Vec::new() })),
1810			Err(e) => Err(Status::internal(format!("Definition failed: {}", e))),
1811		}
1812	}
1813
1814	/// Register Reference Provider - Register a reference provider
1815	async fn register_reference_provider(
1816		&self,
1817		request:Request<RegisterProviderRequest>,
1818	) -> Result<Response<Empty>, Status> {
1819		let req = request.into_inner();
1820		dev_log!(
1821			"cocoon",
1822			"[CocoonService] Registering reference provider for '{}' with handle {}",
1823			req.language_selector,
1824			req.handle
1825		);
1826		self.RegisterProvider(req.handle, ProviderType::References, &req.language_selector, &req.extension_id);
1827		Ok(Response::new(Empty {}))
1828	}
1829
1830	/// Provide References - delegates to LanguageFeatureProviderRegistry
1831	async fn provide_references(
1832		&self,
1833		request:Request<ProvideReferencesRequest>,
1834	) -> Result<Response<ProvideReferencesResponse>, Status> {
1835		let req = request.into_inner();
1836		dev_log!(
1837			"cocoon",
1838			"[CocoonService] Providing references for provider {}",
1839			req.provider_handle
1840		);
1841
1842		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
1843		let document_uri =
1844			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
1845		let position = req.position.as_ref();
1846		let position_dto = PositionDTO {
1847			LineNumber:position.map(|p| p.line).unwrap_or(0),
1848			Column:position.map(|p| p.character).unwrap_or(0),
1849		};
1850		let context_dto = json!({ "includeDeclaration": true });
1851
1852		match self
1853			.environment
1854			.ProvideReferences(document_uri, position_dto, context_dto)
1855			.await
1856		{
1857			Ok(Some(locations)) => {
1858				let proto_locations = locations
1859					.iter()
1860					.map(|loc| {
1861						Location {
1862							uri:Some(Uri { value:loc.Uri.to_string() }),
1863							range:Some(Range {
1864								start:Some(Position {
1865									line:loc.Range.StartLineNumber,
1866									character:loc.Range.StartColumn,
1867								}),
1868								end:Some(Position { line:loc.Range.EndLineNumber, character:loc.Range.EndColumn }),
1869							}),
1870						}
1871					})
1872					.collect();
1873				Ok(Response::new(ProvideReferencesResponse { locations:proto_locations }))
1874			},
1875			Ok(None) => Ok(Response::new(ProvideReferencesResponse { locations:Vec::new() })),
1876			Err(e) => Err(Status::internal(format!("References failed: {}", e))),
1877		}
1878	}
1879
1880	/// Register Code Actions Provider - Register code actions provider
1881	async fn register_code_actions_provider(
1882		&self,
1883		request:Request<RegisterProviderRequest>,
1884	) -> Result<Response<Empty>, Status> {
1885		let req = request.into_inner();
1886		dev_log!(
1887			"cocoon",
1888			"[CocoonService] Registering code actions provider for '{}' with handle {}",
1889			req.language_selector,
1890			req.handle
1891		);
1892		self.RegisterProvider(req.handle, ProviderType::CodeAction, &req.language_selector, &req.extension_id);
1893		Ok(Response::new(Empty {}))
1894	}
1895
1896	/// Provide Code Actions - delegates to LanguageFeatureProviderRegistry
1897	async fn provide_code_actions(
1898		&self,
1899		request:Request<ProvideCodeActionsRequest>,
1900	) -> Result<Response<ProvideCodeActionsResponse>, Status> {
1901		let req = request.into_inner();
1902		dev_log!(
1903			"cocoon",
1904			"[CocoonService] Providing code actions for provider {}",
1905			req.provider_handle
1906		);
1907
1908		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
1909		let document_uri =
1910			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
1911		let range = req.range.as_ref();
1912		let range_dto = json!({
1913			"StartLineNumber": range.and_then(|r| r.start.as_ref()).map(|p| p.line).unwrap_or(0),
1914			"StartColumn": range.and_then(|r| r.start.as_ref()).map(|p| p.character).unwrap_or(0),
1915			"EndLineNumber": range.and_then(|r| r.end.as_ref()).map(|p| p.line).unwrap_or(0),
1916			"EndColumn": range.and_then(|r| r.end.as_ref()).map(|p| p.character).unwrap_or(0),
1917		});
1918		let context_dto = json!({ "diagnostics": [], "only": null });
1919
1920		match self.environment.ProvideCodeActions(document_uri, range_dto, context_dto).await {
1921			Ok(Some(value)) => {
1922				// CodeActions returns raw Value — proto response has actions:Vec<CodeAction>
1923				// For now, return empty if we can't parse. Full mapping requires matching
1924				// the CodeAction proto fields to the VS Code code action format.
1925				Ok(Response::new(ProvideCodeActionsResponse { actions:Vec::new() }))
1926			},
1927			Ok(None) => Ok(Response::new(ProvideCodeActionsResponse { actions:Vec::new() })),
1928			Err(e) => Err(Status::internal(format!("Code actions failed: {}", e))),
1929		}
1930	}
1931
1932	// ==================== Window Operations ====================
1933
1934	/// Show Text Document - emit a Tauri event so Sky opens the document tab.
1935	async fn show_text_document(
1936		&self,
1937		request:Request<ShowTextDocumentRequest>,
1938	) -> Result<Response<ShowTextDocumentResponse>, Status> {
1939		use tauri::Emitter;
1940
1941		let req = request.into_inner();
1942		let Uri = req.uri.as_ref().map(|U| U.value.clone()).unwrap_or_default();
1943		dev_log!("cocoon", "[CocoonService] show_text_document: {}", Uri);
1944
1945		let _ = self.environment.ApplicationHandle.emit(
1946			"sky://editor/openDocument",
1947			json!({ "uri": Uri, "viewColumn": req.view_column }),
1948		);
1949
1950		Ok(Response::new(ShowTextDocumentResponse { success:true }))
1951	}
1952
1953	/// Show Information Message - delegate to
1954	/// UserInterfaceProvider::ShowMessage.
1955	async fn show_information_message(
1956		&self,
1957		request:Request<ShowMessageRequest>,
1958	) -> Result<Response<ShowMessageResponse>, Status> {
1959		use CommonLibrary::UserInterface::{
1960			DTO::MessageSeverity::MessageSeverity,
1961			UserInterfaceProvider::UserInterfaceProvider,
1962		};
1963
1964		let req = request.into_inner();
1965		dev_log!("cocoon", "[CocoonService] show_information_message: {}", req.message);
1966
1967		let _ = self.environment.ShowMessage(MessageSeverity::Info, req.message, None).await;
1968
1969		Ok(Response::new(ShowMessageResponse { success:true }))
1970	}
1971
1972	/// Show Warning Message - delegate to UserInterfaceProvider::ShowMessage.
1973	async fn show_warning_message(
1974		&self,
1975		request:Request<ShowMessageRequest>,
1976	) -> Result<Response<ShowMessageResponse>, Status> {
1977		use CommonLibrary::UserInterface::{
1978			DTO::MessageSeverity::MessageSeverity,
1979			UserInterfaceProvider::UserInterfaceProvider,
1980		};
1981
1982		let req = request.into_inner();
1983		dev_log!("cocoon", "warn: [CocoonService] show_warning_message: {}", req.message);
1984
1985		let _ = self.environment.ShowMessage(MessageSeverity::Warning, req.message, None).await;
1986
1987		Ok(Response::new(ShowMessageResponse { success:true }))
1988	}
1989
1990	/// Show Error Message - delegate to UserInterfaceProvider::ShowMessage.
1991	async fn show_error_message(
1992		&self,
1993		request:Request<ShowMessageRequest>,
1994	) -> Result<Response<ShowMessageResponse>, Status> {
1995		use CommonLibrary::UserInterface::{
1996			DTO::MessageSeverity::MessageSeverity,
1997			UserInterfaceProvider::UserInterfaceProvider,
1998		};
1999
2000		let req = request.into_inner();
2001		dev_log!("cocoon", "error: [CocoonService] show_error_message: {}", req.message);
2002
2003		let _ = self.environment.ShowMessage(MessageSeverity::Error, req.message, None).await;
2004
2005		Ok(Response::new(ShowMessageResponse { success:true }))
2006	}
2007
2008	/// Create Status Bar Item - emit Tauri event for Sky to render status bar
2009	/// entry.
2010	async fn create_status_bar_item(
2011		&self,
2012		request:Request<CreateStatusBarItemRequest>,
2013	) -> Result<Response<CreateStatusBarItemResponse>, Status> {
2014		use tauri::Emitter;
2015
2016		let req = request.into_inner();
2017		dev_log!("cocoon", "[CocoonService] create_status_bar_item: {}", req.id);
2018
2019		let _ = self.environment.ApplicationHandle.emit(
2020			"sky://statusbar/create",
2021			json!({ "id": req.id, "text": req.text, "tooltip": req.tooltip }),
2022		);
2023
2024		Ok(Response::new(CreateStatusBarItemResponse { item_id:req.id.clone() }))
2025	}
2026
2027	/// Set Status Bar Text - emit Tauri event for Sky status bar update.
2028	async fn set_status_bar_text(&self, request:Request<SetStatusBarTextRequest>) -> Result<Response<Empty>, Status> {
2029		use tauri::Emitter;
2030
2031		let req = request.into_inner();
2032		dev_log!(
2033			"cocoon",
2034			"[CocoonService] set_status_bar_text: id={} text={}",
2035			req.item_id,
2036			req.text
2037		);
2038
2039		let _ = self
2040			.environment
2041			.ApplicationHandle
2042			.emit("sky://statusbar/update", json!({ "id": req.item_id, "text": req.text }));
2043
2044		Ok(Response::new(Empty {}))
2045	}
2046
2047	/// Create Webview Panel - Generate handle, emit creation event to Sky
2048	async fn create_webview_panel(
2049		&self,
2050		request:Request<CreateWebviewPanelRequest>,
2051	) -> Result<Response<CreateWebviewPanelResponse>, Status> {
2052		use tauri::Emitter;
2053
2054		let req = request.into_inner();
2055		let Handle = SystemTime::now()
2056			.duration_since(UNIX_EPOCH)
2057			.map(|D| D.as_millis() as u32)
2058			.unwrap_or(0);
2059
2060		dev_log!(
2061			"cocoon",
2062			"[CocoonService] create_webview_panel: handle={} view_type={} title={}",
2063			Handle,
2064			req.view_type,
2065			req.title
2066		);
2067
2068		let _ = self.environment.ApplicationHandle.emit(
2069			"sky://webview/create",
2070			json!({
2071				"handle": Handle,
2072				"viewType": req.view_type,
2073				"title": req.title,
2074				"viewColumn": req.view_column,
2075				"preserveFocus": req.preserve_focus,
2076				"iconPath": req.icon_path,
2077			}),
2078		);
2079
2080		Ok(Response::new(CreateWebviewPanelResponse { handle:Handle }))
2081	}
2082
2083	/// Set Webview HTML - Send HTML update to Sky via Tauri event
2084	async fn set_webview_html(&self, request:Request<SetWebviewHtmlRequest>) -> Result<Response<Empty>, Status> {
2085		use tauri::Emitter;
2086
2087		let req = request.into_inner();
2088		dev_log!(
2089			"cocoon",
2090			"[CocoonService] set_webview_html: handle={} ({} bytes)",
2091			req.handle,
2092			req.html.len()
2093		);
2094
2095		let _ = self
2096			.environment
2097			.ApplicationHandle
2098			.emit("sky://webview/setHtml", json!({ "handle": req.handle, "html": req.html }));
2099
2100		Ok(Response::new(Empty {}))
2101	}
2102
2103	/// On Did Receive Message - Forward webview message to Cocoon via Tauri
2104	/// event
2105	async fn on_did_receive_message(
2106		&self,
2107		request:Request<OnDidReceiveMessageRequest>,
2108	) -> Result<Response<Empty>, Status> {
2109		use tauri::Emitter;
2110
2111		let req = request.into_inner();
2112		dev_log!("cocoon", "[CocoonService] on_did_receive_message: handle={}", req.handle);
2113
2114		let MessagePayload = match &req.message {
2115			Some(on_did_receive_message_request::Message::StringMessage(S)) => json!(S),
2116			Some(on_did_receive_message_request::Message::BytesMessage(B)) => json!(B),
2117			None => serde_json::Value::Null,
2118		};
2119
2120		let _ = self.environment.ApplicationHandle.emit(
2121			"sky://webview/message",
2122			json!({ "handle": req.handle, "message": MessagePayload }),
2123		);
2124
2125		Ok(Response::new(Empty {}))
2126	}
2127
2128	// ==================== File System ====================
2129
2130	/// Read File - Read file contents
2131	async fn read_file(&self, request:Request<ReadFileRequest>) -> Result<Response<ReadFileResponse>, Status> {
2132		let req = request.into_inner();
2133		let Path = Self::UriToPath(req.uri.as_ref())
2134			.ok_or_else(|| Status::invalid_argument("read_file: missing or empty URI"))?;
2135
2136		dev_log!("cocoon", "[CocoonService] Reading file: {:?}", Path);
2137
2138		let Content = tokio::fs::read(&Path).await.map_err(|Error| {
2139			dev_log!("cocoon", "warn: [CocoonService] read_file failed for {:?}: {}", Path, Error);
2140			Status::not_found(format!("read_file: {}: {}", Path.display(), Error))
2141		})?;
2142
2143		Ok(Response::new(ReadFileResponse {
2144			content:Content,
2145			encoding:"utf-8".to_string(),
2146		}))
2147	}
2148
2149	/// Write File - Write file contents
2150	async fn write_file(&self, request:Request<WriteFileRequest>) -> Result<Response<Empty>, Status> {
2151		let req = request.into_inner();
2152		let Path = Self::UriToPath(req.uri.as_ref())
2153			.ok_or_else(|| Status::invalid_argument("write_file: missing or empty URI"))?;
2154
2155		dev_log!(
2156			"cocoon",
2157			"[CocoonService] Writing file: {:?} ({} bytes)",
2158			Path,
2159			req.content.len()
2160		);
2161
2162		// Ensure parent directory exists
2163		if let Some(Parent) = Path.parent() {
2164			if !Parent.as_os_str().is_empty() {
2165				tokio::fs::create_dir_all(Parent)
2166					.await
2167					.map_err(|Error| Status::internal(format!("write_file: create_dir_all {:?}: {}", Parent, Error)))?;
2168			}
2169		}
2170
2171		tokio::fs::write(&Path, &req.content).await.map_err(|Error| {
2172			dev_log!("cocoon", "warn: [CocoonService] write_file failed for {:?}: {}", Path, Error);
2173			Status::internal(format!("write_file: {}: {}", Path.display(), Error))
2174		})?;
2175
2176		Ok(Response::new(Empty {}))
2177	}
2178
2179	/// Stat - Get file metadata
2180	async fn stat(&self, request:Request<StatRequest>) -> Result<Response<StatResponse>, Status> {
2181		let req = request.into_inner();
2182		let Path =
2183			Self::UriToPath(req.uri.as_ref()).ok_or_else(|| Status::invalid_argument("stat: missing or empty URI"))?;
2184
2185		dev_log!("cocoon", "[CocoonService] Stat: {:?}", Path);
2186
2187		let Metadata = tokio::fs::metadata(&Path).await.map_err(|Error| {
2188			dev_log!("cocoon", "warn: [CocoonService] stat failed for {:?}: {}", Path, Error);
2189			Status::not_found(format!("stat: {}: {}", Path.display(), Error))
2190		})?;
2191
2192		let Mtime = Metadata
2193			.modified()
2194			.ok()
2195			.and_then(|T| T.duration_since(UNIX_EPOCH).ok())
2196			.map(|D| D.as_millis() as u64)
2197			.unwrap_or(0);
2198
2199		Ok(Response::new(StatResponse {
2200			is_file:Metadata.is_file(),
2201			is_directory:Metadata.is_dir(),
2202			size:Metadata.len(),
2203			mtime:Mtime,
2204		}))
2205	}
2206
2207	/// Read Directory - List directory contents
2208	async fn readdir(&self, request:Request<ReaddirRequest>) -> Result<Response<ReaddirResponse>, Status> {
2209		let req = request.into_inner();
2210		let Path = Self::UriToPath(req.uri.as_ref())
2211			.ok_or_else(|| Status::invalid_argument("readdir: missing or empty URI"))?;
2212
2213		dev_log!("cocoon", "[CocoonService] Readdir: {:?}", Path);
2214
2215		let mut ReadDir = tokio::fs::read_dir(&Path).await.map_err(|Error| {
2216			dev_log!("cocoon", "warn: [CocoonService] readdir failed for {:?}: {}", Path, Error);
2217			Status::not_found(format!("readdir: {}: {}", Path.display(), Error))
2218		})?;
2219
2220		let mut Entries = Vec::new();
2221		while let Ok(Some(Entry)) = ReadDir.next_entry().await {
2222			if let Some(Name) = Entry.file_name().to_str() {
2223				Entries.push(Name.to_string());
2224			}
2225		}
2226
2227		Ok(Response::new(ReaddirResponse { entries:Entries }))
2228	}
2229
2230	/// Watch File - Watch file for changes
2231	///
2232	/// Logs the watch request. Full inotify/FSEvents integration is a P1 task
2233	/// requiring the `notify` crate wired into ApplicationState.
2234	async fn watch_file(&self, request:Request<WatchFileRequest>) -> Result<Response<Empty>, Status> {
2235		let req = request.into_inner();
2236		let Uri = req.uri.as_ref().map(|U| U.value.as_str()).unwrap_or("");
2237		dev_log!(
2238			"cocoon",
2239			"[CocoonService] watch_file registered (polling not yet active): {}",
2240			Uri
2241		);
2242		// TODO(P1): Wire notify crate watcher; store WatcherHandle in
2243		// ApplicationState.Feature.Watchers keyed by URI for cancellation on
2244		// cancel_operation.
2245		Ok(Response::new(Empty {}))
2246	}
2247
2248	// ==================== Workspace Operations ====================
2249
2250	/// Find Files - Search for files using glob pattern across workspace
2251	/// folders.
2252	async fn find_files(&self, request:Request<FindFilesRequest>) -> Result<Response<FindFilesResponse>, Status> {
2253		let req = request.into_inner();
2254		dev_log!("cocoon", "[CocoonService] Finding files with pattern: {}", req.pattern);
2255
2256		use globset::{Glob, GlobSetBuilder};
2257
2258		// Build glob matcher
2259		let GlobMatcher = Glob::new(&req.pattern)
2260			.map_err(|Error| {
2261				Status::invalid_argument(format!("find_files: invalid pattern '{}': {}", req.pattern, Error))
2262			})?
2263			.compile_matcher();
2264
2265		// Collect workspace root folders from ApplicationState
2266		let Roots:Vec<std::path::PathBuf> = {
2267			match self.environment.ApplicationState.Workspace.WorkspaceFolders.lock() {
2268				Ok(Guard) => Guard.iter().map(|F| std::path::PathBuf::from(F.URI.path())).collect(),
2269				Err(_) => Vec::new(),
2270			}
2271		};
2272
2273		let SearchRoots = if Roots.is_empty() {
2274			vec![std::env::current_dir().unwrap_or_default()]
2275		} else {
2276			Roots
2277		};
2278
2279		// Walk each root and collect matching paths
2280		let mut Uris = Vec::new();
2281
2282		fn WalkAndCollect(
2283			Directory:&std::path::Path,
2284			Root:&std::path::Path,
2285			Matcher:&globset::GlobMatcher,
2286			Results:&mut Vec<String>,
2287		) {
2288			if let Ok(Entries) = std::fs::read_dir(Directory) {
2289				for Entry in Entries.flatten() {
2290					let EntryPath = Entry.path();
2291					if EntryPath.is_dir() {
2292						WalkAndCollect(&EntryPath, Root, Matcher, Results);
2293					} else if let Ok(Relative) = EntryPath.strip_prefix(Root) {
2294						if Matcher.is_match(Relative) {
2295							Results.push(format!("file://{}", EntryPath.display()));
2296						}
2297					}
2298				}
2299			}
2300		}
2301
2302		for Root in &SearchRoots {
2303			WalkAndCollect(Root, Root, &GlobMatcher, &mut Uris);
2304		}
2305
2306		dev_log!(
2307			"cocoon",
2308			"[CocoonService] find_files: {} results for pattern '{}'",
2309			Uris.len(),
2310			req.pattern
2311		);
2312		Ok(Response::new(FindFilesResponse { uris:Uris }))
2313	}
2314
2315	/// Find Text in Files - walk workspace and grep for pattern.
2316	///
2317	/// Uses a simple line-by-line scan (not indexed). Returns up to 1000
2318	/// matches. Indexing integration is a P2 task.
2319	async fn find_text_in_files(
2320		&self,
2321		request:Request<FindTextInFilesRequest>,
2322	) -> Result<Response<FindTextInFilesResponse>, Status> {
2323		let req = request.into_inner();
2324		if req.pattern.is_empty() {
2325			return Ok(Response::new(FindTextInFilesResponse::default()));
2326		}
2327		dev_log!("cocoon", "[CocoonService] find_text_in_files: pattern='{}'", req.pattern);
2328
2329		let Roots:Vec<std::path::PathBuf> = {
2330			match self.environment.ApplicationState.Workspace.WorkspaceFolders.lock() {
2331				Ok(Guard) => Guard.iter().map(|F| std::path::PathBuf::from(F.URI.path())).collect(),
2332				Err(_) => Vec::new(),
2333			}
2334		};
2335		let SearchRoots = if Roots.is_empty() {
2336			vec![std::env::current_dir().unwrap_or_default()]
2337		} else {
2338			Roots
2339		};
2340
2341		let Pattern = req.pattern.clone();
2342		let Matches = tokio::task::spawn_blocking(move || {
2343			let mut Results:Vec<TextMatch> = Vec::new();
2344			const MAX_MATCHES:usize = 1000;
2345
2346			fn WalkAndSearch(Dir:&std::path::Path, Pattern:&str, Results:&mut Vec<TextMatch>) {
2347				if Results.len() >= 1000 {
2348					return;
2349				}
2350				if let Ok(Entries) = std::fs::read_dir(Dir) {
2351					for Entry in Entries.flatten() {
2352						if Results.len() >= MAX_MATCHES {
2353							break;
2354						}
2355						let Path = Entry.path();
2356						if Path.is_dir() {
2357							// Skip hidden dirs and common noise dirs
2358							let DirName = Path.file_name().and_then(|N| N.to_str()).unwrap_or("");
2359							if DirName.starts_with('.') || DirName == "node_modules" || DirName == "target" {
2360								continue;
2361							}
2362							WalkAndSearch(&Path, Pattern, Results);
2363						} else if Path.is_file() {
2364							if let Ok(Content) = std::fs::read_to_string(&Path) {
2365								for (LineIdx, Line) in Content.lines().enumerate() {
2366									if Results.len() >= MAX_MATCHES {
2367										break;
2368									}
2369									if let Some(ColIdx) = Line.find(Pattern) {
2370										Results.push(TextMatch {
2371											uri:Some(Uri { value:format!("file://{}", Path.display()) }),
2372											range:Some(Range {
2373												start:Some(Position { line:LineIdx as u32, character:ColIdx as u32 }),
2374												end:Some(Position {
2375													line:LineIdx as u32,
2376													character:(ColIdx + Pattern.len()) as u32,
2377												}),
2378											}),
2379											preview:Line.to_string(),
2380										});
2381									}
2382								}
2383							}
2384						}
2385					}
2386				}
2387			}
2388
2389			for Root in &SearchRoots {
2390				WalkAndSearch(Root, &Pattern, &mut Results);
2391				if Results.len() >= MAX_MATCHES {
2392					break;
2393				}
2394			}
2395			Results
2396		})
2397		.await
2398		.unwrap_or_default();
2399
2400		dev_log!(
2401			"cocoon",
2402			"[CocoonService] find_text_in_files: {} matches for '{}'",
2403			Matches.len(),
2404			req.pattern
2405		);
2406		Ok(Response::new(FindTextInFilesResponse { matches:Matches }))
2407	}
2408
2409	/// Open Document - emit Tauri event for Sky to open the editor tab.
2410	async fn open_document(
2411		&self,
2412		request:Request<OpenDocumentRequest>,
2413	) -> Result<Response<OpenDocumentResponse>, Status> {
2414		use tauri::Emitter;
2415
2416		let req = request.into_inner();
2417		let Uri = req.uri.as_ref().map(|U| U.value.clone()).unwrap_or_default();
2418		dev_log!("cocoon", "[CocoonService] open_document: {}", Uri);
2419
2420		let _ = self.environment.ApplicationHandle.emit(
2421			"sky://editor/openDocument",
2422			json!({ "uri": Uri, "viewColumn": req.view_column }),
2423		);
2424
2425		Ok(Response::new(OpenDocumentResponse { success:true }))
2426	}
2427
2428	/// Save All - emit Tauri event for Sky to save all open documents.
2429	async fn save_all(&self, request:Request<SaveAllRequest>) -> Result<Response<SaveAllResponse>, Status> {
2430		use tauri::Emitter;
2431
2432		let req = request.into_inner();
2433		dev_log!("cocoon", "[CocoonService] save_all: includeUntitled={}", req.include_untitled);
2434
2435		let _ = self
2436			.environment
2437			.ApplicationHandle
2438			.emit("sky://editor/saveAll", json!({ "includeUntitled": req.include_untitled }));
2439
2440		Ok(Response::new(SaveAllResponse { success:true }))
2441	}
2442
2443	/// Apply Edit - emit Tauri event for Sky to apply text edits in the editor.
2444	async fn apply_edit(&self, request:Request<ApplyEditRequest>) -> Result<Response<ApplyEditResponse>, Status> {
2445		use tauri::Emitter;
2446
2447		let req = request.into_inner();
2448		let Uri = req.uri.as_ref().map(|U| U.value.clone()).unwrap_or_default();
2449		dev_log!("cocoon", "[CocoonService] apply_edit: uri={} edits={}", Uri, req.edits.len());
2450
2451		let EditsJson:Vec<serde_json::Value> = req.edits.iter().map(|E| {
2452			json!({
2453				"range": {
2454					"start": E.range.as_ref().and_then(|R| R.start.as_ref()).map(|P| json!({ "line": P.line, "character": P.character })),
2455					"end": E.range.as_ref().and_then(|R| R.end.as_ref()).map(|P| json!({ "line": P.line, "character": P.character })),
2456				},
2457				"newText": E.new_text,
2458			})
2459		}).collect();
2460
2461		let _ = self
2462			.environment
2463			.ApplicationHandle
2464			.emit("sky://editor/applyEdits", json!({ "uri": Uri, "edits": EditsJson }));
2465
2466		Ok(Response::new(ApplyEditResponse { success:true }))
2467	}
2468
2469	/// Update Configuration - Notify Sky of configuration changes via Tauri
2470	/// event
2471	async fn update_configuration(
2472		&self,
2473		request:Request<UpdateConfigurationRequest>,
2474	) -> Result<Response<Empty>, Status> {
2475		use tauri::Emitter;
2476
2477		let req = request.into_inner();
2478		dev_log!(
2479			"cocoon",
2480			"[CocoonService] update_configuration: {} changed keys",
2481			req.changed_keys.len()
2482		);
2483
2484		// Forward configuration changes to Sky for workbench settings refresh
2485		let _ = self.environment.ApplicationHandle.emit(
2486			"sky://configuration/changed",
2487			json!({
2488				"changedKeys": req.changed_keys,
2489			}),
2490		);
2491
2492		Ok(Response::new(Empty {}))
2493	}
2494
2495	/// Update Workspace Folders - Update workspace folders
2496	async fn update_workspace_folders(
2497		&self,
2498		request:Request<UpdateWorkspaceFoldersRequest>,
2499	) -> Result<Response<Empty>, Status> {
2500		let req = request.into_inner();
2501		dev_log!(
2502			"cocoon",
2503			"[CocoonService] Updating workspace: {} additions, {} removals",
2504			req.additions.len(),
2505			req.removals.len()
2506		);
2507
2508		// Update WorkspaceState in MountainEnvironment
2509		for addition in &req.additions {
2510			dev_log!(
2511				"cocoon",
2512				"[CocoonService] Adding workspace folder: {} ({})",
2513				addition.name,
2514				addition.uri.as_ref().map(|u| &u.value).unwrap_or(&"?".to_string())
2515			);
2516		}
2517		for removal in &req.removals {
2518			dev_log!(
2519				"cocoon",
2520				"[CocoonService] Removing workspace folder: {}",
2521				removal.uri.as_ref().map(|u| &u.value).unwrap_or(&"?".to_string())
2522			);
2523		}
2524
2525		// Apply additions and removals to ApplicationState.Workspace
2526		{
2527			let mut Folders = self.environment.ApplicationState.Workspace.GetWorkspaceFolders();
2528
2529			// Remove by URI
2530			let RemovalUris:Vec<String> = req
2531				.removals
2532				.iter()
2533				.filter_map(|F| F.uri.as_ref().map(|U| U.value.clone()))
2534				.collect();
2535			Folders.retain(|F| !RemovalUris.contains(&F.URI.to_string()));
2536
2537			// Append additions
2538			let ExistingCount = Folders.len();
2539			for (Idx, Addition) in req.additions.iter().enumerate() {
2540				let UriValue = Addition.uri.as_ref().map(|U| U.value.as_str()).unwrap_or("");
2541				if let Ok(ParsedUrl) = url::Url::parse(UriValue) {
2542					if let Ok(DTO) = WorkspaceFolderStateDTO::New(ParsedUrl, Addition.name.clone(), ExistingCount + Idx)
2543					{
2544						Folders.push(DTO);
2545					}
2546				}
2547			}
2548
2549			self.environment.ApplicationState.Workspace.SetWorkspaceFolders(Folders);
2550		}
2551
2552		Ok(Response::new(Empty {}))
2553	}
2554
2555	// ==================== Terminal ====================
2556
2557	/// Open Terminal - create PTY via TerminalProvider and return the terminal
2558	/// ID.
2559	async fn open_terminal(&self, request:Request<OpenTerminalRequest>) -> Result<Response<Empty>, Status> {
2560		let req = request.into_inner();
2561		dev_log!("cocoon", "[CocoonService] Opening terminal: {}", req.name);
2562
2563		// Build options JSON matching TerminalStateDTO::Create expectations
2564		let Options = json!({
2565			"name": req.name,
2566			"shellPath": if req.shell_path.is_empty() { serde_json::Value::Null } else { json!(req.shell_path) },
2567			"shellArgs": req.shell_args,
2568			"cwd": if req.cwd.is_empty() { serde_json::Value::Null } else { json!(req.cwd) },
2569		});
2570
2571		match self.environment.CreateTerminal(Options).await {
2572			Ok(Info) => {
2573				dev_log!("cocoon", "[CocoonService] Terminal created: {:?}", Info);
2574				Ok(Response::new(Empty {}))
2575			},
2576			Err(Error) => {
2577				dev_log!("cocoon", "error: [CocoonService] open_terminal failed: {}", Error);
2578				Err(Status::internal(format!("open_terminal: {}", Error)))
2579			},
2580		}
2581	}
2582
2583	/// Terminal Input - write bytes to PTY stdin via TerminalProvider.
2584	async fn terminal_input(&self, request:Request<TerminalInputRequest>) -> Result<Response<Empty>, Status> {
2585		let req = request.into_inner();
2586		let TerminalId = req.terminal_id as u64;
2587		dev_log!(
2588			"cocoon",
2589			"[CocoonService] terminal_input: id={} bytes={}",
2590			TerminalId,
2591			req.data.len()
2592		);
2593
2594		let Text = String::from_utf8_lossy(&req.data).into_owned();
2595
2596		match self.environment.SendTextToTerminal(TerminalId, Text).await {
2597			Ok(()) => Ok(Response::new(Empty {})),
2598			Err(Error) => {
2599				dev_log!(
2600					"cocoon",
2601					"warn: [CocoonService] terminal_input failed id={}: {}",
2602					TerminalId,
2603					Error
2604				);
2605				Err(Status::not_found(format!("terminal_input: {}", Error)))
2606			},
2607		}
2608	}
2609
2610	/// Close Terminal - dispose PTY and cleanup resources via TerminalProvider.
2611	async fn close_terminal(&self, request:Request<CloseTerminalRequest>) -> Result<Response<Empty>, Status> {
2612		let req = request.into_inner();
2613		let TerminalId = req.terminal_id as u64;
2614		dev_log!("cocoon", "[CocoonService] close_terminal: id={}", TerminalId);
2615
2616		match self.environment.DisposeTerminal(TerminalId).await {
2617			Ok(()) => Ok(Response::new(Empty {})),
2618			Err(Error) => {
2619				dev_log!(
2620					"cocoon",
2621					"warn: [CocoonService] close_terminal failed id={}: {}",
2622					TerminalId,
2623					Error
2624				);
2625				Err(Status::internal(format!("close_terminal: {}", Error)))
2626			},
2627		}
2628	}
2629
2630	/// Accept Terminal Opened - Notification: Terminal opened
2631	async fn accept_terminal_opened(
2632		&self,
2633		request:Request<TerminalOpenedNotification>,
2634	) -> Result<Response<Empty>, Status> {
2635		let req = request.into_inner();
2636		dev_log!(
2637			"cocoon",
2638			"[CocoonService] Terminal opened notification: {} (ID: {})",
2639			req.name,
2640			req.terminal_id
2641		);
2642
2643		// Forward terminal opened event to Sky for UI update
2644		use tauri::Emitter;
2645		let _ = self
2646			.environment
2647			.ApplicationHandle
2648			.emit("sky://terminal/opened", json!({ "id": req.terminal_id, "name": req.name }));
2649
2650		Ok(Response::new(Empty {}))
2651	}
2652
2653	/// Accept Terminal Closed - Forward close event to Sky
2654	async fn accept_terminal_closed(
2655		&self,
2656		request:Request<TerminalClosedNotification>,
2657	) -> Result<Response<Empty>, Status> {
2658		use tauri::Emitter;
2659
2660		let req = request.into_inner();
2661		dev_log!("cocoon", "[CocoonService] Terminal closed: {}", req.terminal_id);
2662
2663		let _ = self
2664			.environment
2665			.ApplicationHandle
2666			.emit("sky://terminal/closed", json!({ "id": req.terminal_id }));
2667
2668		Ok(Response::new(Empty {}))
2669	}
2670
2671	/// Accept Terminal Process ID - Forward PID to Sky
2672	async fn accept_terminal_process_id(
2673		&self,
2674		request:Request<TerminalProcessIdNotification>,
2675	) -> Result<Response<Empty>, Status> {
2676		use tauri::Emitter;
2677
2678		let req = request.into_inner();
2679		dev_log!(
2680			"cocoon",
2681			"[CocoonService] Terminal PID: {} for terminal {}",
2682			req.process_id,
2683			req.terminal_id
2684		);
2685
2686		let _ = self.environment.ApplicationHandle.emit(
2687			"sky://terminal/processId",
2688			json!({ "id": req.terminal_id, "pid": req.process_id }),
2689		);
2690
2691		Ok(Response::new(Empty {}))
2692	}
2693
2694	/// Accept Terminal Process Data - Stream output to Sky via Tauri event
2695	async fn accept_terminal_process_data(
2696		&self,
2697		request:Request<TerminalDataNotification>,
2698	) -> Result<Response<Empty>, Status> {
2699		use tauri::Emitter;
2700
2701		let req = request.into_inner();
2702		dev_log!(
2703			"cocoon",
2704			"[CocoonService] Terminal data for {}: {} bytes",
2705			req.terminal_id,
2706			req.data.len()
2707		);
2708
2709		let DataString = String::from_utf8_lossy(&req.data).to_string();
2710		let _ = self
2711			.environment
2712			.ApplicationHandle
2713			.emit("sky://terminal/data", json!({ "id": req.terminal_id, "data": DataString }));
2714
2715		Ok(Response::new(Empty {}))
2716	}
2717
2718	// ==================== Tree View ====================
2719
2720	/// Register Tree View Provider - Register a tree view provider
2721	async fn register_tree_view_provider(
2722		&self,
2723		request:Request<RegisterTreeViewProviderRequest>,
2724	) -> Result<Response<Empty>, Status> {
2725		let req = request.into_inner();
2726		dev_log!("cocoon", "[CocoonService] Registering tree view provider: {}", req.view_id);
2727
2728		// Register tree view provider in ApplicationState and notify Sky
2729		use tauri::Emitter;
2730
2731		let Handle = req
2732			.view_id
2733			.as_bytes()
2734			.iter()
2735			.fold(0u32, |Acc, B| Acc.wrapping_mul(31).wrapping_add(*B as u32));
2736		let dto = ProviderRegistrationDTO {
2737			Handle,
2738			ProviderType:ProviderType::TreeView,
2739			Selector:json!([{ "viewId": req.view_id }]),
2740			SideCarIdentifier:"cocoon-main".to_string(),
2741			ExtensionIdentifier:json!(req.extension_id),
2742			Options:Some(json!({ "viewId": req.view_id })),
2743		};
2744		self.environment
2745			.ApplicationState
2746			.Extension
2747			.ProviderRegistration
2748			.RegisterProvider(Handle, dto);
2749
2750		let _ = self.environment.ApplicationHandle.emit(
2751			"sky://treeView/register",
2752			json!({ "viewId": req.view_id, "extensionId": req.extension_id }),
2753		);
2754
2755		Ok(Response::new(Empty {}))
2756	}
2757
2758	/// Get Tree Children - Request tree view children from registered provider
2759	async fn get_tree_children(
2760		&self,
2761		request:Request<GetTreeChildrenRequest>,
2762	) -> Result<Response<GetTreeChildrenResponse>, Status> {
2763		let req = request.into_inner();
2764		dev_log!("cocoon", "[CocoonService] get_tree_children: view={}", req.view_id);
2765
2766		// Tree children are fetched by forwarding to Cocoon via the generic RPC path.
2767		// The extension registers a TreeDataProvider; when Sky needs children,
2768		// Mountain looks up the provider handle and invokes Cocoon.
2769		// For now return empty — will be wired when Cocoon activation is complete.
2770		Ok(Response::new(GetTreeChildrenResponse { items:Vec::new() }))
2771	}
2772
2773	// ==================== SCM ====================
2774
2775	/// Register SCM Provider - Register source control provider
2776	async fn register_scm_provider(
2777		&self,
2778		request:Request<RegisterScmProviderRequest>,
2779	) -> Result<Response<Empty>, Status> {
2780		let req = request.into_inner();
2781		dev_log!("cocoon", "[CocoonService] Registering SCM provider: {}", req.scm_id);
2782
2783		// Register SCM provider in ApplicationState and notify Sky
2784		use tauri::Emitter;
2785
2786		let Handle = req
2787			.scm_id
2788			.as_bytes()
2789			.iter()
2790			.fold(0u32, |Acc, B| Acc.wrapping_mul(31).wrapping_add(*B as u32));
2791		let dto = ProviderRegistrationDTO {
2792			Handle,
2793			ProviderType:ProviderType::SourceControl,
2794			Selector:json!([{ "scmId": req.scm_id }]),
2795			SideCarIdentifier:"cocoon-main".to_string(),
2796			ExtensionIdentifier:json!(req.extension_id),
2797			Options:Some(json!({ "scmId": req.scm_id })),
2798		};
2799		self.environment
2800			.ApplicationState
2801			.Extension
2802			.ProviderRegistration
2803			.RegisterProvider(Handle, dto);
2804
2805		let _ = self.environment.ApplicationHandle.emit(
2806			"sky://scm/register",
2807			json!({ "scmId": req.scm_id, "extensionId": req.extension_id }),
2808		);
2809
2810		Ok(Response::new(Empty {}))
2811	}
2812
2813	/// Update SCM Group - Forward resource state changes to Sky
2814	async fn update_scm_group(&self, request:Request<UpdateScmGroupRequest>) -> Result<Response<Empty>, Status> {
2815		use tauri::Emitter;
2816
2817		let req = request.into_inner();
2818		dev_log!(
2819			"cocoon",
2820			"[CocoonService] update_scm_group: provider={} group={}",
2821			req.provider_id,
2822			req.group_id
2823		);
2824
2825		let ResourceStates:Vec<serde_json::Value> = req
2826			.resource_states
2827			.iter()
2828			.map(|Rs| {
2829				json!({
2830					"uri": Rs.uri.as_ref().map(|U| U.value.as_str()).unwrap_or(""),
2831					"decorations": Rs.decorations,
2832				})
2833			})
2834			.collect();
2835
2836		let _ = self.environment.ApplicationHandle.emit(
2837			"sky://scm/updateGroup",
2838			json!({
2839				"providerId": req.provider_id,
2840				"groupId": req.group_id,
2841				"resourceStates": ResourceStates,
2842			}),
2843		);
2844
2845		Ok(Response::new(Empty {}))
2846	}
2847
2848	/// Execute Git - Spawn native `git` process with provided arguments.
2849	///
2850	/// Runs git in the repository directory supplied by the extension host,
2851	/// captures stdout/stderr, and returns the raw bytes. Mirrors VS Code's
2852	/// `$gitExec` IPC call used by the built-in Git extension.
2853	async fn git_exec(&self, request:Request<GitExecRequest>) -> Result<Response<GitExecResponse>, Status> {
2854		let req = request.into_inner();
2855		dev_log!("cocoon", "[CocoonService] git_exec: {}", req.args.join(" "));
2856
2857		let WorkingDir = if req.repository_path.is_empty() {
2858			std::env::current_dir().unwrap_or_default()
2859		} else {
2860			std::path::PathBuf::from(&req.repository_path)
2861		};
2862
2863		let Output = tokio::process::Command::new("git")
2864			.args(&req.args)
2865			.current_dir(&WorkingDir)
2866			.output()
2867			.await
2868			.map_err(|Error| {
2869				dev_log!("cocoon", "error: [CocoonService] git_exec failed to spawn: {}", Error);
2870				Status::internal(format!("git_exec: failed to spawn git: {}", Error))
2871			})?;
2872
2873		let ExitCode = Output.status.code().unwrap_or(-1);
2874		dev_log!(
2875			"cocoon",
2876			"[CocoonService] git_exec exit={} stdout={} bytes stderr={} bytes",
2877			ExitCode,
2878			Output.stdout.len(),
2879			Output.stderr.len()
2880		);
2881
2882		// Combine stdout lines into repeated string output; prepend stderr lines
2883		// with "stderr: " prefix so extension can differentiate them.
2884		let StdoutStr = String::from_utf8_lossy(&Output.stdout);
2885		let StderrStr = String::from_utf8_lossy(&Output.stderr);
2886		let mut OutputLines:Vec<String> = StdoutStr.lines().map(|L| L.to_string()).collect();
2887		for Line in StderrStr.lines() {
2888			OutputLines.push(format!("stderr: {}", Line));
2889		}
2890
2891		Ok(Response::new(GitExecResponse { output:OutputLines, exit_code:ExitCode }))
2892	}
2893
2894	// ==================== Debug ====================
2895
2896	/// Register Debug Adapter - Register debug adapter
2897	async fn register_debug_adapter(
2898		&self,
2899		request:Request<RegisterDebugAdapterRequest>,
2900	) -> Result<Response<Empty>, Status> {
2901		let req = request.into_inner();
2902		dev_log!("cocoon", "[CocoonService] Registering debug adapter: {}", req.debug_type);
2903
2904		// Register debug adapter in ApplicationState and notify Sky
2905		use tauri::Emitter;
2906
2907		let Handle = req
2908			.debug_type
2909			.as_bytes()
2910			.iter()
2911			.fold(0u32, |Acc, B| Acc.wrapping_mul(31).wrapping_add(*B as u32));
2912		let dto = ProviderRegistrationDTO {
2913			Handle,
2914			ProviderType:ProviderType::DebugAdapter,
2915			Selector:json!([{ "debugType": req.debug_type }]),
2916			SideCarIdentifier:"cocoon-main".to_string(),
2917			ExtensionIdentifier:json!(req.extension_id),
2918			Options:Some(json!({ "debugType": req.debug_type })),
2919		};
2920		self.environment
2921			.ApplicationState
2922			.Extension
2923			.ProviderRegistration
2924			.RegisterProvider(Handle, dto);
2925
2926		let _ = self.environment.ApplicationHandle.emit(
2927			"sky://debug/register",
2928			json!({ "debugType": req.debug_type, "extensionId": req.extension_id }),
2929		);
2930
2931		Ok(Response::new(Empty {}))
2932	}
2933
2934	/// Start Debugging - Emit debug start event to Sky, return session ID
2935	async fn start_debugging(
2936		&self,
2937		request:Request<StartDebuggingRequest>,
2938	) -> Result<Response<StartDebuggingResponse>, Status> {
2939		use tauri::Emitter;
2940
2941		let req = request.into_inner();
2942		dev_log!("cocoon", "[CocoonService] start_debugging: type={}", req.debug_type);
2943
2944		let SessionId = format!(
2945			"debug-{}",
2946			SystemTime::now().duration_since(UNIX_EPOCH).map(|D| D.as_millis()).unwrap_or(0)
2947		);
2948
2949		let _ = self.environment.ApplicationHandle.emit(
2950			"sky://debug/start",
2951			json!({
2952				"sessionId": SessionId,
2953				"debugType": req.debug_type,
2954				"configuration": req.configuration.as_ref().map(|C| json!({
2955					"name": C.name,
2956					"type": C.r#type,
2957					"request": C.request,
2958				})),
2959			}),
2960		);
2961
2962		Ok(Response::new(StartDebuggingResponse { success:true }))
2963	}
2964
2965	// ==================== Save Participants ====================
2966
2967	/// Participate in Save - Extension participates in save
2968	async fn participate_in_save(
2969		&self,
2970		request:Request<ParticipateInSaveRequest>,
2971	) -> Result<Response<ParticipateInSaveResponse>, Status> {
2972		let req = request.into_inner();
2973		dev_log!("cocoon", "[CocoonService] Participating in save for: {:?}", req.uri);
2974
2975		// Save participants are extension-registered onWillSaveTextDocument handlers.
2976		// Cocoon invokes this when an extension wants to participate in a save.
2977		// The extension has already computed its edits — they arrive via gRPC from
2978		// the Cocoon extension host. For now, pass through with no edits since
2979		// extension activation is not yet complete.
2980		dev_log!("cocoon", "[CocoonService] Save reason: {:?}, uri: {:?}", req.reason, req.uri);
2981
2982		Ok(Response::new(ParticipateInSaveResponse { edits:Vec::new() }))
2983	}
2984
2985	// ==================== Secret Storage ====================
2986
2987	/// Get Secret - retrieve from the OS keychain via SecretProvider.
2988	async fn get_secret(&self, request:Request<GetSecretRequest>) -> Result<Response<GetSecretResponse>, Status> {
2989		let req = request.into_inner();
2990		dev_log!("cocoon", "[CocoonService] get_secret: key={}", req.key);
2991
2992		// The gRPC proto only carries `key`; we use the app name as the
2993		// extension identifier (keyring service scoping).
2994		match self.environment.GetSecret(String::new(), req.key.clone()).await {
2995			Ok(Some(Value)) => Ok(Response::new(GetSecretResponse { value:Value })),
2996			Ok(None) => Ok(Response::new(GetSecretResponse { value:String::new() })),
2997			Err(Error) => {
2998				dev_log!("cocoon", "warn: [CocoonService] get_secret failed key={}: {}", req.key, Error);
2999				Err(Status::internal(format!("get_secret: {}", Error)))
3000			},
3001		}
3002	}
3003
3004	/// Store Secret - persist to the OS keychain via SecretProvider.
3005	async fn store_secret(&self, request:Request<StoreSecretRequest>) -> Result<Response<Empty>, Status> {
3006		let req = request.into_inner();
3007		dev_log!("cocoon", "[CocoonService] store_secret: key={}", req.key);
3008
3009		match self.environment.StoreSecret(String::new(), req.key.clone(), req.value).await {
3010			Ok(()) => Ok(Response::new(Empty {})),
3011			Err(Error) => {
3012				dev_log!("cocoon", "warn: [CocoonService] store_secret failed key={}: {}", req.key, Error);
3013				Err(Status::internal(format!("store_secret: {}", Error)))
3014			},
3015		}
3016	}
3017
3018	/// Delete Secret - remove from the OS keychain via SecretProvider.
3019	async fn delete_secret(&self, request:Request<DeleteSecretRequest>) -> Result<Response<Empty>, Status> {
3020		let req = request.into_inner();
3021		dev_log!("cocoon", "[CocoonService] delete_secret: key={}", req.key);
3022
3023		match self.environment.DeleteSecret(String::new(), req.key.clone()).await {
3024			Ok(()) => Ok(Response::new(Empty {})),
3025			Err(Error) => {
3026				dev_log!(
3027					"cocoon",
3028					"warn: [CocoonService] delete_secret failed key={}: {}",
3029					req.key,
3030					Error
3031				);
3032				Err(Status::internal(format!("delete_secret: {}", Error)))
3033			},
3034		}
3035	}
3036
3037	// ==================== Extended Language Provider Handlers ====================
3038
3039	/// Document Highlight Provider - Register
3040	async fn register_document_highlight_provider(
3041		&self,
3042		request:Request<RegisterProviderRequest>,
3043	) -> Result<Response<Empty>, Status> {
3044		let req = request.into_inner();
3045		dev_log!("cocoon", "[CocoonService] Registering Document Highlight Provider");
3046		self.RegisterProvider(
3047			req.handle,
3048			ProviderType::DocumentHighlight,
3049			&req.language_selector,
3050			&req.extension_id,
3051		);
3052		Ok(Response::new(Empty {}))
3053	}
3054
3055	/// document highlights - delegates to LanguageFeatureProviderRegistry
3056	async fn provide_document_highlights(
3057		&self,
3058		request:Request<ProvideDocumentHighlightsRequest>,
3059	) -> Result<Response<ProvideDocumentHighlightsResponse>, Status> {
3060		let req = request.into_inner();
3061		dev_log!("cocoon", "[CocoonService] Providing document highlights");
3062
3063		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3064		let document_uri =
3065			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3066		let position = req.position.as_ref();
3067		let position_dto = PositionDTO {
3068			LineNumber:position.map(|p| p.line).unwrap_or(0),
3069			Column:position.map(|p| p.character).unwrap_or(0),
3070		};
3071
3072		match self.environment.ProvideDocumentHighlights(document_uri, position_dto).await {
3073			Ok(_result) => Ok(Response::new(ProvideDocumentHighlightsResponse::default())),
3074			Err(e) => Err(Status::internal(format!("Document highlights failed: {}", e))),
3075		}
3076	}
3077
3078	/// Document Symbol Provider - Register
3079	async fn register_document_symbol_provider(
3080		&self,
3081		request:Request<RegisterProviderRequest>,
3082	) -> Result<Response<Empty>, Status> {
3083		let req = request.into_inner();
3084		dev_log!("cocoon", "[CocoonService] Registering Document Symbol Provider");
3085		self.RegisterProvider(
3086			req.handle,
3087			ProviderType::DocumentSymbol,
3088			&req.language_selector,
3089			&req.extension_id,
3090		);
3091		Ok(Response::new(Empty {}))
3092	}
3093
3094	/// document symbols - delegates to LanguageFeatureProviderRegistry
3095	async fn provide_document_symbols(
3096		&self,
3097		request:Request<ProvideDocumentSymbolsRequest>,
3098	) -> Result<Response<ProvideDocumentSymbolsResponse>, Status> {
3099		let req = request.into_inner();
3100		dev_log!("cocoon", "[CocoonService] Providing document symbols");
3101
3102		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3103		let document_uri =
3104			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3105
3106		match self.environment.ProvideDocumentSymbols(document_uri).await {
3107			Ok(_result) => Ok(Response::new(ProvideDocumentSymbolsResponse::default())),
3108			Err(e) => Err(Status::internal(format!("Document symbols failed: {}", e))),
3109		}
3110	}
3111
3112	/// Workspace Symbol Provider - Register
3113	async fn register_workspace_symbol_provider(
3114		&self,
3115		request:Request<RegisterProviderRequest>,
3116	) -> Result<Response<Empty>, Status> {
3117		let req = request.into_inner();
3118		dev_log!("cocoon", "[CocoonService] Registering Workspace Symbol Provider");
3119		self.RegisterProvider(
3120			req.handle,
3121			ProviderType::WorkspaceSymbol,
3122			&req.language_selector,
3123			&req.extension_id,
3124		);
3125		Ok(Response::new(Empty {}))
3126	}
3127
3128	/// workspace symbols - delegates to LanguageFeatureProviderRegistry
3129	async fn provide_workspace_symbols(
3130		&self,
3131		request:Request<ProvideWorkspaceSymbolsRequest>,
3132	) -> Result<Response<ProvideWorkspaceSymbolsResponse>, Status> {
3133		let req = request.into_inner();
3134		dev_log!("cocoon", "[CocoonService] Providing workspace symbols for query: {}", req.query);
3135
3136		match self.environment.ProvideWorkspaceSymbols(req.query).await {
3137			Ok(_result) => Ok(Response::new(ProvideWorkspaceSymbolsResponse::default())),
3138			Err(e) => Err(Status::internal(format!("Workspace symbols failed: {}", e))),
3139		}
3140	}
3141
3142	/// Rename Provider - Register
3143	async fn register_rename_provider(
3144		&self,
3145		request:Request<RegisterProviderRequest>,
3146	) -> Result<Response<Empty>, Status> {
3147		let req = request.into_inner();
3148		dev_log!("cocoon", "[CocoonService] Registering Rename Provider");
3149		self.RegisterProvider(req.handle, ProviderType::Rename, &req.language_selector, &req.extension_id);
3150		Ok(Response::new(Empty {}))
3151	}
3152
3153	/// rename edits - delegates to LanguageFeatureProviderRegistry
3154	async fn provide_rename_edits(
3155		&self,
3156		request:Request<ProvideRenameEditsRequest>,
3157	) -> Result<Response<ProvideRenameEditsResponse>, Status> {
3158		let req = request.into_inner();
3159		dev_log!("cocoon", "[CocoonService] Providing rename edits: new_name={}", req.new_name);
3160
3161		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3162		let document_uri =
3163			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3164		let position = req.position.as_ref();
3165		let position_dto = PositionDTO {
3166			LineNumber:position.map(|p| p.line).unwrap_or(0),
3167			Column:position.map(|p| p.character).unwrap_or(0),
3168		};
3169
3170		match self
3171			.environment
3172			.ProvideRenameEdits(document_uri, position_dto, req.new_name)
3173			.await
3174		{
3175			Ok(_result) => Ok(Response::new(ProvideRenameEditsResponse::default())),
3176			Err(e) => Err(Status::internal(format!("Rename edits failed: {}", e))),
3177		}
3178	}
3179
3180	/// Document Formatting Provider - Register
3181	async fn register_document_formatting_provider(
3182		&self,
3183		request:Request<RegisterProviderRequest>,
3184	) -> Result<Response<Empty>, Status> {
3185		let req = request.into_inner();
3186		dev_log!("cocoon", "[CocoonService] Registering Document Formatting Provider");
3187		self.RegisterProvider(
3188			req.handle,
3189			ProviderType::DocumentFormatting,
3190			&req.language_selector,
3191			&req.extension_id,
3192		);
3193		Ok(Response::new(Empty {}))
3194	}
3195
3196	/// document formatting - delegates to LanguageFeatureProviderRegistry
3197	async fn provide_document_formatting(
3198		&self,
3199		request:Request<ProvideDocumentFormattingRequest>,
3200	) -> Result<Response<ProvideDocumentFormattingResponse>, Status> {
3201		let req = request.into_inner();
3202		dev_log!("cocoon", "[CocoonService] Providing document formatting");
3203
3204		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3205		let document_uri =
3206			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3207		let options_dto = json!({ "tabSize": 4, "insertSpaces": true });
3208
3209		match self.environment.ProvideDocumentFormattingEdits(document_uri, options_dto).await {
3210			Ok(_result) => Ok(Response::new(ProvideDocumentFormattingResponse::default())),
3211			Err(e) => Err(Status::internal(format!("Document formatting failed: {}", e))),
3212		}
3213	}
3214
3215	/// Document Range Formatting Provider - Register
3216	async fn register_document_range_formatting_provider(
3217		&self,
3218		request:Request<RegisterProviderRequest>,
3219	) -> Result<Response<Empty>, Status> {
3220		let req = request.into_inner();
3221		dev_log!("cocoon", "[CocoonService] Registering Document Range Formatting Provider");
3222		self.RegisterProvider(
3223			req.handle,
3224			ProviderType::DocumentRangeFormatting,
3225			&req.language_selector,
3226			&req.extension_id,
3227		);
3228		Ok(Response::new(Empty {}))
3229	}
3230
3231	/// document range formatting - delegates to LanguageFeatureProviderRegistry
3232	async fn provide_document_range_formatting(
3233		&self,
3234		request:Request<ProvideDocumentRangeFormattingRequest>,
3235	) -> Result<Response<ProvideDocumentRangeFormattingResponse>, Status> {
3236		let req = request.into_inner();
3237		dev_log!("cocoon", "[CocoonService] Providing document range formatting");
3238
3239		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3240		let document_uri =
3241			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3242		let range = req.range.as_ref();
3243		let range_dto = json!({
3244			"StartLineNumber": range.and_then(|r| r.start.as_ref()).map(|p| p.line).unwrap_or(0),
3245			"StartColumn": range.and_then(|r| r.start.as_ref()).map(|p| p.character).unwrap_or(0),
3246			"EndLineNumber": range.and_then(|r| r.end.as_ref()).map(|p| p.line).unwrap_or(0),
3247			"EndColumn": range.and_then(|r| r.end.as_ref()).map(|p| p.character).unwrap_or(0),
3248		});
3249		let options_dto = json!({ "tabSize": 4, "insertSpaces": true });
3250
3251		match self
3252			.environment
3253			.ProvideDocumentRangeFormattingEdits(document_uri, range_dto, options_dto)
3254			.await
3255		{
3256			Ok(_result) => Ok(Response::new(ProvideDocumentRangeFormattingResponse::default())),
3257			Err(e) => Err(Status::internal(format!("Document range formatting failed: {}", e))),
3258		}
3259	}
3260
3261	/// On Type Formatting Provider - Register
3262	async fn register_on_type_formatting_provider(
3263		&self,
3264		request:Request<RegisterOnTypeFormattingProviderRequest>,
3265	) -> Result<Response<Empty>, Status> {
3266		let req = request.into_inner();
3267		dev_log!("cocoon", "[CocoonService] Registering On Type Formatting Provider");
3268		self.RegisterProvider(
3269			req.handle,
3270			ProviderType::OnTypeFormatting,
3271			&req.language_selector,
3272			&req.extension_id,
3273		);
3274		Ok(Response::new(Empty {}))
3275	}
3276
3277	/// on-type formatting - delegates to LanguageFeatureProviderRegistry
3278	async fn provide_on_type_formatting(
3279		&self,
3280		request:Request<ProvideOnTypeFormattingRequest>,
3281	) -> Result<Response<ProvideOnTypeFormattingResponse>, Status> {
3282		let req = request.into_inner();
3283		dev_log!("cocoon", "[CocoonService] Providing on-type formatting");
3284
3285		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3286		let document_uri =
3287			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3288		let position = req.position.as_ref();
3289		let position_dto = PositionDTO {
3290			LineNumber:position.map(|p| p.line).unwrap_or(0),
3291			Column:position.map(|p| p.character).unwrap_or(0),
3292		};
3293		let options_dto = json!({ "tabSize": 4, "insertSpaces": true });
3294
3295		match self
3296			.environment
3297			.ProvideOnTypeFormattingEdits(document_uri, position_dto, req.character, options_dto)
3298			.await
3299		{
3300			Ok(_result) => Ok(Response::new(ProvideOnTypeFormattingResponse::default())),
3301			Err(e) => Err(Status::internal(format!("On-type formatting failed: {}", e))),
3302		}
3303	}
3304
3305	/// Signature Help Provider - Register
3306	async fn register_signature_help_provider(
3307		&self,
3308		request:Request<RegisterSignatureHelpProviderRequest>,
3309	) -> Result<Response<Empty>, Status> {
3310		let req = request.into_inner();
3311		dev_log!("cocoon", "[CocoonService] Registering Signature Help Provider");
3312		self.RegisterProvider(
3313			req.handle,
3314			ProviderType::SignatureHelp,
3315			&req.language_selector,
3316			&req.extension_id,
3317		);
3318		Ok(Response::new(Empty {}))
3319	}
3320
3321	/// signature help - delegates to LanguageFeatureProviderRegistry
3322	async fn provide_signature_help(
3323		&self,
3324		request:Request<ProvideSignatureHelpRequest>,
3325	) -> Result<Response<ProvideSignatureHelpResponse>, Status> {
3326		let req = request.into_inner();
3327		dev_log!("cocoon", "[CocoonService] Providing signature help");
3328
3329		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3330		let document_uri =
3331			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3332		let position = req.position.as_ref();
3333		let position_dto = PositionDTO {
3334			LineNumber:position.map(|p| p.line).unwrap_or(0),
3335			Column:position.map(|p| p.character).unwrap_or(0),
3336		};
3337		let context_dto = json!({ "triggerKind": 1, "isRetrigger": false });
3338
3339		match self
3340			.environment
3341			.ProvideSignatureHelp(document_uri, position_dto, context_dto)
3342			.await
3343		{
3344			Ok(_result) => Ok(Response::new(ProvideSignatureHelpResponse::default())),
3345			Err(e) => Err(Status::internal(format!("Signature help failed: {}", e))),
3346		}
3347	}
3348
3349	/// Code Lens Provider - Register
3350	async fn register_code_lens_provider(
3351		&self,
3352		request:Request<RegisterProviderRequest>,
3353	) -> Result<Response<Empty>, Status> {
3354		let req = request.into_inner();
3355		dev_log!("cocoon", "[CocoonService] Registering Code Lens Provider");
3356		self.RegisterProvider(req.handle, ProviderType::CodeLens, &req.language_selector, &req.extension_id);
3357		Ok(Response::new(Empty {}))
3358	}
3359
3360	/// code lenses - delegates to LanguageFeatureProviderRegistry
3361	async fn provide_code_lenses(
3362		&self,
3363		request:Request<ProvideCodeLensesRequest>,
3364	) -> Result<Response<ProvideCodeLensesResponse>, Status> {
3365		let req = request.into_inner();
3366		dev_log!("cocoon", "[CocoonService] Providing code lenses");
3367
3368		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3369		let document_uri =
3370			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3371
3372		match self.environment.ProvideCodeLenses(document_uri).await {
3373			Ok(_result) => Ok(Response::new(ProvideCodeLensesResponse::default())),
3374			Err(e) => Err(Status::internal(format!("Code lenses failed: {}", e))),
3375		}
3376	}
3377
3378	/// Folding Range Provider - Register
3379	async fn register_folding_range_provider(
3380		&self,
3381		request:Request<RegisterProviderRequest>,
3382	) -> Result<Response<Empty>, Status> {
3383		let req = request.into_inner();
3384		dev_log!("cocoon", "[CocoonService] Registering Folding Range Provider");
3385		self.RegisterProvider(
3386			req.handle,
3387			ProviderType::FoldingRange,
3388			&req.language_selector,
3389			&req.extension_id,
3390		);
3391		Ok(Response::new(Empty {}))
3392	}
3393
3394	/// folding ranges - delegates to LanguageFeatureProviderRegistry
3395	async fn provide_folding_ranges(
3396		&self,
3397		request:Request<ProvideFoldingRangesRequest>,
3398	) -> Result<Response<ProvideFoldingRangesResponse>, Status> {
3399		let req = request.into_inner();
3400		dev_log!("cocoon", "[CocoonService] Providing folding ranges");
3401
3402		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3403		let document_uri =
3404			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3405
3406		match self.environment.ProvideFoldingRanges(document_uri).await {
3407			Ok(_result) => Ok(Response::new(ProvideFoldingRangesResponse::default())),
3408			Err(e) => Err(Status::internal(format!("Folding ranges failed: {}", e))),
3409		}
3410	}
3411
3412	/// Selection Range Provider - Register
3413	async fn register_selection_range_provider(
3414		&self,
3415		request:Request<RegisterProviderRequest>,
3416	) -> Result<Response<Empty>, Status> {
3417		let req = request.into_inner();
3418		dev_log!("cocoon", "[CocoonService] Registering Selection Range Provider");
3419		self.RegisterProvider(
3420			req.handle,
3421			ProviderType::SelectionRange,
3422			&req.language_selector,
3423			&req.extension_id,
3424		);
3425		Ok(Response::new(Empty {}))
3426	}
3427
3428	/// selection ranges - delegates to LanguageFeatureProviderRegistry
3429	async fn provide_selection_ranges(
3430		&self,
3431		request:Request<ProvideSelectionRangesRequest>,
3432	) -> Result<Response<ProvideSelectionRangesResponse>, Status> {
3433		let req = request.into_inner();
3434		dev_log!("cocoon", "[CocoonService] Providing selection ranges");
3435
3436		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3437		let document_uri =
3438			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3439		let PositionDTOs:Vec<PositionDTO> = req
3440			.positions
3441			.iter()
3442			.map(|P| PositionDTO { LineNumber:P.line, Column:P.character })
3443			.collect();
3444
3445		match self.environment.ProvideSelectionRanges(document_uri, PositionDTOs).await {
3446			Ok(_result) => Ok(Response::new(ProvideSelectionRangesResponse::default())),
3447			Err(e) => Err(Status::internal(format!("Selection ranges failed: {}", e))),
3448		}
3449	}
3450
3451	/// Semantic Tokens Provider - Register
3452	async fn register_semantic_tokens_provider(
3453		&self,
3454		request:Request<RegisterSemanticTokensProviderRequest>,
3455	) -> Result<Response<Empty>, Status> {
3456		let req = request.into_inner();
3457		dev_log!("cocoon", "[CocoonService] Registering Semantic Tokens Provider");
3458		self.RegisterProvider(
3459			req.handle,
3460			ProviderType::SemanticTokens,
3461			&req.language_selector,
3462			&req.extension_id,
3463		);
3464		Ok(Response::new(Empty {}))
3465	}
3466
3467	/// semantic tokens - delegates to LanguageFeatureProviderRegistry
3468	async fn provide_semantic_tokens_full(
3469		&self,
3470		request:Request<ProvideSemanticTokensRequest>,
3471	) -> Result<Response<ProvideSemanticTokensResponse>, Status> {
3472		let req = request.into_inner();
3473		dev_log!("cocoon", "[CocoonService] Providing semantic tokens");
3474
3475		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3476		let document_uri =
3477			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3478
3479		match self.environment.ProvideSemanticTokensFull(document_uri).await {
3480			Ok(_result) => Ok(Response::new(ProvideSemanticTokensResponse::default())),
3481			Err(e) => Err(Status::internal(format!("Semantic tokens failed: {}", e))),
3482		}
3483	}
3484
3485	/// Inlay Hints Provider - Register
3486	async fn register_inlay_hints_provider(
3487		&self,
3488		request:Request<RegisterProviderRequest>,
3489	) -> Result<Response<Empty>, Status> {
3490		let req = request.into_inner();
3491		dev_log!("cocoon", "[CocoonService] Registering Inlay Hints Provider");
3492		self.RegisterProvider(req.handle, ProviderType::InlayHint, &req.language_selector, &req.extension_id);
3493		Ok(Response::new(Empty {}))
3494	}
3495
3496	/// inlay hints - delegates to LanguageFeatureProviderRegistry
3497	async fn provide_inlay_hints(
3498		&self,
3499		request:Request<ProvideInlayHintsRequest>,
3500	) -> Result<Response<ProvideInlayHintsResponse>, Status> {
3501		let req = request.into_inner();
3502		dev_log!("cocoon", "[CocoonService] Providing inlay hints");
3503
3504		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3505		let document_uri =
3506			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3507		let range = req.range.as_ref();
3508		let range_dto = json!({
3509			"StartLineNumber": range.and_then(|r| r.start.as_ref()).map(|p| p.line).unwrap_or(0),
3510			"StartColumn": range.and_then(|r| r.start.as_ref()).map(|p| p.character).unwrap_or(0),
3511			"EndLineNumber": range.and_then(|r| r.end.as_ref()).map(|p| p.line).unwrap_or(0),
3512			"EndColumn": range.and_then(|r| r.end.as_ref()).map(|p| p.character).unwrap_or(0),
3513		});
3514
3515		match self.environment.ProvideInlayHints(document_uri, range_dto).await {
3516			Ok(_result) => Ok(Response::new(ProvideInlayHintsResponse::default())),
3517			Err(e) => Err(Status::internal(format!("Inlay hints failed: {}", e))),
3518		}
3519	}
3520
3521	/// Type Hierarchy Provider - Register
3522	async fn register_type_hierarchy_provider(
3523		&self,
3524		request:Request<RegisterProviderRequest>,
3525	) -> Result<Response<Empty>, Status> {
3526		let req = request.into_inner();
3527		dev_log!("cocoon", "[CocoonService] Registering Type Hierarchy Provider");
3528		self.RegisterProvider(
3529			req.handle,
3530			ProviderType::TypeHierarchy,
3531			&req.language_selector,
3532			&req.extension_id,
3533		);
3534		Ok(Response::new(Empty {}))
3535	}
3536
3537	/// type hierarchy supertypes - delegates to LanguageFeatureProviderRegistry
3538	async fn provide_type_hierarchy_supertypes(
3539		&self,
3540		request:Request<ProvideTypeHierarchyRequest>,
3541	) -> Result<Response<ProvideTypeHierarchyResponse>, Status> {
3542		let req = request.into_inner();
3543		dev_log!("cocoon", "[CocoonService] Providing type hierarchy supertypes");
3544
3545		let item_dto = json!({
3546			"name": req.item.as_ref().map(|i| i.name.as_str()).unwrap_or(""),
3547			"uri": req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or(""),
3548		});
3549		match self.environment.ProvideTypeHierarchySupertypes(item_dto).await {
3550			Ok(_result) => Ok(Response::new(ProvideTypeHierarchyResponse::default())),
3551			Err(e) => Err(Status::internal(format!("Type hierarchy supertypes failed: {}", e))),
3552		}
3553	}
3554
3555	/// type hierarchy subtypes - delegates to LanguageFeatureProviderRegistry
3556	async fn provide_type_hierarchy_subtypes(
3557		&self,
3558		request:Request<ProvideTypeHierarchyRequest>,
3559	) -> Result<Response<ProvideTypeHierarchyResponse>, Status> {
3560		let req = request.into_inner();
3561		dev_log!("cocoon", "[CocoonService] Providing type hierarchy subtypes");
3562
3563		let item_dto = json!({
3564			"name": req.item.as_ref().map(|i| i.name.as_str()).unwrap_or(""),
3565			"uri": req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or(""),
3566		});
3567		match self.environment.ProvideTypeHierarchySubtypes(item_dto).await {
3568			Ok(_result) => Ok(Response::new(ProvideTypeHierarchyResponse::default())),
3569			Err(e) => Err(Status::internal(format!("Type hierarchy subtypes failed: {}", e))),
3570		}
3571	}
3572
3573	/// Call Hierarchy Provider - Register
3574	async fn register_call_hierarchy_provider(
3575		&self,
3576		request:Request<RegisterProviderRequest>,
3577	) -> Result<Response<Empty>, Status> {
3578		let req = request.into_inner();
3579		dev_log!("cocoon", "[CocoonService] Registering Call Hierarchy Provider");
3580		self.RegisterProvider(
3581			req.handle,
3582			ProviderType::CallHierarchy,
3583			&req.language_selector,
3584			&req.extension_id,
3585		);
3586		Ok(Response::new(Empty {}))
3587	}
3588
3589	/// call hierarchy incoming - delegates to LanguageFeatureProviderRegistry
3590	async fn provide_call_hierarchy_incoming_calls(
3591		&self,
3592		request:Request<ProvideCallHierarchyRequest>,
3593	) -> Result<Response<ProvideCallHierarchyResponse>, Status> {
3594		let req = request.into_inner();
3595		dev_log!("cocoon", "[CocoonService] Providing call hierarchy incoming");
3596
3597		let item_dto = json!({
3598			"name": req.item.as_ref().map(|i| i.name.as_str()).unwrap_or(""),
3599			"uri": req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or(""),
3600		});
3601		match self.environment.ProvideCallHierarchyIncomingCalls(item_dto).await {
3602			Ok(_result) => Ok(Response::new(ProvideCallHierarchyResponse::default())),
3603			Err(e) => Err(Status::internal(format!("Call hierarchy incoming failed: {}", e))),
3604		}
3605	}
3606
3607	/// call hierarchy outgoing - delegates to LanguageFeatureProviderRegistry
3608	async fn provide_call_hierarchy_outgoing_calls(
3609		&self,
3610		request:Request<ProvideCallHierarchyRequest>,
3611	) -> Result<Response<ProvideCallHierarchyResponse>, Status> {
3612		let req = request.into_inner();
3613		dev_log!("cocoon", "[CocoonService] Providing call hierarchy outgoing");
3614
3615		let item_dto = json!({
3616			"name": req.item.as_ref().map(|i| i.name.as_str()).unwrap_or(""),
3617			"uri": req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or(""),
3618		});
3619		match self.environment.ProvideCallHierarchyOutgoingCalls(item_dto).await {
3620			Ok(_result) => Ok(Response::new(ProvideCallHierarchyResponse::default())),
3621			Err(e) => Err(Status::internal(format!("Call hierarchy outgoing failed: {}", e))),
3622		}
3623	}
3624
3625	/// Linked Editing Range Provider - Register
3626	async fn register_linked_editing_range_provider(
3627		&self,
3628		request:Request<RegisterProviderRequest>,
3629	) -> Result<Response<Empty>, Status> {
3630		let req = request.into_inner();
3631		dev_log!("cocoon", "[CocoonService] Registering Linked Editing Range Provider");
3632		self.RegisterProvider(
3633			req.handle,
3634			ProviderType::LinkedEditingRange,
3635			&req.language_selector,
3636			&req.extension_id,
3637		);
3638		Ok(Response::new(Empty {}))
3639	}
3640
3641	/// linked editing ranges - delegates to LanguageFeatureProviderRegistry
3642	async fn provide_linked_editing_ranges(
3643		&self,
3644		request:Request<ProvideLinkedEditingRangesRequest>,
3645	) -> Result<Response<ProvideLinkedEditingRangesResponse>, Status> {
3646		let req = request.into_inner();
3647		dev_log!("cocoon", "[CocoonService] Providing linked editing ranges");
3648
3649		let uri_string = req.uri.as_ref().map(|u| u.value.as_str()).unwrap_or("");
3650		let document_uri =
3651			Url::parse(uri_string).map_err(|e| Status::invalid_argument(format!("Invalid URI: {}", e)))?;
3652		let position = req.position.as_ref();
3653		let position_dto = PositionDTO {
3654			LineNumber:position.map(|p| p.line).unwrap_or(0),
3655			Column:position.map(|p| p.character).unwrap_or(0),
3656		};
3657
3658		match self.environment.ProvideLinkedEditingRanges(document_uri, position_dto).await {
3659			Ok(_result) => Ok(Response::new(ProvideLinkedEditingRangesResponse::default())),
3660			Err(e) => Err(Status::internal(format!("Linked editing ranges failed: {}", e))),
3661		}
3662	}
3663
3664	/// Show Quick Pick - present a selection list via UserInterfaceProvider.
3665	async fn show_quick_pick(
3666		&self,
3667		request:Request<ShowQuickPickRequest>,
3668	) -> Result<Response<ShowQuickPickResponse>, Status> {
3669		let req = request.into_inner();
3670		dev_log!("cocoon", "[CocoonService] show_quick_pick: {} items", req.items.len());
3671
3672		let Items:Vec<QuickPickItemDTO> = req
3673			.items
3674			.iter()
3675			.map(|Item| {
3676				QuickPickItemDTO {
3677					Label:Item.label.clone(),
3678					Description:if Item.description.is_empty() { None } else { Some(Item.description.clone()) },
3679					Detail:None,
3680					Picked:Some(Item.picked),
3681					AlwaysShow:None,
3682				}
3683			})
3684			.collect();
3685
3686		let Options = Some(QuickPickOptionsDTO {
3687			Title:if req.title.is_empty() { None } else { Some(req.title) },
3688			PlaceHolder:if req.placeholder.is_empty() { None } else { Some(req.placeholder) },
3689			CanPickMany:Some(req.can_pick_many),
3690			IgnoreFocusOut:None,
3691		});
3692
3693		match self.environment.ShowQuickPick(Items, Options).await {
3694			Ok(Some(Selected)) => {
3695				// Map selected label strings back to indices via linear search
3696				let SelectedIndices:Vec<u32> = Selected
3697					.iter()
3698					.filter_map(|Label| req.items.iter().position(|Item| &Item.label == Label).map(|Idx| Idx as u32))
3699					.collect();
3700				Ok(Response::new(ShowQuickPickResponse { selected_indices:SelectedIndices }))
3701			},
3702			Ok(None) => Ok(Response::new(ShowQuickPickResponse::default())),
3703			Err(Error) => {
3704				dev_log!("cocoon", "warn: [CocoonService] show_quick_pick failed: {}", Error);
3705				Ok(Response::new(ShowQuickPickResponse::default()))
3706			},
3707		}
3708	}
3709
3710	/// Show Input Box - present a text entry dialog via UserInterfaceProvider.
3711	async fn show_input_box(
3712		&self,
3713		request:Request<ShowInputBoxRequest>,
3714	) -> Result<Response<ShowInputBoxResponse>, Status> {
3715		let req = request.into_inner();
3716		dev_log!("cocoon", "[CocoonService] show_input_box");
3717
3718		let Options = Some(InputBoxOptionsDTO {
3719			Title:if req.title.is_empty() { None } else { Some(req.title) },
3720			PlaceHolder:if req.placeholder.is_empty() { None } else { Some(req.placeholder) },
3721			Value:if req.value.is_empty() { None } else { Some(req.value) },
3722			Prompt:if req.prompt.is_empty() { None } else { Some(req.prompt) },
3723			IsPassword:if req.password { Some(true) } else { None },
3724			IgnoreFocusOut:None,
3725		});
3726
3727		match self.environment.ShowInputBox(Options).await {
3728			Ok(Some(Value)) => Ok(Response::new(ShowInputBoxResponse { value:Value, cancelled:false })),
3729			Ok(None) => Ok(Response::new(ShowInputBoxResponse { value:String::new(), cancelled:true })),
3730			Err(Error) => {
3731				dev_log!("cocoon", "warn: [CocoonService] show_input_box failed: {}", Error);
3732				Ok(Response::new(ShowInputBoxResponse { value:String::new(), cancelled:true }))
3733			},
3734		}
3735	}
3736
3737	/// progress - emit start event to Sky for progress indicator
3738	async fn show_progress(
3739		&self,
3740		request:Request<ShowProgressRequest>,
3741	) -> Result<Response<ShowProgressResponse>, Status> {
3742		use tauri::Emitter;
3743
3744		let req = request.into_inner();
3745		dev_log!("cocoon", "[CocoonService] show_progress: title={}", req.title);
3746
3747		let Handle = SystemTime::now()
3748			.duration_since(UNIX_EPOCH)
3749			.map(|D| D.as_millis() as u32)
3750			.unwrap_or(0);
3751
3752		let _ = self.environment.ApplicationHandle.emit(
3753			"sky://progress/start",
3754			json!({
3755				"handle": Handle,
3756				"title": req.title,
3757				"cancellable": req.cancellable,
3758				"location": req.location,
3759			}),
3760		);
3761
3762		Ok(Response::new(ShowProgressResponse { handle:Handle }))
3763	}
3764
3765	/// progress report - emit update event to Sky
3766	async fn report_progress(&self, request:Request<ReportProgressRequest>) -> Result<Response<Empty>, Status> {
3767		use tauri::Emitter;
3768
3769		let req = request.into_inner();
3770		dev_log!("cocoon", "[CocoonService] report_progress: handle={}", req.handle);
3771
3772		let _ = self.environment.ApplicationHandle.emit(
3773			"sky://progress/update",
3774			json!({
3775				"handle": req.handle,
3776				"message": req.message,
3777				"increment": req.increment,
3778			}),
3779		);
3780
3781		Ok(Response::new(Empty {}))
3782	}
3783
3784	/// webview message - forward to Sky for webview panel message delivery
3785	async fn post_webview_message(
3786		&self,
3787		request:Request<PostWebviewMessageRequest>,
3788	) -> Result<Response<Empty>, Status> {
3789		use tauri::Emitter;
3790
3791		let req = request.into_inner();
3792		dev_log!("cocoon", "[CocoonService] post_webview_message: handle={}", req.handle);
3793
3794		let MessagePayload = match &req.message {
3795			Some(post_webview_message_request::Message::StringMessage(S)) => json!(S),
3796			Some(post_webview_message_request::Message::BytesMessage(B)) => json!(B),
3797			None => serde_json::Value::Null,
3798		};
3799
3800		let _ = self.environment.ApplicationHandle.emit(
3801			"sky://webview/postMessage",
3802			json!({
3803				"handle": req.handle,
3804				"message": MessagePayload,
3805			}),
3806		);
3807
3808		Ok(Response::new(Empty {}))
3809	}
3810
3811	/// webview dispose - emit dispose event to Sky
3812	async fn dispose_webview_panel(
3813		&self,
3814		request:Request<DisposeWebviewPanelRequest>,
3815	) -> Result<Response<Empty>, Status> {
3816		use tauri::Emitter;
3817
3818		let req = request.into_inner();
3819		dev_log!("cocoon", "[CocoonService] dispose_webview_panel: handle={}", req.handle);
3820
3821		let _ = self
3822			.environment
3823			.ApplicationHandle
3824			.emit("sky://webview/dispose", json!({ "handle": req.handle }));
3825
3826		Ok(Response::new(Empty {}))
3827	}
3828
3829	/// external URI - open URL via Tauri shell plugin
3830	async fn open_external(&self, request:Request<OpenExternalRequest>) -> Result<Response<Empty>, Status> {
3831		use tauri::Emitter;
3832
3833		let req = request.into_inner();
3834		dev_log!("cocoon", "[CocoonService] open_external: {}", req.uri);
3835
3836		let _ = self
3837			.environment
3838			.ApplicationHandle
3839			.emit("sky://native/openExternal", json!({ "url": req.uri }));
3840
3841		Ok(Response::new(Empty {}))
3842	}
3843
3844	/// file delete
3845	async fn delete_file(&self, request:Request<DeleteFileRequest>) -> Result<Response<Empty>, Status> {
3846		let req = request.into_inner();
3847		let Path =
3848			Self::UriToPath(req.uri.as_ref()).ok_or_else(|| Status::invalid_argument("delete_file: missing URI"))?;
3849
3850		dev_log!("cocoon", "[CocoonService] delete_file: {:?}", Path);
3851
3852		if Path.is_dir() {
3853			tokio::fs::remove_dir_all(&Path).await
3854		} else {
3855			tokio::fs::remove_file(&Path).await
3856		}
3857		.map_err(|Error| Status::internal(format!("delete_file: {}: {}", Path.display(), Error)))?;
3858
3859		Ok(Response::new(Empty {}))
3860	}
3861
3862	/// file rename (move)
3863	async fn rename_file(&self, request:Request<RenameFileRequest>) -> Result<Response<Empty>, Status> {
3864		let req = request.into_inner();
3865		let OldPath = Self::UriToPath(req.source.as_ref())
3866			.ok_or_else(|| Status::invalid_argument("rename_file: missing source URI"))?;
3867		let NewPath = Self::UriToPath(req.target.as_ref())
3868			.ok_or_else(|| Status::invalid_argument("rename_file: missing target URI"))?;
3869
3870		dev_log!("cocoon", "[CocoonService] rename_file: {:?} → {:?}", OldPath, NewPath);
3871
3872		if let Some(Parent) = NewPath.parent() {
3873			if !Parent.as_os_str().is_empty() {
3874				tokio::fs::create_dir_all(Parent)
3875					.await
3876					.map_err(|Error| Status::internal(format!("rename_file: create_dir_all failed: {}", Error)))?;
3877			}
3878		}
3879
3880		tokio::fs::rename(&OldPath, &NewPath)
3881			.await
3882			.map_err(|Error| Status::internal(format!("rename_file: {}: {}", OldPath.display(), Error)))?;
3883
3884		Ok(Response::new(Empty {}))
3885	}
3886
3887	/// file copy
3888	async fn copy_file(&self, request:Request<CopyFileRequest>) -> Result<Response<Empty>, Status> {
3889		let req = request.into_inner();
3890		let SrcPath = Self::UriToPath(req.source.as_ref())
3891			.ok_or_else(|| Status::invalid_argument("copy_file: missing source URI"))?;
3892		let DstPath = Self::UriToPath(req.target.as_ref())
3893			.ok_or_else(|| Status::invalid_argument("copy_file: missing target URI"))?;
3894
3895		dev_log!("cocoon", "[CocoonService] copy_file: {:?} → {:?}", SrcPath, DstPath);
3896
3897		if let Some(Parent) = DstPath.parent() {
3898			if !Parent.as_os_str().is_empty() {
3899				tokio::fs::create_dir_all(Parent)
3900					.await
3901					.map_err(|Error| Status::internal(format!("copy_file: create_dir_all failed: {}", Error)))?;
3902			}
3903		}
3904
3905		tokio::fs::copy(&SrcPath, &DstPath)
3906			.await
3907			.map_err(|Error| Status::internal(format!("copy_file: {}: {}", SrcPath.display(), Error)))?;
3908
3909		Ok(Response::new(Empty {}))
3910	}
3911
3912	/// directory creation
3913	async fn create_directory(&self, request:Request<CreateDirectoryRequest>) -> Result<Response<Empty>, Status> {
3914		let req = request.into_inner();
3915		let Path = Self::UriToPath(req.uri.as_ref())
3916			.ok_or_else(|| Status::invalid_argument("create_directory: missing URI"))?;
3917
3918		dev_log!("cocoon", "[CocoonService] create_directory: {:?}", Path);
3919
3920		tokio::fs::create_dir_all(&Path)
3921			.await
3922			.map_err(|Error| Status::internal(format!("create_directory: {}: {}", Path.display(), Error)))?;
3923
3924		Ok(Response::new(Empty {}))
3925	}
3926
3927	/// Create output channel - notify Sky to create a named output panel.
3928	async fn create_output_channel(
3929		&self,
3930		request:Request<CreateOutputChannelRequest>,
3931	) -> Result<Response<CreateOutputChannelResponse>, Status> {
3932		use tauri::Emitter;
3933
3934		let req = request.into_inner();
3935		dev_log!("cocoon", "[CocoonService] create_output_channel: '{}'", req.name);
3936
3937		let _ = self
3938			.environment
3939			.ApplicationHandle
3940			.emit("sky://output/create", json!({ "channel": req.name }));
3941
3942		Ok(Response::new(CreateOutputChannelResponse { channel_id:req.name.clone() }))
3943	}
3944
3945	/// Append text to an output channel panel.
3946	async fn append_output(&self, request:Request<AppendOutputRequest>) -> Result<Response<Empty>, Status> {
3947		use tauri::Emitter;
3948
3949		let req = request.into_inner();
3950		let _ = self
3951			.environment
3952			.ApplicationHandle
3953			.emit("sky://output/append", json!({ "channel": req.channel_id, "text": req.value }));
3954		Ok(Response::new(Empty {}))
3955	}
3956
3957	/// Clear an output channel panel.
3958	async fn clear_output(&self, request:Request<ClearOutputRequest>) -> Result<Response<Empty>, Status> {
3959		use tauri::Emitter;
3960
3961		let req = request.into_inner();
3962		let _ = self
3963			.environment
3964			.ApplicationHandle
3965			.emit("sky://output/clear", json!({ "channel": req.channel_id }));
3966		Ok(Response::new(Empty {}))
3967	}
3968
3969	/// Show an output channel panel.
3970	async fn show_output(&self, request:Request<ShowOutputRequest>) -> Result<Response<Empty>, Status> {
3971		use tauri::Emitter;
3972
3973		let req = request.into_inner();
3974		let _ = self
3975			.environment
3976			.ApplicationHandle
3977			.emit("sky://output/show", json!({ "channel": req.channel_id }));
3978		Ok(Response::new(Empty {}))
3979	}
3980
3981	/// Dispose an output channel (no cleanup needed; Sky removes the panel on
3982	/// demand).
3983	async fn dispose_output(&self, request:Request<DisposeOutputRequest>) -> Result<Response<Empty>, Status> {
3984		use tauri::Emitter;
3985
3986		let req = request.into_inner();
3987		let _ = self
3988			.environment
3989			.ApplicationHandle
3990			.emit("sky://output/dispose", json!({ "channel": req.channel_id }));
3991		Ok(Response::new(Empty {}))
3992	}
3993
3994	/// Task Provider - Register in ApplicationState for provider lookup
3995	async fn register_task_provider(
3996		&self,
3997		request:Request<RegisterTaskProviderRequest>,
3998	) -> Result<Response<Empty>, Status> {
3999		let req = request.into_inner();
4000		dev_log!("cocoon", "[CocoonService] Registering Task Provider: type={}", req.r#type);
4001
4002		// Task providers don't have handles in proto — use a hash of the type string
4003		let Handle = req
4004			.r#type
4005			.as_bytes()
4006			.iter()
4007			.fold(0u32, |Acc, B| Acc.wrapping_mul(31).wrapping_add(*B as u32));
4008		let dto = ProviderRegistrationDTO {
4009			Handle,
4010			ProviderType:ProviderType::Task,
4011			Selector:json!([{ "language": "*" }]),
4012			SideCarIdentifier:"cocoon-main".to_string(),
4013			ExtensionIdentifier:json!(req.extension_id),
4014			Options:None,
4015		};
4016		self.environment
4017			.ApplicationState
4018			.Extension
4019			.ProviderRegistration
4020			.RegisterProvider(Handle, dto);
4021
4022		Ok(Response::new(Empty {}))
4023	}
4024
4025	/// task execution - forward to Sky via Tauri event
4026	async fn execute_task(&self, request:Request<ExecuteTaskRequest>) -> Result<Response<ExecuteTaskResponse>, Status> {
4027		use tauri::Emitter;
4028
4029		let req = request.into_inner();
4030		dev_log!(
4031			"cocoon",
4032			"[CocoonService] execute_task: name={} source={}",
4033			req.name,
4034			req.source
4035		);
4036
4037		let _ = self
4038			.environment
4039			.ApplicationHandle
4040			.emit("sky://task/execute", json!({ "name": req.name, "source": req.source }));
4041
4042		Ok(Response::new(ExecuteTaskResponse { task_id:0, success:true }))
4043	}
4044
4045	/// task termination - signal the running task to stop
4046	async fn terminate_task(&self, request:Request<TerminateTaskRequest>) -> Result<Response<Empty>, Status> {
4047		use tauri::Emitter;
4048
4049		let req = request.into_inner();
4050		dev_log!("cocoon", "[CocoonService] terminate_task: id={}", req.task_id);
4051
4052		let _ = self
4053			.environment
4054			.ApplicationHandle
4055			.emit("sky://task/terminate", json!({ "id": req.task_id }));
4056
4057		Ok(Response::new(Empty {}))
4058	}
4059
4060	/// authentication session - retrieve or create an auth session
4061	async fn get_authentication_session(
4062		&self,
4063		request:Request<GetAuthenticationSessionRequest>,
4064	) -> Result<Response<GetAuthenticationSessionResponse>, Status> {
4065		let req = request.into_inner();
4066		dev_log!(
4067			"cocoon",
4068			"[CocoonService] get_authentication_session: provider={}",
4069			req.provider_id
4070		);
4071
4072		// Return empty session — auth providers register themselves via
4073		// register_authentication_provider and get stored in ApplicationState.
4074		// The full OAuth flow requires Mountain to open a browser window.
4075		Ok(Response::new(GetAuthenticationSessionResponse::default()))
4076	}
4077
4078	/// Authentication Provider - Register in ApplicationState
4079	async fn register_authentication_provider(
4080		&self,
4081		request:Request<RegisterAuthenticationProviderRequest>,
4082	) -> Result<Response<Empty>, Status> {
4083		let req = request.into_inner();
4084		dev_log!("cocoon", "[CocoonService] Registering Authentication Provider: id={}", req.id);
4085
4086		let Handle = req
4087			.id
4088			.as_bytes()
4089			.iter()
4090			.fold(0u32, |Acc, B| Acc.wrapping_mul(31).wrapping_add(*B as u32));
4091		let dto = ProviderRegistrationDTO {
4092			Handle,
4093			ProviderType:ProviderType::Authentication,
4094			Selector:json!([{ "provider": req.id }]),
4095			SideCarIdentifier:"cocoon-main".to_string(),
4096			ExtensionIdentifier:json!(req.extension_id),
4097			Options:Some(json!({ "label": req.label, "supportsMultipleAccounts": req.supports_multiple_accounts })),
4098		};
4099		self.environment
4100			.ApplicationState
4101			.Extension
4102			.ProviderRegistration
4103			.RegisterProvider(Handle, dto);
4104
4105		Ok(Response::new(Empty {}))
4106	}
4107
4108	/// debug stop - signal the debug adapter to terminate
4109	async fn stop_debugging(&self, request:Request<StopDebuggingRequest>) -> Result<Response<Empty>, Status> {
4110		use tauri::Emitter;
4111
4112		let req = request.into_inner();
4113		dev_log!("cocoon", "[CocoonService] stop_debugging: session={}", req.session_id);
4114
4115		let _ = self
4116			.environment
4117			.ApplicationHandle
4118			.emit("sky://debug/stop", json!({ "sessionId": req.session_id }));
4119
4120		Ok(Response::new(Empty {}))
4121	}
4122
4123	/// extension info - look up a single extension by ID in ApplicationState
4124	async fn get_extension(
4125		&self,
4126		request:Request<GetExtensionRequest>,
4127	) -> Result<Response<GetExtensionResponse>, Status> {
4128		use CommonLibrary::ExtensionManagement::ExtensionManagementService::ExtensionManagementService;
4129
4130		let req = request.into_inner();
4131		dev_log!("cocoon", "[CocoonService] get_extension: {}", req.extension_id);
4132
4133		let ExtensionOption = self.environment.GetExtension(req.extension_id.clone()).await.ok().flatten();
4134
4135		let InfoOption = ExtensionOption.map(|Value| {
4136			ExtensionInfo {
4137				id:req.extension_id,
4138				display_name:Value.get("Name").and_then(|V| V.as_str()).unwrap_or("").to_string(),
4139				version:Value.get("Version").and_then(|V| V.as_str()).unwrap_or("").to_string(),
4140				is_active:true, // scanned = considered active for now
4141				extension_path:Value
4142					.get("ExtensionLocation")
4143					.and_then(|V| V.as_str())
4144					.unwrap_or("")
4145					.to_string(),
4146			}
4147		});
4148
4149		Ok(Response::new(GetExtensionResponse { extension:InfoOption }))
4150	}
4151
4152	/// all extensions - return all scanned extensions from ApplicationState
4153	async fn get_all_extensions(&self, request:Request<Empty>) -> Result<Response<GetAllExtensionsResponse>, Status> {
4154		use CommonLibrary::ExtensionManagement::ExtensionManagementService::ExtensionManagementService;
4155
4156		let _req = request.into_inner();
4157
4158		let Extensions = self.environment.GetExtensions().await.unwrap_or_default();
4159
4160		let ExtensionInfoList = Extensions
4161			.iter()
4162			.map(|Value| {
4163				ExtensionInfo {
4164					id:Value.get("Identifier").and_then(|V| V.as_str()).unwrap_or("").to_string(),
4165					display_name:Value.get("Name").and_then(|V| V.as_str()).unwrap_or("").to_string(),
4166					version:Value.get("Version").and_then(|V| V.as_str()).unwrap_or("").to_string(),
4167					is_active:true,
4168					extension_path:Value
4169						.get("ExtensionLocation")
4170						.and_then(|V| V.as_str())
4171						.unwrap_or("")
4172						.to_string(),
4173				}
4174			})
4175			.collect();
4176
4177		Ok(Response::new(GetAllExtensionsResponse { extensions:ExtensionInfoList }))
4178	}
4179
4180	/// Terminal Resize - emit a Tauri event so Sky can resize the xterm view.
4181	///
4182	/// PTY-level resize (via `portable_pty::MasterPty::resize`) is a P1 task
4183	/// that requires storing the PTY master handle in `TerminalStateDTO`.
4184	/// The Tauri event lets the UI immediately resize its canvas.
4185	async fn resize_terminal(&self, request:Request<ResizeTerminalRequest>) -> Result<Response<Empty>, Status> {
4186		use tauri::Emitter;
4187
4188		let req = request.into_inner();
4189		dev_log!(
4190			"cocoon",
4191			"[CocoonService] resize_terminal: id={} cols={} rows={}",
4192			req.terminal_id,
4193			req.cols,
4194			req.rows
4195		);
4196
4197		// Notify Sky/Wind of the new dimensions for UI resize
4198		let _ = self.environment.ApplicationHandle.emit(
4199			"sky://terminal/resize",
4200			json!({ "id": req.terminal_id, "cols": req.cols, "rows": req.rows }),
4201		);
4202
4203		// TODO(P1): Call portable_pty::MasterPty::resize once PtyMaster handle
4204		// is stored in TerminalStateDTO (requires wrapping MasterPty in Arc<Mutex>)
4205
4206		Ok(Response::new(Empty {}))
4207	}
4208
4209	/// Get Configuration - retrieve a configuration value from
4210	/// ConfigurationProvider.
4211	async fn get_configuration(
4212		&self,
4213		request:Request<GetConfigurationRequest>,
4214	) -> Result<Response<GetConfigurationResponse>, Status> {
4215		use CommonLibrary::Configuration::{
4216			ConfigurationProvider::ConfigurationProvider,
4217			DTO::ConfigurationOverridesDTO::ConfigurationOverridesDTO,
4218		};
4219
4220		let req = request.into_inner();
4221		let Key = if req.section.is_empty() {
4222			if req.key.is_empty() { None } else { Some(req.key.clone()) }
4223		} else if req.key.is_empty() {
4224			Some(req.section.clone())
4225		} else {
4226			Some(format!("{}.{}", req.section, req.key))
4227		};
4228
4229		dev_log!("cocoon", "[CocoonService] get_configuration: key={:?}", Key);
4230
4231		match self
4232			.environment
4233			.GetConfigurationValue(Key, ConfigurationOverridesDTO::default())
4234			.await
4235		{
4236			Ok(Value) => {
4237				let Bytes = serde_json::to_vec(&Value).unwrap_or_default();
4238				Ok(Response::new(GetConfigurationResponse { value:Bytes }))
4239			},
4240			Err(Error) => {
4241				dev_log!("cocoon", "warn: [CocoonService] get_configuration failed: {}", Error);
4242				Ok(Response::new(GetConfigurationResponse::default()))
4243			},
4244		}
4245	}
4246}