Mountain/Vine/Server/Notification/OutputChannelAppend.rs
1#![allow(non_snake_case)]
2//! Cocoon → Mountain `outputChannel.append` notification.
3//! Twin of `output.append`; see `OutputCreate.rs` for the duplicate-wire
4//! rationale.
5
6use serde_json::Value;
7use tauri::Emitter;
8
9use crate::{Vine::Server::MountainVinegRPCService::MountainVinegRPCService, dev_log};
10
11pub async fn OutputChannelAppend(Service:&MountainVinegRPCService, Parameter:&Value) {
12 let _ = Service.ApplicationHandle().emit("sky://output/append", Parameter);
13 // Per-append fire - `roo-cline`, `TypeScript`, `dart-code` all stream
14 // stdout into their output channels which fires 200+ appends per
15 // boot. The Sky-side consumer already sees the data via
16 // `sky://output/append`; the tag line here adds no signal beyond
17 // volume for THOSE channels. Route to `output-verbose`.
18 //
19 // Exception: vscode.git's "Git" channel logs activation flow at
20 // `[Model][doInitialScan]`, `[main] Using git`, `[main] Failed to create
21 // model` etc. - these are critical for diagnosing the F6 silent-bail
22 // (vscode.git activates ok but never reaches createSourceControl).
23 // Surface those at the `grpc` tag so they appear in `Trace=short`
24 // runs without forcing the user to enable `output-verbose` and drown
25 // in TypeScript / dart-code / roo-cline noise.
26 let ChannelName = Parameter
27 .get("channel")
28 .or_else(|| Parameter.get("name"))
29 .and_then(Value::as_str)
30 .unwrap_or("?");
31 // Char-aware truncation. Slicing a `&str` at `&S[..200]` panics when
32 // byte 200 lands inside a multi-byte UTF-8 codepoint (vscode.git's
33 // progress messages contain `•` which is 3 bytes; if the message is
34 // >200 bytes and the bullet sits across the boundary, the slice
35 // crashes the tokio worker - observed live during SCM viewlet open).
36 // Walk char boundaries instead so the cut always lands between codepoints.
37 let TruncatedValue = Parameter
38 .get("value")
39 .and_then(Value::as_str)
40 .map(|S| {
41 if S.len() > 200 {
42 let CutAt = S
43 .char_indices()
44 .map(|(Index, _)| Index)
45 .take_while(|Index| *Index <= 200)
46 .last()
47 .unwrap_or(0);
48 format!("{}…", &S[..CutAt])
49 } else {
50 S.to_string()
51 }
52 })
53 .unwrap_or_else(|| "<no-value>".to_string());
54 if ChannelName.eq_ignore_ascii_case("git")
55 || ChannelName.eq_ignore_ascii_case("source control")
56 || ChannelName.eq_ignore_ascii_case("scm")
57 {
58 dev_log!(
59 "grpc",
60 "[OutputChannel:{}] {}",
61 ChannelName,
62 TruncatedValue.trim_end_matches('\n')
63 );
64 } else {
65 dev_log!("output-verbose", "[OutputChannel] append channel={}", ChannelName);
66 }
67}