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
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""
|
|
2
|
+
统一 Cron 定时任务工具
|
|
3
|
+
======================
|
|
4
|
+
|
|
5
|
+
对齐 openclaw 的 cron 工具设计,将创建、列表、删除、开关、手动触发
|
|
6
|
+
合并为单一工具,通过 action 参数区分操作。
|
|
7
|
+
|
|
8
|
+
支持的操作(actions):
|
|
9
|
+
- status: 查看调度器状态
|
|
10
|
+
- list: 列出所有定时任务
|
|
11
|
+
- add: 创建新的定时任务
|
|
12
|
+
- update: 修改已有任务(启用/禁用、更新计划等)
|
|
13
|
+
- remove: 删除定时任务
|
|
14
|
+
- run: 手动触发执行任务
|
|
15
|
+
|
|
16
|
+
数据模型(对齐 openclaw CronJob):
|
|
17
|
+
- id: 唯一标识符(自动生成)
|
|
18
|
+
- name: 人类可读名称
|
|
19
|
+
- schedule: 5 字段 cron 表达式(本地时间)
|
|
20
|
+
- prompt: 触发时执行的提示词
|
|
21
|
+
- enabled: 是否启用
|
|
22
|
+
- recurring: 是否重复执行
|
|
23
|
+
- delete_after_run: 执行后自动删除
|
|
24
|
+
|
|
25
|
+
使用示例:
|
|
26
|
+
>>> from illusion.tools.cron_tool import CronTool
|
|
27
|
+
>>> tool = CronTool()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from pydantic import BaseModel, Field
|
|
35
|
+
|
|
36
|
+
from illusion.services.cron import (
|
|
37
|
+
delete_cron_job,
|
|
38
|
+
get_cron_job,
|
|
39
|
+
load_cron_jobs,
|
|
40
|
+
set_job_enabled,
|
|
41
|
+
upsert_cron_job,
|
|
42
|
+
validate_cron_expression,
|
|
43
|
+
)
|
|
44
|
+
from illusion.services.cron_scheduler import (
|
|
45
|
+
execute_job,
|
|
46
|
+
get_scheduler,
|
|
47
|
+
is_scheduler_running,
|
|
48
|
+
)
|
|
49
|
+
from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
|
|
50
|
+
|
|
51
|
+
# 支持的操作列表
|
|
52
|
+
_ACTIONS = ("status", "list", "add", "update", "remove", "run")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CronToolInput(BaseModel):
|
|
56
|
+
"""统一 Cron 工具参数。
|
|
57
|
+
|
|
58
|
+
属性:
|
|
59
|
+
action: 操作类型
|
|
60
|
+
name: 任务名称(add 可选;update/remove/run 必填)
|
|
61
|
+
schedule: 5 字段 cron 表达式(add/update)
|
|
62
|
+
prompt: 触发时执行的提示词(add 必填;update 可选)
|
|
63
|
+
recurring: 是否重复执行(add/update)
|
|
64
|
+
delete_after_run: 执行后自动删除(add/update)
|
|
65
|
+
enabled: 启用/禁用状态(update)
|
|
66
|
+
include_disabled: 列表是否包含禁用任务(list)
|
|
67
|
+
timeout_seconds: 手动运行超时秒数(run)
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
action: str = Field(
|
|
71
|
+
description=f"Action: {', '.join(_ACTIONS)}",
|
|
72
|
+
)
|
|
73
|
+
# add/update 操作通用参数
|
|
74
|
+
name: str | None = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description="Job name or ID (optional for add; required for update/remove/run)",
|
|
77
|
+
)
|
|
78
|
+
schedule: str | None = Field(
|
|
79
|
+
default=None,
|
|
80
|
+
description="5-field cron expression in local time (add/update). E.g. '*/5 * * * *', '0 9 * * 1-5'",
|
|
81
|
+
)
|
|
82
|
+
prompt: str | None = Field(
|
|
83
|
+
default=None,
|
|
84
|
+
description="Prompt to execute in an isolated session when the job fires (required for add; optional for update)",
|
|
85
|
+
)
|
|
86
|
+
recurring: bool | None = Field(
|
|
87
|
+
default=None,
|
|
88
|
+
description="True = fire on every cron match until deleted. False = fire once then auto-delete. Set to true/false to change (add/update).",
|
|
89
|
+
)
|
|
90
|
+
delete_after_run: bool | None = Field(
|
|
91
|
+
default=None,
|
|
92
|
+
description="Delete the job record after execution (add/update). Set to true/false to change.",
|
|
93
|
+
)
|
|
94
|
+
# update 操作参数
|
|
95
|
+
enabled: bool | None = Field(
|
|
96
|
+
default=None,
|
|
97
|
+
description="Enable or disable the job (update)",
|
|
98
|
+
)
|
|
99
|
+
# list 操作参数
|
|
100
|
+
include_disabled: bool = Field(
|
|
101
|
+
default=False,
|
|
102
|
+
description="Include disabled jobs in list output",
|
|
103
|
+
)
|
|
104
|
+
# run 操作参数
|
|
105
|
+
timeout_seconds: int = Field(
|
|
106
|
+
default=300,
|
|
107
|
+
ge=1,
|
|
108
|
+
le=3600,
|
|
109
|
+
description="Timeout in seconds for manual run",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CronTool(BaseTool):
|
|
114
|
+
"""Manage scheduled cron jobs (status/list/add/update/remove/run).
|
|
115
|
+
|
|
116
|
+
Aligned with openclaw's cron tool design.
|
|
117
|
+
Jobs execute in isolated sessions via `illusion -p`, not blocking the current session.
|
|
118
|
+
Uses standard 5-field cron expressions in the user's local timezone.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
name = "cron"
|
|
122
|
+
description = """Manage scheduled cron jobs (status/list/add/update/remove/run). Use for reminders, delayed follow-ups, and recurring tasks. Do not emulate scheduling with exec sleep.
|
|
123
|
+
|
|
124
|
+
ACTIONS:
|
|
125
|
+
- status: Check scheduler status and job counts
|
|
126
|
+
- list: List all scheduled jobs (use include_disabled:true to show disabled)
|
|
127
|
+
- add: Create a new scheduled job (requires schedule, prompt; optional name)
|
|
128
|
+
- update: Modify an existing job (requires name; can update schedule/prompt/recurring/delete_after_run/enabled)
|
|
129
|
+
- remove: Delete a job (requires name)
|
|
130
|
+
- run: Manually trigger a job immediately (requires name)
|
|
131
|
+
|
|
132
|
+
SCHEDULE (standard 5-field cron, user's local time):
|
|
133
|
+
- minute hour day-of-month month day-of-week
|
|
134
|
+
- "*/5 * * * *" = every 5 minutes
|
|
135
|
+
- "0 * * * *" = every hour
|
|
136
|
+
- "0 9 * * 1-5" = weekdays at 9am local
|
|
137
|
+
- "30 14 7 5 *" = May 7th at 2:30pm
|
|
138
|
+
|
|
139
|
+
ONE-SHOT EXAMPLES:
|
|
140
|
+
"remind me at 2:30pm today" -> schedule: "30 14 <today_dom> <today_month> *", recurring: false
|
|
141
|
+
"run smoke test tomorrow morning" -> schedule: "57 8 <tomorrow_dom> <tomorrow_month> *", recurring: false
|
|
142
|
+
|
|
143
|
+
AVOID :00 AND :30 when the task allows it:
|
|
144
|
+
"every morning around 9" -> "57 8 * * *" or "3 9 * * *" (not "0 9 * * *")
|
|
145
|
+
"hourly" -> "7 * * * *" (not "0 * * * *")
|
|
146
|
+
|
|
147
|
+
EXECUTION:
|
|
148
|
+
Jobs run via `illusion -p` in an isolated subprocess, not blocking the current session.
|
|
149
|
+
The scheduler auto-starts when a job is created.
|
|
150
|
+
Recurring jobs do not auto-expire; delete them manually when no longer needed.
|
|
151
|
+
|
|
152
|
+
Returns JSON result for each action."""
|
|
153
|
+
input_model = CronToolInput
|
|
154
|
+
|
|
155
|
+
async def execute(
|
|
156
|
+
self,
|
|
157
|
+
arguments: CronToolInput,
|
|
158
|
+
context: ToolExecutionContext,
|
|
159
|
+
) -> ToolResult:
|
|
160
|
+
action = arguments.action.strip().lower()
|
|
161
|
+
|
|
162
|
+
if action not in _ACTIONS:
|
|
163
|
+
return ToolResult(
|
|
164
|
+
output=f"Unknown action: {action!r}. Supported: {', '.join(_ACTIONS)}",
|
|
165
|
+
is_error=True,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
handler = {
|
|
169
|
+
"status": self._handle_status,
|
|
170
|
+
"list": self._handle_list,
|
|
171
|
+
"add": self._handle_add,
|
|
172
|
+
"update": self._handle_update,
|
|
173
|
+
"remove": self._handle_remove,
|
|
174
|
+
"run": self._handle_run,
|
|
175
|
+
}[action]
|
|
176
|
+
|
|
177
|
+
return await handler(arguments, context)
|
|
178
|
+
|
|
179
|
+
# ------------------------------------------------------------------
|
|
180
|
+
# status: 调度器状态
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
async def _handle_status(
|
|
184
|
+
self,
|
|
185
|
+
arguments: CronToolInput,
|
|
186
|
+
context: ToolExecutionContext,
|
|
187
|
+
) -> ToolResult:
|
|
188
|
+
"""查看调度器运行状态和任务统计。"""
|
|
189
|
+
del arguments, context
|
|
190
|
+
|
|
191
|
+
scheduler = get_scheduler()
|
|
192
|
+
status = scheduler.status()
|
|
193
|
+
|
|
194
|
+
state = "running" if status["running"] else "stopped"
|
|
195
|
+
lines = [
|
|
196
|
+
f"Scheduler: {state}",
|
|
197
|
+
f"Total jobs: {status['total_jobs']}",
|
|
198
|
+
f"Enabled: {status['enabled_jobs']}",
|
|
199
|
+
]
|
|
200
|
+
if status.get("pid"):
|
|
201
|
+
lines.append(f"PID: {status['pid']}")
|
|
202
|
+
|
|
203
|
+
return ToolResult(output="\n".join(lines))
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------------------------
|
|
206
|
+
# list: 列出任务
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
async def _handle_list(
|
|
210
|
+
self,
|
|
211
|
+
arguments: CronToolInput,
|
|
212
|
+
context: ToolExecutionContext,
|
|
213
|
+
) -> ToolResult:
|
|
214
|
+
"""列出所有定时任务。"""
|
|
215
|
+
del context
|
|
216
|
+
|
|
217
|
+
jobs = load_cron_jobs()
|
|
218
|
+
|
|
219
|
+
if not arguments.include_disabled:
|
|
220
|
+
jobs = [j for j in jobs if j.get("enabled", True)]
|
|
221
|
+
|
|
222
|
+
if not jobs:
|
|
223
|
+
return ToolResult(output="No cron jobs configured.")
|
|
224
|
+
|
|
225
|
+
scheduler_state = "running" if is_scheduler_running() else "stopped"
|
|
226
|
+
lines = [f"Scheduler: {scheduler_state}", ""]
|
|
227
|
+
|
|
228
|
+
for job in jobs:
|
|
229
|
+
enabled = "+" if job.get("enabled", True) else "-"
|
|
230
|
+
name = job.get("name", job.get("id", "?"))
|
|
231
|
+
schedule = job.get("schedule", "?")
|
|
232
|
+
recurring = "recurring" if job.get("recurring", True) else "one-shot"
|
|
233
|
+
|
|
234
|
+
last_run = job.get("last_run", "never")
|
|
235
|
+
if last_run != "never":
|
|
236
|
+
last_run = last_run[:19]
|
|
237
|
+
last_status = job.get("last_status", "")
|
|
238
|
+
status_str = f" ({last_status})" if last_status else ""
|
|
239
|
+
|
|
240
|
+
next_run = job.get("next_run", "n/a")
|
|
241
|
+
if next_run != "n/a":
|
|
242
|
+
next_run = next_run[:19]
|
|
243
|
+
|
|
244
|
+
errors = job.get("consecutive_errors", 0)
|
|
245
|
+
error_str = f" [errors: {errors}]" if errors > 0 else ""
|
|
246
|
+
|
|
247
|
+
lines.append(
|
|
248
|
+
f"[{enabled}] {name} {schedule} ({recurring})\n"
|
|
249
|
+
f" prompt: {job.get('prompt', '?')[:60]}\n"
|
|
250
|
+
f" last: {last_run}{status_str} next: {next_run}{error_str}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return ToolResult(output="\n".join(lines))
|
|
254
|
+
|
|
255
|
+
# ------------------------------------------------------------------
|
|
256
|
+
# add: 创建任务
|
|
257
|
+
# ------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
async def _handle_add(
|
|
260
|
+
self,
|
|
261
|
+
arguments: CronToolInput,
|
|
262
|
+
context: ToolExecutionContext,
|
|
263
|
+
) -> ToolResult:
|
|
264
|
+
"""创建新的定时任务,并自动启动调度器。"""
|
|
265
|
+
if not arguments.schedule:
|
|
266
|
+
return ToolResult(
|
|
267
|
+
output="Missing required parameter: schedule\n"
|
|
268
|
+
"Use a 5-field cron expression, e.g. '*/5 * * * *' (every 5 min)",
|
|
269
|
+
is_error=True,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if not arguments.prompt:
|
|
273
|
+
return ToolResult(
|
|
274
|
+
output="Missing required parameter: prompt\n"
|
|
275
|
+
"prompt is executed in an isolated session when the job fires",
|
|
276
|
+
is_error=True,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if not validate_cron_expression(arguments.schedule):
|
|
280
|
+
return ToolResult(
|
|
281
|
+
output=(
|
|
282
|
+
f"Invalid cron expression: {arguments.schedule!r}\n"
|
|
283
|
+
"Use 5-field format: minute hour day month weekday\n"
|
|
284
|
+
"Examples: '*/5 * * * *' (every 5 min), '0 9 * * 1-5' (weekdays 9am)"
|
|
285
|
+
),
|
|
286
|
+
is_error=True,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# 构建任务字典
|
|
290
|
+
job_data: dict[str, Any] = {
|
|
291
|
+
"schedule": arguments.schedule.strip(),
|
|
292
|
+
"prompt": arguments.prompt,
|
|
293
|
+
"recurring": arguments.recurring
|
|
294
|
+
if arguments.recurring is not None
|
|
295
|
+
else True,
|
|
296
|
+
"delete_after_run": arguments.delete_after_run
|
|
297
|
+
if arguments.delete_after_run is not None
|
|
298
|
+
else False,
|
|
299
|
+
"cwd": str(context.cwd),
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if arguments.name:
|
|
303
|
+
job_data["name"] = arguments.name.strip()
|
|
304
|
+
|
|
305
|
+
# 创建任务
|
|
306
|
+
job_id = upsert_cron_job(job_data)
|
|
307
|
+
|
|
308
|
+
# 自动启动调度器(如果未运行)
|
|
309
|
+
try:
|
|
310
|
+
scheduler = get_scheduler()
|
|
311
|
+
if not scheduler.is_running:
|
|
312
|
+
await scheduler.start()
|
|
313
|
+
except Exception:
|
|
314
|
+
# 调度器启动失败不应阻止任务创建
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
kind = "recurring" if (arguments.recurring if arguments.recurring is not None else True) else "one-shot"
|
|
318
|
+
name_display = arguments.name or job_id
|
|
319
|
+
return ToolResult(
|
|
320
|
+
output=f"Created {kind} job '{name_display}' [{arguments.schedule}] (id: {job_id})"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# ------------------------------------------------------------------
|
|
324
|
+
# update: 修改任务
|
|
325
|
+
# ------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
async def _handle_update(
|
|
328
|
+
self,
|
|
329
|
+
arguments: CronToolInput,
|
|
330
|
+
context: ToolExecutionContext,
|
|
331
|
+
) -> ToolResult:
|
|
332
|
+
"""修改已有任务的计划、启用状态等。"""
|
|
333
|
+
del context
|
|
334
|
+
|
|
335
|
+
if not arguments.name:
|
|
336
|
+
return ToolResult(
|
|
337
|
+
output="Missing required parameter: name (job name or ID to update)",
|
|
338
|
+
is_error=True,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
job = get_cron_job(arguments.name)
|
|
342
|
+
if job is None:
|
|
343
|
+
return ToolResult(
|
|
344
|
+
output=f"Cron job not found: {arguments.name}",
|
|
345
|
+
is_error=True,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
changes: list[str] = []
|
|
349
|
+
|
|
350
|
+
if arguments.enabled is not None:
|
|
351
|
+
if set_job_enabled(arguments.name, arguments.enabled):
|
|
352
|
+
state = "enabled" if arguments.enabled else "disabled"
|
|
353
|
+
changes.append(f"{state}")
|
|
354
|
+
else:
|
|
355
|
+
return ToolResult(
|
|
356
|
+
output=f"Failed to update job: {arguments.name}",
|
|
357
|
+
is_error=True,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if arguments.schedule is not None:
|
|
361
|
+
if not validate_cron_expression(arguments.schedule):
|
|
362
|
+
return ToolResult(
|
|
363
|
+
output=f"Invalid cron expression: {arguments.schedule!r}",
|
|
364
|
+
is_error=True,
|
|
365
|
+
)
|
|
366
|
+
job["schedule"] = arguments.schedule.strip()
|
|
367
|
+
upsert_cron_job(job)
|
|
368
|
+
changes.append(f"schedule={arguments.schedule.strip()}")
|
|
369
|
+
|
|
370
|
+
if arguments.prompt is not None:
|
|
371
|
+
job["prompt"] = arguments.prompt
|
|
372
|
+
upsert_cron_job(job)
|
|
373
|
+
changes.append("prompt updated")
|
|
374
|
+
|
|
375
|
+
if arguments.recurring is not None:
|
|
376
|
+
if arguments.recurring != job.get("recurring", True):
|
|
377
|
+
job["recurring"] = arguments.recurring
|
|
378
|
+
upsert_cron_job(job)
|
|
379
|
+
changes.append(f"recurring={arguments.recurring}")
|
|
380
|
+
|
|
381
|
+
if arguments.delete_after_run is not None:
|
|
382
|
+
if arguments.delete_after_run != job.get("delete_after_run", False):
|
|
383
|
+
job["delete_after_run"] = arguments.delete_after_run
|
|
384
|
+
upsert_cron_job(job)
|
|
385
|
+
changes.append(f"delete_after_run={arguments.delete_after_run}")
|
|
386
|
+
|
|
387
|
+
if changes:
|
|
388
|
+
return ToolResult(
|
|
389
|
+
output=f"Updated cron job: {arguments.name} ({', '.join(changes)})"
|
|
390
|
+
)
|
|
391
|
+
else:
|
|
392
|
+
return ToolResult(
|
|
393
|
+
output="No fields to update (available: schedule, prompt, recurring, enabled, delete_after_run)",
|
|
394
|
+
is_error=True,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# ------------------------------------------------------------------
|
|
398
|
+
# remove: 删除任务
|
|
399
|
+
# ------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
async def _handle_remove(
|
|
402
|
+
self,
|
|
403
|
+
arguments: CronToolInput,
|
|
404
|
+
context: ToolExecutionContext,
|
|
405
|
+
) -> ToolResult:
|
|
406
|
+
"""删除定时任务。"""
|
|
407
|
+
del context
|
|
408
|
+
|
|
409
|
+
if not arguments.name:
|
|
410
|
+
return ToolResult(
|
|
411
|
+
output="Missing required parameter: name (job name or ID to remove)",
|
|
412
|
+
is_error=True,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
if not delete_cron_job(arguments.name):
|
|
416
|
+
return ToolResult(
|
|
417
|
+
output=f"Cron job not found: {arguments.name}",
|
|
418
|
+
is_error=True,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
return ToolResult(output=f"Deleted cron job: {arguments.name}")
|
|
422
|
+
|
|
423
|
+
# ------------------------------------------------------------------
|
|
424
|
+
# run: 手动触发
|
|
425
|
+
# ------------------------------------------------------------------
|
|
426
|
+
|
|
427
|
+
async def _handle_run(
|
|
428
|
+
self,
|
|
429
|
+
arguments: CronToolInput,
|
|
430
|
+
context: ToolExecutionContext,
|
|
431
|
+
) -> ToolResult:
|
|
432
|
+
"""手动触发执行定时任务。"""
|
|
433
|
+
del context
|
|
434
|
+
|
|
435
|
+
if not arguments.name:
|
|
436
|
+
return ToolResult(
|
|
437
|
+
output="Missing required parameter: name (job name or ID to run)",
|
|
438
|
+
is_error=True,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
job = get_cron_job(arguments.name)
|
|
442
|
+
if job is None:
|
|
443
|
+
return ToolResult(
|
|
444
|
+
output=f"Cron job not found: {arguments.name}",
|
|
445
|
+
is_error=True,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
prompt = job.get("prompt", "")
|
|
449
|
+
if not prompt:
|
|
450
|
+
return ToolResult(
|
|
451
|
+
output=f"Job has no prompt: {arguments.name}",
|
|
452
|
+
is_error=True,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# 在独立会话中执行
|
|
456
|
+
entry = await execute_job(job, timeout=arguments.timeout_seconds)
|
|
457
|
+
|
|
458
|
+
status = entry.get("status", "unknown")
|
|
459
|
+
returncode = entry.get("returncode", "?")
|
|
460
|
+
stdout = entry.get("stdout", "").strip()
|
|
461
|
+
stderr = entry.get("stderr", "").strip()
|
|
462
|
+
|
|
463
|
+
parts = [f"Triggered {arguments.name} ({status}, rc={returncode})"]
|
|
464
|
+
if stdout:
|
|
465
|
+
parts.append(f"Output:\n{stdout}")
|
|
466
|
+
if stderr and status != "success":
|
|
467
|
+
parts.append(f"Error:\n{stderr}")
|
|
468
|
+
|
|
469
|
+
return ToolResult(
|
|
470
|
+
output="\n".join(parts),
|
|
471
|
+
is_error=status != "success",
|
|
472
|
+
metadata={"returncode": returncode, "status": status},
|
|
473
|
+
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
进入计划模式工具模块
|
|
3
|
+
====================
|
|
4
|
+
|
|
5
|
+
本模块提供进入计划权限模式的工具。
|
|
6
|
+
|
|
7
|
+
主要功能:
|
|
8
|
+
- 切换设置权限模式为计划模式
|
|
9
|
+
- 允许在编写代码前探索代码库并设计实现方案
|
|
10
|
+
|
|
11
|
+
类说明:
|
|
12
|
+
- EnterPlanModeToolInput: 工具输入模型(无操作)
|
|
13
|
+
- EnterPlanModeTool: 进入计划模式工具
|
|
14
|
+
|
|
15
|
+
使用示例:
|
|
16
|
+
>>> # 工具自动由系统调用,用户批准后进入计划模式
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel
|
|
22
|
+
|
|
23
|
+
from illusion.config.settings import load_settings, save_settings
|
|
24
|
+
from illusion.permissions import PermissionMode
|
|
25
|
+
from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EnterPlanModeToolInput(BaseModel):
|
|
29
|
+
"""无操作输入模型
|
|
30
|
+
|
|
31
|
+
此工具不需要任何输入参数。
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class EnterPlanModeTool(BaseTool):
|
|
36
|
+
"""切换设置权限模式为计划模式
|
|
37
|
+
|
|
38
|
+
此工具用于在开始非平凡的实现任务之前主动使用。
|
|
39
|
+
获得用户对方法的批准可以防止浪费精力并确保一致性。
|
|
40
|
+
此工具将您转换到计划模式,在那里您可以探索代码库并设计实现方案以供用户批准。
|
|
41
|
+
|
|
42
|
+
何时使用此工具:
|
|
43
|
+
- 新功能实现时
|
|
44
|
+
- 有多种有效方法时
|
|
45
|
+
- 需要修改代码时
|
|
46
|
+
- 需要架构决策时
|
|
47
|
+
- 可能涉及多个文件时
|
|
48
|
+
- 需求不清晰时
|
|
49
|
+
- 用户偏好很重要时
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name = "enter_plan_mode"
|
|
53
|
+
description = """Use this tool proactively when you're about to start a non-trivial implementation task. Getting user sign-off on your approach before writing code prevents wasted effort and ensures alignment. This tool transitions you into plan mode where you can explore the codebase and design an implementation approach for user approval.
|
|
54
|
+
|
|
55
|
+
## When to Use This Tool
|
|
56
|
+
|
|
57
|
+
**Prefer using EnterPlanMode** for implementation tasks unless they're simple. Use it when ANY of these conditions apply:
|
|
58
|
+
|
|
59
|
+
1. **New Feature Implementation**: Adding meaningful new functionality
|
|
60
|
+
- Example: "Add a logout button" - where should it go? What should happen on click?
|
|
61
|
+
- Example: "Add form validation" - what rules? What error messages?
|
|
62
|
+
|
|
63
|
+
2. **Multiple Valid Approaches**: The task can be solved in several different ways
|
|
64
|
+
- Example: "Add caching to the API" - could use Redis, in-memory, file-based, etc.
|
|
65
|
+
- Example: "Improve performance" - many optimization strategies possible
|
|
66
|
+
|
|
67
|
+
3. **Code Modifications**: Changes that affect existing behavior or structure
|
|
68
|
+
- Example: "Update the login flow" - what exactly should change?
|
|
69
|
+
- Example: "Refactor this component" - what's the target architecture?
|
|
70
|
+
|
|
71
|
+
4. **Architectural Decisions**: The task requires choosing between patterns or technologies
|
|
72
|
+
- Example: "Add real-time updates" - WebSockets vs SSE vs polling
|
|
73
|
+
- Example: "Implement state management" - Redux vs Context vs custom solution
|
|
74
|
+
|
|
75
|
+
5. **Multi-File Changes**: The task will likely touch more than 2-3 files
|
|
76
|
+
- Example: "Refactor the authentication system"
|
|
77
|
+
- Example: "Add a new API endpoint with tests"
|
|
78
|
+
|
|
79
|
+
6. **Unclear Requirements**: You need to explore before understanding the full scope
|
|
80
|
+
- Example: "Make the app faster" - need to profile and identify bottlenecks
|
|
81
|
+
- Example: "Fix the bug in checkout" - need to investigate root cause
|
|
82
|
+
|
|
83
|
+
7. **User Preferences Matter**: The implementation could reasonably go multiple ways
|
|
84
|
+
- If you would use AskUserQuestion to clarify the approach, use EnterPlanMode instead
|
|
85
|
+
- Plan mode lets you explore first, then present options with context
|
|
86
|
+
|
|
87
|
+
## When NOT to Use This Tool
|
|
88
|
+
|
|
89
|
+
Only skip EnterPlanMode for simple tasks:
|
|
90
|
+
- Single-line or few-line fixes (typos, obvious bugs, small tweaks)
|
|
91
|
+
- Adding a single function with clear requirements
|
|
92
|
+
- Tasks where the user has given very specific, detailed instructions
|
|
93
|
+
- Pure research/exploration tasks (use the Agent tool with explore agent instead)
|
|
94
|
+
|
|
95
|
+
## What Happens in Plan Mode
|
|
96
|
+
|
|
97
|
+
In plan mode, you'll:
|
|
98
|
+
1. Thoroughly explore the codebase using Glob, Grep, and Read tools
|
|
99
|
+
2. Understand existing patterns and architecture
|
|
100
|
+
3. Design an implementation approach
|
|
101
|
+
4. Present your plan to the user for approval
|
|
102
|
+
5. Use AskUserQuestion if you need to clarify approaches
|
|
103
|
+
6. Exit plan mode with ExitPlanMode when ready to implement
|
|
104
|
+
|
|
105
|
+
## Examples
|
|
106
|
+
|
|
107
|
+
### GOOD - Use EnterPlanMode:
|
|
108
|
+
User: "Add user authentication to the app"
|
|
109
|
+
- Requires architectural decisions (session vs JWT, where to store tokens, middleware structure)
|
|
110
|
+
|
|
111
|
+
User: "Optimize the database queries"
|
|
112
|
+
- Multiple approaches possible, need to profile first, significant impact
|
|
113
|
+
|
|
114
|
+
User: "Implement dark mode"
|
|
115
|
+
- Architectural decision on theme system, affects many components
|
|
116
|
+
|
|
117
|
+
User: "Add a delete button to the user profile"
|
|
118
|
+
- Seems simple but involves: where to place it, confirmation dialog, API call, error handling, state updates
|
|
119
|
+
|
|
120
|
+
User: "Update the error handling in the API"
|
|
121
|
+
- Affects multiple files, user should approve the approach
|
|
122
|
+
|
|
123
|
+
### BAD - Don't use EnterPlanMode:
|
|
124
|
+
User: "Fix the typo in the README"
|
|
125
|
+
- Straightforward, no planning needed
|
|
126
|
+
|
|
127
|
+
User: "Add a console.log to debug this function"
|
|
128
|
+
- Simple, obvious implementation
|
|
129
|
+
|
|
130
|
+
User: "What files handle routing?"
|
|
131
|
+
- Research task, not implementation planning
|
|
132
|
+
|
|
133
|
+
## Important Notes
|
|
134
|
+
|
|
135
|
+
- This tool REQUIRES user approval - they must consent to entering plan mode
|
|
136
|
+
- If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work
|
|
137
|
+
- Users appreciate being consulted before significant changes are made to their codebase"""
|
|
138
|
+
input_model = EnterPlanModeToolInput
|
|
139
|
+
|
|
140
|
+
async def execute(self, arguments: EnterPlanModeToolInput, context: ToolExecutionContext) -> ToolResult:
|
|
141
|
+
# 删除未使用的参数
|
|
142
|
+
del arguments, context
|
|
143
|
+
# 加载设置并将权限模式切换为计划模式
|
|
144
|
+
settings = load_settings()
|
|
145
|
+
settings.permission.mode = PermissionMode.PLAN
|
|
146
|
+
save_settings(settings)
|
|
147
|
+
return ToolResult(output="Permission mode set to plan")
|