aicortexpy 0.2.1__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.
Files changed (97) hide show
  1. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/PKG-INFO +10 -7
  2. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/README.md +9 -6
  3. aicortexpy-0.2.2/aicortex/__init__.py +9 -0
  4. aicortexpy-0.2.2/aicortex/__main__.py +5 -0
  5. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/__init__.py +1 -1
  6. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/acp_client.py +92 -3
  7. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/acp_conn.py +49 -5
  8. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/agent_env.py +14 -3
  9. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/chat_acp_pool.py +10 -0
  10. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/cli.py +1 -1
  11. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/config.py +1 -1
  12. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/daemon.py +20 -1
  13. aicortexpy-0.2.2/aicortexpy/permission.py +107 -0
  14. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/process.py +19 -13
  15. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/runner.py +68 -4
  16. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/sandbox.py +2 -2
  17. aicortexpy-0.2.2/aicortexpy/security.py +168 -0
  18. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/wakeup.py +15 -0
  19. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy.egg-info/PKG-INFO +10 -7
  20. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy.egg-info/SOURCES.txt +7 -1
  21. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy.egg-info/entry_points.txt +0 -1
  22. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy.egg-info/top_level.txt +1 -0
  23. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/pyproject.toml +9 -4
  24. aicortexpy-0.2.2/scripts/aicortex +3 -0
  25. aicortexpy-0.2.2/scripts/aicortex.cmd +3 -0
  26. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/MANIFEST.in +0 -0
  27. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/__main__.py +0 -0
  28. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/acp_errors.py +0 -0
  29. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/artifact_ws.py +0 -0
  30. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/artifacts.py +0 -0
  31. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/attachments.py +0 -0
  32. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/auto_commit.py +0 -0
  33. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/capabilities.py +0 -0
  34. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/cli_context.py +0 -0
  35. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/client.py +0 -0
  36. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/codex_config.py +0 -0
  37. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/__init__.py +0 -0
  38. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/agent_cmd.py +0 -0
  39. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/attachment_cmd.py +0 -0
  40. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/auth_cmd.py +0 -0
  41. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/autopilot_cmd.py +0 -0
  42. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/config_cmd.py +0 -0
  43. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/daemon_cmd.py +0 -0
  44. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/graph_cmd.py +0 -0
  45. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/issue_cmd.py +0 -0
  46. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/label_cmd.py +0 -0
  47. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/meeting_cmd.py +0 -0
  48. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/memory_cmd.py +0 -0
  49. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/project_cmd.py +0 -0
  50. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/repo_cmd.py +0 -0
  51. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/runtime_cmd.py +0 -0
  52. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/setup_cmd.py +0 -0
  53. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/skill_cmd.py +0 -0
  54. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/squad_cmd.py +0 -0
  55. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/update_cmd.py +0 -0
  56. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/version_cmd.py +0 -0
  57. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/commands/workspace_cmd.py +0 -0
  58. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/agent-memory/SKILL.md +0 -0
  59. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +0 -0
  60. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +0 -0
  61. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +0 -0
  62. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +0 -0
  63. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-basics/SKILL.md +0 -0
  64. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +0 -0
  65. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +0 -0
  66. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/aicortex-graph/SKILL.md +0 -0
  67. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/graphify/SKILL.md +0 -0
  68. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/question-form/SKILL.md +0 -0
  69. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/test-report-generator/SKILL.md +0 -0
  70. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +0 -0
  71. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +0 -0
  72. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/design_seed.py +0 -0
  73. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/diskusage.py +0 -0
  74. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/execenv.py +0 -0
  75. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/gc.py +0 -0
  76. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/handoff_bundle.py +0 -0
  77. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/health.py +0 -0
  78. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/id_resolve.py +0 -0
  79. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/local_skills.py +0 -0
  80. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/models.py +0 -0
  81. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/oauth_login.py +0 -0
  82. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/openclaw_config.py +0 -0
  83. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/output.py +0 -0
  84. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/pinned_workdir.py +0 -0
  85. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/platform_client.py +0 -0
  86. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/platform_info.py +0 -0
  87. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/poisoned.py +0 -0
  88. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/procutil.py +0 -0
  89. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/prompt.py +0 -0
  90. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/repo.py +0 -0
  91. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/runtime_config.py +0 -0
  92. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/stream.py +0 -0
  93. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/terminal.py +0 -0
  94. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy/tunnel.py +0 -0
  95. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy.egg-info/dependency_links.txt +0 -0
  96. {aicortexpy-0.2.1 → aicortexpy-0.2.2}/aicortexpy.egg-info/requires.txt +0 -0
  97. {aicortexpy-0.2.1 → 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.1
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
- 也可用 `python -m aicortexpy`(与 `aicortex` / `aicortexpy` 入口等价)。
29
+ 等价入口:`python -m aicortex`(推荐)/ `python -m aicortexpy` / `aicortexpy`。
30
30
 
31
31
  | | |
32
32
  |--|--|
33
- | PyPI / 模块 | `aicortexpy` |
34
- | Console scripts | `aicortex`、`aicortexpy`(`aicortex` 刻意同名,供 Agent 提示词零改动) |
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-daemon
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-daemon
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
- 尚未:浏览器 OAuth login、`update` 二进制自更新(请用 `pip install -U aicortexpy`)。
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
- 也可用 `python -m aicortexpy`(与 `aicortex` / `aicortexpy` 入口等价)。
12
+ 等价入口:`python -m aicortex`(推荐)/ `python -m aicortexpy` / `aicortexpy`。
13
13
 
14
14
  | | |
15
15
  |--|--|
16
- | PyPI / 模块 | `aicortexpy` |
17
- | Console scripts | `aicortex`、`aicortexpy`(`aicortex` 刻意同名,供 Agent 提示词零改动) |
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-daemon
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-daemon
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
- 尚未:浏览器 OAuth login、`update` 二进制自更新(请用 `pip install -U aicortexpy`)。
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__"]
@@ -0,0 +1,5 @@
1
+ """Allow `python -m aicortex` (same as `python -m aicortexpy`)."""
2
+
3
+ from aicortexpy.cli import main
4
+
5
+ raise SystemExit(main())
@@ -1,3 +1,3 @@
1
1
  """AICortex Python CLI + local agent runtime daemon."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
@@ -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__(self, stdin: TextIO, *, tool_name_fn: Callable[[str, str], str] | None = None) -> None:
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
- option_id = _select_permission_option_id(raw.get("params"))
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"):
@@ -414,6 +502,7 @@ def _select_permission_option_id(params: Any) -> str:
414
502
  return fallback
415
503
 
416
504
 
505
+
417
506
  def _normalize_update(data: Any) -> tuple[str, Any]:
418
507
  if not isinstance(data, dict):
419
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()
@@ -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(self._stdin_text, tool_name_fn=tool_name_fn)
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 = ["--yolo", "--trust", "--sandbox", "disabled", "acp", *filtered]
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
- conn._start_process(argv, work, env, tool_name_fn=hermes_tool_name_from_title)
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", "--trust-all-tools", *filtered]
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
- conn._start_process(argv, work, env, tool_name_fn=kiro_tool_name)
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 aicortexpy."""
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 aicortexpy %*\r\n',
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 aicortexpy "$@"\n', encoding="utf-8")
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:
@@ -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
 
@@ -52,7 +52,7 @@ def _setup_logging(
52
52
  """Configure root logging.
53
53
 
54
54
  - CLI one-shots default to WARNING (quiet).
55
- - Daemon run defaults to INFO (useful in python-daemon.log).
55
+ - Daemon run defaults to INFO (useful in python-aicortex.log).
56
56
  - `-v` or `--log-level DEBUG` enables DEBUG.
57
57
  """
58
58
  if log_level:
@@ -225,7 +225,7 @@ def discover_agents() -> dict[str, AgentEntry]:
225
225
  def default_daemon_id() -> str:
226
226
  host = socket.gethostname() or "local"
227
227
  # Stable-ish id per machine for python daemon
228
- seed = f"python-daemon:{host}:{platform.node()}"
228
+ seed = f"python-aicortex:{host}:{platform.node()}"
229
229
  return "py-" + uuid.uuid5(uuid.NAMESPACE_DNS, seed).hex[:12]
230
230
 
231
231
 
@@ -48,6 +48,7 @@ class Daemon:
48
48
  self.runtime_index: dict[str, dict[str, Any]] = {}
49
49
  self.workspace_runtimes: dict[str, list[str]] = {}
50
50
  self.workspace_names: dict[str, str] = {}
51
+ self.workspace_settings: dict[str, dict] = {}
51
52
  self._reregister_backoff: dict[str, float] = {}
52
53
  self.active_tasks = 0
53
54
  self._health: HealthServer | None = None
@@ -56,6 +57,10 @@ class Daemon:
56
57
  self.active_env_roots = ActiveEnvRoots()
57
58
  self.repo_allowlist = RepoAllowlist()
58
59
  self.wakeup_hub = WakeupHub()
60
+ from .permission import PermissionBroker
61
+
62
+ self.permissions = PermissionBroker()
63
+ self.wakeup_hub.permissions = self.permissions
59
64
 
60
65
  def stop(self) -> None:
61
66
  self._stop.set()
@@ -200,6 +205,10 @@ class Daemon:
200
205
  if not wid:
201
206
  continue
202
207
  self.workspace_names[wid] = name
208
+ settings = w.get("settings")
209
+ self.workspace_settings[wid] = (
210
+ settings if isinstance(settings, dict) else {}
211
+ )
203
212
  with self._lock:
204
213
  has = wid in self.workspace_runtimes and self.workspace_runtimes[wid]
205
214
  if has:
@@ -212,6 +221,7 @@ class Daemon:
212
221
  rids = self.workspace_runtimes.pop(wid, [])
213
222
  for rid in rids:
214
223
  self.runtime_index.pop(rid, None)
224
+ self.workspace_settings.pop(wid, None)
215
225
  self.repo_allowlist.drop_workspace(wid)
216
226
  log.info("dropped workspace %s", wid)
217
227
 
@@ -239,7 +249,7 @@ class Daemon:
239
249
  "legacy_daemon_ids": [],
240
250
  "device_name": self.cfg.device_name,
241
251
  "cli_version": self.cfg.cli_version,
242
- "launched_by": "python-daemon",
252
+ "launched_by": "python-aicortex",
243
253
  "timezone": detect_local_timezone(),
244
254
  "runtimes": runtimes,
245
255
  }
@@ -549,6 +559,12 @@ class Daemon:
549
559
  marked_roots = self._predict_env_roots(task)
550
560
  for root in marked_roots:
551
561
  self.active_env_roots.mark(root)
562
+ from .security import parse_workspace_security_settings
563
+
564
+ sec = parse_workspace_security_settings(
565
+ self.workspace_settings.get(task.workspace_id) or {}
566
+ )
567
+ # Roots filled inside run_agent once work_dir is known.
552
568
  result = None
553
569
  try:
554
570
  with self._lock:
@@ -567,6 +583,9 @@ class Daemon:
567
583
  on_session=on_session,
568
584
  acp_pool=self.chat_acp_pool,
569
585
  idle_watchdog=self.cfg.agent_idle_watchdog,
586
+ security_policy=sec,
587
+ # HITL permission prompts are Chat-only; Issue tasks auto-approve.
588
+ permissions=self.permissions if task.chat_session_id else None,
570
589
  )
571
590
  except Exception as e:
572
591
  log.exception("run_agent crashed: %s", e)