foliko 1.0.63 → 1.0.65
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.
- package/cli/src/commands/chat.js +16 -0
- package/examples/basic.js +110 -110
- package/examples/mcp-example.js +53 -53
- package/examples/skill-example.js +49 -49
- package/examples/test-mcp.js +79 -79
- package/examples/test-reload.js +61 -61
- package/package.json +1 -1
- package/plugins/ambient-agent-plugin.js +845 -1060
- package/plugins/email.js +112 -27
- package/plugins/file-system-plugin.js +30 -0
- package/plugins/scheduler-plugin.js +11 -11
- package/skills/workflow-guide/SKILL.md +139 -73
- package/src/capabilities/workflow-engine.js +137 -11
- package/src/executors/executor-base.js +58 -58
- package/test-server.js +25 -25
- package/test.txt +3 -3
|
@@ -20,6 +20,7 @@ const StepType = {
|
|
|
20
20
|
SEQUENTIAL: 'sequential',
|
|
21
21
|
LOOP: 'loop',
|
|
22
22
|
DELAY: 'delay',
|
|
23
|
+
TOOL: 'tool',
|
|
23
24
|
INPUT: 'input',
|
|
24
25
|
OUTPUT: 'output'
|
|
25
26
|
}
|
|
@@ -244,6 +245,112 @@ class DelayStep extends WorkflowStep {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
/**
|
|
249
|
+
* 工具步骤
|
|
250
|
+
*/
|
|
251
|
+
class ToolStep extends WorkflowStep {
|
|
252
|
+
constructor(config) {
|
|
253
|
+
super({ ...config, type: StepType.TOOL })
|
|
254
|
+
this.tool = config.tool
|
|
255
|
+
this.args = config.args || {}
|
|
256
|
+
this.outputVariable = config.outputVariable || null
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async execute(context, engine) {
|
|
260
|
+
if (!this.tool) {
|
|
261
|
+
throw new Error('Tool step requires a tool name')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log(`[Workflow] Executing tool: ${this.tool}`)
|
|
265
|
+
|
|
266
|
+
// 解析工具参数,支持变量引用
|
|
267
|
+
const resolvedArgs = this._resolveArgs(this.args, context)
|
|
268
|
+
|
|
269
|
+
// 获取当前 sessionId(从上下文变量或执行上下文)
|
|
270
|
+
let sessionId = context.variables._sessionId
|
|
271
|
+
if (!sessionId && engine.framework.getExecutionContext) {
|
|
272
|
+
const ctx = engine.framework.getExecutionContext()
|
|
273
|
+
sessionId = ctx?.sessionId
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 使用 sessionId 上下文执行工具
|
|
277
|
+
let result
|
|
278
|
+
if (sessionId && engine.framework.runWithContext) {
|
|
279
|
+
result = await engine.framework.runWithContext(
|
|
280
|
+
{ sessionId },
|
|
281
|
+
async () => await engine.framework.executeTool(this.tool, resolvedArgs)
|
|
282
|
+
)
|
|
283
|
+
} else {
|
|
284
|
+
result = await engine.framework.executeTool(this.tool, resolvedArgs)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 保存结果到变量
|
|
288
|
+
if (this.outputVariable) {
|
|
289
|
+
context.variables[this.outputVariable] = result
|
|
290
|
+
}
|
|
291
|
+
context.lastResult = result
|
|
292
|
+
|
|
293
|
+
// 检查工具执行结果
|
|
294
|
+
if (result && result.error) {
|
|
295
|
+
console.warn(`[Workflow] Tool ${this.tool} returned error: ${result.error}`)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 解析参数中的变量引用
|
|
303
|
+
* 支持 {{variableName}} 语法
|
|
304
|
+
*/
|
|
305
|
+
_resolveArgs(args, context) {
|
|
306
|
+
const resolved = {}
|
|
307
|
+
for (const [key, value] of Object.entries(args)) {
|
|
308
|
+
resolved[key] = this._resolveValue(value, context)
|
|
309
|
+
}
|
|
310
|
+
return resolved
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_resolveValue(value, context) {
|
|
314
|
+
if (typeof value === 'string') {
|
|
315
|
+
// 替换 {{variable}} 或 {{variables.name}} 语法
|
|
316
|
+
return value.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
|
|
317
|
+
// 支持 variables.xxx 或直接 xxx 格式
|
|
318
|
+
if (path.startsWith('variables.')) {
|
|
319
|
+
return this._getNestedValue(context, path) ?? match
|
|
320
|
+
} else if (path.startsWith('context.')) {
|
|
321
|
+
return this._getNestedValue(context, path) ?? match
|
|
322
|
+
} else {
|
|
323
|
+
// 尝试从 context.variables 获取
|
|
324
|
+
const val = this._getNestedValue(context.variables, path)
|
|
325
|
+
if (val !== undefined) return val
|
|
326
|
+
// 尝试从 context 直接获取
|
|
327
|
+
return this._getNestedValue(context, path) ?? match
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
} else if (Array.isArray(value)) {
|
|
331
|
+
return value.map(v => this._resolveValue(v, context))
|
|
332
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
333
|
+
const resolved = {}
|
|
334
|
+
for (const [k, v] of Object.entries(value)) {
|
|
335
|
+
resolved[k] = this._resolveValue(v, context)
|
|
336
|
+
}
|
|
337
|
+
return resolved
|
|
338
|
+
}
|
|
339
|
+
return value
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
_getNestedValue(obj, path) {
|
|
343
|
+
if (!obj) return undefined
|
|
344
|
+
const parts = path.split('.')
|
|
345
|
+
let current = obj
|
|
346
|
+
for (const part of parts) {
|
|
347
|
+
if (current === null || current === undefined) return undefined
|
|
348
|
+
current = current[part]
|
|
349
|
+
}
|
|
350
|
+
return current
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
247
354
|
/**
|
|
248
355
|
* 工作流引擎
|
|
249
356
|
*/
|
|
@@ -316,6 +423,7 @@ class WorkflowPlugin extends Plugin {
|
|
|
316
423
|
this._engine.registerStepType(StepType.SEQUENTIAL, SequentialStep)
|
|
317
424
|
this._engine.registerStepType(StepType.LOOP, LoopStep)
|
|
318
425
|
this._engine.registerStepType(StepType.DELAY, DelayStep)
|
|
426
|
+
this._engine.registerStepType(StepType.TOOL, ToolStep)
|
|
319
427
|
|
|
320
428
|
// 注册工作流执行工具
|
|
321
429
|
framework.registerTool({
|
|
@@ -325,8 +433,14 @@ class WorkflowPlugin extends Plugin {
|
|
|
325
433
|
workflow: z.string().describe('工作流定义(JSON 或 JavaScript 代码)'),
|
|
326
434
|
input: z.object({}).optional().describe('工作流输入参数')
|
|
327
435
|
}),
|
|
328
|
-
execute: async (args) => {
|
|
329
|
-
|
|
436
|
+
execute: async (args, framework) => {
|
|
437
|
+
// 获取当前 sessionId
|
|
438
|
+
let sessionId = null
|
|
439
|
+
const ctx = framework.getExecutionContext()
|
|
440
|
+
if (ctx?.sessionId) {
|
|
441
|
+
sessionId = ctx.sessionId
|
|
442
|
+
}
|
|
443
|
+
return await this.executeWorkflow(args.workflow, args.input || {}, sessionId)
|
|
330
444
|
}
|
|
331
445
|
})
|
|
332
446
|
|
|
@@ -455,18 +569,24 @@ class WorkflowPlugin extends Plugin {
|
|
|
455
569
|
/**
|
|
456
570
|
* 执行工作流
|
|
457
571
|
*/
|
|
458
|
-
async executeWorkflow(workflowDef, input = {}) {
|
|
572
|
+
async executeWorkflow(workflowDef, input = {}, sessionId = null) {
|
|
459
573
|
try {
|
|
460
574
|
let workflow
|
|
461
575
|
|
|
462
576
|
if (typeof workflowDef === 'string') {
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
577
|
+
// 尝试作为工作流名称加载(先检查是否已加载的工作流)
|
|
578
|
+
const workflowName = workflowDef.trim()
|
|
579
|
+
if (this._workflows.has(workflowName)) {
|
|
580
|
+
workflow = this._workflows.get(workflowName)
|
|
581
|
+
} else {
|
|
582
|
+
// 尝试解析 JSON 或执行代码
|
|
583
|
+
try {
|
|
584
|
+
workflow = JSON.parse(workflowDef)
|
|
585
|
+
} catch {
|
|
586
|
+
// eslint-disable-next-line no-new-func
|
|
587
|
+
const fn = new Function('engine', 'return (' + workflowDef + ')')
|
|
588
|
+
workflow = fn(this._engine)
|
|
589
|
+
}
|
|
470
590
|
}
|
|
471
591
|
} else {
|
|
472
592
|
workflow = workflowDef
|
|
@@ -474,6 +594,11 @@ class WorkflowPlugin extends Plugin {
|
|
|
474
594
|
|
|
475
595
|
const context = this._engine.createContext(input, {})
|
|
476
596
|
|
|
597
|
+
// 将 sessionId 存储到上下文变量,供工具步骤使用
|
|
598
|
+
if (sessionId) {
|
|
599
|
+
context.variables._sessionId = sessionId
|
|
600
|
+
}
|
|
601
|
+
|
|
477
602
|
// 执行工作流步骤
|
|
478
603
|
if (workflow.steps && Array.isArray(workflow.steps)) {
|
|
479
604
|
const results = []
|
|
@@ -513,5 +638,6 @@ module.exports = {
|
|
|
513
638
|
ConditionStep,
|
|
514
639
|
SequentialStep,
|
|
515
640
|
LoopStep,
|
|
516
|
-
DelayStep
|
|
641
|
+
DelayStep,
|
|
642
|
+
ToolStep
|
|
517
643
|
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Executor 基类
|
|
3
|
-
* 执行器的基类,定义执行器接口
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { EventEmitter } = require('../utils/event-emitter')
|
|
7
|
-
|
|
8
|
-
class ExecutorBase extends EventEmitter {
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} name - 执行器名称
|
|
11
|
-
*/
|
|
12
|
-
constructor(name) {
|
|
13
|
-
super()
|
|
14
|
-
this.name = name
|
|
15
|
-
this._enabled = true
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 执行
|
|
20
|
-
* @param {Object} params - 执行参数
|
|
21
|
-
* @returns {Promise<any>}
|
|
22
|
-
*/
|
|
23
|
-
async execute(params) {
|
|
24
|
-
throw new Error('execute() must be implemented')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 启用执行器
|
|
29
|
-
*/
|
|
30
|
-
enable() {
|
|
31
|
-
this._enabled = true
|
|
32
|
-
return this
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 禁用执行器
|
|
37
|
-
*/
|
|
38
|
-
disable() {
|
|
39
|
-
this._enabled = false
|
|
40
|
-
return this
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 是否启用
|
|
45
|
-
*/
|
|
46
|
-
isEnabled() {
|
|
47
|
-
return this._enabled
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 销毁
|
|
52
|
-
*/
|
|
53
|
-
destroy() {
|
|
54
|
-
this.removeAllListeners()
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
module.exports = { ExecutorBase }
|
|
1
|
+
/**
|
|
2
|
+
* Executor 基类
|
|
3
|
+
* 执行器的基类,定义执行器接口
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { EventEmitter } = require('../utils/event-emitter')
|
|
7
|
+
|
|
8
|
+
class ExecutorBase extends EventEmitter {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} name - 执行器名称
|
|
11
|
+
*/
|
|
12
|
+
constructor(name) {
|
|
13
|
+
super()
|
|
14
|
+
this.name = name
|
|
15
|
+
this._enabled = true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 执行
|
|
20
|
+
* @param {Object} params - 执行参数
|
|
21
|
+
* @returns {Promise<any>}
|
|
22
|
+
*/
|
|
23
|
+
async execute(params) {
|
|
24
|
+
throw new Error('execute() must be implemented')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 启用执行器
|
|
29
|
+
*/
|
|
30
|
+
enable() {
|
|
31
|
+
this._enabled = true
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 禁用执行器
|
|
37
|
+
*/
|
|
38
|
+
disable() {
|
|
39
|
+
this._enabled = false
|
|
40
|
+
return this
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 是否启用
|
|
45
|
+
*/
|
|
46
|
+
isEnabled() {
|
|
47
|
+
return this._enabled
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 销毁
|
|
52
|
+
*/
|
|
53
|
+
destroy() {
|
|
54
|
+
this.removeAllListeners()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { ExecutorBase }
|
package/test-server.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
const { serve } = require('@hono/node-server');
|
|
2
|
-
const { Hono } = require('hono');
|
|
3
|
-
|
|
4
|
-
const app = new Hono();
|
|
5
|
-
|
|
6
|
-
// 简单路由
|
|
7
|
-
app.get('/test', (c) => {
|
|
8
|
-
console.log('Handler called');
|
|
9
|
-
return c.json({ message: 'Hello World' });
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const server = serve({
|
|
13
|
-
fetch: app.fetch,
|
|
14
|
-
port: 3001
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
server.on('request', (req) => {
|
|
18
|
-
console.log('Request:', req.method, req.url);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
server.on('error', (err) => {
|
|
22
|
-
console.error('Server error:', err);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
console.log('Server started on port 3001');
|
|
1
|
+
const { serve } = require('@hono/node-server');
|
|
2
|
+
const { Hono } = require('hono');
|
|
3
|
+
|
|
4
|
+
const app = new Hono();
|
|
5
|
+
|
|
6
|
+
// 简单路由
|
|
7
|
+
app.get('/test', (c) => {
|
|
8
|
+
console.log('Handler called');
|
|
9
|
+
return c.json({ message: 'Hello World' });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const server = serve({
|
|
13
|
+
fetch: app.fetch,
|
|
14
|
+
port: 3001
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
server.on('request', (req) => {
|
|
18
|
+
console.log('Request:', req.method, req.url);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
server.on('error', (err) => {
|
|
22
|
+
console.error('Server error:', err);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.log('Server started on port 3001');
|
package/test.txt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Hello from static resource test!
|
|
2
|
-
This is a test file for verifying static resource serving.
|
|
3
|
-
Timestamp: 2026-03-24
|
|
1
|
+
Hello from static resource test!
|
|
2
|
+
This is a test file for verifying static resource serving.
|
|
3
|
+
Timestamp: 2026-03-24
|