Files
1jehuang--jcode/crates/jcode-protocol/src/comm_format.rs
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

647 lines
22 KiB
Rust

use std::collections::{HashMap, HashSet};
use super::{
AgentInfo, AgentStatusSnapshot, AwaitedMemberStatus, ContextEntry, HistoryMessage,
PlanGraphStatus, SwarmChannelInfo, ToolCallSummary,
};
pub fn format_comm_plan_followup(summary: &PlanGraphStatus) -> String {
let mut parts = Vec::new();
parts.push(format!("active={}", summary.active_ids.len()));
if !summary.next_ready_ids.is_empty() {
parts.push(format!("next={}", summary.next_ready_ids.join(", ")));
}
if !summary.newly_ready_ids.is_empty() {
parts.push(format!(
"newly_ready={}",
summary.newly_ready_ids.join(", ")
));
}
parts.join(" · ")
}
pub fn default_comm_cleanup_target_statuses() -> Vec<String> {
vec![
"ready".to_string(),
"completed".to_string(),
"failed".to_string(),
"stopped".to_string(),
"crashed".to_string(),
]
}
pub fn default_comm_run_await_statuses() -> Vec<String> {
vec![
"ready".to_string(),
"completed".to_string(),
"failed".to_string(),
"stopped".to_string(),
"crashed".to_string(),
]
}
pub fn default_comm_await_target_statuses() -> Vec<String> {
vec![
"ready".to_string(),
"completed".to_string(),
"stopped".to_string(),
"failed".to_string(),
"crashed".to_string(),
]
}
pub fn comm_cleanup_candidate_session_ids(
owner_session_id: &str,
members: &[AgentInfo],
target_status: &[String],
requested_session_ids: &[String],
force: bool,
) -> Vec<String> {
let status_filter: HashSet<&str> = target_status.iter().map(String::as_str).collect();
let requested: HashSet<&str> = requested_session_ids.iter().map(String::as_str).collect();
let restrict_to_requested = !requested.is_empty();
let mut ids = members
.iter()
.filter(|member| member.session_id != owner_session_id)
.filter(|member| !restrict_to_requested || requested.contains(member.session_id.as_str()))
.filter(|member| {
member
.status
.as_deref()
.is_some_and(|status| status_filter.contains(status))
})
.filter(|member| {
force || member.report_back_to_session_id.as_deref() == Some(owner_session_id)
})
.map(|member| member.session_id.clone())
.collect::<Vec<_>>();
ids.sort();
ids
}
pub fn format_comm_context_entries(entries: &[ContextEntry]) -> String {
if entries.is_empty() {
"No shared context found.".to_string()
} else {
let mut output = String::from("Shared context from other agents:\n\n");
for entry in entries {
let from = entry.from_name.as_deref().unwrap_or(&entry.from_session);
output.push_str(&format!(
" {} (from {}): {}\n",
entry.key, from, entry.value
));
}
output
}
}
pub fn duplicate_comm_friendly_names<'a>(
names: impl IntoIterator<Item = Option<&'a str>>,
) -> HashSet<&'a str> {
let mut counts = HashMap::<&'a str, usize>::new();
for name in names.into_iter().flatten() {
*counts.entry(name).or_default() += 1;
}
counts
.into_iter()
.filter_map(|(name, count)| (count > 1).then_some(name))
.collect()
}
pub fn comm_session_display_suffix(session_id: &str) -> &str {
let suffix = session_id.rsplit('_').next().unwrap_or(session_id);
if suffix.len() > 6 {
&suffix[suffix.len() - 6..]
} else {
suffix
}
}
pub fn comm_display_friendly_name(
friendly_name: Option<&str>,
session_id: &str,
duplicate_names: &HashSet<&str>,
) -> String {
match friendly_name {
Some(name) if duplicate_names.contains(name) => {
format!("{} [{}]", name, comm_session_display_suffix(session_id))
}
Some(name) => name.to_string(),
None => session_id.to_string(),
}
}
pub fn format_comm_members(current_session_id: &str, members: &[AgentInfo]) -> String {
if members.is_empty() {
"No other agents in this codebase.".to_string()
} else {
let duplicate_names = duplicate_comm_friendly_names(
members.iter().map(|member| member.friendly_name.as_deref()),
);
let mut output = String::from("Agents in this codebase:\n\n");
for member in members {
let name = comm_display_friendly_name(
member.friendly_name.as_deref(),
&member.session_id,
&duplicate_names,
);
let session = &member.session_id;
let role = member.role.as_deref().unwrap_or("agent");
let files = member.files_touched.join(", ");
let status = member.status.as_deref().unwrap_or("unknown");
let is_me = session == current_session_id;
let role_label = if role != "agent" {
format!(" [{}]", role)
} else {
String::new()
};
// Status line: lifecycle + detail, then a contextual age label.
// For an idle/ready agent the "age" is how long it has been idle;
// for a running agent it is how long the current turn has run.
let detail_suffix = member
.detail
.as_deref()
.map(|detail| format!(" — {}", detail))
.unwrap_or_default();
// Stable task label: what this agent was spawned/assigned for.
// Skip when the transient detail already says the same thing.
let task_suffix = match member.task_label.as_deref() {
Some(task)
if !task.trim().is_empty()
&& member.detail.as_deref().is_none_or(|d| !d.contains(task)) =>
{
format!("\n Task: {}", task)
}
_ => String::new(),
};
let age_suffix = match member.status_age_secs {
Some(age) if status == "ready" || status == "idle" => {
format!(" · idle {}", format_secs(age))
}
Some(age) if status == "running" => format!(" · {}", format_secs(age)),
Some(age) => format!(" · {} ago", format_secs(age)),
None => String::new(),
};
// Last observed activity (tokens/tools/heartbeats). status_age only
// tracks lifecycle transitions, so a worker mid-turn for minutes
// looks stale without this even while it streams tokens.
let activity_age_suffix = match member.last_activity_age_secs {
Some(age) if status == "running" || status == "queued" => {
format!(" · active {} ago", format_secs(age))
}
_ => String::new(),
};
// Live activity: what the agent is doing right now.
let activity_suffix = match member.activity.as_ref() {
Some(activity) if activity.is_processing => {
match activity.current_tool_name.as_deref() {
Some(tool) => format!("\n Activity: working ({})", tool),
None => "\n Activity: thinking".to_string(),
}
}
_ => String::new(),
};
// Progress: todos completed / total.
let progress_suffix = match (member.todos_completed, member.todos_total) {
(Some(done), Some(total)) if total > 0 => {
format!("\n Progress: {}/{} todos", done, total)
}
_ => String::new(),
};
// Live work signal: recent token churn + cumulative + turns.
let mut work_meta = Vec::new();
if let (Some(recent), Some(window)) =
(member.recent_total_tokens, member.recent_window_secs)
&& recent > 0
{
work_meta.push(format!("{} tok/{}s", format_count(recent), window));
}
if let Some(turns) = member.turn_count.filter(|turns| *turns > 0) {
work_meta.push(format!("{} turns", turns));
}
if let Some(total) = member.cumulative_total_tokens.filter(|total| *total > 0) {
work_meta.push(format!("{} tok total", format_count(total)));
}
let work_suffix = if work_meta.is_empty() {
String::new()
} else {
format!("\n Work: {}", work_meta.join(" · "))
};
// Model line.
let model_suffix = match (
member.provider_name.as_deref(),
member.provider_model.as_deref(),
) {
(Some(provider), Some(model)) => format!("\n Model: {}/{}", provider, model),
(None, Some(model)) => format!("\n Model: {}", model),
_ => String::new(),
};
let mut extra_meta = Vec::new();
if member.is_headless == Some(true) {
extra_meta.push("headless".to_string());
}
if let Some(owner) = member.report_back_to_session_id.as_deref() {
if owner == current_session_id {
extra_meta.push("owned_by_you".to_string());
} else {
extra_meta.push(format!("owned_by={owner}"));
}
}
if let Some(attachments) = member.live_attachments {
extra_meta.push(format!("attachments={attachments}"));
}
let meta_suffix = if extra_meta.is_empty() {
String::new()
} else {
format!("\n Meta: {}", extra_meta.join(" · "))
};
// Completion report when the agent has finished.
let report_suffix = match member.latest_completion_report.as_deref() {
Some(report) if !report.trim().is_empty() => {
format!("\n Report: {}", truncate_report(report))
}
_ => String::new(),
};
output.push_str(&format!(
" {}{} ({})\n Status: {}{}{}{}{}{}{}{}{}{}{}{}\n",
name,
role_label,
if is_me { "you" } else { session },
status,
detail_suffix,
age_suffix,
activity_age_suffix,
task_suffix,
activity_suffix,
progress_suffix,
work_suffix,
model_suffix,
if files.is_empty() {
String::new()
} else {
format!("\n Files: {}", files)
},
meta_suffix,
report_suffix,
));
}
output
}
}
/// Format a duration in seconds into a compact human label (e.g. `45s`, `3m`, `2h`).
fn format_secs(secs: u64) -> String {
if secs < 60 {
format!("{}s", secs)
} else if secs < 3600 {
format!("{}m", secs / 60)
} else {
format!("{}h", secs / 3600)
}
}
/// Format a token count compactly (e.g. `850`, `12.3k`, `1.2M`).
fn format_count(count: u64) -> String {
if count < 1_000 {
count.to_string()
} else if count < 1_000_000 {
format!("{:.1}k", count as f64 / 1_000.0)
} else {
format!("{:.1}M", count as f64 / 1_000_000.0)
}
}
/// Truncate a completion report to a single compact line for the roster view.
fn truncate_report(report: &str) -> String {
const MAX: usize = 120;
let one_line: String = report.split_whitespace().collect::<Vec<_>>().join(" ");
if one_line.chars().count() > MAX {
let truncated: String = one_line.chars().take(MAX).collect();
format!("{}…", truncated)
} else {
one_line
}
}
pub fn format_comm_tool_summary(target: &str, calls: &[ToolCallSummary]) -> String {
if calls.is_empty() {
format!("No tool calls found for {}", target)
} else {
let call_count = calls.len();
let mut output = format!(
"Tool call summary for {} ({} call{}):\n\n",
target,
call_count,
if call_count == 1 { "" } else { "s" }
);
for call in calls {
output.push_str(&format!(" {}{}\n", call.tool_name, call.brief_output));
}
output
}
}
pub fn format_comm_status_snapshot(snapshot: &AgentStatusSnapshot) -> String {
let target = snapshot
.friendly_name
.as_deref()
.unwrap_or(&snapshot.session_id);
let status = snapshot.status.as_deref().unwrap_or("unknown");
let mut output = format!(
"Status snapshot for {} ({})\n\n",
target, snapshot.session_id
);
output.push_str(&format!(" Lifecycle: {}", status));
if let Some(detail) = snapshot.detail.as_deref() {
output.push_str(&format!(" — {}", detail));
}
output.push('\n');
let activity = snapshot
.activity
.as_ref()
.map(|activity| match activity.current_tool_name.as_deref() {
Some(tool_name) => format!("busy ({tool_name})"),
None if activity.is_processing => "busy".to_string(),
_ => "idle".to_string(),
})
.unwrap_or_else(|| "idle".to_string());
output.push_str(&format!(" Activity: {}\n", activity));
if let Some(role) = snapshot.role.as_deref() {
output.push_str(&format!(" Role: {}\n", role));
}
if let Some(swarm_id) = snapshot.swarm_id.as_deref() {
output.push_str(&format!(" Swarm: {}\n", swarm_id));
}
let mut meta = Vec::new();
if snapshot.is_headless == Some(true) {
meta.push("headless".to_string());
}
if let Some(attachments) = snapshot.live_attachments {
meta.push(format!("attachments={attachments}"));
}
if let Some(age_secs) = snapshot.last_activity_age_secs {
meta.push(format!("active={} ago", format_secs(age_secs)));
}
if let Some(age_secs) = snapshot.status_age_secs {
meta.push(format!("status_age={}s", age_secs));
}
if let Some(age_secs) = snapshot.joined_age_secs {
meta.push(format!("joined={}s", age_secs));
}
if !meta.is_empty() {
output.push_str(&format!(" Meta: {}\n", meta.join(" · ")));
}
if snapshot.provider_name.is_some() || snapshot.provider_model.is_some() {
let provider = snapshot.provider_name.as_deref().unwrap_or("unknown");
let model = snapshot.provider_model.as_deref().unwrap_or("unknown");
output.push_str(&format!(" Provider: {} / {}\n", provider, model));
}
if snapshot.files_touched.is_empty() {
output.push_str(" Files: (none)\n");
} else {
output.push_str(&format!(" Files: {}\n", snapshot.files_touched.join(", ")));
}
output
}
pub fn format_comm_plan_status(summary: &PlanGraphStatus) -> String {
let swarm_id = summary.swarm_id.as_deref().unwrap_or("unknown");
let mut output = format!(
"Plan status for swarm {}\n\n Version: {}\n Mode: {}\n Items: {}\n",
swarm_id, summary.version, summary.mode, summary.item_count
);
// Growth accounting: deep mode is meant to outgrow its seed (decomposition,
// gate-injected gaps). Surfacing seeded-vs-grown makes a plan that never
// grew visibly under-explored.
if summary.mode.eq_ignore_ascii_case("deep") && summary.item_count > 0 {
output.push_str(&format!(
" Growth: {} seeded -> {} nodes ({} machinery-grown)",
summary.seeded_count, summary.item_count, summary.grown_count
));
if summary.grown_count == 0 {
output.push_str(
" — the graph has not grown beyond its seed yet; \
expect expand_node decomposition and gate-injected gaps",
);
}
output.push('\n');
}
output.push_str(&format!(
" Ready: {}\n",
if summary.ready_ids.is_empty() {
"(none)".to_string()
} else {
summary.ready_ids.join(", ")
}
));
output.push_str(&format!(
" Next up: {}\n",
if summary.next_ready_ids.is_empty() {
"(none)".to_string()
} else {
summary.next_ready_ids.join(", ")
}
));
if !summary.newly_ready_ids.is_empty() {
output.push_str(&format!(
" Newly ready: {}\n",
summary.newly_ready_ids.join(", ")
));
}
if !summary.blocked_ids.is_empty() {
output.push_str(&format!(" Blocked: {}\n", summary.blocked_ids.join(", ")));
}
if !summary.active_ids.is_empty() {
output.push_str(&format!(" Active: {}\n", summary.active_ids.join(", ")));
}
if !summary.completed_ids.is_empty() {
output.push_str(&format!(
" Completed: {}\n",
summary.completed_ids.join(", ")
));
}
if !summary.failed_ids.is_empty() {
output.push_str(&format!(
" Failed (terminal without completing): {}\n",
summary.failed_ids.join(", ")
));
// Recorded failure reasons (from the durable task progress): a run
// that burned nodes on e.g. a 401 credential wave must explain itself
// here instead of only listing ids.
for id in &summary.failed_ids {
if let Some(reason) = summary.failed_reasons.get(id) {
output.push_str(&format!(" {}: {}\n", id, reason));
}
}
}
if !summary.low_confidence_ids.is_empty() {
output.push_str(&format!(
" Low confidence (completed but shaky; widen with follow-up nodes): {}\n",
summary.low_confidence_ids.join(", ")
));
}
if !summary.cycle_ids.is_empty() {
output.push_str(&format!(" Cycles: {}\n", summary.cycle_ids.join(", ")));
}
if !summary.unresolved_dependency_ids.is_empty() {
output.push_str(&format!(
" Missing deps: {}\n",
summary.unresolved_dependency_ids.join(", ")
));
}
output
}
pub fn format_comm_context_history(target: &str, messages: &[HistoryMessage]) -> String {
if messages.is_empty() {
format!("No conversation history for {}", target)
} else {
let mut output = format!(
"Conversation context for {} ({} messages):\n\n",
target,
messages.len()
);
for msg in messages {
let truncated = if msg.content.len() > 500 {
format!("{}...", &msg.content[..500])
} else {
msg.content.clone()
};
output.push_str(&format!("[{}] {}\n\n", msg.role, truncated));
}
output
}
}
pub fn truncate_comm_completion_report(report: &str) -> String {
const MAX_REPORT_CHARS: usize = 4000;
let report = report.trim();
if report.chars().count() <= MAX_REPORT_CHARS {
return report.to_string();
}
let suffix = "\n\n[Report truncated by jcode.]";
let keep = MAX_REPORT_CHARS.saturating_sub(suffix.chars().count());
let mut out: String = report.chars().take(keep).collect();
out.push_str(suffix);
out
}
pub fn latest_assistant_comm_report(messages: &[HistoryMessage]) -> Option<String> {
messages.iter().rev().find_map(|message| {
if message.role != "assistant" {
return None;
}
let report = message.content.trim();
(!report.is_empty()).then(|| truncate_comm_completion_report(report))
})
}
pub fn resolve_optional_comm_target_session(
target: Option<String>,
current_session: &str,
) -> String {
match target {
Some(target) if target == "current" => current_session.to_string(),
Some(target) => target,
None => current_session.to_string(),
}
}
pub fn format_comm_awaited_members_with_reports(
completed: bool,
summary: &str,
members: &[AwaitedMemberStatus],
reports: &HashMap<String, String>,
) -> String {
// An any-mode wait can complete while some members are still pending, so
// only claim "All members done" when every member actually matched.
let all_done = members.iter().all(|member| member.done);
let mut output = if completed && all_done {
format!("All members done. {}\n", summary)
} else if completed {
format!("Await satisfied. {}\n", summary)
} else {
format!("Await incomplete. {}\n", summary)
};
if !members.is_empty() {
let duplicate_names = duplicate_comm_friendly_names(
members.iter().map(|member| member.friendly_name.as_deref()),
);
output.push_str("\nMember statuses:\n");
for member in members {
let name = comm_display_friendly_name(
member.friendly_name.as_deref(),
&member.session_id,
&duplicate_names,
);
let icon = if member.done { "✓" } else { "✗" };
output.push_str(&format!(" {} {} ({})\n", icon, name, member.status));
}
}
let mut report_members: Vec<_> = members
.iter()
.filter_map(|member| {
member
.completion_report
.as_ref()
.or_else(|| reports.get(&member.session_id))
.map(|report| (member, report))
})
.collect();
report_members.sort_by(|(left, _), (right, _)| left.session_id.cmp(&right.session_id));
if !report_members.is_empty() {
let duplicate_names = duplicate_comm_friendly_names(
members.iter().map(|member| member.friendly_name.as_deref()),
);
output.push_str("\nCompletion reports:\n");
for (member, report) in report_members {
let name = comm_display_friendly_name(
member.friendly_name.as_deref(),
&member.session_id,
&duplicate_names,
);
output.push_str(&format!(
"\n--- {} ({}) ---\n{}\n",
name, member.status, report
));
}
}
output
}
pub fn format_comm_channels(channels: &[SwarmChannelInfo]) -> String {
if channels.is_empty() {
"No swarm channels found.".to_string()
} else {
let mut output = String::from("Swarm channels:\n\n");
for channel in channels {
output.push_str(&format!(
" #{}{} subscriber{}\n",
channel.channel,
channel.member_count,
if channel.member_count == 1 { "" } else { "s" }
));
}
output
}
}