Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/allow/apis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ endfor
ENDSESSION
enumset
environstrings
etw
EXACTSIZEONLY
EXECUTEDEFAULT
EXECUTEONLYONCE
Expand Down
70 changes: 70 additions & 0 deletions tools/wta/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions tools/wta/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,7 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
tracing-appender = "0.2"
which = "7"
win_etw_macros = "0.1.8"
win_etw_provider = "0.1.8"
rust-i18n = "3"
sys-locale = "0.3"
29 changes: 29 additions & 0 deletions tools/wta/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,33 @@ fn main() {
// (including the .NET CLR) default to 4–8 MB stacks.
#[cfg(all(debug_assertions, target_os = "windows", target_env = "msvc"))]
println!("cargo:rustc-link-arg=/STACK:8388608");

// ETW telemetry provider-group GUID injection.
//
// `telemetry_template.rs` contains a placeholder provider_group_guid
// ("ffffffff-ffff-…"). When the `MAGIC_TRACING_GUID` env var is set
// (internal builds), we replace the placeholder with the real GUID so
// events route to the Microsoft telemetry pipeline. OSS builds keep
// the placeholder — the provider still registers, but events land in
// an unrouted group.
//
// This pattern is borrowed from microsoft/sudo (sudo_events/build.rs).
let template = std::fs::read_to_string("src/telemetry_template.rs")
.expect("failed to read src/telemetry_template.rs");
let output = match std::env::var("MAGIC_TRACING_GUID") {
Ok(guid) => template.replace("ffffffff-ffff-ffff-ffff-ffffffffffff", &guid),
Err(_) => template,
};
let out_dir = std::env::var("OUT_DIR").unwrap();
let dest = std::path::Path::new(&out_dir).join("telemetry_generated.rs");
// Only write when contents differ to avoid unnecessary recompiles.
let needs_write = std::fs::read_to_string(&dest)
.map(|existing| existing != output)
.unwrap_or(true);
if needs_write {
std::fs::write(&dest, output).expect("failed to write telemetry_generated.rs");
}

println!("cargo:rerun-if-changed=src/telemetry_template.rs");
println!("cargo:rerun-if-env-changed=MAGIC_TRACING_GUID");
}
19 changes: 18 additions & 1 deletion tools/wta/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6278,14 +6278,17 @@ impl App {
fn turn_close_finalize_autofix(&mut self, session_id: &str, buf: &str) {
match parse_autofix_response(buf) {
AutofixDecision::Fix(recommendations) => {
crate::telemetry::error_fix_resolved("fix_suggested");
self.turn_surface_fix(session_id, recommendations, "autofix_fix");
self.turn_release_end_pending(session_id);
}
AutofixDecision::Explain { title, explanation } => {
crate::telemetry::error_fix_resolved("explained");
self.turn_surface_explain(session_id, title, explanation, "autofix_explain");
self.turn_release_end_pending(session_id);
}
AutofixDecision::Ignore => {
crate::telemetry::error_fix_resolved("ignored");
let target_tab = self.tab_for_session(session_id);
let pane_id = self.session_tab(session_id).autofix.pane_id.clone();
self.log_selection_phase_for(
Expand Down Expand Up @@ -6450,10 +6453,24 @@ impl App {
.prompt()
.and_then(|p| p.autofix.as_ref())
.map(|a| a.target_pane_id.clone());
let is_autofix = armed_pane.is_some();

// ETW: record the action type before `choice` is moved.
let action_type = match choice.actions.first() {
Some(crate::coordinator::RecommendedAction::Send { .. }) => "send",
Some(crate::coordinator::RecommendedAction::OpenAndSend { .. }) => "open_and_send",
Some(crate::coordinator::RecommendedAction::Open { .. }) => "open",
None => "none",
};
crate::telemetry::agent_response_action(action_type, is_autofix);
if is_autofix {
crate::telemetry::error_fix_resolved("applied");
}

let _ = self
.recommendation_tx
.send(crate::coordinator::ChoiceExecution { choice, insert_only });
if armed_pane.is_some() {
if is_autofix {
self.emit_autofix_state_cleared(&target_tab);
}
self.session_tab_mut(session_id).autofix.pane_id = None;
Expand Down
3 changes: 3 additions & 0 deletions tools/wta/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod runtime_paths;
mod rtl;
mod pane_context;
mod shell;
mod telemetry;
#[cfg(test)]
mod test_support;
mod theme;
Expand Down Expand Up @@ -1303,6 +1304,7 @@ async fn delegate_with_context(

async fn run_default_tui(cli: Cli) -> Result<()> {
let _guard = logging::init("main");
telemetry::init();
tracing::info!("=== run_default_tui started ===");

// Debug channel for TUI debug panel (WT protocol traffic viewer)
Expand Down Expand Up @@ -1346,6 +1348,7 @@ async fn run_default_tui(cli: Cli) -> Result<()> {
/// supplied named pipe and forwards ACP traffic over it.
pub(crate) async fn run_default_tui_over_pipe(cli: Cli, pipe_name: String) -> Result<()> {
let _guard = logging::init("main_helper");
telemetry::init();
tracing::info!(target: "helper", pipe = %pipe_name, "=== wta-helper starting (TUI) ===");

// Debug channel — same wiring as run_default_tui.
Expand Down
26 changes: 25 additions & 1 deletion tools/wta/src/protocol/acp/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::coordinator::default_supported_delegate_agents;
use crate::pane_context::PaneContext;
use crate::shell::{ShellManager, TerminalConfig};

const ACTIVE_PANE_CONTEXT_MAX_CHARS: usize = 4000;
const ACTIVE_PANE_CONTEXT_MAX_CHARS: usize = 16000;

/// Which prompt template was last shipped on a given ACP session.
/// Used by [`TemplateMemo`] to decide whether the next turn needs to
Expand Down Expand Up @@ -729,7 +729,11 @@ async fn complete_prompt_request<T, E: std::fmt::Display>(
prompt_timing: &PromptTimingState,
event_tx: &mpsc::UnboundedSender<AppEvent>,
session_id: String,
submitted_at_unix_s: f64,
) {
let success = result.is_ok();
let duration_ms = ((now_unix_s() - submitted_at_unix_s) * 1000.0).max(0.0) as u64;

match result {
Ok(_) => {
let timing_note = prompt_timing.complete(&session_id, true, None);
Expand Down Expand Up @@ -771,6 +775,9 @@ async fn complete_prompt_request<T, E: std::fmt::Display>(
});
}
}

// ETW: record that the agent's response completed.
crate::telemetry::agent_response_received(success, duration_ms);
}

fn humanize_model_name(model: &str) -> String {
Expand Down Expand Up @@ -2017,6 +2024,11 @@ pub async fn run_acp_client_over_pipe(
load_session_supported,
});

// Record the agent identity for ETW telemetry events.
if let Some(info) = init_resp.agent_info.as_ref() {
crate::telemetry::set_agent_id(&info.name);
}

// Per-tab session cache. Same semantics as in `run_inner`.
let tab_to_session: Arc<tokio::sync::Mutex<HashMap<String, acp::SessionId>>> =
Arc::new(tokio::sync::Mutex::new(HashMap::new()));
Expand Down Expand Up @@ -2789,6 +2801,12 @@ async fn run_inner(
load_session_supported,
});

// Record the agent identity for ETW telemetry events.
// Use the registry-sanitized id (e.g. "copilot", "claude") rather than
// the raw command path to avoid leaking local filesystem paths.
let telemetry_agent_id = crate::agent_registry::lookup_profile(raw_program).id;
crate::telemetry::set_agent_id(telemetry_agent_id);

Comment thread
PankajBhojwani marked this conversation as resolved.
// Per-tab session cache, shared across all in-flight prompt tasks.
// The startup session is bound to the owner tab GUID passed in by WT
// (via --owner-tab-id) so the first prompt on that tab reuses the
Expand Down Expand Up @@ -3390,6 +3408,9 @@ async fn dispatch_prompt_body(
});
prompt_timing_task.mark_prompt_sent(&prompt_session_id_str);

// ETW: record that a prompt was dispatched to the agent.
crate::telemetry::agent_prompt_sent(prompt.is_autofix);

// Register a cancel oneshot for this prompt. The cancel
// listener picks the sender out by session_id and signals it
// when the user presses Ctrl+C.
Expand All @@ -3412,6 +3433,7 @@ async fn dispatch_prompt_body(
&prompt_timing_task,
&event_tx_task,
prompt_session_id_str.clone(),
prompt.submitted_at_unix_s,
)
.await;
false
Expand Down Expand Up @@ -3514,6 +3536,7 @@ mod tests {
&prompt_timing,
&event_tx,
"test-session".to_string(),
now_unix_s(),
)
.await;

Expand All @@ -3537,6 +3560,7 @@ mod tests {
&prompt_timing,
&event_tx,
"test-session".to_string(),
now_unix_s(),
)
.await;

Expand Down
Loading
Loading