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,377 @@
1
+ """
2
+ 后台任务管理器模块
3
+ =================
4
+
5
+ 本模块管理后台 shell 和 agent 子进程任务。
6
+
7
+ 主要功能:
8
+ - 创建待处理任务
9
+ - 创建 Shell 任务
10
+ - 创建 Agent 任务
11
+ - 更新任务
12
+ - 停止任务
13
+ - 读写任务输出
14
+
15
+ 类说明:
16
+ - BackgroundTaskManager: 后台任务管理器类
17
+ - create_pending_task: 创建待处理任务
18
+ - create_shell_task: 创建 Shell 任务
19
+ - create_agent_task: 创建 Agent 任务
20
+ - update_task: 更新任务
21
+ - stop_task: 停止任务
22
+
23
+ 使用示例:
24
+ >>> from illusion.tasks.manager import BackgroundTaskManager, get_task_manager
25
+ >>> # 获取任务管理器
26
+ >>> manager = get_task_manager()
27
+ >>> # 创建 Shell 任务
28
+ >>> record = await manager.create_shell_task(command="ls -la", description="列出文件", cwd=".")
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import asyncio
34
+ import os
35
+ import shlex
36
+ import time
37
+ from dataclasses import replace
38
+ from pathlib import Path
39
+ from uuid import uuid4
40
+
41
+ from illusion.config.paths import get_tasks_dir
42
+ from illusion.tasks.types import TaskRecord, TaskStatus, TaskType, to_task_internal_status
43
+ from illusion.utils.shell import create_shell_subprocess
44
+
45
+
46
+ class BackgroundTaskManager:
47
+ """管理 shell 和 agent 子进程任务。"""
48
+
49
+ def __init__(self) -> None:
50
+ self._tasks: dict[str, TaskRecord] = {}
51
+ self._processes: dict[str, asyncio.subprocess.Process] = {}
52
+ self._waiters: dict[str, asyncio.Task[None]] = {}
53
+ self._output_locks: dict[str, asyncio.Lock] = {}
54
+ self._input_locks: dict[str, asyncio.Lock] = {}
55
+ self._generations: dict[str, int] = {}
56
+
57
+ def create_pending_task(
58
+ self,
59
+ *,
60
+ subject: str,
61
+ description: str,
62
+ active_form: str | None = None,
63
+ ) -> TaskRecord:
64
+ """创建用于跟踪的待处理任务(非后台进程)。"""
65
+ task_id = _task_id("in_process_teammate")
66
+ output_path = get_tasks_dir() / f"{task_id}.log"
67
+ record = TaskRecord(
68
+ id=task_id,
69
+ type="in_process_teammate",
70
+ status="pending",
71
+ description=description,
72
+ subject=subject,
73
+ active_form=active_form,
74
+ cwd=str(Path.cwd().resolve()),
75
+ output_file=output_path,
76
+ created_at=time.time(),
77
+ )
78
+ output_path.parent.mkdir(parents=True, exist_ok=True)
79
+ output_path.write_text("", encoding="utf-8")
80
+ self._tasks[task_id] = record
81
+ return record
82
+
83
+ async def create_shell_task(
84
+ self,
85
+ *,
86
+ command: str,
87
+ description: str,
88
+ cwd: str | Path,
89
+ task_type: TaskType = "local_bash",
90
+ ) -> TaskRecord:
91
+ """启动后台 shell 命令。"""
92
+ task_id = _task_id(task_type)
93
+ output_path = get_tasks_dir() / f"{task_id}.log"
94
+ record = TaskRecord(
95
+ id=task_id,
96
+ type=task_type,
97
+ status="running",
98
+ description=description,
99
+ cwd=str(Path(cwd).resolve()),
100
+ output_file=output_path,
101
+ command=command,
102
+ created_at=time.time(),
103
+ started_at=time.time(),
104
+ )
105
+ output_path.write_text("", encoding="utf-8")
106
+ self._tasks[task_id] = record
107
+ self._output_locks[task_id] = asyncio.Lock()
108
+ self._input_locks[task_id] = asyncio.Lock()
109
+ await self._start_process(task_id)
110
+ return record
111
+
112
+ async def create_agent_task(
113
+ self,
114
+ *,
115
+ prompt: str,
116
+ description: str,
117
+ cwd: str | Path,
118
+ task_type: TaskType = "local_agent",
119
+ model: str | None = None,
120
+ api_key: str | None = None,
121
+ command: str | None = None,
122
+ ) -> TaskRecord:
123
+ """作为子进程启动本地 agent 任务。"""
124
+ if command is None:
125
+ effective_api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
126
+ if not effective_api_key:
127
+ raise ValueError(
128
+ "Local agent tasks require ANTHROPIC_API_KEY or an explicit command override"
129
+ )
130
+ cmd = ["python", "-m", "illusion", "--headless", "--api-key", effective_api_key]
131
+ if model:
132
+ cmd.extend(["--model", model])
133
+ command = " ".join(shlex.quote(part) for part in cmd)
134
+
135
+ record = await self.create_shell_task(
136
+ command=command,
137
+ description=description,
138
+ cwd=cwd,
139
+ task_type=task_type,
140
+ )
141
+ updated = replace(record, prompt=prompt)
142
+ if task_type != "local_agent":
143
+ updated.metadata["agent_mode"] = task_type
144
+ self._tasks[record.id] = updated
145
+ await self.write_to_task(record.id, prompt)
146
+ return updated
147
+
148
+ def get_task(self, task_id: str) -> TaskRecord | None:
149
+ """返回一个任务记录。"""
150
+ return self._tasks.get(task_id)
151
+
152
+ def list_tasks(self, *, status: TaskStatus | None = None) -> list[TaskRecord]:
153
+ """返回所有任务,可选按状态过滤。"""
154
+ tasks = list(self._tasks.values())
155
+ if status is not None:
156
+ tasks = [task for task in tasks if task.status == status]
157
+ return sorted(tasks, key=lambda item: item.created_at, reverse=True)
158
+
159
+ def update_task(
160
+ self,
161
+ task_id: str,
162
+ *,
163
+ subject: str | None = None,
164
+ description: str | None = None,
165
+ active_form: str | None = None,
166
+ status: str | None = None,
167
+ owner: str | None = None,
168
+ progress: int | None = None,
169
+ status_note: str | None = None,
170
+ metadata: dict | None = None,
171
+ add_blocks: list[str] | None = None,
172
+ add_blocked_by: list[str] | None = None,
173
+ comments: str | None = None,
174
+ ) -> TaskRecord:
175
+ """更新用于协调和 UI 显示的可变任务元数据。"""
176
+ task = self._require_task(task_id)
177
+
178
+ # 处理删除
179
+ if status == "deleted":
180
+ self._tasks.pop(task_id, None)
181
+ return task
182
+
183
+ if subject is not None:
184
+ task.subject = subject
185
+ if description is not None and description.strip():
186
+ task.description = description.strip()
187
+ if active_form is not None:
188
+ task.active_form = active_form
189
+ if status is not None:
190
+ task.status = to_task_internal_status(status)
191
+ if owner is not None:
192
+ task.owner = owner
193
+ if progress is not None:
194
+ task.metadata["progress"] = str(progress)
195
+ if status_note is not None:
196
+ note = status_note.strip()
197
+ if note:
198
+ task.metadata["status_note"] = note
199
+ else:
200
+ task.metadata.pop("status_note", None)
201
+ if metadata is not None:
202
+ for key, value in metadata.items():
203
+ if value is None:
204
+ task.metadata.pop(key, None)
205
+ else:
206
+ task.metadata[key] = str(value)
207
+ if add_blocks is not None:
208
+ for block_id in add_blocks:
209
+ if block_id not in task.blocks:
210
+ task.blocks.append(block_id)
211
+ if add_blocked_by is not None:
212
+ for blocker_id in add_blocked_by:
213
+ if blocker_id not in task.blocked_by:
214
+ task.blocked_by.append(blocker_id)
215
+ if comments is not None:
216
+ task.comments.append(comments)
217
+ return task
218
+
219
+ async def stop_task(self, task_id: str) -> TaskRecord:
220
+ """终止运行中的任务。"""
221
+ task = self._require_task(task_id)
222
+ process = self._processes.get(task_id)
223
+ if process is None:
224
+ if task.status in {"completed", "failed", "killed"}:
225
+ return task
226
+ raise ValueError(f"Task {task_id} is not running")
227
+
228
+ process.terminate()
229
+ try:
230
+ await asyncio.wait_for(process.wait(), timeout=3)
231
+ except asyncio.TimeoutError:
232
+ process.kill()
233
+ await process.wait()
234
+
235
+ task.status = "killed"
236
+ task.ended_at = time.time()
237
+ return task
238
+
239
+ async def write_to_task(self, task_id: str, data: str) -> None:
240
+ """向任务 stdin 写入一行,需要时自动恢复本地 agent。"""
241
+ task = self._require_task(task_id)
242
+ async with self._input_locks[task_id]:
243
+ process = await self._ensure_writable_process(task)
244
+ process.stdin.write((data.rstrip("\n") + "\n").encode("utf-8"))
245
+ try:
246
+ await process.stdin.drain()
247
+ except (BrokenPipeError, ConnectionResetError):
248
+ if task.type not in {"local_agent", "remote_agent", "in_process_teammate"}:
249
+ raise ValueError(f"Task {task_id} does not accept input") from None
250
+ process = await self._restart_agent_task(task)
251
+ process.stdin.write((data.rstrip("\n") + "\n").encode("utf-8"))
252
+ await process.stdin.drain()
253
+
254
+ def read_task_output(self, task_id: str, *, max_bytes: int = 12000) -> str:
255
+ """返回任务输出文件的尾部。"""
256
+ task = self._require_task(task_id)
257
+ content = task.output_file.read_text(encoding="utf-8", errors="replace")
258
+ if len(content) > max_bytes:
259
+ return content[-max_bytes:]
260
+ return content
261
+
262
+ async def _watch_process(
263
+ self,
264
+ task_id: str,
265
+ process: asyncio.subprocess.Process,
266
+ generation: int,
267
+ ) -> None:
268
+ """监视子进程直到完成。"""
269
+ reader = asyncio.create_task(self._copy_output(task_id, process))
270
+ return_code = await process.wait()
271
+ await reader
272
+
273
+ current_generation = self._generations.get(task_id)
274
+ if current_generation != generation:
275
+ return
276
+
277
+ task = self._tasks[task_id]
278
+ task.return_code = return_code
279
+ if task.status != "killed":
280
+ task.status = "completed" if return_code == 0 else "failed"
281
+ task.ended_at = time.time()
282
+ self._processes.pop(task_id, None)
283
+ self._waiters.pop(task_id, None)
284
+
285
+ async def _copy_output(self, task_id: str, process: asyncio.subprocess.Process) -> None:
286
+ """将进程输出复制到任务输出文件。"""
287
+ if process.stdout is None:
288
+ return
289
+ while True:
290
+ chunk = await process.stdout.read(4096)
291
+ if not chunk:
292
+ return
293
+ async with self._output_locks[task_id]:
294
+ with self._tasks[task_id].output_file.open("ab") as handle:
295
+ handle.write(chunk)
296
+
297
+ def _require_task(self, task_id: str) -> TaskRecord:
298
+ """返回任务记录,不存在则抛出异常。"""
299
+ task = self._tasks.get(task_id)
300
+ if task is None:
301
+ raise ValueError(f"No task found with ID: {task_id}")
302
+ return task
303
+
304
+ async def _start_process(self, task_id: str) -> asyncio.subprocess.Process:
305
+ """启动任务进程。"""
306
+ task = self._require_task(task_id)
307
+ if task.command is None:
308
+ raise ValueError(f"Task {task_id} does not have a command to run")
309
+
310
+ generation = self._generations.get(task_id, 0) + 1
311
+ self._generations[task_id] = generation
312
+ process = await create_shell_subprocess(
313
+ task.command,
314
+ cwd=task.cwd,
315
+ stdin=asyncio.subprocess.PIPE,
316
+ stdout=asyncio.subprocess.PIPE,
317
+ stderr=asyncio.subprocess.STDOUT,
318
+ )
319
+ self._processes[task_id] = process
320
+ self._waiters[task_id] = asyncio.create_task(
321
+ self._watch_process(task_id, process, generation)
322
+ )
323
+ return process
324
+
325
+ async def _ensure_writable_process(
326
+ self,
327
+ task: TaskRecord,
328
+ ) -> asyncio.subprocess.Process:
329
+ """确保任务可写入,必要时重启。"""
330
+ process = self._processes.get(task.id)
331
+ if process is not None and process.stdin is not None and process.returncode is None:
332
+ return process
333
+ if task.type not in {"local_agent", "remote_agent", "in_process_teammate"}:
334
+ raise ValueError(f"Task {task.id} does not accept input")
335
+ return await self._restart_agent_task(task)
336
+
337
+ async def _restart_agent_task(self, task: TaskRecord) -> asyncio.subprocess.Process:
338
+ """重启 agent 任务。"""
339
+ if task.command is None:
340
+ raise ValueError(f"Task {task.id} does not have a restart command")
341
+
342
+ waiter = self._waiters.get(task.id)
343
+ if waiter is not None and not waiter.done():
344
+ await waiter
345
+
346
+ restart_count = int(task.metadata.get("restart_count", "0")) + 1
347
+ task.metadata["restart_count"] = str(restart_count)
348
+ task.status = "running"
349
+ task.started_at = time.time()
350
+ task.ended_at = None
351
+ task.return_code = None
352
+ return await self._start_process(task.id)
353
+
354
+
355
+ # 按任务目录隔离的任务管理器缓存
356
+ _MANAGERS_BY_KEY: dict[str, BackgroundTaskManager] = {}
357
+
358
+
359
+ def get_task_manager() -> BackgroundTaskManager:
360
+ """返回单例任务管理器。"""
361
+ current_key = str(get_tasks_dir().resolve())
362
+ manager = _MANAGERS_BY_KEY.get(current_key)
363
+ if manager is None:
364
+ manager = BackgroundTaskManager()
365
+ _MANAGERS_BY_KEY[current_key] = manager
366
+ return manager
367
+
368
+
369
+ def _task_id(task_type: TaskType) -> str:
370
+ """生成任务 ID 前缀。"""
371
+ prefixes = {
372
+ "local_bash": "b",
373
+ "local_agent": "a",
374
+ "remote_agent": "r",
375
+ "in_process_teammate": "t",
376
+ }
377
+ return f"{prefixes[task_type]}{uuid4().hex[:8]}"
@@ -0,0 +1,21 @@
1
+ """
2
+ 停止任务辅助模块
3
+ ================
4
+
5
+ 本模块提供停止运行中任务的辅助函数。
6
+
7
+ 使用示例:
8
+ >>> from illusion.tasks.stop_task import stop_task
9
+ >>> # 停止任务
10
+ >>> await stop_task("task_id")
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from illusion.tasks.manager import get_task_manager
16
+ from illusion.tasks.types import TaskRecord
17
+
18
+
19
+ async def stop_task(task_id: str) -> TaskRecord:
20
+ """通过默认任务管理器停止运行中的任务。"""
21
+ return await get_task_manager().stop_task(task_id)
@@ -0,0 +1,88 @@
1
+ """
2
+ 任务数据模型模块
3
+ ================
4
+
5
+ 本模块定义任务相关的数据类型。
6
+
7
+ 类型说明:
8
+ - TaskType: 任务类型
9
+ - TaskStatus: 任务状态
10
+ - TaskUpdateStatus: 任务更新状态(含 deleted)
11
+
12
+ 类说明:
13
+ - TaskRecord: 后台任务的运行时表示
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import Literal, cast
21
+
22
+
23
+ # 任务类型
24
+ TaskType = Literal["local_bash", "local_agent", "remote_agent", "in_process_teammate"]
25
+ # 任务状态
26
+ TaskStatus = Literal["pending", "running", "completed", "failed", "killed"]
27
+ # 对外显示状态
28
+ TaskDisplayStatus = Literal["pending", "in_progress", "completed", "failed", "killed"]
29
+
30
+ # 扩展状态,包含任务更新操作的 deleted
31
+ TaskUpdateStatus = Literal["pending", "in_progress", "completed", "deleted"]
32
+
33
+ _INTERNAL_TO_DISPLAY_STATUS: dict[str, str] = {
34
+ "pending": "pending",
35
+ "running": "in_progress",
36
+ "completed": "completed",
37
+ "failed": "failed",
38
+ "killed": "killed",
39
+ }
40
+
41
+ _DISPLAY_TO_INTERNAL_STATUS: dict[str, str] = {
42
+ "pending": "pending",
43
+ "in_progress": "running",
44
+ "completed": "completed",
45
+ "failed": "failed",
46
+ "killed": "killed",
47
+ }
48
+
49
+
50
+ def to_task_display_status(status: TaskStatus | str) -> TaskDisplayStatus | str:
51
+ """将内部任务状态转换为对外显示状态。"""
52
+ mapped = _INTERNAL_TO_DISPLAY_STATUS.get(status)
53
+ if mapped is None:
54
+ return status
55
+ return cast(TaskDisplayStatus, mapped)
56
+
57
+
58
+ def to_task_internal_status(status: TaskUpdateStatus | TaskDisplayStatus | TaskStatus | str) -> TaskStatus:
59
+ """将对外状态转换为内部任务状态。"""
60
+ mapped = _DISPLAY_TO_INTERNAL_STATUS.get(status, status)
61
+ if mapped not in {"pending", "running", "completed", "failed", "killed"}:
62
+ raise ValueError(f"Unsupported task status: {status}")
63
+ return cast(TaskStatus, mapped)
64
+
65
+
66
+ @dataclass
67
+ class TaskRecord:
68
+ """后台任务的运行时表示。"""
69
+
70
+ id: str
71
+ type: TaskType
72
+ status: TaskStatus
73
+ description: str
74
+ cwd: str
75
+ output_file: Path
76
+ command: str | None = None
77
+ prompt: str | None = None
78
+ subject: str | None = None
79
+ active_form: str | None = None
80
+ owner: str | None = None
81
+ blocked_by: list[str] = field(default_factory=list)
82
+ blocks: list[str] = field(default_factory=list)
83
+ created_at: float = 0.0
84
+ started_at: float | None = None
85
+ ended_at: float | None = None
86
+ return_code: int | None = None
87
+ metadata: dict[str, str] = field(default_factory=dict)
88
+ comments: list[str] = field(default_factory=list)
@@ -0,0 +1,126 @@
1
+ """
2
+ 内置工具注册模块
3
+ ================
4
+
5
+ 本模块提供 IllusionCode 内置工具的注册和管理功能。
6
+
7
+ 主要组件:
8
+ - BaseTool: 工具抽象基类
9
+ - ToolExecutionContext: 工具执行上下文
10
+ - ToolResult: 工具执行结果
11
+ - ToolRegistry: 工具注册表
12
+ - create_default_tool_registry: 创建默认工具注册表
13
+
14
+ 使用示例:
15
+ >>> from illusion.tools import create_default_tool_registry, ToolRegistry
16
+ >>> registry = create_default_tool_registry()
17
+ """
18
+
19
+ from illusion.tools.ask_user_question_tool import AskUserQuestionTool
20
+ from illusion.tools.agent_tool import AgentTool
21
+ from illusion.tools.bash_tool import BashTool
22
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolRegistry, ToolResult
23
+ from illusion.tools.config_tool import ConfigTool
24
+ from illusion.tools.cron_tool import CronTool
25
+ from illusion.tools.enter_plan_mode_tool import EnterPlanModeTool
26
+ from illusion.tools.enter_worktree_tool import EnterWorktreeTool
27
+ from illusion.tools.exit_plan_mode_tool import ExitPlanModeTool
28
+ from illusion.tools.exit_worktree_tool import ExitWorktreeTool
29
+ from illusion.tools.file_edit_tool import FileEditTool
30
+ from illusion.tools.file_read_tool import FileReadTool
31
+ from illusion.tools.file_write_tool import FileWriteTool
32
+ from illusion.tools.glob_tool import GlobTool
33
+ from illusion.tools.grep_tool import GrepTool
34
+ from illusion.tools.list_mcp_resources_tool import ListMcpResourcesTool
35
+ from illusion.tools.lsp_tool import LspTool
36
+ from illusion.tools.mcp_auth_tool import McpAuthTool
37
+ from illusion.tools.mcp_tool import McpToolAdapter
38
+ from illusion.tools.notebook_edit_tool import NotebookEditTool
39
+ from illusion.tools.powershell_tool import PowerShellTool
40
+ from illusion.tools.read_mcp_resource_tool import ReadMcpResourceTool
41
+ from illusion.tools.repl_tool import ReplTool
42
+ from illusion.tools.send_message_tool import SendMessageTool
43
+ from illusion.tools.skill_tool import SkillTool
44
+ from illusion.tools.sleep_tool import SleepTool
45
+ from illusion.tools.structured_output_tool import StructuredOutputTool
46
+ from illusion.tools.team_create_tool import TeamCreateTool
47
+ from illusion.tools.team_delete_tool import TeamDeleteTool
48
+ from illusion.tools.task_create_tool import TaskCreateTool
49
+ from illusion.tools.task_get_tool import TaskGetTool
50
+ from illusion.tools.task_list_tool import TaskListTool
51
+ from illusion.tools.task_output_tool import TaskOutputTool
52
+ from illusion.tools.task_stop_tool import TaskStopTool
53
+ from illusion.tools.task_update_tool import TaskUpdateTool
54
+ from illusion.tools.todo_write_tool import TodoWriteTool
55
+ from illusion.tools.tool_search_tool import ToolSearchTool
56
+ from illusion.tools.web_fetch_tool import WebFetchTool
57
+ from illusion.tools.web_search_tool import WebSearchTool
58
+
59
+
60
+ def create_default_tool_registry(mcp_manager=None, is_interactive: bool = True) -> ToolRegistry:
61
+ """返回默认内置工具注册表
62
+
63
+ Args:
64
+ mcp_manager: MCP 管理器(可选)
65
+ is_interactive: 是否为交互模式(默认True)。非交互模式下会加载StructuredOutputTool。
66
+
67
+ Returns:
68
+ ToolRegistry: 工具注册表
69
+ """
70
+ registry = ToolRegistry()
71
+ tools = [
72
+ BashTool(),
73
+ PowerShellTool(),
74
+ ReplTool(),
75
+ AskUserQuestionTool(),
76
+ FileReadTool(),
77
+ FileWriteTool(),
78
+ FileEditTool(),
79
+ NotebookEditTool(),
80
+ LspTool(),
81
+ McpAuthTool(),
82
+ GlobTool(),
83
+ GrepTool(),
84
+ SkillTool(),
85
+ ToolSearchTool(),
86
+ WebFetchTool(),
87
+ WebSearchTool(),
88
+ ConfigTool(),
89
+ SleepTool(),
90
+ EnterWorktreeTool(),
91
+ ExitWorktreeTool(),
92
+ TodoWriteTool(),
93
+ EnterPlanModeTool(),
94
+ ExitPlanModeTool(),
95
+ CronTool(),
96
+ TaskCreateTool(),
97
+ TaskGetTool(),
98
+ TaskListTool(),
99
+ TaskStopTool(),
100
+ TaskOutputTool(),
101
+ TaskUpdateTool(),
102
+ AgentTool(),
103
+ SendMessageTool(),
104
+ TeamCreateTool(),
105
+ TeamDeleteTool(),
106
+ ]
107
+ # StructuredOutputTool 只在非交互模式下加载
108
+ if not is_interactive:
109
+ tools.append(StructuredOutputTool())
110
+ for tool in tools:
111
+ registry.register(tool)
112
+ if mcp_manager is not None:
113
+ registry.register(ListMcpResourcesTool(mcp_manager))
114
+ registry.register(ReadMcpResourceTool(mcp_manager))
115
+ for tool_info in mcp_manager.list_tools():
116
+ registry.register(McpToolAdapter(mcp_manager, tool_info))
117
+ return registry
118
+
119
+
120
+ __all__ = [
121
+ "BaseTool",
122
+ "ToolExecutionContext",
123
+ "ToolRegistry",
124
+ "ToolResult",
125
+ "create_default_tool_registry",
126
+ ]