use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use std::time::Instant;
use futures_util::FutureExt as _;
use tracing::Instrument as _;
use crate::db::ipc::{CallReply, CallRequest};
use crate::tools::ToolAnswer;
pub(crate) async fn execute(call: CallRequest) -> CallReply {
match crate::cli::wait_for_service().await {
crate::cli::ServiceGate::Ready => {}
crate::cli::ServiceGate::Unavailable => {
return CallReply::unavailable(
"the instance did not finish starting up, so it cannot serve command-line \
operations",
);
}
crate::cli::ServiceGate::StillStarting => {
return CallReply::unavailable(
"the instance has not finished starting up, so it cannot serve command-line \
operations yet — nothing ran; try again once it is up",
);
}
crate::cli::ServiceGate::NotAccepted => {
return CallReply::not_accepted(
"the instance is shutting down, so it did not take the operation on — nothing \
ran; try again once it is up",
);
}
}
if !crate::cli::fronts_tool(&call.tool) {
return unknown_tool_refusal(&call);
}
if call.tool == "document"
&& call.args.get("action").and_then(serde_json::Value::as_str)
== Some(crate::cli::DOCUMENT_READ)
{
return execute_document_read(&call).await;
}
let ws = crate::users::personal_workspace_struct(crate::users::ADMIN_USER_NAME);
let tools = crate::Role::Assistant.tools(
&ws,
true,
Arc::new(crate::tools::chrome::ChromeRunSessions::default()),
);
let Some(tool) = crate::tools::find_tool(&tools, &call.tool) else {
return unknown_tool_refusal(&call);
};
let tool_name = tool.name();
let user_name = crate::users::ADMIN_USER_NAME.to_string();
let channel = crate::cli::CHANNEL.to_string();
let tracking = Some(crate::agent::registry::AgentTracking {
agent_id: "cli".to_string(),
generation: 0,
role: "cli".to_string(),
workspace: ws.name.clone(),
});
let args = call.args.clone();
let started = Instant::now();
let output_dir = call.output_dir.as_deref().map(std::path::PathBuf::from);
let outcome = carry_out(&ws, async {
crate::agent::CURRENT_TOOL_COMMAND_LINE
.scope(true, async {
crate::agent::CURRENT_TOOL_USER_NAME
.scope(user_name, async {
crate::agent::CURRENT_TOOL_CHANNEL
.scope(channel, async {
crate::agent::CURRENT_TOOL_AGENT_TRACKING
.scope(tracking, async {
crate::agent::CURRENT_TOOL_OUTPUT_DIR
.scope(output_dir, tool.execute_answer(&ws, args))
.await
})
.await
})
.await
})
.await
})
.await
})
.await;
let duration_ms = crate::util::millis_i64(started.elapsed());
record_operation(&call, tool_name, duration_ms, &outcome, &ws.path);
reply(outcome, &call.secrets)
}
async fn execute_document_read(call: &CallRequest) -> CallReply {
let ws = crate::users::personal_workspace_struct(crate::users::ADMIN_USER_NAME);
let Some(destination) = call.output_dir.as_deref() else {
return CallReply::refused(
"the document family's read was sent with no destination directory, so nothing ran",
);
};
let started = Instant::now();
let outcome = carry_out(
&ws,
crate::tools::read_document::read_for_command_line(
&ws,
&call.args,
std::path::Path::new(destination),
),
)
.await;
record_operation(
call,
"document",
crate::util::millis_i64(started.elapsed()),
&outcome,
&ws.path,
);
reply(outcome, &call.secrets)
}
async fn carry_out<T>(
ws: &crate::Workspace,
operation: impl std::future::Future<Output = anyhow::Result<T>>,
) -> Result<T, String> {
let span = tracing::info_span!(
"agent",
agent_id = %"cli",
role = %"cli",
workspace = %ws.path
);
match AssertUnwindSafe(operation)
.catch_unwind()
.instrument(span)
.await
{
Ok(Ok(value)) => Ok(value),
Ok(Err(e)) => Err(format!("{e:#}")),
Err(payload) => Err(crate::util::panic_message(&*payload)),
}
}
pub(crate) fn hold_flight() -> crate::agent::registry::NonAgentCallGuard {
let ws = crate::users::personal_workspace_struct(crate::users::ADMIN_USER_NAME);
crate::agent::registry::NON_AGENT_CALLS.register("cli_command", &ws.name, None, false, None)
}
fn unknown_tool_refusal(call: &CallRequest) -> CallReply {
let wording = if crate::cli::fronts_tool(&call.tool) {
format!(
"the running product's command line holds no tool named '{}' — nothing ran",
call.tool
)
} else {
format!(
"the running product does not serve '{}' on its command line — it was built before \
that command existed, so restart it from this build and try again; nothing ran",
call.tool
)
};
CallReply::unavailable(crate::util::redact_secrets(&wording, &call.secrets))
}
const PARTIAL_RECORD_REASON: &str = "the operation reported that part of it was not done";
fn record_operation(
call: &CallRequest,
tool_name: &str,
duration_ms: i64,
outcome: &Result<ToolAnswer, String>,
workspace_path: &str,
) {
let record = operation_record(call, tool_name, duration_ms, outcome);
crate::telemetry::enqueue(crate::telemetry::TelemetryRecord::ToolCalls(
crate::telemetry::ToolCallBatch {
source: crate::stats::CallSource::CommandLine,
agent_id: "cli".to_string(),
role: "cli".to_string(),
workspace: workspace_path.to_string(),
recorded_at: crate::db::now(),
records: vec![record],
},
));
}
fn operation_record(
call: &CallRequest,
tool_name: &str,
duration_ms: i64,
outcome: &Result<ToolAnswer, String>,
) -> crate::ToolCallRecord {
crate::ToolCallRecord {
tool_name: tool_name.to_string(),
arguments: crate::stats::recorded_arguments(&call.args, &call.secrets),
duration_ms,
success: matches!(outcome, Ok(answer) if !answer.partial),
error_message: match outcome {
Ok(answer) if answer.partial => Some(PARTIAL_RECORD_REASON.to_string()),
Ok(_) => None,
Err(reason) => {
let reason = crate::util::redact_secrets(reason, &call.secrets);
let reason = crate::tools::tool_failure_reason(tool_name, &reason);
(!reason.is_empty()).then_some(reason)
}
},
}
}
fn reply(outcome: Result<ToolAnswer, String>, secrets: &[String]) -> CallReply {
match outcome {
Ok(answer) => {
let text = crate::util::redact_secrets(&answer.text, secrets);
if answer.partial {
CallReply::partial(text)
} else {
CallReply::done(text)
}
}
Err(reason) => CallReply::refused(crate::util::redact_secrets(&reason, secrets)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::ipc::CallOutcome;
#[test]
fn replies_carry_the_answer_with_the_callers_secrets_removed() {
let secrets = vec!["s3cret".to_string()];
let done = reply(
Ok(ToolAnswer::done("registered, key s3cret stored")),
&secrets,
);
assert_eq!(done.outcome, CallOutcome::Done);
assert_eq!(done.answer, "registered, key [REDACTED] stored");
let partial = reply(
Ok(ToolAnswer::partial(
"added, but the activation write failed",
)),
&secrets,
);
assert_eq!(partial.outcome, CallOutcome::Partial);
let refused = reply(Err("no workspace named s3cret".to_string()), &secrets);
assert_eq!(refused.outcome, CallOutcome::Refused);
assert_eq!(refused.answer, "no workspace named [REDACTED]");
}
#[test]
fn a_recorded_row_reports_a_partial_answer_as_a_failure() {
let call = |args: serde_json::Value, secrets: Vec<String>| CallRequest {
tool: "document".to_string(),
args,
secrets,
output_dir: None,
};
let done = operation_record(
&call(serde_json::json!({}), Vec::new()),
"document",
5,
&Ok(ToolAnswer::done("a document was produced")),
);
assert!(done.success);
assert_eq!(done.error_message, None);
let partial = operation_record(
&call(serde_json::json!({}), Vec::new()),
"document",
5,
&Ok(ToolAnswer::partial("produced, but a placeholder stayed")),
);
assert!(!partial.success);
assert_eq!(
partial.error_message.as_deref(),
Some(PARTIAL_RECORD_REASON)
);
let refused = operation_record(
&call(
serde_json::json!({ "key": "s3cret" }),
vec!["s3cret".to_string()],
),
"document",
5,
&Err("nothing named s3cret".to_string()),
);
assert!(!refused.success);
assert_eq!(
refused.error_message.as_deref(),
Some("Error executing document: nothing named [REDACTED]")
);
}
#[tokio::test]
async fn execute_serves_the_workspace_ticket_and_comment_operations() {
crate::util::test::init_test_stores().await;
crate::util::test::create_test_workspace("/tmp/ws_cli_ops", "ws_cli_ops").await;
let id = crate::pipeline::board::store()
.create_ticket(&crate::pipeline::board::TicketParams {
title: "Ticket the command line reads".to_string(),
description: "created by the instance-side test".to_string(),
workspace_name: "ws_cli_ops".to_string(),
phase: crate::pipeline::board::TicketPhase::Backlog,
prerequisites: Vec::new(),
reporter: "cli-test".to_string(),
embedding: None,
priority: 1,
})
.await
.expect("create the ticket the operations act on");
crate::cli::mark_ready();
let show = execute(CallRequest {
tool: "workspace".to_string(),
args: serde_json::json!({ "action": "ticket", "ticket_id": id }),
secrets: Vec::new(),
output_dir: None,
})
.await;
assert_eq!(show.outcome, CallOutcome::Done, "answer: {}", show.answer);
assert!(
show.answer.contains("Ticket the command line reads"),
"the ticket's own display must reach the caller: {}",
show.answer
);
let note = execute(CallRequest {
tool: "workspace".to_string(),
args: serde_json::json!({
"action": "comment",
"ticket_id": id,
"content": "a note from a script",
}),
secrets: Vec::new(),
output_dir: None,
})
.await;
assert_eq!(note.outcome, CallOutcome::Done, "answer: {}", note.answer);
assert!(
note.answer.contains(&id) && note.answer.contains("assistant"),
"a note is recorded as the assistant leaves it: {}",
note.answer
);
}
#[tokio::test]
#[ignore = "requires the managed bun runtime, installed on the product's first start; runs only when explicitly invoked"]
async fn execute_produces_a_document_into_the_destination_the_caller_chose() {
async fn drive(
destination: &std::path::Path,
args: serde_json::Value,
) -> Vec<std::path::PathBuf> {
let reply = execute(CallRequest {
tool: "document".to_string(),
args,
secrets: Vec::new(),
output_dir: Some(destination.to_string_lossy().into_owned()),
})
.await;
assert_eq!(reply.outcome, CallOutcome::Done, "answer: {}", reply.answer);
reply
.answer
.split("[FILE:")
.skip(1)
.map(|rest| std::path::PathBuf::from(rest.split(']').next().unwrap_or_default()))
.inspect(|file| {
assert!(
file.is_absolute() && file.exists() && file.parent() == Some(destination),
"a produced file is named by its absolute path, is there, and lands in \
the destination the caller chose: {}",
file.display()
);
})
.collect()
}
if crate::tools::bun::bun_binary_path().is_none() {
return;
}
crate::util::test::init_test_stores().await;
crate::cli::mark_ready();
let destination = crate::util::test::test_root().join("cli-destination");
let _ = tokio::fs::remove_dir_all(&destination).await;
tokio::fs::create_dir_all(&destination)
.await
.expect("a destination of the caller's own");
let created = drive(
&destination,
serde_json::json!({ "action": "create", "format": "docx", "file_name": "from-a-script", "content": [{ "type": "paragraph", "text": "produced by a script" }] }),
)
.await;
assert_eq!(created, [destination.join("from-a-script.docx")]);
let mut pages = Vec::new();
for name in ["one", "two"] {
let pdf = drive(
&destination,
serde_json::json!({ "action": "create", "format": "pdf", "file_name": name, "content": [{ "type": "paragraph", "text": name }] }),
)
.await;
pages.push(pdf.into_iter().next().expect("create writes one pdf"));
}
let merged = drive(
&destination,
serde_json::json!({ "action": "pdf_merge", "paths": [pages[0].to_string_lossy(), pages[1].to_string_lossy()], "file_name": "merged" }),
)
.await;
let merged = merged.first().expect("a merge writes one file");
let parts = drive(
&destination,
serde_json::json!({ "action": "pdf_split", "path": merged.to_string_lossy(), "pages": ["1", "2"], "file_name": "page" }),
)
.await;
assert_eq!(parts.len(), 2, "one file per page entry: {parts:?}");
let cropped = drive(
&destination,
serde_json::json!({
"action": "pdf_crop",
"path": parts[0].to_string_lossy(),
"area": { "x": 0.0, "y": 0.0, "width": 0.5, "height": 1.0 },
"file_name": "cropped",
}),
)
.await;
assert_eq!(cropped, [destination.join("cropped.pdf")]);
let resized = drive(
&destination,
serde_json::json!({
"action": "pdf_resize",
"path": cropped[0].to_string_lossy(),
"width": 300.0,
"height": 200.0,
"file_name": "resized",
}),
)
.await;
assert_eq!(resized, [destination.join("resized.pdf")]);
let agents = crate::users::personal_workspace_path(crate::users::ADMIN_USER_NAME)
.join("generated")
.join("from-a-script.docx");
assert!(
tokio::fs::metadata(&agents).await.is_err(),
"a command-line call must not write into the agent's own generated directory"
);
}
#[tokio::test]
#[ignore = "requires the managed bun runtime, installed on the product's first start; runs only when explicitly invoked"]
async fn execute_reports_a_shortfall_the_document_tool_found() {
if crate::tools::bun::bun_binary_path().is_none() {
return;
}
crate::util::test::init_test_stores().await;
crate::cli::mark_ready();
let destination = crate::util::test::test_root().join("cli-destination-shortfall");
let _ = tokio::fs::remove_dir_all(&destination).await;
tokio::fs::create_dir_all(&destination)
.await
.expect("a destination of the caller's own");
let sample = destination.join("sample.docx");
let created = execute(CallRequest {
tool: "document".to_string(),
args: serde_json::json!({
"action": "create",
"format": "docx",
"file_name": "sample",
"content": [{ "type": "paragraph", "text": "Hello, {name}! {unfilled}" }],
}),
secrets: Vec::new(),
output_dir: Some(destination.to_string_lossy().into_owned()),
})
.await;
assert_eq!(
created.outcome,
CallOutcome::Done,
"answer: {}",
created.answer
);
assert!(tokio::fs::metadata(&sample).await.is_ok());
let filled = execute(CallRequest {
tool: "document".to_string(),
args: serde_json::json!({
"action": "fill_template",
"path": sample.to_string_lossy(),
"values": { "name": "Ada" },
"file_name": "filled",
}),
secrets: Vec::new(),
output_dir: Some(destination.to_string_lossy().into_owned()),
})
.await;
assert_eq!(
filled.outcome,
CallOutcome::Partial,
"a shortfall must reach the caller as one: {}",
filled.answer
);
assert!(
filled.answer.contains("no values for these placeholders"),
"the product's own answer names the part that did not succeed: {}",
filled.answer
);
}
#[tokio::test]
async fn execute_renders_a_file_into_the_destination_the_caller_chose() {
crate::util::test::init_test_stores().await;
crate::cli::mark_ready();
let destination = crate::util::test::test_root().join("cli-rasterize");
let _ = tokio::fs::remove_dir_all(&destination).await;
tokio::fs::create_dir_all(&destination)
.await
.expect("a destination of the caller's own");
let drawing = destination.join("drawing.svg");
tokio::fs::write(
&drawing,
r##"<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8">
<rect width="12" height="8" fill="#ff0000"/></svg>"##,
)
.await
.expect("a drawing at hand");
let reply = execute(CallRequest {
tool: "rasterize".to_string(),
args: serde_json::json!({ "path": drawing.to_string_lossy() }),
secrets: Vec::new(),
output_dir: Some(destination.to_string_lossy().into_owned()),
})
.await;
assert_eq!(reply.outcome, CallOutcome::Done, "answer: {}", reply.answer);
let picture = marked_picture(&reply.answer);
assert!(picture.is_absolute(), "{}", picture.display());
assert_eq!(picture.parent(), Some(destination.as_path()));
assert_eq!(
tokio::fs::read(&picture)
.await
.expect("the picture is there")[..8],
[0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a]
);
assert!(
tokio::fs::metadata(
crate::users::personal_workspace_path(crate::users::ADMIN_USER_NAME)
.join("generated")
.join(picture.file_name().expect("the picture has a name"))
)
.await
.is_err(),
"a command-line call must not write into the agent's own generated directory"
);
if crate::tools::bun::bun_binary_path().is_none() {
return;
}
let created = execute(CallRequest {
tool: "document".to_string(),
args: serde_json::json!({
"action": "create",
"format": "pdf",
"file_name": "page-source",
"content": [{ "type": "paragraph", "text": "a page to render" }],
}),
secrets: Vec::new(),
output_dir: Some(destination.to_string_lossy().into_owned()),
})
.await;
let source = created
.answer
.split("[FILE:")
.nth(1)
.and_then(|rest| rest.split(']').next())
.map_or_else(
|| panic!("the PDF is named: {}", created.answer),
std::path::PathBuf::from,
);
let page = execute(CallRequest {
tool: "rasterize".to_string(),
args: serde_json::json!({ "path": source.to_string_lossy(), "page": 1 }),
secrets: Vec::new(),
output_dir: Some(destination.to_string_lossy().into_owned()),
})
.await;
assert_eq!(page.outcome, CallOutcome::Done, "answer: {}", page.answer);
let page_picture = marked_picture(&page.answer);
assert_eq!(page_picture.parent(), Some(destination.as_path()));
assert_eq!(
tokio::fs::read(&page_picture)
.await
.expect("the page's picture is there")[..8],
[0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a]
);
}
#[tokio::test]
async fn execute_reads_a_document_into_the_destination_the_caller_chose() {
crate::util::test::init_test_stores().await;
crate::cli::mark_ready();
let destination = crate::util::test::test_root().join("cli-read-destination");
let _ = tokio::fs::remove_dir_all(&destination).await;
tokio::fs::create_dir_all(&destination)
.await
.expect("a destination of the caller's own");
let source = destination.join("report.docx");
tokio::fs::write(
&source,
crate::ooxml::test_fixtures::zip_fixture(&[(
"word/document.xml",
crate::ooxml::test_fixtures::DOCX_BODY,
)]),
)
.await
.expect("a document at hand");
let reply = execute(CallRequest {
tool: "document".to_string(),
args: serde_json::json!({
"action": crate::cli::DOCUMENT_READ,
"path": source.to_string_lossy(),
}),
secrets: Vec::new(),
output_dir: Some(destination.to_string_lossy().into_owned()),
})
.await;
assert_eq!(reply.outcome, CallOutcome::Done, "answer: {}", reply.answer);
let file = reply
.answer
.split(crate::tools::document::FILE_MARKER)
.nth(1)
.and_then(|rest| rest.split(']').next())
.map_or_else(
|| panic!("the reading names its file: {}", reply.answer),
std::path::PathBuf::from,
);
assert!(
file.is_absolute() && file.starts_with(&destination),
"the file lands under the destination the caller chose: {}",
file.display()
);
assert!(file.exists(), "the named file is there: {}", file.display());
let text = tokio::fs::read_to_string(&file)
.await
.expect("the reading's text");
assert!(
text.contains("First paragraph") && text.contains("Second paragraph"),
"the reader's extracted text is written as the text: {text}"
);
}
fn marked_picture(answer: &str) -> std::path::PathBuf {
std::path::PathBuf::from(
answer
.strip_prefix("[IMAGE:")
.and_then(|marker| marker.split(']').next())
.unwrap_or_else(|| panic!("the answer carries the marker: {answer}")),
)
}
}