foliko 1.0.10 → 1.0.13

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.
@@ -34,7 +34,10 @@
34
34
  "Bash(node -c plugins/default-plugins.js && node -c src/core/plugin-manager.js && echo \"Syntax OK\")",
35
35
  "Bash(node -c plugins/install-plugin.js && echo \"Syntax OK\")",
36
36
  "Bash(node -c cli/src/ui/chat-ui.js && echo \"Syntax OK\")",
37
- "Bash(node -c plugins/default-plugins.js && echo \"Syntax OK\")"
37
+ "Bash(node -c plugins/default-plugins.js && echo \"Syntax OK\")",
38
+ "Bash(node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
39
+ "Bash(node -c plugins/telegram-plugin.js && node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
40
+ "Bash(node -c plugins/telegram-plugin.js && echo \"Syntax OK\")"
38
41
  ]
39
42
  }
40
43
  }
package/SPEC.md CHANGED
@@ -304,8 +304,34 @@ const response = await agent.chat('你好')
304
304
  'agent:message' // Agent消息
305
305
  'agent:tool-call' // Agent调用工具
306
306
  'agent:tool-result' // 工具执行结果
307
+ 'scheduler:task_created' // 定时任务创建
308
+ 'scheduler:task_cancelled' // 定时任务取消
309
+ 'scheduler:task_completed' // 定时任务完成
310
+ 'scheduler:task_failed' // 定时任务失败
311
+ 'scheduler:reminder' // 定时提醒触发
312
+ 'think:thought_completed' // 思考完成
313
+ 'think:reflection_needs_attention' // 反思需要关注
307
314
  ```
308
315
 
316
+ ### 事件详情
317
+
318
+ #### Scheduler 事件
319
+
320
+ | 事件 | 数据 | 说明 |
321
+ |------|------|------|
322
+ | `scheduler:task_created` | `{ taskId, taskName, type, scheduleTime, cronExpression }` | 任务创建时触发 |
323
+ | `scheduler:task_cancelled` | `{ taskId, taskName }` | 任务取消时触发 |
324
+ | `scheduler:task_completed` | `{ taskId, taskName, type }` | 任务完成时触发 |
325
+ | `scheduler:task_failed` | `{ taskId, taskName, error }` | 任务失败时触发 |
326
+ | `scheduler:reminder` | `{ taskId, taskName, message, sessionId, llm }` | 提醒触发时触发 |
327
+
328
+ #### Think 事件
329
+
330
+ | 事件 | 数据 | 说明 |
331
+ |------|------|------|
332
+ | `think:thought_completed` | `thought` | 思考完成时触发 |
333
+ | `think:reflection_needs_attention` | `{ thought, reason }` | 反思需要关注时触发 |
334
+
309
335
  ## 六、热重载机制
310
336
 
311
337
  ### 设计原则
@@ -384,6 +410,10 @@ agent.chat('请重载 my-plugin 插件')
384
410
  - 绑定主 Agent 持续对话
385
411
  - 支持 MarkdownV2
386
412
  - 图片/文档接收保存
413
+ - 监听 `scheduler:reminder` 事件发送定时提醒
414
+
415
+ **监听事件**:
416
+ - `scheduler:reminder` - 接收定时任务提醒并发送到 Telegram
387
417
 
388
418
  **配置**:
389
419
  ```javascript
@@ -395,6 +425,53 @@ agent.chat('请重载 my-plugin 插件')
395
425
  }
