From d5fdd644cfa646ac3fe20cc8e506591095cb30e8 Mon Sep 17 00:00:00 2001 From: AaronZ345 <34849476+AaronZ345@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:08:37 +0800 Subject: [PATCH 1/2] feat: add silent scheduler agent tasks --- agent/tools/scheduler/integration.py | 6 ++ agent/tools/scheduler/scheduler_tool.py | 8 +++ tests/test_scheduler_silent.py | 93 +++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 tests/test_scheduler_silent.py diff --git a/agent/tools/scheduler/integration.py b/agent/tools/scheduler/integration.py index 7421a525..3d6b8f8d 100644 --- a/agent/tools/scheduler/integration.py +++ b/agent/tools/scheduler/integration.py @@ -255,6 +255,12 @@ def _execute_agent_task(task: dict, agent_bridge) -> bool: logger.error(f"[Scheduler] Task {task['id']}: No result from agent execution") return True # agent ran but produced nothing; don't loop + if action.get("silent", False): + logger.info( + f"[Scheduler] Task {task['id']} executed successfully in silent mode" + ) + return True + from channel.channel_factory import create_channel channel = create_channel(channel_type) if not channel: diff --git a/agent/tools/scheduler/scheduler_tool.py b/agent/tools/scheduler/scheduler_tool.py index 8535f1ca..e251fa52 100644 --- a/agent/tools/scheduler/scheduler_tool.py +++ b/agent/tools/scheduler/scheduler_tool.py @@ -24,6 +24,7 @@ class SchedulerTool(BaseTool): "⚠️ 重要:仅当需要「定时/提醒/每天/每周/X分钟后/X点」等延迟或周期执行时才使用此工具。" "使用方法:\n" "- 创建:action='create', name='任务名', message/ai_task='内容', schedule_type='once/interval/cron', schedule_value='...'\n" + "- 静默AI任务:创建 ai_task 时设置 silent=true,任务会正常执行但不向聊天发送结果\n" "- 查询:action='list' / action='get', task_id='任务ID'\n" "- 管理:action='delete/enable/disable', task_id='任务ID'\n\n" "调度类型:\n" @@ -64,6 +65,11 @@ class SchedulerTool(BaseTool): "schedule_value": { "type": "string", "description": "调度值: cron表达式/间隔秒数/时间(+5s,+10m,+1h或ISO格式)" + }, + "silent": { + "type": "boolean", + "default": False, + "description": "静默模式(仅用于AI任务): true时正常执行AI任务,但不向用户会话发送结果" } }, "required": ["action"] @@ -184,6 +190,8 @@ class SchedulerTool(BaseTool): "channel_type": self.config.get("channel_type", "unknown"), "notify_session_id": notify_session_id, } + if kwargs.get("silent", False): + action["silent"] = True # 针对钉钉单聊,额外存储 sender_staff_id msg = context.kwargs.get("msg") diff --git a/tests/test_scheduler_silent.py b/tests/test_scheduler_silent.py new file mode 100644 index 00000000..2d2baa29 --- /dev/null +++ b/tests/test_scheduler_silent.py @@ -0,0 +1,93 @@ +"""Regression tests for silent scheduled agent tasks.""" + +import os +import sys +import unittest +from types import SimpleNamespace +from unittest.mock import patch + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +from agent.tools.scheduler.integration import _execute_agent_task +from agent.tools.scheduler.scheduler_tool import SchedulerTool + + +class _Context(dict): + kwargs = {} + + +class _TaskStore: + def __init__(self): + self.added = [] + + def add_task(self, task): + self.added.append(task) + + +class _AgentBridge: + def __init__(self, content="maintenance complete"): + self.content = content + self.calls = [] + + def agent_reply(self, task_description, **kwargs): + self.calls.append((task_description, kwargs)) + return SimpleNamespace(content=self.content) + + +class TestSchedulerSilentMode(unittest.TestCase): + def test_schema_exposes_silent_for_agent_tasks(self): + silent = SchedulerTool.params["properties"]["silent"] + + self.assertEqual(silent["type"], "boolean") + self.assertFalse(silent["default"]) + self.assertIn("AI", silent["description"]) + + def test_create_persists_silent_on_agent_task(self): + tool = SchedulerTool({"channel_type": "web"}) + store = _TaskStore() + tool.task_store = store + tool.current_context = _Context( + receiver="user-1", + session_id="session-1", + isgroup=False, + ) + + result = tool.execute( + { + "action": "create", + "name": "refresh token", + "ai_task": "refresh the token", + "schedule_type": "interval", + "schedule_value": "3000", + "silent": True, + } + ) + + self.assertEqual(result.status, "success") + self.assertEqual(len(store.added), 1) + self.assertIs(store.added[0]["action"]["silent"], True) + + def test_silent_agent_task_executes_without_delivery(self): + bridge = _AgentBridge() + task = { + "id": "task-1", + "action": { + "type": "agent_task", + "task_description": "rotate logs", + "receiver": "user-1", + "is_group": False, + "channel_type": "web", + "silent": True, + }, + } + + with patch("channel.channel_factory.create_channel") as create_channel: + result = _execute_agent_task(task, bridge) + + self.assertTrue(result) + self.assertEqual(len(bridge.calls), 1) + create_channel.assert_not_called() + + +if __name__ == "__main__": + unittest.main() From b55c592714b1f48f8411bcbc2e5512194021089b Mon Sep 17 00:00:00 2001 From: zhayujie Date: Fri, 17 Jul 2026 10:53:29 +0800 Subject: [PATCH 2/2] feat: refine scheduler silent mode to avoid mislabeling delivery tasks --- agent/tools/scheduler/scheduler_tool.py | 11 +++++++---- tests/test_scheduler_silent.py | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/agent/tools/scheduler/scheduler_tool.py b/agent/tools/scheduler/scheduler_tool.py index e251fa52..de8802a1 100644 --- a/agent/tools/scheduler/scheduler_tool.py +++ b/agent/tools/scheduler/scheduler_tool.py @@ -24,7 +24,6 @@ class SchedulerTool(BaseTool): "⚠️ 重要:仅当需要「定时/提醒/每天/每周/X分钟后/X点」等延迟或周期执行时才使用此工具。" "使用方法:\n" "- 创建:action='create', name='任务名', message/ai_task='内容', schedule_type='once/interval/cron', schedule_value='...'\n" - "- 静默AI任务:创建 ai_task 时设置 silent=true,任务会正常执行但不向聊天发送结果\n" "- 查询:action='list' / action='get', task_id='任务ID'\n" "- 管理:action='delete/enable/disable', task_id='任务ID'\n\n" "调度类型:\n" @@ -69,7 +68,7 @@ class SchedulerTool(BaseTool): "silent": { "type": "boolean", "default": False, - "description": "静默模式(仅用于AI任务): true时正常执行AI任务,但不向用户会话发送结果" + "description": "Silent mode (default false): when true, the task runs normally but its result is not pushed. Set true only when the user explicitly says they don't need the result; reminder, notification and broadcast tasks must keep it false" } }, "required": ["action"] @@ -190,6 +189,7 @@ class SchedulerTool(BaseTool): "channel_type": self.config.get("channel_type", "unknown"), "notify_session_id": notify_session_id, } + # silent only applies to ai_task; fixed messages always deliver if kwargs.get("silent", False): action["silent"] = True @@ -224,14 +224,17 @@ class SchedulerTool(BaseTool): content_desc = f"💬 固定消息: {message}" else: content_desc = f"🤖 AI任务: {ai_task}" - + + # Warn the user at creation time so a mistaken silent flag is easy to spot + silent_desc = "\n🔇 静默模式: 执行后不会推送结果" if action.get("silent") else "" + return ( f"✅ 定时任务创建成功\n\n" f"📋 任务ID: {task_id}\n" f"📝 名称: {name}\n" f"⏰ 调度: {schedule_desc}\n" f"👤 接收者: {receiver_desc}\n" - f"{content_desc}\n" + f"{content_desc}{silent_desc}\n" f"🕐 下次执行: {next_run.strftime('%Y-%m-%d %H:%M:%S') if next_run else '未知'}" ) diff --git a/tests/test_scheduler_silent.py b/tests/test_scheduler_silent.py index 2d2baa29..fabc64be 100644 --- a/tests/test_scheduler_silent.py +++ b/tests/test_scheduler_silent.py @@ -40,7 +40,6 @@ class TestSchedulerSilentMode(unittest.TestCase): self.assertEqual(silent["type"], "boolean") self.assertFalse(silent["default"]) - self.assertIn("AI", silent["description"]) def test_create_persists_silent_on_agent_task(self): tool = SchedulerTool({"channel_type": "web"})