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,259 @@
1
+ """
2
+ 进程内代理执行后端模块
3
+ =====================
4
+
5
+ 本模块实现进程内的代理执行后端。
6
+ 使用 :mod:`contextvars` 在当前 Python 进程中将代理作为 asyncio Task 运行,
7
+ 实现每个代理的上下文隔离。
8
+
9
+ 主要组件:
10
+ - InProcessBackend: 进程内执行后端,实现 TeammateExecutor 协议
11
+
12
+ 使用示例:
13
+ >>> from illusion.swarm.in_process import InProcessBackend
14
+ >>> backend = InProcessBackend()
15
+ >>> result = await backend.spawn(config)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import asyncio
21
+ import contextlib
22
+ import logging
23
+ import time
24
+ import uuid
25
+ from dataclasses import dataclass, field
26
+
27
+ from illusion.swarm.agent_executor import (
28
+ AgentExecutionContext,
29
+ AgentSpawnConfig,
30
+ AgentAbortController,
31
+ set_agent_context,
32
+ _register_agent,
33
+ _unregister_agent,
34
+ )
35
+ from illusion.swarm.types import (
36
+ BackendType,
37
+ SpawnResult,
38
+ TeammateMessage,
39
+ TeammateSpawnConfig,
40
+ )
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # InProcessBackend
47
+ # ---------------------------------------------------------------------------
48
+
49
+
50
+ @dataclass
51
+ class _AgentEntry:
52
+ """运行中进程内代理的内部注册表条目。"""
53
+
54
+ task: asyncio.Task[None]
55
+ abort_controller: AgentAbortController
56
+ task_id: str
57
+ started_at: float = field(default_factory=time.time)
58
+
59
+
60
+ class InProcessBackend:
61
+ """将代理作为当前进程中的 asyncio Task 运行的 TeammateExecutor。"""
62
+
63
+ type: BackendType = "in_process"
64
+
65
+ def __init__(self) -> None:
66
+ self._active: dict[str, _AgentEntry] = {}
67
+
68
+ def is_available(self) -> bool:
69
+ """进程内后端始终可用。"""
70
+ return True
71
+
72
+ async def spawn(self, config: TeammateSpawnConfig) -> SpawnResult:
73
+ """将进程内代理生成为 asyncio Task。"""
74
+ agent_id = f"{config.name}@{config.team}"
75
+ task_id = f"in_process_{uuid.uuid4().hex[:12]}"
76
+
77
+ # 检查是否已存在活跃的同名代理
78
+ if agent_id in self._active:
79
+ entry = self._active[agent_id]
80
+ if not entry.task.done():
81
+ logger.warning("[InProcessBackend] spawn(): %s is already running", agent_id)
82
+ return SpawnResult(
83
+ task_id=task_id,
84
+ agent_id=agent_id,
85
+ backend_type=self.type,
86
+ success=False,
87
+ error=f"Agent {agent_id!r} is already running",
88
+ )
89
+
90
+ # 创建中止控制器
91
+ abort_controller = AgentAbortController()
92
+
93
+ # 创建代理生成配置
94
+ spawn_config = AgentSpawnConfig(
95
+ name=config.name,
96
+ prompt=config.prompt,
97
+ cwd=config.cwd,
98
+ model=config.model,
99
+ system_prompt=config.system_prompt,
100
+ permission_mode=None,
101
+ parent_session_id=config.parent_session_id,
102
+ )
103
+
104
+ # 预先创建并注册执行上下文,以便 send_message 可以立即找到代理
105
+ from illusion.swarm.agent_executor import AgentExecutionContext, _register_agent
106
+ ctx = AgentExecutionContext(
107
+ agent_id=agent_id,
108
+ agent_name=config.name,
109
+ prompt=config.prompt,
110
+ model=config.model,
111
+ cwd=__import__("pathlib").Path(config.cwd),
112
+ abort_controller=abort_controller,
113
+ )
114
+ _register_agent(ctx)
115
+
116
+ # 创建 asyncio Task
117
+ task = asyncio.create_task(
118
+ self._run_agent(spawn_config, agent_id, abort_controller, ctx),
119
+ name=f"agent-{agent_id}",
120
+ )
121
+
122
+ entry = _AgentEntry(
123
+ task=task,
124
+ abort_controller=abort_controller,
125
+ task_id=task_id,
126
+ )
127
+ self._active[agent_id] = entry
128
+
129
+ # 添加完成回调
130
+ def _on_done(t: asyncio.Task[None]) -> None:
131
+ self._active.pop(agent_id, None)
132
+ if not t.cancelled() and t.exception() is not None:
133
+ logger.error("[InProcessBackend] Agent %s raised exception: %s", agent_id, t.exception())
134
+
135
+ task.add_done_callback(_on_done)
136
+
137
+ logger.debug("[InProcessBackend] spawned %s (task_id=%s)", agent_id, task_id)
138
+ return SpawnResult(
139
+ task_id=task_id,
140
+ agent_id=agent_id,
141
+ backend_type=self.type,
142
+ )
143
+
144
+ async def _run_agent(
145
+ self,
146
+ config: AgentSpawnConfig,
147
+ agent_id: str,
148
+ abort_controller: AgentAbortController,
149
+ ctx: AgentExecutionContext | None = None,
150
+ ) -> None:
151
+ """运行代理的内部协程。"""
152
+ if ctx is None:
153
+ ctx = AgentExecutionContext(
154
+ agent_id=agent_id,
155
+ agent_name=config.name,
156
+ prompt=config.prompt,
157
+ model=config.model,
158
+ cwd=__import__("pathlib").Path(config.cwd),
159
+ abort_controller=abort_controller,
160
+ )
161
+ _register_agent(ctx)
162
+ set_agent_context(ctx)
163
+
164
+ try:
165
+ # 注意:这里需要 query_context 和 parent_registry
166
+ # 在实际使用中,这些应该通过某种方式传入
167
+ # 目前这是一个占位实现
168
+ logger.info("[InProcessBackend] %s: agent started (stub)", agent_id)
169
+ ctx.status = "running"
170
+
171
+ # 等待取消或完成
172
+ while not abort_controller.is_cancelled:
173
+ await asyncio.sleep(0.1)
174
+
175
+ except asyncio.CancelledError:
176
+ logger.debug("[InProcessBackend] %s: task cancelled", agent_id)
177
+ raise
178
+ except Exception:
179
+ logger.exception("[InProcessBackend] %s: unhandled exception", agent_id)
180
+ finally:
181
+ ctx.status = "stopped"
182
+ _unregister_agent(agent_id)
183
+
184
+ async def send_message(self, agent_id: str, message: TeammateMessage) -> None:
185
+ """向运行中的代理发送消息。"""
186
+ # 首先检查 self._active 中是否有该代理
187
+ entry = self._active.get(agent_id)
188
+ if entry is not None and not entry.task.done():
189
+ # 代理正在运行,但需要找到其 AgentExecutionContext
190
+ # 从全局注册表中查找
191
+ from illusion.swarm.agent_executor import get_active_agent
192
+ agent_ctx = get_active_agent(agent_id)
193
+ if agent_ctx is not None:
194
+ await agent_ctx.message_queue.put(message)
195
+ logger.debug("[InProcessBackend] sent message to %s", agent_id)
196
+ return
197
+
198
+ # 回退:尝试从全局注册表查找
199
+ from illusion.swarm.agent_executor import get_active_agent, get_active_agent_by_name
200
+ agent_name = agent_id.split("@")[0] if "@" in agent_id else agent_id
201
+
202
+ agent_ctx = get_active_agent(agent_id)
203
+ if agent_ctx is None:
204
+ agent_ctx = get_active_agent_by_name(agent_name)
205
+
206
+ if agent_ctx is not None:
207
+ await agent_ctx.message_queue.put(message)
208
+ logger.debug("[InProcessBackend] sent message to %s", agent_id)
209
+ else:
210
+ raise ValueError(f"No active agent found for {agent_id!r}")
211
+
212
+ async def shutdown(self, agent_id: str, *, force: bool = False, timeout: float = 10.0) -> bool:
213
+ """终止运行中的进程内代理。"""
214
+ entry = self._active.get(agent_id)
215
+ if entry is None:
216
+ logger.debug("[InProcessBackend] shutdown(): %s not found", agent_id)
217
+ return False
218
+
219
+ if entry.task.done():
220
+ self._active.pop(agent_id, None)
221
+ return True
222
+
223
+ if force:
224
+ entry.abort_controller.request_cancel(reason="force shutdown", force=True)
225
+ entry.task.cancel()
226
+ with contextlib.suppress(asyncio.CancelledError):
227
+ await asyncio.wait_for(asyncio.shield(entry.task), timeout=timeout)
228
+ else:
229
+ entry.abort_controller.request_cancel(reason="graceful shutdown")
230
+ try:
231
+ await asyncio.wait_for(asyncio.shield(entry.task), timeout=timeout)
232
+ except asyncio.TimeoutError:
233
+ logger.warning("[InProcessBackend] %s did not exit within %.1fs — forcing", agent_id, timeout)
234
+ entry.abort_controller.request_cancel(reason="timeout — forcing", force=True)
235
+ entry.task.cancel()
236
+ with contextlib.suppress(asyncio.CancelledError):
237
+ await entry.task
238
+
239
+ self._active.pop(agent_id, None)
240
+ logger.debug("[InProcessBackend] shut down %s", agent_id)
241
+ return True
242
+
243
+ def list_agents(self) -> list[tuple[str, bool, float]]:
244
+ """返回 (agent_id, is_running, duration_seconds) 元组列表。"""
245
+ now = time.time()
246
+ result = []
247
+ for agent_id, entry in self._active.items():
248
+ is_running = not entry.task.done()
249
+ duration = now - entry.started_at
250
+ result.append((agent_id, is_running, duration))
251
+ return result
252
+
253
+ async def shutdown_all(self, *, force: bool = False, timeout: float = 10.0) -> None:
254
+ """终止所有活跃代理。"""
255
+ agent_ids = list(self._active.keys())
256
+ await asyncio.gather(
257
+ *(self.shutdown(aid, force=force, timeout=timeout) for aid in agent_ids),
258
+ return_exceptions=True,
259
+ )
@@ -0,0 +1,136 @@
1
+ """
2
+ 子进程代理执行后端模块
3
+ =====================
4
+
5
+ 本模块实现基于子进程的 TeammateExecutor 接口。
6
+ 使用现有的 :class:`~illusion.tasks.manager.BackgroundTaskManager`
7
+ 来创建和管理子进程,通过 stdin/stdout 进行通信。
8
+
9
+ 主要组件:
10
+ - SubprocessBackend: 子进程执行后端
11
+
12
+ 使用示例:
13
+ >>> from illusion.swarm.subprocess_backend import SubprocessBackend
14
+ >>> backend = SubprocessBackend()
15
+ >>> result = await backend.spawn(config)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import logging
22
+
23
+ from illusion.swarm.agent_executor import (
24
+ _build_agent_cli_flags,
25
+ _build_agent_env_vars,
26
+ _get_agent_command,
27
+ )
28
+ from illusion.swarm.types import (
29
+ BackendType,
30
+ SpawnResult,
31
+ TeammateMessage,
32
+ TeammateSpawnConfig,
33
+ )
34
+ from illusion.tasks.manager import get_task_manager
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ class SubprocessBackend:
40
+ """TeammateExecutor 实现,每个代理作为独立子进程运行。"""
41
+
42
+ type: BackendType = "subprocess"
43
+
44
+ def __init__(self) -> None:
45
+ self._agent_tasks: dict[str, str] = {}
46
+
47
+ def is_available(self) -> bool:
48
+ """子进程后端始终可用。"""
49
+ return True
50
+
51
+ async def spawn(self, config: TeammateSpawnConfig) -> SpawnResult:
52
+ """通过任务管理器作为子进程生成新代理。"""
53
+ agent_id = f"{config.name}@{config.team}"
54
+
55
+ # 构建 CLI 命令
56
+ flags = _build_agent_cli_flags(
57
+ model=config.model,
58
+ permission_mode=None,
59
+ )
60
+ extra_env = _build_agent_env_vars()
61
+ env_prefix = " ".join(f"{k}={v!r}" for k, v in extra_env.items())
62
+
63
+ agent_cmd = _get_agent_command()
64
+ cmd_parts = [agent_cmd, "-m", "illusion"] + flags
65
+ command = f"{env_prefix} {' '.join(cmd_parts)}" if env_prefix else " ".join(cmd_parts)
66
+
67
+ # 创建任务
68
+ manager = get_task_manager()
69
+ try:
70
+ record = await manager.create_agent_task(
71
+ prompt=config.prompt,
72
+ description=f"Agent: {agent_id}",
73
+ cwd=config.cwd,
74
+ task_type="in_process_teammate",
75
+ model=config.model,
76
+ command=command,
77
+ )
78
+ except Exception as exc:
79
+ logger.error("[SubprocessBackend] Failed to spawn agent %s: %s", agent_id, exc)
80
+ return SpawnResult(
81
+ task_id="",
82
+ agent_id=agent_id,
83
+ backend_type=self.type,
84
+ success=False,
85
+ error=str(exc),
86
+ )
87
+
88
+ self._agent_tasks[agent_id] = record.id
89
+ logger.debug("[SubprocessBackend] Spawned agent %s as task %s", agent_id, record.id)
90
+ return SpawnResult(
91
+ task_id=record.id,
92
+ agent_id=agent_id,
93
+ backend_type=self.type,
94
+ )
95
+
96
+ async def send_message(self, agent_id: str, message: TeammateMessage) -> None:
97
+ """通过 stdin 向运行中的代理发送消息。"""
98
+ task_id = self._agent_tasks.get(agent_id)
99
+ if task_id is None:
100
+ raise ValueError(f"No active subprocess for agent {agent_id!r}")
101
+
102
+ payload = {
103
+ "text": message.text,
104
+ "from": message.from_agent,
105
+ "timestamp": message.timestamp,
106
+ }
107
+ if message.color:
108
+ payload["color"] = message.color
109
+ if message.summary:
110
+ payload["summary"] = message.summary
111
+
112
+ manager = get_task_manager()
113
+ await manager.write_to_task(task_id, json.dumps(payload))
114
+ logger.debug("[SubprocessBackend] Sent message to %s (task %s)", agent_id, task_id)
115
+
116
+ async def shutdown(self, agent_id: str, *, force: bool = False) -> bool:
117
+ """终止子进程代理。"""
118
+ task_id = self._agent_tasks.get(agent_id)
119
+ if task_id is None:
120
+ logger.warning("[SubprocessBackend] shutdown() called for unknown agent %s", agent_id)
121
+ return False
122
+
123
+ manager = get_task_manager()
124
+ try:
125
+ await manager.stop_task(task_id)
126
+ except ValueError as exc:
127
+ logger.debug("[SubprocessBackend] stop_task for %s: %s", task_id, exc)
128
+ finally:
129
+ self._agent_tasks.pop(agent_id, None)
130
+
131
+ logger.debug("[SubprocessBackend] Shut down agent %s (task %s)", agent_id, task_id)
132
+ return True
133
+
134
+ def get_task_id(self, agent_id: str) -> str | None:
135
+ """返回给定代理的任务管理器任务 ID。"""
136
+ return self._agent_tasks.get(agent_id)
@@ -0,0 +1,123 @@
1
+ """
2
+ Swarm 团队辅助模块
3
+ =================
4
+
5
+ 本模块提供 team_create / team_delete 相关的团队文件与目录管理能力。
6
+
7
+ 主要功能:
8
+ - 团队名称规范化
9
+ - 团队配置文件读写
10
+ - 团队任务目录初始化与重置
11
+ - 会话级团队清理注册
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import re
18
+ import shutil
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from illusion.config.paths import get_config_dir, get_data_dir
23
+
24
+ # 团队负责人固定名称,与上游行为对齐
25
+ TEAM_LEAD_NAME = "team-lead"
26
+
27
+ # 当前会话中创建的团队集合(用于会话结束时兜底清理)
28
+ _SESSION_CREATED_TEAMS: set[str] = set()
29
+
30
+
31
+ def sanitize_name(name: str) -> str:
32
+ """将名称转换为适合文件系统与标识符的 slug。"""
33
+ sanitized = re.sub(r"[^a-zA-Z0-9]", "-", name).strip("-").lower()
34
+ return sanitized or "team"
35
+
36
+
37
+ def get_team_dir(team_name: str) -> Path:
38
+ """返回团队目录路径(~/.illusion/teams/{team}/)。"""
39
+ return get_config_dir() / "teams" / sanitize_name(team_name)
40
+
41
+
42
+ def get_team_file_path(team_name: str) -> Path:
43
+ """返回团队配置文件路径。"""
44
+ return get_team_dir(team_name) / "config.json"
45
+
46
+
47
+ def get_team_tasks_dir(task_list_id: str) -> Path:
48
+ """返回团队任务目录路径(~/.illusion/data/tasks/{taskListId}/)。"""
49
+ return get_data_dir() / "tasks" / sanitize_name(task_list_id)
50
+
51
+
52
+ def read_team_file(team_name: str) -> dict[str, Any] | None:
53
+ """读取团队配置文件。"""
54
+ path = get_team_file_path(team_name)
55
+ if not path.exists():
56
+ return None
57
+ with path.open("r", encoding="utf-8") as handle:
58
+ data = json.load(handle)
59
+ if isinstance(data, dict):
60
+ return data
61
+ return None
62
+
63
+
64
+ def write_team_file(team_name: str, team_file: dict[str, Any]) -> None:
65
+ """写入团队配置文件。"""
66
+ path = get_team_file_path(team_name)
67
+ path.parent.mkdir(parents=True, exist_ok=True)
68
+ with path.open("w", encoding="utf-8") as handle:
69
+ json.dump(team_file, handle, ensure_ascii=False, indent=2)
70
+
71
+
72
+ def ensure_tasks_dir(task_list_id: str) -> None:
73
+ """确保团队任务目录存在。"""
74
+ get_team_tasks_dir(task_list_id).mkdir(parents=True, exist_ok=True)
75
+
76
+
77
+ def reset_task_list(task_list_id: str) -> None:
78
+ """重置团队任务目录内容。"""
79
+ tasks_dir = get_team_tasks_dir(task_list_id)
80
+ tasks_dir.mkdir(parents=True, exist_ok=True)
81
+ for child in tasks_dir.iterdir():
82
+ if child.is_dir():
83
+ shutil.rmtree(child, ignore_errors=True)
84
+ else:
85
+ child.unlink(missing_ok=True)
86
+
87
+
88
+ def register_team_for_session_cleanup(team_name: str) -> None:
89
+ """将团队登记到会话结束清理列表。"""
90
+ _SESSION_CREATED_TEAMS.add(team_name)
91
+
92
+
93
+ def unregister_team_for_session_cleanup(team_name: str) -> None:
94
+ """从会话结束清理列表中移除团队。"""
95
+ _SESSION_CREATED_TEAMS.discard(team_name)
96
+
97
+
98
+ def cleanup_team_directories(team_name: str) -> None:
99
+ """清理团队目录和团队任务目录。"""
100
+ team_file = read_team_file(team_name)
101
+ if team_file:
102
+ members = team_file.get("members", [])
103
+ if isinstance(members, list):
104
+ for member in members:
105
+ if not isinstance(member, dict):
106
+ continue
107
+ worktree_path = member.get("worktreePath")
108
+ if isinstance(worktree_path, str) and worktree_path.strip():
109
+ shutil.rmtree(Path(worktree_path), ignore_errors=True)
110
+
111
+ shutil.rmtree(get_team_dir(team_name), ignore_errors=True)
112
+ shutil.rmtree(get_team_tasks_dir(team_name), ignore_errors=True)
113
+
114
+
115
+ async def cleanup_session_teams() -> None:
116
+ """清理当前会话创建但未显式删除的团队。"""
117
+ if not _SESSION_CREATED_TEAMS:
118
+ return
119
+ teams = list(_SESSION_CREATED_TEAMS)
120
+ for team_name in teams:
121
+ cleanup_team_directories(team_name)
122
+ _SESSION_CREATED_TEAMS.clear()
123
+
@@ -0,0 +1,159 @@
1
+ """
2
+ Swarm 后端类型定义模块
3
+ =====================
4
+
5
+ 本模块定义 Agent 派发功能使用的所有类型和协议。
6
+ 包括后端类型、代理生成配置、消息类型等。
7
+
8
+ 类型定义:
9
+ - BackendType: 支持的后端类型
10
+ - TeammateSpawnConfig: 代理生成配置
11
+ - SpawnResult: 生成结果
12
+ - TeammateMessage: 代理间消息
13
+
14
+ 协议:
15
+ - TeammateExecutor: 代理执行器协议
16
+
17
+ 使用示例:
18
+ >>> from illusion.swarm.types import BackendType, TeammateExecutor, SpawnResult
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from dataclasses import dataclass, field
24
+ from typing import Literal, Protocol, runtime_checkable
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # 后端类型字面量
29
+ # ---------------------------------------------------------------------------
30
+
31
+ BackendType = Literal["subprocess", "in_process"]
32
+ """所有支持的后端类型。"""
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # 代理生成配置
37
+ # ---------------------------------------------------------------------------
38
+
39
+
40
+ @dataclass
41
+ class TeammateSpawnConfig:
42
+ """生成代理的配置。"""
43
+
44
+ name: str
45
+ """人类可读的代理名称(例如 ``"researcher"``)。"""
46
+
47
+ team: str
48
+ """此代理所属的团队名称。"""
49
+
50
+ prompt: str
51
+ """代理的初始提示词/任务。"""
52
+
53
+ cwd: str
54
+ """代理的工作目录。"""
55
+
56
+ parent_session_id: str
57
+ """父会话 ID(用于转录关联)。"""
58
+
59
+ model: str | None = None
60
+ """此代理的模型覆盖。"""
61
+
62
+ system_prompt: str | None = None
63
+ """代理的系统提示词。"""
64
+
65
+ system_prompt_mode: Literal["default", "replace", "append"] | None = None
66
+ """如何应用系统提示词:替换或追加到默认。"""
67
+
68
+ color: str | None = None
69
+ """代理的可选 UI 颜色。"""
70
+
71
+ color_override: str | None = None
72
+ """明确的颜色覆盖(优先于 ``color``)。"""
73
+
74
+ permissions: list[str] = field(default_factory=list)
75
+ """授予此代理的工具权限。"""
76
+
77
+ plan_mode_required: bool = False
78
+ """此代理是否必须在实现前进入 plan 模式。"""
79
+
80
+ allow_permission_prompts: bool = False
81
+ """当为 False(默认)时,未列出的工具被自动拒绝。"""
82
+
83
+ worktree_path: str | None = None
84
+ """可选的 git worktree 路径,用于隔离的文件系统访问。"""
85
+
86
+ session_id: str | None = None
87
+ """明确的会话 ID(如果未提供则生成)。"""
88
+
89
+
90
+ # ---------------------------------------------------------------------------
91
+ # 生成结果和消息
92
+ # ---------------------------------------------------------------------------
93
+
94
+
95
+ @dataclass
96
+ class SpawnResult:
97
+ """生成代理的结果。"""
98
+
99
+ task_id: str
100
+ """任务管理器中的任务 ID。"""
101
+
102
+ agent_id: str
103
+ """唯一代理标识符。"""
104
+
105
+ backend_type: BackendType
106
+ """用于生成此代理的后端。"""
107
+
108
+ success: bool = True
109
+ error: str | None = None
110
+
111
+
112
+ @dataclass
113
+ class TeammateMessage:
114
+ """发送给代理的消息。"""
115
+
116
+ text: str
117
+ from_agent: str
118
+ color: str | None = None
119
+ timestamp: str | None = None
120
+ summary: str | None = None
121
+
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # TeammateExecutor 协议
125
+ # ---------------------------------------------------------------------------
126
+
127
+
128
+ @runtime_checkable
129
+ class TeammateExecutor(Protocol):
130
+ """代理执行后端的协议。
131
+
132
+ 抽象化跨子进程和进程内后端的生成/消息/关闭操作。
133
+ """
134
+
135
+ type: BackendType
136
+
137
+ def is_available(self) -> bool:
138
+ """检查此后端在系统上是否可用。"""
139
+ ...
140
+
141
+ async def spawn(self, config: TeammateSpawnConfig) -> SpawnResult:
142
+ """使用给定配置生成新代理。"""
143
+ ...
144
+
145
+ async def send_message(self, agent_id: str, message: TeammateMessage) -> None:
146
+ """向运行中的代理发送消息。"""
147
+ ...
148
+
149
+ async def shutdown(self, agent_id: str, *, force: bool = False) -> bool:
150
+ """终止代理。
151
+
152
+ Args:
153
+ agent_id: 要终止的代理。
154
+ force: 如果为 True,立即杀死。如果为 False,尝试优雅关闭。
155
+
156
+ Returns:
157
+ 如果代理成功终止返回 True。
158
+ """
159
+ ...