foliko 1.0.84 → 1.0.85
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.
|
@@ -318,6 +318,7 @@ module.exports = function (Plugin) {
|
|
|
318
318
|
description: '获取页面HTML内容(注意:复杂页面会导致token超标,建议使用 page_structure 获取页面结构)',
|
|
319
319
|
inputSchema: z.object({
|
|
320
320
|
contentOnly: z.boolean().optional().describe('仅获取body内容'),
|
|
321
|
+
toMarkdown: z.boolean().optional().default(true).describe('是否将HTML转换为Markdown格式,默认开启'),
|
|
321
322
|
pageId: z.string().optional().describe('页面ID,默认当前页面'),
|
|
322
323
|
}),
|
|
323
324
|
execute: async (args) => {
|
|
@@ -325,19 +326,116 @@ module.exports = function (Plugin) {
|
|
|
325
326
|
? this.pages.get(args.pageId)
|
|
326
327
|
: this.getCurrentPage();
|
|
327
328
|
|
|
328
|
-
|
|
329
|
+
let html = args.contentOnly
|
|
329
330
|
? await page.evaluate(() => document.body ? document.body.innerHTML : '')
|
|
330
331
|
: await page.content();
|
|
331
332
|
|
|
333
|
+
let content = html;
|
|
334
|
+
let isMarkdown = false;
|
|
335
|
+
|
|
336
|
+
// 如果需要转换为 Markdown(默认为 true)
|
|
337
|
+
const shouldConvertToMarkdown = args.toMarkdown !== false;
|
|
338
|
+
if (shouldConvertToMarkdown) {
|
|
339
|
+
content = this._htmlToMarkdown(html);
|
|
340
|
+
isMarkdown = true;
|
|
341
|
+
}
|
|
342
|
+
|
|
332
343
|
return {
|
|
333
344
|
success: true,
|
|
334
|
-
html: html,
|
|
335
|
-
|
|
345
|
+
html: isMarkdown ? undefined : html,
|
|
346
|
+
markdown: isMarkdown ? content : undefined,
|
|
347
|
+
content: content,
|
|
348
|
+
length: content.length,
|
|
349
|
+
isMarkdown: isMarkdown,
|
|
336
350
|
url: page.url()
|
|
337
351
|
};
|
|
338
352
|
}
|
|
339
353
|
},
|
|
340
354
|
|
|
355
|
+
// HTML 转 Markdown 辅助方法
|
|
356
|
+
_htmlToMarkdown(html) {
|
|
357
|
+
if (!html) return '';
|
|
358
|
+
|
|
359
|
+
// 移除脚本和样式标签内容
|
|
360
|
+
let markdown = html
|
|
361
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
362
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
363
|
+
.replace(/<!--[\s\S]*?-->/g, '');
|
|
364
|
+
|
|
365
|
+
// 标题转换
|
|
366
|
+
markdown = markdown.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, '# $1\n\n');
|
|
367
|
+
markdown = markdown.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, '## $1\n\n');
|
|
368
|
+
markdown = markdown.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, '### $1\n\n');
|
|
369
|
+
markdown = markdown.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, '#### $1\n\n');
|
|
370
|
+
markdown = markdown.replace(/<h5[^>]*>([\s\S]*?)<\/h5>/gi, '##### $1\n\n');
|
|
371
|
+
markdown = markdown.replace(/<h6[^>]*>([\s\S]*?)<\/h6>/gi, '###### $1\n\n');
|
|
372
|
+
|
|
373
|
+
// 换行和段落
|
|
374
|
+
markdown = markdown.replace(/<br\s*\/?>/gi, '\n');
|
|
375
|
+
markdown = markdown.replace(/<\/p>/gi, '\n\n');
|
|
376
|
+
markdown = markdown.replace(/<\/div>/gi, '\n');
|
|
377
|
+
markdown = markdown.replace(/<\/li>/gi, '\n');
|
|
378
|
+
|
|
379
|
+
// 列表
|
|
380
|
+
markdown = markdown.replace(/<ul[^>]*>/gi, '\n');
|
|
381
|
+
markdown = markdown.replace(/<ol[^>]*>/gi, '\n');
|
|
382
|
+
markdown = markdown.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '- $1\n');
|
|
383
|
+
|
|
384
|
+
// 链接和图片
|
|
385
|
+
markdown = markdown.replace(/<a[^>]*href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, '[$2]($1)');
|
|
386
|
+
markdown = markdown.replace(/<img[^>]*src=["']([^"']*)["'][^>]*alt=["']([^"']*)["'][^>]*\/?>/gi, '');
|
|
387
|
+
markdown = markdown.replace(/<img[^>]*alt=["']([^"']*)["'][^>]*src=["']([^"']*)["'][^>]*\/?>/gi, '');
|
|
388
|
+
markdown = markdown.replace(/<img[^>]*src=["']([^"']*)["'][^>]*\/?>/gi, '');
|
|
389
|
+
|
|
390
|
+
// 粗体和斜体
|
|
391
|
+
markdown = markdown.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/gi, '**$1**');
|
|
392
|
+
markdown = markdown.replace(/<b[^>]*>([\s\S]*?)<\/b>/gi, '**$1**');
|
|
393
|
+
markdown = markdown.replace(/<em[^>]*>([\s\S]*?)<\/em>/gi, '*$1*');
|
|
394
|
+
markdown = markdown.replace(/<i[^>]*>([\s\S]*?)<\/i>/gi, '*$1*');
|
|
395
|
+
|
|
396
|
+
// 代码
|
|
397
|
+
markdown = markdown.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, '`$1`');
|
|
398
|
+
markdown = markdown.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, '```\n$1\n```');
|
|
399
|
+
|
|
400
|
+
// 表格 (简单支持)
|
|
401
|
+
const tableRegex = /<table[^>]*>([\s\S]*?)<\/table>/gi;
|
|
402
|
+
markdown = markdown.replace(tableRegex, (match, tableContent) => {
|
|
403
|
+
let mdTable = '';
|
|
404
|
+
const rows = tableContent.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi) || [];
|
|
405
|
+
rows.forEach((row, idx) => {
|
|
406
|
+
const cells = row.match(/<t[hd][^>]*>([\s\S]*?)<\/t[hd]>/gi) || [];
|
|
407
|
+
const mdRow = cells.map(cell => {
|
|
408
|
+
return cell.replace(/<t[hd][^>]*>/, '').replace(/<\/t[hd]>/, '').trim();
|
|
409
|
+
}).join(' | ');
|
|
410
|
+
if (idx === 0) {
|
|
411
|
+
mdTable += mdRow + '\n' + mdRow.split('|').map(() => '---').join('|') + '\n';
|
|
412
|
+
} else {
|
|
413
|
+
mdTable += mdRow + '\n';
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
return mdTable + '\n';
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// 移除所有剩余 HTML 标签
|
|
420
|
+
markdown = markdown.replace(/<[^>]+>/g, '');
|
|
421
|
+
|
|
422
|
+
// 清理实体编码
|
|
423
|
+
markdown = markdown
|
|
424
|
+
.replace(/ /g, ' ')
|
|
425
|
+
.replace(/&/g, '&')
|
|
426
|
+
.replace(/</g, '<')
|
|
427
|
+
.replace(/>/g, '>')
|
|
428
|
+
.replace(/"/g, '"')
|
|
429
|
+
.replace(/'/g, "'")
|
|
430
|
+
.replace(/ /g, ' ')
|
|
431
|
+
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec));
|
|
432
|
+
|
|
433
|
+
// 清理多余空行
|
|
434
|
+
markdown = markdown.replace(/\n{3,}/g, '\n\n').trim();
|
|
435
|
+
|
|
436
|
+
return markdown;
|
|
437
|
+
},
|
|
438
|
+
|
|
341
439
|
// 获取页面结构(优化版 - 返回精简的结构化数据,避免token超标)
|
|
342
440
|
page_structure: {
|
|
343
441
|
description: '获取页面关键结构信息(推荐使用),返回可交互元素的精简结构,避免获取完整HTML导致的token超标问题',
|
package/package.json
CHANGED
|
@@ -345,6 +345,14 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
345
345
|
|
|
346
346
|
reload(framework) {
|
|
347
347
|
this._framework = framework;
|
|
348
|
+
// 重新扫描所有已加载插件的 tools
|
|
349
|
+
this._extensions.clear();
|
|
350
|
+
const plugins = framework.pluginManager.getAll();
|
|
351
|
+
for (const { instance: plugin } of plugins) {
|
|
352
|
+
this._scanPluginTools(plugin);
|
|
353
|
+
}
|
|
354
|
+
// 刷新所有 Agent 的扩展提示词
|
|
355
|
+
this._refreshAllAgentsExtPrompt(framework);
|
|
348
356
|
}
|
|
349
357
|
|
|
350
358
|
async uninstall(framework) {
|
|
@@ -272,9 +272,9 @@ class SessionPlugin extends Plugin {
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
// 如果会话数超过限制,清理最老的
|
|
275
|
-
if (this._sessions.size >= this.config.maxSessions) {
|
|
276
|
-
|
|
277
|
-
}
|
|
275
|
+
// if (this._sessions.size >= this.config.maxSessions) {
|
|
276
|
+
// this._cleanupLRU()
|
|
277
|
+
// }
|
|
278
278
|
|
|
279
279
|
const session = {
|
|
280
280
|
id,
|
package/src/core/agent-chat.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const { EventEmitter } = require('../utils/event-emitter');
|
|
7
7
|
const { logger } = require('../utils/logger');
|
|
8
|
-
const { generateText,
|
|
8
|
+
const { generateText, stepCountIs } = require('ai');
|
|
9
9
|
const { prepareMessagesForAPI } = require('../utils');
|
|
10
10
|
|
|
11
11
|
// 模型上下文限制表(留 15-20% 余量给 system prompt 和输出)
|
|
@@ -708,13 +708,8 @@ ${truncatedContent}${truncatedNote}
|
|
|
708
708
|
throw new Error('AI client not configured.');
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
-
// 准备传给 agent
|
|
712
|
-
|
|
713
|
-
messages: this._messages,
|
|
714
|
-
reasoning: 'all',
|
|
715
|
-
toolCalls: 'none',
|
|
716
|
-
});
|
|
717
|
-
|
|
711
|
+
// 准备传给 agent 的消息
|
|
712
|
+
// ToolLoopAgent 会自动处理消息格式,不需要手动 prune
|
|
718
713
|
const agent = new ToolLoopAgent({
|
|
719
714
|
model: this._aiClient,
|
|
720
715
|
instructions: this._systemPrompt,
|
|
@@ -725,7 +720,7 @@ ${truncatedContent}${truncatedNote}
|
|
|
725
720
|
|
|
726
721
|
// 使用 runWithContext 让工具执行时能获取 sessionId
|
|
727
722
|
const result = await framework.runWithContext(context, async () => {
|
|
728
|
-
return agent.generate({ messages:
|
|
723
|
+
return agent.generate({ messages: this._messages, ...this.providerOptions });
|
|
729
724
|
});
|
|
730
725
|
|
|
731
726
|
this._messages.push(...result.response.messages);
|
|
@@ -805,14 +800,8 @@ ${truncatedContent}${truncatedNote}
|
|
|
805
800
|
throw new Error('AI client not configured.');
|
|
806
801
|
}
|
|
807
802
|
|
|
808
|
-
// 准备传给 agent
|
|
809
|
-
//
|
|
810
|
-
const messagesForAgent = pruneMessages({
|
|
811
|
-
messages: this._messages,
|
|
812
|
-
reasoning: 'all',
|
|
813
|
-
toolCalls: 'none', // 保留所有 tool calls,避免丢失工具执行历史
|
|
814
|
-
});
|
|
815
|
-
|
|
803
|
+
// 准备传给 agent 的消息
|
|
804
|
+
// ToolLoopAgent 会自动处理消息格式,不需要手动 prune
|
|
816
805
|
const agent = new ToolLoopAgent({
|
|
817
806
|
model: this._aiClient,
|
|
818
807
|
instructions: this._systemPrompt,
|
|
@@ -824,7 +813,7 @@ ${truncatedContent}${truncatedNote}
|
|
|
824
813
|
// 使用 runWithContext 让工具执行时能获取 sessionId(支持并行)
|
|
825
814
|
const result = await framework.runWithContext(context, async () => {
|
|
826
815
|
return agent.stream({
|
|
827
|
-
messages:
|
|
816
|
+
messages: this._messages,
|
|
828
817
|
...this.providerOptions,
|
|
829
818
|
});
|
|
830
819
|
});
|