foliko 1.1.92 → 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.
Files changed (212) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CLAUDE.md +56 -30
  3. package/REFACTORING_PLAN.md +645 -0
  4. package/docs/architecture.md +131 -0
  5. package/docs/migration.md +57 -0
  6. package/docs/public-api.md +138 -0
  7. package/docs/usage.md +385 -0
  8. package/examples/ambient-example.js +20 -137
  9. package/examples/basic.js +21 -48
  10. package/examples/bootstrap.js +16 -74
  11. package/examples/mcp-example.js +6 -29
  12. package/examples/skill-example.js +6 -19
  13. package/examples/workflow.js +8 -56
  14. package/package.json +8 -4
  15. package/plugins/README.md +49 -0
  16. package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
  17. package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
  18. package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
  19. package/plugins/ambient/README.md +14 -0
  20. package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
  21. package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
  22. package/plugins/{ambient-agent → ambient}/index.js +2 -2
  23. package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
  24. package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
  25. package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
  26. package/plugins/core/default/bootstrap.js +202 -0
  27. package/plugins/core/default/config.js +220 -0
  28. package/plugins/core/default/index.js +58 -0
  29. package/plugins/core/mcp/index.js +1 -0
  30. package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
  31. package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
  32. package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
  33. package/plugins/{session-plugin.js → core/session/index.js} +9 -73
  34. package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
  35. package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
  36. package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
  37. package/plugins/{think-plugin.js → core/think/index.js} +24 -91
  38. package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
  39. package/plugins/default-plugins.js +6 -720
  40. package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
  41. package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
  42. package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
  43. package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
  44. package/plugins/install/README.md +9 -0
  45. package/plugins/{install-plugin.js → install/index.js} +3 -3
  46. package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
  47. package/plugins/{web-plugin.js → io/web/index.js} +11 -113
  48. package/plugins/memory/README.md +13 -0
  49. package/plugins/{memory-plugin.js → memory/index.js} +4 -18
  50. package/plugins/messaging/email/README.md +19 -0
  51. package/plugins/{email → messaging/email}/index.js +2 -2
  52. package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
  53. package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
  54. package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
  55. package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
  56. package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
  57. package/plugins/{tools-plugin.js → tools/index.js} +68 -116
  58. package/plugins/trading/README.md +15 -0
  59. package/plugins/{gate-trading.js → trading/index.js} +8 -8
  60. package/{examples → sandbox}/test-concurrent-chat.js +2 -2
  61. package/{examples → sandbox}/test-long-chat.js +2 -2
  62. package/{examples → sandbox}/test-session-chat.js +2 -2
  63. package/{examples → sandbox}/test-web-plugin.js +1 -1
  64. package/{examples → sandbox}/test-weixin-feishu.js +2 -2
  65. package/src/agent/base.js +56 -0
  66. package/src/{core/agent-chat.js → agent/chat.js} +11 -11
  67. package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
  68. package/src/agent/index.js +111 -0
  69. package/src/agent/main.js +337 -0
  70. package/src/agent/prompt.js +78 -0
  71. package/src/agent/sub.js +198 -0
  72. package/src/agent/worker.js +104 -0
  73. package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
  74. package/{cli/src → src/cli}/commands/chat.js +25 -21
  75. package/{cli/src → src/cli}/index.js +1 -0
  76. package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
  77. package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
  78. package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
  79. package/src/{core → common}/constants.js +3 -0
  80. package/src/common/errors.js +402 -0
  81. package/src/{utils → common}/logger.js +33 -0
  82. package/src/{utils/chat-queue.js → common/queue.js} +2 -2
  83. package/src/config/plugin-config.js +50 -0
  84. package/src/context/agent.js +32 -0
  85. package/src/context/compaction-prompts.js +170 -0
  86. package/src/context/compaction-utils.js +191 -0
  87. package/src/context/compressor.js +413 -0
  88. package/src/context/index.js +9 -0
  89. package/src/{core/context-manager.js → context/manager.js} +1 -1
  90. package/src/context/request.js +50 -0
  91. package/src/context/session.js +33 -0
  92. package/src/context/storage.js +30 -0
  93. package/src/executors/mcp-client.js +153 -0
  94. package/src/executors/mcp-desc.js +236 -0
  95. package/src/executors/mcp-executor.js +91 -956
  96. package/src/{core → framework}/command-registry.js +1 -1
  97. package/src/framework/framework.js +300 -0
  98. package/src/framework/index.js +18 -0
  99. package/src/framework/lifecycle.js +203 -0
  100. package/src/framework/loader.js +78 -0
  101. package/src/framework/registry.js +86 -0
  102. package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
  103. package/src/index.js +130 -15
  104. package/src/llm/index.js +26 -0
  105. package/src/llm/provider.js +212 -0
  106. package/src/llm/registry.js +11 -0
  107. package/src/{core/token-counter.js → llm/tokens.js} +4 -37
  108. package/src/{core/plugin-base.js → plugin/base.js} +10 -136
  109. package/src/plugin/index.js +14 -0
  110. package/src/plugin/loader.js +101 -0
  111. package/src/plugin/manager.js +261 -0
  112. package/src/{core → session}/branch-summary-auto.js +2 -2
  113. package/src/{core/chat-session.js → session/chat.js} +2 -2
  114. package/src/session/index.js +7 -0
  115. package/src/{core/session-manager.js → session/session.js} +2 -2
  116. package/src/session/ttl.js +92 -0
  117. package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
  118. package/src/tool/executor.js +85 -0
  119. package/src/tool/index.js +15 -0
  120. package/src/tool/registry.js +143 -0
  121. package/src/{core/tool-router.js → tool/router.js} +17 -124
  122. package/src/tool/schema.js +108 -0
  123. package/src/utils/data-splitter.js +1 -1
  124. package/src/utils/download.js +1 -1
  125. package/src/utils/index.js +6 -6
  126. package/src/utils/message-validator.js +1 -1
  127. package/tests/core/context-storage.test.js +46 -0
  128. package/tests/core/llm.test.js +54 -0
  129. package/tests/core/plugin.test.js +42 -0
  130. package/tests/core/tool.test.js +60 -0
  131. package/tests/setup.js +10 -0
  132. package/tests/smoke.test.js +58 -0
  133. package/vitest.config.js +9 -0
  134. package/cli/src/daemon.js +0 -149
  135. package/docs/CONTEXT_DESIGN.md +0 -1596
  136. package/docs/ai-sdk-optimization.md +0 -655
  137. package/docs/features.md +0 -120
  138. package/docs/qq-bot.md +0 -976
  139. package/docs/quick-reference.md +0 -160
  140. package/docs/user-manual.md +0 -1391
  141. package/images/geometric_shapes.jpg +0 -0
  142. package/images/sunset_mountain_lake.jpg +0 -0
  143. package/skills/poster-guide/SKILL.md +0 -792
  144. package/src/capabilities/index.js +0 -11
  145. package/src/core/agent.js +0 -808
  146. package/src/core/context-compressor.js +0 -959
  147. package/src/core/enhanced-context-compressor.js +0 -210
  148. package/src/core/framework.js +0 -1422
  149. package/src/core/index.js +0 -30
  150. package/src/core/plugin-manager.js +0 -961
  151. package/src/core/provider-registry.js +0 -159
  152. package/src/core/provider.js +0 -156
  153. package/src/core/request-context.js +0 -98
  154. package/src/core/subagent.js +0 -442
  155. package/src/core/system-prompt-builder.js +0 -120
  156. package/src/core/tool-executor.js +0 -202
  157. package/src/core/tool-registry.js +0 -517
  158. package/src/core/worker-agent.js +0 -192
  159. package/src/executors/executor-base.js +0 -58
  160. package/src/utils/error-boundary.js +0 -363
  161. package/src/utils/error.js +0 -374
  162. package/system.md +0 -1645
  163. package/website_v2/README.md +0 -57
  164. package/website_v2/SPEC.md +0 -1
  165. package/website_v2/docs/api.html +0 -128
  166. package/website_v2/docs/configuration.html +0 -147
  167. package/website_v2/docs/plugin-development.html +0 -129
  168. package/website_v2/docs/project-structure.html +0 -89
  169. package/website_v2/docs/skill-development.html +0 -85
  170. package/website_v2/index.html +0 -489
  171. package/website_v2/scripts/main.js +0 -93
  172. package/website_v2/styles/animations.css +0 -8
  173. package/website_v2/styles/docs.css +0 -83
  174. package/website_v2/styles/main.css +0 -417
  175. package/xhs_auth.json +0 -268
  176. package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
  177. /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
  178. /package/plugins/{email → messaging/email}/constants.js +0 -0
  179. /package/plugins/{email → messaging/email}/handlers.js +0 -0
  180. /package/plugins/{email → messaging/email}/monitor.js +0 -0
  181. /package/plugins/{email → messaging/email}/parser.js +0 -0
  182. /package/plugins/{email → messaging/email}/reply.js +0 -0
  183. /package/plugins/{email → messaging/email}/utils.js +0 -0
  184. /package/{examples → sandbox}/test-chat.js +0 -0
  185. /package/{examples → sandbox}/test-mcp.js +0 -0
  186. /package/{examples → sandbox}/test-reload.js +0 -0
  187. /package/{examples → sandbox}/test-telegram.js +0 -0
  188. /package/{examples → sandbox}/test-tg-bot.js +0 -0
  189. /package/{examples → sandbox}/test-tg-simple.js +0 -0
  190. /package/{examples → sandbox}/test-tg.js +0 -0
  191. /package/{examples → sandbox}/test-think.js +0 -0
  192. /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
  193. /package/{cli/src → src/cli}/commands/daemon.js +0 -0
  194. /package/{cli/src → src/cli}/commands/list.js +0 -0
  195. /package/{cli/src → src/cli}/commands/plugin.js +0 -0
  196. /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
  197. /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
  198. /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
  199. /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
  200. /package/{cli/src → src/cli}/utils/ansi.js +0 -0
  201. /package/{cli/src → src/cli}/utils/config.js +0 -0
  202. /package/{cli/src → src/cli}/utils/markdown.js +0 -0
  203. /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
  204. /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
  205. /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
  206. /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
  207. /package/src/{utils/event-emitter.js → common/events.js} +0 -0
  208. /package/src/{utils → common}/id.js +0 -0
  209. /package/src/{utils → common}/retry.js +0 -0
  210. /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
  211. /package/src/{core/session-entry.js → session/entry.js} +0 -0
  212. /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Tool Schema utilities