396
426
  ```
397
427
 
428
+ ### 7.5 Scheduler Plugin (plugins/scheduler-plugin.js)
429
+
430
+ **功能**:
431
+ - 定时任务调度(Cron 表达式、相对时间、绝对时间)
432
+ - 支持 LLM 自动检测(简单提醒直接显示,需要处理的任务启用 LLM)
433
+ - 会话自动检测(自动发送到最近活跃的会话)
434
+ - 事件系统(供其他插件监听和响应)
435
+
436
+ **工具**:
437
+ - `schedule_task` - 创建定时任务
438
+ - `schedule_list` - 列出所有任务
439
+ - `schedule_cancel` - 取消任务
440
+ - `cron_examples` - Cron 表达式示例
441
+
442
+ **schedule_task 参数**:
443
+ ```javascript
444
+ {
445
+ name: '任务名称', // 可选
446
+ scheduleTime: '10 minutes', // 必填,支持多种格式
447
+ message: '提醒内容', // 必填
448
+ repeat: false, // 可选,是否重复
449
+ cronExpression: '*/5 * * * *', // 可选,Cron 表达式
450
+ sessionId: 'telegram_123', // 可选,会话 ID
451
+ llm: true // 可选,是否需要 LLM 处理(自动检测)
452
+ }
453
+ ```
454
+
455
+ **时间格式**:
456
+ | 格式 | 示例 | 说明 |
457
+ |------|------|------|
458
+ | 相对时间 | `10 seconds`, `5 minutes`, `1 hour`, `2 days` | 多久后执行 |
459
+ | 绝对时间 | `12:00`, `14:30:00` | 具体时间(次日开始) |
460
+ | Cron | `*/5 * * * *`, `0 9 * * *` | Cron 表达式 |
461
+
462
+ **自动 LLM 检测**:
463
+ - 简单提醒(喝水、吃饭)→ 直接显示
464
+ - 查询/分析任务(查看列表、分析数据)→ 启用 LLM
465
+
466
+ **事件**:
467
+ | 事件 | 时机 |
468
+ |------|------|
469
+ | `scheduler:task_created` | 任务创建时 |
470
+ | `scheduler:task_cancelled` | 任务取消时 |
471
+ | `scheduler:task_completed` | 任务完成时 |
472
+ | `scheduler:task_failed` | 任务失败时 |
473
+ | `scheduler:reminder` | 提醒触发时 |
474
+
398
475
  ## 八、使用示例
399
476
 
400
477
  ### 8.1 基础用法
@@ -491,7 +568,7 @@ class Framework {
491
568
  - [x] Session 管理 (`plugins/session-plugin.js`) - 多会话支持、历史记录
492
569
  - [x] Audit 审计日志 (`plugins/audit-plugin.js`) - 操作日志记录和查询
493
570
  - [x] Rules 规则引擎 (`plugins/rules-plugin.js`) - 权限控制、内容过滤
494
- - [x] Scheduler 定时任务 (`plugins/scheduler-plugin.js`) - Cron 调度
571
+ - [x] Scheduler 定时任务 (`plugins/scheduler-plugin.js`) - Cron 调度、事件系统、自动 LLM 检测
495
572
  - [x] Storage 存储 (`plugins/storage-plugin.js`) - 键值对持久化存储
496
573
  - [x] SubAgent 子Agent (`plugins/subagent-plugin.js`) - 子Agent隔离工具集
497
574
  - [x] Email 插件 (`plugins/email.js`) - 邮件收发
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.10",
3
+ "version": "1.0.13",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -106,25 +106,60 @@ class SchedulerPlugin extends Plugin {
106
106
  // 注册调度工具
107
107
  framework.registerTool({
108
108
  name: 'schedule_task',
109
- description: '设置定时提醒任务。支持多种时间格式:相对时间(1 minute, 2 hours)、具体时间(12:00)、Cron表达式(* * * * *)',
109
+ description: '设置定时提醒任务。支持多种时间格式:相对时间(1 minute, 2 hours)、具体时间(12:00)、Cron表达式(* * * * *)。系统会自动判断任务是否需要 LLM 处理。',
110
110
  inputSchema: z.object({
111
111
  name: z.string().optional().describe('任务名称'),
112
112
  scheduleTime: z.string().describe('执行时间。支持格式:\n- 相对时间: "1 minute", "2 hours", "1 day"\n- 具体时间: "12:00", "14:30"\n- Cron表达式: "*/5 * * * *" (每5分钟)'),
113
- message: z.string().describe('提醒消息内容'),
113
+ message: z.string().describe('提醒消息内容。系统会自动判断:\n- 简单提醒(喝水、吃饭)直接显示\n- 需要查询/分析的任务(查看列表、分析数据)自动启用 LLM'),
114
114
  repeat: z.boolean().optional().describe('是否重复执行 (默认 false)'),
115
- cronExpression: z.string().optional().describe('Cron 表达式 (当 repeat 为 true 时使用)')
115
+ cronExpression: z.string().optional().describe('Cron 表达式 (当 repeat 为 true 时使用)'),
116
+ sessionId: z.string().optional().describe('会话 ID(提醒将发送到该会话,不填则使用默认会话)'),
117
+ llm: z.boolean().optional().describe('是否需要 LLM 处理(自动检测,可手动覆盖)')
116
118
  }),
117
119
  execute: async (args) => {
118
120
  try {
119
- const { scheduleTime, message, repeat, cronExpression } = args
121
+ const { scheduleTime, message, repeat, cronExpression, sessionId } = args
120
122
  const agent = this._getAgent()
121
123
  if (!agent) {
122
124
  return { success: false, error: 'Agent not available' }
123
125
  }
124
126
 
127
+ // 如果没有指定 sessionId,自动获取当前活跃会话
128
+ let targetSessionId = sessionId
129
+ if (!targetSessionId) {
130
+ const sessionPlugin = this._framework.pluginManager.get('session')
131
+ if (sessionPlugin) {
132
+ const sessions = sessionPlugin.listSessions()
133
+ // 获取最近的活跃会话
134
+ if (sessions.length > 0) {
135
+ // 按 lastActive 排序,取最新的
136
+ sessions.sort((a, b) => {
137
+ const aTime = a.lastActive ? new Date(a.lastActive).getTime() : 0
138
+ const bTime = b.lastActive ? new Date(b.lastActive).getTime() : 0
139
+ return bTime - aTime
140
+ })
141
+ targetSessionId = sessions[0].id
142
+ console.log(`[Scheduler] Auto-detected active session: ${targetSessionId}`)
143
+ }
144
+ }
145
+ }
146
+
125
147
  const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
126
148
  let task
127
149
 
150
+ // 自动检测是否需要 LLM 处理
151
+ const LLM_KEYWORDS = [
152
+ '分析', '查询', '查看', '检查', '总结', '搜索', '获取',
153
+ 'list', 'get', 'check', 'search', 'find', 'fetch',
154
+ '什么', '如何', '为什么', '什么时候', '多少', '谁',
155
+ '今天', '明天', '昨天', '这周', '这月', '今年'
156
+ ]
157
+ const messageLower = message.toLowerCase()
158
+ const needsLLM = args.llm === true || LLM_KEYWORDS.some(kw =>
159
+ message.includes(kw) || messageLower.includes(kw.toLowerCase())
160
+ )
161
+ const llmMode = needsLLM
162
+
128
163
  // 检测是否像 Cron 表达式
129
164
  const isCron = /^[\d*,\/-\s]+$/.test(scheduleTime) && scheduleTime.split(' ').length >= 5
130
165
 
@@ -144,7 +179,9 @@ class SchedulerPlugin extends Plugin {
144
179
  lastRun: null,
145
180
  runCount: 0,
146
181
  timer: null,
147
- cronTask: null
182
+ cronTask: null,
183
+ sessionId: targetSessionId || null,
184
+ llm: llmMode
148
185
  }
149
186
 
150
187
  // 使用 node-cron 调度
@@ -164,7 +201,9 @@ class SchedulerPlugin extends Plugin {
164
201
  createdAt: new Date(),
165
202
  lastRun: null,
166
203
  runCount: 0,
167
- timer: null
204
+ timer: null,
205
+ sessionId: targetSessionId || null,
206
+ llm: llmMode
168
207
  }
169
208
  task.timer = setTimeout(async () => {
170
209
  await this._executeTask(task)
@@ -186,7 +225,9 @@ class SchedulerPlugin extends Plugin {
186
225
  createdAt: new Date(),
187
226
  lastRun: null,
188
227
  runCount: 0,
189
- timer: null
228
+ timer: null,
229
+ sessionId: targetSessionId || null,
230
+ llm: llmMode
190
231
  }
191
232
  task.timer = setTimeout(async () => {
192
233
  await this._executeTask(task)
@@ -196,6 +237,15 @@ class SchedulerPlugin extends Plugin {
196
237
  this._tasks.set(task.id, task)
197
238
  this._taskStats.total++
198
239
 
240
+ // 发送任务创建事件
241
+ this._framework.emit('scheduler:task_created', {
242
+ taskId: task.id,
243
+ taskName: task.name,
244
+ type: task.type,
245
+ scheduleTime,
246
+ cronExpression: task.cronExpression
247
+ })
248
+
199
249
  return {
200
250
  success: true,
201
251
  taskId: task.id,
@@ -203,7 +253,9 @@ class SchedulerPlugin extends Plugin {
203
253
  scheduleTime,
204
254
  executeAt: task.runAt ? task.runAt.toISOString() : null,
205
255
  cronExpression: task.cronExpression,
206
- message: repeat ? '定时任务已创建 (重复执行)' : '提醒已设置'
256
+ message: repeat ? '定时任务已创建 (重复执行)' : '提醒已设置',
257
+ sessionId: sessionId || 'default',
258
+ llm: llmMode
207
259
  }
208
260
  } catch (err) {
209
261
  return { success: false, error: err.message }
@@ -249,7 +301,15 @@ class SchedulerPlugin extends Plugin {
249
301
  return { success: false, error: 'Task not found' }
250
302
  }
251
303
 
304
+ const taskName = task.name
252
305
  this._cancelTask(task)
306
+
307
+ // 发送任务取消事件
308
+ this._framework.emit('scheduler:task_cancelled', {
309
+ taskId: args.taskId,
310
+ taskName
311
+ })
312
+
253
313
  return { success: true, cancelled: args.taskId }
254
314
  }
255
315
  })
@@ -322,37 +382,61 @@ class SchedulerPlugin extends Plugin {
322
382
  }
323
383
 
324
384
  /**
325
- * 执行任务 - 唤醒 Agent 发送消息
385
+ * 执行任务
326
386
  */
327
387
  async _executeTask(task) {
328
- const agent = this._getAgent()
329
- if (!agent) {
330
- console.error('[Scheduler] Agent not available')
331
- return
332
- }
333
-
334
388
  console.log(`[Scheduler] Executing task: ${task.name} (${task.id})`)
335
389
  console.log(`[Scheduler] Message: ${task.message}`)
390
+ if (task.sessionId) {
391
+ console.log(`[Scheduler] Target session: ${task.sessionId}`)
392
+ }
393
+ console.log(`[Scheduler] LLM mode: ${task.llm ? 'enabled' : 'disabled'}`)
336
394
 
337
395
  task.lastRun = new Date()
338
396
  task.runCount++
339
397
  this._taskStats.running++
340
398
 
341
399
  try {
342
- // 调用 Agent 的 chat 方法发送消息
343
- const result = await agent.chat(task.message, {
344
- isScheduledTask: true
345
- })
400
+ if (task.llm) {
401
+ // LLM 模式:调用 Agent 处理
402
+ const agent = this._getAgent()
403
+ if (!agent) {
404
+ console.error('[Scheduler] Agent not available')
405
+ return
406
+ }
346
407
 
347
- this._taskStats.completed++
408
+ const result = await agent.chat(task.message, {
409
+ isScheduledTask: true,
410
+ sessionId: task.sessionId
411
+ })
348
412
 
349
- // 输出结果
350
- if (result && result.message) {
351
- console.log(`\n🔔 [定时提醒] ${result.message}\n`)
352
- } else if (result && result.text) {
353
- console.log(`\n🔔 [定时提醒] ${result.text}\n`)
413
+ this._taskStats.completed++
414
+
415
+ if (result && result.message) {
416
+ console.log(`\n🔔 [定时提醒] ${result.message}\n`)
417
+ } else if (result && result.text) {
418
+ console.log(`\n🔔 [定时提醒] ${result.text}\n`)
419
+ }
354
420
  } else {
421
+ // 直接显示模式:只显示提醒,不发 LLM
355
422
  console.log(`\n🔔 [定时提醒] ${task.message}\n`)
423
+
424
+ // 发送事件,让其他插件(如 telegram)处理通知
425
+ this._framework.emit('scheduler:reminder', {
426
+ taskId: task.id,
427
+ taskName: task.name,
428
+ message: task.message,
429
+ sessionId: task.sessionId,
430
+ llm: task.llm
431
+ })
432
+
433
+ this._framework.emit('scheduler:task_completed', {
434
+ taskId: task.id,
435
+ taskName: task.name,
436
+ type: task.type
437
+ })
438
+
439
+ this._taskStats.completed++
356
440
  }
357
441
 
358
442
  // 一次性任务执行后清理
@@ -360,11 +444,18 @@ class SchedulerPlugin extends Plugin {
360
444
  this._cleanupTask(task.id)
361
445
  }
362
446
 
363
- return result
447
+ return { success: true }
364
448
  } catch (err) {
365
449
  this._taskStats.failed++
366
450
  console.error(`[Scheduler] Task ${task.name} failed: ${err.message}`)
367
451
 
452
+ // 发送任务失败事件
453
+ this._framework.emit('scheduler:task_failed', {
454
+ taskId: task.id,
455
+ taskName: task.name,
456
+ error: err.message
457
+ })
458
+
368
459
  // 一次性任务失败后也清理
369
460
  if (task.type === 'once' && !task.cronTask) {
370
461
  this._cleanupTask(task.id)
@@ -105,6 +105,12 @@ module.exports = function(Plugin) {
105
105
  this._framework.on('agent:created', (agent) => {
106
106
  console.log('[Telegram] New agent created:', agent.name)
107
107
  })
108
+
109
+ // 监听定时提醒事件
110
+ this._framework.on('scheduler:reminder', async (data) => {
111
+ console.log('[Telegram] Received scheduler reminder:', data)
112
+ await this._handleScheduledReminder(data)
113
+ })
108
114
  }
109
115
 
110
116
  } catch (err) {
@@ -112,6 +118,49 @@ module.exports = function(Plugin) {
112
118
  }
113
119
  }
114
120
 
121
+ /**
122
+ * 处理定时提醒
123
+ */
124
+ async _handleScheduledReminder(data) {
125
+ const { taskName, message, sessionId } = data
126
+
127
+ // 构建提醒消息
128
+ const reminderText = `🔔 [${taskName}]\n\n${message}`
129
+
130
+ // 如果有 sessionId 是 telegram 类型的,发送到对应 chat
131
+ if (sessionId && sessionId.startsWith('telegram_')) {
132
+ const chatId = sessionId.replace('telegram_', '')
133
+ try {
134
+ await this._bot.sendMessage(chatId, reminderText)
135
+ console.log(`[Telegram] Reminder sent to chat ${chatId}`)
136
+ } catch (err) {
137
+ console.error(`[Telegram] Failed to send reminder:`, err.message)
138
+ }
139
+ return
140
+ }
141
+
142
+ // 其他情况(包括 null 或其他 sessionId),发送到最近的 Telegram 会话
143
+ if (this._sessions.size > 0) {
144
+ // 按最后活跃时间排序
145
+ const sortedSessions = Array.from(this._sessions.entries())
146
+ .sort((a, b) => {
147
+ const aTime = a[1].lastActive ? new Date(a[1].lastActive).getTime() : 0
148
+ const bTime = b[1].lastActive ? new Date(b[1].lastActive).getTime() : 0
149
+ return bTime - aTime
150
+ })
151
+
152
+ const [chatId] = sortedSessions[0]
153
+ try {
154
+ await this._bot.sendMessage(chatId, reminderText)
155
+ console.log(`[Telegram] Reminder sent to chat ${chatId}`)
156
+ } catch (err) {
157
+ console.error(`[Telegram] Failed to send reminder to ${chatId}:`, err.message)
158
+ }
159
+ } else {
160
+ console.log('[Telegram] No active Telegram sessions to send reminder')
161
+ }
162
+ }
163
+
115
164
  /**
116
165
  * 获取主Agent
117
166
  */