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,100 @@
1
+ """
2
+ 最小 REPL shell 执行工具
3
+ =======================
4
+
5
+ 本模块提供在类 REPL 环境中执行 shell 命令的功能。
6
+
7
+ 主要组件:
8
+ - ReplTool: REPL 风格的 shell 执行工具
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools import ReplTool
12
+ >>> tool = ReplTool()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ from pathlib import Path
19
+
20
+ from pydantic import BaseModel, Field
21
+
22
+ from illusion.sandbox import SandboxUnavailableError
23
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
24
+ from illusion.utils.shell import create_shell_subprocess
25
+
26
+
27
+ class ReplToolInput(BaseModel):
28
+ """REPL 工具参数。
29
+
30
+ 属性:
31
+ command: 要执行的 shell 命令
32
+ cwd: 可选的工作目录覆盖
33
+ timeout_seconds: 超时秒数(1-600)
34
+ """
35
+
36
+ command: str = Field(description="Shell command to execute")
37
+ cwd: str | None = Field(default=None, description="Working directory override")
38
+ timeout_seconds: int = Field(default=120, ge=1, le=600)
39
+
40
+
41
+ class ReplTool(BaseTool):
42
+ """使用类 REPL 的执行界面执行 shell 命令。
43
+
44
+ 用于在交互式环境中执行命令。
45
+ """
46
+
47
+ name = "repl"
48
+ description = "Run shell commands using a REPL-style execution surface."
49
+ input_model = ReplToolInput
50
+
51
+ async def execute(self, arguments: ReplToolInput, context: ToolExecutionContext) -> ToolResult:
52
+ # 解析工作目录
53
+ cwd = Path(arguments.cwd).expanduser() if arguments.cwd else context.cwd
54
+ try:
55
+ # 创建 shell 子进程
56
+ process = await create_shell_subprocess(
57
+ arguments.command,
58
+ cwd=cwd,
59
+ stdin=asyncio.subprocess.DEVNULL, # 防止 Windows 上的句柄继承死锁
60
+ stdout=asyncio.subprocess.PIPE,
61
+ stderr=asyncio.subprocess.PIPE,
62
+ )
63
+ except SandboxUnavailableError as exc:
64
+ return ToolResult(output=str(exc), is_error=True)
65
+
66
+ try:
67
+ # 等待命令完成
68
+ stdout, stderr = await asyncio.wait_for(
69
+ process.communicate(),
70
+ timeout=arguments.timeout_seconds,
71
+ )
72
+ except asyncio.TimeoutError:
73
+ # 超时后终止进程
74
+ process.kill()
75
+ await process.wait()
76
+ return ToolResult(
77
+ output=f"Command timed out after {arguments.timeout_seconds} seconds",
78
+ is_error=True,
79
+ )
80
+
81
+ # 收集输出
82
+ parts = []
83
+ if stdout:
84
+ parts.append(stdout.decode("utf-8", errors="replace").rstrip())
85
+ if stderr:
86
+ parts.append(stderr.decode("utf-8", errors="replace").rstrip())
87
+
88
+ text = "\n".join(part for part in parts if part).strip()
89
+ if not text:
90
+ text = "(no output)"
91
+
92
+ # 截断过长的输出
93
+ if len(text) > 12000:
94
+ text = f"{text[:12000]}\n...[truncated]..."
95
+
96
+ return ToolResult(
97
+ output=text,
98
+ is_error=process.returncode != 0,
99
+ metadata={"returncode": process.returncode},
100
+ )
@@ -0,0 +1,112 @@
1
+ """
2
+ 发送消息工具
3
+ ============
4
+
5
+ 本模块提供向运行中的代理发送消息的功能,对齐标准 SendMessageTool 架构。
6
+
7
+ 主要组件:
8
+ - SendMessageTool: 向代理发送消息的工具
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools import SendMessageTool
12
+ >>> tool = SendMessageTool()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class SendMessageToolInput(BaseModel):
27
+ """发送消息参数。
28
+
29
+ 属性:
30
+ to: 目标代理名称或 ID
31
+ message: 消息内容
32
+ """
33
+
34
+ to: str = Field(description="Agent name or ID to send the message to")
35
+ message: str = Field(description="Message content to send")
36
+
37
+
38
+ class SendMessageTool(BaseTool):
39
+ """向运行中的代理发送消息。
40
+
41
+ 用于与代理通信或发送继续指令。
42
+ """
43
+
44
+ name = "send_message"
45
+ description = """Send a message to another agent.
46
+
47
+ Use this tool to communicate with running agents. Messages from teammates are delivered automatically; you don't check an inbox. Refer to teammates by name, never by UUID.
48
+
49
+ Usage:
50
+ - Send to agent by name: `SendMessage({ to: "researcher", message: "..." })`
51
+ - Send to agent by ID: `SendMessage({ to: "agent_abc123", message: "..." })`
52
+
53
+ When continuing a completed agent, the agent resumes with its full context preserved.
54
+ """
55
+ input_model = SendMessageToolInput
56
+
57
+ async def execute(self, arguments: SendMessageToolInput, context: ToolExecutionContext) -> ToolResult:
58
+ """执行发送消息。
59
+
60
+ Args:
61
+ arguments: 工具输入参数。
62
+ context: 工具执行上下文。
63
+
64
+ Returns:
65
+ ToolResult: 工具执行结果。
66
+ """
67
+ # 延迟导入以避免循环依赖
68
+ from illusion.swarm.agent_executor import (
69
+ TeammateMessage,
70
+ get_active_agent,
71
+ get_active_agent_by_name,
72
+ )
73
+ from illusion.tasks.manager import get_task_manager
74
+
75
+ target = arguments.to
76
+ message_text = arguments.message
77
+
78
+ # 首先尝试通过名称查找活跃的进程内代理
79
+ agent_ctx = get_active_agent_by_name(target)
80
+ if agent_ctx is None:
81
+ agent_ctx = get_active_agent(target)
82
+
83
+ if agent_ctx is not None:
84
+ msg = TeammateMessage(
85
+ text=message_text,
86
+ from_agent="coordinator",
87
+ )
88
+ await agent_ctx.message_queue.put(msg)
89
+ logger.debug("[SendMessage] Sent message to in-process agent %s", agent_ctx.agent_id)
90
+ return ToolResult(output=f"Sent message to agent '{target}'")
91
+
92
+ # 尝试通过任务管理器写入(子进程代理)
93
+ try:
94
+ await get_task_manager().write_to_task(target, message_text)
95
+ return ToolResult(output=f"Sent message to task '{target}'")
96
+ except ValueError:
97
+ pass
98
+
99
+ # 尝试查找匹配的任务
100
+ task_manager = get_task_manager()
101
+ for task in task_manager.list_tasks(status="running"):
102
+ if task.description and target in task.description:
103
+ try:
104
+ await task_manager.write_to_task(task.id, message_text)
105
+ return ToolResult(output=f"Sent message to task '{task.id}' (matched '{target}')")
106
+ except ValueError as exc:
107
+ return ToolResult(output=str(exc), is_error=True)
108
+
109
+ return ToolResult(
110
+ output=f"No active agent or task found matching '{target}'",
111
+ is_error=True,
112
+ )
@@ -0,0 +1,187 @@
1
+ """
2
+ Shell 工具共享模块
3
+ =================
4
+
5
+ 本模块提供 shell 命令执行的共享工具,包括错误码标准化、输出归一化和命令执行器。
6
+
7
+ 主要功能:
8
+ - ShellErrorCode: 标准化 shell 退出码常量
9
+ - NormalizedResult: 标准化命令执行结果数据类
10
+ - OutputNormalizer: 输出解码与归一化处理
11
+ - CommandExecutor: 统一命令执行器,处理超时、解码、归一化
12
+
13
+ 类说明:
14
+ - ShellErrorCode: 标准化 shell 退出码常量类
15
+ - SUCCESS: 成功 (0)
16
+ - GENERAL_ERROR: 一般错误 (1)
17
+ - COMMAND_NOT_FOUND: 命令未找到 (127)
18
+ - TIMEOUT: 超时 (-1)
19
+ - PERMISSION_DENIED: 权限拒绝 (126)
20
+ - SIGNAL_BASE: 信号基准 (128 + signal_number)
21
+
22
+ - NormalizedResult: 标准化命令执行结果
23
+ - output: 输出文本
24
+ - is_error: 是否为错误
25
+ - return_code: 返回码
26
+ - metadata: 元数据字典
27
+
28
+ - OutputNormalizer: 输出解码与归一化处理
29
+ - decode_output: 健壮解码(UTF-8 → UTF-16LE → locale → replace)
30
+ - format_result: 生成上下文相关的输出消息
31
+
32
+ - CommandExecutor: 统一命令执行器
33
+ - run_and_normalize: 等待进程完成,捕获输出,归一化结果
34
+
35
+ 使用示例:
36
+ >>> from illusion.tools.shell_common import ShellErrorCode, NormalizedResult
37
+ >>> print(ShellErrorCode.SUCCESS) # 输出: 0
38
+ >>> result = NormalizedResult(output="test", is_error=False, return_code=0)
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import asyncio
44
+ import locale
45
+ from dataclasses import dataclass, field
46
+ from typing import Any
47
+
48
+
49
+ # 输出截断阈值(描述中对外暴露此值,修改时需同步更新工具描述文本)
50
+ MAX_OUTPUT_LENGTH = 30_000
51
+
52
+
53
+ class ShellErrorCode:
54
+ """标准化 shell 退出码常量。"""
55
+
56
+ SUCCESS = 0
57
+ GENERAL_ERROR = 1
58
+ COMMAND_NOT_FOUND = 127
59
+ TIMEOUT = -1
60
+ PERMISSION_DENIED = 126
61
+ SIGNAL_BASE = 128 # 128 + signal_number
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class NormalizedResult:
66
+ """标准化命令执行结果。"""
67
+
68
+ output: str
69
+ is_error: bool
70
+ return_code: int
71
+ metadata: dict[str, Any] = field(default_factory=dict)
72
+
73
+
74
+ class OutputNormalizer:
75
+ """输出解码与归一化处理。"""
76
+
77
+ @staticmethod
78
+ def decode_output(data: bytes) -> str:
79
+ """健壮解码:UTF-8 → UTF-16LE(如含 null 字节)→ locale → replace。"""
80
+ if not data:
81
+ return ""
82
+
83
+ encodings: list[str] = ["utf-8"]
84
+
85
+ # Windows PowerShell 经常输出 UTF-16LE —— 含 null 字节时优先尝试
86
+ if b"\x00" in data:
87
+ encodings.append("utf-16-le")
88
+
89
+ preferred = locale.getpreferredencoding(False)
90
+ if preferred and preferred.lower() not in {"utf-8", "utf8"}:
91
+ encodings.append(preferred)
92
+
93
+ for encoding in encodings:
94
+ try:
95
+ return data.decode(encoding)
96
+ except UnicodeDecodeError:
97
+ continue
98
+
99
+ return data.decode("utf-8", errors="replace")
100
+
101
+ @staticmethod
102
+ def format_result(
103
+ *,
104
+ stdout: bytes,
105
+ stderr: bytes,
106
+ return_code: int,
107
+ timed_out: bool,
108
+ timeout_seconds: int,
109
+ ) -> NormalizedResult:
110
+ """生成上下文相关的输出消息,消除 '(no output)' 歧义。"""
111
+ if timed_out:
112
+ output = f"Command timed out after {timeout_seconds}s"
113
+ return NormalizedResult(
114
+ output=output,
115
+ is_error=True,
116
+ return_code=-1,
117
+ metadata={"returncode": -1, "timed_out": True},
118
+ )
119
+
120
+ decoded_stdout = OutputNormalizer.decode_output(stdout).rstrip()
121
+ decoded_stderr = OutputNormalizer.decode_output(stderr).rstrip()
122
+
123
+ parts = []
124
+ if decoded_stdout:
125
+ parts.append(decoded_stdout)
126
+ if decoded_stderr:
127
+ parts.append(decoded_stderr)
128
+
129
+ text = "\n".join(parts).strip()
130
+
131
+ if not text:
132
+ # 上下文相关的空输出消息
133
+ if return_code == 0:
134
+ text = "Command completed successfully (no output produced)\nExit code: 0"
135
+ else:
136
+ text = (
137
+ f"Process exited with code {return_code} but produced no output\n"
138
+ f"Exit code: {return_code}"
139
+ )
140
+
141
+ if len(text) > MAX_OUTPUT_LENGTH:
142
+ text = f"{text[:MAX_OUTPUT_LENGTH]}\n...[truncated]..."
143
+
144
+ return NormalizedResult(
145
+ output=text,
146
+ is_error=return_code != 0,
147
+ return_code=return_code,
148
+ metadata={"returncode": return_code},
149
+ )
150
+
151
+
152
+ class CommandExecutor:
153
+ """统一命令执行器,处理超时、解码、归一化。"""
154
+
155
+ @staticmethod
156
+ async def run_and_normalize(
157
+ process: asyncio.subprocess.Process,
158
+ *,
159
+ timeout: int,
160
+ ) -> NormalizedResult:
161
+ """等待进程完成,捕获输出,归一化结果。
162
+
163
+ 调用方负责创建 process(保留各自的 shell/sandbox 逻辑),
164
+ 本方法负责统一的超时、解码、截断和上下文化消息。
165
+ """
166
+ try:
167
+ stdout, stderr = await asyncio.wait_for(
168
+ process.communicate(),
169
+ timeout=timeout,
170
+ )
171
+ except asyncio.TimeoutError:
172
+ process.kill()
173
+ await process.wait()
174
+ return NormalizedResult(
175
+ output=f"Command timed out after {timeout}s",
176
+ is_error=True,
177
+ return_code=-1,
178
+ metadata={"returncode": -1, "timed_out": True},
179
+ )
180
+
181
+ return OutputNormalizer.format_result(
182
+ stdout=stdout or b"",
183
+ stderr=stderr or b"",
184
+ return_code=process.returncode if process.returncode is not None else -1,
185
+ timed_out=False,
186
+ timeout_seconds=timeout,
187
+ )
@@ -0,0 +1,86 @@
1
+ """
2
+ 技能内容读取工具
3
+ ================
4
+
5
+ 本模块提供读取已加载技能内容的功能,用于执行斜杠命令和自定义技能。
6
+
7
+ 主要组件:
8
+ - SkillTool: 读取技能内容的工具
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools import SkillTool
12
+ >>> tool = SkillTool()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pydantic import BaseModel, Field
18
+
19
+ from illusion.skills import load_skill_registry
20
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
21
+
22
+
23
+ class SkillToolInput(BaseModel):
24
+ """技能查找参数。
25
+
26
+ 属性:
27
+ name: 技能名称
28
+ args: 技能的可选参数
29
+ """
30
+
31
+ name: str = Field(description="Skill name")
32
+ args: str | None = Field(default=None, description="Optional arguments for the skill")
33
+
34
+
35
+ class SkillTool(BaseTool):
36
+ """返回已加载技能的内容。
37
+
38
+ 用于执行斜杠命令(/command)或调用自定义技能。
39
+ """
40
+
41
+ name = "skill"
42
+ description = """Load a skill's instructions so you can then execute them yourself.
43
+
44
+ This tool does NOT execute anything — it only returns the skill's content (instructions, workflows, examples). You must read the returned content and follow it step by step to complete the task.
45
+
46
+ When users ask you to perform tasks, check if any of the available skills match. Skills provide specialized capabilities and domain knowledge.
47
+
48
+ When users reference a "slash command" or "/<something>" (e.g., "/commit", "/review-pr"), they are referring to a skill. Use this tool to load its instructions.
49
+
50
+ How to use:
51
+ - Call this tool with the skill name and optional arguments
52
+ - The tool returns the skill's instructions — you then execute them
53
+ - Examples:
54
+ - `skill: "pdf"` — loads the pdf skill's instructions
55
+ - `skill: "commit", args: "-m 'Fix bug'"` — loads with arguments
56
+ - `skill: "review-pr", args: "123"` — loads with arguments
57
+ - `skill: "ms-office-suite:pdf"` — loads using fully qualified name
58
+
59
+ Important:
60
+ - Available skills are listed in <system-reminder> messages in the conversation
61
+ - When a skill matches the user's request, this is a BLOCKING REQUIREMENT: call this Skill tool to load its instructions BEFORE generating any other response about the task
62
+ - NEVER mention a skill without actually calling this tool
63
+ - Do not load a skill that is already active in the current context
64
+ - Do not use this tool for built-in CLI commands (like /help, /clear, etc.)
65
+ - If you see a <command-name> tag in the current conversation turn, the skill's instructions have ALREADY been loaded — follow the instructions directly instead of calling this tool again"""
66
+ input_model = SkillToolInput
67
+
68
+ def is_read_only(self, arguments: SkillToolInput) -> bool:
69
+ del arguments
70
+ return True
71
+
72
+ async def execute(self, arguments: SkillToolInput, context: ToolExecutionContext) -> ToolResult:
73
+ # 加载技能注册表
74
+ registry = load_skill_registry(context.cwd)
75
+ # 尝试多种名称格式匹配
76
+ skill = registry.get(arguments.name) or registry.get(arguments.name.lower()) or registry.get(arguments.name.title())
77
+ if skill is None:
78
+ return ToolResult(output=f"Skill not found: {arguments.name}", is_error=True)
79
+
80
+ # 获取技能内容
81
+ content = skill.content
82
+ # 如果提供了参数,替换 $ARGUMENTS 占位符
83
+ if arguments.args and "$ARGUMENTS" in content:
84
+ content = content.replace("$ARGUMENTS", arguments.args)
85
+
86
+ return ToolResult(output=content)
@@ -0,0 +1,62 @@
1
+ """
2
+ 休眠工具
3
+ ========
4
+
5
+ 本模块提供暂停执行的功能。
6
+
7
+ 主要组件:
8
+ - SleepTool: 暂停执行的工具
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools import SleepTool
12
+ >>> tool = SleepTool()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
22
+
23
+
24
+ class SleepToolInput(BaseModel):
25
+ """休眠参数。
26
+
27
+ 属性:
28
+ seconds: 休眠秒数
29
+ """
30
+
31
+ seconds: float = Field(default=1.0, ge=0.0, le=30.0)
32
+
33
+
34
+ class SleepTool(BaseTool):
35
+ """短暂暂停执行。
36
+
37
+ 用于等待指定时间后继续执行。
38
+ """
39
+
40
+ name = "sleep"
41
+ description = """Wait for a specified duration. The user can interrupt the sleep at any time.
42
+
43
+ Use this when the user tells you to sleep or rest, when you have nothing to do, or when you're waiting for something.
44
+
45
+ You may receive <tick> prompts - these are periodic check-ins. Look for useful work to do before sleeping.
46
+
47
+ You can call this concurrently with other tools - it won't interfere with them.
48
+
49
+ Prefer this over `Bash(sleep ...)` - it doesn't hold a shell process.
50
+
51
+ Each wake-up costs an API call, but the prompt cache expires after 5 minutes of inactivity - balance accordingly."""
52
+ input_model = SleepToolInput
53
+
54
+ def is_read_only(self, arguments: SleepToolInput) -> bool:
55
+ del arguments
56
+ return True
57
+
58
+ async def execute(self, arguments: SleepToolInput, context: ToolExecutionContext) -> ToolResult:
59
+ del context
60
+ # 异步休眠
61
+ await asyncio.sleep(arguments.seconds)
62
+ return ToolResult(output=f"Slept for {arguments.seconds} seconds")
@@ -0,0 +1,58 @@
1
+ """
2
+ 结构化输出工具
3
+ =============
4
+
5
+ 本模块提供以模式验证的 JSON 格式返回最终响应的功能。
6
+
7
+ 主要组件:
8
+ - StructuredOutputTool: 结构化输出工具
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools import StructuredOutputTool
12
+ >>> tool = StructuredOutputTool()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
22
+
23
+
24
+ class StructuredOutputToolInput(BaseModel):
25
+ """任意结构化 payload。
26
+
27
+ 属性:
28
+ structured_output: 结构化输出 payload
29
+ """
30
+
31
+ structured_output: dict[str, object] = Field(
32
+ default_factory=dict,
33
+ description="Structured output payload",
34
+ )
35
+
36
+
37
+ class StructuredOutputTool(BaseTool):
38
+ """以结构化 JSON 形式返回最终响应。
39
+
40
+ 用于按照请求的格式返回结构化输出。
41
+ """
42
+
43
+ name = "structured_output"
44
+ description = """Use this tool to return your final response in the requested structured format. You MUST call this tool exactly once at the end of your response to provide the structured output."""
45
+ input_model = StructuredOutputToolInput
46
+
47
+ def is_read_only(self, arguments: StructuredOutputToolInput) -> bool:
48
+ del arguments
49
+ return True
50
+
51
+ async def execute(
52
+ self,
53
+ arguments: StructuredOutputToolInput,
54
+ context: ToolExecutionContext,
55
+ ) -> ToolResult:
56
+ del context
57
+ output = json.dumps(arguments.structured_output, ensure_ascii=False)
58
+ return ToolResult(output=output)