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,237 @@
1
+ """
2
+ 会话持久化辅助模块
3
+ ================
4
+
5
+ 本模块提供会话状态持久化功能,支持保存和加载会话快照。
6
+
7
+ 主要功能:
8
+ - 获取项目会话目录
9
+ - 保存会话快照
10
+ - 加载会话快照
11
+ - 列出会话快照
12
+ - 导出会话记录为 Markdown
13
+
14
+ 类说明:
15
+ - get_project_session_dir: 获取项目会话目录
16
+ - save_session_snapshot: 保存会话快照
17
+ - load_session_snapshot: 加载会话快照
18
+ - list_session_snapshots: 列出会话快照
19
+ - export_session_markdown: 导出为 Markdown
20
+
21
+ 使用示例:
22
+ >>> from illusion.services.session_storage import get_project_session_dir, save_session_snapshot
23
+ >>> # 获取项目会话目录
24
+ >>> session_dir = get_project_session_dir("/path/to/project")
25
+ >>> # 保存会话快照
26
+ >>> save_session_snapshot(cwd="/path/to/project", model="claude-3", messages=[...], usage=...)
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import json
32
+ import time
33
+ from hashlib import sha1
34
+ from pathlib import Path
35
+ from typing import Any
36
+ from uuid import uuid4
37
+
38
+ from illusion.api.usage import UsageSnapshot
39
+ from illusion.config.paths import get_sessions_dir
40
+ from illusion.engine.messages import ConversationMessage
41
+
42
+
43
+ def get_project_session_dir(cwd: str | Path) -> Path:
44
+ """返回项目的会话目录。"""
45
+ path = Path(cwd).resolve()
46
+ # 使用路径的 SHA1 哈希前 12 位作为目录名的一部分
47
+ digest = sha1(str(path).encode("utf-8")).hexdigest()[:12]
48
+ session_dir = get_sessions_dir() / f"{path.name}-{digest}"
49
+ session_dir.mkdir(parents=True, exist_ok=True)
50
+ return session_dir
51
+
52
+
53
+ def save_session_snapshot(
54
+ *,
55
+ cwd: str | Path,
56
+ model: str,
57
+ system_prompt: str,
58
+ messages: list[ConversationMessage],
59
+ usage: UsageSnapshot,
60
+ session_id: str | None = None,
61
+ ) -> Path:
62
+ """持久化会话快照。同时按 ID 保存和保存为 latest。"""
63
+ session_dir = get_project_session_dir(cwd)
64
+ sid = session_id or uuid4().hex[:12]
65
+ now = time.time()
66
+ # 从第一个用户消息提取摘要
67
+ summary = ""
68
+ for msg in messages:
69
+ if msg.role == "user" and msg.text.strip():
70
+ summary = msg.text.strip()[:80]
71
+ break
72
+
73
+ payload = {
74
+ "session_id": sid,
75
+ "cwd": str(Path(cwd).resolve()),
76
+ "model": model,
77
+ "system_prompt": system_prompt,
78
+ "messages": [message.model_dump(mode="json") for message in messages],
79
+ "usage": usage.model_dump(),
80
+ "created_at": now,
81
+ "summary": summary,
82
+ "message_count": len(messages),
83
+ }
84
+ data = json.dumps(payload, indent=2) + "\n"
85
+
86
+ # 保存为 latest
87
+ latest_path = session_dir / "latest.json"
88
+ latest_path.write_text(data, encoding="utf-8")
89
+
90
+ # 按会话 ID 保存
91
+ session_path = session_dir / f"session-{sid}.json"
92
+ session_path.write_text(data, encoding="utf-8")
93
+
94
+ return latest_path
95
+
96
+
97
+ def load_session_snapshot(cwd: str | Path) -> dict[str, Any] | None:
98
+ """加载项目的最新会话快照。"""
99
+ path = get_project_session_dir(cwd) / "latest.json"
100
+ if not path.exists():
101
+ return None
102
+ return json.loads(path.read_text(encoding="utf-8"))
103
+
104
+
105
+ def list_session_snapshots(cwd: str | Path, limit: int = 20) -> list[dict[str, Any]]:
106
+ """列出项目的已保存会话,按最新优先排序。"""
107
+ session_dir = get_project_session_dir(cwd)
108
+ sessions: list[dict[str, Any]] = []
109
+ seen_ids: set[str] = set()
110
+
111
+ # 命名会话文件
112
+ for path in sorted(session_dir.glob("session-*.json"), key=lambda p: p.stat().st_mtime, reverse=True):
113
+ try:
114
+ data = json.loads(path.read_text(encoding="utf-8"))
115
+ sid = data.get("session_id", path.stem.replace("session-", ""))
116
+ seen_ids.add(sid)
117
+ summary = data.get("summary", "")
118
+ if not summary:
119
+ # 从消息中提取
120
+ for msg in data.get("messages", []):
121
+ if msg.get("role") == "user":
122
+ texts = [b.get("text", "") for b in msg.get("content", []) if b.get("type") == "text"]
123
+ summary = " ".join(texts).strip()[:80]
124
+ if summary:
125
+ break
126
+ sessions.append({
127
+ "session_id": sid,
128
+ "summary": summary,
129
+ "message_count": data.get("message_count", len(data.get("messages", []))),
130
+ "model": data.get("model", ""),
131
+ "created_at": data.get("created_at", path.stat().st_mtime),
132
+ })
133
+ except (json.JSONDecodeError, OSError):
134
+ continue
135
+ if len(sessions) >= limit:
136
+ break
137
+
138
+ # 也包含 latest.json(如果没有对应的会话文件)
139
+ latest_path = session_dir / "latest.json"
140
+ if latest_path.exists() and len(sessions) < limit:
141
+ try:
142
+ data = json.loads(latest_path.read_text(encoding="utf-8"))
143
+ sid = data.get("session_id", "latest")
144
+ if sid not in seen_ids:
145
+ summary = data.get("summary", "")
146
+ if not summary:
147
+ for msg in data.get("messages", []):
148
+ if msg.get("role") == "user":
149
+ texts = [b.get("text", "") for b in msg.get("content", []) if b.get("type") == "text"]
150
+ summary = " ".join(texts).strip()[:80]
151
+ if summary:
152
+ break
153
+ sessions.append({
154
+ "session_id": sid,
155
+ "summary": summary or "(latest session)",
156
+ "message_count": data.get("message_count", len(data.get("messages", []))),
157
+ "model": data.get("model", ""),
158
+ "created_at": data.get("created_at", latest_path.stat().st_mtime),
159
+ })
160
+ except (json.JSONDecodeError, OSError):
161
+ pass
162
+
163
+ # 按 created_at 降序排序
164
+ sessions.sort(key=lambda s: s.get("created_at", 0), reverse=True)
165
+ return sessions[:limit]
166
+
167
+
168
+ def load_session_by_id(cwd: str | Path, session_id: str) -> dict[str, Any] | None:
169
+ """按 ID 加载特定会话。"""
170
+ session_dir = get_project_session_dir(cwd)
171
+ # 先尝试命名会话
172
+ path = session_dir / f"session-{session_id}.json"
173
+ if path.exists():
174
+ return json.loads(path.read_text(encoding="utf-8"))
175
+ # 回退到 latest.json(如果 session_id 匹配)
176
+ latest = session_dir / "latest.json"
177
+ if latest.exists():
178
+ data = json.loads(latest.read_text(encoding="utf-8"))
179
+ if data.get("session_id") == session_id or session_id == "latest":
180
+ return data
181
+ return None
182
+
183
+
184
+ def delete_session_by_id(cwd: str | Path, session_id: str) -> bool:
185
+ """按 ID 删除特定会话。返回是否成功删除。"""
186
+ session_dir = get_project_session_dir(cwd)
187
+ path = session_dir / f"session-{session_id}.json"
188
+ if path.exists():
189
+ path.unlink()
190
+ # 如果删除的是 latest.json 对应的会话,也删除 latest.json
191
+ latest = session_dir / "latest.json"
192
+ if latest.exists():
193
+ try:
194
+ data = json.loads(latest.read_text(encoding="utf-8"))
195
+ if data.get("session_id") == session_id:
196
+ latest.unlink()
197
+ except (json.JSONDecodeError, OSError):
198
+ pass
199
+ return True
200
+ return False
201
+
202
+
203
+ def delete_all_sessions(cwd: str | Path) -> int:
204
+ """删除项目的所有会话快照。返回删除的文件数量。"""
205
+ session_dir = get_project_session_dir(cwd)
206
+ count = 0
207
+ for path in session_dir.glob("session-*.json"):
208
+ path.unlink()
209
+ count += 1
210
+ latest = session_dir / "latest.json"
211
+ if latest.exists():
212
+ latest.unlink()
213
+ count += 1
214
+ return count
215
+
216
+
217
+ def export_session_markdown(
218
+ *,
219
+ cwd: str | Path,
220
+ messages: list[ConversationMessage],
221
+ ) -> Path:
222
+ """将会话记录导出为 Markdown。"""
223
+ session_dir = get_project_session_dir(cwd)
224
+ path = session_dir / "transcript.md"
225
+ parts: list[str] = ["# IllusionCode Session Transcript"]
226
+ for message in messages:
227
+ parts.append(f"\n## {message.role.capitalize()}\n")
228
+ text = message.text.strip()
229
+ if text:
230
+ parts.append(text)
231
+ for block in message.tool_uses:
232
+ parts.append(f"\n```tool\n{block.name} {json.dumps(block.input, ensure_ascii=True)}\n```")
233
+ for block in message.content:
234
+ if getattr(block, "type", "") == "tool_result":
235
+ parts.append(f"\n```tool-result\n{block.content}\n```")
236
+ path.write_text("\n".join(parts).strip() + "\n", encoding="utf-8")
237
+ return path
@@ -0,0 +1,72 @@
1
+ """
2
+ Token 估算工具
3
+ ==============
4
+
5
+ 本模块提供 Token 估算功能,使用改进的启发式方法:
6
+ - 基础估算:字符数 / 4(适用于英文为主的文本)
7
+ - CJK 优化:中日韩文字按 2 字符/token 估算(更密集)
8
+ - JSON 优化:JSON 内容按 2 字节/token 估算
9
+ - 混合文本自动检测
10
+
11
+ 主要功能:
12
+ - 估算单个文本的 Token 数量
13
+ - 估算消息列表的总 Token 数量
14
+ - 检测文本是否包含 CJK 字符
15
+
16
+ 使用示例:
17
+ >>> from illusion.services.token_estimation import estimate_tokens
18
+ >>> tokens = estimate_tokens("Hello, world!")
19
+ >>> print(tokens) # 输出约 4
20
+ >>> tokens = estimate_tokens("你好世界")
21
+ >>> print(tokens) # 输出约 4
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import re
27
+
28
+ # CJK 字符范围的正则表达式(匹配中日韩统一表意文字等)
29
+ _CJK_PATTERN = re.compile(
30
+ r"[\u4e00-\u9fff" # CJK Unified Ideographs
31
+ r"\u3400-\u4dbf" # CJK Unified Ideographs Extension A
32
+ r"\uf900-\ufaff" # CJK Compatibility Ideographs
33
+ r"\u3040-\u309f" # Hiragana
34
+ r"\u30a0-\u30ff" # Katakana
35
+ r"\uac00-\ud7af" # Hangul Syllables
36
+ r"]"
37
+ )
38
+
39
+
40
+ def _has_cjk(text: str) -> bool:
41
+ """检查文本是否包含 CJK 字符。"""
42
+ return bool(_CJK_PATTERN.search(text))
43
+
44
+
45
+ def estimate_tokens(text: str) -> int:
46
+ """使用改进的启发式方法估算纯文本的 Token 数。
47
+
48
+ 对于英文为主的文本,使用字符数 / 4 的估算。
49
+ 对于包含 CJK 字符的文本,使用字符数 / 2 的估算(CJK 文字更密集)。
50
+ """
51
+ if not text:
52
+ return 0
53
+ if _has_cjk(text):
54
+ # CJK 文字:每个字符约 0.5-1 token,使用 /2 估算
55
+ return max(1, (len(text) + 1) // 2)
56
+ # 英文为主:约 4 字符/token
57
+ return max(1, (len(text) + 3) // 4)
58
+
59
+
60
+ def estimate_tokens_for_json(text: str) -> int:
61
+ """估算 JSON 文本的 Token 数。
62
+
63
+ JSON 内容比普通文本更密集,使用 2 字节/token 估算。
64
+ """
65
+ if not text:
66
+ return 0
67
+ return max(1, len(text.encode("utf-8")) // 2)
68
+
69
+
70
+ def estimate_message_tokens(messages: list[str]) -> int:
71
+ """估算消息字符串集合的 Token 总数。"""
72
+ return sum(estimate_tokens(message) for message in messages)
@@ -0,0 +1,60 @@
1
+ """
2
+ Skill 模块导出
3
+ =============
4
+
5
+ 本模块导出 skills 子目录中的公共接口。
6
+
7
+ 导出内容:
8
+ - SkillDefinition: Skill 定义数据类
9
+ - SkillRegistry: Skill 注册表
10
+ - get_user_skills_dir: 用户 skills 目录
11
+ - load_skill_registry: 加载 skill 注册表
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING: # pragma: no cover
19
+ from illusion.skills.registry import SkillRegistry
20
+ from illusion.skills.types import SkillDefinition
21
+
22
+ __all__ = [
23
+ "SkillDefinition",
24
+ "SkillRegistry",
25
+ "get_project_rules_dir",
26
+ "get_project_skills_dir",
27
+ "get_user_skills_dir",
28
+ "load_skill_registry",
29
+ ]
30
+
31
+
32
+ def __getattr__(name: str):
33
+ if name in {
34
+ "get_user_skills_dir",
35
+ "get_project_skills_dir",
36
+ "get_project_rules_dir",
37
+ "load_skill_registry",
38
+ }:
39
+ from illusion.skills.loader import (
40
+ get_project_rules_dir,
41
+ get_project_skills_dir,
42
+ get_user_skills_dir,
43
+ load_skill_registry,
44
+ )
45
+
46
+ return {
47
+ "get_user_skills_dir": get_user_skills_dir,
48
+ "get_project_skills_dir": get_project_skills_dir,
49
+ "get_project_rules_dir": get_project_rules_dir,
50
+ "load_skill_registry": load_skill_registry,
51
+ }[name]
52
+ if name == "SkillRegistry":
53
+ from illusion.skills.registry import SkillRegistry
54
+
55
+ return SkillRegistry
56
+ if name == "SkillDefinition":
57
+ from illusion.skills.types import SkillDefinition
58
+
59
+ return SkillDefinition
60
+ raise AttributeError(name)
@@ -0,0 +1,110 @@
1
+ """
2
+ 内置 Skill 定义模块
3
+ ==================
4
+
5
+ 本模块从 .md 文件加载内置 skill 定义。
6
+
7
+ 主要功能:
8
+ - 从 content 目录加载所有内置 skills
9
+ - 解析 Markdown 文件的前 matter
10
+
11
+ 类说明:
12
+ - get_bundled_skills: 加载所有内置 skills
13
+
14
+ 使用示例:
15
+ >>> from illusion.skills.bundled import get_bundled_skills
16
+ >>> # 加载内置 skills
17
+ >>> skills = get_bundled_skills()
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from pathlib import Path
23
+
24
+ from illusion.skills.types import SkillDefinition
25
+
26
+ # 内置 skill 内容目录
27
+ _CONTENT_DIR = Path(__file__).parent / "content"
28
+
29
+
30
+ def get_bundled_skills() -> list[SkillDefinition]:
31
+ """从 content 目录加载所有内置 skills(支持 .md、.yaml、.yml)。"""
32
+ skills: list[SkillDefinition] = []
33
+ if not _CONTENT_DIR.exists():
34
+ return skills
35
+ from illusion.skills.loader import _load_yaml_skill
36
+
37
+ for path in sorted(_CONTENT_DIR.iterdir()):
38
+ if not path.is_file():
39
+ continue
40
+ if path.suffix in (".yaml", ".yml"):
41
+ skill = _load_yaml_skill(path, source="bundled")
42
+ if skill:
43
+ skills.append(skill)
44
+ elif path.suffix == ".md":
45
+ content = path.read_text(encoding="utf-8")
46
+ name, description = _parse_frontmatter(path.stem, content)
47
+ skills.append(
48
+ SkillDefinition(
49
+ name=name,
50
+ description=description,
51
+ content=content,
52
+ source="bundled",
53
+ path=str(path),
54
+ )
55
+ )
56
+ return skills
57
+
58
+
59
+ def _parse_frontmatter(default_name: str, content: str) -> tuple[str, str]:
60
+ """从 skill markdown 文件中提取名称和描述。
61
+
62
+ 支持 YAML frontmatter(--- 分隔),并回退到标题/段落解析。
63
+ """
64
+ import yaml
65
+
66
+ name = default_name
67
+ description = ""
68
+ lines = content.splitlines()
69
+
70
+ # 先尝试 YAML frontmatter
71
+ if lines and lines[0].strip() == "---":
72
+ end_idx = -1
73
+ for i, line in enumerate(lines[1:], 1):
74
+ if line.strip() == "---":
75
+ end_idx = i
76
+ break
77
+ if end_idx > 0:
78
+ fm_text = "\n".join(lines[1:end_idx])
79
+ try:
80
+ data = yaml.safe_load(fm_text)
81
+ if isinstance(data, dict):
82
+ if data.get("name"):
83
+ name = str(data["name"]).strip()
84
+ if data.get("description"):
85
+ description = str(data["description"]).strip()
86
+ except Exception:
87
+ # YAML 解析失败,回退到手动解析
88
+ for fm_line in lines[1:end_idx]:
89
+ fm = fm_line.strip()
90
+ if fm.startswith("name:"):
91
+ val = fm[5:].strip().strip("'\"")
92
+ if val:
93
+ name = val
94
+ elif fm.startswith("description:"):
95
+ val = fm[12:].strip().strip("'\"")
96
+ if val:
97
+ description = val
98
+ if description:
99
+ return name, description
100
+
101
+ # 回退:标题 + 第一段
102
+ for line in lines:
103
+ stripped = line.strip()
104
+ if stripped.startswith("# "):
105
+ name = stripped[2:].strip() or default_name
106
+ continue
107
+ if stripped and not stripped.startswith("---") and not stripped.startswith("#"):
108
+ description = stripped[:200]
109
+ break
110
+ return name, description or f"Bundled skill: {name}"
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: batch
3
+ description: Research and plan a large-scale change, then execute it in parallel across 5-30 isolated worktree agents that each open a PR.
4
+ argument-hint: "<instruction>"
5
+ ---
6
+
7
+ # Batch: Parallel Work Orchestration
8
+
9
+ You are orchestrating a large, parallelizable change across this codebase.
10
+
11
+ ## User Instruction
12
+
13
+ The instruction will be provided as arguments.
14
+
15
+ ## Phase 1: Research and Plan (Plan Mode)
16
+
17
+ Call the `enter_plan_mode` tool now to enter plan mode, then:
18
+
19
+ 1. **Understand the scope.** Launch one or more subagents (in the foreground — you need their results) to deeply research what this instruction touches. Find all the files, patterns, and call sites that need to change. Understand the existing conventions so the migration is consistent.
20
+
21
+ 2. **Decompose into independent units.** Break the work into 5–30 self-contained units. Each unit must:
22
+ - Be independently implementable in an isolated git worktree (no shared state with sibling units)
23
+ - Be mergeable on its own without depending on another unit's PR landing first
24
+ - Be roughly uniform in size (split large units, merge trivial ones)
25
+
26
+ Scale the count to the actual work: few files → closer to 5; hundreds of files → closer to 30. Prefer per-directory or per-module slicing over arbitrary file lists.
27
+
28
+ 3. **Determine the e2e test recipe.** Figure out how a worker can verify its change actually works end-to-end — not just that unit tests pass. Look for:
29
+ - A browser-automation tool (for UI changes: click through the affected flow, screenshot the result)
30
+ - A CLI-verifier skill (for CLI changes: launch the app interactively, exercise the changed behavior)
31
+ - A dev-server + curl pattern (for API changes: start the server, hit the affected endpoints)
32
+ - An existing e2e/integration test suite the worker can run
33
+
34
+ If you cannot find a concrete e2e path, use the `ask_user_question` tool to ask the user how to verify this change end-to-end. Offer 2–3 specific options based on what you found.
35
+
36
+ Write the recipe as a short, concrete set of steps that a worker can execute autonomously.
37
+
38
+ 4. **Write the plan.** In your plan file, include:
39
+ - A summary of what you found during research
40
+ - A numbered list of work units — for each: a short title, the list of files/directories it covers, and a one-line description of the change
41
+ - The e2e test recipe (or "skip e2e because …" if the user chose that)
42
+ - The exact worker instructions you will give each agent (the shared template)
43
+
44
+ 5. Call `exit_plan_mode` to present the plan for approval.
45
+
46
+ ## Phase 2: Spawn Workers (After Plan Approval)
47
+
48
+ Once the plan is approved, spawn one background agent per work unit using the `agent` tool. **All agents must use `isolation: "worktree"` and `run_in_background: true`.** Launch them all in a single message block so they run in parallel.
49
+
50
+ For each agent, the prompt must be fully self-contained. Include:
51
+ - The overall goal (the user's instruction)
52
+ - This unit's specific task (title, file list, change description — copied verbatim from your plan)
53
+ - Any codebase conventions you discovered that the worker needs to follow
54
+ - The e2e test recipe from your plan (or "skip e2e because …")
55
+ - The worker instructions below, copied verbatim:
56
+
57
+ ```
58
+ After you finish implementing the change:
59
+ 1. **Simplify** — Invoke the `skill` tool with `name: "simplify"` to review and clean up your changes.
60
+ 2. **Run unit tests** — Run the project's test suite (check for package.json scripts, Makefile targets, or common commands like `npm test`, `bun test`, `pytest`, `go test`). If tests fail, fix them.
61
+ 3. **Test end-to-end** — Follow the e2e test recipe from the coordinator's prompt. If the recipe says to skip e2e for this unit, skip it.
62
+ 4. **Commit and push** — Commit all changes with a clear message, push the branch, and create a PR with `gh pr create`. Use a descriptive title. If `gh` is not available or the push fails, note it in your final message.
63
+ 5. **Report** — End with a single line: `PR: <url>` so the coordinator can track it. If no PR was created, end with `PR: none — <reason>`.
64
+ ```
65
+
66
+ Use `subagent_type: "general-purpose"` unless a more specific agent type fits.
67
+
68
+ ## Phase 3: Track Progress
69
+
70
+ After launching all workers, render an initial status table:
71
+
72
+ | # | Unit | Status | PR |
73
+ |---|------|--------|----|
74
+ | 1 | <title> | running | — |
75
+ | 2 | <title> | running | — |
76
+
77
+ As background-agent completion notifications arrive, parse the `PR: <url>` line from each agent's result and re-render the table with updated status (`done` / `failed`) and PR links. Keep a brief failure note for any agent that did not produce a PR.
78
+
79
+ When all agents have reported, render the final table and a one-line summary (e.g., "22/24 units landed as PRs").
80
+
81
+ ## Rules
82
+
83
+ - This command requires a git repo because it spawns agents in isolated git worktrees and creates PRs from each. Initialize a repo first if needed.
84
+ - Each agent must be fully self-contained — no shared state between agents
85
+ - All agents must use worktree isolation
86
+ - Track progress and report results clearly
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: coding-guidelines
3
+ description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
4
+ ---
5
+
6
+ # Coding Guidelines
7
+
8
+ Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
9
+
10
+ **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
11
+
12
+ ## 1. Think Before Coding
13
+
14
+ **Don't assume. Don't hide confusion. Surface tradeoffs.**
15
+
16
+ Before implementing:
17
+ - State your assumptions explicitly. If uncertain, ask.
18
+ - If multiple interpretations exist, present them - don't pick silently.
19
+ - If a simpler approach exists, say so. Push back when warranted.
20
+ - If something is unclear, stop. Name what's confusing. Ask.
21
+
22
+ ## 2. Simplicity First
23
+
24
+ **Minimum code that solves the problem. Nothing speculative.**
25
+
26
+ - No features beyond what was asked.
27
+ - No abstractions for single-use code.
28
+ - No "flexibility" or "configurability" that wasn't requested.
29
+ - No error handling for impossible scenarios.
30
+ - If you write 200 lines and it could be 50, rewrite it.
31
+
32
+ Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
33
+
34
+ ## 3. Surgical Changes
35
+
36
+ **Touch only what you must. Clean up only your own mess.**
37
+
38
+ When editing existing code:
39
+ - Don't "improve" adjacent code, comments, or formatting.
40
+ - Don't refactor things that aren't broken.
41
+ - Match existing style, even if you'd do it differently.
42
+ - If you notice unrelated dead code, mention it - don't delete it.
43
+
44
+ When your changes create orphans:
45
+ - Remove imports/variables/functions that YOUR changes made unused.
46
+ - Don't remove pre-existing dead code unless asked.
47
+
48
+ The test: Every changed line should trace directly to the user's request.
49
+
50
+ ## 4. Goal-Driven Execution
51
+
52
+ **Define success criteria. Loop until verified.**
53
+
54
+ Transform tasks into verifiable goals:
55
+ - "Add validation" → "Write tests for invalid inputs, then make them pass"
56
+ - "Fix the bug" → "Write a test that reproduces it, then make it pass"
57
+ - "Refactor X" → "Ensure tests pass before and after"
58
+
59
+ For multi-step tasks, state a brief plan:
60
+ ```
61
+ 1. [Step] → verify: [check]
62
+ 2. [Step] → verify: [check]
63
+ 3. [Step] → verify: [check]
64
+ ```
65
+
66
+ Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
67
+
68
+ ---
69
+
70
+ **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: debug
3
+ description: Enable debug logging for this session and help diagnose issues
4
+ ---
5
+
6
+ # Debug Skill
7
+
8
+ Help the user debug an issue they're encountering in this current Illusion Code session.
9
+
10
+ ## Session Debug Log
11
+
12
+ The debug log for the current session is located at the path shown in the session startup output.
13
+
14
+ ## Issue Description
15
+
16
+ The user's issue description will be provided as arguments. If no description is given, read the debug log and summarize any errors, warnings, or notable issues.
17
+
18
+ ## Settings
19
+
20
+ Remember that settings are in:
21
+ * user - ~/.illusion/settings.json
22
+ * project - .illusion/settings.json
23
+
24
+ ## Instructions
25
+
26
+ 1. Review the user's issue description
27
+ 2. Look for [ERROR] and [WARN] entries, stack traces, and failure patterns in the debug log
28
+ 3. Consider launching a subagent to understand the relevant Illusion Code features
29
+ 4. Explain what you found in plain language
30
+ 5. Suggest concrete fixes or next steps
31
+
32
+ ## Rules
33
+
34
+ - Read the error message carefully before searching code
35
+ - Don't guess — verify your hypothesis before changing code
36
+ - Fix the root cause, not the symptom
37
+ - Don't retry the same approach if it failed — investigate why
38
+ - If stuck after 3 attempts, explain what you've tried and ask for help