feat(code): cli sandboxes, enterprise timeouts, secrets projections, resolver lift, workflow exec cancellations (#6247)

* feat(code): cli sandboxes, enterprise timeouts, secrets projections, resolver lift

* fix(execution): harden compatibility and secret diagnostics

* fix(execution): harden generated JavaScript literals

* fix(execution): align timeout cleanup semantics

* fix(tables): decouple stale job cleanup

* fix(execution): drain stale workflow backlog

* test(sandbox): make deadline assertions timing-safe

* fix(execution): lock cleanup candidate batches

* fix(execution): preserve cleanup failure metrics

* cancel route fixes

* separate out mship template and func template

* fix

* fix(execution): harden secret projection and block runs

* fix(workflow): validate draft execution state

* run from block ui disabling

* feat(copilot): expose Sim sandboxes to mothership

* feat(copilot): expose sandbox capability catalog in VFS

* Updates

* fix legacy logs showing up

* fix(copilot): keep sandbox config visible

* fix model provenance issues

* fix lint'

* more lint

* more

* test(files): align provenance copy query order

* consolidate migrations, rollout compat

* integration projections

* update skills

* fix

* add provenance linters

* fix: address review and compatibility regressions

* fix: make tool boundary audit Bun 1.3 compatible

---------

Co-authored-by: Siddharth Ganesan <siddharthganesan@gmail.com>
This commit is contained in:
Vikhyath Mondreti
2026-08-05 19:22:04 -07:00
committed by GitHub
co-authored by Siddharth Ganesan
parent 5baa7a41ec
commit 117fe3137b
826 changed files with 95636 additions and 7877 deletions
+10 -3
View File
@@ -43,7 +43,7 @@ SimStudioClient(api_key: str, base_url: str = "https://sim.ai")
#### Methods
##### execute_workflow(workflow_id, input=None, *, timeout=30.0, stream=None, selected_outputs=None, async_execution=None)
##### execute_workflow(workflow_id, input=None, *, timeout=30.0, stream=None, selected_outputs=None, async_execution=None, execution_timeout_seconds=None)
Execute a workflow with optional input data.
@@ -55,7 +55,13 @@ result = client.execute_workflow("workflow-id", {"message": "Hello, world!"})
result = client.execute_workflow("workflow-id", "NVDA")
# With options (keyword-only arguments)
result = client.execute_workflow("workflow-id", {"message": "Hello"}, timeout=60.0)
result = client.execute_workflow(
"workflow-id",
{"message": "Hello"},
timeout=60.0,
async_execution=True,
execution_timeout_seconds=3600,
)
```
**Parameters:**
@@ -65,6 +71,7 @@ result = client.execute_workflow("workflow-id", {"message": "Hello"}, timeout=60
- `stream` (bool, keyword-only): Enable streaming responses
- `selected_outputs` (list, keyword-only): Block outputs to stream (e.g., `["agent1.content"]`)
- `async_execution` (bool, keyword-only): Execute asynchronously and return execution ID
- `execution_timeout_seconds` (int, keyword-only): Server-side async execution cap from 1 to 604800 seconds. Requires `async_execution=True` and cannot extend the account policy.
**Returns:** `WorkflowExecutionResult` or `AsyncExecutionResult`
@@ -527,4 +534,4 @@ isort simstudio/
## License
Apache-2.0
Apache-2.0
+28 -3
View File
@@ -12,6 +12,7 @@ import os
import requests
MAX_EXECUTION_TIMEOUT_SECONDS = 604_800
__version__ = "0.1.2"
__all__ = [
@@ -155,7 +156,8 @@ class SimStudioClient:
timeout: float = 30.0,
stream: Optional[bool] = None,
selected_outputs: Optional[list] = None,
async_execution: Optional[bool] = None
async_execution: Optional[bool] = None,
execution_timeout_seconds: Optional[int] = None
) -> Union[WorkflowExecutionResult, AsyncExecutionResult]:
"""
Execute a workflow with optional input data.
@@ -172,6 +174,7 @@ class SimStudioClient:
stream: Enable streaming responses (default: None)
selected_outputs: Block outputs to stream (e.g., ["agent1.content"])
async_execution: Execute asynchronously (default: None)
execution_timeout_seconds: Server-side async execution cap in seconds (1-604800)
Returns:
WorkflowExecutionResult or AsyncExecutionResult object
@@ -181,10 +184,29 @@ class SimStudioClient:
"""
url = f"{self.base_url}/api/workflows/{workflow_id}/execute"
if execution_timeout_seconds is not None:
if not async_execution:
raise SimStudioError(
'execution_timeout_seconds is supported only for async executions',
'INVALID_EXECUTION_TIMEOUT'
)
if (
isinstance(execution_timeout_seconds, bool)
or not isinstance(execution_timeout_seconds, int)
or execution_timeout_seconds < 1
or execution_timeout_seconds > MAX_EXECUTION_TIMEOUT_SECONDS
):
raise SimStudioError(
f'execution_timeout_seconds must be an integer between 1 and {MAX_EXECUTION_TIMEOUT_SECONDS}',
'INVALID_EXECUTION_TIMEOUT'
)
# Build headers - async execution uses X-Execution-Mode header
headers = self._session.headers.copy()
if async_execution:
headers['X-Execution-Mode'] = 'async'
if execution_timeout_seconds is not None:
headers['X-Execution-Timeout-Seconds'] = str(execution_timeout_seconds)
try:
# Build JSON body - spread dict inputs at root level, wrap primitives/lists in 'input' field
@@ -421,6 +443,7 @@ class SimStudioClient:
stream: Optional[bool] = None,
selected_outputs: Optional[list] = None,
async_execution: Optional[bool] = None,
execution_timeout_seconds: Optional[int] = None,
max_retries: int = 3,
initial_delay: float = 1.0,
max_delay: float = 30.0,
@@ -436,6 +459,7 @@ class SimStudioClient:
stream: Enable streaming responses
selected_outputs: Block outputs to stream
async_execution: Execute asynchronously
execution_timeout_seconds: Server-side async execution cap in seconds (1-604800)
max_retries: Maximum number of retries (default: 3)
initial_delay: Initial delay in seconds (default: 1.0)
max_delay: Maximum delay in seconds (default: 30.0)
@@ -458,7 +482,8 @@ class SimStudioClient:
timeout=timeout,
stream=stream,
selected_outputs=selected_outputs,
async_execution=async_execution
async_execution=async_execution,
execution_timeout_seconds=execution_timeout_seconds,
)
except SimStudioError as e:
if e.code != 'RATE_LIMIT_EXCEEDED':
@@ -565,4 +590,4 @@ class SimStudioClient:
# For backward compatibility
Client = SimStudioClient
Client = SimStudioClient
+74 -1
View File
@@ -171,6 +171,79 @@ def test_async_header_not_set_when_false(mock_post):
assert "X-Execution-Mode" not in call_args[1]["headers"]
@patch('simstudio.requests.Session.post')
def test_async_execution_timeout_header(mock_post):
mock_response = Mock()
mock_response.ok = True
mock_response.status_code = 202
mock_response.json.return_value = {
"success": True,
"jobId": "job-123",
"statusUrl": "/api/jobs/job-123",
"async": True,
}
mock_response.headers.get.return_value = None
mock_post.return_value = mock_response
client = SimStudioClient(api_key="test-api-key")
client.execute_workflow(
"workflow-id",
{},
async_execution=True,
execution_timeout_seconds=90,
)
headers = mock_post.call_args[1]["headers"]
assert headers["X-Execution-Timeout-Seconds"] == "90"
def test_sync_execution_rejects_execution_timeout():
client = SimStudioClient(api_key="test-api-key")
with pytest.raises(SimStudioError) as exc_info:
client.execute_workflow("workflow-id", {}, execution_timeout_seconds=90)
assert exc_info.value.code == "INVALID_EXECUTION_TIMEOUT"
def test_execution_timeout_rejects_more_than_seven_days():
client = SimStudioClient(api_key="test-api-key")
with pytest.raises(SimStudioError) as exc_info:
client.execute_workflow(
"workflow-id",
{},
async_execution=True,
execution_timeout_seconds=604_801,
)
assert exc_info.value.code == "INVALID_EXECUTION_TIMEOUT"
def test_execute_with_retry_forwards_execution_timeout():
client = SimStudioClient(api_key="test-api-key")
expected = Mock()
with patch.object(client, "execute_workflow", return_value=expected) as execute_workflow:
result = client.execute_with_retry(
"workflow-id",
{"message": "hello"},
async_execution=True,
execution_timeout_seconds=90,
)
assert result is expected
execute_workflow.assert_called_once_with(
"workflow-id",
{"message": "hello"},
timeout=30.0,
stream=None,
selected_outputs=None,
async_execution=True,
execution_timeout_seconds=90,
)
@patch('simstudio.requests.Session.get')
def test_get_job_status_success(mock_get):
"""Test getting job status."""
@@ -534,4 +607,4 @@ def test_execute_workflow_with_dict_input_spreads_at_root(mock_post):
assert request_body["ticker"] == "NVDA"
assert request_body["quantity"] == 100
assert "input" not in request_body # Should not wrap in input field
assert "input" not in request_body # Should not wrap in input field