foliko 1.0.38 → 1.0.40

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.
@@ -80,7 +80,9 @@
80
80
  "Bash(node -c plugins/telegram-plugin.js 2>&1)",
81
81
  "Bash(node -c src/capabilities/skill-manager.js 2>&1)",
82
82
  "Bash(node -c plugins/feishu-plugin.js 2>&1)",
83
- "Bash(node debug-skills.js)"
83
+ "Bash(node debug-skills.js)",
84
+ "Bash(node -c src/capabilities/workflow-engine.js 2>&1)",
85
+ "Bash(node -c skills/workflow-guide/SKILL.md 2>&1 || head -10 skills/workflow-guide/SKILL.md)"
84
86
  ]
85
87
  }
86
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: skill-guide
3
+ description: 技能安装与开发指南。当用户询问"如何添加技能"、"自定义技能"、"安装技能"时立即调用。
4
+ allowed-tools: Read, Write, Edit, Glob, Grep, Bash
5
+ ---
6
+
7
+ # 技能(Skill)安装与开发指南
8
+
9
+ ## 概述
10
+
11
+ 技能(Skill)是 Agent 的扩展能力模块,存放在 `.agent/skills/` 目录下。
12
+
13
+ ## 技能存放位置
14
+
15
+ ```
16
+ 项目目录/
17
+ └── .agent/
18
+ └── skills/
19
+ └── my-skill/ # 技能文件夹
20
+ ├── SKILL.md # 必需,技能定义文件
21
+ └── ... # 其他资源文件(可选)
22
+ ```
23
+
24
+ ## SKILL.md 格式
25
+
26
+ 每个技能必须包含 `SKILL.md` 文件,使用 YAML frontmatter 定义元数据:
27
+
28
+ ```yaml
29
+ ---
30
+ name: my-skill # 技能名称(必需,唯一标识)
31
+ description: 这是一个自定义技能的描述。当用户说"..."时调用此技能。(必需)
32
+ allowed-tools: Read, Write, Edit, Glob, Grep, Bash # 可选,允许使用的工具列表
33
+ license: MIT # 可选,许可证
34
+ compatibility: v1.0.0 # 可选,兼容版本
35
+ ---
36
+
37
+ # 技能标题
38
+
39
+ 这里是技能的详细说明内容,可以使用 Markdown 格式。
40
+ ```
41
+
42
+ ### frontmatter 字段说明
43
+
44
+ | 字段 | 必需 | 说明 |
45
+ |------|------|------|
46
+ | `name` | 是 | 技能唯一标识,字母、数字、下划线、横杠,1-64字符 |
47
+ | `description` | 是 | 技能描述,当用户意图匹配时会被调用 |
48
+ | `allowed-tools` | 否 | 允许使用的工具列表,逗号分隔 |
49
+ | `license` | 否 | 许可证类型 |
50
+ | `compatibility` | 否 | 兼容的框架版本 |
51
+
52
+ ### 正文格式
53
+
54
+ frontmatter 之后是技能的正文内容,支持 Markdown 格式,包含:
55
+ - 详细的功能说明
56
+ - 使用示例
57
+ - 最佳实践
58
+ - 注意事项
59
+
60
+ ## 创建自定义技能
61
+
62
+ 1. 在 `.agent/skills/` 下创建技能文件夹
63
+ 2. 创建 `SKILL.md` 文件,包含完整的 frontmatter
64
+ 3. 在正文中详细描述技能的功能和使用方法
65
+ 4. 调用 `reloadSkills` 工具重载所有技能
66
+
67
+ ## 技能重载
68
+
69
+ 创建或修改技能后,需要调用 `reloadSkills` 工具重载:
70
+
71
+ ```json
72
+ {
73
+ "tool": "reloadSkills",
74
+ "args": {}
75
+ }
76
+ ```
77
+
78
+ ## 示例:创建天气查询技能
79
+
80
+ ```
81
+ .agent/skills/weather/
82
+ └── SKILL.md
83
+ ```
84
+
85
+ SKILL.md 内容:
86
+
87
+ ```yaml
88
+ ---
89
+ name: weather
90
+ description: 查询天气信息。当用户说"天气怎么样"、"今天多少度"时调用。
91
+ allowed-tools: Bash, Read
92
+ ---
93
+
94
+ # 天气查询技能
95
+
96
+ ## 功能
97
+
98
+ 查询指定城市的天气信息。
99
+
100
+ ## 使用方式
101
+
102
+ 用户发送 "北京天气" 或 "今天上海多少度"
103
+
104
+ ## 注意事项
105
+
106
+ - 使用免费的天气 API
107
+ - 响应需要简洁明了
108
+ ```
@@ -0,0 +1,263 @@
1
+ ---
2
+ name: workflow-guide
3
+ description: 工作流与多步任务指南。当用户说"创建工作流"、"多步任务"、"自动化流程"时立即调用。
4
+ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWorkflows
5
+ ---
6
+
7
+ # 工作流(Workflow)开发指南
8
+
9
+ ## 概述
10
+
11
+ 工作流引擎用于处理多步骤任务,通过 `execute_workflow` 工具执行。工作流由多个步骤组成,支持脚本、条件分支、循环、延时等类型。
12
+
13
+ ## 工作流存放位置
14
+
15
+ ```
16
+ 项目目录/
17
+ └── .agent/
18
+ └── workflows/ # 工作流定义目录
19
+ └── my-workflow.json # 工作流 JSON 定义
20
+ └── another.js # 或 JS 文件(导出 default 工作流)
21
+ ```
22
+
23
+ ## 自动加载与注册
24
+
25
+ 将工作流文件放入 `.agent/workflows/` 目录后,系统会自动:
26
+ 1. 加载所有 `.json` 和 `.js` 文件
27
+ 2. 将工作流注册为 `workflow_<文件名>` 工具
28
+
29
+ 例如 `my-workflow.json` 会注册为 `workflow_my_workflow` 工具,可直接调用。
30
+
31
+ ## 重载工作流
32
+
33
+ 创建或修改工作流后,调用 `reloadWorkflows` 重载:
34
+
35
+ ```json
36
+ {
37
+ "tool": "reloadWorkflows",
38
+ "args": {}
39
+ }
40
+ ```
41
+
42
+ ## 使用方式
43
+
44
+ ### 方式一:直接执行(任意工作流)
45
+ ```json
46
+ {
47
+ "tool": "execute_workflow",
48
+ "args": {
49
+ "workflow": "<工作流 JSON 字符串>",
50
+ "input": { "变量名": "值" }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### 方式二:执行已注册的工作流工具
56
+ ```json
57
+ {
58
+ "tool": "workflow_my_workflow",
59
+ "args": {
60
+ "input": { "变量名": "值" }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## 工作流步骤类型
66
+
67
+ ### 1. script - 脚本步骤
68
+
69
+ 执行 JavaScript 脚本:
70
+
71
+ ```json
72
+ {
73
+ "type": "script",
74
+ "name": "步骤名称",
75
+ "outputVariable": "result",
76
+ "script": "context.variables.x = 10; return context.variables.x;"
77
+ }
78
+ ```
79
+
80
+ - `outputVariable`: 可选,将返回值存入上下文变量
81
+ - `context.variables`: 所有步骤共享的变量对象
82
+ - `context.stepResults`: 上一步的输出结果
83
+
84
+ ### 2. loop - 循环步骤
85
+
86
+ 重复执行一组步骤:
87
+
88
+ ```json
89
+ {
90
+ "type": "loop",
91
+ "name": "循环3次",
92
+ "maxIterations": 3,
93
+ "loopVariable": "i",
94
+ "steps": [
95
+ { "type": "script", "name": "执行内容", "script": "..." }
96
+ ]
97
+ }
98
+ ```
99
+
100
+ - `maxIterations`: 最大迭代次数
101
+ - `loopVariable`: 循环计数器变量名(从 0 开始)
102
+
103
+ ### 3. condition - 条件分支
104
+
105
+ 根据条件选择执行分支:
106
+
107
+ ```json
108
+ {
109
+ "type": "condition",
110
+ "name": "判断条件",
111
+ "branches": [
112
+ {
113
+ "name": "条件1",
114
+ "condition": "context.variables.value > 10",
115
+ "steps": [...]
116
+ },
117
+ {
118
+ "name": "条件2",
119
+ "condition": "context.variables.value <= 10",
120
+ "steps": [...]
121
+ }
122
+ ]
123
+ }
124
+ ```
125
+
126
+ ### 4. delay - 延时步骤
127
+
128
+ 等待指定毫秒数:
129
+
130
+ ```json
131
+ {
132
+ "type": "delay",
133
+ "name": "等待1秒",
134
+ "delayMs": 1000
135
+ }
136
+ ```
137
+
138
+ ### 5. parallel - 并行步骤(可选)
139
+
140
+ 并行执行多个步骤:
141
+
142
+ ```json
143
+ {
144
+ "type": "parallel",
145
+ "name": "并行执行",
146
+ "steps": [...]
147
+ }
148
+ ```
149
+
150
+ ## 完整示例
151
+
152
+ ### 示例:用户问候与天气查询工作流
153
+
154
+ ```json
155
+ {
156
+ "name": "greet-and-query-weather",
157
+ "description": "问候用户并查询天气",
158
+ "steps": [
159
+ {
160
+ "type": "script",
161
+ "name": "获取用户名",
162
+ "outputVariable": "username",
163
+ "script": "context.variables.username = context.input.username || '用户'; return context.variables.username;"
164
+ },
165
+ {
166
+ "type": "script",
167
+ "name": "生成问候语",
168
+ "outputVariable": "greeting",
169
+ "script": "return '你好,' + context.variables.username + '!';"
170
+ },
171
+ {
172
+ "type": "condition",
173
+ "name": "检查是否查询天气",
174
+ "branches": [
175
+ {
176
+ "name": "需要查询天气",
177
+ "condition": "context.input.queryWeather === true",
178
+ "steps": [
179
+ {
180
+ "type": "script",
181
+ "name": "模拟天气查询",
182
+ "outputVariable": "weather",
183
+ "script": "return '今天天气晴,温度 25°C';"
184
+ }
185
+ ]
186
+ },
187
+ {
188
+ "name": "不需要查询天气",
189
+ "condition": "true",
190
+ "steps": [
191
+ {
192
+ "type": "script",
193
+ "name": "跳过天气",
194
+ "outputVariable": "weather",
195
+ "script": "return '好的,有需要随时叫我!';"
196
+ }
197
+ ]
198
+ }
199
+ ]
200
+ }
201
+ ]
202
+ }
203
+ ```
204
+
205
+ 调用:
206
+
207
+ ```json
208
+ {
209
+ "tool": "execute_workflow",
210
+ "args": {
211
+ "workflow": "<上面 JSON 转字符串>",
212
+ "input": { "username": "张三", "queryWeather": true }
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### 示例:循环处理任务列表
218
+
219
+ ```json
220
+ {
221
+ "name": "process-items",
222
+ "description": "循环处理列表中的每个项目",
223
+ "steps": [
224
+ {
225
+ "type": "script",
226
+ "name": "初始化列表",
227
+ "outputVariable": "items",
228
+ "script": "context.variables.items = ['任务A', '任务B', '任务C']; return context.variables.items;"
229
+ },
230
+ {
231
+ "type": "loop",
232
+ "name": "处理每个任务",
233
+ "maxIterations": 3,
234
+ "loopVariable": "i",
235
+ "steps": [
236
+ {
237
+ "type": "script",
238
+ "name": "处理任务",
239
+ "outputVariable": "processed",
240
+ "script": "const item = context.variables.items[context.variables.i]; context.variables.processed = (context.variables.processed || []); context.variables.processed.push(item + '_完成'); return item;"
241
+ }
242
+ ]
243
+ }
244
+ ]
245
+ }
246
+ ```
247
+
248
+ ## 注意事项
249
+
250
+ 1. **context.variables** 在所有步骤间共享,可存储中间结果
251
+ 2. **context.input** 是工作流的输入参数
252
+ 3. **context.stepResults** 包含前面各步骤的输出
253
+ 4. 脚本中使用 `return` 值会传递给下一步
254
+ 5. 循环变量 `i` 从 0 开始计数
255
+ 6. 条件分支的 `condition` 是 JavaScript 表达式字符串
256
+
257
+ ## 最佳实践
258
+
259
+ 1. 为每个步骤设置有意义的 `name`
260
+ 2. 使用 `outputVariable` 保存需要跨步骤共享的数据
261
+ 3. 循环前确保有合理的 `maxIterations` 限制
262
+ 4. 条件分支要有兜底的 `condition: "true"` 分支
263
+ 5. 复杂的业务逻辑优先使用脚本步骤
@@ -133,7 +133,7 @@ class SkillManagerPlugin extends Plugin {
133
133
  this.priority = 5
134
134
 
135
135
  this._framework = null
136
- this._skillsDirs = Array.isArray(config.skillsDirs) ? config.skillsDirs : [config.skillsDir || 'skills']
136
+ this._skillsDirs = Array.isArray(config.skillsDirs) ? config.skillsDirs : [config.skillsDir || '.agent/skills', 'skills']
137
137
  this._skills = new Map()
138
138
  this._loaded = false
139
139
  }
@@ -168,6 +168,21 @@ class SkillManagerPlugin extends Plugin {
168
168
  }
169
169
  })
170
170
 
171
+ // 注册 reloadSkills 工具
172
+ framework.registerTool({
173
+ name: 'reloadSkills',
174
+ description: '重载所有技能,当用户添加新技能或修改技能后调用此工具',
175
+ inputSchema: z.object({}),
176
+ execute: async () => {
177
+ this.reload(this._framework)
178
+ return {
179
+ success: true,
180
+ message: `Skills reloaded. Total: ${this._skills.size}`,
181
+ skills: Array.from(this._skills.keys())
182
+ }
183
+ }
184
+ })
185
+
171
186
  return this
172
187
  }
173
188
 
@@ -6,6 +6,8 @@
6
6
  const { EventEmitter } = require('../utils/event-emitter')
7
7
  const { Plugin } = require('../core/plugin-base')
8
8
  const { z } = require('zod')
9
+ const fs = require('fs')
10
+ const path = require('path')
9
11
 
10
12
  /**
11
13
  * 工作流步骤类型
@@ -298,6 +300,9 @@ class WorkflowPlugin extends Plugin {
298
300
 
299
301
  this._framework = null
300
302
  this._engine = null
303
+ this._workflowsDir = config.workflowsDir || '.agent/workflows'
304
+ this._workflows = new Map()
305
+ this._workflowTools = new Map()
301
306
  }
302
307
 
303
308
  install(framework) {
@@ -328,9 +333,124 @@ class WorkflowPlugin extends Plugin {
328
333
  }
329
334
 
330
335
  start(framework) {
336
+ this._loadWorkflows()
337
+ this._registerWorkflowTools()
338
+
339
+ // 注册 reloadWorkflows 工具
340
+ framework.registerTool({
341
+ name: 'reloadWorkflows',
342
+ description: '重载所有工作流,当用户添加或修改工作流后调用此工具',
343
+ inputSchema: z.object({}),
344
+ execute: async () => {
345
+ this.reload(this._framework)
346
+ return {
347
+ success: true,
348
+ message: `Workflows reloaded. Total: ${this._workflows.size}`,
349
+ workflows: Array.from(this._workflows.keys())
350
+ }
351
+ }
352
+ })
353
+
331
354
  return this
332
355
  }
333
356
 
357
+ /**
358
+ * 加载目录中的工作流定义
359
+ */
360
+ _loadWorkflows() {
361
+ const dir = path.resolve(process.cwd(), this._workflowsDir)
362
+ if (!fs.existsSync(dir)) {
363
+ fs.mkdirSync(dir, { recursive: true })
364
+ console.log(`[Workflow] Created workflows directory: ${dir}`)
365
+ return
366
+ }
367
+
368
+ try {
369
+ const files = fs.readdirSync(dir)
370
+ for (const file of files) {
371
+ if (!file.endsWith('.json') && !file.endsWith('.js')) continue
372
+
373
+ const filePath = path.join(dir, file)
374
+ const workflowName = path.basename(file, path.extname(file))
375
+
376
+ // 跳过已存在的工作流
377
+ if (this._workflows.has(workflowName)) continue
378
+
379
+ try {
380
+ const content = fs.readFileSync(filePath, 'utf-8')
381
+ let workflowDef
382
+
383
+ if (file.endsWith('.js')) {
384
+ // 执行 JS 文件获取工作流定义
385
+ // eslint-disable-next-line no-new-func
386
+ const fn = new Function('module', 'exports', 'require', 'process', 'console', '__dirname', '__filename', content)
387
+ const module = { exports: {} }
388
+ fn(module, module.exports, require, process, console, path.dirname(filePath), filePath)
389
+ workflowDef = module.exports.default || module.exports
390
+ } else {
391
+ workflowDef = JSON.parse(content)
392
+ }
393
+
394
+ if (workflowDef && workflowDef.steps) {
395
+ this._workflows.set(workflowName, workflowDef)
396
+ console.log(`[Workflow] Loaded: ${workflowName}`)
397
+ }
398
+ } catch (err) {
399
+ console.error(`[Workflow] Failed to load ${file}:`, err.message)
400
+ }
401
+ }
402
+ } catch (err) {
403
+ console.error('[Workflow] Failed to read workflows directory:', err.message)
404
+ }
405
+ }
406
+
407
+ /**
408
+ * 注册工作流为工具
409
+ */
410
+ _registerWorkflowTools() {
411
+ for (const [name, workflow] of this._workflows) {
412
+ if (this._workflowTools.has(name)) continue
413
+
414
+ const toolName = `workflow_${name}`
415
+ const description = workflow.description || `执行工作流: ${name}`
416
+
417
+ this._framework.registerTool({
418
+ name: toolName,
419
+ description,
420
+ inputSchema: z.object({
421
+ input: z.object({}).optional().describe('工作流输入参数')
422
+ }),
423
+ execute: async (args) => {
424
+ return await this.executeWorkflow(workflow, args.input || {})
425
+ }
426
+ })
427
+
428
+ this._workflowTools.set(name, toolName)
429
+ console.log(`[Workflow] Registered tool: ${toolName}`)
430
+ }
431
+ }
432
+
433
+ /**
434
+ * 重载工作流
435
+ */
436
+ reload(framework) {
437
+ console.log('[Workflow] Reloading...')
438
+ this._framework = framework
439
+
440
+ // 清除已注册的工具
441
+ for (const toolName of this._workflowTools.values()) {
442
+ // 工具注销需要框架支持,这里只清理内部状态
443
+ }
444
+ this._workflowTools.clear()
445
+ this._workflows.clear()
446
+
447
+ // 重新加载
448
+ this._loadWorkflows()
449
+ this._registerWorkflowTools()
450
+
451
+ console.log(`[Workflow] Reloaded. Total workflows: ${this._workflows.size}`)
452
+ }
453
+
334
454
  /**
335
455
  * 执行工作流
336
456
  */
@@ -377,11 +497,6 @@ class WorkflowPlugin extends Plugin {
377
497
  return this._engine
378
498
  }
379
499
 
380
- reload(framework) {
381
- console.log('[WorkflowPlugin] Reloading...')
382
- this._framework = framework
383
- }
384
-
385
500
  uninstall(framework) {
386
501
  this._engine = null
387
502
  this._framework = null