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
- const html = args.contentOnly
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
- length: html.length,
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, '![$2]($1)');
387
+ markdown = markdown.replace(/<img[^>]*alt=["']([^"']*)["'][^>]*src=["']([^"']*)["'][^>]*\/?>/gi, '![$1]($2)');
388
+ markdown = markdown.replace(/<img[^>]*src=["']([^"']*)["'][^>]*\/?>/gi, '![]($1)');
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(/&nbsp;/g, ' ')
425
+ .replace(/&amp;/g, '&')
426
+ .replace(/&lt;/g, '<')
427
+ .replace(/&gt;/g, '>')
428
+ .replace(/&quot;/g, '"')
429
+ .replace(/&#39;/g, "'")
430
+ .replace(/&nbsp;/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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.84",
3
+ "version": "1.0.85",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -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
- this._cleanupLRU()
277
- }
275
+ // if (this._sessions.size >= this.config.maxSessions) {
276
+ // this._cleanupLRU()
277
+ // }
278
278
 
279
279
  const session = {
280
280
  id,
@@ -5,7 +5,7 @@
5
5
 
6
6
  const { EventEmitter } = require('../utils/event-emitter');
7
7
  const { logger } = require('../utils/logger');
8
- const { generateText, pruneMessages, stepCountIs, convertToModelMessages } = require('ai');
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 的消息(移除 reasoning 节省 token,但保留 toolCalls)
712
- const messagesForAgent = pruneMessages({
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: messagesForAgent, ...this.providerOptions });
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 的消息(不做提前修剪,由 prepareStep 处理)
809
- // 但需要移除 reasoning 来节省 token
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: messagesForAgent,
816
+ messages: this._messages,
828
817
  ...this.providerOptions,
829
818
  });
830
819
  });