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
|
@@ -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
|
+
};
|
package/src/utils/download.js
CHANGED
|
@@ -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('
|
|
9
|
+
const { logger } = require('../common/logger');
|
|
10
10
|
class FileDownloader {
|
|
11
11
|
constructor(options = {}) {
|
|
12
12
|
this.timeout = options.timeout || 30000;
|
package/src/utils/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// 日志系统
|
|
6
|
-
const { Logger, logger, LOG_LEVELS } = require('
|
|
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('
|
|
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('
|
|
36
|
+
} = require('../common/errors');
|
|
37
37
|
|
|
38
38
|
// 重试策略
|
|
39
|
-
const { withRetry, retryable, isNetworkError, PRESETS } = require('
|
|
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('
|
|
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('
|
|
541
|
+
editDiff: require('../common/diff'),
|
|
542
542
|
};
|
|
@@ -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,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
|
+
});
|
package/vitest.config.js
ADDED
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
|
-
});
|