diff --git a/Cargo.lock b/Cargo.lock index ae6d840..0d43fa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ dependencies = [ [[package]] name = "a3s-code-core" -version = "4.2.5" +version = "4.2.6" dependencies = [ "a3s-acl 0.2.0", "a3s-ahp", diff --git a/core/Cargo.toml b/core/Cargo.toml index 1b62097..5821b1c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a3s-code-core" -version = "4.2.5" +version = "4.2.6" edition = "2021" authors = ["A3S Lab Team"] license = "MIT" diff --git a/core/src/tools/program_tool.rs b/core/src/tools/program_tool.rs index 562c0a7..2985a21 100644 --- a/core/src/tools/program_tool.rs +++ b/core/src/tools/program_tool.rs @@ -213,12 +213,9 @@ fn script_allowed_tools(args: &serde_json::Value, registry: &ToolRegistry) -> Ha .unwrap_or_else(|| registry.list().into_iter().collect()); allowed.remove("program"); - // Delegation tools can't run inside a PTC script: child agents need the - // multi-threaded session runtime, but the script executes on a nested - // single-thread runtime where they can't fan out. Force the model to call - // them directly instead of `ctx.tool("parallel_task", ...)`. - allowed.remove("task"); - allowed.remove("parallel_task"); + // `task`/`parallel_task` ARE allowed in PTC scripts now: host tool calls run + // on the outer multi-threaded runtime (see execute_host_tool_json), so + // `ctx.tool("parallel_task", …)` fans out child agents in parallel. allowed } @@ -277,6 +274,9 @@ async fn run_quickjs_script( .max_output_bytes .unwrap_or(DEFAULT_SCRIPT_MAX_OUTPUT_BYTES); let executable_source = script_source_with_host_entrypoint(source)?; + // Captured on the outer multi-threaded runtime (we're async here, before the + // VM's nested single-thread runtime is built) so host tool calls fan out. + let outer = tokio::runtime::Handle::current(); let state = Arc::new(Mutex::new(ScriptVmState { registry, ctx, @@ -285,6 +285,7 @@ async fn run_quickjs_script( max_output_bytes, tool_calls: 0, records: Vec::new(), + outer, })); let vm_state = Arc::clone(&state); @@ -407,6 +408,10 @@ struct ScriptVmState { max_output_bytes: usize, tool_calls: usize, records: Vec, + /// Handle to the OUTER multi-threaded session runtime. The script VM runs on + /// a nested single-thread runtime; host tool calls are dispatched here so + /// delegation tools (`parallel_task`/`task`) can actually fan out children. + outer: tokio::runtime::Handle, } fn embedded_script_bootstrap(inputs_json: &str) -> String { @@ -447,7 +452,7 @@ async fn execute_host_tool_json( let args = serde_json::from_str(&args_json).map_err(|err| { JsError::new_from_js_message("string", "object", format!("invalid tool args JSON: {err}")) })?; - let (registry, ctx, max_output_bytes) = { + let (registry, ctx, max_output_bytes, outer) = { let mut script = state.lock().await; if !script.allowed_tools.contains(&tool) { return Err(JsError::new_from_js_message( @@ -468,12 +473,22 @@ async fn execute_host_tool_json( Arc::clone(&script.registry), script.ctx.clone(), script.max_output_bytes, + script.outer.clone(), ) }; - let result = registry - .execute_with_context(&tool, &args, &ctx) + // Run the tool on the OUTER multi-threaded runtime (not this nested + // single-thread VM runtime) so delegation tools can spawn child agents that + // actually run in parallel — `ctx.tool("parallel_task", …)` now fans out. + let tool_for_spawn = tool.clone(); + let result = outer + .spawn(async move { + registry + .execute_with_context(&tool_for_spawn, &args, &ctx) + .await + }) .await + .map_err(|err| JsError::new_from_js_message("tool", "spawn", err.to_string()))? .map_err(|err| JsError::new_from_js_message("tool", "result", err.to_string()))?; let mut output = result.output; if output.len() > max_output_bytes { @@ -676,6 +691,22 @@ mod tests { assert!(!allowed.contains("program")); } + #[test] + fn program_tool_allows_delegation_tools_in_scripts() { + // Delegation tools are allowed in PTC scripts again (host tool calls run + // on the outer multi-threaded runtime, so they fan out). Only `program` + // stays stripped (no nested PTC recursion). + let registry = ToolRegistry::new(PathBuf::from("/tmp")); + let args = serde_json::json!({ + "allowed_tools": ["parallel_task", "task", "program", "echo"] + }); + let allowed = script_allowed_tools(&args, ®istry); + assert!(allowed.contains("parallel_task")); + assert!(allowed.contains("task")); + assert!(allowed.contains("echo")); + assert!(!allowed.contains("program")); + } + #[tokio::test] async fn program_tool_source_uses_default_all_registered_tools() { let registry = Arc::new(ToolRegistry::new(PathBuf::from("/tmp"))); diff --git a/sdk/node/Cargo.toml b/sdk/node/Cargo.toml index 0f4cea9..5b706e6 100644 --- a/sdk/node/Cargo.toml +++ b/sdk/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a3s-code-node" -version = "4.2.5" +version = "4.2.6" edition = "2021" authors = ["A3S Lab Team"] license = "MIT" @@ -11,7 +11,7 @@ description = "A3S Code Node.js bindings - Native addon via napi-rs" crate-type = ["cdylib"] [dependencies] -a3s-code-core = { version = "4.2.5", path = "../../core", features = ["ahp", "s3", "serve"] } +a3s-code-core = { version = "4.2.6", path = "../../core", features = ["ahp", "s3", "serve"] } napi = { version = "2", features = ["async", "napi6", "serde-json"] } napi-derive = "2" tokio = { version = "1.35", features = ["full"] } diff --git a/sdk/node/examples/package-lock.json b/sdk/node/examples/package-lock.json index 57afdfd..f46b45d 100644 --- a/sdk/node/examples/package-lock.json +++ b/sdk/node/examples/package-lock.json @@ -18,7 +18,7 @@ }, "..": { "name": "@a3s-lab/code", - "version": "4.2.5", + "version": "4.2.6", "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2", @@ -27,12 +27,12 @@ "typescript": "^5.9.3" }, "optionalDependencies": { - "@a3s-lab/code-darwin-arm64": "4.2.5", - "@a3s-lab/code-linux-arm64-gnu": "4.2.5", - "@a3s-lab/code-linux-arm64-musl": "4.2.5", - "@a3s-lab/code-linux-x64-gnu": "4.2.5", - "@a3s-lab/code-linux-x64-musl": "4.2.5", - "@a3s-lab/code-win32-x64-msvc": "4.2.5" + "@a3s-lab/code-darwin-arm64": "4.2.6", + "@a3s-lab/code-linux-arm64-gnu": "4.2.6", + "@a3s-lab/code-linux-arm64-musl": "4.2.6", + "@a3s-lab/code-linux-x64-gnu": "4.2.6", + "@a3s-lab/code-linux-x64-musl": "4.2.6", + "@a3s-lab/code-win32-x64-msvc": "4.2.6" } }, "node_modules/@a3s-lab/code": { diff --git a/sdk/node/package-lock.json b/sdk/node/package-lock.json index 0f9296f..070b030 100644 --- a/sdk/node/package-lock.json +++ b/sdk/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@a3s-lab/code", - "version": "4.2.5", + "version": "4.2.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@a3s-lab/code", - "version": "4.2.5", + "version": "4.2.6", "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2", @@ -15,12 +15,12 @@ "typescript": "^5.9.3" }, "optionalDependencies": { - "@a3s-lab/code-darwin-arm64": "4.2.5", - "@a3s-lab/code-linux-arm64-gnu": "4.2.5", - "@a3s-lab/code-linux-arm64-musl": "4.2.5", - "@a3s-lab/code-linux-x64-gnu": "4.2.5", - "@a3s-lab/code-linux-x64-musl": "4.2.5", - "@a3s-lab/code-win32-x64-msvc": "4.2.5" + "@a3s-lab/code-darwin-arm64": "4.2.6", + "@a3s-lab/code-linux-arm64-gnu": "4.2.6", + "@a3s-lab/code-linux-arm64-musl": "4.2.6", + "@a3s-lab/code-linux-x64-gnu": "4.2.6", + "@a3s-lab/code-linux-x64-musl": "4.2.6", + "@a3s-lab/code-win32-x64-msvc": "4.2.6" } }, "node_modules/@a3s-lab/code-darwin-arm64": { diff --git a/sdk/node/package.json b/sdk/node/package.json index 0b57f8d..812d022 100644 --- a/sdk/node/package.json +++ b/sdk/node/package.json @@ -1,6 +1,6 @@ { "name": "@a3s-lab/code", - "version": "4.2.5", + "version": "4.2.6", "description": "A3S Code - Native Node.js bindings for the coding-agent runtime", "main": "index.js", "types": "index.d.ts", @@ -43,11 +43,11 @@ "test:helpers": "node test-helpers.mjs" }, "optionalDependencies": { - "@a3s-lab/code-darwin-arm64": "4.2.5", - "@a3s-lab/code-linux-x64-gnu": "4.2.5", - "@a3s-lab/code-linux-x64-musl": "4.2.5", - "@a3s-lab/code-linux-arm64-gnu": "4.2.5", - "@a3s-lab/code-linux-arm64-musl": "4.2.5", - "@a3s-lab/code-win32-x64-msvc": "4.2.5" + "@a3s-lab/code-darwin-arm64": "4.2.6", + "@a3s-lab/code-linux-x64-gnu": "4.2.6", + "@a3s-lab/code-linux-x64-musl": "4.2.6", + "@a3s-lab/code-linux-arm64-gnu": "4.2.6", + "@a3s-lab/code-linux-arm64-musl": "4.2.6", + "@a3s-lab/code-win32-x64-msvc": "4.2.6" } } diff --git a/sdk/python-bootstrap/pyproject.toml b/sdk/python-bootstrap/pyproject.toml index 3780a7b..7b22a98 100644 --- a/sdk/python-bootstrap/pyproject.toml +++ b/sdk/python-bootstrap/pyproject.toml @@ -7,7 +7,7 @@ name = "a3s-code" # Keep in sync with crates/code core release. The bootstrap loader fetches # the matching native wheel from `https://github.com/AI45Lab/Code/releases/tag/v` # at import time. -version = "4.2.5" +version = "4.2.6" description = "A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases" readme = "README.md" license = {text = "MIT"} diff --git a/sdk/python-bootstrap/src/a3s_code/_bootstrap.py b/sdk/python-bootstrap/src/a3s_code/_bootstrap.py index cd05683..3de2cdc 100644 --- a/sdk/python-bootstrap/src/a3s_code/_bootstrap.py +++ b/sdk/python-bootstrap/src/a3s_code/_bootstrap.py @@ -31,7 +31,7 @@ # Version is the bootstrap's own version, which equals the matching native # wheel version on GH Releases. Bumped by the release workflow. -__version__ = "4.2.5" +__version__ = "4.2.6" _DEFAULT_BASE_URL = "https://github.com/AI45Lab/Code/releases/download" _REQUEST_TIMEOUT_S = 120 diff --git a/sdk/python/Cargo.toml b/sdk/python/Cargo.toml index 158df85..159e066 100644 --- a/sdk/python/Cargo.toml +++ b/sdk/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a3s-code-py" -version = "4.2.5" +version = "4.2.6" edition = "2021" authors = ["A3S Lab Team"] license = "MIT" @@ -12,7 +12,7 @@ name = "a3s_code" crate-type = ["cdylib"] [dependencies] -a3s-code-core = { version = "4.2.5", path = "../../core", features = ["ahp", "s3", "serve"] } +a3s-code-core = { version = "4.2.6", path = "../../core", features = ["ahp", "s3", "serve"] } pyo3 = "0.23" tokio = { version = "1.35", features = ["full"] } serde_json = "1.0" diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 6c128a3..ef82b43 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "a3s-code" -version = "4.2.5" +version = "4.2.6" description = "A3S Code - Native Python bindings for the coding-agent runtime" readme = "README.md" license = {text = "MIT"}