agent-shell-py 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/AGENTS.md +8 -6
- agent_shell_py-0.3.0/README.md → agent_shell_py-0.3.1/PKG-INFO +22 -2
- agent_shell_py-0.3.0/PKG-INFO → agent_shell_py-0.3.1/README.md +13 -11
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/skills/invoking-cli-agents/SKILL.md +12 -2
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/skills/invoking-cli-agents/api-reference.md +6 -1
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/_version.py +2 -2
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/shell.py +21 -1
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/conftest.py +6 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_process_lifecycle.py +21 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_shell.py +58 -1
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/.github/workflows/build.yml +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/.github/workflows/ci.yml +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/.github/workflows/publish.yml +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/.gitignore +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/.python-version +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/LICENSE +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/docs/assets/skill_banner.png +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/docs/development/agent_parameter_comparison.md +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/docs/development/disabled_tools.md +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/docs/development/info.md +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/docs/development/total_token_count.md +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/pyproject.toml +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/skills/delegating-code-review/SKILL.md +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/__init__.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/__init__.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/agent_adapter_protocol.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/claude_code_adapter.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/codex_adapter.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/copilot_cli_adapter.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/cursor_adapter.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/grok_adapter.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/health.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/model_discovery.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/opencode_adapter.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/outcome.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/pi_adapter.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/process_failure.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/response.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/stderr_format.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/tool_denial.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/execution.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/models/__init__.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/models/agent.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/process_cleanup.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/__init__.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/__init__.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_claude_code_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_codex_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_copilot_cli_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_cursor_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_execution_host_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_grok_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_health_check_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_model_discovery_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_opencode_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/e2e/test_pi_e2e.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/__init__.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_claude_code_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_claude_code_mcp_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_codex_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_codex_mcp_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_copilot_cli_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_copilot_cli_mcp_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_cursor_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_cursor_mcp_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_execution_host.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_grok_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_grok_mcp_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_health_check_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_model_discovery_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_opencode_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_opencode_mcp_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_pi_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_pi_mcp_integration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/__init__.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/adapter_matrix.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/codex_fixtures.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/copilot_fixtures.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/cursor_fixtures.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/fixtures.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/grok_fixtures.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/opencode_fixtures.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/pi_fixtures.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_adapter_transport.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_cancel.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_codex_cancel.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_codex_execute.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_codex_parse_event.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_codex_warnings.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_copilot_cli_cancel.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_copilot_cli_execute.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_copilot_cli_parse_event.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_copilot_cli_stream.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_cursor_cancel.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_cursor_execute.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_cursor_parse_event.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_cursor_warnings.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_execute.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_execute_outcome.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_grok_cancel.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_grok_execute.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_grok_parse_event.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_grok_warnings.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_health_probe.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_mcp_server_spec.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_model_discovery.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_models.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_opencode_cancel.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_opencode_execute.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_opencode_parse_event.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_opencode_spawn.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_opencode_stream.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_parse_event.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_pi_cancel.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_pi_execute.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_pi_parse_event.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_pi_warnings.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_process_cleanup.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_process_group_registration.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_response_aggregation.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_shell_cancellation.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_shell_mcp.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_stderr_format.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_stream.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/unit/test_tool_denial.py +0 -0
- {agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/uv.lock +0 -0
|
@@ -158,12 +158,14 @@ classDiagram
|
|
|
158
158
|
The adapter pattern uses Python's `Protocol` (structural typing) rather than ABC, so adapters satisfy the contract implicitly without inheritance. Each adapter translates agent-specific CLI flags and NDJSON output into the shared `StreamEvent`/`AgentResponse` models, while the selected `ExecutionHost` owns process creation and returns a per-run `RunHandle`.
|
|
159
159
|
|
|
160
160
|
Execution location and protection are separate axes. Existing callers default to
|
|
161
|
-
`NativeExecutionHost()` plus `NoIsolation()
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
`NativeExecutionHost()` plus `NoIsolation()` when `AGENTSHELL_ISOLATION_POLICY` is unset. The
|
|
162
|
+
environment accepts `none` or `linux-pid-namespace` as a process-wide construction default;
|
|
163
|
+
explicit `isolation_policy=` takes precedence, and invalid or empty values raise `ValueError`.
|
|
164
|
+
`LinuxPidNamespaceIsolation` is a direct signal boundary: a tiny init/reaper is PID 1 and the CLI
|
|
165
|
+
is PID 2 or later, so child-namespace processes cannot see or signal AgentShell's ancestors. It
|
|
166
|
+
requires Linux, `unshare`, and enabled unprivileged user/PID namespaces; an unavailable request
|
|
167
|
+
raises `IsolationUnavailableError` and never falls back. This is not a general sandbox and does
|
|
168
|
+
not restrict filesystem, credentials, network, tools, or resources. The host/policy applies to
|
|
167
169
|
`execute()`, `stream()`, and `health_check()`; model discovery and MCP configuration remain local.
|
|
168
170
|
|
|
169
171
|
`output_tokens` is a cost measure — the billed output-token count, which **includes reasoning tokens** (billed at the output rate). Each adapter normalises this so the value is consistent across agents (e.g. OpenCode reports reasoning in a sibling field, so its adapter adds it back).
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agent-shell-py
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: A lightweight abstraction for executing CLI coding agents headlessly
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
1
10
|
# Agent Shell
|
|
2
11
|
Agent Shell is a light weight abstraction for executing a cli coding agent headlessly
|
|
3
12
|
and returning the output that can be used programatically as a unified contract
|
|
@@ -74,13 +83,24 @@ separately.
|
|
|
74
83
|
|
|
75
84
|
### Execution host and isolation
|
|
76
85
|
|
|
77
|
-
Existing callers remain unchanged. Omitting both
|
|
78
|
-
`NativeExecutionHost()` plus `NoIsolation()`:
|
|
86
|
+
Existing callers remain unchanged when `AGENTSHELL_ISOLATION_POLICY` is unset. Omitting both
|
|
87
|
+
constructor settings then means `NativeExecutionHost()` plus `NoIsolation()`:
|
|
79
88
|
|
|
80
89
|
```python
|
|
81
90
|
shell = AgentShell(agent_type=AgentType.CLAUDE_CODE)
|
|
82
91
|
```
|
|
83
92
|
|
|
93
|
+
Set a process-wide deployment default without changing Python call sites:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
export AGENTSHELL_ISOLATION_POLICY=linux-pid-namespace
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The accepted values are `none` and `linux-pid-namespace`. AgentShell reads the value when each
|
|
100
|
+
shell is constructed. An empty or unknown value raises `ValueError` rather than silently disabling
|
|
101
|
+
isolation. An explicit constructor policy takes precedence over the environment, so a caller can
|
|
102
|
+
still force one shell to use `NoIsolation()` or a custom policy.
|
|
103
|
+
|
|
84
104
|
To protect the AgentShell owner from broad same-user cleanup commands such as `pkill -f`,
|
|
85
105
|
explicitly request Linux PID namespace isolation:
|
|
86
106
|
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: agent-shell-py
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: A lightweight abstraction for executing CLI coding agents headlessly
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Requires-Python: >=3.12
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
1
|
# Agent Shell
|
|
11
2
|
Agent Shell is a light weight abstraction for executing a cli coding agent headlessly
|
|
12
3
|
and returning the output that can be used programatically as a unified contract
|
|
@@ -83,13 +74,24 @@ separately.
|
|
|
83
74
|
|
|
84
75
|
### Execution host and isolation
|
|
85
76
|
|
|
86
|
-
Existing callers remain unchanged. Omitting both
|
|
87
|
-
`NativeExecutionHost()` plus `NoIsolation()`:
|
|
77
|
+
Existing callers remain unchanged when `AGENTSHELL_ISOLATION_POLICY` is unset. Omitting both
|
|
78
|
+
constructor settings then means `NativeExecutionHost()` plus `NoIsolation()`:
|
|
88
79
|
|
|
89
80
|
```python
|
|
90
81
|
shell = AgentShell(agent_type=AgentType.CLAUDE_CODE)
|
|
91
82
|
```
|
|
92
83
|
|
|
84
|
+
Set a process-wide deployment default without changing Python call sites:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export AGENTSHELL_ISOLATION_POLICY=linux-pid-namespace
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The accepted values are `none` and `linux-pid-namespace`. AgentShell reads the value when each
|
|
91
|
+
shell is constructed. An empty or unknown value raises `ValueError` rather than silently disabling
|
|
92
|
+
isolation. An explicit constructor policy takes precedence over the environment, so a caller can
|
|
93
|
+
still force one shell to use `NoIsolation()` or a custom policy.
|
|
94
|
+
|
|
93
95
|
To protect the AgentShell owner from broad same-user cleanup commands such as `pkill -f`,
|
|
94
96
|
explicitly request Linux PID namespace isolation:
|
|
95
97
|
|
|
@@ -52,14 +52,24 @@ management. All are async. Invocation has three independent choices:
|
|
|
52
52
|
|
|
53
53
|
### Execution Host and Isolation Policy
|
|
54
54
|
|
|
55
|
-
Existing code is backward-compatible:
|
|
56
|
-
with no isolation.
|
|
55
|
+
Existing code is backward-compatible: when `AGENTSHELL_ISOLATION_POLICY` is unset, omitting both
|
|
56
|
+
execution settings selects native execution with no isolation.
|
|
57
57
|
|
|
58
58
|
```python
|
|
59
59
|
shell = AgentShell(agent_type=AgentType.CODEX)
|
|
60
60
|
# Equivalent to NativeExecutionHost() + NoIsolation()
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
For a process-wide deployment default, set the environment before constructing a shell:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
export AGENTSHELL_ISOLATION_POLICY=linux-pid-namespace
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Accepted values are `none` and `linux-pid-namespace`. Empty or unknown values raise `ValueError`.
|
|
70
|
+
An explicit `isolation_policy=` object wins over the environment, which remains the way to select
|
|
71
|
+
a custom policy or override the deployment default for one shell.
|
|
72
|
+
|
|
63
73
|
Opt into direct-signal protection when an agent may run broad same-user cleanup commands such as
|
|
64
74
|
`pkill -f`:
|
|
65
75
|
|
|
@@ -150,7 +150,7 @@ class AgentShell:
|
|
|
150
150
|
self,
|
|
151
151
|
agent_type: AgentType,
|
|
152
152
|
execution_host: ExecutionHost | None = None, # default NativeExecutionHost()
|
|
153
|
-
isolation_policy: IsolationPolicy | None = None, #
|
|
153
|
+
isolation_policy: IsolationPolicy | None = None, # env setting, then NoIsolation()
|
|
154
154
|
): ...
|
|
155
155
|
# raises ValueError for an AgentType with no registered adapter
|
|
156
156
|
|
|
@@ -212,6 +212,11 @@ AgentShell's ancestors from direct same-user signals sent inside the child names
|
|
|
212
212
|
filesystem, credential, network, tool, resource, or general security sandbox. Background
|
|
213
213
|
descendants terminate when the namespace ends.
|
|
214
214
|
|
|
215
|
+
When `isolation_policy` is omitted, `AgentShell` reads `AGENTSHELL_ISOLATION_POLICY` at
|
|
216
|
+
construction. The accepted values are `none` and `linux-pid-namespace`; if the variable is absent,
|
|
217
|
+
the default remains `NoIsolation`. Empty or unknown values raise `ValueError`. Passing an explicit
|
|
218
|
+
policy object takes precedence over the environment.
|
|
219
|
+
|
|
215
220
|
The Linux policy requires `unshare` and supporting kernel configuration. An explicit request that
|
|
216
221
|
cannot be satisfied raises `IsolationUnavailableError` before launching the CLI and never falls
|
|
217
222
|
back to `NoIsolation`. The host/policy selection applies to `execute()`, `stream()`, and
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import os
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import AsyncIterator
|
|
4
5
|
|
|
@@ -13,6 +14,7 @@ from agent_shell.adapters.pi_adapter import PiAdapter
|
|
|
13
14
|
from agent_shell.execution import (
|
|
14
15
|
ExecutionHost,
|
|
15
16
|
IsolationPolicy,
|
|
17
|
+
LinuxPidNamespaceIsolation,
|
|
16
18
|
NativeExecutionHost,
|
|
17
19
|
NoIsolation,
|
|
18
20
|
)
|
|
@@ -37,6 +39,22 @@ _ADAPTERS: dict[AgentType, type[AgentAdapter]] = {
|
|
|
37
39
|
AgentType.GROK: GrokAdapter,
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
_ISOLATION_POLICY_ENV = "AGENTSHELL_ISOLATION_POLICY"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _isolation_policy_from_environment() -> IsolationPolicy:
|
|
46
|
+
value = os.environ.get(_ISOLATION_POLICY_ENV)
|
|
47
|
+
if value is None:
|
|
48
|
+
return NoIsolation()
|
|
49
|
+
if value == "none":
|
|
50
|
+
return NoIsolation()
|
|
51
|
+
if value == "linux-pid-namespace":
|
|
52
|
+
return LinuxPidNamespaceIsolation()
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Unsupported {_ISOLATION_POLICY_ENV} value {value!r}: "
|
|
55
|
+
"expected 'none' or 'linux-pid-namespace'"
|
|
56
|
+
)
|
|
57
|
+
|
|
40
58
|
|
|
41
59
|
class AgentShell():
|
|
42
60
|
def __init__(
|
|
@@ -49,7 +67,9 @@ class AgentShell():
|
|
|
49
67
|
execution_host if execution_host is not None else NativeExecutionHost()
|
|
50
68
|
)
|
|
51
69
|
self.isolation_policy = (
|
|
52
|
-
isolation_policy
|
|
70
|
+
isolation_policy
|
|
71
|
+
if isolation_policy is not None
|
|
72
|
+
else _isolation_policy_from_environment()
|
|
53
73
|
)
|
|
54
74
|
self._adapter = self._resolve_adapter(agent_type=agent_type)
|
|
55
75
|
|
|
@@ -4,6 +4,12 @@ import pytest
|
|
|
4
4
|
from agent_shell import process_cleanup
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
@pytest.fixture(autouse=True)
|
|
8
|
+
def isolate_agentshell_environment(monkeypatch):
|
|
9
|
+
"""Keep deployment-level AgentShell configuration explicit in each test."""
|
|
10
|
+
monkeypatch.delenv("AGENTSHELL_ISOLATION_POLICY", raising=False)
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
@pytest.fixture(autouse=True)
|
|
8
14
|
def isolate_process_group_registry():
|
|
9
15
|
"""Give every test an isolated guardian registry and close anything it leaks."""
|
|
@@ -510,6 +510,27 @@ async def test_agentshell_applies_the_requested_pid_isolation(fake_cli, tmp_path
|
|
|
510
510
|
assert response.response == ""
|
|
511
511
|
|
|
512
512
|
|
|
513
|
+
async def test_agentshell_applies_pid_isolation_selected_by_environment(
|
|
514
|
+
fake_cli, tmp_path, monkeypatch):
|
|
515
|
+
# Arrange — exercise the deployment-level configuration through the public AgentShell
|
|
516
|
+
# boundary. Namespace PID 2 proves the selected policy reached the real CLI process.
|
|
517
|
+
monkeypatch.setenv(
|
|
518
|
+
"AGENTSHELL_ISOLATION_POLICY",
|
|
519
|
+
"linux-pid-namespace",
|
|
520
|
+
)
|
|
521
|
+
shell = AgentShell(agent_type=AgentType.CLAUDE_CODE)
|
|
522
|
+
fake_cli(stdout=[OK_RESULT_EVENT[ClaudeCodeAdapter]], required_pid=2)
|
|
523
|
+
|
|
524
|
+
# Act
|
|
525
|
+
try:
|
|
526
|
+
response = await shell.execute(cwd=str(tmp_path), prompt="ping")
|
|
527
|
+
except IsolationUnavailableError as error:
|
|
528
|
+
pytest.skip(str(error))
|
|
529
|
+
|
|
530
|
+
# Assert
|
|
531
|
+
assert response.response == ""
|
|
532
|
+
|
|
533
|
+
|
|
513
534
|
@pytest.mark.parametrize("adapter_cls", ADAPTERS)
|
|
514
535
|
async def test_cancelled_agent_shell_stream_kills_the_real_process_tree(
|
|
515
536
|
adapter_cls, fake_cli, tmp_path):
|
|
@@ -8,12 +8,52 @@ from agent_shell.adapters.cursor_adapter import CursorAdapter
|
|
|
8
8
|
from agent_shell.adapters.grok_adapter import GrokAdapter
|
|
9
9
|
from agent_shell.adapters.opencode_adapter import OpenCodeAdapter
|
|
10
10
|
from agent_shell.adapters.pi_adapter import PiAdapter
|
|
11
|
-
from agent_shell.execution import
|
|
11
|
+
from agent_shell.execution import (
|
|
12
|
+
LinuxPidNamespaceIsolation,
|
|
13
|
+
NativeExecutionHost,
|
|
14
|
+
NoIsolation,
|
|
15
|
+
)
|
|
12
16
|
from agent_shell.models.agent import AgentResponse, AgentType
|
|
13
17
|
from agent_shell.shell import AgentShell
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
class TestExecutionDefaults:
|
|
21
|
+
def test_environment_can_select_linux_pid_namespace_isolation(self, monkeypatch):
|
|
22
|
+
# Arrange
|
|
23
|
+
monkeypatch.setenv(
|
|
24
|
+
"AGENTSHELL_ISOLATION_POLICY",
|
|
25
|
+
"linux-pid-namespace",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Act
|
|
29
|
+
shell = AgentShell(agent_type=AgentType.CLAUDE_CODE)
|
|
30
|
+
|
|
31
|
+
# Assert
|
|
32
|
+
assert isinstance(shell.isolation_policy, LinuxPidNamespaceIsolation)
|
|
33
|
+
|
|
34
|
+
@pytest.mark.parametrize("value", ["", "container"])
|
|
35
|
+
def test_invalid_environment_is_rejected_instead_of_disabling_isolation(
|
|
36
|
+
self, monkeypatch, value):
|
|
37
|
+
# Arrange
|
|
38
|
+
monkeypatch.setenv("AGENTSHELL_ISOLATION_POLICY", value)
|
|
39
|
+
|
|
40
|
+
# Act / Assert
|
|
41
|
+
with pytest.raises(
|
|
42
|
+
ValueError,
|
|
43
|
+
match="AGENTSHELL_ISOLATION_POLICY",
|
|
44
|
+
):
|
|
45
|
+
AgentShell(agent_type=AgentType.CLAUDE_CODE)
|
|
46
|
+
|
|
47
|
+
def test_environment_can_explicitly_select_no_isolation(self, monkeypatch):
|
|
48
|
+
# Arrange
|
|
49
|
+
monkeypatch.setenv("AGENTSHELL_ISOLATION_POLICY", "none")
|
|
50
|
+
|
|
51
|
+
# Act
|
|
52
|
+
shell = AgentShell(agent_type=AgentType.CLAUDE_CODE)
|
|
53
|
+
|
|
54
|
+
# Assert
|
|
55
|
+
assert isinstance(shell.isolation_policy, NoIsolation)
|
|
56
|
+
|
|
17
57
|
def test_existing_constructor_defaults_to_native_without_isolation(self):
|
|
18
58
|
# Arrange / Act
|
|
19
59
|
shell = AgentShell(agent_type=AgentType.CLAUDE_CODE)
|
|
@@ -38,6 +78,23 @@ class TestExecutionDefaults:
|
|
|
38
78
|
assert shell.execution_host is host
|
|
39
79
|
assert shell.isolation_policy is policy
|
|
40
80
|
|
|
81
|
+
def test_explicit_isolation_policy_overrides_environment(self, monkeypatch):
|
|
82
|
+
# Arrange
|
|
83
|
+
monkeypatch.setenv(
|
|
84
|
+
"AGENTSHELL_ISOLATION_POLICY",
|
|
85
|
+
"linux-pid-namespace",
|
|
86
|
+
)
|
|
87
|
+
policy = NoIsolation()
|
|
88
|
+
|
|
89
|
+
# Act
|
|
90
|
+
shell = AgentShell(
|
|
91
|
+
agent_type=AgentType.CLAUDE_CODE,
|
|
92
|
+
isolation_policy=policy,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Assert
|
|
96
|
+
assert shell.isolation_policy is policy
|
|
97
|
+
|
|
41
98
|
|
|
42
99
|
class TestResolveAdapter:
|
|
43
100
|
def test_resolves_claude_code(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/docs/development/agent_parameter_comparison.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/agent_adapter_protocol.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/claude_code_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/src/agent_shell/adapters/copilot_cli_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_claude_code_integration.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_claude_code_mcp_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_codex_mcp_integration.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_copilot_cli_integration.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_copilot_cli_mcp_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_cursor_mcp_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_grok_mcp_integration.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_health_check_integration.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_model_discovery_integration.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_opencode_integration.py
RENAMED
|
File without changes
|
{agent_shell_py-0.3.0 → agent_shell_py-0.3.1}/tests/integration/test_opencode_mcp_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|