foliko 1.1.93 → 2.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.
- package/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +202 -0
- package/plugins/core/default/config.js +220 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +2 -2
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +261 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{core → common}/constants.js +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
|
@@ -5,52 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs')
|
|
7
7
|
const path = require('path')
|
|
8
|
-
const { Plugin } = require('
|
|
9
|
-
const { logger } = require('
|
|
8
|
+
const { Plugin } = require('../../../src/plugin/base')
|
|
9
|
+
const { logger } = require('../../../src/common/logger')
|
|
10
10
|
const log = logger.child('PythonPluginLoader')
|
|
11
11
|
const { z } = require('zod')
|
|
12
12
|
const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown')
|
|
13
|
-
|
|
14
|
-
// 将 JSON Schema 转换为 Zod Schema
|
|
15
|
-
function jsonSchemaToZod(jsonSchema) {
|
|
16
|
-
if (!jsonSchema || jsonSchema.type !== 'object') {
|
|
17
|
-
return z.object({})
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const properties = jsonSchema.properties || {}
|
|
21
|
-
const required = jsonSchema.required || []
|
|
22
|
-
|
|
23
|
-
const shape = {}
|
|
24
|
-
for (const [key, prop] of Object.entries(properties)) {
|
|
25
|
-
let zodType
|
|
26
|
-
switch (prop.type) {
|
|
27
|
-
case 'string':
|
|
28
|
-
zodType = z.string()
|
|
29
|
-
break
|
|
30
|
-
case 'number':
|
|
31
|
-
zodType = z.number()
|
|
32
|
-
break
|
|
33
|
-
case 'boolean':
|
|
34
|
-
zodType = z.boolean()
|
|
35
|
-
break
|
|
36
|
-
case 'array':
|
|
37
|
-
zodType = z.array(z.any())
|
|
38
|
-
break
|
|
39
|
-
case 'object':
|
|
40
|
-
zodType = jsonSchemaToZod(prop)
|
|
41
|
-
break
|
|
42
|
-
default:
|
|
43
|
-
zodType = z.any()
|
|
44
|
-
}
|
|
45
|
-
// 如果不是 required 字段,则标记为 optional
|
|
46
|
-
if (!required.includes(key)) {
|
|
47
|
-
zodType = zodType.optional()
|
|
48
|
-
}
|
|
49
|
-
shape[key] = zodType
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return z.object(shape)
|
|
53
|
-
}
|
|
13
|
+
const { jsonSchemaToZod } = require('../../../src/tool/schema')
|
|
54
14
|
|
|
55
15
|
class PythonPluginLoader extends Plugin {
|
|
56
16
|
constructor(config = {}) {
|
|
@@ -62,7 +22,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
62
22
|
this._agentDir = config.agentDir || '.foliko' // 默认从 .foliko 目录加载 Python 插件
|
|
63
23
|
this._pythonPlugins = new Map()
|
|
64
24
|
this._framework = null
|
|
65
|
-
this.system = true
|
|
25
|
+
this.system = true
|
|
66
26
|
}
|
|
67
27
|
|
|
68
28
|
install(framework) {
|
|
@@ -80,20 +40,6 @@ class PythonPluginLoader extends Plugin {
|
|
|
80
40
|
// 加载所有 Python 插件(会注册工具到 ExtensionExecutor)
|
|
81
41
|
this._loadPythonPlugins()
|
|
82
42
|
|
|
83
|
-
// 监听 agent 创建事件,附加 Python 插件信息到系统提示词
|
|
84
|
-
// framework.on('agent:created', (agent) => {
|
|
85
|
-
// this._refreshAgentPythonPluginsPrompt(agent)
|
|
86
|
-
// })
|
|
87
|
-
|
|
88
|
-
// 等待框架就绪后,刷新所有已有 agent 的 Python 插件提示词
|
|
89
|
-
// if (framework._ready) {
|
|
90
|
-
// this._refreshAllAgentsPythonPluginsPrompt(framework)
|
|
91
|
-
// } else {
|
|
92
|
-
// framework.once('framework:ready', () => {
|
|
93
|
-
// this._refreshAllAgentsPythonPluginsPrompt(framework)
|
|
94
|
-
// })
|
|
95
|
-
// }
|
|
96
|
-
|
|
97
43
|
return this
|
|
98
44
|
}
|
|
99
45
|
|
|
@@ -121,7 +67,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
121
67
|
if (Object.keys(params).length > 0) {
|
|
122
68
|
try {
|
|
123
69
|
const jsonSchema = this._paramsToJsonSchema(params)
|
|
124
|
-
const zodSchema =
|
|
70
|
+
const zodSchema = jsonSchemaToZod(jsonSchema)
|
|
125
71
|
const schemaMd = zodSchemaToMarkdown(zodSchema)
|
|
126
72
|
if (schemaMd) {
|
|
127
73
|
desc += `**参数:**\n\n${schemaMd}\n`
|
|
@@ -173,133 +119,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
173
119
|
* 将 JSON Schema 转换为 Zod schema
|
|
174
120
|
*/
|
|
175
121
|
_jsonSchemaToZod(jsonSchema) {
|
|
176
|
-
|
|
177
|
-
return z.object({})
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const shape = {}
|
|
182
|
-
const properties = jsonSchema.properties
|
|
183
|
-
const required = jsonSchema.required || []
|
|
184
|
-
|
|
185
|
-
for (const [key, prop] of Object.entries(properties)) {
|
|
186
|
-
shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return z.object(shape)
|
|
190
|
-
} catch (e) {
|
|
191
|
-
return z.object({})
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* 将 JSON Schema 属性转换为 Zod 类型(支持完整嵌套)
|
|
197
|
-
*/
|
|
198
|
-
_jsonSchemaPropToZod(prop, isRequired) {
|
|
199
|
-
// 1. 处理 enum(枚举值)
|
|
200
|
-
if (prop.enum) {
|
|
201
|
-
let zodType = z.string().enum(prop.enum)
|
|
202
|
-
return isRequired ? zodType : zodType.optional()
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const type = prop.type || 'string'
|
|
206
|
-
|
|
207
|
-
// 2. 简单类型
|
|
208
|
-
switch (type) {
|
|
209
|
-
case 'string':
|
|
210
|
-
return isRequired ? z.string() : z.string().optional()
|
|
211
|
-
case 'number':
|
|
212
|
-
case 'integer':
|
|
213
|
-
return isRequired ? z.number() : z.number().optional()
|
|
214
|
-
case 'boolean':
|
|
215
|
-
return isRequired ? z.boolean() : z.boolean().optional()
|
|
216
|
-
default:
|
|
217
|
-
break
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// 3. 数组类型
|
|
221
|
-
if (type === 'array') {
|
|
222
|
-
let itemsZod = z.any()
|
|
223
|
-
if (prop.items) {
|
|
224
|
-
// 支持 oneOf(联合类型数组)
|
|
225
|
-
if (prop.items.oneOf) {
|
|
226
|
-
const unionTypes = prop.items.oneOf.map(item => this._jsonSchemaPropToZod(item, true))
|
|
227
|
-
if (unionTypes.length > 0) {
|
|
228
|
-
itemsZod = z.union(unionTypes)
|
|
229
|
-
}
|
|
230
|
-
} else {
|
|
231
|
-
itemsZod = this._jsonSchemaPropToZod(prop.items, true)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return isRequired ? z.array(itemsZod) : z.array(itemsZod).optional()
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// 4. 对象类型
|
|
238
|
-
if (type === 'object') {
|
|
239
|
-
if (prop.properties && Object.keys(prop.properties).length > 0) {
|
|
240
|
-
const nested = {}
|
|
241
|
-
for (const [k, v] of Object.entries(prop.properties)) {
|
|
242
|
-
nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false)
|
|
243
|
-
}
|
|
244
|
-
const objZod = z.object(nested)
|
|
245
|
-
return isRequired ? objZod : objZod.optional()
|
|
246
|
-
}
|
|
247
|
-
// 无 properties 的 object,视为 record
|
|
248
|
-
const recordZod = z.record(z.any())
|
|
249
|
-
return isRequired ? recordZod : recordZod.optional()
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// 5. oneOf(联合类型)
|
|
253
|
-
if (prop.oneOf) {
|
|
254
|
-
const unionTypes = prop.oneOf.map(item => this._jsonSchemaPropToZod(item, true))
|
|
255
|
-
if (unionTypes.length > 0) {
|
|
256
|
-
const unionZod = z.union(unionTypes)
|
|
257
|
-
return isRequired ? unionZod : unionZod.optional()
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// 6. Fallback
|
|
262
|
-
return isRequired ? z.any() : z.any().optional()
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* 刷新单个 agent 的 Python 插件提示词
|
|
267
|
-
*/
|
|
268
|
-
_refreshAgentPythonPluginsPrompt(agent) {
|
|
269
|
-
const existingPrompt = agent._originalPrompt || ''
|
|
270
|
-
if (existingPrompt.includes('【Python 插件】')) {
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const pyDesc = this._buildPythonPluginsDescription()
|
|
275
|
-
if (!pyDesc) return
|
|
276
|
-
|
|
277
|
-
// 将 Python 插件描述追加到系统提示词
|
|
278
|
-
//agent.setSystemPrompt(existingPrompt + '\n\n' + pyDesc)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* 刷新所有 agent 的 Python 插件提示词
|
|
283
|
-
*/
|
|
284
|
-
_refreshAllAgentsPythonPluginsPrompt(framework) {
|
|
285
|
-
const visited = new Set()
|
|
286
|
-
|
|
287
|
-
const traverse = (agent) => {
|
|
288
|
-
if (!agent || visited.has(agent)) return
|
|
289
|
-
visited.add(agent)
|
|
290
|
-
this._refreshAgentPythonPluginsPrompt(agent)
|
|
291
|
-
|
|
292
|
-
// 递归处理子 agent
|
|
293
|
-
const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map()
|
|
294
|
-
for (const [name, subAgentInfo] of subAgents) {
|
|
295
|
-
traverse(subAgentInfo.agent)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const agents = framework._agents || []
|
|
300
|
-
for (const agent of agents) {
|
|
301
|
-
traverse(agent)
|
|
302
|
-
}
|
|
122
|
+
return jsonSchemaToZod(jsonSchema)
|
|
303
123
|
}
|
|
304
124
|
|
|
305
125
|
/**
|
|
@@ -496,7 +316,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
496
316
|
const inner = normalized.slice(1, -1)
|
|
497
317
|
const properties = {}
|
|
498
318
|
const required = []
|
|
499
|
-
|
|
319
|
+
|
|
500
320
|
const parts = this._splitByComma(inner)
|
|
501
321
|
for (const part of parts) {
|
|
502
322
|
const colonIdx = part.indexOf(':')
|
|
@@ -3,26 +3,21 @@
|
|
|
3
3
|
* 控制工具调用权限、内容过滤、触发动作
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { Plugin } = require('
|
|
7
|
-
const { logger } = require('
|
|
6
|
+
const { Plugin } = require('../../../src/plugin/base')
|
|
7
|
+
const { logger } = require('../../../src/common/logger')
|
|
8
8
|
const log = logger.child('RulesPlugin')
|
|
9
9
|
const { z } = require('zod')
|
|
10
10
|
const fs = require('fs')
|
|
11
11
|
const path = require('path')
|
|
12
|
+
const os = require('os')
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
* 内置规则类型
|
|
15
|
-
*/
|
|
16
14
|
const RuleType = {
|
|
17
|
-
ALLOW: 'allow',
|
|
18
|
-
DENY: 'deny',
|
|
19
|
-
TRANSFORM: 'transform',
|
|
20
|
-
RATE_LIMIT: 'rate_limit'
|
|
15
|
+
ALLOW: 'allow',
|
|
16
|
+
DENY: 'deny',
|
|
17
|
+
TRANSFORM: 'transform',
|
|
18
|
+
RATE_LIMIT: 'rate_limit'
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
/**
|
|
24
|
-
* 规则评估结果
|
|
25
|
-
*/
|
|
26
21
|
class RuleResult {
|
|
27
22
|
constructor(allowed, rule = null, transform = null) {
|
|
28
23
|
this.allowed = allowed
|
|
@@ -38,13 +33,14 @@ class RulesPlugin extends Plugin {
|
|
|
38
33
|
this.version = '1.0.0'
|
|
39
34
|
this.description = '规则引擎插件,用于控制工具调用权限、内容过滤、触发动作'
|
|
40
35
|
this.priority = 25
|
|
41
|
-
this.system = true
|
|
42
|
-
|
|
36
|
+
this.system = true
|
|
37
|
+
|
|
43
38
|
this.config = {
|
|
44
39
|
rulesDir: config.rulesDir || '.foliko/rules',
|
|
45
40
|
autoLoad: config.autoLoad !== false
|
|
46
41
|
}
|
|
47
42
|
|
|
43
|
+
this._homeRulesDir = path.join(os.homedir(), '.foliko', 'rules')
|
|
48
44
|
this._framework = null
|
|
49
45
|
this._rules = []
|
|
50
46
|
this._ruleStats = {
|
|
@@ -61,12 +57,11 @@ class RulesPlugin extends Plugin {
|
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
start(framework) {
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
if (this.config.autoLoad && this.config.rulesDir && !this._loaded) {
|
|
61
|
+
this._loaded = true;
|
|
66
62
|
this.loadFromDirectory(this.config.rulesDir)
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
// 注册规则管理工具
|
|
70
65
|
framework.registerTool({
|
|
71
66
|
name: 'rules_list',
|
|
72
67
|
description: '列出所有规则',
|
|
@@ -164,63 +159,131 @@ class RulesPlugin extends Plugin {
|
|
|
164
159
|
return this
|
|
165
160
|
}
|
|
166
161
|
|
|
167
|
-
/**
|
|
168
|
-
* 从目录加载规则文件
|
|
169
|
-
*/
|
|
170
162
|
loadFromDirectory(rulesDir) {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
const cwd = this._framework?.getCwd?.() ?? process.cwd()
|
|
164
|
+
const dirs = [
|
|
165
|
+
this._homeRulesDir, // ~/.foliko/rules(优先)
|
|
166
|
+
path.resolve(cwd, rulesDir || this.config.rulesDir), // 项目级
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
for (const resolvedDir of dirs) {
|
|
170
|
+
if (!fs.existsSync(resolvedDir)) continue
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const files = fs.readdirSync(resolvedDir).filter(f => f.endsWith('.json') || f.endsWith('.md'))
|
|
174
|
+
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const filePath = path.join(resolvedDir, file)
|
|
177
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
178
|
+
|
|
179
|
+
if (file.endsWith('.json')) {
|
|
180
|
+
const rules = JSON.parse(content)
|
|
181
|
+
for (const rule of rules) {
|
|
182
|
+
if (rule.pattern) rule.pattern = new RegExp(rule.pattern)
|
|
183
|
+
rule.enabled = rule.enabled !== false
|
|
184
|
+
this._rules.push(rule)
|
|
185
|
+
}
|
|
186
|
+
} else if (file.endsWith('.md')) {
|
|
187
|
+
// 解析 YAML frontmatter
|
|
188
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
189
|
+
if (!match) continue;
|
|
190
|
+
const fm = this._parseFrontmatter(match[1]);
|
|
191
|
+
const rule = {
|
|
192
|
+
name: fm.name || fm.id || path.basename(file, '.md'),
|
|
193
|
+
type: fm.type || 'deny',
|
|
194
|
+
target: fm.target || (fm.trigger?.conditions?.[0]?.tool_name) || '*',
|
|
195
|
+
pattern: fm.condition ? new RegExp(fm.condition) : null,
|
|
196
|
+
description: fm.description || '',
|
|
197
|
+
action: fm.action || {},
|
|
198
|
+
enabled: fm.enabled !== false,
|
|
199
|
+
};
|
|
200
|
+
this._rules.push(rule);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
log.error(` Failed to load rules from ${resolvedDir}: ${err.message}`)
|
|
205
|
+
}
|
|
176
206
|
}
|
|
207
|
+
}
|
|
177
208
|
|
|
178
|
-
|
|
179
|
-
|
|
209
|
+
_parseFrontmatter(yaml) {
|
|
210
|
+
const result = {};
|
|
211
|
+
const lines = yaml.split('\n');
|
|
212
|
+
let currentKey = null;
|
|
213
|
+
let currentIndent = 0;
|
|
214
|
+
const stack = [];
|
|
180
215
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const rules = JSON.parse(content)
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
const trimmed = line.trim();
|
|
218
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
185
219
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
const indent = line.length - line.trimStart().length;
|
|
221
|
+
const colonIndex = trimmed.indexOf(':');
|
|
222
|
+
|
|
223
|
+
if (colonIndex > 0 && !trimmed.startsWith('-')) {
|
|
224
|
+
const key = trimmed.substring(0, colonIndex).trim();
|
|
225
|
+
let value = trimmed.substring(colonIndex + 1).trim();
|
|
226
|
+
|
|
227
|
+
// 处理数组项后面的属性 (conditions 列表项)
|
|
228
|
+
if (trimmed.startsWith('- ') && colonIndex > 1) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (value === '' || value.startsWith('#')) {
|
|
233
|
+
currentKey = key;
|
|
234
|
+
currentIndent = indent;
|
|
235
|
+
if (!result[key]) result[key] = Array.isArray(result[key]) ? result[key] : {};
|
|
236
|
+
stack.push({ key, indent });
|
|
237
|
+
continue;
|
|
192
238
|
}
|
|
193
239
|
|
|
194
|
-
|
|
240
|
+
value = value.replace(/^["']|["']$/g, '');
|
|
241
|
+
if (value === 'true') value = true;
|
|
242
|
+
else if (value === 'false') value = false;
|
|
243
|
+
|
|
244
|
+
if (currentKey && indent > currentIndent) {
|
|
245
|
+
const parent = result[currentKey];
|
|
246
|
+
if (typeof parent === 'object' && !Array.isArray(parent)) {
|
|
247
|
+
parent[key] = value;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
result[key] = value;
|
|
251
|
+
currentKey = null;
|
|
252
|
+
}
|
|
253
|
+
} else if (trimmed.startsWith('- ')) {
|
|
254
|
+
const item = trimmed.substring(2).trim();
|
|
255
|
+
if (currentKey) {
|
|
256
|
+
if (!Array.isArray(result[currentKey])) result[currentKey] = [];
|
|
257
|
+
const subColon = item.indexOf(':');
|
|
258
|
+
if (subColon > 0) {
|
|
259
|
+
const subKey = item.substring(0, subColon).trim();
|
|
260
|
+
let subVal = item.substring(subColon + 1).trim().replace(/^["']|["']$/g, '');
|
|
261
|
+
const obj = {};
|
|
262
|
+
obj[subKey] = subVal;
|
|
263
|
+
result[currentKey].push(obj);
|
|
264
|
+
} else {
|
|
265
|
+
result[currentKey].push(item);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
195
268
|
}
|
|
196
|
-
} catch (err) {
|
|
197
|
-
log.error(` Failed to load rules: ${err.message}`)
|
|
198
269
|
}
|
|
270
|
+
|
|
271
|
+
return result;
|
|
199
272
|
}
|
|
200
273
|
|
|
201
|
-
/**
|
|
202
|
-
* 评估目标是否符合规则
|
|
203
|
-
* @param {string} target - 目标 (工具名, *, @event-type)
|
|
204
|
-
* @param {Object} input - 输入参数
|
|
205
|
-
* @returns {RuleResult}
|
|
206
|
-
*/
|
|
207
274
|
evaluate(target, input = {}) {
|
|
208
275
|
this._ruleStats.total++
|
|
209
276
|
|
|
210
|
-
// 按优先级排序规则 (先加载的在前)
|
|
211
277
|
for (const rule of this._rules) {
|
|
212
278
|
if (!rule.enabled) continue
|
|
213
279
|
|
|
214
|
-
// 匹配目标
|
|
215
280
|
if (rule.target !== '*' && rule.target !== target) continue
|
|
216
281
|
|
|
217
|
-
// 匹配模式 (如果有)
|
|
218
282
|
if (rule.pattern) {
|
|
219
283
|
const targetStr = typeof input === 'string' ? input : JSON.stringify(input)
|
|
220
284
|
if (!rule.pattern.test(targetStr)) continue
|
|
221
285
|
}
|
|
222
286
|
|
|
223
|
-
// 评估规则
|
|
224
287
|
if (rule.type === RuleType.DENY) {
|
|
225
288
|
this._ruleStats.denied++
|
|
226
289
|
return new RuleResult(false, rule)
|
|
@@ -237,35 +300,22 @@ class RulesPlugin extends Plugin {
|
|
|
237
300
|
}
|
|
238
301
|
}
|
|
239
302
|
|
|
240
|
-
// 没有匹配规则,默认允许
|
|
241
303
|
this._ruleStats.allowed++
|
|
242
304
|
return new RuleResult(true)
|
|
243
305
|
}
|
|
244
306
|
|
|
245
|
-
/**
|
|
246
|
-
* 检查工具调用是否允许
|
|
247
|
-
*/
|
|
248
307
|
checkToolCall(toolName, args) {
|
|
249
308
|
return this.evaluate(toolName, args)
|
|
250
309
|
}
|
|
251
310
|
|
|
252
|
-
/**
|
|
253
|
-
* 检查消息是否允许
|
|
254
|
-
*/
|
|
255
311
|
checkMessage(message, context) {
|
|
256
312
|
return this.evaluate('@message', { message, context })
|
|
257
313
|
}
|
|
258
314
|
|
|
259
|
-
/**
|
|
260
|
-
* 添加规则
|
|
261
|
-
*/
|
|
262
315
|
addRule(rule) {
|
|
263
316
|
this._rules.push(rule)
|
|
264
317
|
}
|
|
265
318
|
|
|
266
|
-
/**
|
|
267
|
-
* 移除规则
|
|
268
|
-
*/
|
|
269
319
|
removeRule(name) {
|
|
270
320
|
const index = this._rules.findIndex(r => r.name === name)
|
|
271
321
|
if (index !== -1) {
|
|
@@ -279,12 +329,19 @@ class RulesPlugin extends Plugin {
|
|
|
279
329
|
this._framework = framework
|
|
280
330
|
this._rules = []
|
|
281
331
|
this._ruleStats = { total: 0, allowed: 0, denied: 0, transformed: 0 }
|
|
332
|
+
this._loaded = false;
|
|
282
333
|
|
|
283
334
|
if (this.config.autoLoad && this.config.rulesDir) {
|
|
284
335
|
this.loadFromDirectory(this.config.rulesDir)
|
|
285
336
|
}
|
|
286
337
|
}
|
|
287
338
|
|
|
339
|
+
async onCwdChanged(oldCwd, newCwd, framework) {
|
|
340
|
+
this._rules = []
|
|
341
|
+
this._ruleStats = { total: 0, allowed: 0, denied: 0, transformed: 0 }
|
|
342
|
+
this._loaded = false
|
|
343
|
+
}
|
|
344
|
+
|
|
288
345
|
uninstall(framework) {
|
|
289
346
|
this._rules = []
|
|
290
347
|
this._ruleStats = { total: 0, allowed: 0, denied: 0, transformed: 0 }
|
|
@@ -292,4 +349,4 @@ class RulesPlugin extends Plugin {
|
|
|
292
349
|
}
|
|
293
350
|
}
|
|
294
351
|
|
|
295
|
-
module.exports = RulesPlugin
|
|
352
|
+
module.exports = RulesPlugin
|