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] 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()