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	// 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}