foliko 1.0.0

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 (54) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/22.txt +10 -0
  3. package/README.md +218 -0
  4. package/SPEC.md +452 -0
  5. package/cli/bin/foliko.js +12 -0
  6. package/cli/src/commands/chat.js +75 -0
  7. package/cli/src/index.js +64 -0
  8. package/cli/src/ui/chat-ui.js +272 -0
  9. package/cli/src/utils/ansi.js +40 -0
  10. package/cli/src/utils/markdown.js +296 -0
  11. package/docs/quick-reference.md +131 -0
  12. package/docs/user-manual.md +1205 -0
  13. package/examples/basic.js +110 -0
  14. package/examples/bootstrap.js +93 -0
  15. package/examples/mcp-example.js +53 -0
  16. package/examples/skill-example.js +49 -0
  17. package/examples/workflow.js +158 -0
  18. package/package.json +36 -0
  19. package/plugins/ai-plugin.js +89 -0
  20. package/plugins/audit-plugin.js +187 -0
  21. package/plugins/default-plugins.js +412 -0
  22. package/plugins/file-system-plugin.js +344 -0
  23. package/plugins/install-plugin.js +93 -0
  24. package/plugins/python-executor-plugin.js +331 -0
  25. package/plugins/rules-plugin.js +292 -0
  26. package/plugins/scheduler-plugin.js +426 -0
  27. package/plugins/session-plugin.js +343 -0
  28. package/plugins/shell-executor-plugin.js +196 -0
  29. package/plugins/storage-plugin.js +237 -0
  30. package/plugins/subagent-plugin.js +395 -0
  31. package/plugins/think-plugin.js +329 -0
  32. package/plugins/tools-plugin.js +114 -0
  33. package/skills/mcp-usage/SKILL.md +198 -0
  34. package/skills/vb-agent-dev/AGENTS.md +162 -0
  35. package/skills/vb-agent-dev/SKILL.md +370 -0
  36. package/src/capabilities/index.js +11 -0
  37. package/src/capabilities/skill-manager.js +319 -0
  38. package/src/capabilities/workflow-engine.js +401 -0
  39. package/src/core/agent-chat.js +311 -0
  40. package/src/core/agent.js +573 -0
  41. package/src/core/framework.js +255 -0
  42. package/src/core/index.js +19 -0
  43. package/src/core/plugin-base.js +205 -0
  44. package/src/core/plugin-manager.js +392 -0
  45. package/src/core/provider.js +108 -0
  46. package/src/core/tool-registry.js +134 -0
  47. package/src/core/tool-router.js +216 -0
  48. package/src/executors/executor-base.js +58 -0
  49. package/src/executors/mcp-executor.js +728 -0
  50. package/src/index.js +37 -0
  51. package/src/utils/event-emitter.js +97 -0
  52. package/test-chat.js +129 -0
  53. package/test-mcp.js +79 -0
  54. package/test-reload.js +61 -0
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Scheduler 定时任务调度插件
3
+ * 支持 Cron 表达式、绝对时间、相对时间调度
4
+ * 任务触发时自动唤醒 Agent 发送消息
5
+ */
6
+
7
+ const { Plugin } = require('../src/core/plugin-base')
8
+ const { z } = require('zod')
9
+
10
+ // 尝试加载 node-cron
11
+ let cron = null
12
+ try {
13
+ cron = require('node-cron')
14
+ } catch (e) {
15
+ console.warn('[Scheduler] node-cron not installed, cron tasks will not work')
16
+ }
17
+
18
+ // 时间解析辅助函数
19
+ function parseDelay(delayStr) {
20
+ const match = delayStr.match(/^(\d+)\s*(second|minute|hour|day|week)s?$/i)
21
+ if (!match) return null
22
+ const value = parseInt(match[1])
23
+ const unit = match[2].toLowerCase()
24
+ const multipliers = {
25
+ second: 1000,
26
+ minute: 60 * 1000,
27
+ hour: 60 * 60 * 1000,
28
+ day: 24 * 60 * 60 * 1000,
29
+ week: 7 * 24 * 60 * 60 * 1000
30
+ }
31
+ return value * (multipliers[unit] || 1000)
32
+ }
33
+
34
+ function parseAtTime(timeStr) {
35
+ const now = new Date()
36
+ // 简单时间格式 "12:00"
37
+ const timeMatch = timeStr.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/)
38
+ if (timeMatch) {
39
+ const date = new Date(now)
40
+ date.setHours(parseInt(timeMatch[1]), parseInt(timeMatch[2]), parseInt(timeMatch[3] || 0), 0)
41
+ if (date <= now) {
42
+ date.setDate(date.getDate() + 1)
43
+ }
44
+ return date
45
+ }
46
+ return new Date(timeStr)
47
+ }
48
+
49
+ /**
50
+ * 生成唯一 ID
51
+ */
52
+ function generateId() {
53
+ if (require('crypto').randomUUID) {
54
+ return require('crypto').randomUUID()
55
+ }
56
+ return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
57
+ }
58
+
59
+ class SchedulerPlugin extends Plugin {
60
+ constructor(config = {}) {
61
+ super()
62
+ this.name = 'scheduler'
63
+ this.version = '1.0.0'
64
+ this.description = '定时任务调度插件,支持 Cron 表达式、绝对时间、相对时间'
65
+ this.priority = 15
66
+
67
+ this.config = {
68
+ checkInterval: config.checkInterval || 1000 // 每秒检查一次
69
+ }
70
+
71
+ this._framework = null
72
+ this._agent = null
73
+ this._tasks = new Map()
74
+ this._timer = null
75
+ this._taskStats = {
76
+ total: 0,
77
+ running: 0,
78
+ completed: 0,
79
+ failed: 0
80
+ }
81
+ }
82
+
83
+ install(framework) {
84
+ this._framework = framework
85
+ return this
86
+ }
87
+
88
+ /**
89
+ * 获取 Agent 实例
90
+ */
91
+ _getAgent() {
92
+ if (this._agent) return this._agent
93
+ if (this._framework._mainAgent) {
94
+ this._agent = this._framework._mainAgent
95
+ } else {
96
+ const agents = this._framework._agents || []
97
+ this._agent = agents.length > 0 ? agents[agents.length - 1] : null
98
+ }
99
+ return this._agent
100
+ }
101
+
102
+ start(framework) {
103
+ // 获取 Agent 实例
104
+ this._agent = this._getAgent()
105
+
106
+ // 注册调度工具
107
+ framework.registerTool({
108
+ name: 'schedule_task',
109
+ description: '设置定时提醒任务。支持多种时间格式:相对时间(1 minute, 2 hours)、具体时间(12:00)、Cron表达式(* * * * *)',
110
+ inputSchema: z.object({
111
+ name: z.string().optional().describe('任务名称'),
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('提醒消息内容'),
114
+ repeat: z.boolean().optional().describe('是否重复执行 (默认 false)'),
115
+ cronExpression: z.string().optional().describe('Cron 表达式 (当 repeat 为 true 时使用)')
116
+ }),
117
+ execute: async (args) => {
118
+ try {
119
+ const { scheduleTime, message, repeat, cronExpression } = args
120
+ const agent = this._getAgent()
121
+ if (!agent) {
122
+ return { success: false, error: 'Agent not available' }
123
+ }
124
+
125
+ const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
126
+ let task
127
+
128
+ // 检测是否像 Cron 表达式
129
+ const isCron = /^[\d*,\/-\s]+$/.test(scheduleTime) && scheduleTime.split(' ').length >= 5
130
+
131
+ if (isCron || repeat) {
132
+ // Cron 任务
133
+ if (!cron) {
134
+ return { success: false, error: 'node-cron not installed' }
135
+ }
136
+ task = {
137
+ id: taskId,
138
+ name: args.name || 'CronTask',
139
+ type: 'cron',
140
+ cronExpression: cronExpression || scheduleTime,
141
+ message,
142
+ enabled: true,
143
+ createdAt: new Date(),
144
+ lastRun: null,
145
+ runCount: 0,
146
+ timer: null,
147
+ cronTask: null
148
+ }
149
+
150
+ // 使用 node-cron 调度
151
+ task.cronTask = cron.schedule(task.cronExpression, async () => {
152
+ await this._executeTask(task)
153
+ })
154
+ } else if (scheduleTime.includes(':')) {
155
+ // 具体时间
156
+ const runAt = parseAtTime(scheduleTime)
157
+ task = {
158
+ id: taskId,
159
+ name: args.name || 'Reminder',
160
+ type: 'once',
161
+ runAt,
162
+ message,
163
+ enabled: true,
164
+ createdAt: new Date(),
165
+ lastRun: null,
166
+ runCount: 0,
167
+ timer: null
168
+ }
169
+ task.timer = setTimeout(async () => {
170
+ await this._executeTask(task)
171
+ }, runAt.getTime() - Date.now())
172
+ } else {
173
+ // 相对时间
174
+ const delayMs = parseDelay(scheduleTime)
175
+ if (!delayMs) {
176
+ return { success: false, error: '无效的时间格式' }
177
+ }
178
+ const runAt = new Date(Date.now() + delayMs)
179
+ task = {
180
+ id: taskId,
181
+ name: args.name || 'Reminder',
182
+ type: 'once',
183
+ runAt,
184
+ message,
185
+ enabled: true,
186
+ createdAt: new Date(),
187
+ lastRun: null,
188
+ runCount: 0,
189
+ timer: null
190
+ }
191
+ task.timer = setTimeout(async () => {
192
+ await this._executeTask(task)
193
+ }, delayMs)
194
+ }
195
+
196
+ this._tasks.set(task.id, task)
197
+ this._taskStats.total++
198
+
199
+ return {
200
+ success: true,
201
+ taskId: task.id,
202
+ name: task.name,
203
+ scheduleTime,
204
+ executeAt: task.runAt ? task.runAt.toISOString() : null,
205
+ cronExpression: task.cronExpression,
206
+ message: repeat ? '定时任务已创建 (重复执行)' : '提醒已设置'
207
+ }
208
+ } catch (err) {
209
+ return { success: false, error: err.message }
210
+ }
211
+ }
212
+ })
213
+
214
+ framework.registerTool({
215
+ name: 'schedule_list',
216
+ description: '列出所有定时任务',
217
+ inputSchema: z.object({}),
218
+ execute: async () => {
219
+ const tasks = Array.from(this._tasks.values()).map(t => ({
220
+ id: t.id,
221
+ name: t.name,
222
+ type: t.type,
223
+ message: t.message,
224
+ enabled: t.enabled,
225
+ nextRun: t.nextRun || t.runAt,
226
+ runCount: t.runCount,
227
+ lastRun: t.lastRun,
228
+ cronExpression: t.cronExpression
229
+ }))
230
+
231
+ return {
232
+ success: true,
233
+ tasks,
234
+ total: tasks.length,
235
+ stats: { ...this._taskStats }
236
+ }
237
+ }
238
+ })
239
+
240
+ framework.registerTool({
241
+ name: 'schedule_cancel',
242
+ description: '取消定时任务',
243
+ inputSchema: z.object({
244
+ taskId: z.string().describe('任务 ID')
245
+ }),
246
+ execute: async (args) => {
247
+ const task = this._tasks.get(args.taskId)
248
+ if (!task) {
249
+ return { success: false, error: 'Task not found' }
250
+ }
251
+
252
+ this._cancelTask(task)
253
+ return { success: true, cancelled: args.taskId }
254
+ }
255
+ })
256
+
257
+ framework.registerTool({
258
+ name: 'cron_examples',
259
+ description: '获取常用 Cron 表达式示例',
260
+ inputSchema: z.object({}),
261
+ execute: async () => {
262
+ return {
263
+ examples: [
264
+ { expression: '* * * * *', description: '每分钟' },
265
+ { expression: '*/5 * * * *', description: '每5分钟' },
266
+ { expression: '*/15 * * * *', description: '每15分钟' },
267
+ { expression: '0 * * * *', description: '每小时' },
268
+ { expression: '0 9 * * *', description: '每天早上9点' },
269
+ { expression: '0 12 * * *', description: '每天中午12点' },
270
+ { expression: '0 18 * * *', description: '每天下午6点' },
271
+ { expression: '0 9 * * 1-5', description: '工作日上午9点' },
272
+ { expression: '0 9 * * 0,6', description: '周末上午9点' },
273
+ { expression: '0 9 * * 1', description: '每周一上午9点' },
274
+ { expression: '0 */2 * * *', description: '每2小时' }
275
+ ]
276
+ }
277
+ }
278
+ })
279
+
280
+ // 启动调度循环(用于检查一次性任务)
281
+ this._startScheduler()
282
+
283
+ return this
284
+ }
285
+
286
+ /**
287
+ * 启动调度循环
288
+ */
289
+ _startScheduler() {
290
+ if (this._timer) {
291
+ clearInterval(this._timer)
292
+ }
293
+
294
+ this._timer = setInterval(() => {
295
+ this._checkTasks()
296
+ }, this.config.checkInterval)
297
+ }
298
+
299
+ /**
300
+ * 检查任务状态(主要用于更新 stats)
301
+ */
302
+ _checkTasks() {
303
+ for (const [id, task] of this._tasks) {
304
+ if (!task.enabled) continue
305
+ // 统计信息更新
306
+ }
307
+ }
308
+
309
+ /**
310
+ * 取消任务
311
+ */
312
+ _cancelTask(task) {
313
+ if (task.timer) {
314
+ clearTimeout(task.timer)
315
+ task.timer = null
316
+ }
317
+ if (task.cronTask) {
318
+ task.cronTask.stop()
319
+ task.cronTask = null
320
+ }
321
+ task.enabled = false
322
+ }
323
+
324
+ /**
325
+ * 执行任务 - 唤醒 Agent 发送消息
326
+ */
327
+ async _executeTask(task) {
328
+ const agent = this._getAgent()
329
+ if (!agent) {
330
+ console.error('[Scheduler] Agent not available')
331
+ return
332
+ }
333
+
334
+ console.log(`[Scheduler] Executing task: ${task.name} (${task.id})`)
335
+ console.log(`[Scheduler] Message: ${task.message}`)
336
+
337
+ task.lastRun = new Date()
338
+ task.runCount++
339
+ this._taskStats.running++
340
+
341
+ try {
342
+ // 调用 Agent 的 chat 方法发送消息
343
+ const result = await agent.chat(task.message, {
344
+ isScheduledTask: true
345
+ })
346
+
347
+ this._taskStats.completed++
348
+
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`)
354
+ } else {
355
+ console.log(`\n🔔 [定时提醒] ${task.message}\n`)
356
+ }
357
+
358
+ // 一次性任务执行后清理
359
+ if (task.type === 'once' && !task.cronTask) {
360
+ this._cleanupTask(task.id)
361
+ }
362
+
363
+ return result
364
+ } catch (err) {
365
+ this._taskStats.failed++
366
+ console.error(`[Scheduler] Task ${task.name} failed: ${err.message}`)
367
+
368
+ // 一次性任务失败后也清理
369
+ if (task.type === 'once' && !task.cronTask) {
370
+ this._cleanupTask(task.id)
371
+ }
372
+
373
+ return { error: err.message }
374
+ } finally {
375
+ this._taskStats.running--
376
+ }
377
+ }
378
+
379
+ /**
380
+ * 清理任务
381
+ */
382
+ _cleanupTask(taskId) {
383
+ const task = this._tasks.get(taskId)
384
+ if (task) {
385
+ if (task.timer) {
386
+ clearTimeout(task.timer)
387
+ task.timer = null
388
+ }
389
+ if (task.cronTask) {
390
+ task.cronTask.stop()
391
+ task.cronTask = null
392
+ }
393
+ this._tasks.delete(taskId)
394
+ }
395
+ }
396
+
397
+ /**
398
+ * 停止所有任务
399
+ */
400
+ stopAll() {
401
+ for (const task of this._tasks.values()) {
402
+ this._cancelTask(task)
403
+ }
404
+
405
+ if (this._timer) {
406
+ clearInterval(this._timer)
407
+ this._timer = null
408
+ }
409
+ }
410
+
411
+ reload(framework) {
412
+ this._framework = framework
413
+ this._agent = this._getAgent()
414
+ this._startScheduler()
415
+ }
416
+
417
+ uninstall(framework) {
418
+ this.stopAll()
419
+ this._tasks.clear()
420
+ this._taskStats = { total: 0, running: 0, completed: 0, failed: 0 }
421
+ this._framework = null
422
+ this._agent = null
423
+ }
424
+ }
425
+
426
+ module.exports = { SchedulerPlugin }