5
+ * Zod/JSON-Schema adaptation helpers
6
+ */
7
+
8
+ const { z } = require('zod');
9
+
10
+ /**
11
+ * Convert JSON Schema to Zod schema
12
+ * @param {Object} jsonSchema
13
+ * @param {boolean} [required] - Whether this schema is required (optional when false)
14
+ * @returns {z.ZodType}
15
+ */
16
+ function jsonSchemaToZod(jsonSchema, required) {
17
+ if (!jsonSchema || typeof jsonSchema !== 'object') {
18
+ return z.any();
19
+ }
20
+
21
+ return _convertSchema(jsonSchema, required);
22
+ }
23
+
24
+ function _convertSchema(schema, required = false) {
25
+ if (!schema) return z.any();
26
+
27
+ let type = schema.type;
28
+ if (Array.isArray(type)) {
29
+ const nonNull = type.filter(t => t !== 'null');
30
+ type = nonNull.length === 1 ? nonNull[0] : 'any';
31
+ }
32
+
33
+ let zodType;
34
+
35
+ switch (type) {
36
+ case 'string': {
37
+ zodType = z.string();
38
+ if (schema.enum) zodType = z.enum(schema.enum);
39
+ if (schema.pattern) zodType = zodType.regex(new RegExp(schema.pattern));
40
+ if (schema.minLength) zodType = zodType.min(schema.minLength);
41
+ if (schema.maxLength) zodType = zodType.max(schema.maxLength);
42
+ break;
43
+ }
44
+ case 'number':
45
+ zodType = z.number();
46
+ if (schema.minimum !== undefined) zodType = zodType.min(schema.minimum);
47
+ if (schema.maximum !== undefined) zodType = zodType.max(schema.maximum);
48
+ break;
49
+ case 'integer':
50
+ zodType = z.number().int();
51
+ if (schema.minimum !== undefined) zodType = zodType.min(schema.minimum);
52
+ if (schema.maximum !== undefined) zodType = zodType.max(schema.maximum);
53
+ break;
54
+ case 'boolean':
55
+ zodType = z.boolean();
56
+ break;
57
+ case 'array': {
58
+ if (schema.items) {
59
+ zodType = z.array(_convertSchema(schema.items, true));
60
+ } else {
61
+ zodType = z.array(z.any());
62
+ }
63
+ break;
64
+ }
65
+ case 'object': {
66
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
67
+ zodType = z.record(z.any());
68
+ break;
69
+ }
70
+ const shape = {};
71
+ const req = schema.required || [];
72
+ for (const [key, prop] of Object.entries(schema.properties)) {
73
+ shape[key] = _convertSchema(prop, req.includes(key));
74
+ }
75
+ const obj = z.object(shape);
76
+ zodType = req.length === Object.keys(shape).length ? obj : obj.partial();
77
+ break;
78
+ }
79
+ case 'null':
80
+ zodType = z.null();
81
+ break;
82
+ default:
83
+ zodType = z.any();
84
+ }
85
+
86
+ if (schema.nullable) zodType = zodType.nullable();
87
+ if (!required && schema.default === undefined) zodType = zodType.optional();
88
+
89
+ return zodType;
90
+ }
91
+
92
+ /**
93
+ * Convert tool definition to AI SDK tool format
94
+ */
95
+ function toAISDKTool(toolDef) {
96
+ const parameters = toolDef.inputSchema || toolDef.parameters;
97
+ return {
98
+ type: 'function',
99
+ name: toolDef.name,
100
+ description: toolDef.description || '',
101
+ parameters: parameters || { type: 'object', properties: {} },
102
+ };
103
+ }
104
+
105
+ module.exports = {
106
+ jsonSchemaToZod,
107
+ toAISDKTool,
108
+ };
@@ -12,7 +12,7 @@
12
12
  * - 任何超过上下文限制的工具结果
