aicortexpy 0.2.0__tar.gz → 0.2.2__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.
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/PKG-INFO +10 -7
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/README.md +9 -6
- aicortexpy-0.2.2/aicortex/__init__.py +9 -0
- aicortexpy-0.2.2/aicortex/__main__.py +5 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/__init__.py +1 -1
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/acp_client.py +112 -6
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/acp_conn.py +51 -7
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/agent_env.py +14 -3
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/attachments.py +3 -1
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/chat_acp_pool.py +10 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/cli.py +35 -3
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/client.py +9 -2
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/agent_cmd.py +37 -0
- aicortexpy-0.2.2/aicortexpy/commands/auth_cmd.py +167 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/autopilot_cmd.py +86 -2
- aicortexpy-0.2.2/aicortexpy/commands/daemon_cmd.py +228 -0
- aicortexpy-0.2.2/aicortexpy/commands/setup_cmd.py +159 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/skill_cmd.py +28 -6
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/squad_cmd.py +22 -1
- aicortexpy-0.2.2/aicortexpy/commands/update_cmd.py +46 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/config.py +82 -8
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/daemon.py +20 -1
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/id_resolve.py +17 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/models.py +8 -0
- aicortexpy-0.2.2/aicortexpy/oauth_login.py +183 -0
- aicortexpy-0.2.2/aicortexpy/output.py +77 -0
- aicortexpy-0.2.2/aicortexpy/permission.py +107 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/platform_client.py +8 -1
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/process.py +21 -14
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/runner.py +118 -6
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/sandbox.py +2 -2
- aicortexpy-0.2.2/aicortexpy/security.py +168 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/wakeup.py +15 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy.egg-info/PKG-INFO +10 -7
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy.egg-info/SOURCES.txt +10 -1
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy.egg-info/entry_points.txt +0 -1
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy.egg-info/top_level.txt +1 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/pyproject.toml +9 -4
- aicortexpy-0.2.2/scripts/aicortex +3 -0
- aicortexpy-0.2.2/scripts/aicortex.cmd +3 -0
- aicortexpy-0.2.0/aicortexpy/commands/auth_cmd.py +0 -87
- aicortexpy-0.2.0/aicortexpy/commands/daemon_cmd.py +0 -97
- aicortexpy-0.2.0/aicortexpy/output.py +0 -41
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/MANIFEST.in +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/__main__.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/acp_errors.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/artifact_ws.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/artifacts.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/auto_commit.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/capabilities.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/cli_context.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/codex_config.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/__init__.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/attachment_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/config_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/graph_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/issue_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/label_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/meeting_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/memory_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/project_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/repo_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/runtime_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/version_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/commands/workspace_cmd.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/agent-memory/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-basics/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-graph/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/graphify/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/question-form/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/test-report-generator/SKILL.md +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/design_seed.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/diskusage.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/execenv.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/gc.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/handoff_bundle.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/health.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/local_skills.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/openclaw_config.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/pinned_workdir.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/platform_info.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/poisoned.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/procutil.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/prompt.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/repo.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/runtime_config.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/stream.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/terminal.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy/tunnel.py +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy.egg-info/dependency_links.txt +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/aicortexpy.egg-info/requires.txt +0 -0
- {aicortexpy-0.2.0 → aicortexpy-0.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: aicortexpy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: AICortex Python CLI and local agent runtime daemon (Go CLI compatible)
|
|
5
5
|
Author: AICortex
|
|
6
6
|
License: Apache-2.0
|
|
@@ -26,20 +26,23 @@ aicortex daemon start
|
|
|
26
26
|
aicortex issue list --output json
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
等价入口:`python -m aicortex`(推荐)/ `python -m aicortexpy` / `aicortexpy`。
|
|
30
30
|
|
|
31
31
|
| | |
|
|
32
32
|
|--|--|
|
|
33
|
-
| PyPI
|
|
34
|
-
|
|
|
33
|
+
| PyPI 包名 | `aicortexpy` |
|
|
34
|
+
| 模块 | `aicortex`(`-m`)、`aicortexpy` |
|
|
35
|
+
| PATH 命令 | Windows:`aicortex.cmd` → `python -m aicortex`(**不**生成 `aicortex.exe`,避免与 Go 安装器 / `.exe` 探测冲突);另有 `aicortexpy.exe` |
|
|
35
36
|
| 配置 | 与 Go 共用 `~/.aicortex/config.json` |
|
|
36
37
|
|
|
37
38
|
## 安装
|
|
38
39
|
|
|
39
40
|
```powershell
|
|
40
41
|
pip install -U aicortexpy
|
|
42
|
+
# 若以前装过会留下 Scripts\aicortex.exe,可手动删掉,让 aicortex.cmd 生效:
|
|
43
|
+
# Remove-Item "$env:LOCALAPPDATA\Programs\Python\Python3*\Scripts\aicortex.exe" -ErrorAction SilentlyContinue
|
|
41
44
|
# 源码:
|
|
42
|
-
cd python-
|
|
45
|
+
cd python-aicortex
|
|
43
46
|
pip install -e .
|
|
44
47
|
```
|
|
45
48
|
|
|
@@ -73,7 +76,7 @@ Health 默认端口 **19514**。不要与 Go daemon 同时跑。
|
|
|
73
76
|
## 发布
|
|
74
77
|
|
|
75
78
|
```powershell
|
|
76
|
-
cd python-
|
|
79
|
+
cd python-aicortex
|
|
77
80
|
python -m build
|
|
78
81
|
python -m twine upload dist/*
|
|
79
82
|
```
|
|
@@ -84,4 +87,4 @@ python -m twine upload dist/*
|
|
|
84
87
|
|
|
85
88
|
已覆盖 Agent/日常平台命令(issue/workspace/agent/squad/project/label/attachment/autopilot/meeting/skill/memory/graph/runtime/repo/auth/config/daemon)。
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
相对 Go:`setup` / `update`(pip) / OAuth login / 平台命令已对齐;二进制自更新请用 `pip install -U aicortexpy`。
|
|
@@ -9,20 +9,23 @@ aicortex daemon start
|
|
|
9
9
|
aicortex issue list --output json
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
等价入口:`python -m aicortex`(推荐)/ `python -m aicortexpy` / `aicortexpy`。
|
|
13
13
|
|
|
14
14
|
| | |
|
|
15
15
|
|--|--|
|
|
16
|
-
| PyPI
|
|
17
|
-
|
|
|
16
|
+
| PyPI 包名 | `aicortexpy` |
|
|
17
|
+
| 模块 | `aicortex`(`-m`)、`aicortexpy` |
|
|
18
|
+
| PATH 命令 | Windows:`aicortex.cmd` → `python -m aicortex`(**不**生成 `aicortex.exe`,避免与 Go 安装器 / `.exe` 探测冲突);另有 `aicortexpy.exe` |
|
|
18
19
|
| 配置 | 与 Go 共用 `~/.aicortex/config.json` |
|
|
19
20
|
|
|
20
21
|
## 安装
|
|
21
22
|
|
|
22
23
|
```powershell
|
|
23
24
|
pip install -U aicortexpy
|
|
25
|
+
# 若以前装过会留下 Scripts\aicortex.exe,可手动删掉,让 aicortex.cmd 生效:
|
|
26
|
+
# Remove-Item "$env:LOCALAPPDATA\Programs\Python\Python3*\Scripts\aicortex.exe" -ErrorAction SilentlyContinue
|
|
24
27
|
# 源码:
|
|
25
|
-
cd python-
|
|
28
|
+
cd python-aicortex
|
|
26
29
|
pip install -e .
|
|
27
30
|
```
|
|
28
31
|
|
|
@@ -56,7 +59,7 @@ Health 默认端口 **19514**。不要与 Go daemon 同时跑。
|
|
|
56
59
|
## 发布
|
|
57
60
|
|
|
58
61
|
```powershell
|
|
59
|
-
cd python-
|
|
62
|
+
cd python-aicortex
|
|
60
63
|
python -m build
|
|
61
64
|
python -m twine upload dist/*
|
|
62
65
|
```
|
|
@@ -67,4 +70,4 @@ python -m twine upload dist/*
|
|
|
67
70
|
|
|
68
71
|
已覆盖 Agent/日常平台命令(issue/workspace/agent/squad/project/label/attachment/autopilot/meeting/skill/memory/graph/runtime/repo/auth/config/daemon)。
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
相对 Go:`setup` / `update`(pip) / OAuth login / 平台命令已对齐;二进制自更新请用 `pip install -U aicortexpy`。
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Top-level alias package so `python -m aicortex` works after install.
|
|
2
|
+
|
|
3
|
+
The implementation lives in ``aicortexpy``; this package exists only as the
|
|
4
|
+
``-m aicortex`` / PATH shim target (especially on Windows via ``aicortex.cmd``).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from aicortexpy import __version__
|
|
8
|
+
|
|
9
|
+
__all__ = ["__version__"]
|
|
@@ -51,7 +51,15 @@ class _PendingTool:
|
|
|
51
51
|
class ACPClient:
|
|
52
52
|
"""Thread-safe JSON-RPC client for one ACP child process."""
|
|
53
53
|
|
|
54
|
-
def __init__(
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
stdin: TextIO,
|
|
57
|
+
*,
|
|
58
|
+
tool_name_fn: Callable[[str, str], str] | None = None,
|
|
59
|
+
security_policy: Any = None,
|
|
60
|
+
permissions: Any = None,
|
|
61
|
+
task_id: str = "",
|
|
62
|
+
) -> None:
|
|
55
63
|
self._stdin = stdin
|
|
56
64
|
self._write_lock = threading.Lock()
|
|
57
65
|
self._mu = threading.Lock()
|
|
@@ -60,6 +68,9 @@ class ACPClient:
|
|
|
60
68
|
self._tool_mu = threading.Lock()
|
|
61
69
|
self._pending_tools: dict[str, _PendingTool] = {}
|
|
62
70
|
self.tool_name_fn = tool_name_fn or default_tool_name
|
|
71
|
+
self.security_policy = security_policy
|
|
72
|
+
self.permissions = permissions
|
|
73
|
+
self.task_id = task_id
|
|
63
74
|
self.session_id = ""
|
|
64
75
|
self.on_message: MessageCB | None = None
|
|
65
76
|
self.on_prompt_done: PromptDoneCB | None = None
|
|
@@ -155,9 +166,9 @@ class ACPClient:
|
|
|
155
166
|
method = str(raw.get("method") or "")
|
|
156
167
|
req_id = raw.get("id")
|
|
157
168
|
if method == "session/request_permission":
|
|
158
|
-
|
|
169
|
+
params = raw.get("params")
|
|
170
|
+
option_id = self._resolve_permission_option(params)
|
|
159
171
|
result = {"outcome": {"outcome": "selected", "optionId": option_id}}
|
|
160
|
-
log.debug("auto-approved permission option_id=%s", option_id)
|
|
161
172
|
elif method == "cursor/ask_question":
|
|
162
173
|
result = {
|
|
163
174
|
"outcome": {
|
|
@@ -194,6 +205,83 @@ class ACPClient:
|
|
|
194
205
|
except OSError as e:
|
|
195
206
|
log.warning("write agent-request response failed: %s", e)
|
|
196
207
|
|
|
208
|
+
def _resolve_permission_option(self, params: Any) -> str:
|
|
209
|
+
from .permission import (
|
|
210
|
+
DEFAULT_PERMISSION_TIMEOUT,
|
|
211
|
+
extract_tool_meta,
|
|
212
|
+
parse_permission_options,
|
|
213
|
+
)
|
|
214
|
+
from .security import (
|
|
215
|
+
extract_permission_paths,
|
|
216
|
+
permission_should_deny,
|
|
217
|
+
select_reject_permission_option_id,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
policy = self.security_policy
|
|
221
|
+
if policy is not None and permission_should_deny(policy, params):
|
|
222
|
+
option_id = select_reject_permission_option_id(params)
|
|
223
|
+
log.info("denied permission outside allowed roots option_id=%s", option_id)
|
|
224
|
+
return option_id
|
|
225
|
+
|
|
226
|
+
permit_all = True if policy is None else bool(getattr(policy, "permit_all", True))
|
|
227
|
+
if permit_all or self.permissions is None:
|
|
228
|
+
option_id = _select_permission_option_id(params)
|
|
229
|
+
log.debug("auto-approved permission option_id=%s", option_id)
|
|
230
|
+
return option_id
|
|
231
|
+
|
|
232
|
+
title, tool_call_id = extract_tool_meta(params)
|
|
233
|
+
options = parse_permission_options(params)
|
|
234
|
+
permission_id, wait_ev = self.permissions.register("")
|
|
235
|
+
paths = extract_permission_paths(params)
|
|
236
|
+
input_payload = {
|
|
237
|
+
"permission_id": permission_id,
|
|
238
|
+
"task_id": self.task_id,
|
|
239
|
+
"options": [o.to_dict() for o in options],
|
|
240
|
+
"paths": paths,
|
|
241
|
+
"tool_call_id": tool_call_id,
|
|
242
|
+
}
|
|
243
|
+
if self.on_message is not None:
|
|
244
|
+
self.on_message(
|
|
245
|
+
{
|
|
246
|
+
"type": "permission-request",
|
|
247
|
+
"tool": title,
|
|
248
|
+
"call_id": permission_id,
|
|
249
|
+
"content": title,
|
|
250
|
+
"input": input_payload,
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
option_id = self.permissions.wait(
|
|
255
|
+
permission_id, wait_ev, DEFAULT_PERMISSION_TIMEOUT
|
|
256
|
+
)
|
|
257
|
+
if not option_id:
|
|
258
|
+
option_id = select_reject_permission_option_id(params)
|
|
259
|
+
log.info(
|
|
260
|
+
"permission request timed out or cancelled; denying permission_id=%s option_id=%s",
|
|
261
|
+
permission_id,
|
|
262
|
+
option_id,
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
log.info(
|
|
266
|
+
"permission resolved by user permission_id=%s option_id=%s",
|
|
267
|
+
permission_id,
|
|
268
|
+
option_id,
|
|
269
|
+
)
|
|
270
|
+
if self.on_message is not None:
|
|
271
|
+
self.on_message(
|
|
272
|
+
{
|
|
273
|
+
"type": "permission-result",
|
|
274
|
+
"tool": title,
|
|
275
|
+
"call_id": permission_id,
|
|
276
|
+
"content": option_id,
|
|
277
|
+
"input": {
|
|
278
|
+
"permission_id": permission_id,
|
|
279
|
+
"option_id": option_id,
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
return option_id
|
|
284
|
+
|
|
197
285
|
def _handle_notification(self, raw: dict[str, Any]) -> None:
|
|
198
286
|
method = str(raw.get("method") or "")
|
|
199
287
|
if method not in ("session/update", "session/notification"):
|
|
@@ -236,7 +324,9 @@ class ACPClient:
|
|
|
236
324
|
if self.on_message:
|
|
237
325
|
self.on_message(
|
|
238
326
|
{
|
|
239
|
-
|
|
327
|
+
# Server/UI wire format uses underscores (Go daemon maps
|
|
328
|
+
# internal "tool-use" → "tool_use" before ReportTaskMessages).
|
|
329
|
+
"type": "tool_use",
|
|
240
330
|
"tool": tool_name,
|
|
241
331
|
"call_id": call_id,
|
|
242
332
|
"input": raw_input,
|
|
@@ -271,8 +361,18 @@ class ACPClient:
|
|
|
271
361
|
output = str(data.get("rawOutput") or data.get("output") or "")
|
|
272
362
|
if not output:
|
|
273
363
|
output = _extract_tool_call_text(content)
|
|
364
|
+
tool_name = ""
|
|
365
|
+
if pending is not None:
|
|
366
|
+
tool_name = pending.tool_name or ""
|
|
274
367
|
if self.on_message:
|
|
275
|
-
self.on_message(
|
|
368
|
+
self.on_message(
|
|
369
|
+
{
|
|
370
|
+
"type": "tool_result",
|
|
371
|
+
"tool": tool_name or "tool",
|
|
372
|
+
"call_id": call_id,
|
|
373
|
+
"output": output,
|
|
374
|
+
}
|
|
375
|
+
)
|
|
276
376
|
|
|
277
377
|
def _emit_deferred_tool_use(self, pending: _PendingTool | None, call_id: str, data: dict) -> None:
|
|
278
378
|
if pending is not None and pending.emitted:
|
|
@@ -300,7 +400,12 @@ class ACPClient:
|
|
|
300
400
|
inp = {}
|
|
301
401
|
if self.on_message:
|
|
302
402
|
self.on_message(
|
|
303
|
-
{
|
|
403
|
+
{
|
|
404
|
+
"type": "tool_use",
|
|
405
|
+
"tool": tool_name,
|
|
406
|
+
"call_id": call_id,
|
|
407
|
+
"input": inp,
|
|
408
|
+
}
|
|
304
409
|
)
|
|
305
410
|
|
|
306
411
|
def _track_tool(self, call_id: str, p: _PendingTool) -> None:
|
|
@@ -397,6 +502,7 @@ def _select_permission_option_id(params: Any) -> str:
|
|
|
397
502
|
return fallback
|
|
398
503
|
|
|
399
504
|
|
|
505
|
+
|
|
400
506
|
def _normalize_update(data: Any) -> tuple[str, Any]:
|
|
401
507
|
if not isinstance(data, dict):
|
|
402
508
|
return "", data
|
|
@@ -25,6 +25,7 @@ from .acp_client import (
|
|
|
25
25
|
)
|
|
26
26
|
from .acp_errors import ACPProviderErrorSniffer, promote_acp_result_on_provider_error
|
|
27
27
|
from .procutil import kill_process_tree, popen_kwargs_for_killable_group
|
|
28
|
+
from .security import SecurityPolicy, build_cursor_acp_global_flags, default_security_policy
|
|
28
29
|
|
|
29
30
|
log = logging.getLogger("aicortexpy.acp")
|
|
30
31
|
|
|
@@ -203,6 +204,8 @@ class _BaseACPConn:
|
|
|
203
204
|
on_message: MessageCB | None = None,
|
|
204
205
|
idle_watchdog: float = 0,
|
|
205
206
|
cancel_event: threading.Event | None = None,
|
|
207
|
+
permissions: Any = None,
|
|
208
|
+
task_id: str = "",
|
|
206
209
|
) -> ACPPromptResult:
|
|
207
210
|
if self._closed or not self._client or not self._session_id:
|
|
208
211
|
return ACPPromptResult(
|
|
@@ -211,6 +214,10 @@ class _BaseACPConn:
|
|
|
211
214
|
error=f"{self.provider} acp connection closed",
|
|
212
215
|
)
|
|
213
216
|
client = self._client
|
|
217
|
+
if permissions is not None:
|
|
218
|
+
client.permissions = permissions
|
|
219
|
+
if task_id:
|
|
220
|
+
client.task_id = task_id
|
|
214
221
|
output_parts: list[str] = []
|
|
215
222
|
messages: list[dict[str, Any]] = []
|
|
216
223
|
prompt_done = threading.Event()
|
|
@@ -226,9 +233,9 @@ class _BaseACPConn:
|
|
|
226
233
|
typ = msg.get("type")
|
|
227
234
|
if typ == "text":
|
|
228
235
|
output_parts.append(str(msg.get("content") or ""))
|
|
229
|
-
elif typ
|
|
236
|
+
elif typ in ("tool_use", "tool-use"):
|
|
230
237
|
in_flight_tools["n"] += 1
|
|
231
|
-
elif typ
|
|
238
|
+
elif typ in ("tool_result", "tool-result"):
|
|
232
239
|
in_flight_tools["n"] = max(0, in_flight_tools["n"] - 1)
|
|
233
240
|
messages.append(msg)
|
|
234
241
|
if on_message:
|
|
@@ -368,6 +375,7 @@ class _BaseACPConn:
|
|
|
368
375
|
env: dict[str, str] | None,
|
|
369
376
|
*,
|
|
370
377
|
tool_name_fn: Callable[[str, str], str],
|
|
378
|
+
security_policy: SecurityPolicy | None = None,
|
|
371
379
|
) -> None:
|
|
372
380
|
kwargs = popen_kwargs_for_killable_group()
|
|
373
381
|
kwargs.update(_hide_window())
|
|
@@ -393,7 +401,11 @@ class _BaseACPConn:
|
|
|
393
401
|
write_through=True,
|
|
394
402
|
line_buffering=True,
|
|
395
403
|
)
|
|
396
|
-
self._client = ACPClient(
|
|
404
|
+
self._client = ACPClient(
|
|
405
|
+
self._stdin_text,
|
|
406
|
+
tool_name_fn=tool_name_fn,
|
|
407
|
+
security_policy=security_policy or default_security_policy(),
|
|
408
|
+
)
|
|
397
409
|
threading.Thread(target=self._read_stdout, name=f"{self.provider}-acp-out", daemon=True).start()
|
|
398
410
|
threading.Thread(target=self._read_stderr, name=f"{self.provider}-acp-err", daemon=True).start()
|
|
399
411
|
log.info("%s acp started pid=%s cwd=%s", self.provider, self._proc.pid, cwd)
|
|
@@ -557,16 +569,26 @@ class CursorACPConn(_BaseACPConn):
|
|
|
557
569
|
timeout: float = 0,
|
|
558
570
|
env: dict[str, str] | None = None,
|
|
559
571
|
allow_resume: bool = False,
|
|
572
|
+
security_policy: SecurityPolicy | None = None,
|
|
560
573
|
) -> CursorACPConn:
|
|
561
574
|
path = shutil.which(executable) or executable
|
|
575
|
+
policy = security_policy or default_security_policy()
|
|
562
576
|
blocked = {"acp", "--yolo", "--force", "--sandbox", "--trust"}
|
|
563
577
|
filtered = _filter_blocked(list(custom_args or []), blocked, {"--sandbox"})
|
|
564
|
-
acp_args = [
|
|
578
|
+
acp_args = [*build_cursor_acp_global_flags(policy), "acp", *filtered]
|
|
565
579
|
argv0, cmd_args = choose_cursor_invocation(path, acp_args)
|
|
566
580
|
argv = [argv0, *cmd_args]
|
|
567
581
|
conn = cls()
|
|
568
582
|
work = cwd or str(Path.cwd())
|
|
569
|
-
|
|
583
|
+
if policy.allowed_roots_enabled and work and work not in policy.allowed_roots:
|
|
584
|
+
policy.allowed_roots = [*policy.allowed_roots, work]
|
|
585
|
+
conn._start_process(
|
|
586
|
+
argv,
|
|
587
|
+
work,
|
|
588
|
+
env,
|
|
589
|
+
tool_name_fn=hermes_tool_name_from_title,
|
|
590
|
+
security_policy=policy,
|
|
591
|
+
)
|
|
570
592
|
hs = HANDSHAKE_TIMEOUT
|
|
571
593
|
if timeout > 0:
|
|
572
594
|
hs = min(hs, timeout)
|
|
@@ -600,16 +622,29 @@ class KiroACPConn(_BaseACPConn):
|
|
|
600
622
|
custom_args: list[str] | None = None,
|
|
601
623
|
timeout: float = 0,
|
|
602
624
|
env: dict[str, str] | None = None,
|
|
625
|
+
security_policy: SecurityPolicy | None = None,
|
|
603
626
|
) -> KiroACPConn:
|
|
604
627
|
path = shutil.which(executable) or executable
|
|
628
|
+
policy = security_policy or default_security_policy()
|
|
605
629
|
blocked = {"acp", "-a", "--trust-all-tools", "--trust-tools"}
|
|
606
630
|
filtered = _filter_blocked(
|
|
607
631
|
list(custom_args or []), blocked, {"--trust-tools"}
|
|
608
632
|
)
|
|
609
|
-
argv = [path, "acp"
|
|
633
|
+
argv = [path, "acp"]
|
|
634
|
+
if policy.permit_all:
|
|
635
|
+
argv.append("--trust-all-tools")
|
|
636
|
+
argv.extend(filtered)
|
|
610
637
|
conn = cls()
|
|
611
638
|
work = cwd or str(Path.cwd())
|
|
612
|
-
|
|
639
|
+
if policy.allowed_roots_enabled and work and work not in policy.allowed_roots:
|
|
640
|
+
policy.allowed_roots = [*policy.allowed_roots, work]
|
|
641
|
+
conn._start_process(
|
|
642
|
+
argv,
|
|
643
|
+
work,
|
|
644
|
+
env,
|
|
645
|
+
tool_name_fn=kiro_tool_name,
|
|
646
|
+
security_policy=policy,
|
|
647
|
+
)
|
|
613
648
|
hs = timeout if timeout > 0 else HANDSHAKE_TIMEOUT
|
|
614
649
|
try:
|
|
615
650
|
conn._handshake(
|
|
@@ -754,6 +789,7 @@ def open_acp_conn(
|
|
|
754
789
|
custom_args: list[str] | None = None,
|
|
755
790
|
timeout: float = 0,
|
|
756
791
|
env: dict[str, str] | None = None,
|
|
792
|
+
security_policy: SecurityPolicy | None = None,
|
|
757
793
|
) -> _BaseACPConn:
|
|
758
794
|
if provider == "cursor":
|
|
759
795
|
return CursorACPConn.open(
|
|
@@ -765,6 +801,7 @@ def open_acp_conn(
|
|
|
765
801
|
timeout=timeout,
|
|
766
802
|
env=env,
|
|
767
803
|
allow_resume=False,
|
|
804
|
+
security_policy=security_policy,
|
|
768
805
|
)
|
|
769
806
|
if provider == "kiro":
|
|
770
807
|
return KiroACPConn.open(
|
|
@@ -775,6 +812,7 @@ def open_acp_conn(
|
|
|
775
812
|
custom_args=custom_args,
|
|
776
813
|
timeout=timeout,
|
|
777
814
|
env=env,
|
|
815
|
+
security_policy=security_policy,
|
|
778
816
|
)
|
|
779
817
|
if provider == "hermes":
|
|
780
818
|
return HermesACPConn.open(
|
|
@@ -824,6 +862,9 @@ def run_acp_oneshot(
|
|
|
824
862
|
on_message: MessageCB | None = None,
|
|
825
863
|
cancel_event: threading.Event | None = None,
|
|
826
864
|
idle_watchdog: float = 0,
|
|
865
|
+
security_policy: SecurityPolicy | None = None,
|
|
866
|
+
permissions: Any = None,
|
|
867
|
+
task_id: str = "",
|
|
827
868
|
) -> ACPPromptResult:
|
|
828
869
|
"""One-shot ACP turn (open → prompt → close). Used for non-chat kiro/hermes/kimi/dsh."""
|
|
829
870
|
t = timeout if timeout > 0 else PROMPT_TIMEOUT
|
|
@@ -837,6 +878,7 @@ def run_acp_oneshot(
|
|
|
837
878
|
custom_args=custom_args,
|
|
838
879
|
timeout=t,
|
|
839
880
|
env=env,
|
|
881
|
+
security_policy=security_policy,
|
|
840
882
|
)
|
|
841
883
|
except Exception as e:
|
|
842
884
|
return ACPPromptResult(status="failed", output="", error=f"{provider} acp open failed: {e}")
|
|
@@ -848,6 +890,8 @@ def run_acp_oneshot(
|
|
|
848
890
|
on_message=on_message,
|
|
849
891
|
idle_watchdog=idle_watchdog,
|
|
850
892
|
cancel_event=cancel_event,
|
|
893
|
+
permissions=permissions,
|
|
894
|
+
task_id=task_id,
|
|
851
895
|
)
|
|
852
896
|
finally:
|
|
853
897
|
conn.close()
|
|
@@ -51,7 +51,11 @@ def _aicortex_bin_dir() -> str | None:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def ensure_cli_shim_dir(state_dir: Path) -> Path | None:
|
|
54
|
-
"""Write a tiny aicortex shim so agents resolve to python -m
|
|
54
|
+
"""Write a tiny aicortex shim so agents resolve to `python -m aicortex`.
|
|
55
|
+
|
|
56
|
+
Prefer .cmd on Windows so PATH resolution does not depend on aicortex.exe
|
|
57
|
+
(pip console_scripts launcher / Go installer binary detection).
|
|
58
|
+
"""
|
|
55
59
|
shim_dir = state_dir / "py-cli-shim"
|
|
56
60
|
try:
|
|
57
61
|
shim_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -63,12 +67,19 @@ def ensure_cli_shim_dir(state_dir: Path) -> Path | None:
|
|
|
63
67
|
if sys.platform == "win32":
|
|
64
68
|
cmd = shim_dir / "aicortex.cmd"
|
|
65
69
|
cmd.write_text(
|
|
66
|
-
f'@echo off\r\n"{py}" -m
|
|
70
|
+
f'@echo off\r\n"{py}" -m aicortex %*\r\n',
|
|
67
71
|
encoding="utf-8",
|
|
68
72
|
)
|
|
73
|
+
# Drop a stale pip console_scripts aicortex.exe in this shim dir if any.
|
|
74
|
+
stale = shim_dir / "aicortex.exe"
|
|
75
|
+
if stale.is_file():
|
|
76
|
+
try:
|
|
77
|
+
stale.unlink()
|
|
78
|
+
except OSError:
|
|
79
|
+
pass
|
|
69
80
|
else:
|
|
70
81
|
sh = shim_dir / "aicortex"
|
|
71
|
-
sh.write_text(f'#!/bin/sh\nexec "{py}" -m
|
|
82
|
+
sh.write_text(f'#!/bin/sh\nexec "{py}" -m aicortex "$@"\n', encoding="utf-8")
|
|
72
83
|
try:
|
|
73
84
|
sh.chmod(0o755)
|
|
74
85
|
except OSError:
|
|
@@ -61,7 +61,9 @@ def materialize_attachments(
|
|
|
61
61
|
if not att_id:
|
|
62
62
|
continue
|
|
63
63
|
try:
|
|
64
|
-
body, header_name = client.fetch_attachment_bytes(
|
|
64
|
+
body, header_name = client.fetch_attachment_bytes(
|
|
65
|
+
att_id, task.workspace_id
|
|
66
|
+
)
|
|
65
67
|
except Exception as e:
|
|
66
68
|
log.warning("fetch attachment failed id=%s: %s", att_id, e)
|
|
67
69
|
continue
|
|
@@ -17,6 +17,7 @@ from .acp_conn import (
|
|
|
17
17
|
kiro_acp_supported,
|
|
18
18
|
)
|
|
19
19
|
from .config import AgentEntry, Config
|
|
20
|
+
from .security import SecurityPolicy
|
|
20
21
|
|
|
21
22
|
log = logging.getLogger("aicortexpy.acp_pool")
|
|
22
23
|
|
|
@@ -101,6 +102,9 @@ class ChatACPPool:
|
|
|
101
102
|
on_message: Any | None = None,
|
|
102
103
|
idle_watchdog: float = 0,
|
|
103
104
|
system_prompt: str = "",
|
|
105
|
+
security_policy: SecurityPolicy | None = None,
|
|
106
|
+
permissions: Any = None,
|
|
107
|
+
task_id: str = "",
|
|
104
108
|
) -> ACPPromptResult:
|
|
105
109
|
key = pool_key(provider, chat_session_id, agent_id, work_dir)
|
|
106
110
|
entry = self._get_or_create(key, provider, chat_session_id, agent_id, work_dir, model)
|
|
@@ -121,6 +125,7 @@ class ChatACPPool:
|
|
|
121
125
|
custom_args=custom_args,
|
|
122
126
|
timeout=timeout,
|
|
123
127
|
env=env,
|
|
128
|
+
security_policy=security_policy,
|
|
124
129
|
)
|
|
125
130
|
entry.model = model
|
|
126
131
|
log.info("%s chat acp pool miss chat=%s", provider, chat_session_id[:8])
|
|
@@ -144,6 +149,8 @@ class ChatACPPool:
|
|
|
144
149
|
on_message=on_message,
|
|
145
150
|
idle_watchdog=idle_watchdog,
|
|
146
151
|
cancel_event=cancel_event,
|
|
152
|
+
permissions=permissions,
|
|
153
|
+
task_id=task_id,
|
|
147
154
|
)
|
|
148
155
|
|
|
149
156
|
if not result.session_id and entry.conn.session_id():
|
|
@@ -173,6 +180,7 @@ class ChatACPPool:
|
|
|
173
180
|
custom_args: list[str],
|
|
174
181
|
timeout: float,
|
|
175
182
|
env: dict[str, str] | None,
|
|
183
|
+
security_policy: SecurityPolicy | None = None,
|
|
176
184
|
) -> ACPChatConn:
|
|
177
185
|
entry = self._agents.get(provider)
|
|
178
186
|
if not entry:
|
|
@@ -186,6 +194,7 @@ class ChatACPPool:
|
|
|
186
194
|
custom_args=custom_args,
|
|
187
195
|
timeout=timeout,
|
|
188
196
|
env=env,
|
|
197
|
+
security_policy=security_policy,
|
|
189
198
|
)
|
|
190
199
|
if provider == "kiro":
|
|
191
200
|
return KiroACPConn.open(
|
|
@@ -196,6 +205,7 @@ class ChatACPPool:
|
|
|
196
205
|
custom_args=custom_args,
|
|
197
206
|
timeout=timeout,
|
|
198
207
|
env=env,
|
|
208
|
+
security_policy=security_policy,
|
|
199
209
|
)
|
|
200
210
|
raise RuntimeError(f"chat acp pool: unsupported provider {provider!r}")
|
|
201
211
|
|
|
@@ -22,8 +22,10 @@ from .commands import (
|
|
|
22
22
|
project_cmd,
|
|
23
23
|
repo_cmd,
|
|
24
24
|
runtime_cmd,
|
|
25
|
+
setup_cmd,
|
|
25
26
|
skill_cmd,
|
|
26
27
|
squad_cmd,
|
|
28
|
+
update_cmd,
|
|
27
29
|
version_cmd,
|
|
28
30
|
workspace_cmd,
|
|
29
31
|
)
|
|
@@ -41,12 +43,31 @@ _LEGACY_DAEMON = {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
|
|
44
|
-
def _setup_logging(
|
|
45
|
-
|
|
46
|
+
def _setup_logging(
|
|
47
|
+
verbose: bool = False,
|
|
48
|
+
*,
|
|
49
|
+
log_level: str = "",
|
|
50
|
+
daemon_mode: bool = False,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Configure root logging.
|
|
53
|
+
|
|
54
|
+
- CLI one-shots default to WARNING (quiet).
|
|
55
|
+
- Daemon run defaults to INFO (useful in python-aicortex.log).
|
|
56
|
+
- `-v` or `--log-level DEBUG` enables DEBUG.
|
|
57
|
+
"""
|
|
58
|
+
if log_level:
|
|
59
|
+
level = getattr(logging, log_level.upper(), logging.INFO)
|
|
60
|
+
elif verbose:
|
|
61
|
+
level = logging.DEBUG
|
|
62
|
+
elif daemon_mode:
|
|
63
|
+
level = logging.INFO
|
|
64
|
+
else:
|
|
65
|
+
level = logging.WARNING
|
|
46
66
|
logging.basicConfig(
|
|
47
67
|
level=level,
|
|
48
68
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
49
69
|
datefmt="%H:%M:%S",
|
|
70
|
+
force=True,
|
|
50
71
|
)
|
|
51
72
|
|
|
52
73
|
|
|
@@ -80,6 +101,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
80
101
|
config_cmd.add_parser(sub)
|
|
81
102
|
daemon_cmd.add_parser(sub)
|
|
82
103
|
runtime_cmd.add_parser(sub)
|
|
104
|
+
setup_cmd.add_parser(sub)
|
|
105
|
+
update_cmd.add_parser(sub)
|
|
83
106
|
version_cmd.add_parser(sub)
|
|
84
107
|
return p
|
|
85
108
|
|
|
@@ -100,8 +123,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
100
123
|
except SystemExit as e:
|
|
101
124
|
return int(e.code) if isinstance(e.code, int) else 1
|
|
102
125
|
|
|
103
|
-
_setup_logging(getattr(ns, "verbose", False))
|
|
104
126
|
cmd = ns.command
|
|
127
|
+
_setup_logging(
|
|
128
|
+
getattr(ns, "verbose", False),
|
|
129
|
+
log_level=str(getattr(ns, "log_level", "") or ""),
|
|
130
|
+
daemon_mode=(
|
|
131
|
+
cmd == "daemon"
|
|
132
|
+
and getattr(ns, "daemon_command", "") in {"start", "run", "restart"}
|
|
133
|
+
),
|
|
134
|
+
)
|
|
105
135
|
|
|
106
136
|
handlers = {
|
|
107
137
|
"issue": issue_cmd.run,
|
|
@@ -123,6 +153,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
123
153
|
"version": version_cmd.run,
|
|
124
154
|
"login": auth_cmd.run_login,
|
|
125
155
|
"auth": auth_cmd.run_auth,
|
|
156
|
+
"setup": setup_cmd.run,
|
|
157
|
+
"update": update_cmd.run,
|
|
126
158
|
}
|
|
127
159
|
handler = handlers.get(cmd)
|
|
128
160
|
if not handler:
|
|
@@ -422,10 +422,17 @@ class DaemonClient:
|
|
|
422
422
|
data = self.get(f"/api/daemon/workspaces/{workspace_id}/repos")
|
|
423
423
|
return data if isinstance(data, dict) else {}
|
|
424
424
|
|
|
425
|
-
def fetch_attachment_bytes(
|
|
425
|
+
def fetch_attachment_bytes(
|
|
426
|
+
self, attachment_id: str, workspace_id: str = ""
|
|
427
|
+
) -> tuple[bytes, str]:
|
|
426
428
|
path = f"/api/daemon/attachments/{attachment_id}/bytes"
|
|
427
429
|
url = self.base_url + path
|
|
428
|
-
|
|
430
|
+
headers = self._headers()
|
|
431
|
+
wid = (workspace_id or "").strip()
|
|
432
|
+
if wid:
|
|
433
|
+
# PAT/JWT daemon auth has no workspace in context; server requires it.
|
|
434
|
+
headers["X-Workspace-ID"] = wid
|
|
435
|
+
req = urllib.request.Request(url, headers=headers, method="GET")
|
|
429
436
|
try:
|
|
430
437
|
with urllib.request.urlopen(req, timeout=self.timeout, context=self._ctx) as resp:
|
|
431
438
|
body = resp.read(MAX_ATTACHMENT_BYTES + 1)
|