foliko 1.0.77 → 1.0.78

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 (36) hide show
  1. package/.agent/data/default.json +31559 -0
  2. package/.agent/data/plugins-state.json +10 -1
  3. package/.claude/settings.local.json +13 -2
  4. package/.env.example +54 -54
  5. package/cli/src/commands/chat.js +1 -1
  6. package/examples/basic.js +1 -1
  7. package/package.json +5 -3
  8. package/plugins/ai-plugin.js +1 -1
  9. package/plugins/ambient-agent/index.js +1 -1
  10. package/plugins/audit-plugin.js +1 -1
  11. package/plugins/default-plugins.js +92 -209
  12. package/plugins/email/index.js +1 -1
  13. package/plugins/extension-executor-plugin.js +326 -0
  14. package/plugins/feishu-plugin.js +1 -1
  15. package/plugins/file-system-plugin.js +57 -6
  16. package/plugins/gate-trading.js +747 -0
  17. package/plugins/install-plugin.js +1 -1
  18. package/plugins/python-executor-plugin.js +1 -1
  19. package/plugins/python-plugin-loader.js +275 -105
  20. package/plugins/rules-plugin.js +1 -1
  21. package/plugins/scheduler-plugin.js +1 -1
  22. package/plugins/session-plugin.js +132 -7
  23. package/plugins/shell-executor-plugin.js +1 -1
  24. package/plugins/storage-plugin.js +24 -1
  25. package/plugins/subagent-plugin.js +2 -2
  26. package/plugins/telegram-plugin.js +1 -1
  27. package/plugins/think-plugin.js +10 -10
  28. package/plugins/tools-plugin.js +1 -1
  29. package/plugins/web-plugin.js +49 -18
  30. package/plugins/weixin-plugin.js +1 -1
  31. package/skills/foliko-dev/SKILL.md +583 -500
  32. package/skills/python-plugin-dev/SKILL.md +238 -266
  33. package/src/core/agent-chat.js +103 -4
  34. package/src/core/agent.js +85 -19
  35. package/src/core/plugin-base.js +43 -0
  36. package/src/executors/mcp-executor.js +126 -22
@@ -31,20 +31,22 @@ class SessionPlugin extends Plugin {
31
31
  this.description = '会话管理插件,支持多会话隔离、历史记录、会话超时清理'
32
32
  this.priority = 5
33
33
 
34
- this.system = true
34
+ this.system = true
35
35
 
36
36
  this.config = {
37
37
  sessionTTL: config.sessionTTL || 30 * 60 * 1000, // 30分钟
38
38
  maxSessions: config.maxSessions || 100,
39
39
  maxHistoryLength: config.maxHistoryLength || 150, // 放宽到 150,Agent 已有智能压缩
40
- autoCleanup: config.autoCleanup !== false,
41
- cleanupInterval: config.cleanupInterval || 5 * 60 * 1000 // 5分钟
40
+ autoCleanup: config.autoCleanup||false, // 默认不开启自动清理,避免误删会话
41
+ cleanupInterval: config.cleanupInterval || 5 * 60 * 1000, // 5分钟
42
+ persistToStorage: config.persistToStorage !== false // 默认持久化到 storage
42
43
  }
43
-
44
+ // console.log('SessionPlugin config:', this.config)
44
45
  this._framework = null
45
46
  this._sessions = new Map()
46
47
  this._cleanupTimer = null
47
48
  this._events = new EventEmitter()
49
+ this._storageKeyPrefix = 'session:'
48
50
  }
49
51
 