13
13
  */
14
14
 
15
- const { logger } = require('./logger');
15
+ const { logger } = require('../common/logger');
16
16
 
17
17
  // 默认分块大小 (字符数,约 50K tokens)
18
18
  const DEFAULT_CHUNK_SIZE = 60000;
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const crypto = require('crypto');
7
7
  const { fileTypeFromFile, fileTypeFromBuffer } = require('file-type');
8
8
  const { downloadAndDecryptMedia } = require('@chnak/weixin-bot');
9
- const { logger } = require('./logger');
9
+ const { logger } = require('../common/logger');
10
10
  class FileDownloader {
11
11
  constructor(options = {}) {
12
12
  this.timeout = options.timeout || 30000;
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  // 日志系统
6
- const { Logger, logger, LOG_LEVELS } = require('./logger');
6
+ const { Logger, logger, LOG_LEVELS } = require('../common/logger');
7
7
 
8
8
  // ID 生成器
9
9
  const {
@@ -17,7 +17,7 @@ const {
17
17
  messageId,
18
18
  ruleId,
19
19
  resetSnowflake,
20
- } = require('./id');
20
+ } = require('../common/id');
21
21
 
22
22
  // 错误处理
23
23
  const {
@@ -33,10 +33,10 @@ const {
33
33
  PluginNotFoundError,
34
34
  isErrorOfType,
35
35
  safeErrorInfo,
36
- } = require('./error');
36
+ } = require('../common/errors');
37
37
 
38
38
  // 重试策略
39
- const { withRetry, retryable, isNetworkError, PRESETS } = require('./retry');
39
+ const { withRetry, retryable, isNetworkError, PRESETS } = require('../common/retry');
40
40
 
41
41
  // 错误边界
42
42
  const {
@@ -45,7 +45,7 @@ const {
45
45
  RecoveryAction,
46
46
  createErrorBoundary,
47
47
  combineBoundaries,
48
- } = require('./error-boundary');
48
+ } = require('../common/errors');
49
49
 
50
50
  /**
51
51
  * 清理 LLM 回复中的思考标记
@@ -538,5 +538,5 @@ module.exports = {
538
538
  prepareMessagesForAPI,
539
539
 
540
540
  // Edit/Diff
541
- editDiff: require('./edit-diff'),
541
+ editDiff: require('../common/diff'),
542
542
  };
@@ -8,7 +8,7 @@
8
8
  * - 减少消息数组多次遍历
9
9
  */
10
10
 
11
- const { logger } = require('./logger');
11
+ const { logger } = require('../common/logger');
12
12
 
13
13
  /**
14
14
  * 收集所有 assistant 消息中的 tool-call ID
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('ContextStorage', () => {
4
+ it('should provide request/session/agent context isolation', () => {
5
+ const storage = require('../../src/context/storage');
6
+
7
+ const results = [];
8
+
9
+ storage.runRequest({ req: 'A' }, () => {
10
+ results.push(storage.getRequest());
11
+ });
12
+
13
+ storage.runSession({ sess: 'B' }, () => {
14
+ results.push(storage.getSession());
15
+ });
16
+
17
+ storage.runAgent({ agent: 'C' }, () => {
18
+ results.push(storage.getAgent());
19
+ });
20
+
21
+ expect(results[0]).toEqual({ req: 'A' });
22
+ expect(results[1]).toEqual({ sess: 'B' });
23
+ expect(results[2]).toEqual({ agent: 'C' });
24
+ });
25
+
26
+ it('should maintain isolation between nested contexts', () => {
27
+ const storage = require('../../src/context/storage');
28
+
29
+ const collected = [];
30
+
31
+ storage.runRequest({ id: 1 }, () => {
32
+ storage.runSession({ sid: 's1' }, () => {
33
+ collected.push({ req: storage.getRequest(), sess: storage.getSession() });
34
+ });
35
+ });
36
+
37
+ storage.runRequest({ id: 2 }, () => {
38
+ collected.push({ req: storage.getRequest(), sess: storage.getSession() });
39
+ });
40
+
41
+ expect(collected[0].req.id).toBe(1);
42
+ expect(collected[0].sess.sid).toBe('s1');
43
+ expect(collected[1].req.id).toBe(2);
44
+ expect(collected[1].sess).toBeUndefined();
45
+ });
46
+ });
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('LLM Module', () => {
4
+ it('should export all expected symbols', () => {
5
+ const llm = require('../../src/llm');
6
+ expect(llm.createAI).toBeDefined();
7
+ expect(llm.createModel).toBeDefined();
8
+ expect(llm.getAvailableProviders).toBeDefined();
9
+ expect(llm.DEFAULT_PROVIDERS).toBeDefined();
10
+ expect(llm.isThinkingModel).toBeDefined();
11
+ expect(llm.ProviderRegistry).toBeDefined();
12
+ expect(llm.getProviderRegistry).toBeDefined();
13
+ expect(llm.TokenCounter).toBeDefined();
14
+ expect(llm.encode).toBeDefined();
15
+ expect(llm.estimateTokens).toBeDefined();
16
+ expect(llm.estimateContextTokens).toBeDefined();
17
+ expect(llm.shouldCompact).toBeDefined();
18
+ });
19
+
20
+ it('should detect thinking models', () => {
21
+ const { isThinkingModel } = require('../../src/llm');
22
+ expect(isThinkingModel('deepseek-v4-pro')).toBe(true);
23
+ expect(isThinkingModel('deepseek-v4-flash')).toBe(true);
24
+ expect(isThinkingModel('deepseek-reasoner')).toBe(true);
25
+ expect(isThinkingModel('gpt-4o')).toBe(false);
26
+ expect(isThinkingModel(null)).toBe(false);
27
+ });
28
+
29
+ it('should provide default provider list', () => {
30
+ const { getAvailableProviders, DEFAULT_PROVIDERS } = require('../../src/llm');
31
+ const providers = getAvailableProviders();
32
+ expect(Array.isArray(providers)).toBe(true);
33
+ expect(providers.length).toBeGreaterThan(0);
34
+ expect(DEFAULT_PROVIDERS.deepseek).toBeDefined();
35
+ expect(DEFAULT_PROVIDERS.anthropic).toBeDefined();
36
+ });
37
+
38
+ it('should have ProviderRegistry singleton', () => {
39
+ const { getProviderRegistry } = require('../../src/llm');
40
+ const reg1 = getProviderRegistry();
41
+ const reg2 = getProviderRegistry();
42
+ expect(reg1).toBe(reg2);
43
+ expect(reg1.getProviderNames().length).toBeGreaterThan(0);
44
+ });
45
+
46
+ it('should count tokens correctly', () => {
47
+ const { TokenCounter, encode } = require('../../src/llm');
48
+ const counter = new TokenCounter();
49
+ expect(typeof counter.countText).toBe('function');
50
+ expect(typeof counter.countMessages).toBe('function');
51
+ expect(encode('hello world')).toBeGreaterThan(0);
52
+ expect(encode('')).toBe(0);
53
+ });
54
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('Plugin Module', () => {
4
+ it('should export all expected symbols', () => {
5
+ const plugin = require('../../src/plugin');
6
+ expect(plugin.Plugin).toBeDefined();
7
+ expect(plugin.PluginType).toBeDefined();
8
+ expect(plugin.PluginManager).toBeDefined();
9
+ expect(plugin.resolvePluginPath).toBeDefined();
10
+ expect(plugin.scanPluginNames).toBeDefined();
11
+ expect(plugin.loadPluginFromPath).toBeDefined();
12
+ });
13
+
14
+ it('should have Plugin base class with default values', () => {
15
+ const { Plugin } = require('../../src/plugin');
16
+ const p = new Plugin();
17
+ expect(p.name).toBe('unnamed-plugin');
18
+ expect(p.version).toBe('1.0.0');
19
+ expect(p.priority).toBe(100);
20
+ expect(p.enabled).toBe(true);
21
+ expect(p.system).toBe(false);
22
+ });
23
+
24
+ it('should have PluginType constants', () => {
25
+ const { PluginType } = require('../../src/plugin');
26
+ expect(PluginType.AI).toBe('ai');
27
+ expect(PluginType.TOOLS).toBe('tools');
28
+ expect(PluginType.EXECUTOR).toBe('executor');
29
+ expect(PluginType.CAPABILITY).toBe('capability');
30
+ });
31
+
32
+ it('should create and configure PluginManager', () => {
33
+ const { PluginManager, PluginType } = require('../../src/plugin');
34
+ const framework = { getCwd: () => '/test', logger: { child: () => ({ debug: () => {}, info: () => {}, warn: () => {} }) } };
35
+ const pm = new PluginManager(framework);
36
+ expect(pm.count()).toBe(0);
37
+
38
+ const mockPlugin = { name: 'test', version: '1.0', enabled: true };
39
+ // PluginManager.register expects Plugin instance
40
+ expect(() => pm.register(mockPlugin)).toThrow('must be an instance of Plugin');
41
+ });
42
+ });
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('Tool Module', () => {
4
+ it('should export all expected symbols', () => {
5
+ const tool = require('../../src/tool');
6
+ expect(tool.ToolRegistry).toBeDefined();
7
+ expect(tool.ToolExecutor).toBeDefined();
8
+ expect(tool.ToolRouter).toBeDefined();
9
+ expect(tool.jsonSchemaToZod).toBeDefined();
10
+ expect(tool.toAISDKTool).toBeDefined();
11
+ expect(tool.INTENT_PATTERNS).toBeDefined();
12
+ });
13
+
14
+ it('should register and execute tools via ToolRegistry', async () => {
15
+ const { ToolRegistry } = require('../../src/tool');
16
+ const registry = new ToolRegistry();
17
+
18
+ const testTool = {
19
+ name: 'echo',
20
+ description: 'Echo back input',
21
+ execute: async (args) => ({ result: args.input }),
22
+ };
23
+
24
+ registry.register(testTool);
25
+ expect(registry.has('echo')).toBe(true);
26
+ expect(registry.get('echo').name).toBe('echo');
27
+ expect(registry.count()).toBe(1);
28
+
29
+ const result = await registry.execute('echo', { input: 'hello' });
30
+ expect(result.result).toBe('hello');
31
+ });
32
+
33
+ it('should throw on missing tool name', () => {
34
+ const { ToolRegistry } = require('../../src/tool');
35
+ const registry = new ToolRegistry();
36
+ expect(() => registry.register({ execute: async () => {} })).toThrow('Tool must have a name');
37
+ });
38
+
39
+ it('should get all tool names', () => {
40
+ const { ToolRegistry } = require('../../src/tool');
41
+ const registry = new ToolRegistry();
42
+ registry.register({ name: 'a', execute: async () => {} });
43
+ registry.register({ name: 'b', execute: async () => {} });
44
+ expect(registry.getNames()).toEqual(['a', 'b']);
45
+ });
46
+
47
+ it('should clear all tools', () => {
48
+ const { ToolRegistry } = require('../../src/tool');
49
+ const registry = new ToolRegistry();
50
+ registry.register({ name: 'a', execute: async () => {} });
51
+ registry.clear();
52
+ expect(registry.count()).toBe(0);
53
+ });
54
+
55
+ it('should throw on tool not found', async () => {
56
+ const { ToolRegistry } = require('../../src/tool');
57
+ const registry = new ToolRegistry();
58
+ await expect(registry.execute('nonexistent', {})).rejects.toThrow('not found');
59
+ });
60
+ });
package/tests/setup.js ADDED
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ // Test setup for Foliko
4
+ const path = require('path');
5
+
6
+ // Set test environment
7
+ process.env.NODE_ENV = 'test';
8
+
9
+ // Suppress logger output during tests
10
+ process.env.LOG_LEVEL = 'silent';
@@ -0,0 +1,58 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('Foliko Framework - Smoke Tests', () => {
4
+ it('should load main module exports', () => {
5
+ const foliko = require('../src/index');
6
+ expect(foliko.Framework).toBeDefined();
7
+ expect(foliko.Agent).toBeDefined();
8
+ expect(foliko.Plugin).toBeDefined();
9
+ expect(foliko.PluginManager).toBeDefined();
10
+ expect(foliko.ToolRegistry).toBeDefined();
11
+ expect(foliko.EventEmitter).toBeDefined();
12
+ expect(foliko.SkillManagerPlugin).toBeDefined();
13
+ expect(foliko.WorkflowPlugin).toBeDefined();
14
+ });
15
+
16
+ it('should create a Framework instance', () => {
17
+ const { Framework } = require('../src/index');
18
+ const framework = new Framework();
19
+ expect(framework).toBeDefined();
20
+ expect(framework.constructor.name).toBe('Framework');
21
+ });
22
+
23
+ it('should register and retrieve tools via ToolRegistry', () => {
24
+ const { ToolRegistry } = require('../src/index');
25
+ const registry = new ToolRegistry();
26
+
27
+ const testTool = {
28
+ name: 'test_tool',
29
+ description: 'A test tool',
30
+ execute: async () => ({ result: 'ok' }),
31
+ };
32
+
33
+ registry.register(testTool);
34
+ expect(registry.get('test_tool')).toBeDefined();
35
+ expect(registry.get('test_tool').name).toBe('test_tool');
36
+ });
37
+
38
+ it('should handle EventEmitter events', () => {
39
+ const { EventEmitter } = require('../src/index');
40
+ const emitter = new EventEmitter();
41
+ const events = [];
42
+ emitter.on('test', (msg) => events.push(msg));
43
+ emitter.emit('test', 'hello');
44
+ expect(events).toEqual(['hello']);
45
+ });
46
+
47
+ it('should load framework utilities', () => {
48
+ const { logger } = require('../src/common/logger');
49
+ expect(logger).toBeDefined();
50
+ expect(typeof logger.info).toBe('function');
51
+ });
52
+
53
+ it('should load core constants', () => {
54
+ const constants = require('../src/common/constants');
55
+ expect(constants).toBeDefined();
56
+ expect(constants.PROMPT_PRIORITY).toBeDefined();
57
+ });
58
+ });
@@ -0,0 +1,9 @@
1
+ const { defineConfig } = require('vitest/config');
2
+
3
+ module.exports = defineConfig({
4
+ test: {
5
+ globals: true,
6
+ setupFiles: ['./tests/setup.js'],
7
+ include: ['tests/**/*.test.js'],
8
+ },
9
+ });
package/cli/src/daemon.js DELETED
@@ -1,149 +0,0 @@
1
- /**
2
- * Foliko 后台服务入口
3
- * 使用 framework.bootstrap() 自动加载 .foliko/ 目录配置
4
- * 持续运行,不退出
5
- */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
-
10
- // 默认配置
11
- const DEFAULT_CONFIG = {
12
- model: 'MiniMax-M2.7',
13
- provider: 'minimax',
14
- baseURL: 'https://api.minimaxi.com/v1',
15
- apiKey: null,
16
- };
17
-
18
- // Provider 默认配置
19
- const PROVIDER_DEFAULTS = {
20
- minimax: {
21
- model: 'MiniMax-M2.7',
22
- baseURL: 'https://api.minimaxi.com/v1',
23
- },
24
- deepseek: {
25
- model: 'deepseek-chat',
26
- baseURL: 'https://api.deepseek.com/v1',
27
- },
28
- };
29
-
30
- // 解析命令行参数 --cwd <path>
31
- const cwdArg = process.argv.find((arg) => arg === '--cwd');
32
- const daemonCwd = cwdArg ? process.argv[process.argv.indexOf(cwdArg) + 1] : process.cwd();
33
-
34
- // 切换到工作目录
35
- if (daemonCwd !== process.cwd()) {
36
- process.chdir(daemonCwd);
37
- }
38
-
39
- // 日志文件
40
- const LOG_FILE = path.join(process.cwd(), '.foliko.log');
41
-
42
- // 覆盖 console.log,同时写日志和终端
43
- const originalLog = console.log;
44
- const originalError = console.error;
45
- console.log = (...args) => {
46
- const msg = `[${new Date().toISOString()}] ` + args.join(' ');
47
- fs.appendFileSync(LOG_FILE, msg + '\n');
48
- originalLog.apply(console, args);
49
- };
50
- console.error = (...args) => {
51
- const msg = `[${new Date().toISOString()}] ERROR ` + args.join(' ');
52
- fs.appendFileSync(LOG_FILE, msg + '\n');
53
- originalError.apply(console, args);
54
- };
55
-
56
- // 覆盖 process.stdout.write,只写日志(捕获框架日志)
57
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
58
- process.stdout.write = (chunk) => {
59
- fs.appendFileSync(LOG_FILE, chunk.toString());
60
- return originalStdoutWrite(chunk);
61
- };
62
-
63
- // 覆盖 process.stderr.write
64
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
65
- process.stderr.write = (chunk) => {
66
- fs.appendFileSync(LOG_FILE, chunk.toString());
67
- return originalStderrWrite(chunk);
68
- };
69
-
70
- // 环境变量已在 commands/daemon.js 中通过 spawn 传递
71
- const { Framework } = require('../../src');
72
-
73
- // 调试:打印接收到的环境变量
74
- console.log('[DEBUG] process.env.QQ_APP_ID =', process.env.QQ_APP_ID);
75
- console.log('[DEBUG] process.env.QQ_CLIENT_SECRET =', process.env.QQ_CLIENT_SECRET ? '***' + process.env.QQ_CLIENT_SECRET.slice(-6) : 'undefined');
76
- console.log('[DEBUG] process.env.FOLIKO_API_KEY =', process.env.FOLIKO_API_KEY ? '***' + process.env.FOLIKO_API_KEY.slice(-6) : 'undefined');
77
-
78
- /**
79
- * 获取环境变量配置
80
- */
81
- function getEnvConfig() {
82
- const provider = process.env.FOLIKO_PROVIDER || DEFAULT_CONFIG.provider;
83
- const providerDefaults = PROVIDER_DEFAULTS[provider] || PROVIDER_DEFAULTS.minimax;
84
-
85
- // 支持多种 API key 环境变量名
86
- let apiKey = process.env.FOLIKO_API_KEY || null;
87
- if (!apiKey) {
88
- // 根据 provider 查找对应的 API key
89
- const upperProvider = provider.toUpperCase().replace(/-/g, '_');
90
- apiKey = process.env[`${upperProvider}_API_KEY`] || null;
91
- }
92
-
93
- return {
94
- model: process.env.FOLIKO_MODEL || providerDefaults.model,
95
- provider: provider,
96
- baseURL: process.env.FOLIKO_BASE_URL || providerDefaults.baseURL,
97
- apiKey: apiKey,
98
- };
99
- }
100
-
101
- /**
102
- * 解析命令行参数(命令行参数优先于环境变量)
103
- */
104
- function parseArgs(args) {
105
- // 先获取环境变量配置(作为默认值)
106
- const envConfig = getEnvConfig();
107
- const options = { ...envConfig };
108
-
109
- return options;
110
- }
111
-
112
-
113
- async function main(args = process.argv.slice(2)) {
114
- console.log('[Foliko] 启动后台服务...');
115
- const options = parseArgs(args);
116
- // 创建框架
117
- const framework = new Framework({ debug: false });
118
-
119
- // 使用 bootstrap 自动加载所有默认插件
120
- await framework.bootstrap({
121
- agentDir: './.foliko',
122
- aiConfig: {
123
- provider: options.provider,
124
- model: options.model,
125
- apiKey: options.apiKey,
126
- baseURL: options.baseURL,
127
- },
128
- });
129
-
130
- console.log('[Foliko] 框架已就绪');
131
- console.log('[Foliko] 已加载插件:', framework.pluginManager.getAll().map((p) => p.name));
132
- console.log('[Foliko] 后台服务运行中 (PID: ' + process.pid + ')');
133
-
134
- // 优雅退出
135
- const shutdown = async () => {
136
- console.log('[Foliko] 正在关闭...');
137
- await framework.destroy();
138
- console.log('[Foliko] 已关闭');
139
- process.exit(0);
140
- };
141
-
142
- process.on('SIGINT', shutdown);
143
- process.on('SIGTERM', shutdown);
144
- }
145
-
146
- main().catch((err) => {
147
- console.log('[Foliko] 启动失败:', err.message);
148
- process.exit(1);
149
- });