Skip to main content

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