Skip to content

Commit 78b8cd7

Browse files
feat(Mountain/Handler/ExtensionStatus): Add extension status notification handlers
- Introduces `extension_status` handler module in Mountain to process Cocoon notifications about extension host lifecycle and activation events - Implements handlers for `$onWillActivateExtension`, `$onDidActivateExtension`, `$onExtensionActivationError`, and `$onExtensionRuntimeError` notifications from the extension host shim - Logs detailed activation metrics (load time, call time) and error information using structured logging - Emits Tauri events (`mountain://extension/*`) to propagate status updates to other Mountain components and Sky frontend - Validates notification parameter structure and provides error context for malformed messages - Lays foundation for future extension state tracking in AppState and potential recovery mechanisms This enhances observability of extension behavior in the Cocoon shim layer, critical for maintaining VS Code extension compatibility while transitioning to Tauri architecture.
1 parent 62c8549 commit 78b8cd7

2 files changed

Lines changed: 228 additions & 0 deletions

File tree

Source/Library.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub mod handlers {
4848
pub mod workspace_fs_api;
4949

5050
pub mod error_utils;
51+
52+
pub mod extension_status;
5153
}
5254

5355
pub mod Entry;
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// ---------------------------------------------------------------------------------------------
2+
// Mountain Extension Host Status Handlers (handlers/extension_status.rs)
3+
// --------------------------------------------------------------------------------------------
4+
// Handles notifications from Cocoon related to the extension host's lifecycle
5+
// and individual extension activation statuses. These are typically
6+
// fire-and-forget notifications from Cocoon to Mountain, primarily for logging
7+
// or potentially triggering other internal Mountain logic based on extension
8+
// states.
9+
//
10+
// Responsibilities:
11+
// - Handling `$onWillActivateExtension` notification: Logs the event.
12+
// - Handling `$onDidActivateExtension` notification: Logs the event and
13+
// activation details.
14+
// - Handling `$onExtensionActivationError` notification: Logs the error
15+
// details.
16+
// - Handling `$onExtensionRuntimeError` notification: Logs the error details.
17+
//
18+
// Key Interactions:
19+
// - Called by `track::dispatch_sidecar_request` for specific notification
20+
// methods.
21+
// - Primarily uses `log` for recording extension lifecycle events.
22+
// - Does not typically return data back to Cocoon for these notifications.
23+
// --------------------------------------------------------------------------------------------
24+
25+
use log::{debug, error, info, trace, warn};
26+
use serde_json::Value;
27+
// Added Manager for potential future app_handle.emit_all
28+
use tauri::{AppHandle, Manager, Runtime};
29+
30+
// For consistent error formatting if needed
31+
use crate::handlers::error_utils;
32+
33+
/// Generic handler for various extension host status notifications from Cocoon.
34+
/// `method`: The specific notification method name (e.g.,
35+
///
36+
/// "$onDidActivateExtension") `params_val`: The `Value` containing parameters
37+
/// from Cocoon (Track.rs ensures this is often Value::Array).
38+
pub async fn handle_ext_host_status<R:Runtime>(
39+
// Keep app_handle for potential future event emissions
40+
app_handle:AppHandle<R>,
41+
42+
method:&str,
43+
44+
params_val:Value,
45+
) -> Result<Value, String> {
46+
// Still Result<Value, String> for consistency with Track dispatcher signature
47+
let params_array = match params_val.as_array() {
48+
Some(arr) => arr,
49+
50+
None => {
51+
let err_msg = format!(
52+
"Parameters for notification method '{}' should be an array, but received type: {:?}",
53+
method,
54+
params_val.kind()
55+
);
56+
57+
error!("[ExtStatus Handler] {}", err_msg);
58+
59+
// This error is for the Track dispatcher if params_val isn't an array as
60+
// expected
61+
return Err(error_utils::rpc_param_error_string(method, "params", "array", None));
62+
},
63+
};
64+
65+
// Extract extension ID if available (common first parameter for these
66+
// notifications)
67+
let extension_id_dto = params_array.get(0);
68+
69+
let extension_id_str = extension_id_dto
70+
// Assuming DTO { value: "pub.name", uuid?: "..." }
71+
72+
.and_then(|v| v.get("value"))
73+
.and_then(Value::as_str)
74+
// Fallback if parsing fails
75+
.unwrap_or("unknown_extension");
76+
77+
trace!(
78+
"[ExtStatus] Method: {}, ExtID: {}, Params Count: {}",
79+
method,
80+
extension_id_str,
81+
params_array.len()
82+
);
83+
84+
match method {
85+
"$onWillActivateExtension" => {
86+
info!(
87+
"[ExtStatus] <= {}: Extension '{}' attempting activation.",
88+
method, extension_id_str
89+
);
90+
91+
// Future: Update some internal state, e.g.,
92+
93+
// AppState.activating_extensions.insert(extension_id_str);
94+
},
95+
96+
"$onDidActivateExtension" => {
97+
// Params: [extensionIdDto, startup: boolean, codeLoadingTime: number,
98+
99+
// activateCallTime: number, activateResolvedTime: number, activationReason:
100+
// IActivationReason]
101+
let startup = params_array.get(1).and_then(Value::as_bool).unwrap_or(false);
102+
103+
// Use -1 to indicate missing/invalid
104+
let code_loading_time = params_array.get(2).and_then(Value::as_f64).unwrap_or(-1.0);
105+
106+
let activate_call_time = params_array.get(3).and_then(Value::as_f64).unwrap_or(-1.0);
107+
108+
let activate_resolved_time = params_array.get(4).and_then(Value::as_f64).unwrap_or(-1.0);
109+
110+
let activation_reason_dto = params_array.get(5);
111+
112+
let activation_event_str = activation_reason_dto
113+
// VS Code activationReason structure
114+
.and_then(|r| r.get("activationEvent"))
115+
.and_then(Value::as_str)
116+
.unwrap_or("N/A");
117+
118+
info!(
119+
"[ExtStatus] <= {}: Extension '{}' ACTIVATED. Startup: {}, LoadTime: {:.2}ms, CallTime: {:.2}ms, \
120+
ResolveTime: {:.2}ms, Event: '{}'",
121+
method,
122+
extension_id_str,
123+
startup,
124+
code_loading_time,
125+
activate_call_time,
126+
activate_resolved_time,
127+
activation_event_str
128+
);
129+
130+
// Future: Update internal state, e.g.,
131+
132+
// AppState.active_extensions.insert(extension_id_str); Example: Emit Tauri
133+
// event for other parts of Mountain or Sky to know
134+
if let Err(e) =
135+
app_handle.emit_all("mountain://extension/activated", serde_json::json!({ "id": extension_id_str }))
136+
{
137+
warn!(
138+
"[ExtStatus] Failed to emit mountain://extension/activated for {}: {}",
139+
extension_id_str, e
140+
);
141+
}
142+
},
143+
144+
"$onExtensionActivationError" => {
145+
let error_details_val = params_array.get(1);
146+
147+
let error_message = error_details_val
148+
.and_then(|e| e.get("message"))
149+
.and_then(Value::as_str)
150+
.unwrap_or("Unknown activation error");
151+
152+
error!(
153+
"[ExtStatus] <= {}: Activation FAILED for extension '{}'. Message: '{}'. FullDetails: {:?}",
154+
method,
155+
extension_id_str,
156+
error_message,
157+
error_details_val.unwrap_or(&Value::Null)
158+
);
159+
160+
// Future: Update internal state, e.g.,
161+
162+
// AppState.failed_extensions.insert(extension_id_str, error_details);
163+
164+
if let Err(e) = app_handle.emit_all(
165+
"mountain://extension/activation_error",
166+
serde_json::json!({
167+
"id": extension_id_str,
168+
169+
// Send details to UI
170+
"error": error_details_val.cloned().unwrap_or_default()
171+
}),
172+
) {
173+
warn!(
174+
"[ExtStatus] Failed to emit mountain://extension/activation_error for {}: {}",
175+
extension_id_str, e
176+
);
177+
}
178+
},
179+
180+
"$onExtensionRuntimeError" => {
181+
// Params: [extensionIdDto, error: SerializedError] (SerializedError usually has
182+
// message, name, stack)
183+
let error_details_val = params_array.get(1);
184+
185+
let error_message = error_details_val
186+
.and_then(|e| e.get("message"))
187+
.and_then(Value::as_str)
188+
.unwrap_or("Unknown runtime error");
189+
190+
error!(
191+
"[ExtStatus] <= {}: Runtime ERROR in extension '{}'. Message: '{}'. FullDetails: {:?}",
192+
method,
193+
extension_id_str,
194+
error_message,
195+
error_details_val.unwrap_or(&Value::Null)
196+
);
197+
198+
// Future: Increment error count for extension, potentially disable if too many
199+
// errors.
200+
if let Err(e) = app_handle.emit_all(
201+
"mountain://extension/runtime_error",
202+
serde_json::json!({
203+
"id": extension_id_str,
204+
205+
"error": error_details_val.cloned().unwrap_or_default()
206+
}),
207+
) {
208+
warn!(
209+
"[ExtStatus] Failed to emit mountain://extension/runtime_error for {}: {}",
210+
extension_id_str, e
211+
);
212+
}
213+
},
214+
215+
_ => {
216+
warn!(
217+
"[ExtStatus] Received unknown status notification method: '{}' with params: {:?}",
218+
method, params_array
219+
);
220+
},
221+
}
222+
223+
// Notifications typically don't require a specific return value to the caller
224+
// (Vine/Track)
225+
Ok(Value::Null)
226+
}

0 commit comments

Comments
 (0)