aicortexpy 0.1.0__py3-none-any.whl

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 (58) hide show
  1. aicortexpy/__init__.py +3 -0
  2. aicortexpy/__main__.py +5 -0
  3. aicortexpy/acp_client.py +559 -0
  4. aicortexpy/acp_conn.py +853 -0
  5. aicortexpy/acp_errors.py +107 -0
  6. aicortexpy/agent_env.py +88 -0
  7. aicortexpy/artifact_ws.py +233 -0
  8. aicortexpy/artifacts.py +67 -0
  9. aicortexpy/attachments.py +85 -0
  10. aicortexpy/auto_commit.py +106 -0
  11. aicortexpy/capabilities.py +92 -0
  12. aicortexpy/chat_acp_pool.py +321 -0
  13. aicortexpy/cli.py +133 -0
  14. aicortexpy/client.py +438 -0
  15. aicortexpy/codex_config.py +98 -0
  16. aicortexpy/config.py +298 -0
  17. aicortexpy/daemon.py +677 -0
  18. aicortexpy/data/default_skills/agent-memory/SKILL.md +67 -0
  19. aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +144 -0
  20. aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +145 -0
  21. aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +87 -0
  22. aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +96 -0
  23. aicortexpy/data/default_skills/aicortex-basics/SKILL.md +10 -0
  24. aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +118 -0
  25. aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +100 -0
  26. aicortexpy/data/default_skills/aicortex-graph/SKILL.md +30 -0
  27. aicortexpy/data/default_skills/graphify/SKILL.md +97 -0
  28. aicortexpy/data/default_skills/question-form/SKILL.md +162 -0
  29. aicortexpy/data/default_skills/test-report-generator/SKILL.md +179 -0
  30. aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +146 -0
  31. aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +275 -0
  32. aicortexpy/design_seed.py +59 -0
  33. aicortexpy/diskusage.py +209 -0
  34. aicortexpy/execenv.py +264 -0
  35. aicortexpy/gc.py +286 -0
  36. aicortexpy/handoff_bundle.py +65 -0
  37. aicortexpy/health.py +178 -0
  38. aicortexpy/local_skills.py +157 -0
  39. aicortexpy/models.py +385 -0
  40. aicortexpy/openclaw_config.py +153 -0
  41. aicortexpy/pinned_workdir.py +60 -0
  42. aicortexpy/platform_info.py +61 -0
  43. aicortexpy/poisoned.py +44 -0
  44. aicortexpy/process.py +168 -0
  45. aicortexpy/procutil.py +61 -0
  46. aicortexpy/prompt.py +235 -0
  47. aicortexpy/repo.py +462 -0
  48. aicortexpy/runner.py +969 -0
  49. aicortexpy/runtime_config.py +540 -0
  50. aicortexpy/sandbox.py +264 -0
  51. aicortexpy/stream.py +422 -0
  52. aicortexpy/terminal.py +147 -0
  53. aicortexpy/tunnel.py +82 -0
  54. aicortexpy/wakeup.py +222 -0
  55. aicortexpy-0.1.0.dist-info/METADATA +106 -0
  56. aicortexpy-0.1.0.dist-info/RECORD +58 -0
  57. aicortexpy-0.1.0.dist-info/WHEEL +5 -0
  58. aicortexpy-0.1.0.dist-info/top_level.txt +1 -0
