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,343 @@
1
+ /**
2
+ * Session 会话管理插件
3
+ * 管理多个用户会话,支持会话隔离和历史记录
4
+ */
5
+
6
+ const { Plugin } = require('../src/core/plugin-base')
7
+ const { z } = require('zod')
8
+
9
+ /**
10
+ * 生成 UUID
11
+ */
12
+ function generateId() {
13
+ if (require('crypto').randomUUID) {
14
+ return require('crypto').randomUUID()
15
+ }
16
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
17
+ const r = Math.random() * 16 | 0
18
+ const v = c === 'x' ? r : (r & 0x3 | 0x8)
19
+ return v.toString(16)
20
+ })
21
+ }
22
+
23
+ class SessionPlugin extends Plugin {
24
+ constructor(config = {}) {
25
+ super()
26
+ this.name = 'session'
27
+ this.version = '1.0.0'
28
+ this.description = '会话管理插件,支持多会话隔离、历史记录、会话超时清理'
29
+ this.priority = 5
30
+
31
+ this.config = {
32
+ sessionTTL: config.sessionTTL || 30 * 60 * 1000, // 30分钟
33
+ maxSessions: config.maxSessions || 100,
34
+ maxHistoryLength: config.maxHistoryLength || 50,
35
+ autoCleanup: config.autoCleanup !== false,
36
+ cleanupInterval: config.cleanupInterval || 5 * 60 * 1000 // 5分钟
37
+ }
38
+
39
+ this._framework = null
40
+ this._sessions = new Map()
41
+ this._cleanupTimer = null
42
+ }
43
+
44
+ install(framework) {
45
+ this._framework = framework
46
+ return this
47
+ }
48
+
49
+ start(framework) {
50
+ // 注册会话管理工具
51
+ framework.registerTool({
52
+ name: 'session_create',
53
+ description: '创建新会话',
54
+ inputSchema: z.object({
55
+ sessionId: z.string().optional().describe('可选:指定会话 ID'),
56
+ metadata: z.object({}).optional().describe('会话元数据')
57
+ }),
58
+ execute: async (args) => {
59
+ const sessionId = args.sessionId || `session_${generateId()}`
60
+ const session = this.createSession(sessionId, { metadata: args.metadata })
61
+ return {
62
+ success: true,
63
+ sessionId: session.id,
64
+ createdAt: session.createdAt
65
+ }
66
+ }
67
+ })
68
+
69
+ framework.registerTool({
70
+ name: 'session_get',
71
+ description: '获取会话信息',
72
+ inputSchema: z.object({
73
+ sessionId: z.string().describe('会话 ID')
74
+ }),
75
+ execute: async (args) => {
76
+ const session = this.getSession(args.sessionId)
77
+ if (!session) {
78
+ return { success: false, error: 'Session not found' }
79
+ }
80
+ return {
81
+ success: true,
82
+ sessionId: session.id,
83
+ messageCount: session.messages.length,
84
+ variables: Object.keys(session.variables),
85
+ createdAt: session.createdAt,
86
+ lastActive: session.lastActive
87
+ }
88
+ }
89
+ })
90
+
91
+ framework.registerTool({
92
+ name: 'session_list',
93
+ description: '列出所有会话',
94
+ inputSchema: z.object({}),
95
+ execute: async () => {
96
+ const sessions = this.listSessions()
97
+ return {
98
+ success: true,
99
+ sessions,
100
+ total: sessions.length
101
+ }
102
+ }
103
+ })
104
+
105
+ framework.registerTool({
106
+ name: 'session_delete',
107
+ description: '删除会话',
108
+ inputSchema: z.object({
109
+ sessionId: z.string().describe('会话 ID')
110
+ }),
111
+ execute: async (args) => {
112
+ const deleted = this.deleteSession(args.sessionId)
113
+ return { success: deleted, deleted: args.sessionId }
114
+ }
115
+ })
116
+
117
+ framework.registerTool({
118
+ name: 'session_history',
119
+ description: '获取会话历史消息',
120
+ inputSchema: z.object({
121
+ sessionId: z.string().describe('会话 ID'),
122
+ limit: z.number().optional().describe('返回消息数量限制')
123
+ }),
124
+ execute: async (args) => {
125
+ const history = this.getHistory(args.sessionId, args.limit)
126
+ return {
127
+ success: true,
128
+ sessionId: args.sessionId,
129
+ messages: history,
130
+ count: history.length
131
+ }
132
+ }
133
+ })
134
+
135
+ framework.registerTool({
136
+ name: 'session_stats',
137
+ description: '获取会话统计信息',
138
+ inputSchema: z.object({}),
139
+ execute: async () => {
140
+ return {
141
+ success: true,
142
+ stats: this.getMemoryStats()
143
+ }
144
+ }
145
+ })
146
+
147
+ // 启动自动清理
148
+ if (this.config.autoCleanup) {
149
+ this._startAutoCleanup()
150
+ }
151
+
152
+ return this
153
+ }
154
+
155
+ /**
156
+ * 创建新会话
157
+ */
158
+ createSession(sessionId, options = {}) {
159
+ const id = sessionId || `session_${generateId()}`
160
+
161
+ if (this._sessions.has(id)) {
162
+ return this._sessions.get(id)
163
+ }
164
+
165
+ // 如果会话数超过限制,清理最老的
166
+ if (this._sessions.size >= this.config.maxSessions) {
167
+ this._cleanupLRU()
168
+ }
169
+
170
+ const session = {
171
+ id,
172
+ messages: options.initialMessages || [],
173
+ variables: options.variables || {},
174
+ metadata: options.metadata || {},
175
+ createdAt: new Date(),
176
+ lastActive: new Date()
177
+ }
178
+
179
+ this._sessions.set(id, session)
180
+ return session
181
+ }
182
+
183
+ /**
184
+ * 获取会话
185
+ */
186
+ getSession(sessionId) {
187
+ const session = this._sessions.get(sessionId)
188
+ if (session) {
189
+ session.lastActive = new Date()
190
+ }
191
+ return session
192
+ }
193
+
194
+ /**
195
+ * 获取或创建会话
196
+ */
197
+ getOrCreateSession(sessionId, options = {}) {
198
+ let session = this.getSession(sessionId)
199
+ if (!session) {
200
+ session = this.createSession(sessionId, options)
201
+ }
202
+ return session
203
+ }
204
+
205
+ /**
206
+ * 添加消息到会话
207
+ */
208
+ addMessage(sessionId, message) {
209
+ const session = this.getSession(sessionId)
210
+ if (session) {
211
+ session.messages.push(message)
212
+ session.lastActive = new Date()
213
+
214
+ // 限制历史长度
215
+ if (session.messages.length > this.config.maxHistoryLength) {
216
+ session.messages = session.messages.slice(-this.config.maxHistoryLength)
217
+ }
218
+ }
219
+ return session
220
+ }
221
+
222
+ /**
223
+ * 获取会话历史
224
+ */
225
+ getHistory(sessionId, limit = null) {
226
+ const session = this.getSession(sessionId)
227
+ if (!session) return []
228
+
229
+ const messages = session.messages
230
+ if (limit) {
231
+ return messages.slice(-limit)
232
+ }
233
+ return messages
234
+ }
235
+
236
+ /**
237
+ * 删除会话
238
+ */
239
+ deleteSession(sessionId) {
240
+ return this._sessions.delete(sessionId)
241
+ }
242
+
243
+ /**
244
+ * 列出所有会话
245
+ */
246
+ listSessions() {
247
+ return Array.from(this._sessions.values()).map(s => ({
248
+ id: s.id,
249
+ messageCount: s.messages.length,
250
+ createdAt: s.createdAt,
251
+ lastActive: s.lastActive,
252
+ metadata: s.metadata
253
+ }))
254
+ }
255
+
256
+ /**
257
+ * 清理过期会话
258
+ */
259
+ cleanup() {
260
+ const now = Date.now()
261
+ let cleanedCount = 0
262
+
263
+ for (const [sessionId, session] of this._sessions) {
264
+ if (now - session.lastActive.getTime() > this.config.sessionTTL) {
265
+ this._sessions.delete(sessionId)
266
+ cleanedCount++
267
+ }
268
+ }
269
+
270
+ return cleanedCount
271
+ }
272
+
273
+ /**
274
+ * LRU 清理 - 删除最老的 20% 会话
275
+ */
276
+ _cleanupLRU() {
277
+ const sessions = Array.from(this._sessions.values())
278
+ if (sessions.length === 0) return
279
+
280
+ // 按最后活跃时间排序
281
+ sessions.sort((a, b) => a.lastActive.getTime() - b.lastActive.getTime())
282
+
283
+ // 删除最老的会话
284
+ const toDelete = sessions.slice(0, Math.ceil(sessions.length * 0.2))
285
+ for (const session of toDelete) {
286
+ this._sessions.delete(session.id)
287
+ }
288
+ }
289
+
290
+ /**
291
+ * 获取内存统计
292
+ */
293
+ getMemoryStats() {
294
+ const sessionCount = this._sessions.size
295
+ const totalMessages = Array.from(this._sessions.values()).reduce(
296
+ (sum, s) => sum + s.messages.length, 0
297
+ )
298
+
299
+ return {
300
+ sessionCount,
301
+ maxSessions: this.config.maxSessions,
302
+ totalMessages,
303
+ ttl: this.config.sessionTTL
304
+ }
305
+ }
306
+
307
+ /**
308
+ * 清理所有会话
309
+ */
310
+ clear() {
311
+ this._sessions.clear()
312
+ }
313
+
314
+ /**
315
+ * 启动自动清理定时器
316
+ */
317
+ _startAutoCleanup() {
318
+ if (this._cleanupTimer) {
319
+ clearInterval(this._cleanupTimer)
320
+ }
321
+ this._cleanupTimer = setInterval(() => {
322
+ const cleaned = this.cleanup()
323
+ if (cleaned > 0) {
324
+ console.log(`[SessionPlugin] Cleaned up ${cleaned} expired sessions`)
325
+ }
326
+ }, this.config.cleanupInterval)
327
+ }
328
+
329
+ reload(framework) {
330
+ this._framework = framework
331
+ }
332
+
333
+ uninstall(framework) {
334
+ if (this._cleanupTimer) {
335
+ clearInterval(this._cleanupTimer)
336
+ this._cleanupTimer = null
337
+ }
338
+ this._sessions.clear()
339
+ this._framework = null
340
+ }
341
+ }
342
+
343
+ module.exports = { SessionPlugin }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Shell 执行器插件
3
+ * 使用 child_process 执行终端命令
4
+ */
5
+
6
+ const { Plugin } = require('../src/core/plugin-base')
7
+ const { spawn } = require('child_process')
8
+ const { z } = require('zod')
9
+
10
+ class ShellExecutorPlugin extends Plugin {
11
+ constructor(config = {}) {
12
+ super()
13
+ this.name = 'shell-executor'
14
+ this.version = '1.0.0'
15
+ this.description = 'Shell 执行器,用于运行终端命令和脚本'
16
+ this.priority = 15
17
+
18
+ this.config = {
19
+ timeout: config.timeout || 60000,
20
+ workingDir: config.workingDir || process.cwd()
21
+ }
22
+
23
+ this._framework = null
24
+ }
25
+
26
+ install(framework) {
27
+ this._framework = framework
28
+ return this
29
+ }
30
+
31
+ start(framework) {
32
+ // 注册 shell 工具
33
+ framework.registerTool({
34
+ name: 'shell',
35
+ description: '执行 Shell 命令',
36
+ inputSchema: z.object({
37
+ command: z.string().describe('要执行的命令'),
38
+ cwd: z.string().optional().describe('工作目录,默认为当前目录'),
39
+ timeout: z.number().optional().describe('超时时间(毫秒),默认 60000')
40
+ }),
41
+ execute: async (args) => {
42
+ const cwd = args.cwd || this.config.workingDir
43
+ const timeout = args.timeout || this.config.timeout
44
+
45
+ return new Promise((resolve) => {
46
+ const startTime = Date.now()
47
+ let output = ''
48
+ let errorOutput = ''
49
+
50
+ // 检测命令类型
51
+ const isWindows = process.platform === 'win32'
52
+ const shell = isWindows ? 'cmd.exe' : '/bin/sh'
53
+ const shellArg = isWindows ? '/c' : '-c'
54
+
55
+ const proc = spawn(shell, [shellArg, args.command], {
56
+ cwd,
57
+ timeout,
58
+ env: { ...process.env }
59
+ })
60
+
61
+ proc.stdout.on('data', (data) => {
62
+ output += data.toString()
63
+ })
64
+
65
+ proc.stderr.on('data', (data) => {
66
+ errorOutput += data.toString()
67
+ })
68
+
69
+ proc.on('close', (code) => {
70
+ const elapsed = Date.now() - startTime
71
+ resolve({
72
+ success: code === 0,
73
+ exitCode: code,
74
+ stdout: output,
75
+ stderr: errorOutput,
76
+ elapsed,
77
+ command: args.command,
78
+ cwd
79
+ })
80
+ })
81
+
82
+ proc.on('error', (err) => {
83
+ resolve({
84
+ success: false,
85
+ error: err.message,
86
+ command: args.command,
87
+ cwd
88
+ })
89
+ })
90
+
91
+ // 超时处理
92
+ setTimeout(() => {
93
+ if (!proc.killed) {
94
+ proc.kill('SIGTERM')
95
+ resolve({
96
+ success: false,
97
+ error: `Command timed out after ${timeout}ms`,
98
+ stdout: output,
99
+ stderr: errorOutput,
100
+ command: args.command,
101
+ cwd,
102
+ timedOut: true
103
+ })
104
+ }
105
+ }, timeout)
106
+ })
107
+ }
108
+ })
109
+
110
+ // 注册 PowerShell 工具 (Windows)
111
+ if (process.platform === 'win32') {
112
+ framework.registerTool({
113
+ name: 'powershell',
114
+ description: '执行 PowerShell 命令 (仅 Windows)',
115
+ inputSchema: z.object({
116
+ command: z.string().describe('要执行的 PowerShell 命令'),
117
+ cwd: z.string().optional().describe('工作目录'),
118
+ timeout: z.number().optional().describe('超时时间(毫秒)')
119
+ }),
120
+ execute: async (args) => {
121
+ const cwd = args.cwd || this.config.workingDir
122
+ const timeout = args.timeout || this.config.timeout
123
+
124
+ return new Promise((resolve) => {
125
+ const startTime = Date.now()
126
+ let output = ''
127
+ let errorOutput = ''
128
+
129
+ const proc = spawn('powershell.exe', ['-NoProfile', '-Command', args.command], {
130
+ cwd,
131
+ timeout,
132
+ env: { ...process.env }
133
+ })
134
+
135
+ proc.stdout.on('data', (data) => {
136
+ output += data.toString()
137
+ })
138
+
139
+ proc.stderr.on('data', (data) => {
140
+ errorOutput += data.toString()
141
+ })
142
+
143
+ proc.on('close', (code) => {
144
+ const elapsed = Date.now() - startTime
145
+ resolve({
146
+ success: code === 0,
147
+ exitCode: code,
148
+ stdout: output,
149
+ stderr: errorOutput,
150
+ elapsed,
151
+ command: args.command,
152
+ cwd
153
+ })
154
+ })
155
+
156
+ proc.on('error', (err) => {
157
+ resolve({
158
+ success: false,
159
+ error: err.message,
160
+ command: args.command,
161
+ cwd
162
+ })
163
+ })
164
+
165
+ setTimeout(() => {
166
+ if (!proc.killed) {
167
+ proc.kill('SIGTERM')
168
+ resolve({
169
+ success: false,
170
+ error: `Command timed out after ${timeout}ms`,
171
+ stdout: output,
172
+ stderr: errorOutput,
173
+ command: args.command,
174
+ cwd,
175
+ timedOut: true
176
+ })
177
+ }
178
+ }, timeout)
179
+ })
180
+ }
181
+ })
182
+ }
183
+
184
+ return this
185
+ }
186
+
187
+ reload(framework) {
188
+ this._framework = framework
189
+ }
190
+
191
+ uninstall(framework) {
192
+ this._framework = null
193
+ }
194
+ }
195
+
196
+ module.exports = { ShellExecutorPlugin }