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
4 changes: 2 additions & 2 deletions contracts/capability-parity.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@
"cwd": "ScriptedTool intentionally uses a logic-only shell without filesystem-backed cwd configuration.",
"custom_builtins": "Only registered ToolDef callbacks become commands; arbitrary Bash builtins are not accepted.",
"host_call_suspension": "ScriptedTool invokes registered callbacks directly and exposes no suspended execution handle.",
"network_policy": "The logic-only orchestration shell has no network builtins.",
"transport_hooks": "The logic-only orchestration shell has no HTTP transport.",
"network_policy": "The logic-only shell has no network builtins.",
"transport_hooks": "The logic-only shell has no HTTP transport.",
"virtual_filesystem": "Filesystem commands and redirections are intentionally rejected.",
"mounts": "Filesystem commands and redirections are intentionally rejected.",
"snapshots": "Each execution uses a fresh logic-only shell.",
Expand Down
20 changes: 18 additions & 2 deletions crates/bashkit/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,10 @@ impl<'a> Context<'a> {
}

/// Run async boundary work under this request's cancellation/lifecycle gate.
#[cfg(any(feature = "http_client", feature = "scripted_tool"))]
pub(crate) async fn run_budgeted<F>(&self, future: F) -> Result<F::Output>
///
/// Host callbacks invoked from a builtin should run through this so a
/// cancelled or over-budget request drops the callback future.
pub async fn run_budgeted<F>(&self, future: F) -> Result<F::Output>
where
F: std::future::Future,
{
Expand All @@ -816,6 +818,20 @@ impl<'a> Context<'a> {
.and_then(|shell| shell.execution_extensions.get::<T>())
}

/// Remaining wall-clock budget of the current `exec*` call, if limited.
pub fn remaining_deadline(&self) -> Option<std::time::Duration> {
self.execution_extension::<ExecutionDeadline>()?
.try_with(ExecutionDeadline::remaining)
.ok()
}

#[cfg(any(feature = "python", feature = "typescript"))]
pub(crate) fn execution_extensions(&self) -> Option<std::sync::Arc<ExecutionExtensions>> {
self.shell
.as_ref()
.map(|shell| shell.execution_extensions.clone())
}

/// Bind a host value to the current execution lease.
pub fn execution_capability<T>(&self, value: T) -> Option<crate::ExecutionCapability<T>>
where
Expand Down
13 changes: 7 additions & 6 deletions crates/bashkit/src/builtins/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,11 @@ impl Python {
self
}

pub(crate) fn with_external_handler_and_prelude(
/// Like [`Self::with_external_handler`], plus a Python prelude evaluated
/// before user code (e.g. classes that route `tools.get_user(...)` to an
/// external function). Lets embedders expose namespaced host APIs
/// without core knowing their shape.
pub fn with_external_handler_and_prelude(
mut self,
names: Vec<String>,
handler: PythonExternalFnHandler,
Expand Down Expand Up @@ -576,11 +580,8 @@ impl Builtin for Python {
.execution_budget()
.and_then(|budget| budget.try_with(Clone::clone).ok());
let future = run_python(&code, &filename, ctx.fs.clone(), ctx.cwd, ctx.env, policy);
#[cfg(feature = "scripted_tool")]
let future = crate::tool_registry::scope_runtime_call(
crate::tool_registry::ToolCallScope::from_context(&ctx),
future,
);
// Host functions read the request through RuntimeCallContext::current().
let future = crate::runtime_call::scope(&ctx, future);
match execution_budget {
Some(budget) => budget.run(future).await?,
None => future.await,
Expand Down
13 changes: 7 additions & 6 deletions crates/bashkit/src/builtins/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,11 @@ impl TypeScriptExtension {
}
}

pub(crate) fn with_external_handler_and_prelude(
/// Like [`Self::with_external_handler`], plus a source prelude evaluated
/// before user code and `(from, to)` call rewrites (e.g. `tools.get_user`
/// to an external function name). Lets embedders expose namespaced host
/// APIs without core knowing their shape.
pub fn with_external_handler_and_prelude(
limits: TypeScriptLimits,
external_fns: Vec<String>,
handler: TypeScriptExternalFnHandler,
Expand Down Expand Up @@ -835,11 +839,8 @@ impl Builtin for TypeScript {
ctx.execution_budget()
.and_then(|budget| budget.try_with(Clone::clone).ok()),
);
#[cfg(feature = "scripted_tool")]
let future = crate::tool_registry::scope_runtime_call(
crate::tool_registry::ToolCallScope::from_context(&ctx),
future,
);
// Host functions read the request through RuntimeCallContext::current().
let future = crate::runtime_call::scope(&ctx, future);
match execution_budget {
Some(budget) => budget.run(future).await?,
None => future.await,
Expand Down
6 changes: 4 additions & 2 deletions crates/bashkit/src/execution_capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,10 @@ impl<T> ExecutionCapability<T> {
self.scope.cleanup_report()
}

#[cfg(feature = "scripted_tool")]
pub(crate) fn derive<U>(&self, value: U) -> Option<ExecutionCapability<U>>
/// Bind another host value to the same execution lease as this handle.
///
/// Returns `None` once the originating execution has ended.
pub fn derive<U>(&self, value: U) -> Option<ExecutionCapability<U>>
where
U: Send + Sync + 'static,
{
Expand Down
74 changes: 0 additions & 74 deletions crates/bashkit/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,83 +430,9 @@ pub use search::{
pub use traits::{DirEntry, FileSystem, FileSystemExt, FileType, Metadata, fs_errors};

use crate::error::Result;
use crate::time_compat::SystemTime;
use std::io::{Error as IoError, ErrorKind};
use std::path::{Path, PathBuf};

/// Filesystem implementation for logic-only shells.
///
/// Important decision: ScriptedTool code mode still needs an `Arc<dyn FileSystem>`
/// because the interpreter and builtin context require one, but every real
/// operation is rejected so scripts cannot use VFS as storage or input.
pub(crate) struct DisabledFs;

fn disabled_fs_error() -> crate::Error {
IoError::new(ErrorKind::PermissionDenied, "filesystem access disabled").into()
}

#[async_trait::async_trait]
impl FileSystemExt for DisabledFs {}

#[async_trait::async_trait]
impl FileSystem for DisabledFs {
async fn read_file(&self, _path: &Path) -> Result<Vec<u8>> {
Err(disabled_fs_error())
}

async fn write_file(&self, _path: &Path, _content: &[u8]) -> Result<()> {
Err(disabled_fs_error())
}

async fn append_file(&self, _path: &Path, _content: &[u8]) -> Result<()> {
Err(disabled_fs_error())
}

async fn mkdir(&self, _path: &Path, _recursive: bool) -> Result<()> {
Err(disabled_fs_error())
}

async fn remove(&self, _path: &Path, _recursive: bool) -> Result<()> {
Err(disabled_fs_error())
}

async fn stat(&self, _path: &Path) -> Result<Metadata> {
Err(disabled_fs_error())
}

async fn read_dir(&self, _path: &Path) -> Result<Vec<DirEntry>> {
Err(disabled_fs_error())
}

async fn exists(&self, _path: &Path) -> Result<bool> {
Ok(false)
}

async fn rename(&self, _from: &Path, _to: &Path) -> Result<()> {
Err(disabled_fs_error())
}

async fn copy(&self, _from: &Path, _to: &Path) -> Result<()> {
Err(disabled_fs_error())
}

async fn symlink(&self, _target: &Path, _link: &Path) -> Result<()> {
Err(disabled_fs_error())
}

async fn read_link(&self, _path: &Path) -> Result<PathBuf> {
Err(disabled_fs_error())
}

async fn chmod(&self, _path: &Path, _mode: u32) -> Result<()> {
Err(disabled_fs_error())
}

async fn set_modified_time(&self, _path: &Path, _time: SystemTime) -> Result<()> {
Err(disabled_fs_error())
}
}

/// Join `child` onto the VFS path `base`, always using `/` as the separator.
///
/// `PathBuf::join`/`push` insert the *host* separator (`\` on Windows) whenever
Expand Down
Loading
Loading