illusion-code 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 (214) hide show
  1. illusion/__init__.py +24 -0
  2. illusion/__main__.py +15 -0
  3. illusion/_frontend/dist/index.mjs +39208 -0
  4. illusion/_frontend/package.json +27 -0
  5. illusion/_frontend/src/App.tsx +624 -0
  6. illusion/_frontend/src/components/CommandPicker.tsx +98 -0
  7. illusion/_frontend/src/components/Composer.tsx +55 -0
  8. illusion/_frontend/src/components/ComposerController.tsx +128 -0
  9. illusion/_frontend/src/components/ConversationView.tsx +750 -0
  10. illusion/_frontend/src/components/Footer.tsx +25 -0
  11. illusion/_frontend/src/components/MarkdownContent.tsx +537 -0
  12. illusion/_frontend/src/components/MarkdownTable.tsx +245 -0
  13. illusion/_frontend/src/components/ModalHost.tsx +425 -0
  14. illusion/_frontend/src/components/MultilineTextInput.tsx +250 -0
  15. illusion/_frontend/src/components/PromptInput.tsx +64 -0
  16. illusion/_frontend/src/components/SelectModal.tsx +78 -0
  17. illusion/_frontend/src/components/SidePanel.tsx +175 -0
  18. illusion/_frontend/src/components/Spinner.tsx +77 -0
  19. illusion/_frontend/src/components/StatusBar.tsx +142 -0
  20. illusion/_frontend/src/components/SwarmPanel.tsx +141 -0
  21. illusion/_frontend/src/components/TodoPanel.tsx +126 -0
  22. illusion/_frontend/src/components/ToolCallDisplay.tsx +202 -0
  23. illusion/_frontend/src/components/TranscriptPane.tsx +79 -0
  24. illusion/_frontend/src/components/WelcomeBanner.tsx +37 -0
  25. illusion/_frontend/src/hooks/useBackendSession.ts +468 -0
  26. illusion/_frontend/src/hooks/useTerminalSize.ts +9 -0
  27. illusion/_frontend/src/i18n.ts +78 -0
  28. illusion/_frontend/src/index.tsx +42 -0
  29. illusion/_frontend/src/theme/ThemeContext.tsx +19 -0
  30. illusion/_frontend/src/theme/builtinThemes.ts +89 -0
  31. illusion/_frontend/src/types.ts +110 -0
  32. illusion/_frontend/src/utils/markdown.ts +33 -0
  33. illusion/_frontend/src/utils/thinking.ts +191 -0
  34. illusion/_frontend/tsconfig.json +13 -0
  35. illusion/_web_dist/assets/index-BseIw-ik.css +10 -0
  36. illusion/_web_dist/assets/index-C_0ZWMuW.js +82 -0
  37. illusion/_web_dist/index.html +16 -0
  38. illusion/api/__init__.py +36 -0
  39. illusion/api/client.py +568 -0
  40. illusion/api/codex_client.py +563 -0
  41. illusion/api/compat.py +138 -0
  42. illusion/api/effort.py +128 -0
  43. illusion/api/errors.py +57 -0
  44. illusion/api/openai_client.py +819 -0
  45. illusion/api/provider.py +148 -0
  46. illusion/api/registry.py +479 -0
  47. illusion/api/usage.py +45 -0
  48. illusion/auth/__init__.py +50 -0
  49. illusion/auth/copilot.py +419 -0
  50. illusion/auth/external.py +612 -0
  51. illusion/auth/flows.py +58 -0
  52. illusion/auth/manager.py +214 -0
  53. illusion/auth/storage.py +372 -0
  54. illusion/bridge/__init__.py +38 -0
  55. illusion/bridge/manager.py +190 -0
  56. illusion/bridge/session_runner.py +84 -0
  57. illusion/bridge/types.py +113 -0
  58. illusion/bridge/work_secret.py +131 -0
  59. illusion/cli.py +1228 -0
  60. illusion/commands/__init__.py +32 -0
  61. illusion/commands/registry.py +1934 -0
  62. illusion/config/__init__.py +39 -0
  63. illusion/config/i18n.py +522 -0
  64. illusion/config/paths.py +259 -0
  65. illusion/config/settings.py +564 -0
  66. illusion/coordinator/__init__.py +41 -0
  67. illusion/coordinator/agent_definitions.py +1093 -0
  68. illusion/coordinator/coordinator_mode.py +127 -0
  69. illusion/engine/__init__.py +95 -0
  70. illusion/engine/cost_tracker.py +55 -0
  71. illusion/engine/messages.py +369 -0
  72. illusion/engine/query.py +632 -0
  73. illusion/engine/query_engine.py +343 -0
  74. illusion/engine/stream_events.py +169 -0
  75. illusion/hooks/__init__.py +67 -0
  76. illusion/hooks/events.py +43 -0
  77. illusion/hooks/executor.py +397 -0
  78. illusion/hooks/hot_reload.py +74 -0
  79. illusion/hooks/loader.py +133 -0
  80. illusion/hooks/schemas.py +121 -0
  81. illusion/hooks/types.py +86 -0
  82. illusion/mcp/__init__.py +104 -0
  83. illusion/mcp/client.py +377 -0
  84. illusion/mcp/config.py +140 -0
  85. illusion/mcp/types.py +175 -0
  86. illusion/memory/__init__.py +36 -0
  87. illusion/memory/manager.py +94 -0
  88. illusion/memory/memdir.py +58 -0
  89. illusion/memory/paths.py +57 -0
  90. illusion/memory/scan.py +120 -0
  91. illusion/memory/search.py +83 -0
  92. illusion/memory/types.py +43 -0
  93. illusion/output_styles/__init__.py +15 -0
  94. illusion/output_styles/loader.py +64 -0
  95. illusion/permissions/__init__.py +39 -0
  96. illusion/permissions/checker.py +174 -0
  97. illusion/permissions/modes.py +38 -0
  98. illusion/platforms.py +148 -0
  99. illusion/plugins/__init__.py +71 -0
  100. illusion/plugins/bundled/__init__.py +0 -0
  101. illusion/plugins/installer.py +59 -0
  102. illusion/plugins/loader.py +301 -0
  103. illusion/plugins/schemas.py +51 -0
  104. illusion/plugins/types.py +56 -0
  105. illusion/prompts/__init__.py +29 -0
  106. illusion/prompts/claudemd.py +74 -0
  107. illusion/prompts/context.py +187 -0
  108. illusion/prompts/environment.py +189 -0
  109. illusion/prompts/system_prompt.py +155 -0
  110. illusion/py.typed +0 -0
  111. illusion/sandbox/__init__.py +29 -0
  112. illusion/sandbox/adapter.py +174 -0
  113. illusion/services/__init__.py +59 -0
  114. illusion/services/compact/__init__.py +1015 -0
  115. illusion/services/cron.py +338 -0
  116. illusion/services/cron_scheduler.py +715 -0
  117. illusion/services/file_history.py +258 -0
  118. illusion/services/lsp/__init__.py +455 -0
  119. illusion/services/session_storage.py +237 -0
  120. illusion/services/token_estimation.py +72 -0
  121. illusion/skills/__init__.py +60 -0
  122. illusion/skills/bundled/__init__.py +110 -0
  123. illusion/skills/bundled/content/batch.md +86 -0
  124. illusion/skills/bundled/content/coding-guidelines.md +70 -0
  125. illusion/skills/bundled/content/debug.md +38 -0
  126. illusion/skills/bundled/content/loop.md +82 -0
  127. illusion/skills/bundled/content/remember.md +105 -0
  128. illusion/skills/bundled/content/simplify.md +53 -0
  129. illusion/skills/bundled/content/skillify.md +113 -0
  130. illusion/skills/bundled/content/stuck.md +54 -0
  131. illusion/skills/bundled/content/update-config.md +329 -0
  132. illusion/skills/bundled/content/verify.md +74 -0
  133. illusion/skills/loader.py +219 -0
  134. illusion/skills/registry.py +40 -0
  135. illusion/skills/types.py +24 -0
  136. illusion/state/__init__.py +18 -0
  137. illusion/state/app_state.py +67 -0
  138. illusion/state/store.py +93 -0
  139. illusion/swarm/__init__.py +71 -0
  140. illusion/swarm/agent_executor.py +857 -0
  141. illusion/swarm/in_process.py +259 -0
  142. illusion/swarm/subprocess_backend.py +136 -0
  143. illusion/swarm/team_helpers.py +123 -0
  144. illusion/swarm/types.py +159 -0
  145. illusion/swarm/worktree.py +347 -0
  146. illusion/tasks/__init__.py +33 -0
  147. illusion/tasks/local_agent_task.py +42 -0
  148. illusion/tasks/local_shell_task.py +27 -0
  149. illusion/tasks/manager.py +377 -0
  150. illusion/tasks/stop_task.py +21 -0
  151. illusion/tasks/types.py +88 -0
  152. illusion/tools/__init__.py +126 -0
  153. illusion/tools/agent_tool.py +388 -0
  154. illusion/tools/ask_user_question_tool.py +186 -0
  155. illusion/tools/base.py +149 -0
  156. illusion/tools/bash_tool.py +413 -0
  157. illusion/tools/config_tool.py +90 -0
  158. illusion/tools/cron_tool.py +473 -0
  159. illusion/tools/enter_plan_mode_tool.py +147 -0
  160. illusion/tools/enter_worktree_tool.py +188 -0
  161. illusion/tools/exit_plan_mode_tool.py +69 -0
  162. illusion/tools/exit_worktree_tool.py +225 -0
  163. illusion/tools/file_edit_tool.py +283 -0
  164. illusion/tools/file_read_tool.py +294 -0
  165. illusion/tools/file_write_tool.py +184 -0
  166. illusion/tools/glob_tool.py +165 -0
  167. illusion/tools/grep_tool.py +190 -0
  168. illusion/tools/list_mcp_resources_tool.py +80 -0
  169. illusion/tools/lsp_tool.py +333 -0
  170. illusion/tools/mcp_auth_tool.py +100 -0
  171. illusion/tools/mcp_tool.py +75 -0
  172. illusion/tools/notebook_edit_tool.py +242 -0
  173. illusion/tools/powershell_tool.py +334 -0
  174. illusion/tools/read_mcp_resource_tool.py +63 -0
  175. illusion/tools/repl_tool.py +100 -0
  176. illusion/tools/send_message_tool.py +112 -0
  177. illusion/tools/shell_common.py +187 -0
  178. illusion/tools/skill_tool.py +86 -0
  179. illusion/tools/sleep_tool.py +62 -0
  180. illusion/tools/structured_output_tool.py +58 -0
  181. illusion/tools/task_create_tool.py +98 -0
  182. illusion/tools/task_get_tool.py +94 -0
  183. illusion/tools/task_list_tool.py +94 -0
  184. illusion/tools/task_output_tool.py +55 -0
  185. illusion/tools/task_stop_tool.py +52 -0
  186. illusion/tools/task_update_tool.py +224 -0
  187. illusion/tools/team_create_tool.py +236 -0
  188. illusion/tools/team_delete_tool.py +104 -0
  189. illusion/tools/todo_write_tool.py +198 -0
  190. illusion/tools/tool_search_tool.py +156 -0
  191. illusion/tools/web_fetch_tool.py +264 -0
  192. illusion/tools/web_search_tool.py +186 -0
  193. illusion/ui/__init__.py +23 -0
  194. illusion/ui/app.py +258 -0
  195. illusion/ui/backend_host.py +1180 -0
  196. illusion/ui/input.py +86 -0
  197. illusion/ui/output.py +363 -0
  198. illusion/ui/permission_dialog.py +47 -0
  199. illusion/ui/permission_store.py +99 -0
  200. illusion/ui/protocol.py +384 -0
  201. illusion/ui/react_launcher.py +280 -0
  202. illusion/ui/runtime.py +787 -0
  203. illusion/ui/textual_app.py +603 -0
  204. illusion/ui/web/__init__.py +10 -0
  205. illusion/ui/web/server.py +87 -0
  206. illusion/ui/web/ws_host.py +1197 -0
  207. illusion/utils/__init__.py +0 -0
  208. illusion/utils/ripgrep.py +299 -0
  209. illusion/utils/shell.py +248 -0
  210. illusion_code-0.1.0.dist-info/METADATA +1159 -0
  211. illusion_code-0.1.0.dist-info/RECORD +214 -0
  212. illusion_code-0.1.0.dist-info/WHEEL +4 -0
  213. illusion_code-0.1.0.dist-info/entry_points.txt +2 -0
  214. illusion_code-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,347 @@
