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,384 @@
1
+ """
2
+ Protocol 协议模块
3
+ ==============
4
+
5
+ 本模块定义 React 终端前后端通信的结构化协议模型。
6
+
7
+ 主要功能:
8
+ - 前端请求模型(FrontendRequest)
9
+ - 后端事件模型(BackendEvent)
10
+ - 转录项模型(TranscriptItem)
11
+ - 任务快照模型(TaskSnapshot)
12
+
13
+ 类说明:
14
+ - FrontendRequest: 前端请求模型
15
+ - BackendEvent: 后端事件模型
16
+ - TranscriptItem: 转录项模型
17
+ - TaskSnapshot: 任务快照模型
18
+
19
+ 使用示例:
20
+ >>> from illusion.ui.protocol import FrontendRequest, BackendEvent, TranscriptItem
21
+ >>>
22
+ >>> # 创建前端请求
23
+ >>> request = FrontendRequest(type="submit_line", line="帮我写一个程序")
24
+ >>>
25
+ >>> # 创建后端事件
26
+ >>> event = BackendEvent.ready(state, tasks, commands)
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from typing import Any, Literal
32
+
33
+ from pydantic import BaseModel, Field
34
+
35
+ from illusion.state.app_state import AppState
36
+ from illusion.bridge.manager import BridgeSessionRecord
37
+ from illusion.mcp.types import McpConnectionStatus
38
+ from illusion.tasks.types import TaskRecord, to_task_display_status
39
+
40
+
41
+ class FrontendRequest(BaseModel):
42
+ """前端请求模型。
43
+
44
+ 表示从 React 前端发送到 Python 后端的请求。
45
+
46
+ Attributes:
47
+ type: 请求类型
48
+ line: 提交的行内容
49
+ command: 命令名称
50
+ command: 命令值
51
+ request_id: 请求 ID
52
+ allowed: 是否允许
53
+ always_allow: 是否总是允许
54
+ tool_name: 工具名称
55
+ answer: 用户答案
56
+ """
57
+
58
+ type: Literal[
59
+ "submit_line",
60
+ "stop",
61
+ "permission_response",
62
+ "question_response",
63
+ "list_sessions",
64
+ "select_command",
65
+ "apply_select_command",
66
+ "shutdown",
67
+ ]
68
+ line: str | None = None
69
+ command: str | None = None
70
+ value: str | None = None
71
+ request_id: str | None = None
72
+ allowed: bool | None = None
73
+ always_allow: bool | None = None
74
+ tool_name: str | None = None
75
+ answer: str | None = None
76
+
77
+
78
+ class TranscriptItem(BaseModel):
79
+ """转录项模型。
80
+
81
+ 表示前端呈现的一行转录内容。
82
+
83
+ Attributes:
84
+ role: 角色(system/user/assistant/tool/tool_result/log)
85
+ text: 文本内容
86
+ tool_name: 工具名称
87
+ tool_input: 工具输入参数
88
+ is_error: 是否为错误
89
+ reasoning: 思考文本(可选)
90
+ """
91
+
92
+ role: Literal["system", "user", "assistant", "tool", "tool_result", "log"]
93
+ text: str
94
+ tool_name: str | None = None
95
+ tool_input: dict[str, Any] | None = None
96
+ is_error: bool | None = None
97
+ reasoning: str | None = None
98
+ tool_use_id: str | None = None
99
+
100
+
101
+ class TaskSnapshot(BaseModel):
102
+ """任务快照模型。
103
+
104
+ UI安全的任务表示形式。
105
+
106
+ Attributes:
107
+ id: 任务 ID
108
+ type: 任务类型
109
+ status: 任务状态
110
+ description: 任务描述
111
+ metadata: 元数据字典
112
+ """
113
+
114
+ id: str
115
+ type: str
116
+ status: str
117
+ description: str
118
+ metadata: dict[str, str] = Field(default_factory=dict)
119
+
120
+ @classmethod
121
+ def from_record(cls, record: TaskRecord) -> "TaskSnapshot":
122
+ """从任务记录创建任务快照。
123
+
124
+ Args:
125
+ record: 任务记录
126
+
127
+ Returns:
128
+ TaskSnapshot: 任务快照
129
+ """
130
+ return cls(
131
+ id=record.id,
132
+ type=record.type,
133
+ status=to_task_display_status(record.status),
134
+ description=record.description,
135
+ metadata=dict(record.metadata),
136
+ )
137
+
138
+
139
+ class BackendEvent(BaseModel):
140
+ """后端事件模型。
141
+
142
+ 表示从 Python 后端发送到 React 前端的事件。
143
+
144
+ Attributes:
145
+ type: 事件类型
146
+ select_options: 选择选项列表
147
+ message: 消息文本
148
+ item: 转录项
149
+ state: 状态字典
150
+ tasks: 任务快照列表
151
+ mcp_servers: MCP 服务器状态列表
152
+ bridge_sessions: 桥接会话列表
153
+ commands: 命令列表
154
+ modal: 模态对话框配置
155
+ tool_name: 工具名称
156
+ tool_input: 工具输入参数
157
+ tool_output: 工具输出
158
+ is_error: 是否为错误
159
+ phase: 当前会话阶段
160
+ tool_count: 工具链中的工具数量
161
+ todo_items: 待办事项列表
162
+ todo_markdown: 待办事项 Markdown
163
+ plan_mode: 计划模式
164
+ swarm_teammates: Swarm 队友列表
165
+ swarm_notifications: Swarm 通知列表
166
+ reasoning: 思考增量或最终思考文本
167
+ command_result_data: 指令结果数据
168
+ """
169
+
170
+ type: Literal[
171
+ "ready",
172
+ "state_snapshot",
173
+ "tasks_snapshot",
174
+ "transcript_item",
175
+ "assistant_delta",
176
+ "assistant_complete",
177
+ "line_complete",
178
+ "tool_started",
179
+ "tool_input_updated",
180
+ "tool_completed",
181
+ "tool_chain_started",
182
+ "tool_chain_completed",
183
+ "clear_transcript",
184
+ "replace_transcript",
185
+ "modal_request",
186
+ "select_request",
187
+ "todo_update",
188
+ "plan_mode_change",
189
+ "swarm_status",
190
+ "command_result",
191
+ "bg_agent_status",
192
+ "error",
193
+ "shutdown",
194
+ ]
195
+ select_options: list[dict[str, Any]] | None = None
196
+ message: str | None = None
197
+ item: TranscriptItem | None = None
198
+ state: dict[str, Any] | None = None
199
+ tasks: list[TaskSnapshot] | None = None
200
+ mcp_servers: list[dict[str, Any]] | None = None
201
+ bridge_sessions: list[dict[str, Any]] | None = None
202
+ commands: list[str] | None = None
203
+ modal: dict[str, Any] | None = None
204
+ tool_name: str | None = None
205
+ tool_input: dict[str, Any] | None = None
206
+ tool_use_id: str | None = None
207
+ output: str | None = None
208
+ is_error: bool | None = None
209
+ phase: str | None = None # 当前会话阶段
210
+ tool_count: int | None = None # 工具链中的工具数量
211
+ # 新增字段用于增强事件
212
+ todo_items: list[dict[str, Any]] | None = None
213
+ todo_markdown: str | None = None
214
+ plan_mode: str | None = None
215
+ swarm_teammates: list[dict[str, Any]] | None = None
216
+ swarm_notifications: list[dict[str, Any]] | None = None
217
+ reasoning: str | None = None
218
+ command_result_data: dict[str, Any] | None = None
219
+ items: list[TranscriptItem] | None = None
220
+
221
+ @classmethod
222
+ def ready(
223
+ cls,
224
+ state: AppState,
225
+ tasks: list[TaskRecord],
226
+ commands: list[str],
227
+ ) -> "BackendEvent":
228
+ """创建就绪事件。
229
+
230
+ Args:
231
+ state: 应用状态
232
+ tasks: 任务记录列表
233
+ commands: 命令列表
234
+
235
+ Returns:
236
+ BackendEvent: 就绪事件
237
+ """
238
+ return cls(
239
+ type="ready",
240
+ state=_state_payload(state),
241
+ tasks=[TaskSnapshot.from_record(task) for task in tasks],
242
+ mcp_servers=[],
243
+ bridge_sessions=[],
244
+ commands=commands,
245
+ )
246
+
247
+ @classmethod
248
+ def state_snapshot(cls, state: AppState) -> "BackendEvent":
249
+ """创建状态快照事件。
250
+
251
+ Args:
252
+ state: 应用状态
253
+
254
+ Returns:
255
+ BackendEvent: 状态快照事件
256
+ """
257
+ return cls(type="state_snapshot", state=_state_payload(state))
258
+
259
+ @classmethod
260
+ def tasks_snapshot(cls, tasks: list[TaskRecord]) -> "BackendEvent":
261
+ """创建任务快照事件。
262
+
263
+ Args:
264
+ tasks: 任务记录列表
265
+
266
+ Returns:
267
+ BackendEvent: 任务快照事件
268
+ """
269
+ return cls(
270
+ type="tasks_snapshot",
271
+ tasks=[TaskSnapshot.from_record(task) for task in tasks],
272
+ )
273
+
274
+ @classmethod
275
+ def status_snapshot(
276
+ cls,
277
+ *,
278
+ state: AppState,
279
+ mcp_servers: list[McpConnectionStatus],
280
+ bridge_sessions: list[BridgeSessionRecord],
281
+ ) -> "BackendEvent":
282
+ """创建状态快照事件(包含 MCP 和桥接信息)。
283
+
284
+ Args:
285
+ state: 应用状态
286
+ mcp_servers: MCP 服务器状态列表
287
+ bridge_sessions: 桥接会话列表
288
+
289
+ Returns:
290
+ BackendEvent: 状态快照事件
291
+ """
292
+ return cls(
293
+ type="state_snapshot",
294
+ state=_state_payload(state),
295
+ mcp_servers=[
296
+ {
297
+ "name": server.name,
298
+ "state": server.state,
299
+ "detail": server.detail,
300
+ "transport": server.transport,
301
+ "auth_configured": server.auth_configured,
302
+ "tool_count": len(server.tools),
303
+ "resource_count": len(server.resources),
304
+ }
305
+ for server in mcp_servers
306
+ ],
307
+ bridge_sessions=[
308
+ {
309
+ "session_id": session.session_id,
310
+ "command": session.command,
311
+ "cwd": session.cwd,
312
+ "pid": session.pid,
313
+ "status": session.status,
314
+ "started_at": session.started_at,
315
+ "output_path": session.output_path,
316
+ }
317
+ for session in bridge_sessions
318
+ ],
319
+ )
320
+
321
+
322
+ def _state_payload(state: AppState) -> dict[str, Any]:
323
+ """将应用状态转换为载荷字典。
324
+
325
+ Args:
326
+ state: 应用状态
327
+
328
+ Returns:
329
+ dict[str, Any]: 状态载荷
330
+ """
331
+ from illusion.swarm.agent_executor import list_active_agents
332
+ return {
333
+ "model": state.model,
334
+ "cwd": state.cwd,
335
+ "provider": state.provider,
336
+ "auth_status": state.auth_status,
337
+ "base_url": state.base_url,
338
+ "permission_mode": _format_permission_mode(state.permission_mode),
339
+ "ui_language": state.ui_language,
340
+ "fast_mode": state.fast_mode,
341
+ "effort": state.effort,
342
+ "passes": state.passes,
343
+ "mcp_connected": state.mcp_connected,
344
+ "mcp_failed": state.mcp_failed,
345
+ "bridge_sessions": state.bridge_sessions,
346
+ "output_style": state.output_style,
347
+ "show_thinking": state.show_thinking,
348
+ "phase": state.phase,
349
+ "session_id": state.session_id,
350
+ "context_window": state.context_window,
351
+ "context_tokens": state.context_tokens,
352
+ "agent_count": len(list_active_agents()),
353
+ }
354
+
355
+
356
+ # 权限模式标签映射
357
+ _MODE_LABELS = {
358
+ "default": "Default",
359
+ "plan": "Plan Mode",
360
+ "full_auto": "Auto",
361
+ "PermissionMode.DEFAULT": "Default",
362
+ "PermissionMode.PLAN": "Plan Mode",
363
+ "PermissionMode.FULL_AUTO": "Auto",
364
+ }
365
+
366
+
367
+ def _format_permission_mode(raw: str) -> str:
368
+ """将原始权限模式转换为人类可读的标签。
369
+
370
+ Args:
371
+ raw: 原始权限模式字符串
372
+
373
+ Returns:
374
+ str: 格式化的权限模式标签
375
+ """
376
+ return _MODE_LABELS.get(raw, raw)
377
+
378
+
379
+ __all__ = [
380
+ "BackendEvent",
381
+ "FrontendRequest",
382
+ "TaskSnapshot",
383
+ "TranscriptItem",
384
+ ]
@@ -0,0 +1,280 @@
1
+ """
2
+ React Launcher React 启动器模块
3
+ =========================
4
+
5
+ 本模块实现默认的 React 终端前端启动器。
6
+
7
+ 主要功能:
8
+ - 解析前端目录路径
9
+ - 构建后端启动命令
10
+ - 启动 React 终端 UI
11
+
12
+ 函数说明:
13
+ - get_frontend_dir: 获取前端目录路径
14
+ - build_backend_command: 构建后端启动命令
15
+ - launch_react_tui: 启动 React 终端 UI
16
+
17
+ 使用示例:
18
+ >>> from illusion.ui.react_launcher import launch_react_tui, get_frontend_dir
19
+ >>>
20
+ >>> # 启动 React TUI
21
+ >>> exit_code = await launch_react_tui(prompt="帮我写一个程序")
22
+ >>>
23
+ >>> # 获取前端目录
24
+ >>> frontend_dir = get_frontend_dir()
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import asyncio
30
+ import json
31
+ import os
32
+ import shutil
33
+ import subprocess
34
+ import sys
35
+ from pathlib import Path
36
+
37
+
38
+ def _resolve_npm() -> str:
39
+ """解析 npm 可执行文件路径(在 Windows 上为 npm.cmd)。
40
+
41
+ Returns:
42
+ str: npm 可执行文件路径
43
+ """
44
+ npm = shutil.which("npm") or "npm"
45
+ if sys.platform == "win32" and not npm.endswith((".cmd", ".bat", ".exe")):
46
+ for ext in (".cmd", ".bat", ".exe"):
47
+ candidate = npm + ext
48
+ if Path(candidate).exists():
49
+ return candidate
50
+ return npm
51
+
52
+
53
+ def _resolve_node() -> str:
54
+ """解析 node 可执行文件路径。
55
+
56
+ Windows 上优先使用 npm 同目录的 node.exe,避免 Python nodejs-wheel
57
+ 包装器干扰。
58
+
59
+ Returns:
60
+ str: node 可执行文件路径
61
+ """
62
+ npm = shutil.which("npm")
63
+ if npm is not None:
64
+ sibling = Path(npm).parent / "node.exe"
65
+ if sibling.exists() and sys.platform == "win32":
66
+ return str(sibling)
67
+ return shutil.which("node") or "node"
68
+
69
+
70
+ def _resolve_tsx_bin(frontend_dir: Path) -> list[str] | None:
71
+ """直接解析 tsx 可执行文件路径,跳过 npm exec 开销。
72
+
73
+ Args:
74
+ frontend_dir: 前端目录路径
75
+
76
+ Returns:
77
+ list[str] | None: tsx 启动命令列表,未找到时返回 None
78
+ """
79
+ node = _resolve_node()
80
+ # Windows: node_modules/.bin/tsx.cmd
81
+ if sys.platform == "win32":
82
+ tsx_cmd = frontend_dir / "node_modules" / ".bin" / "tsx.cmd"
83
+ if tsx_cmd.exists():
84
+ return [str(tsx_cmd)]
85
+ # Unix: node_modules/.bin/tsx
86
+ tsx_bin = frontend_dir / "node_modules" / ".bin" / "tsx"
87
+ if tsx_bin.exists():
88
+ return [node, str(tsx_bin)]
89
+ # 直接调用 tsx 的 CLI 入口
90
+ tsx_mjs = frontend_dir / "node_modules" / "tsx" / "dist" / "cli.mjs"
91
+ if tsx_mjs.exists():
92
+ return [node, str(tsx_mjs)]
93
+ return None
94
+
95
+
96
+ def get_frontend_dir() -> Path:
97
+ """返回 React 终端前端目录。
98
+
99
+ 按以下顺序检查:
100
+ 1. 已安装包内的打包文件(pip install)
101
+ 2. 开发仓库布局(source checkout)
102
+
103
+ Returns:
104
+ Path: 前端目录路径
105
+ """
106
+ # 1. 已安装包内的打包文件:illusion/_frontend/
107
+ pkg_frontend = Path(__file__).resolve().parent.parent / "_frontend"
108
+ if (pkg_frontend / "package.json").exists():
109
+ return pkg_frontend
110
+
111
+ # 2. 开发仓库:<repo>/frontend/terminal/
112
+ repo_root = Path(__file__).resolve().parents[3]
113
+ dev_frontend = repo_root / "frontend" / "terminal"
114
+ if (dev_frontend / "package.json").exists():
115
+ return dev_frontend
116
+
117
+ # 回退到包路径(将显示清晰的错误消息)
118
+ return pkg_frontend
119
+
120
+
121
+ def build_backend_command(
122
+ *,
123
+ cwd: str | None = None,
124
+ model: str | None = None,
125
+ max_turns: int | None = None,
126
+ base_url: str | None = None,
127
+ system_prompt: str | None = None,
128
+ api_key: str | None = None,
129
+ api_format: str | None = None,
130
+ effort: str | None = None,
131
+ ) -> list[str]:
132
+ """返回 React 前端用于生成后端主机的命令。
133
+
134
+ Args:
135
+ cwd: 工作目录
136
+ model: 模型名称
137
+ max_turns: 最大对话轮次
138
+ base_url: API 基础 URL
139
+ system_prompt: 系统提示词
140
+ api_key: API 密钥
141
+ api_format: API 格式
142
+ effort: 推理强度级别
143
+
144
+ Returns:
145
+ list[str]: 后端启动命令列表
146
+ """
147
+ command = [sys.executable, "-m", "illusion", "--backend-only"]
148
+ if cwd:
149
+ command.extend(["--cwd", cwd])
150
+ if model:
151
+ command.extend(["--model", model])
152
+ if max_turns is not None:
153
+ command.extend(["--max-turns", str(max_turns)])
154
+ if base_url:
155
+ command.extend(["--base-url", base_url])
156
+ if system_prompt:
157
+ command.extend(["--system-prompt", system_prompt])
158
+ if api_key:
159
+ command.extend(["--api-key", api_key])
160
+ if api_format:
161
+ command.extend(["--api-format", api_format])
162
+ if effort:
163
+ command.extend(["--effort", effort])
164
+ return command
165
+
166
+
167
+ async def launch_react_tui(
168
+ *,
169
+ prompt: str | None = None,
170
+ cwd: str | None = None,
171
+ model: str | None = None,
172
+ max_turns: int | None = None,
173
+ base_url: str | None = None,
174
+ system_prompt: str | None = None,
175
+ api_key: str | None = None,
176
+ api_format: str | None = None,
177
+ effort: str | None = None,
178
+ ) -> int:
179
+ """启动 React 终端前端作为默认 UI。
180
+
181
+ Args:
182
+ prompt: 初始提示词
183
+ cwd: 工作目录
184
+ model: 模型名称
185
+ max_turns: 最大对话轮次
186
+ base_url: API 基础 URL
187
+ system_prompt: 系统提示词
188
+ api_key: API 密钥
189
+ api_format: API 格式
190
+ effort: 推理强度级别
191
+
192
+ Returns:
193
+ int: 退出代码
194
+ """
195
+ frontend_dir = get_frontend_dir()
196
+ package_json = frontend_dir / "package.json"
197
+ if not package_json.exists():
198
+ raise RuntimeError(f"React terminal frontend is missing: {package_json}")
199
+
200
+ # 解析 npm 路径
201
+ npm = _resolve_npm()
202
+
203
+ # 检查并安装依赖
204
+ if not (frontend_dir / "node_modules").exists():
205
+ install_kwargs: dict = {}
206
+ if sys.platform == "win32":
207
+ install_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
208
+ install = await asyncio.create_subprocess_exec(
209
+ npm,
210
+ "install",
211
+ "--no-fund",
212
+ "--no-audit",
213
+ cwd=str(frontend_dir),
214
+ **install_kwargs,
215
+ )
216
+ if await install.wait() != 0:
217
+ raise RuntimeError("Failed to install React terminal frontend dependencies")
218
+
219
+ # 设置环境变量
220
+ env = os.environ.copy()
221
+ env["ILLUSION_FRONTEND_CONFIG"] = json.dumps(
222
+ {
223
+ "backend_command": build_backend_command(
224
+ cwd=cwd or str(Path.cwd()),
225
+ model=model,
226
+ max_turns=max_turns,
227
+ base_url=base_url,
228
+ system_prompt=system_prompt,
229
+ api_key=api_key,
230
+ api_format=api_format,
231
+ effort=effort,
232
+ ),
233
+ "initial_prompt": prompt,
234
+ }
235
+ )
236
+ node = _resolve_node()
237
+ dist_entry = frontend_dir / "dist" / "index.mjs"
238
+
239
+ if dist_entry.exists():
240
+ # 优先使用 esbuild 预编译产物(最快启动路径)
241
+ process = await asyncio.create_subprocess_exec(
242
+ node,
243
+ str(dist_entry),
244
+ cwd=str(frontend_dir),
245
+ env=env,
246
+ stdin=None,
247
+ stdout=None,
248
+ stderr=None,
249
+ )
250
+ else:
251
+ # 回退到 tsx 实时编译(开发模式)
252
+ tsx_cmd = _resolve_tsx_bin(frontend_dir)
253
+ if tsx_cmd is not None:
254
+ process = await asyncio.create_subprocess_exec(
255
+ *tsx_cmd,
256
+ "src/index.tsx",
257
+ cwd=str(frontend_dir),
258
+ env=env,
259
+ stdin=None,
260
+ stdout=None,
261
+ stderr=None,
262
+ )
263
+ else:
264
+ # 最终回退:通过 npm exec 调用 tsx
265
+ process = await asyncio.create_subprocess_exec(
266
+ npm,
267
+ "exec",
268
+ "--",
269
+ "tsx",
270
+ "src/index.tsx",
271
+ cwd=str(frontend_dir),
272
+ env=env,
273
+ stdin=None,
274
+ stdout=None,
275
+ stderr=None,
276
+ )
277
+ return await process.wait()
278
+
279
+
280
+ __all__ = ["build_backend_command", "get_frontend_dir", "launch_react_tui"]