aicortexpy/terminal.py ADDED
@@ -0,0 +1,147 @@
1
+ """Remote terminal sessions over wakeup WS (MVP; opt-in via aicortex_TERMINAL_PTY).
2
+
3
+ Uses a pipe-based shell session (not full ConPTY/unix pty) so Windows/macOS/Linux
4
+ share one code path. Good enough for command I/O; not a true interactive TTY.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import logging
11
+ import os
12
+ import subprocess
13
+ import threading
14
+ from typing import Any, Callable
15
+
16
+ from .procutil import kill_process_tree, popen_kwargs_for_killable_group
17
+
18
+ log = logging.getLogger("aicortexpy.terminal")
19
+
20
+ MAX_SESSIONS = 10
21
+ SendFn = Callable[[str, dict[str, Any]], None]
22
+
23
+
24
+ def terminal_pty_enabled() -> bool:
25
+ raw = (os.environ.get("aicortex_TERMINAL_PTY") or "").strip().lower()
26
+ return raw in ("1", "true", "yes", "on")
27
+
28
+
29
+ class TerminalManager:
30
+ def __init__(self, send: SendFn | None = None):
31
+ self._send = send
32
+ self._mu = threading.Lock()
33
+ self._sessions: dict[str, dict[str, Any]] = {}
34
+
35
+ def set_send(self, send: SendFn) -> None:
36
+ self._send = send
37
+
38
+ def handle_open(self, payload: dict[str, Any]) -> None:
39
+ if not terminal_pty_enabled():
40
+ self._error(payload, "terminal pty disabled (set aicortex_TERMINAL_PTY=true)")
41
+ return
42
+ sid = str(payload.get("session_id") or payload.get("id") or "").strip()
43
+ if not sid:
44
+ self._error(payload, "session id required")
45
+ return
46
+ with self._mu:
47
+ if len(self._sessions) >= MAX_SESSIONS:
48
+ self._error(payload, "too many terminal sessions")
49
+ return
50
+ if sid in self._sessions:
51
+ self._error(payload, "session already open")
52
+ return
53
+ cwd = str(payload.get("cwd") or os.getcwd())
54
+ shell = os.environ.get("COMSPEC") if os.name == "nt" else (os.environ.get("SHELL") or "/bin/bash")
55
+ try:
56
+ proc = subprocess.Popen(
57
+ [shell],
58
+ stdin=subprocess.PIPE,
59
+ stdout=subprocess.PIPE,
60
+ stderr=subprocess.STDOUT,
61
+ cwd=cwd if os.path.isdir(cwd) else None,
62
+ text=False,
63
+ bufsize=0,
64
+ **popen_kwargs_for_killable_group(),
65
+ )
66
+ except OSError as e:
67
+ self._error(payload, str(e))
68
+ return
69
+ with self._mu:
70
+ self._sessions[sid] = {"proc": proc}
71
+ threading.Thread(
72
+ target=self._reader, args=(sid, proc), name=f"term-{sid[:8]}", daemon=True
73
+ ).start()
74
+ if self._send:
75
+ self._send("terminal:attached", {"session_id": sid})
76
+
77
+ def handle_data(self, payload: dict[str, Any]) -> None:
78
+ sid = str(payload.get("session_id") or payload.get("id") or "").strip()
79
+ data_b64 = str(payload.get("data") or "")
80
+ with self._mu:
81
+ sess = self._sessions.get(sid)
82
+ if not sess:
83
+ return
84
+ proc: subprocess.Popen = sess["proc"]
85
+ try:
86
+ raw = base64.b64decode(data_b64) if data_b64 else b""
87
+ if proc.stdin and raw:
88
+ proc.stdin.write(raw)
89
+ proc.stdin.flush()
90
+ except OSError as e:
91
+ self._error(payload, str(e))
92
+
93
+ def handle_resize(self, payload: dict[str, Any]) -> None:
94
+ # Pipe-based MVP has no PTY size; acknowledge silently.
95
+ return
96
+
97
+ def handle_close(self, payload: dict[str, Any]) -> None:
98
+ sid = str(payload.get("session_id") or payload.get("id") or "").strip()
99
+ with self._mu:
100
+ sess = self._sessions.pop(sid, None)
101
+ if not sess:
102
+ return
103
+ proc: subprocess.Popen = sess["proc"]
104
+ try:
105
+ kill_process_tree(proc.pid)
106
+ except Exception:
107
+ pass
108
+
109
+ def handle_attach(self, payload: dict[str, Any]) -> None:
110
+ sid = str(payload.get("session_id") or payload.get("id") or "").strip()
111
+ with self._mu:
112
+ exists = sid in self._sessions
113
+ if exists and self._send:
114
+ self._send("terminal:attached", {"session_id": sid})
115
+ elif not exists:
116
+ self._error(payload, "session not found")
117
+
118
+ def handle_detach(self, payload: dict[str, Any]) -> None:
119
+ return
120
+
121
+ def _reader(self, sid: str, proc: subprocess.Popen) -> None:
122
+ assert proc.stdout is not None
123
+ try:
124
+ while True:
125
+ chunk = proc.stdout.read(4096)
126
+ if not chunk:
127
+ break
128
+ if self._send:
129
+ self._send(
130
+ "terminal:data",
131
+ {
132
+ "session_id": sid,
133
+ "data": base64.b64encode(chunk).decode("ascii"),
134
+ },
135
+ )
136
+ except Exception as e:
137
+ log.debug("terminal reader %s: %s", sid, e)
138
+ finally:
139
+ with self._mu:
140
+ self._sessions.pop(sid, None)
141
+ if self._send:
142
+ self._send("terminal:close", {"session_id": sid})
143
+
144
+ def _error(self, payload: dict[str, Any], message: str) -> None:
145
+ sid = str(payload.get("session_id") or payload.get("id") or "")
146
+ if self._send:
147
+ self._send("terminal:error", {"session_id": sid, "error": message})
aicortexpy/tunnel.py ADDED
@@ -0,0 +1,82 @@
1
+ """Loopback HTTP tunnel proxy (Go daemon/tunnel/proxy.go)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import json
7
+ import logging
8
+ import threading
9
+ import urllib.error
10
+ import urllib.request
11
+ from typing import Any, Callable
12
+
13
+ log = logging.getLogger("aicortexpy.tunnel")
14
+
15
+ MAX_BODY = 2 * 1024 * 1024
16
+ PROXY_TIMEOUT = 30.0
17
+ _HOP_BY_HOP = {
18
+ "connection",
19
+ "keep-alive",
20
+ "proxy-authenticate",
21
+ "proxy-authorization",
22
+ "te",
23
+ "trailers",
24
+ "transfer-encoding",
25
+ "upgrade",
26
+ }
27
+
28
+ SendFn = Callable[[str, dict[str, Any]], None]
29
+
30
+
31
+ class TunnelProxy:
32
+ def __init__(self, send: SendFn | None = None):
33
+ self._send = send
34
+
35
+ def set_send(self, send: SendFn) -> None:
36
+ self._send = send
37
+
38
+ def handle_request(self, payload: dict[str, Any]) -> None:
39
+ threading.Thread(
40
+ target=self._handle, args=(payload,), name="tunnel-req", daemon=True
41
+ ).start()
42
+
43
+ def _handle(self, payload: dict[str, Any]) -> None:
44
+ req_id = str(payload.get("request_id") or payload.get("requestId") or "")
45
+ resp: dict[str, Any] = {"request_id": req_id}
46
+ try:
47
+ port = int(payload.get("port") or 0)
48
+ if port < 1 or port > 65535:
49
+ raise ValueError(f"invalid port {port}")
50
+ method = str(payload.get("method") or "GET").upper()
51
+ path = str(payload.get("path") or "/")
52
+ if not path.startswith("/"):
53
+ path = "/" + path
54
+ body_b64 = str(payload.get("body") or "")
55
+ body = base64.b64decode(body_b64) if body_b64 else b""
56
+ if len(body) > MAX_BODY:
57
+ raise ValueError("request body too large")
58
+ headers = payload.get("headers") if isinstance(payload.get("headers"), dict) else {}
59
+ url = f"http://127.0.0.1:{port}{path}"
60
+ req = urllib.request.Request(url, data=body or None, method=method)
61
+ for k, v in headers.items():
62
+ if str(k).lower() in _HOP_BY_HOP:
63
+ continue
64
+ req.add_header(str(k), str(v))
65
+ with urllib.request.urlopen(req, timeout=PROXY_TIMEOUT) as http_resp:
66
+ raw = http_resp.read(MAX_BODY + 1)
67
+ if len(raw) > MAX_BODY:
68
+ raise ValueError("response body too large")
69
+ resp["status"] = http_resp.status
70
+ resp_headers: dict[str, str] = {}
71
+ for k, v in http_resp.headers.items():
72
+ if k.lower() in _HOP_BY_HOP:
73
+ continue
74
+ resp_headers[k] = v
75
+ resp["headers"] = resp_headers
76
+ if raw:
77
+ resp["body"] = base64.b64encode(raw).decode("ascii")
78
+ except Exception as e:
79
+ resp["error"] = str(e)
80
+ log.debug("tunnel request failed id=%s: %s", req_id, e)
81
+ if self._send:
82
+ self._send("tunnel:response", resp)
aicortexpy/wakeup.py ADDED
@@ -0,0 +1,222 @@
1
+ """WebSocket task-wakeup + tunnel/artifact/terminal mux (poll fallback remains)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import threading
8
+ import time
9
+ from typing import Any, Callable
10
+ from urllib.parse import urlencode, urlparse, urlunparse
11
+
12
+ from .artifact_ws import ArtifactBrowser
13
+ from .platform_info import host_os
14
+ from .terminal import TerminalManager, terminal_pty_enabled
15
+ from .tunnel import TunnelProxy
16
+
17
+ log = logging.getLogger("aicortexpy.wakeup")
18
+
19
+ EVENT_TASK_AVAILABLE = "daemon:task_available"
20
+ EVENT_HEARTBEAT = "daemon:heartbeat"
21
+
22
+
23
+ def task_wakeup_url(base_url: str, runtime_ids: list[str]) -> str:
24
+ u = urlparse(base_url.strip())
25
+ scheme = u.scheme.lower()
26
+ if scheme == "http":
27
+ scheme = "ws"
28
+ elif scheme == "https":
29
+ scheme = "wss"
30
+ elif scheme not in ("ws", "wss"):
31
+ raise ValueError(f"server URL must be http(s)/ws(s), got {u.scheme!r}")
32
+ path = (u.path or "").rstrip("/") + "/api/daemon/ws"
33
+ ids = sorted({r for r in runtime_ids if r})
34
+ query = urlencode({"runtime_ids": ",".join(ids)})
35
+ return urlunparse((scheme, u.netloc, path, "", query, ""))
36
+
37
+
38
+ class WakeupHub:
39
+ """Owns the daemon WS connection write mux + sideband handlers."""
40
+
41
+ def __init__(self) -> None:
42
+ self._ws = None
43
+ self._write_mu = threading.Lock()
44
+ self.tunnel = TunnelProxy(self.send)
45
+ self.artifacts = ArtifactBrowser(self.send)
46
+ self.terminal = TerminalManager(self.send)
47
+
48
+ def bind_ws(self, ws: Any) -> None:
49
+ self._ws = ws
50
+ self.tunnel.set_send(self.send)
51
+ self.artifacts.set_send(self.send)
52
+ self.terminal.set_send(self.send)
53
+
54
+ def clear_ws(self) -> None:
55
+ self._ws = None
56
+
57
+ def send(self, typ: str, payload: dict[str, Any]) -> None:
58
+ ws = self._ws
59
+ if ws is None:
60
+ return
61
+ msg = {"type": typ, "payload": payload}
62
+ raw = json.dumps(msg, ensure_ascii=False)
63
+ with self._write_mu:
64
+ try:
65
+ ws.send(raw)
66
+ except Exception as e:
67
+ log.debug("wakeup send failed type=%s: %s", typ, e)
68
+
69
+ def dispatch(self, msg: dict[str, Any], wake: threading.Event) -> None:
70
+ typ = str(msg.get("type") or "")
71
+ payload = msg.get("payload")
72
+ if isinstance(payload, str):
73
+ try:
74
+ payload = json.loads(payload)
75
+ except json.JSONDecodeError:
76
+ payload = {}
77
+ if not isinstance(payload, dict):
78
+ payload = msg if typ and typ != EVENT_TASK_AVAILABLE else {}
79
+
80
+ if typ == EVENT_TASK_AVAILABLE:
81
+ log.debug("task wakeup received")
82
+ wake.set()
83
+ return
84
+ if typ == "tunnel:request":
85
+ self.tunnel.handle_request(payload)
86
+ return
87
+ if typ == "artifact:request":
88
+ self.artifacts.handle_request(payload)
89
+ return
90
+ if typ.startswith("terminal:"):
91
+ handlers = {
92
+ "terminal:open": self.terminal.handle_open,
93
+ "terminal:attach": self.terminal.handle_attach,
94
+ "terminal:data": self.terminal.handle_data,
95
+ "terminal:resize": self.terminal.handle_resize,
96
+ "terminal:detach": self.terminal.handle_detach,
97
+ "terminal:close": self.terminal.handle_close,
98
+ }
99
+ fn = handlers.get(typ)
100
+ if fn:
101
+ fn(payload)
102
+ return
103
+
104
+
105
+ def run_wakeup_loop(
106
+ *,
107
+ stop: threading.Event,
108
+ wake: threading.Event,
109
+ get_runtime_ids: Callable[[], list[str]],
110
+ base_url: str,
111
+ token: str,
112
+ version: str = "",
113
+ hub: WakeupHub | None = None,
114
+ ) -> None:
115
+ """Connect to daemon WS; set `wake` when task_available arrives."""
116
+ try:
117
+ import websocket # type: ignore[import-untyped]
118
+ except ImportError:
119
+ log.warning(
120
+ "websocket-client not installed; wakeup WS disabled "
121
+ "(pip install websocket-client). Polling still works."
122
+ )
123
+ stop.wait()
124
+ return
125
+
126
+ hub = hub or WakeupHub()
127
+ backoff = 1.0
128
+
129
+ while not stop.is_set():
130
+ ids = get_runtime_ids()
131
+ if not ids:
132
+ stop.wait(5.0)
133
+ continue
134
+ id_key = tuple(sorted(ids))
135
+ try:
136
+ url = task_wakeup_url(base_url, ids)
137
+ except ValueError as e:
138
+ log.warning("wakeup URL invalid: %s", e)
139
+ stop.wait(30.0)
140
+ continue
141
+
142
+ headers = [
143
+ f"Authorization: Bearer {token}",
144
+ "X-Client-Platform: daemon",
145
+ f"X-Client-OS: {host_os()}",
146
+ ]
147
+ if version:
148
+ headers.append(f"X-Client-Version: {version}")
149
+
150
+ closed = threading.Event()
151
+
152
+ def on_message(_ws: object, message: str | bytes) -> None:
153
+ try:
154
+ raw = message.decode("utf-8") if isinstance(message, bytes) else message
155
+ msg = json.loads(raw)
156
+ except Exception:
157
+ return
158
+ if not isinstance(msg, dict):
159
+ return
160
+ hub.dispatch(msg, wake)
161
+
162
+ def on_error(_ws: object, error: object) -> None:
163
+ log.debug("wakeup ws error: %s", error)
164
+
165
+ def on_close(_ws: object, *_args: object) -> None:
166
+ hub.clear_ws()
167
+ closed.set()
168
+
169
+ def on_open(ws: object) -> None:
170
+ nonlocal backoff
171
+ backoff = 1.0
172
+ hub.bind_ws(ws)
173
+ log.info(
174
+ "task wakeup websocket connected runtimes=%s terminal_pty=%s",
175
+ len(ids),
176
+ terminal_pty_enabled(),
177
+ )
178
+ wake.set()
179
+ # Periodic WS heartbeat (best-effort; server may ignore).
180
+ def _hb() -> None:
181
+ while not stop.is_set() and not closed.is_set():
182
+ hub.send(EVENT_HEARTBEAT, {"ts": int(time.time())})
183
+ stop.wait(25.0)
184
+
185
+ threading.Thread(target=_hb, name="wakeup-hb", daemon=True).start()
186
+
187
+ ws_app = websocket.WebSocketApp(
188
+ url,
189
+ header=headers,
190
+ on_message=on_message,
191
+ on_error=on_error,
192
+ on_close=on_close,
193
+ on_open=on_open,
194
+ )
195
+
196
+ def _run() -> None:
197
+ ws_app.run_forever(ping_interval=30, ping_timeout=10)
198
+
199
+ t = threading.Thread(target=_run, name="wakeup-ws", daemon=True)
200
+ t.start()
201
+
202
+ while not stop.is_set() and not closed.is_set():
203
+ if tuple(sorted(get_runtime_ids())) != id_key:
204
+ try:
205
+ ws_app.close()
206
+ except Exception:
207
+ pass
208
+ break
209
+ stop.wait(1.0)
210
+
211
+ try:
212
+ ws_app.close()
213
+ except Exception:
214
+ pass
215
+ hub.clear_ws()
216
+ t.join(timeout=5.0)
217
+
218
+ if stop.is_set():
219
+ return
220
+ log.debug("wakeup ws reconnect in %.1fs", backoff)
221
+ stop.wait(backoff)
222
+ backoff = min(backoff * 2, 30.0)
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.2
2
+ Name: aicortexpy
3
+ Version: 0.1.0
4
+ Summary: AICortex Python local agent runtime daemon (no Docker)
5
+ Author: AICortex
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://gitee.com/seacozz2007/aicortex
8
+ Project-URL: Repository, https://gitee.com/seacozz2007/aicortex
9
+ Project-URL: Issues, https://gitee.com/seacozz2007/aicortex/issues
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: websocket-client>=1.6
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
15
+ Requires-Dist: build>=1.0; extra == "dev"
16
+ Requires-Dist: twine>=5.0; extra == "dev"
17
+
18
+ # aicortexpy(Python daemon)
19
+
20
+ 用已批准的 `python.exe` 跑本地 agent runtime(跨 Windows / macOS / Linux),**无需 Docker**。
21
+
22
+ | | 名称 |
23
+ |--|--|
24
+ | PyPI | `aicortexpy`(`aicortex` 与已有包 `ai-cortex` 过近被拒) |
25
+ | 模块 | `aicortexpy`(不能叫 `aicortex`:本仓库目录也叫 `aicortex`,在父目录执行会撞 PEP 420 命名空间包) |
26
+ | 调用 | `python -m aicortexpy`(不注册 console script,避免与 Go CLI `aicortex` 抢 PATH) |
27
+
28
+ ```powershell
29
+ pip install aicortexpy
30
+ python -m aicortexpy --help
31
+ ```
32
+
33
+ ## 安装
34
+
35
+ ### 从 PyPI
36
+
37
+ ```powershell
38
+ pip install -U aicortexpy
39
+ ```
40
+
41
+ ### 从源码(本仓库)
42
+
43
+ ```powershell
44
+ cd python-daemon
45
+ pip install -e .
46
+ pip install -e ".[dev]" # 含 build / twine
47
+ ```
48
+
49
+ ## 运行
50
+
51
+ ```powershell
52
+ # 方式 A:经 Go aicortex CLI(推荐;需已 pip 安装本包)
53
+ $env:AICORTEX_DAEMON_BACKEND = "python"
54
+ aicortex daemon start
55
+ aicortex daemon status
56
+ aicortex daemon stop
57
+
58
+ # 方式 B:直接启动
59
+ python -m aicortexpy start --foreground
60
+ python -m aicortexpy capabilities
61
+ python -m aicortexpy --version
62
+ ```
63
+
64
+ 需要 `~/.aicortex/config.json`(token + server_url)以及 PATH 上的 agent CLI。
65
+
66
+ Health 默认端口 **19514**(与 Go / `aicortex daemon status` 一致)。**不要**与 Go daemon 同时跑在同一端口。
67
+
68
+ ## 发布到 PyPI(对齐 aicorkit / aicortex-bridge)
69
+
70
+ 在 `python-daemon/` 目录:
71
+
72
+ ```powershell
73
+ python -m pip install -U build twine
74
+ python -m build
75
+ python -m twine upload dist/*
76
+ ```
77
+
78
+ 版本号改 `pyproject.toml` 的 `version` 与 `aicortexpy/__init__.py` 的 `__version__`(保持一致),再重新 build / upload。
79
+
80
+ ## 已对齐能力(相对 Go daemon)
81
+
82
+ - 注册/心跳/wakeup/claim/cancel、agentEnv、prior_work_dir 复用
83
+ - Claude(stream-json + **MCP `--mcp-config`**)、Codex(+ semantic inactivity)
84
+ - Cursor/Gemini/OpenCode/Copilot **stream-json/json 事件流**
85
+ - Cursor/Kiro chat ACP 池;Kiro/Hermes/Kimi/Dsh ACP oneshot
86
+ - OpenClaw / pi 启动参数;OpenClaw config **fail-closed**
87
+ - Repo:**allowlist** + per-workspace bare + **worktree 锁** + co-authored hook
88
+ - Design:**handoff zip** + **example seed**
89
+ - Model list:**ACP discovery** + 60s cache
90
+ - GC:`.gc_meta.json` + **active-root 保护**
91
+ - Wakeup 侧带:**tunnel**、**artifact browse/chunk upload**、terminal(pipe MVP,需 `AICORTEX_DAEMON_TERMINAL_PTY=true`)
92
+
93
+ **仍偏薄 / partial**:真正的 ConPTY/unix PTY(现为 pipe shell)、OS sandbox wrapper(bwrap/Seatbelt)、Go 式 auto-update(用 `pip install -U aicortexpy`)。
94
+
95
+ ## 环境变量(节选)
96
+
97
+ | 变量 | 含义 |
98
+ |------|------|
99
+ | `AICORTEX_DAEMON_BACKEND` | `python` 时 `aicortex daemon start` 启 Python daemon |
100
+ | `AICORTEX_DAEMON_HEALTH_PORT` | 默认 19514 |
101
+ | `AICORTEX_DAEMON_TERMINAL_PTY` | `true` 启用远程终端(pipe MVP) |
102
+ | `AICORTEX_AGENT_IDLE_WATCHDOG` | 无活动强停秒数(默认 300;`0` 关闭) |
103
+ | `AICORTEX_CODEX_SEMANTIC_INACTIVITY_TIMEOUT` | Codex 无输出强停(默认 600;`0` 关闭) |
104
+ | `AICORTEX_CODEX_MULTI_AGENT` | `1` 保留 Codex native multi_agent |
105
+ | `AICORTEX_INJECT_TENCENT_DOCS` | `1` 注入完整 tencent-docs 包 |
106
+ | `AICORTEX_GC_*` | GC 开关与 TTL |
@@ -0,0 +1,58 @@
1
+ aicortexpy/__init__.py,sha256=JLvtEW8nsZgV2d81Kd4lekdVD3AsBXgF8GH6ZHM6IF0,76
2
+ aicortexpy/__main__.py,sha256=0JBt6BfnwPz39k7xe6r7RsoyW5_XlsefeixJPkuheW0,101
3
+ aicortexpy/acp_client.py,sha256=-pf5QwvZbHUW7a7oKVVRohbDOvfKiOTC5wWhL58pCcY,20424
4
+ aicortexpy/acp_conn.py,sha256=JVVByMD3leiIRbJRD_n8jpkbulMAiwfAbVZj9Tbytv0,28933
5
+ aicortexpy/acp_errors.py,sha256=3kJwwHvk6H8EjVWOU0yj3Uc9rMVF3F05qqfSY5cnVt8,3796
6
+ aicortexpy/agent_env.py,sha256=w54dZS-g5lZNWejnk2wS3UgP9eCdwktAlqm0YotmLU8,2519
7
+ aicortexpy/artifact_ws.py,sha256=KZXSsq1MPiD5nUzTI27vEUoZSbjKLOslJ5HM8VpuzQ4,9385
8
+ aicortexpy/artifacts.py,sha256=0Q-dMq3m_nK7rqpAIAsVTtPQ0TV0AztmSgT8gu3qkv8,1724
9
+ aicortexpy/attachments.py,sha256=khowvPc2jMuDWzhbXTOOOTieiOVnSuoCeispAsE70WE,2547
10
+ aicortexpy/auto_commit.py,sha256=s-Hk1Wk1npk4AaMov0PS4D4_QVDvpzi378Viuqdw86U,3006
11
+ aicortexpy/capabilities.py,sha256=TC8xIYYWOyzg_OfiBJ2MmyK8wFCNnrJ75tOmPD__NEs,6055
12
+ aicortexpy/chat_acp_pool.py,sha256=wYL986M-PJzEAaEKJ5IA19tzu72HuUDsWfEgoaVcvdk,10674
13
+ aicortexpy/cli.py,sha256=W0qTKd7f3tu7Z3K6Exixki1TKS9P-i6njhicN8JxFIk,4315
14
+ aicortexpy/client.py,sha256=7PbG6c-_o86JO7ar85XbP9Q6WcTJQKHZk45-IQICuws,14282
15
+ aicortexpy/codex_config.py,sha256=TNusK1gD0Df9B5ErrSr22peOiZ8MUdLpj8kpc6SncRM,3559
16
+ aicortexpy/config.py,sha256=l4jtSl6ircxk-95LzxZjZv7X8D5OP3W38e13p3vYGWE,10665
17
+ aicortexpy/daemon.py,sha256=FHjVFwTGYtDNvFvsrZibEKA7TXwtMcGFFr8vFqCG388,25283
18
+ aicortexpy/design_seed.py,sha256=GT-7YFoxdx7yBzHK9aV6p1KLmXHoMDhl5RNRjZXTsN8,1981
19
+ aicortexpy/diskusage.py,sha256=lPTxRk6Wez4BRAQLeXlMF4BsLtkvEnKFx5_Iddhu71w,6712
20
+ aicortexpy/execenv.py,sha256=ILnGTDEmtP-XgIIGiNGR6zSEuid9oIUYCq_TFYfC6jg,9639
21
+ aicortexpy/gc.py,sha256=8h1KewYIzdL1NXVX4-WG0owt_P2iD3Lr3ilja-aPPkc,8754
22
+ aicortexpy/handoff_bundle.py,sha256=2DNm4QyiRbVP-J1qbhknoep7Odio5Gyzwrq03BWIcdY,2205
23
+ aicortexpy/health.py,sha256=7btOmCmbW22ZU2Yz7f8JZOQ3m8R7W1DpBnKblbmnTyI,6838
24
+ aicortexpy/local_skills.py,sha256=J7E9ey_hSEnbACG-4b_gfOdLXP3RRyuXDcQ_Xfz1WA8,4968
25
+ aicortexpy/models.py,sha256=4WLGvDc1apbf9SD0gddulgpNNH5lkRUssga1QYxpJN8,12498
26
+ aicortexpy/openclaw_config.py,sha256=FxQlAHcspbRUUMHFrn_bdG8xrXqxl9yGYA-CCbYFnws,5229
27
+ aicortexpy/pinned_workdir.py,sha256=6PHrFJ9YlSvnDEiqs6RSB7dgmKeizgHC9Un-eSADB3I,1689
28
+ aicortexpy/platform_info.py,sha256=AlFztNUlZC4OclTSXjpGd_UwHb-87dQSj58NWObzKfc,1441
29
+ aicortexpy/poisoned.py,sha256=6P6CTi7eTxiJD_709xfNgeO_2qA-A1bQ525P2MNhYEw,1400
30
+ aicortexpy/process.py,sha256=TWX7h9Cp1_lOJ_m0C8EK6_dbcAVmqWFvKj9jrzyrgII,5055
31
+ aicortexpy/procutil.py,sha256=_EftI5v9Yer7T5exd7bPIpLxR5x08tRvu_5C9Mn6gEQ,1596
32
+ aicortexpy/prompt.py,sha256=1s5R7jLPY-n9zmTL1jfPPDDFflLeNymJf5S8WsQHVxM,10280
33
+ aicortexpy/repo.py,sha256=tnNh96q2_UcV3yELOLDufTCkrg9gs81Vmq9WGl-Or6c,15513
34
+ aicortexpy/runner.py,sha256=Aei1m5JUUYwpMP6yYN3xd4VPm5Z22XkP0o3gyB6nPPk,31268
35
+ aicortexpy/runtime_config.py,sha256=W94jPYXP7A7v1-QT6Br_ixlKAB176Ior1RbxUm2YDCI,22725
36
+ aicortexpy/sandbox.py,sha256=jVhByEcYxXcw7nEj2mTl6FyoU8cQ1TA9WxyAxmdtNiQ,9114
37
+ aicortexpy/stream.py,sha256=T7XyYxIoPTdXBTxzP3DUVjPJqTM5KXDtvFy0F8mUXgI,17503
38
+ aicortexpy/terminal.py,sha256=Ml3i0IFJu8ZjSU5S4q8tptV4jBFHdOr8Q0LLx6rV9nk,5405
39
+ aicortexpy/tunnel.py,sha256=A4Va42KxdxSQuVZhNK5MU1N_DTzLOtOAlYweGXaHPEA,3010
40
+ aicortexpy/wakeup.py,sha256=3Rqjs4fkSHw4c3R8Tikm6WZR8u6AfAIWoxsbpwDrB3U,7231
41
+ aicortexpy/data/default_skills/agent-memory/SKILL.md,sha256=c1oEhINeduE8UV5T27RPBWfB0l65U1WoOpOgMu_EGfQ,2992
42
+ aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md,sha256=J9axzhdu-G5O1Crr0488S79UKt4IHD82iY-PCQ-VEY8,4939
43
+ aicortexpy/data/default_skills/aicorkit-graph/SKILL.md,sha256=d438JVjCCiX8V8hkAtQdz3uUfRREG1vuoXFWZvGeQp4,6011
44
+ aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md,sha256=rR8RyNbIx3eK0ktRvxB5Xj4JBnWsu5fY1vLeA3diF0U,3221
45
+ aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md,sha256=ku8sBuVyy3h4K6L5B5GZjnOEFnfBhZeYAvsSiJWxhWA,3563
46
+ aicortexpy/data/default_skills/aicortex-basics/SKILL.md,sha256=1_9fhqvBVy5_Ml7LdVfXVmSiMERoOzCndN8wWbI52gA,385
47
+ aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md,sha256=7cr078IzMq4nVmefok7DJ5YsjSKOI051fCH53sJWaso,4198
48
+ aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md,sha256=YrFCxogq-rKPa8IY6PIv_Yk_qha6hN0SBbQ9HNYyZ_M,3333
49
+ aicortexpy/data/default_skills/aicortex-graph/SKILL.md,sha256=oiFyZRijBYK-Nfw0Yl2sbYljwlsR7DvnUnn6QdkRtUA,1389
50
+ aicortexpy/data/default_skills/graphify/SKILL.md,sha256=rRF9IZ2WGoAe6vpIena8eUpKRaKojElsjBexQndmHHE,4861
51
+ aicortexpy/data/default_skills/question-form/SKILL.md,sha256=pucPRblzVLHxugONJ1p-e7qgFCS8XAUEiHTQ6nj9bVY,5680
52
+ aicortexpy/data/default_skills/test-report-generator/SKILL.md,sha256=-Oe8UdgCZvbeb-2Rk14N9D60NULvJredWmb-HL-X0Qw,8705
53
+ aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py,sha256=hfwgujxS6d-FkaSwjEWIgsP2XFWsHglawtee9gwW8yw,5442
54
+ aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py,sha256=AD4CONpoidzpZStSYZaHw16_3pijyeKWl8y0lqpyjVY,10102
55
+ aicortexpy-0.1.0.dist-info/METADATA,sha256=W_ZFodUFIf7JfR0G3XXjwAubAO8v2ePPd1gf57El14k,3917
56
+ aicortexpy-0.1.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
57
+ aicortexpy-0.1.0.dist-info/top_level.txt,sha256=5qT2YcsQBb-n7HvUk94dAgMOStEeRDJIlDFuEZ7Q5To,11
58
+ aicortexpy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (76.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ aicortexpy