1
+ """
2
+ Git Worktree 隔离模块
3
+ ====================
4
+
5
+ 本模块提供 swarm 代理的 Git worktree 隔离功能。
6
+ 使用 Git worktree 实现代理间的文件系统隔离。
7
+
8
+ 主要组件:
9
+ - WorktreeManager: Git worktree 管理器
10
+ - WorktreeInfo: Worktree 元数据
11
+ - validate_worktree_slug: Worktree slug 验证函数
12
+
13
+ 使用示例:
14
+ >>> from illusion.swarm.worktree import WorktreeManager
15
+ >>>
16
+ >>> manager = WorktreeManager()
17
+ >>> info = await manager.create_worktree(repo_path, "researcher-1", agent_id="agent-1")
18
+ >>> print(f"Worktree created at: {info.path}")
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ import os
25
+ import re
26
+ import subprocess
27
+ import sys
28
+ import time
29
+ from dataclasses import dataclass
30
+ from pathlib import Path
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Slug 验证
34
+ # ---------------------------------------------------------------------------
35
+
36
+ # 有效的路径段正则
37
+ _VALID_SEGMENT = re.compile(r"^[a-zA-Z0-9._-]+$")
38
+ # 最大 slug 长度
39
+ _MAX_SLUG_LENGTH = 64
40
+ # 常见符号链接目录
41
+ _COMMON_SYMLINK_DIRS = ("node_modules", ".venv", "__pycache__", ".tox")
42
+
43
+
44
+ def validate_worktree_slug(slug: str) -> str:
45
+ """清理并验证 worktree slug。
46
+
47
+ 规则:
48
+ - 最多 64 个字符
49
+ - 每个 '/' 分隔的段必须匹配 [a-zA-Z0-9._-]+
50
+ - 拒绝 '.' 和 '..' 段(路径遍历)
51
+ - 拒绝前导/尾随 '/'
52
+
53
+ 如果有效则返回 slug 不变,否则引发 ValueError。
54
+ """
55
+ if not slug:
56
+ raise ValueError("Worktree slug must not be empty")
57
+
58
+ if len(slug) > _MAX_SLUG_LENGTH:
59
+ raise ValueError(
60
+ f"Worktree slug must be {_MAX_SLUG_LENGTH} characters or fewer (got {len(slug)})"
61
+ )
62
+
63
+ # 拒绝绝对路径
64
+ if slug.startswith("/") or slug.startswith("\\"):
65
+ raise ValueError(f"Worktree slug must not be an absolute path: {slug!r}")
66
+
67
+ for segment in slug.split("/"):
68
+ if segment in (".", ".."):
69
+ raise ValueError(
70
+ f"Worktree slug {slug!r}: must not contain '.' or '..' path segments"
71
+ )
72
+ if not _VALID_SEGMENT.match(segment):
73
+ raise ValueError(
74
+ f"Worktree slug {slug!r}: each segment must be non-empty and contain only "
75
+ "letters, digits, dots, underscores, and dashes"
76
+ )
77
+
78
+ return slug
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # 数据结构
83
+ # ---------------------------------------------------------------------------
84
+
85
+ @dataclass
86
+ class WorktreeInfo:
87
+ """管理的 git worktree 的元数据。"""
88
+
89
+ slug: str
90
+ path: Path
91
+ branch: str
92
+ original_path: Path
93
+ created_at: float
94
+ agent_id: str | None = None
95
+
96
+
97
+ # ---------------------------------------------------------------------------
98
+ # 内部辅助函数
99
+ # ---------------------------------------------------------------------------
100
+
101
+ def _flatten_slug(slug: str) -> str:
102
+ """用 '+' 替换 '/' 以避免嵌套目录/分支问题。"""
103
+ return slug.replace("/", "+")
104
+
105
+
106
+ def _worktree_branch(slug: str) -> str:
107
+ """生成 worktree 分支名称。"""
108
+ return f"worktree-{_flatten_slug(slug)}"
109
+
110
+
111
+ async def _run_git(*args: str, cwd: Path) -> tuple[int, str, str]:
112
+ """运行 git 命令,返回 (returncode, stdout, stderr)。"""
113
+ kwargs: dict = {}
114
+ if sys.platform == "win32":
115
+ kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
116
+ proc = await asyncio.create_subprocess_exec(
117
+ "git",
118
+ *args,
119
+ cwd=str(cwd),
120
+ stdin=asyncio.subprocess.DEVNULL,
121
+ stdout=asyncio.subprocess.PIPE,
122
+ stderr=asyncio.subprocess.PIPE,
123
+ env={**os.environ, "GIT_TERMINAL_PROMPT": "0", "GIT_ASKPASS": ""},
124
+ **kwargs,
125
+ )
126
+ stdout_bytes, stderr_bytes = await proc.communicate()
127
+ return (
128
+ proc.returncode or 0,
129
+ stdout_bytes.decode(errors="replace").strip(),
130
+ stderr_bytes.decode(errors="replace").strip(),
131
+ )
132
+
133
+
134
+ async def _symlink_common_dirs(repo_path: Path, worktree_path: Path) -> None:
135
+ """从主仓库符号链接大型公共目录以避免重复。"""
136
+ for dir_name in _COMMON_SYMLINK_DIRS:
137
+ src = repo_path / dir_name
138
+ dst = worktree_path / dir_name
139
+ if dst.exists() or dst.is_symlink():
140
+ continue
141
+ if not src.exists():
142
+ continue
143
+ try:
144
+ dst.symlink_to(src)
145
+ except OSError:
146
+ pass # 非致命:磁盘满、不支持的文件系统等
147
+
148
+
149
+ async def _remove_symlinks(worktree_path: Path) -> None:
150
+ """移除由 _symlink_common_dirs 创建的符号链接。"""
151
+ for dir_name in _COMMON_SYMLINK_DIRS:
152
+ dst = worktree_path / dir_name
153
+ if dst.is_symlink():
154
+ try:
155
+ dst.unlink()
156
+ except OSError:
157
+ pass
158
+
159
+
160
+ # ---------------------------------------------------------------------------
161
+ # WorktreeManager
162
+ # ---------------------------------------------------------------------------
163
+
164
+ class WorktreeManager:
165
+ """管理隔离代理执行的 git worktree。
166
+
167
+ Worktree 存储在 ``base_dir/<slug>/`` 下('/' 替换为 '+' 以保持布局扁平)。
168
+ JSON 元数据文件跟踪活跃 worktree 及其关联的代理 ID,以便可以清理过期的 worktree。
169
+ """
170
+
171
+ def __init__(self, base_dir: Path | None = None) -> None:
172
+ """初始化 WorktreeManager。"""
173
+ self.base_dir: Path = base_dir or Path.home() / ".illusion" / "worktrees"
174
+
175
+ # ------------------------------------------------------------------
176
+ # 公开 API
177
+ # ------------------------------------------------------------------
178
+
179
+ async def create_worktree(
180
+ self,
181
+ repo_path: Path,
182
+ slug: str,
183
+ branch: str | None = None,
184
+ agent_id: str | None = None,
185
+ ) -> WorktreeInfo:
186
+ """为 *slug* 创建(或恢复)git worktree。
187
+
188
+ 如果 worktree 目录已存在且是有效的 git worktree,
189
+ 则在不重新运行 ``git worktree add`` 的情况下恢复。
190
+
191
+ Args:
192
+ repo_path: 主仓库的绝对路径。
193
+ slug: 人类可读标识符(通过 validate_worktree_slug 验证)。
194
+ branch: 要检出的分支名称;默认为生成的 ``worktree-<slug>`` 名称。
195
+ agent_id: 拥有此 worktree 的代理的可选标识符。
196
+
197
+ Returns:
198
+ 描述 worktree 的 WorktreeInfo。
199
+ """
200
+ # 验证 slug
201
+ validate_worktree_slug(slug)
202
+ repo_path = repo_path.resolve()
203
+ self.base_dir.mkdir(parents=True, exist_ok=True)
204
+
205
+ # 扁平化 slug 并构建路径
206
+ flat_slug = _flatten_slug(slug)
207
+ worktree_path = self.base_dir / flat_slug
208
+ worktree_branch = branch or _worktree_branch(slug)
209
+
210
+ # 快速恢复:检查 worktree 是否已注册
211
+ if worktree_path.exists():
212
+ code, _, _ = await _run_git(
213
+ "rev-parse", "--git-dir", cwd=worktree_path
214
+ )
215
+ if code == 0:
216
+ return WorktreeInfo(
217
+ slug=slug,
218
+ path=worktree_path,
219
+ branch=worktree_branch,
220
+ original_path=repo_path,
221
+ created_at=worktree_path.stat().st_mtime,
222
+ agent_id=agent_id,
223
+ )
224
+
225
+ # 新 worktree:-B 重置之前移除留下的孤儿分支
226
+ code, _, stderr = await _run_git(
227
+ "worktree", "add", "-B", worktree_branch, str(worktree_path), "HEAD",
228
+ cwd=repo_path,
229
+ )
230
+ if code != 0:
231
+ raise RuntimeError(f"git worktree add failed: {stderr}")
232
+
233
+ # 符号链接公共目录
234
+ await _symlink_common_dirs(repo_path, worktree_path)
235
+
236
+ return WorktreeInfo(
237
+ slug=slug,
238
+ path=worktree_path,
239
+ branch=worktree_branch,
240
+ original_path=repo_path,
241
+ created_at=time.time(),
242
+ agent_id=agent_id,
243
+ )
244
+
245
+ async def remove_worktree(self, slug: str) -> bool:
246
+ """按 slug 移除 worktree。
247
+
248
+ 首先清理符号链接,然后运行 ``git worktree remove --force``。
249
+
250
+ Returns:
251
+ 如果 worktree 被移除返回 True;如果不存在则返回 False。
252
+ """
253
+ validate_worktree_slug(slug)
254
+ flat_slug = _flatten_slug(slug)
255
+ worktree_path = self.base_dir / flat_slug
256
+
257
+ if not worktree_path.exists():
258
+ return False
259
+
260
+ # 在 git 移除目录之前先移除符号链接
261
+ await _remove_symlinks(worktree_path)
262
+
263
+ # 从 worktree 的 git 元数据确定仓库根目录
264
+ code, git_common, _ = await _run_git(
265
+ "rev-parse", "--git-common-dir", cwd=worktree_path
266
+ )
267
+ if code == 0 and git_common:
268
+ # git_common 指向主仓库内的 .git
269
+ repo_path = Path(git_common).resolve().parent
270
+ if repo_path.exists():
271
+ await _run_git(
272
+ "worktree", "remove", "--force", str(worktree_path),
273
+ cwd=repo_path,
274
+ )
275
+ return True
276
+
277
+ # 回退:尝试从任何工作目录通过绝对路径移除
278
+ # 如果 repo_path 检测失败,尝试使用 cwd=base_dir 移除
279
+ code, _, _ = await _run_git(
280
+ "worktree", "remove", "--force", str(worktree_path),
281
+ cwd=self.base_dir,
282
+ )
283
+ return code == 0
284
+
285
+ async def list_worktrees(self) -> list[WorktreeInfo]:
286
+ """返回 base_dir 下每个已知 worktree 的 WorktreeInfo。"""
287
+ if not self.base_dir.exists():
288
+ return []
289
+
290
+ results: list[WorktreeInfo] = []
291
+ for child in self.base_dir.iterdir():
292
+ if not child.is_dir():
293
+ continue
294
+ code, _, _ = await _run_git("rev-parse", "--git-dir", cwd=child)
295
+ if code != 0:
296
+ continue
297
+
298
+ # 从 HEAD 恢复分支名称
299
+ rc, branch_out, _ = await _run_git(
300
+ "rev-parse", "--abbrev-ref", "HEAD", cwd=child
301
+ )
302
+ branch = branch_out if rc == 0 else "unknown"
303
+
304
+ # 从 git-common-dir 恢复原始仓库路径
305
+ rc2, common_dir, _ = await _run_git(
306
+ "rev-parse", "--git-common-dir", cwd=child
307
+ )
308
+ if rc2 == 0 and common_dir:
309
+ original_path = Path(common_dir).resolve().parent
310
+ else:
311
+ original_path = child
312
+
313
+ # Slug 是目录名(扁平形式);从 '+' 恢复 '/'
314
+ slug = child.name.replace("+", "/")
315
+ results.append(
316
+ WorktreeInfo(
317
+ slug=slug,
318
+ path=child,
319
+ branch=branch,
320
+ original_path=original_path,
321
+ created_at=child.stat().st_mtime,
322
+ )
323
+ )
324
+
325
+ return results
326
+
327
+ async def cleanup_stale(self, active_agent_ids: set[str] | None = None) -> list[str]:
328
+ """移除没有活跃代理的 worktree。
329
+
330
+ Args:
331
+ active_agent_ids: 仍在运行的代理 ID 集合。如果为 None,
332
+ *所有* 有 agent_id 的 worktree 都被视为过期。
333
+
334
+ Returns:
335
+ 已移除的 slugs 列表。
336
+ """
337
+ worktrees = await self.list_worktrees()
338
+ removed: list[str] = []
339
+ for info in worktrees:
340
+ if info.agent_id is None:
341
+ continue
342
+ if active_agent_ids is not None and info.agent_id in active_agent_ids:
343
+ continue
344
+ ok = await self.remove_worktree(info.slug)
345
+ if ok:
346
+ removed.append(info.slug)
347
+ return removed
@@ -0,0 +1,33 @@
1
+ """
2
+ 任务模块导出
3
+ ==========
4
+
5
+ 本模块导出 tasks 子目录中的公共接口。
6
+
7
+ 导出内容:
8
+ - BackgroundTaskManager: 后台任务管理器
9
+ - TaskRecord: 任务记录
10
+ - TaskStatus: 任务状态类型
11
+ - TaskType: 任务类型
12
+ - get_task_manager: 获取任务管理器
13
+ - spawn_local_agent_task: 启动本地 agent 任务
14
+ - spawn_shell_task: 启动 shell 任务
15
+ - stop_task: 停止任务
16
+ """
17
+
18
+ from illusion.tasks.local_agent_task import spawn_local_agent_task
19
+ from illusion.tasks.local_shell_task import spawn_shell_task
20
+ from illusion.tasks.manager import BackgroundTaskManager, get_task_manager
21
+ from illusion.tasks.stop_task import stop_task
22
+ from illusion.tasks.types import TaskRecord, TaskStatus, TaskType
23
+
24
+ __all__ = [
25
+ "BackgroundTaskManager",
26
+ "TaskRecord",
27
+ "TaskStatus",
28
+ "TaskType",
29
+ "get_task_manager",
30
+ "spawn_local_agent_task",
31
+ "spawn_shell_task",
32
+ "stop_task",
33
+ ]
@@ -0,0 +1,42 @@
1
+ """
2
+ 本地 Agent 任务外观模块
3
+ ==================
4
+
5
+ 本模块提供本地 agent 子进程任务的简单接口。
6
+
7
+ 使用示例:
8
+ >>> from illusion.tasks.local_agent_task import spawn_local_agent_task
9
+ >>> # 启动本地 agent 任务
10
+ >>> record = await spawn_local_agent_task(
11
+ ... prompt="帮我写一个 Hello World 程序",
12
+ ... description="编写程序",
13
+ ... cwd="."
14
+ ... )
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ from illusion.tasks.manager import get_task_manager
22
+ from illusion.tasks.types import TaskRecord
23
+
24
+
25
+ async def spawn_local_agent_task(
26
+ *,
27
+ prompt: str,
28
+ description: str,
29
+ cwd: str | Path,
30
+ model: str | None = None,
31
+ api_key: str | None = None,
32
+ command: str | None = None,
33
+ ) -> TaskRecord:
34
+ """启动本地 agent 子进程任务。"""
35
+ return await get_task_manager().create_agent_task(
36
+ prompt=prompt,
37
+ description=description,
38
+ cwd=cwd,
39
+ model=model,
40
+ api_key=api_key,
41
+ command=command,
42
+ )
@@ -0,0 +1,27 @@
1
+ """
2
+ 本地 Shell 任务外观模块
3
+ =====================
4
+
5
+ 本模块提供本地 shell 任务的简单接口。
6
+
7
+ 使用示例:
8
+ >>> from illusion.tasks.local_shell_task import spawn_shell_task
9
+ >>> # 启动本地 shell 任务
10
+ >>> record = await spawn_shell_task("ls -la", "列出文件", ".")
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+
17
+ from illusion.tasks.manager import get_task_manager
18
+ from illusion.tasks.types import TaskRecord
19
+
20
+
21
+ async def spawn_shell_task(command: str, description: str, cwd: str | Path) -> TaskRecord:
22
+ """启动本地 shell 任务。"""
23
+ return await get_task_manager().create_shell_task(
24
+ command=command,
25
+ description=description,
26
+ cwd=cwd,
27
+ )