use super::*;
pub(super) fn roles(state: &MissionControlState) -> Vec<TranscriptCardRole> {
project_cards(state)
.into_iter()
.map(|card| card.role)
.collect()
}
pub(super) fn linked_tool_state(
id: &str,
tool_name: &str,
activity_label: &str,
status: ActivityStatus,
fields: Vec<(String, String)>,
preview: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} {activity_label}",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: tool_name.to_string(),
},
);
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: if tool_name == "subagents" {
ActivityKind::SubagentBatch
} else {
ActivityKind::Tool
},
status,
metadata: ActivityMetadata {
label: activity_label.to_string(),
detail: None,
fields,
},
});
state.apply_activity_event(ActivityEvent::Delta {
id,
preview: preview.to_string(),
});
state
}
pub(super) fn read_card_state(
id: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} read",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: "read".to_string(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: "read".into(),
label: "read".into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn read_result(
requested_path: &str,
success: bool,
error: Option<&str>,
) -> serde_json::Value {
let mut result = serde_json::json!({
"requested_path": requested_path,
"success": success,
});
if let Some(error) = error {
result["error"] = serde_json::Value::String(error.to_string());
}
result
}
pub(super) fn bash_card_state(
id: &str,
tool_name: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} {tool_name}",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: tool_name.to_string(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: tool_name.into(),
label: tool_name.into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn grep_card_state(
id: &str,
tool_name: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} {tool_name}",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: tool_name.to_string(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: tool_name.into(),
label: tool_name.into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn ast_grep_card_state(
id: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} ast_grep",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: "ast_grep".into(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: "ast_grep".into(),
label: "ast_grep".into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn find_card_state(
id: &str,
tool_name: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} {tool_name}",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: tool_name.to_string(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: tool_name.into(),
label: tool_name.into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn view_image_card_state(
id: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} view_image",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: "view_image".to_string(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: "view_image".into(),
label: "view_image".into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn hash_edit_card_state(
id: &str,
status: ActivityStatus,
input: &str,
metadata: serde_json::Value,
output: &str,
applied_diff: Option<&str>,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} hash_edit",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: "hash_edit".to_string(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: "hash_edit".into(),
label: "hash_edit".into(),
params: serde_json::json!({"input":input}),
metadata,
status,
output: output.into(),
applied_diff: applied_diff.map(Into::into),
},
});
state
}
pub(super) fn line_text(line: &Line<'static>) -> String {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
}
pub(super) fn write_card_state(
id: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let activity_id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} write",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: activity_id.clone(),
tool_name: "write".into(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id: activity_id,
detail: crate::output::ToolActivityDetail {
tool_name: "write".into(),
label: "write".into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn list_files_card_state(
id: &str,
status: ActivityStatus,
params: serde_json::Value,
metadata: serde_json::Value,
output: &str,
) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.transcript.push_back(format!(
"tool: {} list_files",
status_mark(transcript_status_from_activity(status))
));
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: id.clone(),
tool_name: "list_files".into(),
},
);
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: "list_files".into(),
label: "list_files".into(),
params,
metadata,
status,
output: output.into(),
applied_diff: None,
},
});
state
}
pub(super) fn append_subagent_card_batch(
state: &mut MissionControlState,
batch: &str,
statuses: &[ActivityStatus],
) -> ActivityId {
let batch_id = ActivityId::new(batch);
let index = state.transcript.len();
state
.transcript
.push_back("tool: ⟳ subagents • running".to_string());
state.transcript.link_activity(
index,
TranscriptActivityLink {
activity_id: batch_id.clone(),
tool_name: "subagents".to_string(),
},
);
state.apply_activity_event(ActivityEvent::Started {
id: batch_id.clone(),
parent_id: None,
kind: ActivityKind::SubagentBatch,
status: ActivityStatus::Running,
metadata: ActivityMetadata {
label: format!("subagents • {} tasks", statuses.len()),
detail: None,
fields: vec![("tasks".to_string(), statuses.len().to_string())],
},
});
for (index, status) in statuses.iter().copied().enumerate() {
let task = index + 1;
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new(format!("{batch}/g{task}")),
parent_id: Some(batch_id.clone()),
kind: ActivityKind::SubagentTask,
status,
metadata: ActivityMetadata {
label: format!("g{task} • depth 1 • task intent {task}"),
detail: Some(format!("FORBIDDEN CHILD PROMPT {task}")),
fields: vec![("identity".to_string(), format!("agent-{task}"))],
},
});
}
batch_id
}
pub(super) fn add_subagent_card_assistant_activity(
state: &mut MissionControlState,
task: &ActivityId,
id: &str,
text: &str,
) {
let id = ActivityId::new(id);
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: Some(task.clone()),
kind: ActivityKind::Assistant,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("assistant"),
});
state.apply_activity_event(ActivityEvent::Delta {
id,
preview: text.to_string(),
});
}
pub(super) fn add_subagent_card_tool_activity(
state: &mut MissionControlState,
task: &ActivityId,
id: &str,
tool_name: &str,
label: &str,
) {
let id = ActivityId::new(id);
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: Some(task.clone()),
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new(label),
});
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: tool_name.into(),
label: label.into(),
params: serde_json::json!({"authorization":"FORBIDDEN PARAM"}),
metadata: serde_json::json!({}),
status: ActivityStatus::Success,
output: "FORBIDDEN OUTPUT SECRET".into(),
applied_diff: None,
},
});
}
pub(super) fn timestamped_long_header_state() -> MissionControlState {
let mut state = linked_tool_state(
"timestamp-long-header",
"very-long-tool-name-hidden-tail",
"very-long-tool-name-hidden-tail",
ActivityStatus::Success,
Vec::new(),
"safe output",
);
let timestamp =
chrono::TimeZone::with_ymd_and_hms(&chrono::Local, 2024, 1, 2, 3, 4, 5).unwrap();
state.set_transcript_timestamp_for_test(0, timestamp);
state
}