50
52
  install(framework) {
@@ -52,7 +54,108 @@ class SessionPlugin extends Plugin {
52
54
  return this
53
55
  }
54
56
 
57
+ /**
58
+ * 获取存储实例
59
+ * @private
60
+ */
61
+ _getStorage() {
62
+ if (!this._framework) return null;
63
+ return this._framework.pluginManager.get('storage');
64
+ }
65
+
66
+ /**
67
+ * 从 storage 加载会话数据
68
+ * @private
69
+ */
70
+ _loadFromStorage(sessionId) {
71
+ const storage = this._getStorage();
72
+ if (!storage) return null;
73
+
74
+ try {
75
+ const store = storage.getStore();
76
+ const data = store.get(this._storageKeyPrefix + sessionId);
77
+ if (data && data.messages) {
78
+ return data;
79
+ }
80
+ } catch (err) {
81
+ // 忽略加载错误
82
+ }
83
+ return null;
84
+ }
85
+
86
+ /**
87
+ * 保存会话数据到 storage
88
+ * @private
89
+ */
90
+ _saveToStorage(session) {
91
+ const storage = this._getStorage();
92
+ if (!storage || !this.config.persistToStorage) return;
93
+
94
+ try {
95
+ storage.setDirect(this._storageKeyPrefix + session.id, {
96
+ messages: session.messages,
97
+ variables: session.variables,
98
+ metadata: session.metadata,
99
+ createdAt: session.createdAt,
100
+ lastActive: session.lastActive
101
+ });
102
+ } catch (err) {
103
+ // 忽略保存错误
104
+ }
105
+ }
106
+
107
+ /**
108
+ * 从 storage 删除会话数据
109
+ * @private
110
+ */
111
+ _deleteFromStorage(sessionId) {
112
+ const storage = this._getStorage();
113
+ if (!storage) return;
114
+
115
+ try {
116
+ storage.deleteDirect(this._storageKeyPrefix + sessionId);
117
+ } catch (err) {
118
+ // 忽略删除错误
119
+ }
120
+ }
121
+
122
+ /**
123
+ * 从 storage 加载所有会话
124
+ * @private
125
+ */
126
+ _loadAllFromStorage() {
127
+ const storage = this._getStorage();
128
+ if (!storage) return;
129
+
130
+ try {
131
+ const store = storage.getStore();
132
+ // 遍历 storage 找出所有 session: 开头的键
133
+ for (const [key, value] of store.entries()) {
134
+ if (key.startsWith(this._storageKeyPrefix)) {
135
+ const sessionId = key.substring(this._storageKeyPrefix.length);
136
+ const sessionData = value;
137
+ if (sessionId && sessionData && sessionData.messages) {
138
+ // 恢复会话到内存
139
+ this._sessions.set(sessionId, {
140
+ id: sessionId,
141
+ messages: sessionData.messages || [],
142
+ variables: sessionData.variables || {},
143
+ metadata: sessionData.metadata || {},
144
+ createdAt: sessionData.createdAt ? new Date(sessionData.createdAt) : new Date(),
145
+ lastActive: sessionData.lastActive ? new Date(sessionData.lastActive) : new Date()
146
+ });
147
+ }
148
+ }
149
+ }
150
+ log.info(`Loaded ${this._sessions.size} sessions from storage`);
151
+ } catch (err) {
152
+ log.warn('Failed to load sessions from storage:', err.message);
153
+ }
154
+ }
155
+
55
156
  start(framework) {
157
+ // 从 storage 加载所有会话
158
+ this._loadAllFromStorage();
56
159
  // 注册会话管理工具
57
160
  framework.registerTool({
58
161
  name: 'session_create',
@@ -183,6 +286,7 @@ class SessionPlugin extends Plugin {
183
286
  }
184
287
 
185
288
  this._sessions.set(id, session)
289
+ this._saveToStorage(session)
186
290
  return session
187
291
  }
188
292
 
@@ -190,11 +294,28 @@ class SessionPlugin extends Plugin {
190
294
  * 获取会话
191
295
  */
192
296
  getSession(sessionId) {
193
- const session = this._sessions.get(sessionId)
297
+ let session = this._sessions.get(sessionId)
194
298
  if (session) {
195
299
  session.lastActive = new Date()
300
+ return session
196
301
  }
197
- return session
302
+
303
+ // 尝试从 storage 加载
304
+ const storageData = this._loadFromStorage(sessionId)
305
+ if (storageData) {
306
+ session = {
307
+ id: sessionId,
308
+ messages: storageData.messages || [],
309
+ variables: storageData.variables || {},
310
+ metadata: storageData.metadata || {},
311
+ createdAt: storageData.createdAt ? new Date(storageData.createdAt) : new Date(),
312
+ lastActive: new Date()
313
+ }
314
+ this._sessions.set(sessionId, session)
315
+ return session
316
+ }
317
+
318
+ return null
198
319
  }
199
320
 
200
321
  /**
@@ -221,6 +342,9 @@ class SessionPlugin extends Plugin {
221
342
  if (session.messages.length > this.config.maxHistoryLength) {
222
343
  session.messages = session.messages.slice(-this.config.maxHistoryLength)
223
344
  }
345
+
346
+ // 持久化到 storage
347
+ this._saveToStorage(session)
224
348
  }
225
349
  return session
226
350
  }
@@ -245,6 +369,7 @@ class SessionPlugin extends Plugin {
245
369
  deleteSession(sessionId) {
246
370
  const deleted = this._sessions.delete(sessionId)
247
371
  if (deleted) {
372
+ this._deleteFromStorage(sessionId)
248
373
  this._events.emit('session:deleted', sessionId)
249
374
  }
250
375
  return deleted
@@ -367,4 +492,4 @@ class SessionPlugin extends Plugin {
367
492
  }
368
493
  }
369
494
 
370
- module.exports = { SessionPlugin }
495
+ module.exports = SessionPlugin
@@ -195,4 +195,4 @@ class ShellExecutorPlugin extends Plugin {
195
195
  }
196
196
  }
197
197
 
198
- module.exports = { ShellExecutorPlugin }
198
+ module.exports = ShellExecutorPlugin
@@ -223,6 +223,29 @@ class StoragePlugin extends Plugin {
223
223
  return this._store
224
224
  }
225
225
 
226
+ /**
227
+ * 直接设置值并自动持久化(供其他插件使用)
228
+ * @param {string} key - 键
229
+ * @param {any} value - 值
230
+ */
231
+ setDirect(key, value) {
232
+ this._store.set(key, value)
233
+ if (this.config.type === 'json') {
234
+ this._persistToFile()
235
+ }
236
+ }
237
+
238
+ /**
239
+ * 删除键并自动持久化(供其他插件使用)
240
+ * @param {string} key - 键
241
+ */
242
+ deleteDirect(key) {
243
+ this._store.delete(key)
244
+ if (this.config.type === 'json') {
245
+ this._persistToFile()
246
+ }
247
+ }
248
+
226
249
  reload(framework) {
227
250
  this._framework = framework
228
251
  this._store.clear()
@@ -238,4 +261,4 @@ class StoragePlugin extends Plugin {
238
261
  }
239
262
  }
240
263
 
241
- module.exports = { StoragePlugin }
264
+ module.exports = StoragePlugin
@@ -841,6 +841,6 @@ class SubAgentManagerPlugin extends Plugin {
841
841
  }
842
842
 
843
843
  module.exports = {
844
- SubAgentPlugin,
845
- SubAgentManagerPlugin
844
+ SubAgentManagerPlugin,
845
+ SubAgentPlugin
846
846
  }
@@ -479,4 +479,4 @@ class TelegramPlugin extends Plugin {
479
479
  }
480
480
  }
481
481
 
482
- module.exports = { TelegramPlugin }
482
+ module.exports = TelegramPlugin
@@ -22,7 +22,7 @@ class ThinkPlugin extends Plugin {
22
22
  maxReflectDepth: config.maxReflectDepth || 3, // 最大反思深度
23
23
  enableContinuous: config.enableContinuous || false // 是否启用持续思考
24
24
  }
25
-
25
+ this.tools = {} // 存储注册的工具函数
26
26
  this._framework = null
27
27
  this._thoughts = [] // 思考记录
28
28
  this._reflectionChain = [] // 反思链
@@ -38,7 +38,7 @@ class ThinkPlugin extends Plugin {
38
38
 
39
39
  start(framework) {
40
40
  // 注册思考工具
41
- framework.registerTool({
41
+ this.tools.think_now = {
42
42
  name: 'think_now',
43
43
  description: '立即触发 LLM 主动思考(用于反思、自我检视、生成想法)',
44
44
  inputSchema: z.object({
@@ -49,9 +49,9 @@ class ThinkPlugin extends Plugin {
49
49
  execute: async (args) => {
50
50
  return await this._triggerThinking(args)
51
51
  }
52
- })
52
+ }
53
53
 
54
- framework.registerTool({
54
+ this.tools.think_continue = {
55
55
  name: 'think_continue',
56
56
  description: '让 LLM 持续思考模式(后台自动思考,定期输出结果)',
57
57
  inputSchema: z.object({
@@ -61,18 +61,18 @@ class ThinkPlugin extends Plugin {
61
61
  execute: async (args) => {
62
62
  return await this._startContinuousThinking(args)
63
63
  }
64
- })
64
+ }
65
65
 
66
- framework.registerTool({
66
+ this.tools.think_stop = {
67
67
  name: 'think_stop',
68
68
  description: '停止持续思考模式',
69
69
  inputSchema: z.object({}),
70
70
  execute: async () => {
71
71
  return this._stopContinuousThinking()
72
72
  }
73
- })
73
+ }
74
74
 
75
- framework.registerTool({
75
+ this.tools.think_get_thoughts = {
76
76
  name: 'think_get_thoughts',
77
77
  description: '获取思考历史记录',
78
78
  inputSchema: z.object({
@@ -86,7 +86,7 @@ class ThinkPlugin extends Plugin {
86
86
  total: this._thoughts.length
87
87
  }
88
88
  }
89
- })
89
+ }
90
90
 
91
91
  return this
92
92
  }
@@ -342,4 +342,4 @@ ${topic || '接下来的行动'}
342
342
  }
343
343
  }
344
344
 
345
- module.exports = { ThinkPlugin }
345
+ module.exports = ThinkPlugin
@@ -193,4 +193,4 @@ class ToolsPlugin extends Plugin {
193
193
  }
194
194
  }
195
195
 
196
- module.exports = { ToolsPlugin }
196
+ module.exports = ToolsPlugin
@@ -5,6 +5,7 @@
5
5
 
6
6
  const { Plugin } = require('../src/core/plugin-base')
7
7
  const { logger } = require('../src/utils/logger')
8
+ const { runInSandbox } = require('../src/utils/sandbox')
8
9
  const log = logger.child('Web')
9
10
  const { z } = require('zod')
10
11
  const { serve } = require('@hono/node-server')
@@ -32,6 +33,8 @@ class WebPlugin extends Plugin {
32
33
  this._app = null
33
34
  this._framework = null
34
35
 
36
+ this.tools = {}
37
+
35
38
  // 数据存储(始终保持原始类型)
36
39
  this._routes = [] // 路由列表
37
40
  this._webhooks = new Map() // webhook Map: id -> {id, path, prompt, sessionId}
@@ -73,7 +76,7 @@ class WebPlugin extends Plugin {
73
76
 
74
77
  _registerTools() {
75
78
  // 启动 Web 服务
76
- this._framework.registerTool({
79
+ this.tools.web_start = {
77
80
  name: 'web_start',
78
81
  description: '启动 Web 服务',
79
82
  inputSchema: z.object({
@@ -81,18 +84,18 @@ class WebPlugin extends Plugin {
81
84
  host: z.string().optional().describe('主机地址,默认 0.0.0.0')
82
85
  }),
83
86
  execute: async (args) => this._startServer(args.port, args.host)
84
- })
87
+ }
85
88
 
86
89
  // 停止 Web 服务
87
- this._framework.registerTool({
90
+ this.tools.web_stop = {
88
91
  name: 'web_stop',
89
92
  description: '停止 Web 服务',
90
93
  inputSchema: z.object({}),
91
94
  execute: async () => this._stopServer()
92
- })
95
+ }
93
96
 
94
97
  // 注册 HTTP 路由
95
- this._framework.registerTool({
98
+ this.tools.web_register_route = {
96
99
  name: 'web_register_route',
97
100
  description: '注册 HTTP 路由',
98
101
  inputSchema: z.object({
@@ -107,10 +110,10 @@ class WebPlugin extends Plugin {
107
110
  description: z.string().optional().describe('路由描述')
108
111
  }),
109
112
  execute: async (args) => this._registerRoute(args.method, args.path, args.handler, args.description)
110
- })
113
+ }
111
114
 
112
115
  // 注册 Webhook(自动生成 /webhook/{id} 链接)
113
- this._framework.registerTool({
116
+ this.tools.web_register_webhook = {
114
117
  name: 'web_register_webhook',
115
118
  description: '注册 Webhook,接收的数据会交给 LLM 处理。自动生成唯一 URL',
116
119
  inputSchema: z.object({
@@ -118,10 +121,10 @@ class WebPlugin extends Plugin {
118
121
  awaitResponse: z.boolean().optional().describe('是否等待 LLM 处理完成再返回响应,默认 false')
119
122
  }),
120
123
  execute: async (args) => this._registerWebhook(args.prompt, args.awaitResponse)
121
- })
124
+ }
122
125
 
123
126
  // 注册静态资源
124
- this._framework.registerTool({
127
+ this.tools.web_register_static = {
125
128
  name: 'web_register_static',
126
129
  description: '注册静态资源文件夹',
127
130
  inputSchema: z.object({
@@ -133,18 +136,18 @@ class WebPlugin extends Plugin {
133
136
  }).optional()
134
137
  }),
135
138
  execute: async (args) => this._registerStatic(args.urlPath, args.folder, args.options)
136
- })
139
+ }
137
140
 
138
141
  // 列出所有路由
139
- this._framework.registerTool({
142
+ this.tools.web_list_routes = {
140
143
  name: 'web_list_routes',
141
144
  description: '列出所有已注册的路由和 Webhook',
142
145
  inputSchema: z.object({}),
143
146
  execute: async () => this._listRoutes()
144
- })
147
+ }
145
148
 
146
149
  // 发送 HTTP 请求
147
- this._framework.registerTool({
150
+ this.tools.web_request = {
148
151
  name: 'web_request',
149
152
  description: '发送 HTTP 请求',
150
153
  inputSchema: z.object({
@@ -154,7 +157,7 @@ class WebPlugin extends Plugin {
154
157
  headers: z.record(z.string()).optional().describe('请求头')
155
158
  }),
156
159
  execute: async (args) => this._sendRequest(args.method, args.path, args.body, args.headers)
157
- })
160
+ }
158
161
  }
159
162
 
160
163
  // ==================== 服务器控制 ====================
@@ -241,9 +244,12 @@ class WebPlugin extends Plugin {
241
244
  }
242
245
  }
243
246
 
244
- // 2. Webhook(精确匹配)
247
+ // 2. Webhook(仅接受 POST)
245
248
  const webhook = this._webhooks.get(pathname)
246
249
  if (webhook) {
250
+ if (c.req.method !== 'POST') {
251
+ return c.json({ success: false, error: 'Method Not Allowed. Webhook only accepts POST.' }, 405)
252
+ }
247
253
  const result = await this._handleWebhook(c, webhook)
248
254
  return c.json(result)
249
255
  }
@@ -336,7 +342,7 @@ class WebPlugin extends Plugin {
336
342
  }
337
343
 
338
344
  // 触发 webhook 处理完成事件
339
- this._framework.emit('webhook:received', { webhook, data: webhookData, response: responseText, sessionId: finalSessionId })
345
+ this._framework.emit('webhook:processed', { webhook, data: webhookData, response: responseText, sessionId: finalSessionId })
340
346
  }).catch(err => {
341
347
  log.error(' Webhook error:', err.message)
342
348
  })
@@ -535,8 +541,33 @@ class WebPlugin extends Plugin {
535
541
 
536
542
  async _executeHandler(handlerCode, context, tools) {
537
543
  try {
544
+ // 辅助函数
545
+ const helpers = {
546
+ // 基础工具
547
+ echo: (val) => val,
548
+ json: (val) => JSON.stringify(val),
549
+ JSON: JSON,
550
+ // 日期时间
551
+ Date: Date,
552
+ now: () => Date.now(),
553
+ // 字符串
554
+ Str: String,
555
+ // 编码
556
+ btoa: (str) => Buffer.from(str).toString('base64'),
557
+ atob: (str) => Buffer.from(str, 'base64').toString(),
558
+ // 格式化
559
+ template: (str, vars) => str.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`),
560
+ // 随机
561
+ uuid: () => Math.random().toString(36).substring(2) + Date.now().toString(36),
562
+ // HTTP 请求(通过 fetch)
563
+ fetch: async (url, options = {}) => {
564
+ const { method = 'GET', headers = {}, body } = options
565
+ const res = await fetch(url, { method, headers, body })
566
+ return { status: res.status, headers: Object.fromEntries(res.headers), body: await res.text() }
567
+ }
568
+ }
538
569
  // 使用沙箱执行用户代码,防止恶意代码执行
539
- return await runInSandbox(handlerCode, { context, tools }, { timeout: 10000 })
570
+ return await runInSandbox(handlerCode, { context, tools, ...helpers }, { timeout: 10000 })
540
571
  } catch (err) {
541
572
  return { success: false, error: `Handler error: ${err.message}` }
542
573
  }
@@ -603,4 +634,4 @@ class WebPlugin extends Plugin {
603
634
  }
604
635
  }
605
636
 
606
- module.exports = { WebPlugin }
637
+ module.exports = WebPlugin
@@ -543,4 +543,4 @@ class WeixinPlugin extends Plugin {
543
543
  }
544
544
  }
545
545
 
546
- module.exports = { WeixinPlugin }
546
+ module.exports = WeixinPlugin