Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
49 changes: 40 additions & 9 deletions core/src/tools/program_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -407,6 +408,10 @@ struct ScriptVmState {
max_output_bytes: usize,
tool_calls: usize,
records: Vec<ScriptCallRecord>,
/// 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 {
Expand Down Expand Up @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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, &registry);
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")));
Expand Down
4 changes: 2 additions & 2 deletions sdk/node/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"] }
Expand Down
14 changes: 7 additions & 7 deletions sdk/node/examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions sdk/node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions sdk/node/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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"
}
}
2 changes: 1 addition & 1 deletion sdk/python-bootstrap/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>`
# 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"}
Expand Down
2 changes: 1 addition & 1 deletion sdk/python-bootstrap/src/a3s_code/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions sdk/python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down
Loading