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.
- illusion/__init__.py +24 -0
- illusion/__main__.py +15 -0
- illusion/_frontend/dist/index.mjs +39208 -0
- illusion/_frontend/package.json +27 -0
- illusion/_frontend/src/App.tsx +624 -0
- illusion/_frontend/src/components/CommandPicker.tsx +98 -0
- illusion/_frontend/src/components/Composer.tsx +55 -0
- illusion/_frontend/src/components/ComposerController.tsx +128 -0
- illusion/_frontend/src/components/ConversationView.tsx +750 -0
- illusion/_frontend/src/components/Footer.tsx +25 -0
- illusion/_frontend/src/components/MarkdownContent.tsx +537 -0
- illusion/_frontend/src/components/MarkdownTable.tsx +245 -0
- illusion/_frontend/src/components/ModalHost.tsx +425 -0
- illusion/_frontend/src/components/MultilineTextInput.tsx +250 -0
- illusion/_frontend/src/components/PromptInput.tsx +64 -0
- illusion/_frontend/src/components/SelectModal.tsx +78 -0
- illusion/_frontend/src/components/SidePanel.tsx +175 -0
- illusion/_frontend/src/components/Spinner.tsx +77 -0
- illusion/_frontend/src/components/StatusBar.tsx +142 -0
- illusion/_frontend/src/components/SwarmPanel.tsx +141 -0
- illusion/_frontend/src/components/TodoPanel.tsx +126 -0
- illusion/_frontend/src/components/ToolCallDisplay.tsx +202 -0
- illusion/_frontend/src/components/TranscriptPane.tsx +79 -0
- illusion/_frontend/src/components/WelcomeBanner.tsx +37 -0
- illusion/_frontend/src/hooks/useBackendSession.ts +468 -0
- illusion/_frontend/src/hooks/useTerminalSize.ts +9 -0
- illusion/_frontend/src/i18n.ts +78 -0
- illusion/_frontend/src/index.tsx +42 -0
- illusion/_frontend/src/theme/ThemeContext.tsx +19 -0
- illusion/_frontend/src/theme/builtinThemes.ts +89 -0
- illusion/_frontend/src/types.ts +110 -0
- illusion/_frontend/src/utils/markdown.ts +33 -0
- illusion/_frontend/src/utils/thinking.ts +191 -0
- illusion/_frontend/tsconfig.json +13 -0
- illusion/_web_dist/assets/index-BseIw-ik.css +10 -0
- illusion/_web_dist/assets/index-C_0ZWMuW.js +82 -0
- illusion/_web_dist/index.html +16 -0
- illusion/api/__init__.py +36 -0
- illusion/api/client.py +568 -0
- illusion/api/codex_client.py +563 -0
- illusion/api/compat.py +138 -0
- illusion/api/effort.py +128 -0
- illusion/api/errors.py +57 -0
- illusion/api/openai_client.py +819 -0
- illusion/api/provider.py +148 -0
- illusion/api/registry.py +479 -0
- illusion/api/usage.py +45 -0
- illusion/auth/__init__.py +50 -0
- illusion/auth/copilot.py +419 -0
- illusion/auth/external.py +612 -0
- illusion/auth/flows.py +58 -0
- illusion/auth/manager.py +214 -0
- illusion/auth/storage.py +372 -0
- illusion/bridge/__init__.py +38 -0
- illusion/bridge/manager.py +190 -0
- illusion/bridge/session_runner.py +84 -0
- illusion/bridge/types.py +113 -0
- illusion/bridge/work_secret.py +131 -0
- illusion/cli.py +1228 -0
- illusion/commands/__init__.py +32 -0
- illusion/commands/registry.py +1934 -0
- illusion/config/__init__.py +39 -0
- illusion/config/i18n.py +522 -0
- illusion/config/paths.py +259 -0
- illusion/config/settings.py +564 -0
- illusion/coordinator/__init__.py +41 -0
- illusion/coordinator/agent_definitions.py +1093 -0
- illusion/coordinator/coordinator_mode.py +127 -0
- illusion/engine/__init__.py +95 -0
- illusion/engine/cost_tracker.py +55 -0
- illusion/engine/messages.py +369 -0
- illusion/engine/query.py +632 -0
- illusion/engine/query_engine.py +343 -0
- illusion/engine/stream_events.py +169 -0
- illusion/hooks/__init__.py +67 -0
- illusion/hooks/events.py +43 -0
- illusion/hooks/executor.py +397 -0
- illusion/hooks/hot_reload.py +74 -0
- illusion/hooks/loader.py +133 -0
- illusion/hooks/schemas.py +121 -0
- illusion/hooks/types.py +86 -0
- illusion/mcp/__init__.py +104 -0
- illusion/mcp/client.py +377 -0
- illusion/mcp/config.py +140 -0
- illusion/mcp/types.py +175 -0
- illusion/memory/__init__.py +36 -0
- illusion/memory/manager.py +94 -0
- illusion/memory/memdir.py +58 -0
- illusion/memory/paths.py +57 -0
- illusion/memory/scan.py +120 -0
- illusion/memory/search.py +83 -0
- illusion/memory/types.py +43 -0
- illusion/output_styles/__init__.py +15 -0
- illusion/output_styles/loader.py +64 -0
- illusion/permissions/__init__.py +39 -0
- illusion/permissions/checker.py +174 -0
- illusion/permissions/modes.py +38 -0
- illusion/platforms.py +148 -0
- illusion/plugins/__init__.py +71 -0
- illusion/plugins/bundled/__init__.py +0 -0
- illusion/plugins/installer.py +59 -0
- illusion/plugins/loader.py +301 -0
- illusion/plugins/schemas.py +51 -0
- illusion/plugins/types.py +56 -0
- illusion/prompts/__init__.py +29 -0
- illusion/prompts/claudemd.py +74 -0
- illusion/prompts/context.py +187 -0
- illusion/prompts/environment.py +189 -0
- illusion/prompts/system_prompt.py +155 -0
- illusion/py.typed +0 -0
- illusion/sandbox/__init__.py +29 -0
- illusion/sandbox/adapter.py +174 -0
- illusion/services/__init__.py +59 -0
- illusion/services/compact/__init__.py +1015 -0
- illusion/services/cron.py +338 -0
- illusion/services/cron_scheduler.py +715 -0
- illusion/services/file_history.py +258 -0
- illusion/services/lsp/__init__.py +455 -0
- illusion/services/session_storage.py +237 -0
- illusion/services/token_estimation.py +72 -0
- illusion/skills/__init__.py +60 -0
- illusion/skills/bundled/__init__.py +110 -0
- illusion/skills/bundled/content/batch.md +86 -0
- illusion/skills/bundled/content/coding-guidelines.md +70 -0
- illusion/skills/bundled/content/debug.md +38 -0
- illusion/skills/bundled/content/loop.md +82 -0
- illusion/skills/bundled/content/remember.md +105 -0
- illusion/skills/bundled/content/simplify.md +53 -0
- illusion/skills/bundled/content/skillify.md +113 -0
- illusion/skills/bundled/content/stuck.md +54 -0
- illusion/skills/bundled/content/update-config.md +329 -0
- illusion/skills/bundled/content/verify.md +74 -0
- illusion/skills/loader.py +219 -0
- illusion/skills/registry.py +40 -0
- illusion/skills/types.py +24 -0
- illusion/state/__init__.py +18 -0
- illusion/state/app_state.py +67 -0
- illusion/state/store.py +93 -0
- illusion/swarm/__init__.py +71 -0
- illusion/swarm/agent_executor.py +857 -0
- illusion/swarm/in_process.py +259 -0
- illusion/swarm/subprocess_backend.py +136 -0
- illusion/swarm/team_helpers.py +123 -0
- illusion/swarm/types.py +159 -0
- illusion/swarm/worktree.py +347 -0
- illusion/tasks/__init__.py +33 -0
- illusion/tasks/local_agent_task.py +42 -0
- illusion/tasks/local_shell_task.py +27 -0
- illusion/tasks/manager.py +377 -0
- illusion/tasks/stop_task.py +21 -0
- illusion/tasks/types.py +88 -0
- illusion/tools/__init__.py +126 -0
- illusion/tools/agent_tool.py +388 -0
- illusion/tools/ask_user_question_tool.py +186 -0
- illusion/tools/base.py +149 -0
- illusion/tools/bash_tool.py +413 -0
- illusion/tools/config_tool.py +90 -0
- illusion/tools/cron_tool.py +473 -0
- illusion/tools/enter_plan_mode_tool.py +147 -0
- illusion/tools/enter_worktree_tool.py +188 -0
- illusion/tools/exit_plan_mode_tool.py +69 -0
- illusion/tools/exit_worktree_tool.py +225 -0
- illusion/tools/file_edit_tool.py +283 -0
- illusion/tools/file_read_tool.py +294 -0
- illusion/tools/file_write_tool.py +184 -0
- illusion/tools/glob_tool.py +165 -0
- illusion/tools/grep_tool.py +190 -0
- illusion/tools/list_mcp_resources_tool.py +80 -0
- illusion/tools/lsp_tool.py +333 -0
- illusion/tools/mcp_auth_tool.py +100 -0
- illusion/tools/mcp_tool.py +75 -0
- illusion/tools/notebook_edit_tool.py +242 -0
- illusion/tools/powershell_tool.py +334 -0
- illusion/tools/read_mcp_resource_tool.py +63 -0
- illusion/tools/repl_tool.py +100 -0
- illusion/tools/send_message_tool.py +112 -0
- illusion/tools/shell_common.py +187 -0
- illusion/tools/skill_tool.py +86 -0
- illusion/tools/sleep_tool.py +62 -0
- illusion/tools/structured_output_tool.py +58 -0
- illusion/tools/task_create_tool.py +98 -0
- illusion/tools/task_get_tool.py +94 -0
- illusion/tools/task_list_tool.py +94 -0
- illusion/tools/task_output_tool.py +55 -0
- illusion/tools/task_stop_tool.py +52 -0
- illusion/tools/task_update_tool.py +224 -0
- illusion/tools/team_create_tool.py +236 -0
- illusion/tools/team_delete_tool.py +104 -0
- illusion/tools/todo_write_tool.py +198 -0
- illusion/tools/tool_search_tool.py +156 -0
- illusion/tools/web_fetch_tool.py +264 -0
- illusion/tools/web_search_tool.py +186 -0
- illusion/ui/__init__.py +23 -0
- illusion/ui/app.py +258 -0
- illusion/ui/backend_host.py +1180 -0
- illusion/ui/input.py +86 -0
- illusion/ui/output.py +363 -0
- illusion/ui/permission_dialog.py +47 -0
- illusion/ui/permission_store.py +99 -0
- illusion/ui/protocol.py +384 -0
- illusion/ui/react_launcher.py +280 -0
- illusion/ui/runtime.py +787 -0
- illusion/ui/textual_app.py +603 -0
- illusion/ui/web/__init__.py +10 -0
- illusion/ui/web/server.py +87 -0
- illusion/ui/web/ws_host.py +1197 -0
- illusion/utils/__init__.py +0 -0
- illusion/utils/ripgrep.py +299 -0
- illusion/utils/shell.py +248 -0
- illusion_code-0.1.0.dist-info/METADATA +1159 -0
- illusion_code-0.1.0.dist-info/RECORD +214 -0
- illusion_code-0.1.0.dist-info/WHEEL +4 -0
- illusion_code-0.1.0.dist-info/entry_points.txt +2 -0
- illusion_code-0.1.0.dist-info/licenses/LICENSE +21 -0
illusion/ui/protocol.py
ADDED
|
@@ -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"]
|