agent-shell-py 0.2.1__tar.gz → 0.2.3__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.2.1 → agent_shell_py-0.2.3}/PKG-INFO +1 -1
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/docs/development/agent_parameter_comparison.md +25 -16
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/_version.py +2 -2
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/claude_code_adapter.py +16 -13
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/codex_adapter.py +16 -12
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/copilot_cli_adapter.py +33 -14
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/cursor_adapter.py +15 -13
- agent_shell_py-0.2.3/src/agent_shell/adapters/model_discovery.py +81 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/opencode_adapter.py +20 -15
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/pi_adapter.py +15 -13
- agent_shell_py-0.2.3/src/agent_shell/process_cleanup.py +158 -0
- agent_shell_py-0.2.3/tests/conftest.py +20 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_model_discovery_integration.py +6 -16
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_process_lifecycle.py +282 -54
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_adapter_transport.py +8 -19
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_cancel.py +1 -1
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_codex_cancel.py +1 -1
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_cancel.py +1 -1
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_stream.py +52 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_cursor_cancel.py +1 -1
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_opencode_cancel.py +1 -1
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_pi_cancel.py +1 -1
- agent_shell_py-0.2.3/tests/unit/test_process_cleanup.py +201 -0
- agent_shell_py-0.2.3/tests/unit/test_process_group_registration.py +57 -0
- agent_shell_py-0.2.1/src/agent_shell/adapters/model_discovery.py +0 -117
- agent_shell_py-0.2.1/src/agent_shell/process_cleanup.py +0 -215
- agent_shell_py-0.2.1/tests/conftest.py +0 -50
- agent_shell_py-0.2.1/tests/unit/test_process_cleanup.py +0 -596
- agent_shell_py-0.2.1/tests/unit/test_process_group_registration.py +0 -208
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/.github/workflows/build.yml +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/.github/workflows/ci.yml +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/.github/workflows/publish.yml +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/.gitignore +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/.python-version +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/AGENTS.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/LICENSE +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/README.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/docs/assets/skill_banner.png +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/docs/development/disabled_tools.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/docs/development/info.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/docs/development/total_token_count.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/pyproject.toml +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/skills/delegating-code-review/SKILL.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/skills/invoking-cli-agents/SKILL.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/skills/invoking-cli-agents/api-reference.md +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/__init__.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/__init__.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/agent_adapter_protocol.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/health.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/outcome.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/response.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/stderr_format.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/tool_denial.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/models/__init__.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/models/agent.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/shell.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/__init__.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/__init__.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_claude_code_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_codex_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_copilot_cli_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_cursor_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_health_check_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_model_discovery_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_opencode_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/e2e/test_pi_e2e.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/__init__.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_claude_code_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_claude_code_mcp_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_codex_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_codex_mcp_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_copilot_cli_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_copilot_cli_mcp_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_cursor_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_cursor_mcp_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_health_check_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_opencode_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_opencode_mcp_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_pi_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/integration/test_pi_mcp_integration.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/__init__.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/adapter_matrix.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/codex_fixtures.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/copilot_fixtures.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/cursor_fixtures.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/fixtures.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/opencode_fixtures.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/pi_fixtures.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_codex_execute.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_codex_parse_event.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_codex_warnings.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_execute.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_parse_event.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_cursor_execute.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_cursor_parse_event.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_cursor_warnings.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_execute.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_execute_outcome.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_health_probe.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_mcp_server_spec.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_model_discovery.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_models.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_opencode_execute.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_opencode_parse_event.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_opencode_spawn.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_opencode_stream.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_parse_event.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_pi_execute.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_pi_parse_event.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_pi_warnings.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_response_aggregation.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_shell.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_shell_cancellation.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_shell_mcp.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_stderr_format.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_stream.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/tests/unit/test_tool_denial.py +0 -0
- {agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/uv.lock +0 -0
{agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/docs/development/agent_parameter_comparison.md
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Agent CLI Parameter Comparison
|
|
2
2
|
|
|
3
3
|
Comparison of headless/non-interactive configuration across supported CLI coding agents.
|
|
4
|
-
Last updated: 2026-08-
|
|
4
|
+
Last updated: 2026-08-05
|
|
5
5
|
|
|
6
6
|
> The summary matrix below has no Pi or Cursor column (it predates both adapters); see the
|
|
7
7
|
> per-agent detail sections, the model-discovery section, and the `disallowed_tools` table.
|
|
@@ -20,9 +20,9 @@ Last updated: 2026-08-02
|
|
|
20
20
|
| **Allowed tools** | `--allowed-tools` | No direct flag | `--allow-tool`, `--available-tools` | `tools` in config |
|
|
21
21
|
| **Disallowed tools** | `--disallowed-tools` | `web_search` config only | `--deny-tool` | `OPENCODE_PERMISSION` env / `permission` config |
|
|
22
22
|
| **Stream output** | `--output-format stream-json` | `--json` (NDJSON) | `--output-format=json` (JSONL) | `--format json` |
|
|
23
|
-
| **Working dir** | cwd + `--add-dir` | `--cd` / `-C` | cwd
|
|
23
|
+
| **Working dir** | cwd + `--add-dir` | `--cd` / `-C` | cwd / `-C` | cwd |
|
|
24
24
|
| **System prompt** | `--system-prompt` / `--append-system-prompt` | No flag (files only) | No flag (files only) | `instructions` in config |
|
|
25
|
-
| **Budget** | `--max-budget-usd` | No direct flag |
|
|
25
|
+
| **Budget** | `--max-budget-usd` | No direct flag | `--max-ai-credits` | No direct flag |
|
|
26
26
|
| **Auto-approve** | `--dangerously-skip-permissions` | `--yolo` | `--yolo` / `--allow-all` | Auto in `run` mode |
|
|
27
27
|
| **Session resume** | `--resume` | `exec resume <id>` | `--resume` | `-s <id>` |
|
|
28
28
|
|
|
@@ -123,23 +123,32 @@ real discovery commands.
|
|
|
123
123
|
|
|
124
124
|
## Copilot CLI (GitHub)
|
|
125
125
|
|
|
126
|
-
- **Headless mode**: `-p` / `--prompt` for one-shot
|
|
127
|
-
- **Model**: `--model
|
|
128
|
-
- **Effort**: `--effort
|
|
129
|
-
|
|
130
|
-
-
|
|
126
|
+
- **Headless mode**: `-p` / `--prompt` for one-shot; `--acp` for Agent Client Protocol
|
|
127
|
+
- **Model**: `--model <model>`; use `auto` to let Copilot choose
|
|
128
|
+
- **Effort**: `--effort` / `--reasoning-effort` with choices
|
|
129
|
+
`none|minimal|low|medium|high|xhigh|max`. AgentShell accepts effort values
|
|
130
|
+
case-insensitively, validates them against these choices, and passes the normalized lowercase
|
|
131
|
+
value to Copilot via `--effort`.
|
|
132
|
+
- **Allowed tools**: `--allow-tool`, `--deny-tool`, `--available-tools`,
|
|
133
|
+
`--excluded-tools`, `--allow-all-tools`
|
|
134
|
+
- **Auto-approve**: `--allow-all-tools`; `--allow-all` / `--yolo` also grant path and URL
|
|
135
|
+
permissions
|
|
136
|
+
- **Agent mode**: `--mode interactive|plan|autopilot`; `--autopilot` is the shortcut
|
|
131
137
|
- **Output format**: `--output-format=json` (JSONL)
|
|
132
138
|
- **Silent mode**: `--silent` suppresses stats, prints only response
|
|
133
|
-
- **Working directory**:
|
|
134
|
-
- **System prompt**: No CLI flag
|
|
135
|
-
- **
|
|
139
|
+
- **Working directory**: CLI supports `-C <directory>`; AgentShell sets subprocess `cwd`
|
|
140
|
+
- **System prompt**: No CLI flag; uses `.github/copilot-instructions.md` and `AGENTS.md` files
|
|
141
|
+
- **AI credit budget**: `--max-ai-credits <credits>` limits credits for the session
|
|
142
|
+
- **Token budget**: No per-run flag; auto-compacts at 95% of the token limit
|
|
136
143
|
- **Path permissions**: `--allow-all-paths`, `--disallow-temp-dir`
|
|
137
144
|
- **URL permissions**: `--allow-all-urls`, `--allow-url`, `--deny-url`
|
|
138
|
-
- **Session**: `--resume`, `--continue`,
|
|
139
|
-
a
|
|
140
|
-
`sessionId`, and an unknown id is rejected
|
|
141
|
-
identity is real evidence the CLI continued that
|
|
142
|
-
|
|
145
|
+
- **Session**: `--resume`, `--continue`, and `--session-id <id>`. The latter resumes an existing
|
|
146
|
+
session or task, or assigns a UUID to a new session. AgentShell resumes with `--resume <id>`.
|
|
147
|
+
The resumed run reports the SAME `sessionId`, and an unknown id is rejected
|
|
148
|
+
("No session, task, or name matched"), so id identity is real evidence the CLI continued that
|
|
149
|
+
session (measured 2026-07-26).
|
|
150
|
+
- **ACP mode**: `--acp` uses stdio by default. Copilot CLI 1.0.78 also accepts hidden
|
|
151
|
+
`--stdio` and `--port <port>` transport options; they are not shown by `copilot --help`.
|
|
143
152
|
|
|
144
153
|
## OpenCode
|
|
145
154
|
|
|
@@ -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.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
{agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/claude_code_adapter.py
RENAMED
|
@@ -7,9 +7,18 @@ import warnings
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import AsyncIterator
|
|
9
9
|
|
|
10
|
-
from agent_shell.models.agent import
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
from agent_shell.models.agent import (
|
|
11
|
+
AgentResponse,
|
|
12
|
+
HealthCheckResult,
|
|
13
|
+
MCPServerSpec,
|
|
14
|
+
MCPServerType,
|
|
15
|
+
StreamEvent,
|
|
16
|
+
)
|
|
17
|
+
from agent_shell.process_cleanup import (
|
|
18
|
+
create_grouped_process,
|
|
19
|
+
kill_process_group,
|
|
20
|
+
release_process,
|
|
21
|
+
)
|
|
13
22
|
from agent_shell.adapters.health import run_health_probe
|
|
14
23
|
from agent_shell.adapters.model_discovery import decode_model_output, run_model_command
|
|
15
24
|
from agent_shell.adapters.response import collect_response
|
|
@@ -105,18 +114,12 @@ class ClaudeCodeAdapter():
|
|
|
105
114
|
logger.debug("Command: %s", cmd)
|
|
106
115
|
logger.info("Process started (cwd=%s)", os.path.abspath(cwd))
|
|
107
116
|
|
|
108
|
-
process = await
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
stdout=asyncio.subprocess.PIPE,
|
|
112
|
-
stderr=asyncio.subprocess.PIPE,
|
|
113
|
-
cwd=os.path.abspath(cwd),
|
|
114
|
-
preexec_fn=os.setsid,
|
|
117
|
+
process = await create_grouped_process(
|
|
118
|
+
cmd,
|
|
119
|
+
cwd=os.path.abspath(cwd),
|
|
115
120
|
)
|
|
116
121
|
|
|
117
122
|
self._active_processes.append(process)
|
|
118
|
-
# setsid makes the child a session leader, so pgid == pid
|
|
119
|
-
register_process_group(process.pid)
|
|
120
123
|
|
|
121
124
|
# Drain stderr concurrently with stdout. Reading it only after the stdout loop can
|
|
122
125
|
# deadlock: a child that fills its stderr pipe buffer (~64KB) mid-run blocks on that
|
|
@@ -227,7 +230,7 @@ class ClaudeCodeAdapter():
|
|
|
227
230
|
|
|
228
231
|
async def cancel(self) -> None:
|
|
229
232
|
for process in self._active_processes:
|
|
230
|
-
kill_process_group(process
|
|
233
|
+
kill_process_group(process)
|
|
231
234
|
self._active_processes.clear()
|
|
232
235
|
|
|
233
236
|
async def health_check(
|
|
@@ -6,9 +6,18 @@ import os
|
|
|
6
6
|
import warnings
|
|
7
7
|
from typing import AsyncIterator
|
|
8
8
|
|
|
9
|
-
from agent_shell.models.agent import
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
from agent_shell.models.agent import (
|
|
10
|
+
AgentResponse,
|
|
11
|
+
HealthCheckResult,
|
|
12
|
+
MCPServerSpec,
|
|
13
|
+
MCPServerType,
|
|
14
|
+
StreamEvent,
|
|
15
|
+
)
|
|
16
|
+
from agent_shell.process_cleanup import (
|
|
17
|
+
create_grouped_process,
|
|
18
|
+
kill_process_group,
|
|
19
|
+
release_process,
|
|
20
|
+
)
|
|
12
21
|
from agent_shell.adapters.health import run_health_probe
|
|
13
22
|
from agent_shell.adapters.model_discovery import decode_model_output, run_model_command
|
|
14
23
|
from agent_shell.adapters.response import collect_response
|
|
@@ -117,17 +126,12 @@ class CodexAdapter:
|
|
|
117
126
|
logger.debug("Command: %s", cmd)
|
|
118
127
|
logger.info("Process started (cwd=%s)", os.path.abspath(cwd))
|
|
119
128
|
|
|
120
|
-
process = await
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
stdout=asyncio.subprocess.PIPE,
|
|
124
|
-
stderr=asyncio.subprocess.PIPE,
|
|
125
|
-
cwd=os.path.abspath(cwd),
|
|
126
|
-
preexec_fn=os.setsid,
|
|
129
|
+
process = await create_grouped_process(
|
|
130
|
+
cmd,
|
|
131
|
+
cwd=os.path.abspath(cwd),
|
|
127
132
|
)
|
|
128
133
|
|
|
129
134
|
self._active_processes.append(process)
|
|
130
|
-
register_process_group(process.pid)
|
|
131
135
|
|
|
132
136
|
# Drain stderr concurrently with stdout. Reading it only after the stdout loop can
|
|
133
137
|
# deadlock: a child that fills its stderr pipe buffer (~64KB) mid-run blocks on that
|
|
@@ -279,7 +283,7 @@ class CodexAdapter:
|
|
|
279
283
|
|
|
280
284
|
async def cancel(self) -> None:
|
|
281
285
|
for process in self._active_processes:
|
|
282
|
-
kill_process_group(process
|
|
286
|
+
kill_process_group(process)
|
|
283
287
|
self._active_processes.clear()
|
|
284
288
|
|
|
285
289
|
async def health_check(
|
{agent_shell_py-0.2.1 → agent_shell_py-0.2.3}/src/agent_shell/adapters/copilot_cli_adapter.py
RENAMED
|
@@ -7,10 +7,16 @@ import warnings
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import AsyncIterator
|
|
9
9
|
|
|
10
|
-
from agent_shell.models.agent import
|
|
10
|
+
from agent_shell.models.agent import (
|
|
11
|
+
AgentResponse,
|
|
12
|
+
HealthCheckResult,
|
|
13
|
+
MCPServerSpec,
|
|
14
|
+
MCPServerType,
|
|
15
|
+
StreamEvent,
|
|
16
|
+
)
|
|
11
17
|
from agent_shell.process_cleanup import (
|
|
18
|
+
create_grouped_process,
|
|
12
19
|
kill_process_group,
|
|
13
|
-
register_process_group,
|
|
14
20
|
release_process,
|
|
15
21
|
)
|
|
16
22
|
from agent_shell.adapters.health import run_health_probe
|
|
@@ -38,6 +44,21 @@ _DISALLOWED_TOOL_MAP = {
|
|
|
38
44
|
"edit": ["write"],
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
_COPILOT_EFFORTS = ("none", "minimal", "low", "medium", "high", "xhigh", "max")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _normalize_effort(effort: str | None) -> str | None:
|
|
51
|
+
if effort is None:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
normalized = effort.lower() if isinstance(effort, str) else None
|
|
55
|
+
if normalized not in _COPILOT_EFFORTS:
|
|
56
|
+
choices = ", ".join(_COPILOT_EFFORTS)
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Unsupported Copilot effort {effort!r}; accepted choices: {choices}"
|
|
59
|
+
)
|
|
60
|
+
return normalized
|
|
61
|
+
|
|
41
62
|
|
|
42
63
|
def _json_rpc_frame(message: dict) -> bytes:
|
|
43
64
|
payload = json.dumps(message, separators=(",", ":")).encode("utf-8")
|
|
@@ -154,6 +175,7 @@ class CopilotCLIAdapter:
|
|
|
154
175
|
session_id: str | None = None,
|
|
155
176
|
disallowed_tools: list[str] | None = None,
|
|
156
177
|
) -> AsyncIterator[StreamEvent]:
|
|
178
|
+
effort = _normalize_effort(effort)
|
|
157
179
|
cmd = [
|
|
158
180
|
"copilot", "-p", prompt,
|
|
159
181
|
"--output-format", "json",
|
|
@@ -195,17 +217,12 @@ class CopilotCLIAdapter:
|
|
|
195
217
|
logger.debug("Command: %s", cmd)
|
|
196
218
|
logger.info("Process started (cwd=%s)", os.path.abspath(cwd))
|
|
197
219
|
|
|
198
|
-
process = await
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
stdout=asyncio.subprocess.PIPE,
|
|
202
|
-
stderr=asyncio.subprocess.PIPE,
|
|
203
|
-
cwd=os.path.abspath(cwd),
|
|
204
|
-
preexec_fn=os.setsid,
|
|
220
|
+
process = await create_grouped_process(
|
|
221
|
+
cmd,
|
|
222
|
+
cwd=os.path.abspath(cwd),
|
|
205
223
|
)
|
|
206
224
|
|
|
207
225
|
self._active_processes.append(process)
|
|
208
|
-
register_process_group(process.pid)
|
|
209
226
|
|
|
210
227
|
# Drain stderr concurrently with stdout. Reading it only after the stdout loop can
|
|
211
228
|
# deadlock: a child that fills its stderr pipe buffer (~64KB) mid-run blocks on that
|
|
@@ -348,7 +365,7 @@ class CopilotCLIAdapter:
|
|
|
348
365
|
|
|
349
366
|
async def cancel(self) -> None:
|
|
350
367
|
for process in self._active_processes:
|
|
351
|
-
kill_process_group(process
|
|
368
|
+
kill_process_group(process)
|
|
352
369
|
self._active_processes.clear()
|
|
353
370
|
|
|
354
371
|
async def health_check(
|
|
@@ -365,7 +382,7 @@ class CopilotCLIAdapter:
|
|
|
365
382
|
timeout: float = 30.0,
|
|
366
383
|
) -> list[str]:
|
|
367
384
|
cmd = ["copilot", "--headless", "--no-auto-update", "--stdio"]
|
|
368
|
-
process
|
|
385
|
+
process = await start_model_process(
|
|
369
386
|
cmd,
|
|
370
387
|
cwd,
|
|
371
388
|
stdin=asyncio.subprocess.PIPE,
|
|
@@ -401,7 +418,7 @@ class CopilotCLIAdapter:
|
|
|
401
418
|
) from error
|
|
402
419
|
finally:
|
|
403
420
|
try:
|
|
404
|
-
await stop_model_processes(process
|
|
421
|
+
await stop_model_processes(process)
|
|
405
422
|
finally:
|
|
406
423
|
if not stderr_task.done():
|
|
407
424
|
stderr_task.cancel()
|
|
@@ -480,8 +497,10 @@ class CopilotCLIAdapter:
|
|
|
480
497
|
|
|
481
498
|
for name, entry in servers.items():
|
|
482
499
|
if not isinstance(entry, dict):
|
|
500
|
+
actual_type = type(entry).__name__
|
|
483
501
|
warnings.warn(
|
|
484
|
-
f"Skipping malformed MCP entry '{name}':
|
|
502
|
+
f"Skipping malformed MCP entry '{name}': "
|
|
503
|
+
f"expected object, got {actual_type}",
|
|
485
504
|
UserWarning,
|
|
486
505
|
stacklevel=2,
|
|
487
506
|
)
|
|
@@ -6,9 +6,17 @@ import os
|
|
|
6
6
|
import warnings
|
|
7
7
|
from typing import AsyncIterator
|
|
8
8
|
|
|
9
|
-
from agent_shell.models.agent import
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
from agent_shell.models.agent import (
|
|
10
|
+
AgentResponse,
|
|
11
|
+
HealthCheckResult,
|
|
12
|
+
MCPServerSpec,
|
|
13
|
+
StreamEvent,
|
|
14
|
+
)
|
|
15
|
+
from agent_shell.process_cleanup import (
|
|
16
|
+
create_grouped_process,
|
|
17
|
+
kill_process_group,
|
|
18
|
+
release_process,
|
|
19
|
+
)
|
|
12
20
|
from agent_shell.adapters.health import run_health_probe
|
|
13
21
|
from agent_shell.adapters.model_discovery import decode_model_output, run_model_command
|
|
14
22
|
from agent_shell.adapters.response import collect_response
|
|
@@ -105,18 +113,12 @@ class CursorAdapter:
|
|
|
105
113
|
logger.debug("Command: %s", cmd)
|
|
106
114
|
logger.info("Process started (cwd=%s)", os.path.abspath(cwd))
|
|
107
115
|
|
|
108
|
-
process = await
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
stdout=asyncio.subprocess.PIPE,
|
|
112
|
-
stderr=asyncio.subprocess.PIPE,
|
|
113
|
-
cwd=os.path.abspath(cwd),
|
|
114
|
-
preexec_fn=os.setsid,
|
|
116
|
+
process = await create_grouped_process(
|
|
117
|
+
cmd,
|
|
118
|
+
cwd=os.path.abspath(cwd),
|
|
115
119
|
)
|
|
116
120
|
|
|
117
121
|
self._active_processes.append(process)
|
|
118
|
-
# setsid makes the child a session leader, so pgid == pid
|
|
119
|
-
register_process_group(process.pid)
|
|
120
122
|
|
|
121
123
|
# Drain stderr concurrently with stdout. Reading it only after the stdout loop can
|
|
122
124
|
# deadlock: a child that fills its stderr pipe buffer (~64KB) mid-run blocks on that
|
|
@@ -281,7 +283,7 @@ class CursorAdapter:
|
|
|
281
283
|
|
|
282
284
|
async def cancel(self) -> None:
|
|
283
285
|
for process in self._active_processes:
|
|
284
|
-
kill_process_group(process
|
|
286
|
+
kill_process_group(process)
|
|
285
287
|
self._active_processes.clear()
|
|
286
288
|
|
|
287
289
|
async def health_check(
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from agent_shell.process_cleanup import create_grouped_process, kill_process_group
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def decode_model_output(output: bytes, source: str) -> str:
|
|
8
|
+
"""Decode model metadata without leaking a low-level codec error."""
|
|
9
|
+
try:
|
|
10
|
+
return output.decode("utf-8")
|
|
11
|
+
except UnicodeDecodeError as error:
|
|
12
|
+
raise RuntimeError(f"{source} returned invalid UTF-8 output") from error
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def _reap_process(process: asyncio.subprocess.Process) -> None:
|
|
16
|
+
try:
|
|
17
|
+
await asyncio.wait_for(process.wait(), timeout=2.0)
|
|
18
|
+
except (TimeoutError, ProcessLookupError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def stop_model_processes(process: asyncio.subprocess.Process) -> None:
|
|
23
|
+
"""Ask the exact guardian to kill the discovery group, then reap the CLI."""
|
|
24
|
+
kill_process_group(process)
|
|
25
|
+
await _reap_process(process)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def start_model_process(
|
|
29
|
+
command: list[str],
|
|
30
|
+
cwd: str,
|
|
31
|
+
*,
|
|
32
|
+
env: dict[str, str] | None = None,
|
|
33
|
+
stdin: int = asyncio.subprocess.DEVNULL,
|
|
34
|
+
) -> asyncio.subprocess.Process:
|
|
35
|
+
"""Start a discovery CLI in a group owned by an exact guardian handle."""
|
|
36
|
+
return await create_grouped_process(
|
|
37
|
+
command,
|
|
38
|
+
cwd=os.path.abspath(cwd),
|
|
39
|
+
env=env,
|
|
40
|
+
stdin=stdin,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def run_model_command(
|
|
45
|
+
command: list[str],
|
|
46
|
+
cwd: str,
|
|
47
|
+
timeout: float,
|
|
48
|
+
*,
|
|
49
|
+
env: dict[str, str] | None = None,
|
|
50
|
+
input_data: bytes | None = None,
|
|
51
|
+
) -> tuple[int, bytes, bytes]:
|
|
52
|
+
"""Run a short-lived model-discovery command and clean up its whole group."""
|
|
53
|
+
process = await start_model_process(
|
|
54
|
+
command,
|
|
55
|
+
cwd,
|
|
56
|
+
env=env,
|
|
57
|
+
stdin=(
|
|
58
|
+
asyncio.subprocess.PIPE
|
|
59
|
+
if input_data is not None
|
|
60
|
+
else asyncio.subprocess.DEVNULL
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
stdout, stderr = await asyncio.wait_for(
|
|
66
|
+
process.communicate(input_data),
|
|
67
|
+
timeout=timeout,
|
|
68
|
+
)
|
|
69
|
+
except TimeoutError as error:
|
|
70
|
+
await stop_model_processes(process)
|
|
71
|
+
rendered = " ".join(command)
|
|
72
|
+
raise RuntimeError(
|
|
73
|
+
f"`{rendered}` timed out after {timeout:g} seconds"
|
|
74
|
+
) from error
|
|
75
|
+
except BaseException:
|
|
76
|
+
await stop_model_processes(process)
|
|
77
|
+
raise
|
|
78
|
+
|
|
79
|
+
returncode = process.returncode
|
|
80
|
+
await stop_model_processes(process)
|
|
81
|
+
return returncode, stdout, stderr
|
|
@@ -7,9 +7,18 @@ import warnings
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import AsyncIterator
|
|
9
9
|
|
|
10
|
-
from agent_shell.models.agent import
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
from agent_shell.models.agent import (
|
|
11
|
+
AgentResponse,
|
|
12
|
+
HealthCheckResult,
|
|
13
|
+
MCPServerSpec,
|
|
14
|
+
MCPServerType,
|
|
15
|
+
StreamEvent,
|
|
16
|
+
)
|
|
17
|
+
from agent_shell.process_cleanup import (
|
|
18
|
+
create_grouped_process,
|
|
19
|
+
kill_process_group,
|
|
20
|
+
release_process,
|
|
21
|
+
)
|
|
13
22
|
from agent_shell.adapters.health import run_health_probe
|
|
14
23
|
from agent_shell.adapters.model_discovery import decode_model_output, run_model_command
|
|
15
24
|
from agent_shell.adapters.response import collect_response
|
|
@@ -103,19 +112,13 @@ class OpenCodeAdapter():
|
|
|
103
112
|
logger.debug("Command: %s", cmd)
|
|
104
113
|
logger.info("Process started (cwd=%s)", os.path.abspath(cwd))
|
|
105
114
|
|
|
106
|
-
process = await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
stderr=asyncio.subprocess.PIPE,
|
|
111
|
-
cwd=os.path.abspath(cwd),
|
|
112
|
-
preexec_fn=os.setsid,
|
|
113
|
-
env=env,
|
|
115
|
+
process = await create_grouped_process(
|
|
116
|
+
cmd,
|
|
117
|
+
cwd=os.path.abspath(cwd),
|
|
118
|
+
env=env,
|
|
114
119
|
)
|
|
115
120
|
|
|
116
121
|
self._active_processes.append(process)
|
|
117
|
-
# setsid makes the child a session leader, so pgid == pid
|
|
118
|
-
register_process_group(process.pid)
|
|
119
122
|
|
|
120
123
|
# Drain stderr concurrently with stdout. Reading it only after the stdout loop can
|
|
121
124
|
# deadlock: a child that fills its stderr pipe buffer (~64KB) mid-run blocks on that
|
|
@@ -344,7 +347,7 @@ class OpenCodeAdapter():
|
|
|
344
347
|
|
|
345
348
|
async def cancel(self) -> None:
|
|
346
349
|
for process in self._active_processes:
|
|
347
|
-
kill_process_group(process
|
|
350
|
+
kill_process_group(process)
|
|
348
351
|
self._active_processes.clear()
|
|
349
352
|
|
|
350
353
|
async def health_check(
|
|
@@ -432,8 +435,10 @@ class OpenCodeAdapter():
|
|
|
432
435
|
|
|
433
436
|
for name, entry in servers.items():
|
|
434
437
|
if not isinstance(entry, dict):
|
|
438
|
+
actual_type = type(entry).__name__
|
|
435
439
|
warnings.warn(
|
|
436
|
-
f"Skipping malformed MCP entry '{name}':
|
|
440
|
+
f"Skipping malformed MCP entry '{name}': "
|
|
441
|
+
f"expected object, got {actual_type}",
|
|
437
442
|
UserWarning,
|
|
438
443
|
stacklevel=2,
|
|
439
444
|
)
|
|
@@ -7,9 +7,17 @@ import re
|
|
|
7
7
|
import warnings
|
|
8
8
|
from typing import AsyncIterator
|
|
9
9
|
|
|
10
|
-
from agent_shell.models.agent import
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
from agent_shell.models.agent import (
|
|
11
|
+
AgentResponse,
|
|
12
|
+
HealthCheckResult,
|
|
13
|
+
MCPServerSpec,
|
|
14
|
+
StreamEvent,
|
|
15
|
+
)
|
|
16
|
+
from agent_shell.process_cleanup import (
|
|
17
|
+
create_grouped_process,
|
|
18
|
+
kill_process_group,
|
|
19
|
+
release_process,
|
|
20
|
+
)
|
|
13
21
|
from agent_shell.adapters.health import run_health_probe
|
|
14
22
|
from agent_shell.adapters.model_discovery import decode_model_output, run_model_command
|
|
15
23
|
from agent_shell.adapters.response import collect_response
|
|
@@ -115,18 +123,12 @@ class PiAdapter:
|
|
|
115
123
|
logger.debug("Command: %s", cmd)
|
|
116
124
|
logger.info("Process started (cwd=%s)", os.path.abspath(cwd))
|
|
117
125
|
|
|
118
|
-
process = await
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
stdout=asyncio.subprocess.PIPE,
|
|
122
|
-
stderr=asyncio.subprocess.PIPE,
|
|
123
|
-
cwd=os.path.abspath(cwd),
|
|
124
|
-
preexec_fn=os.setsid,
|
|
126
|
+
process = await create_grouped_process(
|
|
127
|
+
cmd,
|
|
128
|
+
cwd=os.path.abspath(cwd),
|
|
125
129
|
)
|
|
126
130
|
|
|
127
131
|
self._active_processes.append(process)
|
|
128
|
-
# setsid makes the child a session leader, so pgid == pid
|
|
129
|
-
register_process_group(process.pid)
|
|
130
132
|
|
|
131
133
|
# Drain stderr concurrently with stdout. Reading it only after the stdout loop can
|
|
132
134
|
# deadlock: a child that fills its stderr pipe buffer (~64KB) mid-run blocks on that
|
|
@@ -307,7 +309,7 @@ class PiAdapter:
|
|
|
307
309
|
|
|
308
310
|
async def cancel(self) -> None:
|
|
309
311
|
for process in self._active_processes:
|
|
310
|
-
kill_process_group(process
|
|
312
|
+
kill_process_group(process)
|
|
311
313
|
self._active_processes.clear()
|
|
312
314
|
|
|
313
315
|
async def health_check(
|