(
state: Arc<Mutex<ScriptVmState>>,
tool: String,
args_json: String,
)
| 458 | } |
| 459 | |
| 460 | async fn execute_host_tool_json( |
| 461 | state: Arc<Mutex<ScriptVmState>>, |
| 462 | tool: String, |
| 463 | args_json: String, |
| 464 | ) -> rquickjs::Result<String> { |
| 465 | let args = serde_json::from_str(&args_json).map_err(|err| { |
| 466 | JsError::new_from_js_message("string", "object", format!("invalid tool args JSON: {err}")) |
| 467 | })?; |
| 468 | let (registry, ctx, max_output_bytes, outer) = { |
| 469 | let mut script = state.lock().await; |
| 470 | if !script.allowed_tools.contains(&tool) { |
| 471 | return Err(JsError::new_from_js_message( |
| 472 | "tool", |
| 473 | "allowed tool", |
| 474 | format!("tool '{tool}' is not allowed for this PTC script"), |
| 475 | )); |
| 476 | } |
| 477 | script.tool_calls += 1; |
| 478 | if script.tool_calls > script.max_tool_calls { |
| 479 | return Err(JsError::new_from_js_message( |
| 480 | "tool call", |
| 481 | "limited tool call", |
| 482 | format!("PTC script exceeded maxToolCalls={}", script.max_tool_calls), |
| 483 | )); |
| 484 | } |
| 485 | ( |
| 486 | Arc::clone(&script.registry), |
| 487 | script.ctx.clone(), |
| 488 | script.max_output_bytes, |
| 489 | script.outer.clone(), |
| 490 | ) |
| 491 | }; |
| 492 | |
| 493 | // Run the tool on the OUTER multi-threaded runtime (not this nested |
| 494 | // single-thread VM runtime) so delegation tools can spawn child agents that |
| 495 | // actually run in parallel — `ctx.tool("parallel_task", …)` now fans out. |
| 496 | let tool_for_spawn = tool.clone(); |
| 497 | let result = outer |
| 498 | .spawn(async move { |
| 499 | registry |
| 500 | .execute_with_context(&tool_for_spawn, &args, &ctx) |
| 501 | .await |
| 502 | }) |
| 503 | .await |
| 504 | .map_err(|err| JsError::new_from_js_message("tool", "spawn", err.to_string()))? |
| 505 | .map_err(|err| JsError::new_from_js_message("tool", "result", err.to_string()))?; |
| 506 | let mut output = result.output; |
| 507 | if output.len() > max_output_bytes { |
| 508 | output = truncate_utf8(&output, max_output_bytes).to_string(); |
| 509 | } |
| 510 | let success = result.exit_code == 0; |
| 511 | let metadata = result.metadata.clone(); |
| 512 | let exit_code = result.exit_code; |
| 513 | let name = result.name; |
| 514 | |
| 515 | { |
| 516 | let mut script = state.lock().await; |
| 517 | script.records.push(ScriptCallRecord { |
nothing calls this directly
no test coverage detected