mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-30 08:17:53 +00:00
feat(ptc): fix WASM wiring, add ptc_script tool, nesting depth, Docker SDK
- Fix WASM tool_invoke production wiring: change tool_executor to a shared Arc<std::sync::RwLock> slot with lazy resolution so WASM tools registered during build_all() can access the executor set afterward - Add nesting_depth field to ToolCallRequest and propagate it through the orchestrator's tool_call_handler into JobContext - Add ptc_script built-in tool: runs Python scripts with ironclaw_tools SDK pre-imported, env-scrubbed subprocess, structured output support - Copy Python SDK into Docker worker image at dist-packages path Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
@@ -516,8 +516,11 @@ pub struct WasmToolWrapper {
|
||||
secrets_store: Option<Arc<dyn SecretsStore + Send + Sync>>,
|
||||
/// OAuth refresh configuration for auto-refreshing expired tokens.
|
||||
oauth_refresh: Option<OAuthRefreshConfig>,
|
||||
/// Tool executor for programmatic tool calling from within WASM tools.
|
||||
/// Direct tool executor reference (for tests that wire it explicitly).
|
||||
tool_executor: Option<Arc<ToolExecutor>>,
|
||||
/// Shared slot for lazy executor resolution (production path).
|
||||
/// Reads happen inside `spawn_blocking`, so this uses `std::sync::RwLock`.
|
||||
tool_executor_slot: Option<Arc<std::sync::RwLock<Option<Arc<ToolExecutor>>>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -607,6 +610,7 @@ impl WasmToolWrapper {
|
||||
secrets_store: None,
|
||||
oauth_refresh: None,
|
||||
tool_executor: None,
|
||||
tool_executor_slot: None,
|
||||
};
|
||||
wrapper.append_schema_hint_if_permissive();
|
||||
wrapper
|
||||
@@ -663,15 +667,28 @@ impl WasmToolWrapper {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the tool executor for programmatic tool calling.
|
||||
/// Set the tool executor for programmatic tool calling (direct reference).
|
||||
///
|
||||
/// When set, the WASM `tool_invoke` host function can call other
|
||||
/// registered tools synchronously via a bridged resolver closure.
|
||||
/// Prefer `with_tool_executor_slot()` for production use.
|
||||
pub fn with_tool_executor(mut self, executor: Arc<ToolExecutor>) -> Self {
|
||||
self.tool_executor = Some(executor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the shared tool executor slot for lazy resolution.
|
||||
///
|
||||
/// The executor is read from this slot at execution time, allowing
|
||||
/// it to be set after tool registration (production startup order).
|
||||
pub fn with_tool_executor_slot(
|
||||
mut self,
|
||||
slot: Arc<std::sync::RwLock<Option<Arc<ToolExecutor>>>>,
|
||||
) -> Self {
|
||||
self.tool_executor_slot = Some(slot);
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the resource limits for this tool.
|
||||
pub fn limits(&self) -> &ResourceLimits {
|
||||
&self.prepared.limits
|
||||
@@ -909,10 +926,22 @@ impl Tool for WasmToolWrapper {
|
||||
// Serialize context for WASM
|
||||
let context_json = serde_json::to_string(ctx).ok();
|
||||
|
||||
// Resolve the tool executor: direct reference takes priority, then shared slot.
|
||||
let resolved_executor: Option<Arc<ToolExecutor>> = self
|
||||
.tool_executor
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
self.tool_executor_slot
|
||||
.as_ref()
|
||||
.and_then(|slot| slot.read().ok())
|
||||
.and_then(|guard| guard.clone())
|
||||
});
|
||||
|
||||
// Build a tool resolver closure if we have a tool executor.
|
||||
// The resolver creates a single-threaded tokio runtime (same pattern
|
||||
// as http_request) to bridge the sync WASM callback to async tool execution.
|
||||
let tool_resolver: Option<ToolResolver> = self.tool_executor.as_ref().map(|executor| {
|
||||
let tool_resolver: Option<ToolResolver> = resolved_executor.as_ref().map(|executor| {
|
||||
let executor = Arc::clone(executor);
|
||||
let user_id = ctx.user_id.clone();
|
||||
Arc::new(move |name: &str, params: serde_json::Value, depth: u32| {
|
||||
@@ -958,9 +987,10 @@ impl Tool for WasmToolWrapper {
|
||||
description,
|
||||
schemas,
|
||||
credentials,
|
||||
secrets_store: None, // Not needed in blocking task
|
||||
oauth_refresh: None, // Already used above for pre-refresh
|
||||
tool_executor: None, // Resolver closure captures the executor
|
||||
secrets_store: None, // Not needed in blocking task
|
||||
oauth_refresh: None, // Already used above for pre-refresh
|
||||
tool_executor: None, // Resolver closure captures the executor
|
||||
tool_executor_slot: None, // Resolver closure captures the executor
|
||||
};
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
|
||||
Reference in New Issue
Block a user